[ep9의 학습목표]
1. 파이프를 이용한 IPC 기법
2. 이름 없는 파이프를 이용한 통신 프로그램 작성
3. 이름 있는 파이프를 이용한 통신 프로그램 작성
ㅁ파이프('|'): 두 프로세스 사이에서 한 방향으로 통신할 수 있도록 지원하는 것
파이프는 이름 없는 파이프(익명 파이프)와 이름 있는 파이프로 구분된다
셸에서 파이프 기능은 한 명령의 표준 출력을 다음 명령에서 표준 입력으로 받아 수행하는 것을 의미한다
cat test.c | more # 앞에 있는 명령인 cat test.c의 표준 출력을 다음 명령인 more의 표준 입력으로 사용
위 예를 실행하면 test.c를 화면 단위로 출력한다
ㅁ이름 없는 파이프(pipe)
• 특별한 수식어 없이 그냥 파이프라고 하면 일반적으로 이름 없는 파이프(익명 파이프)를 의미한다
• 이름 없는 파이프는 부모-자식 프로세스 간에 통신을 할 수 있게 해 준다
• 부모 프로세스에서 fork() 함수를 통해 자식 프로세스를 생성하고, 부모 프로세스와 자식 프로세스 간에 통신하는 것
• 따라서 ‘부모 프로세스 → 자식 프로세스’ 또는 ‘자식 프로세스 → 부모 프로세스’ 중 한 방향을 선택해야 한다
※ 파이프를 이용해 양방향 통신을 원할 경우 파이프를 2개 생성해야 한다
다른 프로세스와 통신하기 위해 파이프를 생성
ㅇ FILE* popen(const char* command, const char* type): 파이프 만들기
- command : 셸 명령
- type : 'r'(읽기 전용) 또는 'w'(쓰기 전용)
내부적으로 fork() 함수를 실행해 자식 프로세스를 만들고, command에서 지정한 명령을 exec() 함수로 실행해 자식 프로세스가 수행하도록 한다
자식 프로세스와 파이프를 만들고 mode의 값에 따라 표준 입출력을 연결
성공하면 파일 포인터(fp)를 반환하고, 실패하면 NULL 포인터를 반환한다
(파일 입출력 함수에서 이 파일 포인터를 사용하면 파이프를 읽거나 쓸 수가 있다)
#include <stdio.h>
#include <stdlib.h>
int main( ) {
FILE* fp;
int a;
// 'w' 모드를 사용해 쓰기 전용 파이프를 생성하고 자식 프로세스는 wc -l 명령을 수행하도록 함
// (wc -l은 입력되는 데이터의 행 수를 출력하는 명령)
fp = popen("wc -l", "w");
if (fp == NULL) {
fprintf(stderr, "popen failed\n");
exit(1);
}
// 부모 프로세스에서는 반복문을 사용해 문자열을 파이프로 출력
// 앞서 언급했듯 이 자식 프로세스는 파이프로 입력되는 문자열을 읽어서 wc -l 명령을 수행
for (a = 0; a < 100; a++) {
fprintf(fp, "test line\n");
}
pclose(fp);
return 0;
}
파일 입출력 함수처럼 인자로 지정한 파이프를 닫음
ㅇ int pclose(FILE* stream): 파이프 닫기
관련된 waitpid() 함수를 수행하며, 자식 프로세스들이 종료하기를 기다렸다가 리턴
성공하면 자식 프로세스의 종료 상태를 반환하고, 실패하면 -1을 반환
#include <stdio.h>
#include <stdlib.h>
int main( ) {
FILE* fp;
char buf[256];
fp = popen("date", "r"); // 자식 프로세스에서는 data 명령을 수행
if (fp == NULL) {
fprintf(stderr, "popen failed\n");
exit(1);
}
if (fgets(buf, sizeof(buf), fp) == NULL) { // 자식 프로세스가 기록한 데이터를 읽고 저장한다
fprintf(stderr, "No data from pipe!\n");
exit(1);
}
printf("line : %s\n", buf); // 자식 프로세스가 기록한 데이터를 출력한다
pclose(fp);
return 0;
}
// 실행 결과: 현재 날짜와 시각이 출력된다
// 자식 프로세스가 실행한 date 명령의 결과를 부모 프로세스가 읽어서 출력
ㅇ int pipe(int pipefd[2]): 파이프 만들기
- pipefd[2] : 파이프로 사용할 파일 기술자(fd 2개)
인자로 크기가 2인 정수형 배열을 받고, 이 배열에 파일 기술자(fd) 2개를 저장한다
pipefd[0]은 '읽기 전용'으로 열고 pipefd[1]은 '쓰기 전용'으로 실행
성공하면 0을 반환하고, 실패하면 –1을 반환
[pipe() 함수로 통신하는 과정]
① pipe() 함수를 호출해 파이프에 사용할 파일 기술자(fd)를 얻음
파이프도 파일의 일종이므로 파일(파이프)을 읽고 쓸 수 있는 파일 기술자가 필요한데, 이를 pipe() 함수가 생성
② fork() 함수를 수행해 자식 프로세스를 생성
이때 pipe() 함수에서 생성한 파일 기술자(fd)도 자식 프로세스로 복사 (같은 파일 기술자를 부모 프로세스와 자식 프로세스 모두 가지고 있음)
③ 파이프는 단방향 통신이므로 통신 방향을 결정
부모 프로세스에서 자식 프로세스로 통신할 수 있도록 파일 기술자들이 정리
부모 프로세스가 fd[1]에 쓴 내용을 자식 프로세스가 fd[0]에서 읽는다
만약 파이프의 쓰기 부분이 닫혀 있다면 파이프에서 읽으려고 할 때 0이나 EOF가 리턴
파이프의 읽기 부분이 닫혀 있다면 파이프에 쓰려고 할 때 SIGPIPE 시그널이 발생
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main( ) {
int fd[2];
pid_t pid;
char buf[257];
int len, status;
// pipe() 함수를 사용해 파이프를 생성, pipe() 함수의 인자로는 파일 기술자(fd)를 저장할 배열을 지정
if (pipe(fd) == -1) {
perror("pipe");
exit(1);
}
switch (pid = fork()) { // fork() 함수를 사용해 자식 프로세스를 생성
case -1 :
perror("fork");
exit(1);
break;
case 0 : /* 자식 프로세스의 동작 부분 */
close(fd[1]); // fd[1]을 닫는다는 건 자식 프로세스에서 파이프를 읽기용으로 사용하겠다는 의미
write(1, "Child Process:", 15);
len = read(fd[0], buf, 256); // 파이프 입력 부분인 fd[0]에서 문자열을 읽어들이고
write(1, buf, len); // write() 함수를 사용해 화면에 출력
close(fd[0]);
break;
default : /* 부모 프로세스의 동작 부분 */
close(fd[0]); // fd[0]을 닫는다는 건 파이프를 출력용으로 사용하겠다는 의미
write(fd[1], "Test Message\n", 14); // fd[1]로 문자열을 출력
close(fd[1]);
waitpid(pid, &status, 0); // 자식 프로세스가 종료하기를 기다림
break;
}
return 0;
}
// 실행 결과: 부모 프로세스가 출력한 문자열을 자식 프로세스가 받아 출력하고 있음을 알 수 있음
(pipe() 함수로 명령 실행하기 예제 코드)
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main( ) {
int fd[2];
pid_t pid;
if (pipe(fd) == -1) { // 파이프를 생성
perror("pipe");
exit(1);
}
// fork() 함수를 사용해 자식 프로세스를 생성
// ps 명령의 결과는 기본으로 표준 출력으로 출력되고, grep 명령은 표준 입력을 통해 입력받음
// 따라서 부모 프로세스와 자식 프로세스 간의 통신이 표준 입출력 대신 파이프를 통해 이루어지도록 만들어야 함
switch (pid = fork()) {
case -1 :
perror("fork");
exit(1);
break;
case 0 : /* 자식 프로세스의 동작 부분 */
// 자식 프로세스가 할 일은 부모 프로세스가 파이프로 출력하는 ps -ef 명령의 결과를 받아 grep ssh 명령을 수행하는 것
// 따라서 파이프의 출력 부분이 필요 없으므로 fd[1]을 닫음
close(fd[1]);
// fd[0]의 값이 0이 아니면, 즉 표준 입력이 아니면 fd[0]의 값을 표준 입력으로 복사한 후 fd[0]을 닫음
// 이제 자식 프로세스에서는 표준 입력을 fd[0]이 가리키는 파이프에서 읽음
if (fd[0] != 0) {
dup2(fd[0], 0);
close(fd[0]);
}
// 자식 프로세스가 grep 명령을 exec() 함수로 호출, 이렇게 하면 grep 명령은 표준 입력을 통해 데이터를 읽어들이려 함
// 이미 dup2()를 이용해 표준 입력으로 파이프의 입력 파일 기술자를 복사했으므로 결과적으로 파이프를 통해 데이터를 읽어들임
execlp("grep", "grep", "ssh", (char*)NULL);
exit(1);
break;
default : // 부모 프로세스의 동작을 살펴보면 자식 프로세스와 크게 다를 것이 없음을 알 수 있음
close(fd[0]); // 우선 파이프의 입력 부분이 필요없으므로 닫음
// 파이프의 출력 부분을 표준 출력으로 복사
// 따라서 부모 프로세스에서 표준 출력으로 무언가를 출력하면 파이프를 통해 출력
if (fd[1] != 1) {
dup2(fd[1], 1);
close(fd[1]);
}
// exec() 함수를 사용해 ps -ef 명령을 실행, ps -ef 명령은 기본으로 표준 출력으로 출력하므로 결과가 파이프로 출력
// 이 출력 결과를 자식 프로세스가 읽어들임
execlp("ps", "ps", "-ef", (char*)NULL);
wait(NULL);
break;
}
return 0;
}
// 실행 결과: ps –ef | grep ssh 명령이 실행
// 즉, 부모 프로세스에서 자식 프로세스로 ps–ef 명령의 실행 결과가 전달되었고 자식 프로세스는 여기서 grep ssh 명령을 실행
(양방향 통신하기 예제 코드)
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int fd1[2], fd2[2]; // 파이프 2개를 생성하기 위해 파일 기술자(fd) 배열 2개를 선언
pid_t pid;
char buf[257];
int len, status;
if (pipe(fd1) == -1) { // 파이프를 하나 생성하고 생성된 파일 기술자(fd1)는 부모 프로세스에서 자식 프로세스로 데이터를 보낼 때 사용
perror("pipe");
exit(1);
}
if (pipe(fd2) == -1) { // 다른 파이프를 하나 생성, 생성된 파일 기술자(fd2)는 자식 프로세스에서 부모 프로세스로 데이터를 보낼 때 사용
perror("pipe");
exit(1);
}
switch (pid = fork()) {
case -1 :
perror("fork");
exit(1);
break;
case 0 : /* 자식 프로세스 동작 부분 */
close(fd1[1]); // 자식 프로세스는 부모 프로세스에서 오는 데이터를 읽는 데 사용할 fd1[0]을 남겨 두고 fd1[1]을 닫음
close(fd2[0]); // 또한 부모 프로세스로 데이터를 보내는 데 사용할 fd2[1]을 남겨두고 fd2[0]을 닫음
len = read(fd1[0], buf, 256);
write(1, "Child Process:", 15);
write(1, buf, len); // 읽은 데이터를 화면으로 출력
strcpy(buf, "Good\n");
write(fd2[1], buf, strlen(buf));
break;
default : /* 부모 프로세스 동작 부분 */
// 부모 프로세스는 자식 프로세스와 반대임
close(fd1[0]); // 자식 프로세스에 데이터를 보내는 데 사용할 fd1[1]을 남겨두고
close(fd2[1]); // 자식 프로세스에서 오는 데이터를 읽는 데 사용할 fd2[0]을 남겨둠
write(fd1[1], "Hello\n", 6); // 부모 프로세스가 쓴 데이터(Hello)를 자식 프로세스가 read() 함수로 읽어옴
len = read(fd2[0], buf, 256); // 부모 프로세스에 대한 응답으로 자식 프로세스가 쓴 데이터(Good)를 부모 프로세스가 읽음
write(1, "Parent Process:", 15);
write(1, buf, len); // 읽은 데이터를 화면으로 출력
waitpid(pid, &status, 0);
break;
}
return 0;
}
// 실행 결과: 부모 프로세스와 자식 프로세스가 데이터를 주고받았음을 알 수 있음
// 파일 기술자를 여닫는 과정이 다소 복잡하지만 이와 같이 파이프를 2개 생성하면 양방향 통신이 가능
+ 파이프 추가정보
write시 파이프에 충분한 공간이 있으면 파이프에 저장
공간이 없으면 다른 프로세스에 의해 자료가 읽혀져 파이프에 충분한 공간이 마련될 때까지 수행이 일시 중단
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int count;
void alrm_action(int);
int main() {
int p[2];
int pipe_size;
char c = 'x';
static struct sigaction act;
act.sa_handler = alrm_action;
sigfillset(&(act.sa_mask));
if (pipe(p) == -1) {
perror("pipe call");
exit(1);
}
pipe_size = fpathconf(p[0], _PC_PIPE_BUF);
printf("Maximum size of write to pipe : %d bytes\n", pipe_size);
sigaction(SIGALRM, &act, NULL);
while (1) {
alarm(5);
write(p[1], &c, 1);
if ((++count % 1024) == 0) {
printf("%d characters in pipe\n", count);
}
}
return 0;
}
void alrm_action(int signo) {
printf("write blocked after %d characters\n", count);
exit(0);
}
• 쓰기전용 파일기술자(fd)를 닫았을 때:
- 자료를 쓰기 위해 해당 파이프를 개방한 다른 프로세스가 아직 존재하면 OK
- 쓰기 프로세스가 더 이상 없으면, 그 파이프로부터 자료를 읽으려는 프로세스를 깨우고 0을 반환. 파일의 끝에 도달한 것 같은 효과를 발생
• 읽기전용 파일기술자(fd)를 닫았을 때:
- 자료를 읽기 위해 해당 파이프를 개방한 다른 프로세스가 존재하면 OK
- 없으면 자료 쓰기를 기다리는 모든 프로세스는 커널로부터 SIGPIPE 시그널을 받고 -1로 복귀. 오류발생
• 봉쇄되지 않는 read/write
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#define MSGSIZE 6
int parent(int*);
int child (int*);
char* msg1 = "hello";
char* msg2 = "bye!!";
int main() {
int pfd[2];
if (pipe(pfd) == -1) {
perror("pipe call");
}
if (fcntl(pfd[0], F_SETFL, O_NONBLOCK) == -1) {
perror("fcntl call");
}
switch (fork()) {
case -1 : perror("fork call");
case 0 : child(pfd);
default : parent(pfd);
}
return 0;
}
int parent(int p[2]) {
int nread;
char buf[MSGSIZE];
close (p[1]);
for (;;) {
switch (nread = read(p[0], buf, MSGSIZE)) {
case -1:
if (errno == EAGAIN) {
printf("(pipe empty)\n");
sleep(1);
break;
}
case 0:
printf("End of conversation\n");
exit(0);
default:
printf("MSG = %s\n", buf);
}
}
}
int child(int p[2]) {
int count;
close(p[0]);
for (count = 0; count < 3; count++) {
write(p[1], msg1, MSGSIZE);
sleep(3);
}
write(p[1], msg2, MSGSIZE);
exit(0);
}
- if (fcntl(filedes, F_SETFL, O_NONBLOCK) == -1) perror
- filedes가 파이프에 대한 쓰기 전용 파일기술자이면, 파이프에 대한 write는 파이프가 완전히 차 있더라도 봉쇄되지 않음. 대신 write는 1로 복귀하고 errno를 EAGAIN으로 지정
- 파이프에 대한 읽기 전용 파일기술자라면 즉시-1로 복귀. Errno는 EAGAIN으로 지정
ㅇ int select (int nfds, fd_set* readfs, fd_set* writefs, fd_set* errorfs, struct timeval* timeout): 부모프로세스가 서버프로세스로 동작하고, 자신과 통신하는 클라이언트(자식) 프로세스를 임의의 수만큼 가지는 경우 사용
- nfds : 서버가 잠재적 흥미를 가지는 파일기술자의 수
0(stdin), 1(stdout), 2(stderr)는 default, 두 개의 파일을 더 개방하면 nfds = 5
fd_set으로 정의된 인수들은 비트마스크, 각 비트가 하나의 파일기술자를 나타낸다. (한 비트가 켜져 있으면 해당 파일 기술자에 대한 흥미를 나타냄)
- readfs: 읽을 만한 가치가 있는 것이 있는가?
- writefs: 임의의 주어진 파일기술자가 쓰기를 받아들일 준비가 되어 있는가?
- errorfs: 주어진 파일기술자 중 하나라도 오류가 발생했는가?
/* fdset이 가리키는 마스크를 초기화 */
void FD_ZERO(fd_set* fdset);
/* fdset가 가리키는 마스크 내의 비트, fd를 1로 설정 */
void FD_SET(int fd, fd_set* fdset);
/* fdset가 가리키는 마스크 내의 비트, fd가 설정되어 있는가?
int FD_ISSET(int fd, fd_set* fdset);
/* fdset가 가리키는 마스크 내의 비트, fd를 0으로 설정
void FD_CLR(int fd, fd_set* fdset);
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/wait.h>
#define MSGSIZE 6
char* msg1 = "hello";
char* msg2 = "bye!!";
void parent(int p[3][2]);
int child(int[]);
int main() {
int pip[3][2];
int i;
for (i = 0; i < 3; i++) {
if (pipe(pip[i]) == -1) {
perror("pipe call");
}
switch(fork()) {
case -1 :
perror("fork call");
case 0 :
child(pip[i]);
}
}
parent(pip);
exit(0);
return 0;
}
void parent(int p[3][2]) {
char buf[MSGSIZE], ch;
fd_set set, master;
int i;
for (i = 0; i < 3; i++) {
close(p[i][1]);
}
FD_ZERO(&master);
FD_SET(0, &master);
for (i = 0; i < 3; i++) {
FD_SET(p[i][0], &master);
}
while (set = master, select(p[2][0] + 1, &set, NULL, NULL, NULL) > 0) {
if (FD_ISSET(0, &set)) {
printf("From standard input ...");
read(0, &ch, 1);
printf("%c\n", ch);
}
for (i = 0; i < 3; i++) {
if (FD_ISSET(p[i][0], &set)) {
if (read(p[i][0], buf, MSGSIZE) > 0) {
printf("Message from child %d\n", i);
printf("MSG=%s\n", buf);
}
}
}
if (waitpid (-1, NULL, WNOHANG) == -1) {
return;
}
}
}
int child(int p[2]) {
int count;
close (p[0]);
for (count = 0; count < 2; count++) {
write(p[1], msg1, MSGSIZE);
sleep(getpid() % 4);
}
write(p[1], msg2, MSGSIZE);
exit(0);
}
Timeout은 struct timeval에 대한 포인터
#include <sys/time.h>
struct timeval {
long tv_sec;
long tv_usec;
}
포인터가 NULL이면 select는 흥미 있는 일이 일어날 때까지 봉쇄. 만일 timeout이 0초를 포함하는 구조를 가리키면 즉각 복귀. 0이 아닌 값을 포함하고 있으면 지정된 시간 후에 복귀
select의 반환값은 실패 시 -1, time-out에는 0, 이외에는 흥미 있는 파일기술자의 수를 나타내는 정수를 반환
int fd1, fd2;
fd_set readset;
fd1 = open(“file1”, O_RDONLY);
fd2 = open(“file2”, O_RDONLY);
FD_ZERO(&readset);
FD_SET(fd1, &readset);
FD_SET(fd2, &readset);
switch(select(5, &readset, NULL, NULL, NULL)) {
.
.
.
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
ㅁ이름 있는 파이프(FIFO)
부모-자식 프로세스 관계가 아닌 독립적인 프로세스들이 파이프를 이용하려면 파일처럼 이름이 있어야 한다
이름 있는 파이프는 특수 파일의 한 종류로, FIFO라고도 한다
※ 모든 프로세스가 이름 있는 파이프를 이용해 통신할 수 있다
mknod 명령은 FIFO 파일뿐만 아니라 특수 파일도 생성하는 명령이다
여기에 -m 옵션은 새로 생성되는 FIFO 파일의 접근 권한을 지정해 준다 (이 옵션을 생략하면 umask 값에 따라 기본 권한을 설정)
ㅇ int mknod(const char* pathname, mode_t mode, dev_t dev): 특수 파일 생성
- pathname : 특수 파일을 생성할 경로
- mode : 특수 파일의 종류와 접근 권한 지정
- dev : 블록/문자 장치 설정값
수행에 성공하면 0을, 실패하면 –1을 반환
<mode에 지정하는 특수 파일의 종류>
• S_IFIFO : FIFO 특수 파일
• S_IFCHR : 문자 장치 특수 파일
• S_IFBLK : 블록 장치 특수 파일
• S_IFSOCK : 유닉스 도메인 소켓 파일
• S_IFREG : 일반 파일
(세번째 인자인 dev는 생성하려는 특수 파일이 블록 장치 특수 파일이나 문자 장치 특수 파일일 때만 의미가 있다)
ㅇ int mkfifo(const char* pathname, mode_t mode): FIFO 파일 생성
- pathname : FIFO 파일을 생성할 경로
- mode : 접근 권한 지정
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main( ) {
// mknod() 함수로 파일의 종류를 S_IFIFO로 지정 (접근 권한은 0644로)
if (mknod("HAN-FIFO", S_IFIFO | 0644, 0) == -1) {
perror("mknod");
exit(1);
}
if (mkfifo("BIT-FIFO", 0644) == -1) {
perror("mkfifo");
exit(1);
}
return 0;
}
// 실행 결과: FIFO 파일이 생성
[Server-Client 프로그램 예제]
// 서버 프로그램 코드
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
int pd, n;
char msg[] = "Hello, FIFO";
printf("Server =====\n");
// mkfifo() 함수로 HAN-FIFO라는 FIFO 파일을 생성
if (mkfifo("./HAN-FIFO", 0666) == -1) {
perror("mkfifo");
exit(1);
}
// 서버에서 클라이언트로 데이터를 전송할 것이므로 HAN-FIFO 파일을 쓰기 전용으로 실행
if ((pd = open("./HAN-FIFO", O_WRONLY)) == -1) {
perror("open");
exit(1);
}
printf("To Client : %s\n", msg);
// 클라이언트로 “Hello, FIFO”라는 메시지를 전송
n = write(pd, msg, strlen(msg) + 1);
if (n == -1) {
perror("write");
exit(1);
}
close(pd);
return 0;
}
// 클라이언트 프로그램 코드
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int pd, n;
char inmsg[80];
// HAN-FIFO 파일을 읽기 전용으로 실행
// 클라이언트 프로그램은 이미 HAN-FIFO 파일이 있다고 가정하고 파일을 열고 있으므로
// 서버 프로그램을 먼저 실행해야 함
if ((pd = open("./HAN-FIFO", O_RDONLY)) == -1) {
perror("open");
exit(1);
}
printf("Client =====\n");
write(1, "From Server :", 13);
// 반복문을 수행하며 FIFO 파일에서 데이터를 읽어와 출력
while ((n = read(pd, inmsg, 80)) > 0) {
write(1, inmsg, n);
}
if (n == -1) {
perror("read");
exit(1);
}
write(1, "\n", 1);
close(pd);
return 0;
}
// 실행 결과: 서버에서 보낸 메시지를 클라이언트가 정확히 받아 출력하고 있음
// 서버 프로세스를 먼저 실행하면 클라이언트 프로세스가 실행할 때까지 기다리고 있다가 실행
참고 및 출처: 시스템 프로그래밍 리눅스&유닉스(이종원)
'Computer Science > UNIX & Linux' 카테고리의 다른 글
[UNIX/Linux] ep9+) 파이프 함수 실습 (0) | 2024.11.04 |
---|---|
CUDA + RAID 기반 데이터 분산 시뮬레이션 (8) | 2024.10.31 |
[UNIX/Linux] ep8+) 시그널 함수 실습 (1) | 2024.10.24 |
[UNIX/Linux] ep8) 시그널 (0) | 2024.10.23 |
[UNIX/Linux] ep7+) 프로세스 생성과 실행 함수 실습 (0) | 2024.10.22 |