본문 바로가기

시스템

[시스템 프로그래밍] exit / fork / wait / execve

Process Terminating : exit syscall

process가 실행을 마치는 과정은 사람에 따라 워딩을 다르게 하지만 어쨌든!

<종료(terminating) + 후처리(reaped by parents)> 로 이루어져 있다.

그 중 종료(terminating) 에 대해 알아보겠다.

 

우선 process가 terminating하는 원인은 크게 두가지다.

종료하라는 signal을 받거나, exit syscall을 호출하거나.

이때 exit syscall에 대해 잘 정리해두고 기억해야한다.

void exit (int status)

process가 이 exit syscall을 호출하면,

kernel이 status변수에 종료 상태를 저장하면서 해당 process를 terminate 시켜준다.

(일반적으로 status값이 0이면 정상 종료, 0이 아니면 비정상 종료이다.)

자연스럽게 한번 call하면 return하지 않는다는 걸 알 수 있다. - return type도 void!

 

Process Creating : fork syscall

system call fork를 통해 새로운 process를 만든다. 

이때 process hierarchy가 생겨난다. 바로 부모와 자식 개념인데,

부모 process가 fork를 호출하여 자식 process를 만드는 것이다.

그 과정을 구체적으로 살펴보자. :

부모 process에서 fork를 호출하여, 의도적인 exception에 의해 control이 kernel로 넘어간다.
kernel은 *메모리 상태가 같은 복제 process를 만들어내고, 원본을 parent, 복제품을 child라 정한다.
user code로 return할 때, return값이 0이면 child process이고 child's pid이면 parent process이다.

여기도 자연스럽게 call은 한번, return은 두번한다는 걸 알 수 있다.

*메모리 내부의 stack+heap+data+code 세트와 saved registers, file descriptors 등이 같다.

 

Process Reaping : wait syscall

앞서 설명한 process가 실행을 마치는 과정, <종료(terminating) + 후처리(reaping)>에서

이번엔 후처리(reaping) 에 대해 알아볼 것이다.

한 process가 exit을 호출하여 kernel에 의해 terminate되었다고 생각해보자.

이 process는 여전히 자원을 소모하고 있다... 왜 이렇게 만들었지?

예를 들어 terminate했으면서 exit status를 저장하고 있는 것만 봐도 알 수 있다.

이때 이 '종료o 후처리x'의 상태를 zombie 라 부른다 ㅋㅋ

 

그럼 후처리, reaping은 어떻게 이뤄지는 것이냐 -

기본적으로 종료된 child의 parent가 담당한다고 보면 된다.

parent는 wait syscall을 호출함으로써 child를 reaping할 때까지 자신의 실행을 중지한다.

그렇게 kernel로 control이 넘어가고, kernel은 zombie child를 찐으로 제거해준다.

그리고 다시 parent로 control을 돌려줄 때, (wait의) 리턴값으로 제거한 child의 pid를 돌려준다. => 형광펜 칠해진 전 과정이 reaping!

pid_t wait (int *status)

이때 kernel은 인자로 함께 호출되었던 status변수에, 그 제거한 child의 exit status를 저장해준다.

아까 그 종료되었음에도 여전히 자원을 소모하던 예시인 exit status는 다 정리가 되어 이제 parent가 보관하게 되는 것이다.

 

 

execve syscall

int execve (char *filename, char *argv[], char *envp[])

fork syscall을 통해 자꾸만 child를 만들어보려했던 이유는 새로운 프로그램을 실행하기 위해서이다.

예를 들어 shell이 ls라는 명령어를 받아 적절한 결과를 내주는 동작은,

parent process인 shell이 fork를 통해 child process를 만들고 이 child를 ls 프로그램으로 바꿔치기한 것이다.

이 과정을 자세히 설명해보겠다.

우선 parent process가 fork syscall을 통해 자신의 복제품 child process를 만든다.

이때 child는 parent처럼 메모리 공간을 갖게 되었을 것이다.

여기서 execve syscall이 호출되면, kernel은 child의 메모리 공간에 있던 내용을 지우고(parent와 동일하던 그 내용!)

ls program을 load해준다.

pid와 몇몇을 제외한 모두를 지우고 ls program으로 바꿔치기하는 것이다.

code 영역도 바뀌었을테니 execve는 return할 수 없을 것이다.