1. 파이프
파이프는 프로세스 간 통신(IPC) 메커니즘으로, 프로세스들 사이에서 데이터를 전달하는 데 사용됩니다. 본질적으로 운영체제 커널이 관리하는 메모리 버퍼이며, 파일 디스크립터를 통해 데이터를 읽고 씁니다. 파이프의 핵심은 메모리 버퍼이지만, 운영체제는 파이프를 파일처럼 `read` 및 `write`와 같은 파일 작업 인터페이스로 다룰 수 있도록 구현합니다. 따라서 파이프는 특수한 파일이지만, 데이터를 영구적으로 저장하는 용도는 아닙니다.
1) 명명 파이프와 익명 파이프
- 익명 파이프(Anonymous Pipe): 친연 관계에 있는 프로세스(예: 부모-자식 프로세스) 사이에서만 사용 가능한 통신 메커니즘입니다.
- 단일 부모 프로세스와 그 자식 프로세스 간의 통신에 사용됩니다.
- 데이터 흐름은 관련 프로세스 간에만 전달되며, 파일 시스템을 거치지 않습니다.
- `pipe` 시스템 호출: 익명 파이프를 생성합니다.
int pipe(int pipe_descriptors[2]);int fd_pipe[2]; pipe(fd_pipe); // fd_pipe[0]은 읽기용 파일 디스크립터, fd_pipe[1]은 쓰기용 파일 디스크립터입니다.- `pipe_descriptors`: 두 개의 파일 디스크립터가 포함된 배열로, `pipe_descriptors[0]`은 읽기 끝(read end), `pipe_descriptors[1]`은 쓰기 끝(write end)을 나타냅니다.
- 반환 값: `0`은 성공을, `-1`은 오류를 나타내며 `errno`를 설정합니다.
- 명명 파이프(Named Pipe): 파일 시스템을 통해 통신 인터페이스를 제공하는 영구적인 파이프로, 친연 관계가 없는 프로세스 간 통신을 지원합니다.
- `mkfifo` 시스템 호출: 명명 파이프 파일을 생성합니다.
mkfifo [파일이름]mkfifo my_fifo_pipe // 'my_fifo_pipe'라는 명명 파이프 생성int mkfifo(const char *path, mode_t mode);mkfifo("my_fifo_pipe", 0644); // 'my_fifo_pipe'라는 파이프 생성 (권한 0644)- `path`: 생성할 명명 파이프의 경로명입니다.
- `mode`: 생성될 명명 파이프의 접근 권한을 지정합니다 (파일 권한과 동일, 예: `0644`는 소유자 읽기/쓰기, 그룹 및 기타 사용자 읽기 전용).
- 반환 값: `0`은 성공을, `-1`은 오류를 나타내며 `errno`를 설정합니다.
- `unlink` 시스템 호출: 파일 또는 디렉토리 링크를 삭제하는 데 사용됩니다. 명명 파이프를 삭제할 때도 사용합니다.
unlink [파일이름]unlink my_fifo_pipe // 'my_fifo_pipe'라는 파이프 삭제int unlink(const char* path);unlink("my_fifo_pipe"); // 'my_fifo_pipe'라는 파이프 삭제- `path`: 삭제할 파일 또는 명명 파이프의 경로명입니다.
- 반환 값: `0`은 성공을, `-1`은 오류를 나타내며 `errno`를 설정합니다.
- `mkfifo` 시스템 호출: 명명 파이프 파일을 생성합니다.
2) 파이프의 특징
- 순차적 읽기/쓰기: 파이프 데이터는 바이트 스트림 형태로 쓰기 끝에서 흘러들어와 읽기 끝에서 흘러나옵니다. 순차적으로만 읽고 쓸 수 있으며, 임의 접근을 지원하지 않습니다. 파이프는 파일 포인터 개념이 없으며 `seek` 작업을 지원하지 않습니다.
- 일회성 전달: 데이터는 파이프를 통해 한 프로세스에서 다른 프로세스로 전달되며, 읽고 나면 사라지므로 다시 읽을 수 없습니다. 이는 파이프가 임시적이며 저장 능력을 갖지 않음을 의미합니다.
- 반이중 통신(Half-Duplex): 파이프는 기본적으로 반이중이며, 데이터는 한 방향(쓰기 끝에서 읽기 끝으로)으로만 전달될 수 있습니다. 양방향 통신이 필요한 경우, 송신 및 수신을 위한 두 개의 파이프를 생성해야 합니다.
- 파일 디스크립터 기반: 파이프의 읽기 끝과 쓰기 끝은 각각 두 개의 파일 디스크립터에 해당하며, 이들은 관련 프로세스들 사이에서 공유됩니다. 파이프 생성 시 파일 디스크립터 배열이 반환됩니다. 프로세스가 종료되면 파이프도 자연스럽게 해제됩니다.
- 제한된 용량: 파이프의 메모리 버퍼 크기는 제한되어 있습니다 (일반적으로 운영체제에서 4KB 또는 64KB로 설정). 쓰기 끝에서 버퍼 용량을 초과하는 데이터를 쓰면, 읽기 끝에서 일부 데이터를 읽을 때까지 쓰기 작업은 블록(block)됩니다. 파이프를 읽을 때 파이프 버퍼에 데이터가 없으면, 데이터가 쓰여질 때까지 읽기 프로세스는 블록됩니다.
- 바이트 스트림 지향: 데이터는 파이프에 바이트 단위로 순차적으로 쓰여지고, 읽기 끝에서는 동일한 순서로 읽힙니다. 전송되는 데이터에는 명확한 경계 표시가 없으며, 읽기/쓰기 작업은 내용 분리보다는 바이트 수만 압니다. 파이프는 데이터의 특정 형식(예: 텍스트, 이진, 구조화된 데이터)에 상관없이 바이트 전송만 담당합니다. 특정 형식이나 구분자가 필요한 경우, 프로그램이 직접 정의하고 파싱해야 합니다. 바이트 스트림 지향은 파이프가 기록된 데이터의 순서대로만 읽을 수 있으며, 파일처럼 임의 접근(예: 오프셋 지정 읽기)을 지원하지 않음을 의미합니다.
부모-자식 프로세스가 협력할 때, 동기화 및 상호 배제 메커니즘을 통해 자원 접근을 조정하여 파이프 파일의 데이터 전송이 안전하고 신뢰할 수 있도록 보장합니다.
`PIPE_BUF`: 파이프와 관련된 중요한 상수로, 파이프 쓰기 작업이 원자적으로 완료될 수 있는 최대 데이터 양을 정의합니다.
- 쓰기 바이트 수가 `PIPE_BUF` 이하인 경우, 시스템은 전체 쓰기 작업이 다른 프로세스의 쓰기 작업에 의해 중단되지 않도록 보장합니다.
- 쓰기 바이트 수가 `PIPE_BUF`를 초과하는 경우, 시스템은 여러 개의 쓰기 작업으로 분할할 수 있으며, 이 때 다른 프로세스의 쓰기 작업이 중간에 삽입될 수 있습니다.
파이프의 4가지 상황:
- 읽기/쓰기 끝이 정상일 때, 파이프가 비어 있으면 읽기 끝은 블록됩니다.
- 읽기/쓰기 끝이 정상일 때, 파이프가 가득 차 있으면 쓰기 끝은 블록됩니다.
- 읽기 끝이 정상이고 쓰기 끝이 닫히면, 읽기 끝은 `0`을 읽어 파일의 끝에 도달했음을 알리며 블록되지 않습니다.
- 쓰기 끝이 닫히고 읽기 끝이 정상일 때, 운영체제는 `13-SIGPIPE` 신호를 통해 쓰기 중인 프로세스를 종료시킵니다.
3) 익명 파이프와 명명 파이프 비교
| 특성 | 익명 파이프 | 명명 파이프 |
|---|---|---|
| 생성 방식 | `pipe()` 시스템 호출 사용 | `mkfifo()` 또는 `mknod()` 시스템 호출 사용 |
| 통신 범위 | 친연 관계에 있는 프로세스(부모-자식 등)에만 한정 | 친연 관계가 없는 프로세스 간 통신 지원 |
| 존재 시간 | 프로세스 종료 후 자동으로 소멸 | 파일 시스템에 파일 형태로 존재하며, 수동으로 삭제해야 함 |
| 접근 방식 | 파일 디스크립터 기반으로 접근 | 파일 경로를 통해 접근 |
| 용량 제한 | 운영체제에서 설정하는 버퍼 크기 (예: 4KB/64KB) | 운영체제에서 설정하는 버퍼 크기 |
| 양방향 통신 | 두 개의 익명 파이프 생성 필요 | 두 개의 명명 파이프 생성 필요 |
2. 공유 메모리
System V 공유 메모리는 여러 프로세스가 하나의 메모리 영역을 공유할 수 있도록 허용하는 프로세스 간 통신(IPC) 메커니즘입니다. 이 메커니즘을 통해 프로세스 간 데이터 복사 오버헤드를 피함으로써 효율적으로 데이터를 교환할 수 있습니다.
1) 공유 메모리 시스템 호출
- `ftok()`: IPC(공유 메모리, 세마포어, 메시지 큐 등)에서 사용되는 고유 식별자를 생성하는 함수입니다.
key_t ftok(const char *pathname, int proj_id);key_t ipc_key = ftok("/tmp/my_shm_file", 123); // '/tmp/my_shm_file' 파일을 기반으로 키 생성- `pathname`: 실제 파일 시스템에 존재하는 파일의 경로명입니다. 보통 `/tmp`나 프로그램 디렉토리 내의 파일을 선택합니다.
- `proj_id`: 동일한 경로 내에서 생성된 식별자가 여전히 고유하도록 보장하는 사용자 정의 값입니다.
- 반환 값: `key_t` 타입의 고유 식별자(일반적으로 정수 타입)를 반환하며, 실패 시 `-1`을 반환하고 `errno`를 설정합니다.
- `shmget()`: 공유 메모리 세그먼트를 생성하거나 기존 세그먼트의 ID를 가져옵니다.
int shmget(key_t key, size_t size, int shmflg);// 1024바이트 크기의 공유 메모리 세그먼트를 생성하고, 권한은 0666으로 설정 int shared_mem_id = shmget(ipc_key, 1024, IPC_CREAT | 0666);- `key`: 공유 메모리를 얻을 때 사용되는 고유 식별자입니다. 생성 시 `key`는 이미 존재해야 합니다.
- `size`: 공유 메모리 세그먼트의 크기(바이트 단위)입니다.
- `shmflg`: 권한 플래그로, 파일 권한과 유사합니다.
- `IPC_CREAT`: 지정된 `key`에 해당하는 IPC 객체(공유 메모리 세그먼트, 메시지 큐 또는 세마포어)가 존재하지 않으면 새로 생성합니다. 이미 존재하면 기존 공유 메모리 세그먼트의 식별자를 반환합니다.
- `IPC_EXCL`: `IPC_CREAT`와 함께 사용되며(예: `IPC_CREAT | IPC_EXCL`), 요청된 공유 메모리가 새로 생성되는 것임을 보장합니다. 지정된 `key`에 해당하는 공유 메모리 세그먼트가 이미 존재하면 오류를 반환하고 `errno`를 `EEXIST`로 설정합니다.
- 권한 설정: `0666`, `0664` 등 (리눅스 파일 권한과 동일).
- 반환 값: 공유 메모리 세그먼트의 고유 식별자(`shmid`)를 반환하며, 실패 시 `-1`을 반환합니다.
1. 왜 운영체제가 직접 `key`를 생성하지 않을까요?
- 운영체제가 무작위로 `key`를 생성한다면, 여러 프로세스가 이 `key`를 공유하기 위해 추가적인 메커니즘이 필요하여 복잡성이 증가합니다.
- 프로그램 재시작 시, 이전에 생성된 IPC 리소스에 접근하는 방법을 보장하기 어렵습니다. `ftok` 메커니즘은 동일한 경로와 `proj_id`를 기반으로 동일한 `key`를 얻을 수 있도록 보장합니다.
- 시스템이 생성한 `key`는 시간이나 사용 조건에 따라 충돌할 수 있지만, `ftok` 방식은 개발자가 경로와 `proj_id`를 기반으로 고유성을 수동으로 제어할 수 있게 합니다.
2. `key`와 `shmid`의 관계
- 두 프로그램이 하나의 공유 메모리 세그먼트를 얻고 싶을 때, 해당 공유 데이터 세그먼트의 `shmid`가 얼마인지 알 필요가 없습니다. 두 프로그램은 파일명과 지정된 `proj_id`를 약속하고, `ftok`를 사용하여 해당하는 고유 식별자(key)를 생성합니다. 이 `key`를 운영체제에 넘겨주면, 운영체제는 공유 메모리 세그먼트를 통합적으로 관리하며 `shmid`를 반환하고, 이후에는 이 `shmid`를 사용하여 작업을 수행합니다.
- `key`는 운영체제 내에서 고유성을 식별합니다.
- `shmid`는 프로세스 내에서 자원의 고유성을 나타냅니다.
- 간단히 기억하세요: `key`는 프로그래머가 사용하고, `shmid`는 시스템이 사용합니다!
- `shmat()`: 공유 메모리를 프로세스 주소 공간에 연결합니다.
void* shmat(int shmid, const void* shmaddr, int shmflg);// 공유 메모리 세그먼트를 주소 공간에 연결 char* shm_buffer = (char*) shmat(shared_mem_id, NULL, 0);- `shmid`: `shmget()`이 반환한 공유 메모리 세그먼트 식별자입니다.
- `shmaddr`: 공유 메모리가 연결될 주소를 지정합니다. 일반적으로 `NULL`을 전달합니다.
- `shmflg`: 연결 플래그로, 일반적으로 `0`입니다.
- 반환 값: 연결된 프로세스 공간의 가상 주소입니다.
- `shmdt()`: 공유 메모리 세그먼트를 현재 프로세스에서 분리합니다.
int shmdt(const void* shmaddr);// 공유 메모리 세그먼트 분리 shmdt(shm_buffer);- `shmaddr`: `shmat()`이 반환한 공유 메모리 주소입니다.
- 반환 값: 성공 시 `0`, 실패 시 `-1`을 반환합니다.
- `shmctl()`: 공유 메모리를 제어합니다.
int shmctl(int shmid, int cmd, struct shmid_ds* buf);// 공유 메모리 세그먼트 삭제 shmctl(shared_mem_id, IPC_RMID, NULL);- `shmid`: `shmget()`이 반환한 공유 메모리 세그먼트 식별자입니다.
- `cmd`: 제어 명령입니다.
- `IPC_RMID`: 공유 메모리 세그먼트를 삭제합니다.
- `IPC_STAT`: `shmid_ds` 구조체의 데이터를 공유 메모리의 현재 연관 값으로 설정합니다.
- `IPC_SET`: 프로세스가 충분한 권한을 가지고 있다면, 공유 메모리의 현재 연관 값을 `shmid_ds` 데이터 구조체에 주어진 값으로 설정합니다.
- `buf`: 제어 정보를 전달하는 데 사용되는 구조체 포인터입니다. 삭제 시에는 `NULL`이 될 수 있습니다.
- 반환 값: 명령에 따라 반환 값이 다르며, 실패 시 `-1`을 반환합니다.
1. 공유 메모리 생성 및 쓰기:
#include
#include
#include
#include
#include // for exit
int main() {
key_t ipc_key = ftok("my_shm_data", 123); // 고유 키 생성 (ftok 파일이 실제 존재해야 함)
if (ipc_key == -1) {
perror("ftok failed");
exit(EXIT_FAILURE);
}
// 공유 메모리 세그먼트 생성 (1024바이트)
int shared_mem_id = shmget(ipc_key, 1024, 0666 | IPC_CREAT);
if (shared_mem_id == -1) {
perror("shmget failed");
exit(EXIT_FAILURE);
}
// 공유 메모리 세그먼트를 프로세스 주소 공간에 연결
char* shm_buffer = (char*) shmat(shared_mem_id, NULL, 0);
if (shm_buffer == (char*)-1) {
perror("shmat failed");
exit(EXIT_FAILURE);
}
printf("공유 메모리에 데이터 쓰기: ");
fgets(shm_buffer, 1024, stdin); // 공유 메모리에 데이터 쓰기
printf("메모리에 쓰여진 데이터: %s\n", shm_buffer);
// 공유 메모리 세그먼트 분리
if (shmdt(shm_buffer) == -1) {
perror("shmdt failed");
exit(EXIT_FAILURE);
}
// 중요: 이 프로세스에서 공유 메모리를 삭제하지 않고,
// 읽기 프로세스에서 삭제하는 것이 일반적입니다.
// shmctl(shared_mem_id, IPC_RMID, NULL);
return 0;
}
2. 공유 메모리 읽기:
#include
#include
#include
#include // for exit
int main() {
key_t ipc_key = ftok("my_shm_data", 123); // 동일한 고유 키 생성
if (ipc_key == -1) {
perror("ftok failed");
exit(EXIT_FAILURE);
}
// 공유 메모리 세그먼트 가져오기
int shared_mem_id = shmget(ipc_key, 1024, 0666);
if (shared_mem_id == -1) {
perror("shmget failed");
exit(EXIT_FAILURE);
}
// 공유 메모리 세그먼트를 프로세스 주소 공간에 연결
char* shm_buffer = (char*) shmat(shared_mem_id, NULL, 0);
if (shm_buffer == (char*)-1) {
perror("shmat failed");
exit(EXIT_FAILURE);
}
printf("메모리에서 읽은 데이터: %s\n", shm_buffer);
// 공유 메모리 세그먼트 분리
if (shmdt(shm_buffer) == -1) {
perror("shmdt failed");
exit(EXIT_FAILURE);
}
// 공유 메모리 세그먼트 삭제 (모든 프로세스가 분리된 후 물리 메모리 해제)
if (shmctl(shared_mem_id, IPC_RMID, NULL) == -1) {
perror("shmctl IPC_RMID failed");
exit(EXIT_FAILURE);
}
printf("공유 메모리 세그먼트가 삭제되었습니다.\n");
return 0;
}
2) 공유 메모리 원리
- 공유 메모리 생성
- `shmget()`을 호출하여 공유 메모리 세그먼트를 생성합니다.
- 운영체제는 공유 메모리 세그먼트에 물리 메모리를 할당하고 고유 식별자(`shmid`)를 생성합니다.
- 시스템은 공유 메모리 테이블(일반적으로 커널 데이터 구조의 일부)을 유지하여 공유 메모리의 메타데이터(예: 크기, 권한, 참조 카운트 등)를 기록합니다.
1. 메타데이터 저장: 커널은 `shmid_ds` 구조체를 사용하여 공유 메모리 세그먼트의 메타 정보를 저장합니다.
struct shmid_ds { struct ipc_perm shm_perm; // 권한 정보, key 포함 size_t shm_segsz; // 메모리 세그먼트 크기 time_t shm_atime; // 마지막 연결 시간 time_t shm_dtime; // 마지막 분리 시간 time_t shm_ctime; // 생성 시간 pid_t shm_cpid; // 공유 메모리를 생성한 프로세스 ID pid_t shm_lpid; // 마지막으로 공유 메모리에 접근한 프로세스 ID shmatt_t shm_nattch; // 현재 이 공유 메모리 세그먼트에 연결된 프로세스 수 (shmat() 호출 수) // ... 기타 필드 };2. `ipcs`: 생성된 공유 메모리 확인
ipcs [옵션]ipcs -m // 공유 메모리 상태만 표시- `-m`: 공유 메모리 상태만 표시합니다.
- `-q`: 메시지 큐 상태만 표시합니다.
- `-s`: 세마포어 상태만 표시합니다.
- 메모리 매핑
- `shmat()` 호출 시, 운영체제는 공유 메모리 세그먼트의 물리 페이지를 호출 프로세스의 가상 주소 공간에 매핑합니다.
- 운영체제는 페이지 테이블을 수정하여 프로세스의 가상 주소가 공유 메모리의 물리 주소를 가리키도록 합니다.
- 반환된 가상 주소는 프로세스가 일반 변수를 다루듯이 직접 접근할 수 있습니다.
- 다중 프로세스 접근
- 서로 다른 프로세스가 `shmat()`을 호출하면, 이들의 가상 주소 공간은 모두 동일한 물리 메모리 영역을 가리키게 됩니다.
- 물리 메모리를 수정함으로써 데이터는 해당 공유 메모리 세그먼트에 연결된 모든 프로세스에 의해 접근 가능해집니다.
- 분리 및 정리
- 공유 메모리 세그먼트 분리: `shmdt()`를 호출하여 공유 메모리를 프로세스의 가상 주소 공간에서 매핑 해제합니다. 물리 메모리는 참조 카운트가 0보다 큰 한 계속 존재합니다.
- 공유 메모리 세그먼트 삭제: `shmctl()`을 `IPC_RMID` 명령과 함께 호출하여 공유 메모리 세그먼트를 삭제합니다. 모든 프로세스가 분리된 후에야 운영체제는 관련 물리 메모리를 해제합니다.
1. `ipcrm`: 특정 공유 메모리 세그먼트 삭제
ipcrm -m [shmid]
3) 공유 메모리의 특징
공유 메모리의 주요 특징은 효율적이고 직접적이며 낮은 오버헤드로 프로세스 간 데이터를 공유하는 것이지만, 동시에 프로세스 간 동기화 및 보안 문제를 야기하므로 데이터 충돌과 불일치를 피하기 위한 적절한 동기화 메커니즘이 필요합니다.
- 효율적인 통신 방식
- 공유 메모리는 데이터를 커널을 통해 복사할 필요가 없으므로 프로세스 간 통신 중 가장 빠른 방법입니다. 데이터가 공유 메모리 영역에서 직접 공유되어 프로세스 간 데이터 복사 오버헤드를 피합니다.
- 프로세스는 공유 메모리 내의 데이터를 직접 읽고 쓸 수 있으며, 데이터 전송을 위한 컨텍스트 스위칭 횟수를 줄입니다.
- 메모리 영역 공유
- 여러 프로세스가 동일한 물리 메모리 블록을 각자의 가상 주소 공간에 매핑할 수 있습니다. 이를 통해 공유된 메모리 영역에 접근하여 데이터를 교환할 수 있습니다.
- 각 프로세스는 공유 메모리의 매핑된 사본을 가지며, 일반 메모리에 접근하는 것처럼 읽고 쓸 수 있습니다.
- 동기화 메커니즘 필요
- 여러 프로세스가 메모리 내의 데이터를 공유할 수 있지만, 이는 경쟁 조건(race condition)과 데이터 불일치(공유 메모리에 아무 데이터도 쓰지 않아도 읽을 수 있음)와 같은 문제를 야기할 수 있습니다.
- 따라서 공유 메모리를 사용할 때는 일반적으로 세마포어, 뮤텍스(상호 배제 잠금), 또는 조건 변수와 같은 추가적인 동기화 메커니즘을 사용하여 공유 메모리에 접근하는 프로세스 간의 조정을 보장하고, 동시에 동일한 데이터를 수정하여 발생할 수 있는 불일치를 방지해야 합니다.
- 메모리 보호 및 접근 권한
- 공유 메모리 영역의 접근 권한은 운영체제의 제어 메커니즘을 통해 관리될 수 있습니다. 서로 다른 프로세스는 다른 접근 권한(예: 읽기/쓰기, 읽기 전용, 쓰기 전용)을 가질 수 있습니다.
- 운영체제는 프로세스가 공유 메모리에 접근할 때 경계를 넘어 접근하거나 다른 프로세스의 데이터를 손상시키지 않도록 보장합니다.
- 생명 주기 관리
- 공유 메모리의 생명 주기는 일반적으로 운영체제에 의해 관리됩니다. 공유 메모리 세그먼트가 생성되면, 운영체제는 물리 메모리 영역을 할당하고 여러 프로세스가 이 메모리를 매핑하고 사용할 수 있도록 허용합니다. 프로세스는 `shmdt`(매핑 해제) 작업을 통해 공유 메모리를 분리할 수 있지만, 공유 메모리 자체는 프로세스가 종료된 후 자동으로 파괴되지 않으며, 일반적으로 `shmctl` 또는 유사한 함수를 통해 삭제해야 합니다.
- 공유 메모리 세그먼트의 생명 주기는 `shmctl`을 호출한 프로세스에 의해 제어됩니다 (즉, 생성한 주체가 삭제를 책임집니다).
3. 세마포어
세마포어(Semaphore)는 프로세스 간 통신의 중요한 메커니즘 중 하나로, 동기화 및 상호 배제 문제를 해결하는 데 사용됩니다. 이는 다중 스레드 프로그래밍과 다중 프로세스 프로그래밍에서 공유 자원에 대한 접근을 제어하는 데 널리 활용됩니다.
1) 세마포어 시스템 호출
- `semget()`: 세마포어 집합을 생성하거나 기존 세마포어 집합의 ID를 가져옵니다.
int semget(key_t key, int num_sems, int semflg);// 하나의 세마포어를 생성하고, 권한은 0666으로 설정 int semaphore_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);- `key`: 세마포어를 얻을 때 사용되는 고유 식별자입니다. 생성 시 `key`는 이미 존재해야 합니다. `IPC_PRIVATE`는 키를 특정 파일과 연결하지 않고, 해당 프로세스에서만 유효한 세마포어 집합을 생성할 때 사용됩니다.
- `num_sems`: 세마포어 집합 내의 세마포어 개수입니다.
- `semflg`: 권한 플래그로, 파일 권한과 유사합니다.
- `IPC_CREAT`: 지정된 `key`에 해당하는 IPC 객체(공유 메모리 세그먼트, 메시지 큐 또는 세마포어)가 존재하지 않으면 새로 생성합니다. 이미 존재하면 기존 세마포어 집합의 식별자를 반환합니다.
- `IPC_EXCL`: `IPC_CREAT`와 함께 사용되며(예: `IPC_CREAT | IPC_EXCL`), 요청된 세마포어가 새로 생성되는 것임을 보장합니다. 지정된 `key`에 해당하는 세마포어 집합이 이미 존재하면 오류를 반환하고 `errno`를 `EEXIST`로 설정합니다.
- 권한 설정: `0666`, `0664` 등.
- 반환 값: 세마포어 집합의 고유 식별자(`semid`)를 반환하며, 실패 시 `-1`을 반환합니다.
- `semctl()`: 세마포어에 대한 제어 작업을 수행합니다. 초기화, 값 가져오기, 삭제 등이 포함됩니다.
int semctl(int semid, int semnum, int cmd, ...);// 첫 번째 세마포어 (인덱스 0)의 값을 1로 초기화 semctl(semaphore_id, 0, SETVAL, 1); // 세마포어 집합 삭제 semctl(semaphore_id, 0, IPC_RMID);- `semid`: 세마포어 집합의 식별자입니다.
- `semnum`: 세마포어의 인덱스(0부터 시작)입니다.
- `cmd`: 제어 명령입니다.
- `SETVAL`: 세마포어의 값을 설정합니다.
- `GETVAL`: 세마포어의 값을 가져옵니다.
- `IPC_RMID`: 세마포어 집합을 삭제합니다.
- 선택적 매개변수 `arg`: `cmd`에 따라 `int` 또는 `union semun` 타입의 값일 수 있습니다.
- 반환 값: 명령에 따라 반환 값이 다르며, 실패 시 `-1`을 반환합니다.
- `semop()`: 세마포어에 대한 P 연산(wait) 또는 V 연산(signal)과 같은 작업을 실행합니다.
int semop(int semid, struct sembuf *semaphore_ops, size_t num_ops);struct sembuf wait_op = {0, -1, 0}; // P 연산 (세마포어 값 1 감소) struct sembuf signal_op = {0, 1, 0}; // V 연산 (세마포어 값 1 증가) // P 연산 실행 (자원 획득 시도) semop(semaphore_id, &wait_op, 1); // 임계 영역 코드 (Critical Section) printf("임계 영역 진입\n"); // V 연산 실행 (자원 반납) printf("임계 영역 종료\n"); semop(semaphore_id, &signal_op, 1);- `semid`: 세마포어 집합의 식별자입니다.
- `semaphore_ops`: `struct sembuf` 타입의 작업 배열입니다.
// 구조체 정의 struct sembuf { unsigned short sem_num; // 세마포어 인덱스 (0부터 시작) short sem_op; // 작업 값 (-1: P 연산, +1: V 연산) short sem_flg; // 작업 플래그 (예: SEM_UNDO - 프로세스 종료 시 자동 원복) }; - `num_ops`: 작업의 개수입니다.
- 반환 값: 성공 시 `0`, 실패 시 `-1`을 반환합니다.
프로세스 통신의 본질은 서로 다른 프로세스가 동일한 자원을 볼 수 있게 하는 것입니다.