프로세스 간 통신 (Inter-Process Communication, IPC)
date
Apr 17, 2024
slug
ipc
status
Published
tags
Computer Science
summary
type
Post
쓰레드는 프로세스 안에서 데이터가 공유 되었지만, 프로세스들이 정보를 교환하는 방법은
fork
, exec
를 통해 열린 파일들을 전달하거나 파일 시스템을 거쳐야 했다.APUE에서는 전통적인 프로세스 간 통신 방법인 파이프와 FIFO, 메시지 대기열, 세마포어, 공유 메모리를 설명한다.
파이프 (Pipe)
시험 범위 X
XSI IPC
메시지 대기열, 세마포어, 공유 메모리를 이용하는 형태의 IPC들을 XSI IPC라고 부르며, 세 종류의 IPC에는 비슷한 점이 존재한다.
식별자와 키를 사용
IPC 구조체 또는 IPC 객체를 생성할 때 정수 식별자를 통해서 참조한다.
→ 메시지를 대기열에 보내거나 대기열에서 메시지를 가져올 때 대기열의 식별자만 알면 된다.
그러나 식별자는 IPC 객체의 내부적인 이름이며, 프로세스들이 동일한 IPC 객체에 접근하기 위해서 각 IPC 객체마다 외부 이름으로 쓰이는 키를 부여할 수 있다.
또한 FLAG 값을 통해 키를 어떻게 관리할 것인지 정의할 수 있다.
IPC_PRIVATE
IPC 객체가 항상 새로 생성되게 하는 상수 값이다. 이 기법의 단점은 서버가 정수 식별자를 파일에 기록하고 이후 클라이언트가 조회하기 위해서 파일 시스템 연산들이 필요하다고 한다.
→ 일반적으로 부모-자식 프로세스 관계에서 사용된다.
IPC_CREAT
IPC 객체를 생성할 때 사용하는 플래그이며, IPC 객체가 존재하지 않으면 새로 생성, 이미 존재하는 경우 해당 IPC 객체의 식별자를 반환한다.
IPC_EXCL
새로운 IPC 객체를 생성할 때 기존 객체가 참조되는 일을 없게 하려면 FLAG 값을 IPC_CREAT와 IPC_EXCL의 조합으로 사용하면 된다.
만약 IPC 객체가 이미 존재하는 경우
EEXIST(-1)
오류가 반환 된다. 구조체
IPC 객체를 생성하면 접근 권한 구조체인
ipc_perm
가 정의된다.IPC 종류에 따라 다른 구조체를 제공하며, 해당 구조체 내부에
ipc_perm
구조체가 공통으로 사용된다.
→ 메세지 큐 구조체인 msqid_ds
, 세마포어 구조체인 semid_ds
, 공유 메모리 구조체인 shmid_ds
가 있다.장점과 단점
프로세스 간 효율적인 통신과 동기화를 위해서 XSI IPC는 많은 이점이 있지만, 단점도 존재한다.
IPC 객체는 시스템 전역 객체이며 참조 횟수가 관리되지 않는다는 것이다.
메시지를 넣고 나서 종료했을 때
msgrcv
나 msgctl
을 호출하거나 ipcrm
명령을 실행하는 등 명시적으로 읽거나 삭제하기 전까지, 시스템이 재시동되기 전까지는 시스템에 그대로 유지된다.메시지 대기열
시험 범위 X
세마포어 (Semaphore)
- 자원을 제어하는 세마포어의 값을 점검한다.
- 값이 양수라면 프로세스는 자원을 사용할 수 있으며, 사용한다면 세마포어 값을 1 감소한다. → 자원을 사용했음을 뜻한다.
- 세마포어 값이 0이면 0보다 커질 때까지 프로세스는 sleep 상태를 유지하며, 깨어나면 다시 1단계로 돌아간다.
예제 코드
공유 메모리 (Shared Memory)
둘 이상의 프로세스가 메모리의 한 영역을 공유하게 하는 기능
클라이언트와 서버 사이에서 자료를 복사하거나 넘기는 작업이 없기 때문에 가장 빠른 형태의 IPC
공유 메모리를 사용할 때 신경 써야 하는 부분은 여러 프로세스들의 접근을 동기화하는 것이다.
→ 익명의 메모리 구역을 공유하여 사용하기 때문이다. - 쓰레드에서 lock, unlock을 사용하는 이유와 같다.
→ 이때 레코드 잠금, 뮤텍스를 사용할 수도 있으나 세마포어를 흔히 사용한다.
공유 메모리 식별자를 얻기 위해
shmget
함수를 사용한다. 반환 값을 통해 이미 생성되어 있는지, 생성해야 하는지 등을 판단할 수 있다.다양한 공유 메모리 연산을 수행하는 범용 함수이며, cmd 인자에 들어오는 값에 따라 다양한 동작을 수행한다.
IPC_STAT
이 구역에 대한
shmid_ds
구조체를 buf가 가리키는 곳에 저장한다.IPC_SET
buf가 가리키는 구조체의
shm_perm.uid
, shm_perm.gid
, shm_perm.mode
필드를 구역의 shmid_ds
구조체에 복사한다.
superuser 특권을 가진 프로세스만 실행 가능하다고 한다.IPC_RMID
공유 메모리 구역을 제거하는 기능이며, 제거된 이후에는
shmat
로 부착할 수 없다.공유 메모리 구역의 식별자를 얻은 후에는
shmat
함수를 호출해서 그 구역을 프로세스의 주소 공간에 부착한다.공유 메모리 구역을 다 사용했다면
shmdt
를 호출하여 구역에서 떼어내야 한다.
→ 그 구역의 식별자, 관련 자료구조를 시스템에서 제거한다는 뜻은 아니라고 한다. shmctl
의 IPC_RMID
명령을 사용해야 실제로 제거된다.
→ 더 이상 사용하지 않겠다는 것이지 제거하는 개념이 아님예제 코드
POSIX 세마포어
XSI 세마포어의 단점을 해결하기 위해 POSIX 세마포어 인터페이스가 고안되었다.
XSI 세마포어보다 높은 성능을 내도록 구현할 수 있고, 사용 방법도 간단하고 익숙하며, 우아하게 동작한다고 한다.
→ XSI 세마포어가 제거된 경우 errno를 EIDRM으로 설정하고 오류를 돌려줬지만, POSIX 세마포어는 세마포어의 마지막 참조를 해제할 때까지 정상적으로 동작 된다고 한다.
명명된 세마포어를 새로 생성하거나 기존의 것을 참조할 때
sem_open
을 사용한다.oflag는 공유 메모리의 flag와 같은 역할이며, 예를 들어
O_CREAT | O_EXCEL
플래그를 설정하면 항상 새 세마포어가 생성된다.세마포어를 다 사용한 이후에는
sem_close
를 호출해서 연관된 모든 자원을 해제한다.
그러나 shmdt
에서 설명했듯이 sem_close
도 호출한다고 세마포어의 값에 영향을 주지는 않는다.명명된 세마포어를 파괴하고 싶다면
sem_unlink
를 사용한다.
그러나 세마포어를 참조하는 프로세스가 남아있지 않을 때 제거가 되며,
그런 프로세스가 남아 있다면 참조되고 있는 모든 세마포어가 sem_close
등에 의해 종료될 때까지 연기된다.세마포어의 값을 감소할 때는
sem_wait
또는 sem_trywait
을 사용한다.만약 세마포어 값이 0인 상태에서
sem_wait
을 호출하면 호출이 차단된다.
→ 세마포어 값을 감소하는 동작을 성공하거나, signal(신호)에 의해 가로채여야 호출이 반환된다.차단을 피하고 싶다면
sem_trywait
을 사용하면 된다. - errno를 EAGAIN으로 설정한 후 차단 없이 -1
을 돌려준다.세마포어 값을 감소하면서 호출이 차단될 시간을 지정할 수도 있다. -
sem_timedwait
사용
→ 어떤 시간을 지정해도 세마포어 값이 감소되는 동작은 무조건 수행되며, 감소하지 못한 채로 시간이 만료되면 errno를 ETIMEDOUT으로 설정하고 -1
을 돌려준다.차단된 프로세스에
sem_post
를 호출하면 깨어나며, sem_post
에 의해 증가된 세마포어 값이 sem_wait
또는 sem_timedwait
에 의해 감소된다.무명 세마포어를 생성할 때
sem_init
를 사용한다. - 하나의 프로세스에서 POSIX 세마포어를 사용할 때 무명 세마포어가 더 사용하기 쉽다고 한다.
무명과 명명된 세마포어의 차이는 세마포어를 생성하고 파괴하는 방법의 차이이다.sem_open
은 반환 값으로 세마포어의 포인터를 돌려줬지만, sem_init
는 첫 번째 인자인 sem
에 초기화된 세마포어를 저장한다.무명 세마포어를 다 사용한 후에는
sem_destroy
를 호출해서 폐기한다.
→ 호출한 이후에는 sem
이 가리키는 세마포어는 사용할 수 없다. sem_init
를 다시 호출해서 세마포어를 재초기화해야 한다.세마포어의 현재 값을 조회할 때 사용하는 함수이다.
atomic하게 동작하지 않기 때문에 함수로 세마포어 값을 읽은 시점과 값을 사용하려는 시점 사이에 세마포어 값이 변할 수도 있음을 주의해야 한다. -
sem_getvalue
는 디버깅용으로나 유용하다고 한다.예제 코드
쓰레드와 세마포어를 사용하는 예제
세마포어를 사용한 예제는 여러 쓰레드가 동시에 접근하지 않기 때문에 하나씩 출력되고 있으며, count 결과도 정상적으로 출력된다.
세마포어를 사용하지 않은 예제는 두 쓰레드가 같은 결과를 출력하는데, 이는 count 값이 증가하기 전에 가져온 것임을 확인할 수 있다.
sem_post
를 4번 호출하여 세마포어 값은 4로 시작되었으며, wait_fun
쓰레드에서는 sem_wait
을 호출할 때마다 1씩 줄어들고, 카운트가 0일 때 차단된다.공유 메모리와 세마포어를 사용하는 예제
세마포어를 2개 사용하여 child process 입장에서는 parent process에서
semP
의 잠금을 풀을 때까지 대기하며,
parent process 입장에서는 child process에서 semC
의 잠금을 풀을 때까지 대기하기 때문에 서로 번갈아 가면서 출력 된다.