Unix는 C 프로그램 프로세스들을 다루기 위한 여러가지 system call들을 제공합니다.
각 프로세스들은 unique한 양수 값인 Process ID(PID)를 가지게 됩니다. getpid
함수는 호출한 프로세스의 PID값을 반환하는 함수입니다. getppid
함수는 호출한 프로세스의 부모프로세스 PID를 반환합니다.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
int main(){
int pid = getpid();
int ppid = getppid();
printf("pid는 %d입니다.\n", pid);
printf("ppid는 %d입니다.", ppid);
}
프로세스는 크게 3가지 상태중 한가지를 가지게 됩니다.
SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU
signal를 받으면 멈추고 SIGCONT
signal을 받기 전까지 그 상태가 유지됨.(SIGCONT를 받으면 다시 프로세스가 돌아갑니다.)부모 프로세스는 fork
함수을 호출함으로써 새로운 running 자식 프로세스를 생성할 수 있습니다.
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
pid_t fork(void);
int main(){
int pid =fork();
if(pid ==0){
printf("자식! \n");
}
if(pid > 0){
printf("부모! \n");
}
}
새로 생성된 자식 프로세스는 완전히는 아니지만 부모프로세스와 거의 동일합니다. 자식은 부모와 동일한 가상메모리 (data, code, heap, shared libraries, stack)와 file descriptor의 복사본을 받습니다. 이는 child가 부모가 오픈한 그 어떤 파일이든 읽고 쓸수 있다는 것을 의미합니다. 이 둘의 가장 두드러진 차이점은 PID가 다르다는 것입니다.
fork 함수는 호출은 한번 되고 return은 2번 하는 흥미로운 함수입니다.(return 한번은 부모 프로세스, 한번은 자식프로세스에서 합니다.) 부모에서 fork 함수는 자식의 PID를 자식에서는 integer 0을 반환합니다. 자식 PID는 항상 0이 아니기에, return 값을 통해서 우리는 프로그램이 자식을 실행중인지 부모를 실행중인지 명확히 알 수 있습니다.
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
pid_t fork(void);
int main(){
pid_t pid;
int x = 1;
pid = fork();
if(pid == 0){
printf("child: x=%d\n", ++x);
exit(0);
}
printf("parent: x=%d\n", --x);
exit(0);
}
❯❯❯ ./fork
parent: x=0
child: x=2
위 예시를 한번 살펴보도록 하겠습니다.
프로세스가 terminate되면, kernel은 해당 프로세스를 즉시 시스템에서 제거하지 않습니다. 대신에 프로세스는 자기 부모 프로세스에게 reap될 때까지 terminate 상태를 유지합니다. 부모 가 자식을 reap하면, 커널은 자식 exit status를 부모에게 전달하고 terminate된 process를 제거합니다.(이 시점부터 자식 프로세스는 존재하지 않게됩니다.) 여기서 terminate 상태이며 아직 reap되지 못한 프로세스를 좀비라고 일컫습니다.
부모 프로세스가 terminate되면, init process가 자식 프로세스들의 부모 프로세스가 됩니다.
Init Process는 3가지 특징을 가지는 프로세스입니다.
부모 프로세스가 자식 좀비 프로세스들을 reap하지 않고 terminate 되면, kernel은 init 프로세스가 자식 프로세스들을 reap하게끔 합니다. 이렇게 kernel에서 좀비 프로세스를 관리해 주지만, 좀비 자식들을 부모에서 reap하는 것은 필요합니다. 좀비가 돌고있지는 않더라도 여전히 시스템 메모리 리소스를 잡아먹기 때문입니다.
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int *statusp, int options)
프로세스는 waitpid 함수 호출을 통해, 자식 프로세스가 terminate 또는 stop할 때까지 기다릴 수 있습니다. waitpid함수는 다소 복잡한데, 기본적으로 자식 프로세스가 terminate 될 때까지 호출한 부모 프로세스 진행을 suspend합니다.(options 인자로 0을 준 경우가 이에 해당) waitpid 함수는 terminate된 자식의 PID를 반환합니다.(자식이 waitpid 함수 호출전 terminate했어도 자식 PID를 반환합니다.) 이 시점에서 terminate된 자식프로세스는 reap되고 커널은 시스템으로부터 해당 프로세스의 모든 흔적을 제거합니다.
waitset 멤버들은 pid 인자에 의해 결정됩니다.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statusp, int options);
waitpid 함수는 Unix process group을 포함한 여러 다른 형태의 waitset 또한 지원합니다.(하지만 여기서는 다루지 않습니다.)
waitpid 함수는 options 인자를 통해 default behavior를 바꿀 수 있습니다.
|
(oring)을 통해 이 옵션들을 조합할 수 있습니다. 예를들어,
만약 statusp 인자가 non-NULL인 경우, waitpid 함수가 자식 status 정보를 인코딩합니다. wait.h는 status 인자를 해석할 몇몇 매크로들을 정의한 파일을 포함하고 있습니다.
만약 호출하는 프로세스가 자식이 없는 경우 waitpid는 -1을 반환합니다. 그리고 errno를 ECHILD로 설정합니다. 만약 waitpid 함수가 signal에 의해 interrupt된 경우 -1을 반환하고 errno을 EINTR로 설정합니다.
waitpid는 상당히 복잡해서 몇개의 예시를 보는 것이 도움이 됩니다.
#include "csapp.h"
#define N 2
void unix_error(char *msg) /* Unix-style error */
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}
pid_t Fork(void)
{
pid_t pid;
if ((pid = fork()) < 0)
unix_error("Fork error");
return pid;
}
int main(){
int status, i;
pid_t pid;
for(i = 0; i < N; i++){
if((pid = Fork()) == 0){
exit(100 + i);
}
}
while((pid = waitpid(-1, &status, 0)) > 0){
if(WIFEXITED(status)){
printf("child %d terminated normally with exit status=%d\n", pid, WEXITSTATUS(status));
}
else{
printf("child %d terminated abnormally\n", pid);
}
}
if(errno != ECHILD){
unix_error("waitpid error");
}
exit(0);
}
for 문에서 child process를 n개 생성하고 각각 process가 고유한 exit status와 함꼐 terminate합니다.