티스토리 뷰

Study/OS

[Process] Fork-exec Model

생각많은 소심남 2014. 8. 29. 18:05

* 이 글은 Coursera에서 제공되는 HW/SW Interface 강의를 요약한 내용입니다.


지난 포스트에서 fork의 동작에 대해서 간단하게 소개했다. 그런데 그때도 이야기 했던 것처럼 fork는 단순히 현재 process의 copy만 새롭게 만드는 형태였다. 그래서 fork만 수행하면 process는 다음과 같이 생성된다.


지금의 예시는 만약 /usr/bin에 있는 ls 명령 실행시킬때 fork-exec 의 형태를 표현한 것이다. 이렇게 같은 stack, heap, data, code를 가진 child process가 생성된다. 그런데 계속 반복적으로 나오는 내용이긴 하지만 process는 program을 실행시키는 instance이기 때문에 code의 path가 /usr/bin/bash로 정해진 이 상태에서는 사용자가 원하는 명령을 할 수 없다. 이 state를 사용자 명령으로 바꿔주기 위한 syscall이 바로 exec이다.


이중에서 지난 포스트에서도 소개했던 execve()같은 것도 일종의 exec() 계열인데 위 그림에서 소개하고 있는 것처럼 code 영역을 바꿔주는 역할을 한다. 그중에서 execve는 인자로 filename 과 argument, environment parameter(envp)를 받는다. 우선 이렇게 받은 인자는 stack에 저장되는 다음과 같은 형태를 띈다.

argc와 argv는 흔히 봐왔던 argument count와 argument variable인데 조금 생소한게 envp일 것이다. envp는 말그대로 시스템 환경 변수를 나타내는데 프로그램 내에서만 정의된게 아닌 OS내에서 전역적으로 참고해야할 시스템 파일 경로가 여기에 담긴다. 참고해야 할 것은 exec()은 정상적으로 실행될 경우 반환값이 없다. 즉, code가 사용자에 의해서 바뀌면 그때부터 control flow자체가 child process에 따라서 수행되기 때문에 exec()를 실행시키는 process에서 받는 반환값자체는 없다. 단 만약 파일을 권한 문제나 경로 문제로 인해서 실행시키지 못하는 경우에는 error parameter를 반환하게 된다. 이런 과정을 거친 후에 code, Data, stack을 전부 바꿔주게 된다. 이때 pid나 사용자 argument 자체는 exec()가 실행되기 이전부터 받아온 값이므로 이 값은 계속 유지한다.참고로 argv, envp 는 일종의 pointer array로 되어 있다.

 이렇게 process 를 생성하고 사용자 정의 프로그램을 실행시켰으면 이제 남은 동작이 process를 어떻게 유지하고 종료시키느냐가 남았는데 우선 종료시키는 syscall은 exit()이다. process가 계속 실행되고 있는 이상 system resource를 계속 점유하기 때문에 process내의 program이 종료되면 반드시 종료를 시켜줘야 한다. 이때 exit()을 써주면 실행되고 있는 process가 종료된다. 이때 return 값을 통해서 정상적으로 종료했는지 비정상적으로 종료되었는지를 알 수 있다. 여기서 한가지더 참고할 함수가 atexit()라는 함수다. 이 함수는 exit() 수행 후 인자로 등록된 명령을 바로 수행하는 역할을 한다. 그래서 예를 들어 process가 Fork후에 종료시에 다음과 같이 cleanup()을 미리 등록(register)시키면 exit() 수행시 같이 실행되는 것을 확인할 수 있다.



앞에서도 잠깐 언급했던 것처럼 process는 계속 system resource를 점유하기 때문에 필요없을 때는 멈추던지의 control을 줘야한다. 그런데 간혹 우리가 종료 signal을 보냈음에도 process가 계속 idle 상태에서 system resource를 점유하는 현상이 발생한다. 보통 이런 경우를 zombie state라고 한다. 보통 child process를 생성한 process가 Reaping이라는 과정을 통해서 exit status를 보내줘야 process를 돌리고 있는 kernel이 child process를 종료시킬 수 있는데 zombie state의 경우는 생성한 process가 먼저 종료되었을 때 발생한다. 그래서 기본적인 process termination mechanism은 child process가 끝나고 나서야 parent process 가 종료되게끔 되어있다. 그래서 보통 child process가 실행되고 있는 동안 parent는 wait()를 수행하면서 대기하게 된다. 만에 하나 그렇지 않을 경우가 발생할 수 있는데 이때는 process tree에서 가장 최상위에 있는 init process(pid=1)가 child process를 종료시킨다.

 그럼 일단 parent도 child가 종료되는 timing을 알기 위해서 두 process가 synchronization이 이뤄져야 하는데 이걸 방금 나왔던 wait()이 하게 된다. 이것도 역시 인자를 받는데 이 인자가 받는 것이 바로 child process의 state다. 그래서 앞에서 설명한 내용과 같이 생각해보면 child process가 exit()을 통해서 종료되는 시점이 되면 parent process한테 state가 바뀌었다고 말하게 되고, 이걸 받을때까지 대기하게 된다. 보통 wait()이 정상적으로 수행하게 되면 child process의 pid를 반환하면서 reaping이 되게 된다. 여기에 여러개의 child process중에서 특정 pid를 가진 process만 종료시키는 waitpid() syscall로 있다. 실제 예는 다음과 같다.


parent가 이걸 실행시키게 되면 fork가 되면서 child process와 parent process는 다음 영역을 실행시킨다.


이때 parent process에서 수행되는  wait()은 child process 가 exit()을 수행할 때 반환하는 값을 읽을 때까지 계속 대기하게 된다.

지금까지 간단하게 Fork-exec 모델을 통해서 process management가 되는 구조를 살펴보았다. 사실 예전에 공룡책을 통해서도 process에 관해서 정리했던 거 같은데 너무 내용도 가물가물한거 같아서 이번 기회에 강의를 들으면서 정리를 했었다. 뭔가 부족한 부분이 아직도 있는거 같아서 이런건 직접 만들어보면서 해봐야 되겠다.

'Study > OS' 카테고리의 다른 글

[Memory] Address Translation  (1) 2014.09.02
[Memory] Virtual memory caches  (5) 2014.09.01
[Memory] Indirection  (0) 2014.09.01
[Process] Creating New Processes  (0) 2014.08.29
[Process] What is a process?  (2) 2014.08.29
[Process] Exceptional Control  (0) 2014.08.28
[Memory] Memory Consideration in OS  (0) 2013.11.26
댓글