Task 통신및동기화 : 파이프 (Pipe) Chapter #11
파이프 (Unamed Pipe) 표준입출력과파이프 FIFO(Named Pipe) 강의목차 Unix System Programming 2
파이프 (Unnamed Pipe) 파이프 (Pipe) (1) 하나의프로세스를다른프로세스에연결시켜주는단방향의통신채널 입출력채널과동기화기능을제공하는특수한형태의파일 파일을이용한프로세스간의통신 읽기전용및쓰기전용채널을분리하여제공 최소 512 Bytes의버퍼를제공 대부분의 UNIX 시스템에서는훨씬큰크기의파이프를제공 쓰기요구에대해버퍼가차있는경우에쓰기요구를대기상태로전환 (process blocking) 다른프로세스에의하여자료가읽혀져파이프에충분한공간이마련될때까지수행이일시중단된다 읽기요구에대해 FIFO(First In First Out) 방식으로데이터를전송하나버퍼가비어있는경우에읽기요구를대기상태로전환 Unix System Programming 3
파이프생성 pipe() #include <unistd.h> int pipe(int fd[2]) 파이프 (Pipe) (2) /* int fd[2] : 파일디스크립터배열 fd[0] 읽기용파일디스크립터, fd[1] 쓰기용파일디스크립터 */ /* 정상종료하면 0 을반환하고, 에러가발생한경우에서는 -1 를반환한다. 외부변수 errno 에에러를나타내는값을설정한다 */ Unix System Programming 4
파이프 (Pipe) (3) 파이프를이용한프로세스간의통신 부모프로세스가파이프를생성하고자식프로세스를생성하면파이프에대한파일디스크립터를상속 파이프에대한파일디스크립터공유를통해부모및자식프로세스간에또는같은가족프로세스간에통신이가능 대개의경우에단방향통신을적용 데이터를쓰는프로세스에서읽기용 fd[0] 를닫음 데이터를읽는프로세스에서는쓰기용 fd[1] 를닫음 Unix System Programming 5
예제프로그램 #1 #include <unistd.h> #include <stdio.h> 파이프 (Pipe) (4) /* 아래숫자는마지막에 null 문자들을포함한다 */ #define MSGSIZE 16 char *msg1 = "hello, world #1"; char *msg2 = "hello, world #2"; char *msg3 = "hello, world #3"; int main(void) { char inbuf[msgsize]; int p[2], j; /* 파이프를개방한다 */ if(pipe(p)== -1) { perror("pipecall"); exit(1); Unix System Programming 6
파이프 (Pipe) (5) /* 파이프에쓴다 */ write(p[1], msg1, MSGSIZE); write(p[1], msg2, MSGSIZE); write(p[1], msg3, MSGSIZE); /* 파이프로부터읽는다. */ for(j = 0; j < 3; j++) { read (p[0], inbuf, MSGSIZE); printf("%s\n", inbuf); exit (0); Unix System Programming 7
예제프로그램 #2 #include <unistd.h> #include <stdio.h> 파이프 (Pipe) (6) /* 아래숫자는마지막에 null 문자들을포함한다 */ #define MSGSIZE 16 char *msg1 = "hello, world #1"; char *msg2 = "hello, world #2"; char *msg3 = "hello, world #3"; int main(void) { char inbuf[msgsize]; int p[2], j; pid_t pid; /* 파이프를개방한다 */ if(pipe(p)== -1) { perror("pipecall"); exit(1); Unix System Programming 8
파이프 (Pipe) (7) switch (pid= fork()) { case -1: perror("fork call"); exit (2); case 0: /* 자식일경우파이프에쓴다. */ write (p[1], msg1, MSGSIZE); write (p[1], msg2, MSGSIZE); write (p[1], msg3, MSGSIZE); break; default: /* 부모일경우파이프로부터읽는다. */ for (j = 0; j < 3; j++) { read (p[0], inbuf, MSGSIZE); printf("%s\n", inbuf); wait (NULL); exit (0); Unix System Programming 9
파이프 (Pipe) (8) Unix System Programming 10
예제프로그램 #3 #include <unistd.h> #include <stdio.h> 파이프 (Pipe) (9) /* 아래숫자는마지막에 null 문자들을포함한다 */ #define MSGSIZE 16 char *msg1 = "hello, world #1"; char *msg2 = "hello, world #2"; char *msg3 = "hello, world #3"; int main(void) { char inbuf[msgsize]; int p[2], j; pid_t pid; /* 파이프를개방한다 */ if(pipe(p)== -1) { perror("pipecall"); exit(1); Unix System Programming 11
파이프 (Pipe) (10) */ switch (pid= fork()) { case -1: perror("fork call"); exit (2); case 0:/* 자식일경우, 읽기파일기술자를닫고파이프에쓴다. */ close(p[0]); write (p[1], msg1, MSGSIZE); write (p[1], msg2, MSGSIZE); write (p[1], msg3, MSGSIZE); break; default:/* 부모일경우, 쓰기파일기술자를닫고파이프로부터읽는다. close(p[1]); for (j = 0; j < 3; j++) { read (p[0], inbuf, MSGSIZE); printf("%s\n", inbuf); wait (NULL); exit (0); Unix System Programming 12
파이프 (Pipe) (11) Unix System Programming 13
예제프로그램 #4 파이프 (Pipe) (12) 교재 pp.342~343, simple_pipe.c 새로운프로세스를만들어자식프로세스에서부모프로세스에게명령어행을통해입력받은메시지를보내는프로그램 Unix System Programming 14
파이프 (Pipe) (13) 파이프크기 /* 봉쇄 (block) 될때까지파이프에쓴다. */ #include <signal.h> #include <unistd.h> #include <limits.h> int count; void alrm_action(int); /* 파이프의크기를결정한다. */ pipe_size = fpathconf(p[0],_pc_pipe_buf); printf("maximum size of write to pipe: %d bytes\n", pipe_size); /* 시그널핸들러를지정한다. */ sigaction(sigalrm, &act, NULL); int main(void) { 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); while (1){ alarm (20); write(p[1], &c, 1); alarm(0); if ((++count % 1024) == 0) printf("%d characters in pipe\n", count); exit(0); /* SIGALRM을받을경우호출한다. */ void alrm_action(intsigno){ printf("write blocked after %d characters\n", count); exit (0); Unix System Programming 15
파이프닫기 (Close) 파이프 (Pipe) (14) Write 파이프를닫았을경우 파이프에대한 read 는파일의끝 (EOF) 에도달한것과마찬가지로 0 을반환 Read 파이프를닫았을경우 이파이프에 write 를시도하는모든프로세스는커널로부터 SIGPIPE 시그널을받는다 Unix System Programming 16
파이프 (Pipe) (15) Non-blocking Read/Write 파이프에대해 fstat() 를사용 하나의프로세스만이파이프로부터데이터를읽는경우에사용 fcntl() 사용 O_NONBLOCK 플래그사용 #include <fcntl.h>... if (fcntl(filedes, F_SETFL, O_NONBLOCK)== -1) perror("fcntl"); Unix System Programming 17
파이프 (Pipe) (16) Non-blocking Read/Write 예제프로그램 #include <fcntl.h> #include <errno.h> #define MSGSIZE 6 int parent(int*); int child(int*); char *msg1 = "hello"; char *msg2 = "bye!!"; int main(void){ int pfd[2]; /* 파이프를개방한다 */ if(pipe (pfd) == -1) fatal ("pipe call"); /* p[0] 의 O_NONBLOCK 플래그를 1 로설정한다 */ if (fcntl(pfd[0], F_SETFL, O_NONBLOCK) == -1) fatal ("fcntlcall"); switch(fork()){ case -1: /* 오류 */ fatal("fork call"); case 0: /* 자식 */ child(pfd); default: /* 부모 */ parent (pfd); exit(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; else fatal ("read call"); case 0: /* 파이프가닫혔음. */ Unix System Programming printf("end of conversation\n"); 18 exit (0);
파이프 (Pipe) (17) default: printf("msg=%s\n", buf); /* switch */ /* for */ int child(intp[2]) { int count; close(p[0]); for (count= 0; count < 3; count++) { write (p[1], msg1, MSGSIZE); sleep(3); * 실행예 : MSG=hello (pipe empty) (pipe empty) (pipe empty) MSG=hello (pipe empty) (pipe empty) (pipe empty) MSG=hello (pipe empty) (pipe empty) (pipe empty) MSG=bye!! END of conversation /* 마지막메시지를보낸다 */ write (p[1], msg2, MSGSIZE); exit (0); int fatal(char *s) /* 오류메시지를프린트하고죽는다. */ { perror(s);exit(1); Unix System Programming 19
파이프 (Pipe) (18) 다수의파이프다루기 select() select() 시스템호출 여러개의파이프를동시에취급하는경우 일반파일또는 termianl 및 socket 등의특수파일등에도사용가능 사용법 #include <sys/time.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) /* int nfds : 검사하여야할파일디스크립터의개수 */ /* fd_set *readfds, *writefds, *errorfds : fd_set 비트마스크에대한포인터 */ /* struct timeval *timeout : 사건발생까지의대기시간지정 timeout=null: 사건이발생할때까지 blocking timeout 의값 =0 : 검사후에즉시반환 timeout 의값!=0 : 지정된시간을대기후에반환 */ Unix System Programming 20
파이프 (Pipe) (19) 다수의파이프다루기 select() 파일디스크립터비트마스크조작매크로 : #include <sys/time.h> /* 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); Unix System Programming 21
파이프 (Pipe) (20) 다수의파이프다루기 select() 사용예 : #include <sys/time.h> #include <sys/types.h> #include <fcntl.h>.. 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); 하위 5 개의비트만을검사 switch(select(5, &readset, NULL, NULL, NULL)) { /* 논리 */ Unix System Programming 22
파이프 (Pipe) (21) 다수의파이프다루기 select() : 예제프로그램 /* server 세개의자식을생성하고이어그들을서비스한다 */ #include <sys/time.h> #include <sys/wait.h> #define MSGSIZE 6 char *msg1 = "hello"; char *msg2 = "bye!!"; void parent(int[][]); int child(int[]); int main(void) { int pip[3][2]; int i; switch (fork()) { case -1: /* 오류 */ fatal ("fork call"); case 0: /* 자식 */ child (pip[i]); parent(pip); exit (0); /* 세개의통신파이프를생성하고, 세개의자식을낳는다. */ for (i = 0; i < 3; i++) { if (pipe(pip[i]) == -1) fatal ("pipe call"); Unix System Programming 23
파이프 (Pipe) (22) /* 부모는세개의파이프에전부귀를기울이고있다. */ 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]); /* select 시스템호출의비트마스크를설정한다. */ FD_ZERO (&master); FD_SET (0, &master); for (i = 0; i <3; i++) FD_SET (p[i][0], &master); /* 타임아웃없이 select 를호출한다. 사건이발생할때까지 select 는봉쇄될것이다 */ set = master; while (select(p[2][0]+1, &set, NULL, NULL, NULL) > 0) { /* 표준입력, 즉화일기술자 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; Unix System Programming 24
파이프 (Pipe) (23) 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); * 실행예 : Message from child 0 MSG=hello Message from child 1 MSG=hello Message from child 2 MSG=hello d ( 사용자가 d 를입력하고리턴을친다 ) From standard input d From standard input Message from child 0 MSG=hello Message from child 1 MSG=hello Message from child 2 MSG=hello Message from child 0 MSG=bye Message from child 1 MSG=bye Message from child 2 MSG=bye Unix System Programming 25
파이프를쌍방향통신 파이프 (Pipe) (24) 2 개의파이프를이용하여부모와자식프로세스간에쌍방향으로통신이가능 Unix System Programming 26
예제프로그램 #1 파이프 (Pipe) (25) 교재 pp. 345~347, duplex_pipe.c 새로운프로세스를만들어자식프로세스와부모프로세스간에서명령어행에서입력받은메시지를쌍방향으로주고받는프로그램 Unix System Programming 27
표준입출력과파이프 (1) 쉘의파이프기능 두개의프로세스사이에표준입출력을연결시키는기능 예 : ls wc pipe(), dup2(), close() 시스템호출을사용하여구현 쉘에서개방되어있는파일기술자는 exec() 을수행한이후에도여전히개방 exec() 을수행하기전에 ls의표준출력을 write pipe에연결시키고, wc의표준입력을 read pipe에연결시킨다 Unix System Programming 28
표준입출력과파이프 (2) /* join 두명령을파이프로결합한다. */ Int join (char *com1[], char *com2[]) { int p[2], status; /* 명령을수행할자식을생성한다. */ switch (fork()){ case -1:/* 오류 */ fatal ("1st fork call in join"); case 0: /* 자식 */ break; default:/* 부모 */ wait(&status); return (status); /* 루틴의나머지부분으로자식에의해수행된다. */ /* 파이프를만든다. */ if (pipe(p) == -1) fatal ("pipe call in join"); /* 다른프로세스를생성한다. */ switch (fork()){ case -1:/* 오류 */ fatal ("2nd fork call in join"); */ case 0: /* 쓰는프로세스 */ /* 표준출력이파이프로가게한다. */ dup2(p[1],1); /* 화일기술자를절약한다. */ close (p[0]); close (p[1]); execvp(com1[0], com1); /* execvp 가복귀하면, 오류가발생한것임. fatal("1st execvp call in join"); default: /* 읽는프로세스 */ /* 표준입력이파이프로부터오게한다 */ dup2(p[0], 0); close (p[0]); close (p[1]); execvp(com2[0], com2); fatal ("2nd execvpcall in join"); /* switch */ /* join */ Unix System Programming 29
표준입출력과파이프 (3) #include <stdio.h> int main(void) { char *one[4] = {"ls", "-l", "/usr/lib", NULL; char *two[3] = {"grep", ^d", NULL; int ret; ret = join (one, two); printf("join returned %d\n", ret); exit(0); Unix System Programming 30
표준입출력과파이프 (4) 예제프로그램 #1 교재 pp.230~351, shell_pipe.c 쉘의파이프기능을시물레이션하는프로그램 Unix System Programming 31
FIFO(Named Pipe) (1) Unnamed Pipe 의단점 부모와자식프로세스사이의통신에만사용가능 영구히존재할수없음 FIFO(Named Pipe) UNIX 파일이름을부여하여생성되며, 일반파일과같이파일시스템에의해관리된다 예 : prw-rw-r-- 1 ben usr 0 Aug 1 21:05 channel 임의의두프로세스연결에사용가능 영구적으로존재 생성명령어 : /etc/mknod channel p Unix System Programming 32
FIFO(Named Pipe) (2) FIFO(Named Pipe) 생성 mkfifo() #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode) /* const char *pathname : FIFO 경로명 */ /* mode_t mode : FIFO 접근권한모드 */ Unix System Programming 33
FIFO(Named Pipe) (3) FIFO(Named Pipe) 생성 mkfifo() #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> mkfifo("/tmp/fifo", 0666); fd= open("/tmp/fifo", O_WRONLY); 다른프로세스가읽기위해 FIFO 를 open 할때까지 block 된다 non-blocking open : if ((fd= open("/tmp/fifo", O_WRONLY O_NONBLOCK) == -1) perror( openon fifo ); Unix System Programming 34
예제프로그램 #1 FIFO(Named Pipe) (4) /* sendmessage--fifo 를통해메시지를보낸다. */ #include <fcntl.h> #include <stdio.h> #include <errno.h> #define MSGSIZ 63 char *fifo= "fifo"; int main(int argc, char **argv) { int fd, j, nwrite; char msgbuf[msgsiz+1]; if (argc< 2) { fprintf(stderr, "Usage: sendmessage msg... \n"); exit(1); /* O_NONBLOCK 을설정하여 fifo 를개방한다. */ if ((fd= open(fifo, O_WRONLY O_NONBLOCK)) < 0) fatal ("fifoopen failed"); /* 메시지를보낸다. */ for ( j = 1; j < argc; j++){ if (strlen(argv[j]) > MSGSIZ){ fprintf(stderr, "message too long %s\n", argv[j]); continue; strcpy(msgbuf, argv[j]); if ((nwrite= write (fd, msgbuf, MSGSIZ+1)) == -1) fatal ("message write failed"); exit (0); Unix System Programming 35
FIFO(Named Pipe) (5) /* rcvmessage--fifo 를통해메시지를받는다. */ #include <fcntl.h> #include <stdio.h> #include <errno.h> #define MSGSIZ 63 char *fifo= "fifo"; int main (int argc, char **argv) { int fd; char msgbuf[msgsiz+1]; /* 메시지를받는다 */ for(;;){ if (read(fd, msgbuf, MSGSIZ+1) <0) fatal ("message read failed"); /** 메시지를프린트한다 ; 실제로는보다흥미있는일이수행된다.*/ printf("message received:%s\n", msgbuf); /* fifo 가이미존재하지않으면, 생성한다 */ if (mkfifo(fifo, 0666) == -1) { if (errno!= EEXIST) fatal ("receiver: mkfifo"); /* fifo 를읽기와쓰기용으로개방한다. */ if ((fd= open(fifo, O_RDWR)) < 0) fatal ("fifoopen failed"); Unix System Programming 36
FIFO(Named Pipe) (6) * 실행예 : $ rcvmessage& 40 $ sendmessage message 1 message 2 message received: message 1 message received: message 2 $ sendmessage message number 3 message receive: message number 3 Unix System Programming 37