8. 프로세스간통신 IPC(Inter Process Communication) 은멀티프로세스로구현된서버에서프로세스간에데이터를전달하는데사용 IPC 기술 파이프, 메시지큐, 세마포어, 공유메모리 1
8.1 파이프 2
파이프 (PIPE) 운영체제가제공하는프로세스간통신채널로서특별한타입의파일 일반파일과달리메모리에저장되지않고운영체제가관리하는임시파일 데이터저장용이아닌프로세스간데이터전달용으로사용 파이프를이용한프로세스간통신 송신측은파이프에데이터를쓰고수신측은파이프에서데이터를읽음 스트림채널을제공 TCP연결은원격프로세스간에스트림채널을제공 pipe는같은컴퓨터내의프로세스간의스트림채널을제공 송신된데이터는바이트순서를유지 3
파이프생성 파이프를열기위해서는시스템콜 pipe() 를사용 하나의파이프를생성하면두개 ( 읽기, 쓰기 ) 의파일디스크립터가생성됨 #include <unistd.h> int pipe(int fd[2]); fd[0] 은읽기용, fd[1] 은쓰기용 파이프를생성한프로세스가 fork() 를호출하면자식프로세스는부모프로세스의파이프를공유 부모, 자식프로세스가동일디스크립터 (fd[1]: 쓰기, fd[0]: 읽기 ) 를사용 fd[1] : write 부모프로세스 fd[0] : read 파이프 fd[1] : write 자식프로세스 fd[0] : read 4
파이프생성 부모에서자식으로데이터를보내는경우 fd[1] : write 부모프로세스 fd[0] : read 파이프 fd[1] : write 자식프로세스 fd[0] : read 사용하지않는파일디스크립터를닫은상태 파이프를이용한부모와자식프로세스간통신 fd[1] : write fd[0] : read 부모프로세스 파이프 ( 스트림 ) 자식프로세스 5
파이프생성 파이프생성코드 int fd[2]; pid_t pid; pipe(fd); if ((pid=fork())<0) { exit(0); } else if (pid>0) { close(fd[0]); } else if (pid==0) { close(fd[1]); } // 자식프로세스생성 // 부모프로세스 : 읽기용파일디스크립터제거 // 자식프로세스 : 쓰기용파일디스크립터제거 파이프의특징 단방향스트림채널만제공 양방향통신을위해서는추가적인파이프를생성해야함 6
파이프를이용한에코서버, 클라이언트프로그램 (udpserv_pipeecho.c, udpechocli.c) 두개의프로세스로구성된 UDP형에코서버프로그램 부모프로세스 (parent_start) 는클라이언트로부터받은메시지를파이프에 write UDP 서버이므로부모프로세스는클라이언트가보내온데이터와함께클라이언트의주소를자식프로세스에게전달 자식프로세스 (child_start) 는파이프로부터데이터를 read 하여클라이언트로에코 파이프를통해전달할데이터의구조체정의 // 파이프에쓰는데이터구조 typedef struct mseg { } mesg_t; struct sockaddr_in addr; // 클라이언트주소 char data[max_bufsz]; // 에코할데이터 클라이언트는 UDP 소켓을개설, 키보드입력을 sendto() 로서버에전달, recvfrom() 으로수신하여화면에출력 7
8.2 FIFO FIFO(named pipe) 는임의의프로세스간의통신을허용 8
FIFO 생성 파이프는 fork() 로만들어진프로세스들사이의통신에만사용가능한제약이있음 제한을극복하기위해파이프에이름을지정하고임의의다른프로세스에서파이프에접근하도록한것을 named pipe(fifo) 라함 mkfifo() int mkfifo(const char *pathname, mode_t mode); pathname : 파이프의이름, 경로명이없으면현재디렉토리 mode : FIFO 의파일접근권한설정 읽기 / 쓰기수행 FIFO 를생성한후 FIFO 를 open() 해야함 FIFO 도파이프의일종으로프로세스간스트림채널을제공 write mode /tmp/myfifo read mode 프로세스 A FIFO( 스트림 ) 프로세스 B 9
FIFO 를이용한에코서버프로그램 (udpserv_fifoecho.c) 파이프를이용한에코서버를 FIFO 를이용하여작성 부모프로세스 클라이언트로부터받은메시지를 FIFO 에 write 자식프로세스 FIFO 로부터데이터를읽어클라이언트에에코 FIFO 에서는 FIFO 의이름만알면언제든지오픈하여사용 파이프에서는 fork() 전에파이프를생성해야부모, 자식프로세스가파일을공유할수있다. 10
8.3 메시지큐 메시지큐를사용하여프로세스사이에통신 특정프로세스에게메시지단위전송이가능 메시지전송에우선순위부여가능 파이프나 FIFO는프로세스사이에제공되는스트림을사용하여통신 메시지단위로송신할때는주의가필요 11
메시지큐개요 메시지단위 의송수신용큐 메시지전송에우선순위부여가가능 메시지큐를이용한프로세스간통신 put : 메시지큐에메시지쓰기 get : 메시지큐의메시지읽기 프로세스 A 프로세스 B... 프로세스 C put get put get put get msg5 msg4 msg3 msg2 msg1 메시지큐 12
타입이있는메시지큐 2 차원메시지큐 프로세스 A 프로세스 B... 프로세스 C put get put get put get 1 msg4 msg3 msg2 msg1 2 3 msg1 msg2 msg1 4... N 메시지큐 13
타입이있는메시지큐 (2) 메시지송수신시에프로세스는타입을지정 큐를타입값으로구분하고, 메시지를읽고쓸때큐타입을지정 특정프로세스에메시지전송이가능 예 : 메시지를 put 할때수신할프로세스의 PID 값을타입으로지정하고, 수신프로세스는자신의 PID 를타입으로 get 메시지큐의우선순위부여가능 예 : 타입값을우선순위로사용하면우선순위가높은메시지를먼저수신하고우선순위가높은메시지가없으면우선순위가낮은메시지를수신 14
메시지큐생성 msgget() int msgget(key_t key, int msgflg); key 메시지큐를구분하기위한고유키 메시지큐를생성하지않은다른프로세스가메시지큐에접근하려면 key를알아야함 msgflag : 메시지큐생성시옵션을지정 (bitmask 형태 ) IPC_CREATE, IPC_EXCL 등의상수와파일접근권한지정 메시지큐 ID 를반환 : 다른프로세스는이 ID 를통해메시지큐를사용 msqid_ds 구조체 메시지큐가생성될때마다메시지큐에관한각종정보를담는메시지큐객체를생성 마지막으로송신또는수신한프로세스 PID 송수신시간 큐의최대바이트수 메시지큐소유자정보 15
메시지큐객체 struct msqid_ds { struct ipc_perm msg_perm; // 메시지큐접근권한 struct msg *msg_first; // 처음메시지 struct msg *msg_last; // 마지막메시지 time_t msg_stime; // 마지막메시지송신시각 time_t msg_rtime; // 마지막메시지수신시각 time_t msg_ctime; // 마지막으로 change가수행된시각 struct wait_queue *wwait; // wait 큐 struct wait_queue *rwait; ushort msg_cbytes; // 큐에있는모든메시지들의바이트수 ushort msg_qnum; // 큐에있는메시지수 ushort msg_qbytes; // 메시지큐최대바이트수 ushort msg_lspid; // 마지막으로 msgsnd를수행한 PID ushort msg_lrpid; // 마지막으로받은 PID }; struct ipc_perm { key_t key; ushort uid; // owner의 euid와 egid ushort gid; ushort cuid; // 생성자의 euid와 egid ushort cgid; ushort mode; // 접근모드의하위 9bits ushort seq; // 순서번호 (sequence number) }; 16
msgflag 옵션 IPC_CREAT 동일한 key를사용하는메시지큐가존재하면그객체에대한 ID를정상적으로리턴 존재하지않는다면메시지큐객체를생성하고 ID 를리턴 IPC_EXCL 동일한 key를사용하는메시지큐가존재하면 -1을리턴 단독으로사용하지못하고 IPC_CREAT와같이사용해야함 IPC_PRIVATEPRIVATE key가없는메시지큐생성 명시적으로키값을정의하여사용할필요가없는경우이용 예 : 메시지큐 ID를서로공유할수있는부모와자식프로세스사이에사용가능 외부의다른프로세스는이메시지큐에접근불가 msgget() 사용예 : mkq.c 키값을인자로주어메시지큐객체를생성한후메시지큐 ID 를출력 17
실행결과 $ mkq 123 created queue id = 229377 key is = 123 opened queue id = 229377 18
메시지송신 msgsnd() 메시지큐에메시지를넣는함수 int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg); struct msgbuf { long mtype; // 메시지타입 > 0 char mtext[1]; // 메시지데이터 } mtype 은 1 이상이어야함 msgbuf의첫4바이트는반드시 long 타입 mtext는문자열, binary등임의의데이터사용가능 msgsz 는 mtext 만의크기 msgflg 를 IPC_NOWAIT 로하면메시지큐공간이부족한경우블록되지않고 EAGAIN 에러코드와함께 -1 을리턴 0 으로한경우메시지큐공간이부족하면블록 msgsnd 함수는성공하면 0, 실패하면 -1 을리턴 19
메시지수신 msgrcv() 메시지큐로부터메시지를읽는함수 ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtype, int msgflg); msqid : 메시지큐객체 ID msgp : 메시지큐에서읽은메시지를저장하는수신공간 msgsz : 수신공간의크기 msgflg : 메시지가없는경우취할동작, 0이면대기, IPC_NOWAIT이면 EAGAIN 에러코드와 -1을리턴 읽은메시지가수신공간크기보다크면 E2BIG 에러가발생 msgflg g를 MSG_NOERROR로설정하면 msgsz 크기만큼만읽고, 나머지는잘려진다. msgtype은메시지큐에서읽을메시지타입을지정 0 으로하면타입의구분없이메시지큐에입력된순서대로읽음 -10 을지정하면타입이 10 보다같거나작은메시지를읽음» 타입이 10 인메시지부터우선순위를두어읽음 20
메시지큐이용예 (qsnd_pid.c) msgtype 값을특정프로세스의 PID로지정하여지정된프로세스가메시지를수신하는프로그램 자식프로세스는 msgtype=getppid() 로부모프로세스의 PID를구하여이를큐타입으로사용하여메시지큐로송신 부모프로세스는메시지를수신후메시지큐를삭제 실행결과 msgctl(qid, IPC_RMID, 0) $ qsnd_pid 1234 Enter message to post : 10510 프로세스메시지큐읽기대기중.. Hi! my parent <----- 자식프로세스입력데이터 message posted recv = 14 bytes <------ 부모프로세스출력 type = 10510 수신프로세스 PID= 10510 value = Hi! my parent 21
메시지큐제어 msgctl() 메시지큐에관한정보읽기, 동작허가권한변경, 메시지큐삭제등을제어 int msgctl(int msqid, int cmd, struct msqid_ds *buf); msqid : 메시지큐객체 ID cmd : 제어명령구분 IPC_STAT : 메시지큐객체에대한정보를얻는명령 IPC_SET : r/w 권한, euid, egid, msg_qbytes를변경하는명령 IPC_RMID : 메시지큐를삭제하는명령 buf : cmd 명령에따라동작 IPC_SET r/w권한, euid, egid, msg_qbytes만변경이가능 IPC_STAT 명령으로메시지큐객체를얻은후변경시키고 IPC_SET을호출 IPC_RMID 삭제명령을내렸을때아직읽지않은메시지가있어도즉시삭제 22
msgctl() 사용예 (qctl.c) IPC_STAT 를사용해메시지큐객체를얻어와서큐에대한정보를출력하고, IPC_RMID 를사용하여큐를삭제 실행결과 $mkq 1234 created queue id = 163840 key is = 1234 opened queue id = 163840 $ qctl 1234 큐의최대바이트수 : 16384 큐의유효사용자 UID : 502 큐의유효사용자 GID : 502 큐접근권한 : 0666 메시지큐 163840 삭제됨 23
메시지큐를이용한에코서버 (msgq_echoserv.c, res_send.c) UDP 형에코서버를메시지큐를이용하여멀티프로세스모델로구현 REQ_RECV_PROC : 에코요청메시지를수신하는프로세스 에코요청을메시지큐에넣고다음요청을기다리는동작을반복 한개의프로세스만실행 RES_SEND_PROC _ : 에코메시지를응답하는프로세스 메시지큐에서읽기를대기하다메시지가도착하면클라이언트에게응답하는동작을반복 여러개의프로세스를실행 한개의메시지큐로부터메시지를읽도록내부적으로동기화 RES_SEND_PROC 를생성하는코드 #define RES_SEND_PROC SEND res_send void fork_and_exec(char *key, char *port) { pid_t pid = fork(); if (pid < 0) errquit(" fork fail"); else if(pid >0) return; execlp(res_send_proc, SEND RES_SEND_PROC, SEND key, port, 0); perror("execlp fail "); } 24
메시지큐를이용한에코서버 ( 계속 ) 메시지큐로전달할메시지구조 typedef struct _msg { long msg_type; // 메시지큐타입 struct sockaddr_in addr; // 클라이언트의소켓주소 char msg_text[ MAX_BUFSZ ]; // 메시지를저장 } msg_t ; 메시지큐와소켓을생성후클라이언트의메시지가도착하면 recvfrom으로읽고, msgsnd를사용하여메시지큐에넣는다. while(1) { nbytes = recvfrom(sock,pmsg.msg_text, t MAX_BUFSZ, 0, (struct sockaddr *)&pmsg.addr, &len); if(nbytes < 0) { perror("recvfrom fail "); continue; } pmsg.msg_text[ nbytes ] = 0; } // 메시지큐에쓰기 if( msgsnd(msqid, &pmsg, size, 0)==-1 ) errquit("msgsnd fail "); 25
RES_SEND_PROC(res_send.c) execpl 가호출될때인자로부터키와포트번호를얻는다. 메시지큐에서읽기를대기하다메시지가도착하면클라이언트에게응답메시지를전송 수행결과 여러프로세스가순서대로에코를처리 커널의프로세스스케쥴러가최근에실행한프로세스의우선순위를낮게주어같은프로세스가연속적으로 CPU 서비스를받는것을금지 $ msgq_echoserv 12345 9999 Server starting. reply process PID = 7228 aaaaaaaaaaaaaaaaaaaaaa reply process PID = 7229 bbbbb reply process PID = 7230 ccccccccccccccccccc reply process PID = 7228 dddddddddddddd reply process PID = 7229 eeeeeeeeeee reply process PID = 7230 ffffffffffffffffffffffffffffffffffffff 26
8.4 공유메모리 프로세스간의통신을위해서공유메모리를사용하는방법을소개 27
공유메모리사용 공유메모리 프로세스들이공통으로사용할수있는메모리영역 특정메모리영역을다른프로세스와공유하여프로세스간통신이가능 데이터를한번읽어도데이터가계속남아있음 같은데이터를여러프로세스가중복하여읽어야할때효과적 공유메모리를생성하는함수 : shmget() int shmget(key_t key, int size, int shmflg); int 타입의공유메모리 ID 를리턴 struct shmid_ds 구조체에정보를저장 key : 새로생성될공유메모리를식별하기위한값 다른프로세스가접근하기위해서는이키값을알아야함 int size : 공유메모리크기 shmflg : 공유메모리생성옵션을지정 bitmask 형태의인자 IPC_CREAT, IPC_EXCL, 파일접근권한 28
공유메모리사용 shmid_ds 구조체 sturct t shmid_ds d { struct ipc_perm shm_perm; // 동작허가사항 int shm_segsz; // 세그먼트의크기 (bytes) time_t t shm_atime; // 마지막 attach 시각 time_t shm_dtime; // 마지막 detach 시각 time_t shm_ctime; // 마지막 change 시각 unsigned short shm_cpid; // 생성자의 PID unsigned short shm_lpid; // 마지막접근자의 PID short shm_nattch; // 현재 attaches 프로세스수 // 아래는 private unsigned short shm_npages; // 세그먼트의크기 (pages) unsigned long *shm_pages; // array of ptrs to frames ->SHMMAX sturct vm_area_struct *attaches; // descriptors for attaches }; 29
Shmflg : 공유메모리생성옵션 IPC_CREAT 를설정한경우 같은 key값을사용하는공유메모리가존재하면해당객체에대한 ID를리턴 같은 key 값의공유메모리가존재하지않으면새로운공유메모리를생성하고그 ID 를리턴 IPC_EXCL 과 IPC_CREAT 를같이설정한경우 같은 key값을사용하는공유메모리가존재하면 shmget() 호출은실패하고 -1 을리턴 IPC_CREAT 와같이사용해야함 IPC_PRIVATE key 값이없는공유메모리를생성 명시적으로 key값을사용할필요가없는경우에사용 메시지큐 ID를서로공유할수있는부모와자식프로세스사이에사용가능 외부의다른프로세스는이메시지큐에접근불가 30
공유메모리첨부 shmat() 공유메모리생성후실제사용전에물리적주소를자신의프로세스의가상메모리주소로맵핑 ( 프로세스에공유메모리를첨부한다고표현 ) void *shmat(int shmid, const void *shmaddr, int shmflg); shmid : 공유메모리객체 ID shmaddr : 첨부시킬프로세스의메모리주소 0 : 커널이자동으로빈공간을찾아서처리 shmflg : 공유메모리옵션 0 : 읽기 / 쓰기모드 SHM_RDONLY : 읽기전용 호출성공시첨부된주소를리턴하고, 에러시 -1 을리턴 사용예 int shmid = shmget(0x1234, 1024, IPC_CREAT 0600); char *myaddr = shmat(shmid, 0, 0);// 자동으로빈공간을찾고, 읽기 / 쓰기가능 // 오류발생시 -1 을리턴 if (myaddr = (chat *)-1) { perror( 공유메모리를 attach 하지못했습니다.\n ); exit(0); } 31
공유메모리의분리 shmdt() 공유메모리의사용종료시, 자신이사용하던메모리영역에서공유메모리를분리 int shmdt(const void *shmaddr); shmaddr : shmat() 가리턴했던주소, 현재프로세스에첨부된공유메모리의시작주소 공유메모리의분리가공유메모리의삭제를의미하지는않음 다른프로세스는계속그공유메모리를사용할수있음 shmid_ds 구조체의 shm_nattach 멤버변수 shmat() 로공유메모리를첨부하면 1 증가 공유메모리를분리하면 1 감소 32
공유메모리제어 공유메모리의정보읽기, 동작허가권한변경, 공유메모리삭제등의공유메모리제어 int shmctl(int shmid, int cmd, struct shmid_ds *buf); shmid : 공유메모리객체 ID cmd : 수행할명령 IPC_STAT : 공유메모리객체에대한정보를얻어오는명령 shmctl(shmid, IPC_STAT, buf) IPC_SET : r/w 권한, euid, egid 를변경하는명령 shmctl(shmid, IPC_SET, &shmds) IPC_RMID : 공유메모리를삭제하는명령 shmctl(shmid, IPC_RMID, 0) buf : cmd 명령에따라의미가변경 공유메모리객체정보를얻어오는명령 : 얻어온객체를 buf 에저장 동작허가권한을변경하는명령 : 변경할내용을 buf 에저장 33
공유메모리의동기화문제처리 공유메모리는메모리에기록한데이터를다수의프로세스가복사하여읽는것이가능 같은데이터를다수의프로세스에게전달이용이 예 : 상태정보를공유메모리에쓰면여러프로세스가읽도록하는경우 여러프로세스가병행하여쓰기 / 읽기작업을수행하면동기화문제가발생할수있음 하나의공유데이터를둘이상의프로세스가동시에접근함으로써발생할수있는문제 데이터의값이부정확하게사용되는문제가있음 해결방안 : 세마포어, 스레드 34
동기화문제예 (shmbusyaccess.c) 3개의프로세스 ( 부모, 2개의자식 ) 가경쟁적으로공유메모리에접근하여동기화문제를발생시키는예 공유메모리생성하고두개의자식프로세스를생성 3개의프로세스에서공유메모리를접근하는 busy() 를수행 busy() 에서는 access_shm() shm() 을호출하여공유메모리에접근 access_shm() 공유메모리에자신의 PID를기록 지연 공유메모리에남아있는 PID 와자신의 PID 를비교 실행결과 다르면 access_shm() 이반환되기전에다른프로세스가공유메모리를접근한경우 동일하면정상적으로처리 $ shmbusyaccess 12345 Error(count=9975) : 다른프로세스도동시에공유메모리접근함 Error(count=31642) : 다른프로세스도동시에공유메모리접근함 Error(count=43453) : 다른프로세스도동시에공유메모리접근함... 35
8.5 세마포어 동기화문제를소개 세마포어를이용하여동기화문제를해결하는방법 36
세마포어 프로세스간통신에서발생할수있는동기화문제를해결하기위해사용 동기화문제예 프로세스 A의작업 : { printf( A: before increase x=%d\n, x); x++; printf( A: after increase x=%d\n, x); } 프로세스 B의작업 : { printf( B: before decrease x=%d\n, x); x--; printf( B: after decrease x=%d\n, x); } X 의초기값이 3 이고, 프로세스 A, B 가차례로수행했을때결과 A: before increase x=3 A: after increase x=4 // A 는 x=4 를사용 B: before decrease x=4 B: after decrease x=3 // B 는 x=3 을사용 37
동기화문제예 ( 계속 ) A 가수행하는중간 (x++ 를수행하기직전 ) 에 B 가 CPU 스케줄을받아 x 를접근한경우의결과 A: before increase x=3 B: before decrease x=3 // B는 x=3를사용 B: after decrease x=2 // B는 x=2를사용 A: after decrease x=3 // A 는 x=3 을사용 A 가 x++ 를수행한직후에 B 가 CPU 스케줄을받아 x 를접근한경우의결과 A: before increase x=3 B: before decrease x=4 // x 는 x++ 직후이므로 4 B: after decrease x=3 // B는 x=3를사용 A: after increase x=3 // A는 x=3을사용 38
세마포어의정의 동기화문제해결방법 공유데이터를액세스하는프로세스수를한순간에하나로제한 세마포어를사용하여제한할수있음 세마포어 공유데이터에대해현재사용가능한데이터수를나타낸다. 이진세마포어 : 공유데이터가한개일경우, 0 과 1 값을사용 동작방법 공유데이터에접근하기전에세마포어 s의값을확인 1이면 s를 0으로변경하고액세스 0이면대기 액세스가끝나면 s 값을 1 로다시변경 카운터세마포어 : 공유할수있는데이터가둘이상일경우 공유데이터가 5 개이면세마포어값은최대 5 를가진다. 세마포어가 4이면 4개의공유데이터가남아있음을의미 39
semget(), 세마포어생성 semget() 세마포어를생성할때사용하는함수 int semget(key_t t key, int nsems, int semflg); key : 세마포어를구분하기위한키 다른프로세스는이키를알아야사용할수있음 nsems : 세마포어집합을구성하는멤버의수 3 으로설정하면 3 개의세마포어집합을얻음 semflg : 세마포어생성옵션, 세마포어객체에대한접근권한을설정 세마포어정보를갖는세마포어객체를생성하고세마포어객체 ID 를반환 세마포어객체 : semid_ds 구조체 struct semid_ds d { struct ipc_perm sem_perm; // 접근허가내용, ipc.h time_t sem_otime; // 최근세마포어조작시각 time_t sem_ctime; // 최근변경시각 struct t sem *sem_base; // 첫세마포어포인터 struct wait_queue *eventn; struct wait_queue *eventz; struct sem_undo *undo; // 배열안에있는 undo의수 ushort sem_nsems; // 세마포어멤버수 }; 40
semflg : 세마포어생성에관한옵션, 접근권한을설정 IPC_CREAT 같은 key값을사용하는세마포어가존재하면해당객체에대한 ID를리턴 같은 key값의세마포어가존재하지않으면새로운세마포어를생성하고그id를리턴 semget(key, nsems, IPC_CREAT mode); IPC_EXCL 같은 key 값을사용하는세마포어가존재하면 semget() 호출은실패하고 -1을리턴 IPC_CREAT와같이사용해야함 mode = 0660; semget(key, nsems, IPC_CREAT IPC_EXCL mode); IPC_PRIVATE key값이없는세마포어를생성 명시적으로 key값을사용할필요가없는경우에사용 세마포어생성후 fork() 를호출하면자식프로세스는세마포어객체의 ID 를상속받으므로접근이가능 부모와자식프로세스간의통신을위해편리하게사용 41
세마포어연산 세마포어의값을변경 ( 증가또는감소하는것 ) semop() 함수사용 int semop(int semid, struct sembuf *opeations, unsigned nsops); semid : 세마포어 ID operations : 세마포어를변경시킬값이있는구조체 sembuf 구조체사용 nsops : operations가몇개의리스트를가지는지명시 sembuf 구조체 struct sembuf { short sem_num; // 멤버세마포어번호 (0 번이첫번째멤버 ) short sem_op; // 세마포어연산내용 short sem_flg; // 조작플래그 }; 42
sembuf 구조체 sem_num 세마포어집합중몇번째멤버세마포어를연산할지구분하는번호 첫번째멤버세마포어일경우 0 을사용 sem_op 증가또는감소시킬값 : 1증가시키려면 +1, 3감소시키려면 -3 sem_flg 0, IPC_NOWAIT, SEM_UNDO 등을 bitmask 형태로취함 0 디폴트인블록킹모드로 semop() 를수행 어떤세마포어값을 N만큼감소시키려할때현재값이 N-1이하면세마포어값이 N 이상이될때가지블록 IPC_NOWAIT 세마포어값이부족해도블록되지않고리턴 리턴값은 -1이고에러코드는 EAGAIN SEM_UNDO 프로세스의종료시커널은해당세마포어연산을취소 어떤프로세스가세마포어값을감소한후바로비정상적으로종료되었을경우원래의값으로환원할기회를읽게되며이는다른프로세스가공유데이터를계속사용할수없게함 43
세마포어사용예 연필 3 자루, 노트 3 권의공유데이터가있고, 각프로세스는연필 1 자루와공책 1 권을동시에사용하여작업한다. 현재 10 개의프로세스가실행중 // 세마포어값을증감시키기위한 sembuf 구조체정의 struct sembuf increase[] = {{0, +1, SEM_UNDO}, {1, +1, SEM_UNDO}}; struct sembuf decrease[] = {{0, -1, SEM_UNDO}, {1, -1, SEM_UNDO}}; //nsems=2 : 연필 (0) 과노트 (1) 에대해각각의세마포어를생성 semid = semget(mykey, 2, IPC_CREAT mode); // 세마포어값감소 semop(semid, &decrease[0], 1) // 연필 semop(semid, &decrease[1], 1) // 노트 // 연필과노트를사용하는작업기술. // 세마포어값증가 semop(semid, &increase[0], 1) // 연필 semop(semid, &increase[1], 1) // 노트 44
세마포어제어 세마포어의사용종료, 세마포어값읽기및설정, 특정멤버세마포어를기다리는프로세스의수를확인할때사용하는함수 int semctl(int semid, int member_index, int cmd, union semun semarg); semid : 제어할대상의세마포어 ID member_index : 세마포어멤버번호 cmd : 수행할동작 semarg : cmd의종류에따라다르게사용되는인자 (semun 타입 ) union semun { int val; struct semid_ds buf; unsigned short int *array; struct seminfo * buf; void * pad; }; // SETVAL을위한값 // IPC_STAT, IPC_SET을위한버퍼 // GETALL, SETALL 을위한배열 // IPC_INFO를위한버퍼 // dummy 45
cmd IPC_STAT semid_ds 타입의세마포어객체를얻어오는명령 union semun semarg 인자에세마포어객체가리턴 member_index 인자는사용되지않음 (0) struct semid_ds ds semobj; union semun semarg; semarg.buf = &semobj; semctl(semid, 0, IPC_STAT, semarg); SETVAL 공유데이터의수를설정해주는작업 ( 세마포어객체초기화 ) semarg 에설정할값을지정하여 semarg.val 변수에입력 union semun semarg; unsigned short semvalue = 10; semarg.val = semvalue; semctl(semid, 2, SETVAL, semarg); // 2번세마포어값을 10으로설정 46
cmd SETALL 세마포어집합내의모든세마포어의값을초기화하는명령 semarg.array 인자에초기값을배열형태로지정 member_index는사용하지않음 unsigned short values = {3, 14, 1, 25}; union semun semarg; semarg.array = values; semctl(semid, 0, SETALL, semarg); //4개의세마포어의값을초기화 GETVAL 특정멤버세마포어의현재값을얻어오는명령 int n = semctl(semid, 2, GETVAL, 0); // 2 번세마포어값을구함 47
cmd GETALL 모든멤버세마포어의현재값을읽는데사용 union semun semarg; unsigned short semvalues[3]; // 세마포어의수는 3 semarg.array = semvalues; semctl(semid, 0, GETALL, semarg); for (i=0; i<3; i++) printf( %d번멤버세마포어의값 = %d\n, i, semvalue[i]); GETNCNT 세마포어값이원하는값이상으로증가되기를기다리는프로세스의수를얻는데사용 세마포어를기다리는프로세스의수를반환 union semun semarg 인자는사용안함 (0) semctl(semid, member_index, GETNCNT, 0); 48
cmd GETPID 특정멤버세마포어에대해마지막으로 semop() 함수를수행한프로세스의 PID 를얻는데사용 int pid = semctl(semid, member_index, GETPID, 0); IPC_RMID 세마포어를삭제하는명령 semctl(semid, 0, IPC_RMID, 0); 49
세마포어예제 (pen_and_note.c) 연필한자루와노트두권을 4개의프로세스가연필한자루와노트한권을동시에사용하는프로그램 실행결과 $ pen_and_note [pid:18178] 연필을들고 [pid:18178] 노트를들고 [pid:18178] 공부를함 [pid:18178] 연필을내려놓음 [pid:18178] 노트를내려놓음 [pid:18177] 연필을들고 [pid:18177] 노트를들고 [pid:18177] 공부를함 [pid:18177] 연필을내려놓음 [pid:18177] 노트를내려놓음... 50
공유메모리의동기화문제처리 (shmcontrol.c) 세마포어를이용해공유메모리의동기화를해결 공유메모리사용시동기화문제가있었던 shmbusyaccess.c를세마포어를사용하여해결 공유메모리접근함수 access_shm() 에서세마포어를이용하여 critical 영역으로보호하여두프로세스가 access_shm() 을동시에호출못하게한다. 실행결과 access_shm() 호출전후에세마포어를사용하기위하여 semop() 를호출. 은정상적으로공유메모리에접근하고있다는것을나타낸다. $ shmcotrol 1234 2345............ 51