동시성 프로그램을 만드는 가장 간단한 방법은 프로세스를 사용하는 것이며, fork, exec, waitpid와 같은 친숙한 함수를 활용하여 주로 구현된다. 예를 들어 동시성 서버를 구현하는 자연스러운 방법은 부모에서 클라이언트 연결 요청을 수락하여, 이후 새로운 자식 프로세스를 생성하여 각각의 새로운 클라이언트를 서비스하는 것이 있다.
이것이 어떻게 동작하는지 알아보기 위해 두 개의 클라이언트와 listen 식별자에서 연결 요청을 기다리는 하나의 서버가 있다고 가정하자.
연결 요청을 서버가 수락하게 되면 해당 그림의 connfd(4) 처럼 연결 요청을 수락하게 된다. 서버는 자식을 fork 하고, 자식은 서버의 식별자 테이블 사본 전체를 가져오게 된다.
자식은 listenfd(3)의 자신의 사본을 닫고 부모는 connfd(4)에 대한 자신의 사본을 닫는데, 그 이유는 이들이 더 이상 필요하지 않기 때문이다. 이때 자식 프로세스는 클라이언트를 서비스하느라 바쁜 상태이다. 부모와 자식의 연결 식별자들은 각각 동일한 파일 테이블 엔트리를 가리키지 때문에 부모가 이 연결 식별자의 자신만의 사본을 닫는 것은 매우 중요하다. 그렇지 않으면 연결 식별자 4에 대한 테이블 엔트리는 절대로 반환되지 않으며, 이는 메모리 누수로 이어져 가용 메모리의 소모로 시스템을 죽게 만들 수 있다.
이후 부모는 클라이언트 1을 위한 자식을 만든 이후에도 클라이언트 2에 대한 연결 요청을 수락하고 새로운 연결 식별자 connfd(5)를 리턴한다. 부모는 계속해서 새로운 자식을 fork하고, 또 계속해서 다음 연결 요청을 기다리게 된다.
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_strorage clientaddr;
signal(SIGCHLD, sigchld_handler);
listenfd = open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen);
if (fork() == 0) {
close(listenfd); // 자식 프로세스는 listenfd를 닫는다.
echo(connfd); // 동작을 수행하고 종료
close(connfd);
exit(0);
}
close(connfd); // 부모 프로세스는 connfd를 닫는다.
}
}
프로세스 기반 동시성 서버의 특징은 다음과 같다.
- 서버들은 장기간 돌아가므로 좀비들을 처리하는 SIGCHLD 핸들러가 필수적이다. SIGCHLD 시그널들은 SIGCHLD 핸들러가 돌고 있는 동안 블록되고 Unix 시그널들은 큐에 들어가지 않기에 SIGCHLD 핸들러는 다수의 좀비 자식들을 청소할 준비를 해야한다.
- 부모와 자식은 connfd 사본을 닫아야 한다. 앞에서 말했듯, 이는 부모 쪽에서 특히나 더 중요하며 메모리 누수를 피하기 위해 자식으로 분기된 연결 식별자의 사본을 닫아야 한다.
- 마지막으로, 소켓의 파일 테이블 엔트리 내의 참조 카운트 때문에 클라이언트로의 연결은 부모와 자식의 connfd 사본이 모두 닫힐 때까지는 종료되지 않을 것이다.
프로세스의 장단점
프로세스는 부모와 자식 사이에 상태 정보를 공유하는 것에 대해 깔끔한 모델을 가지고 있다. 단적인 예로 사용자 주소 공간이 분리된다. 프로세스들이 분리된 주소 공간을 가지는 것은 장점이자 단점인데 한 프로세스가 우연히 다른 프로세스의 가상 메모리에 접근하는 것은 불가능하며, 이로 인해 많은 혼란을 제거할 수 잇다.
그러나 별도의 주소공간은 프로세스가 상태 정보를 공유하는 것을 어렵게 한다. 정보를 공유하기 위해서 IPC 매커니즘을 거쳐야만 하는데, 이는 프로세스 제어와 IPC를 수행하기 위해서 추가적인 오버헤드를 야기시킨다는 단점으로 작용한다.
'CS > OS' 카테고리의 다른 글
동기화 (1) (0) | 2022.12.07 |
---|---|
쓰레드 (1) | 2022.12.07 |
프로세스 제어 (0) | 2022.12.07 |
시스템 콜의 에러 처리 (0) | 2022.12.07 |
컨텍스트 스위치 (0) | 2022.12.06 |