티스토리 뷰

wait 시스템콜은 호출시 int형 변수에 자식 프로세스가 종료될 당시의 상태를 기록하고 그 죽은 자식프로세스의 pid를 리턴한다.

자식 프로세스가 종료 됬는데 부모 프로세스가 자식 프로세스를 wait 하고있지 않은 경우에는 커널이 완전히 자식 프로세스를 free시키지 않고 메모리에 소량의 정보를 가지고 있다. 그렇기 때문에 자식은 좀비 프로세스가 된다. 따라서 시그널핸들러를 부모 프로세스에 작성해서 자식 프로세스가 죽었을때 완전히 메모리 free시키도록 해야한다.


한마디로 fork()를 하면 어디선가 wait()를 꼭해줘야한다.


for( ; ;) {

int stat;

connfd = accept();

if((n=fork()) == 0){

//자식 프로세스 처리

}

close(connfd);

wait(&stat);


}

위의 프로그램은 우리가 여태 짰던 concurrent 서버를 간략히 나타낸 것이다.

그런데 wait시스템콜을 원래는 for문이 나타나기 전에 시그널 핸들러를 등록해놓고 그 핸들러에서 호출을 했었는데

만약에 wait시스템콜이 저 위치에 나타나면 어떻게 될까?

오류가 나지는 않는다,하지만 방금전에 만들었던 자식 프로세스가 클라이언트와 데이터를 주고 받고 있을텐데

이 시스템콜은 죽어있는 자식 프로세스가 없으면 블럭되서 자식이 죽을때 까지 기다린다.

그렇게 되면 자식 프로세스가 데이터 송수신을 마칠때까지 

다른 클라이언트의 요청을 받지 못하기 때문에 concurrent서버의 기능이 사라지고 iterative서버 처럼 동작하게 되는것이다.



위의 프로그램은 sig_chld라는 시그널 핸들러이다.(자식프로세스 사망 -> SIGCHLD이벤트발생 -> sig_chld프로시져 호출)

원래는 시그널 핸들러 안에서는 standard I/O를 호출하면안된다(printf,scanf). read또는 write시스템콜만 사용해야 한다.

(나중에 이유가 나온다)


자식 프로세스가 죽었기 때문에 SIGCHLD이벤트가 발생했고 그렇기 때문에 sig_chld프로시져가 호출 된것이다.

따라서 wait는 블럭되지 않고 그 반쯤 죽어있는 자식프로세스를 메모리에서 완전히 free시킨다음 리턴한다.

이때 부모 프로세스는 다른 클라이언트의 요청을 기다리며 accept()에서 블럭이 되어있을 것이다.


서버 부모 프로세스는 accept()에서 블럭, 서버 자식 프로세스는 free, 클라이언트 프로세스 종료

이 3가지 상황이 현재까지 일어났기 때문에 sig_chld가 출력한 child16942 terminated는 당연하다.

하지만 맨 마지막줄에 accept error가 일어났다.


이 for문 안에서 errno를 출력하면 위처럼 accept error 문장이 나오게 된다.

서버 자식 프로세스는 언제 종료될지 그 누구도 알 수 없고 예상할 수도 없다. 이런것을 asynchronos event(비동기적 이벤트)이다.

따라서 시그널 핸들러가 언제 호출될지 알 수 없다.


서버 부모프로세스의 어떤 instruction을 실행하다가 비동기적 이벤트인 SIG_CHLD이벤트가 발생해서 sig_chld시그널 핸들러를 호출할지 누구도 알수 없다는 얘기이다. 시그널은 소프트웨어 인터럽트라고도 불린다.


따라서 커널입장에서 비동기적인 이벤트를 처리하는 시그널 핸들러를 실행시킬때 context switching 하는게 굉장히 불편하다.

시그널 핸들러를 실행시키게 되면 그 시그널 핸들러를 가지고 있는 서버 부모 프로세스의 context를 망가뜨렸을 가능성이 있다.


이것을 예상할수있으면 우리가 프로그램으로 처리해줄수있는데 시그널 핸들러는 비동기적 이벤트를 처리하므로 커널이 예상을 할 수가없다.

즉 부모 프로세스를 실행 하는 와중에 시그널때문에 시그널 핸들러를 실행시키고 돌아왔을때 값들이 꼬여 있을 가능성이 있다.

그래서 유닉스를 만들때 한가지 약속을 하게 된다.


비동기적 이벤트가 발생해서 시그널 핸들러가 실행되고 리턴됬을때 혹시라도 해당 시그널 핸들러가 실행된 프로세스가 블락되어있는 상태였다면

그 블락을 풀고 -1을 리턴시켜 버린다.(시그널 핸들러가 무슨짓을 할지 모르기 때문에 그 블락된 시스템콜이 잘못된 값을 가지고 리턴될수 있음)


우리가 만든 프로그램에서는 부모 프로세스는 accept에서 블락 되어있는 상태에서 자식 프로세스가 종료되어 시그널 핸들러가 작동 하게 된다.

그렇다면 위의 약속에 의해서 시그널 핸들러가 종료 될때 accept시스템콜도 -1을 리턴하게 될것이다.

accept시스템콜은 에러가 발생하면 -1을 리턴하고 전역변수인 errno에 원인을 정수 형태로 적어놓는다.

저런 상황때문에 accept가 -1을 리턴하면 errno에 EINTR(Error Inturrept)를 설정한다.

위의 에러 문장을 보면 accept error : Inturrepted System call이라고 적혀있는데, 해석해보면 방해받은 시스템콜이라고 할 수 있다.

다시 말해서, 시스템콜이(accept) 어떤 시그널(SIGCHLD)에 의해 방해(오류발생가능성 때문에 리턴)를 받았다는것이다.


accept가 -1을 리턴했기 때문에 다시 accept를 호출하면 그만이다. 그래서 accept() == -1 && errno= EINTR일때 continue; 한것이다.


서버 자식 프로세스가 6개가 있었는데 3개가 동시에 죽었다고 해보자.

이런 상황에서 SIGCHLD가 발생하고 시그널 핸들러가 호출 될것인데, 동일한 이벤트가 3번이 발생할 것이다. 

그런데 유닉스 설계할때 시그널 핸들러가 호출된 중에 발생한 동일한 이벤트에 대해서는 no queueing하기로 약속 되어 있다. 

즉, 동일한 이벤트에 대해서는 한번만 시그널핸들러를 호출하게끔 설계 되어 있다.


위의 시그널 핸들러가 동작하는 와중에 다른 2개의 서버 자식 프로세스가 죽었다고 해서 이 시그널 핸들러가 2번더 호출 되는것이 아니다.

no queueing하기 때문에 시그널 핸들러는 딱 한번만 호출 되고 말것이다. 근데 반쯤 죽었지만 부모에서 처리를 안해준 자식이 2개가 더있으니까 시그널 핸들러에서 이 나머지 좀비들을 처리해 줘야 한다.


웹서버와 같은 엄청나게 바쁜 프로세스에서 좀비 자식 프로세스들을 없애기 위해 위의 시그널 핸들러를 그대로 사용할수 없다.

그래서 시그널 핸들러를 조금 수정해야 한다.


wait를 좀비 자식 프로세스의 개수만큼 반복 시켜서 깔끔하게 메모리에서 제거 시켜야 한다.

근데 이걸 그냥 반복문으로 처리하면 될까?


while(wait()>0) 이런식으로 처리하면 바쁜 concurrent서버에서 문제가 해결 될까?

아니다. 10개의 자식 프로세스가 모두 죽었으면 상관없는데 3개만 죽었다고 생각했을때 3개까지는 문제가 없는데 4번째 자식 프로세스를 wait()시스템콜을 해버리면 자식이 좀비가 될떄까지 기다린다. 그렇게 되면 부모 프로세스와 자식프로세스가 둘다 일을 진행 시키지 못한다.


자식 프로세스가 죽는일이 가끔일어나면 다시 말해서 시그널 핸들러를 처리하는동안 다른 자식 프로세스가 좀비가 되지 않는다면, 시그널 핸들러가 반복해서 알아서 출력이 되므로 문제가 생기지 않는다 wait를 처음 만들때는 바쁜 웹서버같은것이 존재하지않았기 때문에 문제가 발생하지 않았었다.


시그널 핸들러를 처리하는 와중에 발생한 이벤트는 유닉스가 반복해서 처리하지 않는다. 

그렇기 때문에 유저가 프로그램으로 그걸 처리해 주어야 한다. 하지만 반복문으로 처리하는경우 프로세스가 블럭되는 문제점이 있으니까

non-blocking 시스템콜인 waitpid()를 호출해야 한다. 이 시스템 콜은 바쁜 서버가 등장하고 나서 필요에 의해 나중에 추가된 시스템 콜이다. 




waitpid의 첫번째 인자로 -1을 전달하면 자식 프로세스중 어떤것을 의미한다(any)


위의 그림은 바쁜 서버에 맞게 수정된 시그널 핸들러이다.

waitpid가 실행 되는데 첫번째 인자가 -1이므로 자식 프로세스중 어떤것을 의미하고 WNOHANG은 블럭 시키지 않겠다는 것이다.

위의 문장은 waitpid가 실행될 당시 좀비 자식 프로세스가 있다면 모두 free시키고(while문으로 반복)죽지 않고 살아있는 자식 프로세스가 있더라도 블럭시키지 않겠다는 의미이다.(WNOHANG)


아까 예를 다시 들어보면 10개중 3개가 좀비고 7개는 살아있다 쳤을때

1,2,3번 처리를 waitpid로 정상 처리하고 4번을 처리하려고 할때 블럭이 일어나지 않게 된다.(WNOHANG에 의해서)



댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함