본문 바로가기
Computer Science/UNIX & Linux

[UNIX/Linux] ep10-1) 메시지 큐, 공유 메모리

by 클레어몬트 2024. 11. 6.

https://claremont.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-IPCInter-Process-Communication

[운영체제] IPC(Inter-Process Communication)

https://claremont.tistory.com/entry/ep2-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4process [운영체제] ep2) 프로세스(process)ㅁ프로세스(process): 실행 중인 프로그램 (실행/스케줄링의 단위 및 자료구조)보조기억장치에 저장

claremont.tistory.com

 
[ep10의 학습목표]
1. UNIX 시스템 V에서 제공하는 IPC 기법
2. 메시지 큐를 이용한 통신 프로그램 작성
3. 공유 메모리를 이용한 통신 프로그램 작성
4. 세마포를 이용한 IPC 기법
 
 
UNIX는 크게 BSD 계열과 시스템 V 계열로 구분할 수가 있다
시스템 V IPC: 시스템 V 계열 UNIX에서 개발한 프로세스 간 통신 방법(IPC)
1. 메시지 큐
2. 공유 메모리
3. 세마포
 
 
IPC 객체: + ID
시스템 V IPC를 사용하려면 IPC 객체를 생성해야 하는데 이를 위해 공통으로 사용하는 기본 요소가 키와 식별자(ID)이다. IPC 객체는 키를 통해 생성되며, 생성된 객체는 ID를 통해 접근할 수 있다.
(객체를 생성하고 현재 사용 중인 각 IPC의 상태를 확인하고 사용을 마친 객체는 삭제할 수 있도록 관리 명령을 제공)

 
 
<키 생성 방법>
1. IPC_PRIVATE 지정
식별자를 알아야 통신할 수 있으므로 IPC_PRIVATE를 키로 지정해 생성된 식별자를 Server와 Client 모두 알 수 있게 생성
(fork() 함수로 생성된 부모-자식 프로세스 간 통신에서 유용하게 사용할 수 있다)
 
2. ftok() 함수: 경로명과 숫자값을 받아 키를 생성
Server와 Client가 같은 경로명과 숫자값을 지정하면 공통 식별자를 생성할 수 있다
 
[키 생성 주의사항]
• 같은 키로 생성된 식별자는 통신에 사용할 수 있음
• 미리 정해진 키를 서버와 클라이언트 프로세스가 공유할 수 있게 해야 함
• 헤더 파일이나 환경 설정 파일에 키를 저장해 공유할 수 있음, 다만 이 키를 제3의 프로세스가 먼저 사용하고 있으면 안 됨
※ 각자 현재 directory로 수정할 것, key값을 줄 경우 뒤 3자리 정수를 이용할 것
 
ㅇ key_t ftok(const char* pathname, int proj_id): 키 생성하기
- pathname : 파일 시스템에 이미 존재하는 임의의 파일 경로명
- proj_id : 키값을 생성할 때 지정하는 임의의 번호(1~255)
pathname에 지정한 경로명과 proj_id에 지정한 정숫값을 조합해 새로운 키를 생성, 이 키는 IPC 객체를 생성할 때 사용
(경로명은 파일 시스템에 존재해야 하며 접근 권한이 있어야 함)
성공하면 생성한 키값을 반환, 실패하면 –1을 반환
 
(IPC 공통 구조체)

struct ipc_perm {
    key_t __key; // 키값
    uid_t uid; // 구조체의 소유자 ID
    gid_t gid; // 구조체의 소유 그룹 ID
    uid_t cuid; // 구조체를 생성한 사용자 ID
    gid_t cgid; // 구조체를 생성한 그룹 ID
    unsigned short mode; // 구조체에 대한 접근 권한
    unsigned short __seq; // 일련번호
};

 
ㅁ시스템 V IPC 정보 검색
ㅇipcs 명령의 기본 형식
• ipcs: 시스템 V IPC의 정보를 검색하고 현재 상태를 확인하는 명령
• ipcs 명령을 실행하는 동안에도 IPC의 상태가 변경될 수 있음
• ipcs 명령은 검색하는 순간의 정확성만 보장
• 상태가 변경된 정보를 보려면 ipcs 명령을 다시 수행

ipcs [-ihVmqsaclptu]

 
① 일반 옵션
출력 옵션은 하나만 지정, 여러 개를 지정하면 마지막에 지정한 옵션이 적용
-i id : id로 지정한 특정 요소에 대한 상세 정보를 출력, 이 옵션은 –m, –q, -s 중 하나와 결합해 사용
-h : 도움말을 출력
-V : 버전 정보를 출력
 
② 자원 옵션
-m : 공유 메모리 정보만 검색
-q : 메시지 큐 정보만 검색
-s : 세마포어 정보만 검색
-a : 공유 메모리, 메시지 큐, 세마포어 모두의 정보를 검색, 이 옵션이 기본

ipcs 명령의 다양한 옵션을 적절히 조합해 원하는 정보를 검색할 수 있음

 
ㅇipcmk 명령

ipcmk [options]

 
-M size : size에 지정한 바이트 크기로 공유 메모리를 생성 (KB, MB, GB 단위를 사용)
-Q : 메시지 큐를 생성
-S number : number에 지정한 개수의 요소를 갖는 세마포어를 생성
-p mode : 자원의 접근 권한을 지정, 기본값은 0644
 
ㅇipcrm 명령

ipcrm [options]

-a : 모든 자원을 제거 (이 옵션은 자원을 모두 삭제하므로 주의해서 사용해야 함)
-M shmkey : shmkey로 생성한 공유 메모리의 마지막 연결이 해제된 후 공유 메모리를 제거
-m shmid : shmid로 지정한 공유 메모리를 삭제 (공유 메모리에 대한 마지막 해제 동작 이후에 관련된 메모리 세그먼트가 제거)
-Q msgkey : msgkey로 생성한 메시지 큐를 제거
-q msgid : msgid로 지정한 메시지 큐를 삭제
-S semkey : semkey로 생성한 세마포어를 삭제
-s semid : semid로 지정한 세마포어를 삭제
 
 
 
1. 메시지 큐: 우편함처럼 메시지 큐를 만든 후 이를 통해 메시지를 주고받음
메시지 큐는 파이프와 유사하나 파이프는 스트림 기반으로 동작하고 메시지 큐는 메시지(또는 패킷) 단위로 동작

생성, 전송, 수신, 제어

 
인자로 키와 플래그를 받아 메시지 큐 식별자를 반환
ㅇ int msgget(key_t key, int msgflg): 메시지 큐 식별자 생성
- key : 메시지 큐를 구별하는 키 (IPC_PRIVATE나 ftok() 함수로 생성한 키를 지정)
- msgflg : 메시지 큐의 속성을 설정하는 플래그 (플래그와 접근 권한을 지정)
 
(사용할 수 있는 플래그)
• IPC_CREAT(0001000) : 새로운 키면 식별자를 새로 생성
• IPC_EXCL(0002000) : 이미 존재하는 키면 오류가 발생
 
메시지 큐 식별자와 관련된 메시지 큐와 IPC 구조체가 새로 생성되는 경우
① key가 IPC_PRIVATE일 경우
② key가 IPC_PRIVATE이 아니며 key에 지정한 식별자와 관련된 다른 메시지 큐가 없고 플래그(msgflg)에 IPC_CREAT가 설정되어 있는 경우
만약 두 가지 경우가 아니라면 msgget( ) 함수는 기존 메시지 큐의 식별자를 반환
만약 msgflg에 IPC_CREAT와 IPC_EXCL이 둘 다 설정되어 있고 key와 관련된 메시지 큐가 이미 존재한다면 오류를 반환
msgget() 함수는 수행에 성공하면 메시지 큐 식별자를 음수가 아닌 정수로 반환, 실패하면 -1을 반환
 
(msqid_ds 구조체)

struct msqid_ds {
    struct ipc_perm msg_perm; // IPC 공통 구조체(ipc_perm)
    time_t msg_stime; // 마지막으로 메시지를 보낸 시각
    time_t msg_rtime; // 마지막으로 메시지를 읽은 시각
    time_t msg_ctime; // 마지막으로 메시지 큐의 권한을 바꾼 시각
    unsigned long __msg_cbytes; // 현재 메시지 큐에 있는 메시지의 총 바이트 수
    msgqnum_t msg_qnum; // 메시지 큐에 있는 메시지의 개수
    msglen_t msg_qbytes; // 메시지 큐의 최대 크기(바이트 수)
    pid_t msg_lspid; // 마지막으로 메시지를 보낸 프로세스의 PID
    pid_t msg_lrpid; // 마지막으로 메시지를 읽은 프로세스의 PID
};

 
+ 식별자가 리턴할 때 메시지 큐 구조체 값의 설정

msg_perm.cuid, msg_perm.uid : 함수를 호출한 프로세스의 유효 사용자 ID로 설정
msg_perm.cgid, msg_perm.gid : 함수를 호출한 프로세스의 유효 그룹 ID로 설정
msg_perm.mode의 하위 9비트 : msgflg 값의 하위 9비트로 설정
msg_qnum, msg_lspid, msg_lrpid, msg_stime, msg_rtime : 0으로 설정
msg_ctime : 현재 시간으로 설정
msg_qbytes : 시스템의 제한값으로 설정

 
msgget() 함수로 메시지 큐 식별자를 생성하는 예

key_t key;
int id;

key = ftok("keyfile", 1);
id = msgget(key, IPC_CREAT | 0640); // 접근 권한 = 0640(rw-r-----)

 
 
msgget() 함수가 리턴한 메시지 큐(msqid)를 통해 크기가 msgsz인 메시지를 메시지 버퍼(msgp)에 담아 전송
ㅇ int msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg): 메시지 전송
- msqid : msgget 함수로 생성한 메시지 큐 식별자
- msgp : 메시지를 담고 있는 메시지 버퍼의 주소
- msgsz : 메시지의 크기(0~시스템이 정한 최댓값)
- msgflg : 블록 모드(0) / 비블록 모드(IPC_NOWAIT)
메시지를 담고 있는 메시지 버퍼는 msgbuf 구조체를 사용
세 번째 인자인 msgsz에는 전송하는 메시지의 크기를 지정
msgflg에는 메시지 큐가 가득 찼을 때의 동작을 지정 (0이나 IPC_NOWAIT을 지정)
만약 수행에 실패하면 -1을 반환하고 메시지는 전송하지 않는다
 
msgbuf 구조체는 sys/msg.h 파일에 정의되어 있고, man msgsnd에서 확인할 수 있다

// msgbuf 구조체는 메시지 큐를 생성할 때 앞의 형식대로 사용자가 정의해 사용
struct msgbuf {
    long mtype; // 메시지 유형으로 양수를 지정
    char mtext[1]; // msgsnd() 함수의 msgsz로 지정한 크기의 버퍼로 메시지 내용이 저장
};

 
 
msgsnd() 함수가 수행에 성공하면 msqid_ds 구조체의 항목에서 msg_qnum 값이 1 증가하고 msg_lspid는 msgsnd() 함수를
호출한 프로세스의 ID로 설정
 
(client1.c 코드)

#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 메시지 버퍼로 사용할 구조체를 문법에 따라 정의
// 메시지를 저장할 배열의 크기는 송수신할 메시지의 크기에 따라 적절하게 조절
struct mymsgbuf {
    long mtype;
    char mtext[80];
};

int main() {
    key_t key;
    int msgid;
    struct mymsgbuf mesg;

    key = ftok("keyfile", 1); // 키를 정의
    msgid = msgget(key, IPC_CREAT | 0644); // 메시지 식별자를 생성
    if (msgid == -1) {
        perror("msgget");
        exit(1);
    }

    mesg.mtype = 1; // 전송한 메시지 버퍼를 설정 - 메시지 유형을 1로 정의
    strcpy(mesg.mtext, "Message Q Test"); // 메시지 버퍼 배열에 문자열을 복사

    // msgsnd() 함수를 사용해 메시지를 전송
    if (msgsnd(msgid, (void*)&mesg, 80, IPC_NOWAIT) == -1) {
        perror("msgsnd");
        exit(1);
    }

    return 0;
}
// 실행 결과: 생성된 메시지 큐를 ipcs -q 명령으로 확인한 실행 결과를 보면 생성된 메시지 큐의 식별자는 1
// 접근 권한은 0644로 나오고 messages의 값은 1로 증가하며 메시지의 바이트 수는 80바이트임을 알 수 있음
// ipcrm 명령으로 메시지 큐를 삭제할 수 있음

 
 
메시지 큐로 메시지를 수신하는 데 사용
msqid가 가리키는 메시지 큐에서 msgtyp이 지정하는 메시지를 읽어 msgp가 가리키는 메시지 버퍼에 저장
ㅇ ssize_t msgrcv(int msqid, void* msgp, size_t msgsz, long msgtyp, int msgflg): 메시지 수신
- msqid : msgget() 함수로 생성한 메시지 큐 식별자
- msgp : 메시지를 담고 있는 메시지 버퍼의 주소
- msgsz : 메시지 버퍼의 크기
- msgtyp : 읽어올 메시지의 유형
- msgflg : 블록 모드(0) / 비블록 모드(IPC_NOWAIT)
메시지 버퍼의 크기는 msgsz에 지정하고, msgtflg는 메시지 큐가 비었을 때 어떻게 동작할 것인지 알림
 
(네 번째 인자 msgtyp에 지정할 수 있는 값)
• 0 : 메시지 큐의 가장 앞에 있는 메시지를 읽어옴
• 양수 : 메시지 큐에서 msgtyp로 지정한 유형과 같은 메시지를 읽어옴
• 음수 : 메시지 유형이 msgtyp로 지정한 값의 절댓값과 같거나 작은 메시지를 읽어옴
 
다섯 번째 인자인 msgflg에는 msgsnd( ) 함수처럼 블록 모드 / 비블록 모드를 지정
• msgflg가 0이면 메시지 큐에 메시지가 올 때까지 기다림
• msgflg가 IPC_NOWAIT이면 메시지 큐가 비었을 때 기다리지 않고 즉시 오류를 리턴
• msgrcv() 함수의 수행이 성공하면 msqid_ds 구조체 항목에서 msg_qnum 값이 1 감소하고 msg_lrpid는 msgrcv() 함수를 호출한 프로세스의 ID로 설정
• msg_rtime은 현재 시각으로 설정
수행에 성공하면 읽어온 메시지의 바이트 수를 반환, 실패하면 -1을 반환하고 메시지를 읽어오지 않는다
 
(server1.c 코드)

#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>

// 메시지 버퍼로 사용할 구조체를 문법에 따라 정의
struct mymsgbuf {
    long mtype;
    char mtext[80];
};

int main() {
    struct mymsgbuf inmsg;
    key_t key;
    int msgid, len;

    key = ftok("keyfile", 1); // 경로명과 정숫값을 사용해 키를 생성
    if ((msgid = msgget(key, 0)) < 0) { // msgget() 함수의 두 번째 인자를 0으로 지정해 기존 메시지 큐의 식별자를 리턴
        perror("msgget");
        exit(1);
    }

    // msgrcv() 함수를 사용해 메시지를 읽어옴
    // 버퍼의 크기는 80바이트로 하고 큐가 비었을 경우 기다리도록 지정
    len = msgrcv(msgid, &inmsg, 80, 0, 0); 
    printf("Received Msg = %s, Len=%d\n", inmsg.mtext, len);

    return 0;
}
// 실행 결과 앞선 예제(client1.c)에서 보낸 메시지를 제대로 읽어왔음을 알 수 있음
// ipcs -q 명령으로 메시지 큐의 상태를 보면 큐의 메시지 개수가 1에서 0으로 감
// 메시지 큐 내의 총 바이트 수도 0으로 감소

 
 
ㅇ int msgctl(int msqid, int cmd, struct msqid_ds* buf): 메시지 제어
- msqid : msgget() 함수로 생성한 메시지 큐 식별자
- cmd : 수행할 제어 기능
- buf : 제어 기능에 사용되는 메시지 큐 구조체의 주소
msqid로 지정한 메시지 큐에서 cmd에 지정한 제어를 수행
buf는 cmd의 종류에 따라 제어값을 지정하거나 읽어오는 데 사용
수행에 성공하면 0을, 실패하면 -1을 반환
 
(cmd에 지정하는 기능)
• IPC_STAT : 현재 메시지 큐의 정보를 buf로 지정한 메모리에 저장
• IPC_SET : 메시지 큐의 정보 중 msg_perm.uid, msg_perm.gid, msg_perm. mode, msg_qbytes 값을 세 번째 인자로 지정한 값으로 변경 (이 명령은 root 권한이 있거나 유효 사용자 ID인 경우만 사용할 수 있음, msg_qbytes는 root 권한이 있어야 변경할 수 있음)
• IPC_RMID : msqid로 지정한 메시지 큐를 제거하고 관련된 데이터 구조체를 제거
• IPC_INFO : Linux에서만 사용할 수 있으며 메시지 큐의 제한값을 buf에 저장 (이때 buf는 msginfo 구조체로 형변환을 해야 함)
 
(msginfo 구조체)

struct msginfo {
    int msgpool; // 메시지 데이터를 저장할 수 있는 버퍼 공간의 크기(KB)
    int msgmap; // 메시지 맵의 최대 항목 수
    int msgmax; // 한 메시지에 저장할 수 있는 메시지의 최대 크기(바이트)
    int msgmnb; // 메시지 큐에 작성할 수 있는 최대 크기(바이트)로, msgget() 함수로 메시지 큐를 생성할 때 msg_qbytes 값 초기
화에 사용
    int msgmni; // 메시지 큐의 최대 개수
    int msgssz; // 메시지 세그먼트 크기
    int msgtql; // 시스템에 있는 모든 메시지 큐의 최대 메시지 개수
    unsigned short msgseg; // 세그먼트의 최대 개수
};

 

#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    key_t key;
    int msgid;

    key = ftok("keyfile", 1); // 키를 생성해 메시지 큐 식별자를 반환받음
    msgid = msgget(key, IPC_CREAT | 0644);
    if (msgid == -1) {
        perror("msgget");
        exit(1);
    }

    printf("Before IPC_RMID\n");
    system("ipcs -q");
    msgctl(msgid, IPC_RMID, (struct msqid_ds*)NULL); // 메시지 큐 식별자를 IPC_RMID 명령으로 제거
    printf("After IPC_RMID\n");
    system("ipcs -q");

    return 0;
}
// 실행 결과: msgctl() 함수를 호출하기 전에 printf로 출력했을 때는 메시지 큐 3이 있었는데
// msgctl() 함수를 호출한 후에는 메시지 큐가 제거되어 보이지 않음
// 메시지 큐 사용을 마치면 이처럼 msgctl() 함수를 사용해 반드시 메시지 큐를 삭제해야 함
메시지 큐가 제거된 모습

 
 
※ Message Queue Flags
▪ IPC_NOWAIT
- msgsnd에서 IPC_NOWAIT가 설정되지 않은 경우, 메시지를 보내는데 충분한 자원이 없으면 수면됨. 설정 시 전달할 수 없으면 즉시 -1로 복귀 (errno=EAGAIN)
- msgrcv에서 IPC_NOWAIT가 설정되지 않은 경우, 큐에 적당한 메시지가 없으면 수면됨. 설정 시 즉시 복귀
 
▪ MSG_NOERROR
메시지의 내용이 size보다 긴 경우, 설정되지 않으면 실패. 지정된 경우 초과분을 잘라냄
 
 
 
2. 공유 메모리(shared memory): 한 프로세스의 일부분을 다른 프로세스와 공유
메모리의 일부 공간을 두 독립적인 프로세스에서 공유하고, 해당 메모리를 통해 데이터를 주고받는다

 
ㅇ int shmget(key_t key, size_t size, int shmflg): 공유 메모리 식별자 생성
- key : IPC_PRIVATE 또는 ftok() 함수로 생성한 키
- size : 공유할 메모리의 크기 (이미 공유된 메모리의 식별자를 읽어오는 것이라면 무시)
- shmflg : 공유 메모리의 속성을 지정하는 플래그
shmflg에는 플래그와 접근 권한을 지정
사용할 수 있는 플래그는 msgget() 함수와 마찬가지로 IPC_CREAT와 IPC_EXCL
이 플래그들은 man msgget으로 세부 내용을 확인하고 사용
 
[공유 메모리 식별자와 관련된 공유 메모리와 데이터 구조체가 새로 생성되는 경우]
• key가 IPC_PRIVATE일 경우
• key가 0이 아니며 다른 식별자와 관련되어 있지 않고, 플래그(msgflg)에 IPC_CREAT가 설정될 경우
 
공유 메모리 정보를 담고 있는 구조체는 shmid_ds로, sys/shm.h 파일에 정의

struct shmid_ds {
    struct ipc_perm msg_perm; //  IPC 공통 구조체(shm_perm)
    size_t shm_segsz; // 공유 메모리 세그먼트의 크기를 (Byte 단위)
    time_t shm_atime; // 마지막으로 공유 메모리를 연결(shmat)한 시간
    time_t shm_dtime; // 마지막으로 공유 메모리의 연결을 해제(shmdt)한 시간
    time_t shm_ctime; // 마지막으로 공유 메모리의 접근 권한을 변경한 시간
    pid_tt_t shm_lpid; // 마지막으로 shmop 동작을 한 프로세스의 PID
    pid_t shm_cpid; // 공유 메모리를 생성한 프로세스의 PID
    shmatt_t shm_nattch; // 공유 메모리를 연결하고 있는 프로세스의 개수
    ...
};

 

#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    key_t key;
    int shmid;

    key = ftok("shmfile", 1); // ftok() 함수를 사용해 키를 생성
    shmid = shmget(key, 1024, IPC_CREAT|0644); // shmget() 함수를 사용해 공유 메모리를 생성
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    return 0;
}
// 실행 결과: 공유 메모리가 생성되었고 nattach의 값이 0이므로 아직 이 메모리에 연결한 프로세스가 없음을 알 수 있음

 
 
shmget() 함수로 생성한 공유 메모리의 식별자를 shmid에 지정하고, shmaddr에 공유 메모리를 연결할 주소를 지정
ㅇ void* shmat(int shmid, const void* shmaddr, int shmflg): 공유 메모리 연결
- shmid : shmget() 함수로 생성한 공유 메모리 식별자
- shmaddr : 공유 메모리를 연결할 주소
- shmflg : 공유 메모리에 대한 읽기/쓰기 권한
shmaddr에는 특별한 경우가 아니면 NULL을 지정
(shmaddr의 값이 NULL이면 시스템이 알아서 적절한 주소에 공유 메모리를 연결)
(만약 shmaddr의 값이 NULL이 아니고 shmflg에 SHM_RND가 지정되어 있으면 메모리는 SHMLBA의 가장 가까운 배수의
주소에 연결)
shmflg는 0이면 공유 메모리에 대해 읽기와 쓰기가 가능하고, SHM_RDONLY면 읽기만 가능
수행에 성공하면 연결된 공유 메모리의 시작 주소를 반환
 
 
shmaddr에 공유 메모리의 시작 주소를 지정
ㅇ int shmdt(const void* shmaddr): 공유 메모리 연결 해제
- shmaddr : 연결을 해제할 공유 메모리의 시작 주소
(이 시작 주소는 shmat() 함수의 리턴값)
 
 
shmid가 가리키는 공유 메모리에 cmd로 지정한 제어 기능을 수행
ㅇ int shmctl(int shmid, int cmd, struct shmid_ds* buf): 공유 메모리 제어
- shmid : shmget() 함수로 생성한 공유 메모리 식별자
- cmd : 수행할 제어 기능
- buf : 제어 기능에 사용되는 공유 메모리 구조체의 주소
buf에는 제어 기능에 따라 사용되는 공유 메모리 구조체의 주소를 지정
• PC_STAT : 현재 공유 메모리의 정보를 buf로 지정한 메모리에 저장
• IPC_SET : 공유 메모리의 정보 중 shm_perm.uid, shm_perm.gid, shm_perm.mode 값을 세 번째 인자로 지정한 값으로 변경 (이 명령은 root 권한이 있거나 유효 사용자 ID인 경우만 사용할 수 있음)
• IPC_RMID : shmid로 지정한 공유 메모리를 제거하고 관련된 데이터 구조체를 제거

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int shmid, i;
    char* shmaddr, *shmaddr2;

	// 키를 IPC_PRIVATE로 지정해 공유 메모리를 20바이트 크기로 생성
    shmid = shmget(IPC_PRIVATE, 20, IPC_CREAT | 0644);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    switch (fork()) { // 자식 프로세스 생성
        case -1:
            perror("fork");
            exit(1);
            break;
        case 0: /* 자식 프로세스가 수행하는 부분 */
            shmaddr = (char*)shmat(shmid, (char*)NULL, 0); // 공유 메모리를 연결
            printf("Child Process =====\n");
            for (i = 0; i < 10; i++) { // 반복문으로 'a' ~ 'j' 까지 문자 10개를 기록
                shmaddr[i] = 'a' + i;
			}
            shmdt((char*)shmaddr); // 공유 메모리를 해제
            exit(0);
            break;
        default: /* 부모 프로세스가 수행하는 부분 */
            wait(0); // 부모 프로세스는 일단 자식 프로세스가 종료하기를 기다림
            shmaddr2 = (char*)shmat(shmid, (char*)NULL, 0); // 공유 메모리를 연결
            printf("Parent Process =====\n");
            for (i = 0; i < 10; i++) { // 공유 메모리의 내용을 읽어서 출력
                printf("%c ", shmaddr2[i]);
			}
            printf("\n");
            sleep(5);
            shmdt((char*)shmaddr2); // 공유 메모리의 연결을 해제
            shmctl(shmid, IPC_RMID, (struct shmid_ds*)NULL); // shmctl() 함수를 사용해 공유 메모리를 제거
            break;
    }

    return 0;
}
// 실행 결과: 부모 프로세스가 데이터를 제대로 받아서 출력했음을 알 수 있음
부모 프로세스가 데이터를 공유 메모리를 통해 받음을 알 수 있다

 
※ 부모 프로세스 수행 부분에서 sleep(5) 함수를 사용한 이유는 다른 터미널에서 ipcs 명령으로 공유 메모리의 상태를 확인하기 위해서이다

 
 
[예제 11-6]
Listener(서버 역할) - server.c

#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void handler(int dummy) {
    ;
}

int main() {
    key_t key;
    int shmid;
    void* shmaddr;
    char buf[1024];
    sigset_t mask;
    
    // 공유 메모리를 생성
    key = ftok("shmfile", 1);
    shmid = shmget(key, 1024, IPC_CREAT|0666);
    
    // SIGUSR1 시그널을 제외하고 모든 시그널을 블로킹
    sigfillset(&mask);
    sigdelset(&mask, SIGUSR1);
    signal(SIGUSR1, handler);
    
    // SIGUSR1 시그널을 받을 때까지 기다림
    printf("Listener wait for Talker\n");
    sigsuspend(&mask);
    
    // 시그널을 받으면 공유 메모리를 연결하고 talker가 공유 메모리에 저장한 데이터를 읽어서 출력
    printf("Listener Start =====\n");
    shmaddr = shmat(shmid, NULL, 0);
    strcpy(buf, shmaddr);
    printf("Listener received : %s\n", buf);
    
    // 다시 공유 메모리에 응답 데이터를 저장한 후 잠시 sleep() 함수를 실행
    // 공유 메모리의 연결을 끊기 전에 sleep() 함수를 실행하는 이유는 talker가 ipcs를 실행할 시간을 주기 위해서
    strcpy(shmaddr, "Have a nice day\n");
    sleep(3);
    shmdt(shmaddr); // 공유 메모리와 연결을 해제

    return 0;
}

 
 
Talker(클라이언트 역할) - client.c

#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv) {
    key_t key;
    int shmid;
    void* shmaddr;
    char buf[1024];
    
    key = ftok("shmfile", 1); // talker는 listener와 같은 파일과 정숫값을 사용해 키를 생성
    shmid = shmget(key, 1024, 0); // 키값으로 listener가 만든 공유 메모리의 식별자를 읽어옴
    
	// 공유 메모리와 연결하고 해당 메모리에 인사말을 복사
    shmaddr = shmat(shmid, NULL, 0);
    strcpy(shmaddr, "Hello, I'm talker\n");
    
    kill(atoi(argv[1]), SIGUSR1); // 명령행 인자로 받은 listener의 PID를 지정하고 SIGUSR1 시그널을 전송
    sleep(2); // sleep() 함수를 수행해 잠시 기다렸다가 
    strcpy(buf, shmaddr); // 공유 메모리에서 listener가 보낸 응답을 읽어 출력
    
    printf("Listener said : %s\n", buf);
    system("ipcs -mo"); // 현재 공유 메모리의 상태 정보를 검색
    shmdt(shmaddr); // 공유 메모리 연결을 해제
    shmctl(shmid, IPC_RMID, NULL); // 공유 메모리를 제거

	return 0;
}

 
 
 
 
 
 
세마포는 ep)10-2에서 계속..
https://claremont.tistory.com/entry/UNIXLinux-ep10-2-%EC%84%B8%EB%A7%88%ED%8F%AC

[UNIX/Linux] ep10-2) 세마포

https://claremont.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-IPCInter-Process-Communication [운영체제] IPC(Inter-Process Communication)https://claremont.tistory.com/entry/ep2-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4process [운영체제] ep2)

claremont.tistory.com

 
참고 및 출처: 시스템 프로그래밍 리눅스&유닉스(이종원)