[네트워크] fork 시스템콜과 좀비 프로세스 (feat. 에코 서버 프로그램)

minji·2021년 10월 27일
1

클라이언트에서 서버로 특정 메세지를 보내면 이를 그대로 echo 시켜주는 echo server를 구현해보려고 한다.
이 때, 서버가 여러 클라이언트에 대해 서비스를 제공할 수 있도록 하려면 어떻게 해야할까?

accept() 함수의 경우 한 번에 하나의 클라이언트와 연결을 맺을 수 있었다.
그렇다면 for문을 돌려 여러번의 accept() 함수를 호출하면 해결할 수 있을까?

답은 그렇지 않다.

for(;;){
	connfd = accept(,,,);	//한 클라이언트와 연결 설정
    	while( 클라이언트가 연결을 종료할 때 까지 ){
    		read(,,);
    		write(,,);
   }
}

이런 식으로 코드를 짠다고 가정해보자.
그렇다면 한 클라이언트와의 연결 설정 후, 해당 클라이언트가 연결을 종료할 때까지 for문 내에 묶여있는다.
즉, 또 다른 클라이언트와 동시에 통신할 수 없다.

이 때 사용해야 하는 것이 멀티 프로세스 이며, 이에 사용되는 시스템콜이 바로 fork() 이다.

fork() 시스템 콜

특정 프로세스에서 fork() 시스템 콜을 호출하면, 호출한 프로세스와 똑같은 자원이 메모리에 그대로 복사된다.
이 때, 복사된 프로세스를 자식 프로세스 / 복사한 프로세스를 부모 프로세스 라고 부른다.

즉, 부모는 자식에게 본인의 프로세스를 복사해서 전달하며, 자식은 해당 프로세스 자원을 받아 자신만의 작업을 실행할 수 있다.

( 참조 : https://man7.org/linux/man-pages/man2/fork.2.html )

fork() 함수는 부모인지 자식인지에 따라 그 리턴값이 구분된다.

부모 프로세스 : 생성한 자식 프로세스의 pid (process id)
자식 프로세스 : 0

즉, fock 함수를 호출한 후, 그 리턴값으로 부모/자식을 구분하고 그에 맞는 작업을 수행하도록 할 수 있다.


echo server의 구현

fork() 를 이용해, 다시 echo server에 대해 생각해보자.

우리가 fork 함수를 이용해 멀티 프로세스를 구현하면, 여러개의 자식 프로세스를 생성해 각각 하나의 클라이언트와 통신하도록 할 수 있으며, 부모는 기존의 하던 작업을 계속해서 수행할 수 있다.

위의 코드의 이전에 socket, bind, listen 의 과정이 구현되었다고 가정하고, 여러 개의 자식 프로세스가 각각의 클라이언트와 통신할 수 있음을 보이는 부분에 집중하자.

우선, childpid = fork() 의 값을 통해, 부모 프로세스인지 자식 프로세스인지를 구분할 수 있다.

반환값이 0인 경우, 자식 프로세스 에 해당하므로 연결요청을 수락한 클라이언트 (connfd) 에 대해 echo 서비스를 제공하도록 한다.
str_echo 함수가 해당 기능을 담당한다.

그렇지 않은 경우, 부모 프로세스 에 해당하므로, 기존의 connfd를 닫고 다시 connfd = accept(); 부분으로 돌아가 또다른 클라이언트의 연결을 수락한다.

echo client의 구현

클라이언트의 경우 서버와 달리, 기존의 코드 그대로 connect 요청을 하면 된다.
연결요청이 완료되면 str_cli 함수를 호출해 서버와 데이터 송수신을 한다.
이 경우 마찬가지로, socket생성 및 주소설정은 앞서 모두 이루어졌다고 가정하자.

위와 같이, str_cli 내에서 서버와 에코 통신을 계속 진행하고 input == NULL 인 경우에 종료하도록 했다.
참고로, fgets 함수는 표준입력으로 ctrl+D (윈도우의 경우 ctrl+Z) 를 입력받은 경우 그 값을 NULL 로 저장한다.


테스트

위와 같이 서버를 먼저 띄우자.

다음과 같이 두 개의 클라이언트에 대해 프로그램을 실행해, 서버에 연결을 요청한다.
connet() return! 을 통해 정상적으로 연결이 설정되었음을 알 수 있다.

다음으로, 각각 클라이언트에서 메세지를 입력해보면 메세지가 그대로 echo되어 돌아오는 것을 확인할 수 있다.

서버측의 화면을 확인해보자. 우리가 작성한대로, 두 번의 accept와 두 번의 fork 가 이루어졌으며 서로 다른 pid의 두 자식 프로세스가 생성되었음을 볼 수 있다.

이번에는 첫 번째 클라이언트에서 ctrl+D 를 입력해 서버와의 연결을 끊어보자. 예상대로라면 30644번의 child가 해제되어야 할 것이다.예상대로 30644 번 프로세스의 클라이언트와의 연결이 잘 종료된 것을 확인할 수 있다.


문제점(좀비 프로세스 생성)

앞선 상황(30644번 프로세스의 종료) 에서 프로세스 상태를 출력한 화면이다.
3번째 줄의 PID = 30640 에 집중해보면, 프로세스의 상태가 Z로 표시되어있는 것을 확인할 수 있다.

이는 해당 프로세스가 좀비 프로세스 상태라는 의미이다.

그렇다면 좀비프로세스란 무엇일까?

좀비 프로세스는 자식 프로세스가 종료되었음에도 불구하고 커널의 자원상에서 삭제되지 못한 채 남아있는 경우를 말한다.
즉, 종료된 프로세스가 커널 공간을 차지하고 있다는 것이다.

우리는 이를 내버려두어서는 안되며 적적히 처리(제거)해야 한다.
왜냐하면 좀비 상태의 프로세스가 많아질수록 커널 자원을 그만큼 차지한다는 의미이며, 이는 결국 더이상의 프로세스를 생성할 수 없게되는 상황에 이를 수도 있기 때문이다.

좀비 프로세스를 제거할 수 있는 방법은 시그널을 등록하는 것이다. 그 방법에 대해서는 다음 시간에 알아보도록 하자.

profile
SW Engineer

0개의 댓글