개요
컴퓨팅, 특히 유닉스 운영 체제와 유닉스 계열 환경에서 포크(fork)란 프로세스가 자기 자신을 복제하는 동작이다. 이는 일반적으로 시스템 호출의 일종이며, 커널 안에서 구현된다. 포크는 유닉스 계열 운영 체제에서 프로세스를 만드는 주된 방식이다. 복제의 대상을 부모 프로세스라 하고 그 결과물을 자식 프로세스라 한다.
멀티태스킹 운영 체제에서, 프로세스(실행 중인 프로그램)는 예를 들어 다른 프로그램들을 실행하기 위해 새로운 프로세스를 만들 필요가 있다. 포크와 그 변종들은 일반적으로 유닉스 계열 운영 체제에서 이러한 일을 하는 유일한 수단이다. 프로세스가 다른 프로그램의 실행을 시작하기 위해서는 먼저 자기 자신의 사본을 만들게 된다. 자식 프로세스라 부르는 이 사본은 exec 시스템 호출을 호출하여 다른 프로그램에 자신을 겹쳐놓는다. 즉, 다른 프로그램을 선호하면서 이전 프로그램의 실행을 중단시킨다. 포크 동작은 자식을 위해 별도의 주소 공간을 만든다. 자식 프로세스는 부모 프로세스의 정확히 동일한 사본의 메모리 세그먼트를 가지고 있다.
Fork는 OOM(out of memory)혹은 too man y process때문에 실패할 수 있다.
작동 원리
int fork(void) > return to parent child PID NUMBER, return to child 0
fork는 입력 인자는 없고, 자식 프로세스에게는 0을 부모 프로세스에게는 자식 프로세스의 PID를 리턴한다.
- 우선 kernel에 리소스가 있는지 확인
- process table slot에서 새로운 PID를 지정
- user가 프로세스를 너무 많이 돌리고 있지 않은지 확인
- child state를 created로 설정
- parent process에서 child process로 process table 데이터 복사 (process ID 같은 것은 제외하고)
- 현재 directory의 inode를 증가시키고 root을 변환
- file table의 open file count를 1 증가
- parent context를 메모리의 복사 (u area, text, data, stack) Copy on write기술을 이용해도 됨.
- dumy system lebvel context를 child system level로 push
예시
int main(void)
{
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
}
else if (pid == 0) {
printf("Hello from the child process!\n");
_exit(EXIT_SUCCESS);
}
else {
int status;
(void)waitpid(pid, &status, 0);
}
return EXIT_SUCCESS;
}
Race Condition In Fork
#include <fcntl.h>
#include <stdlib.h>
int fdrd,fdwt;
char c;
void rdwrt();
main(int argc,char *argv[])
{
if(argc!=3)
exit(1);
if((fdrd=open(argv[1],O_RDONLY))==-1)
exit(1);
if((fdwt=creat(argv[2],0666))==01)
exit(1);
fork();
rdwrt();
exit(0);
}
void rdwrt()
{
for(;;)
{
if(read(fdrd,&c,1)!=1)
return;
write(fdwt,&c,1);
}
}
위와 같은 경우에서 Input이
- ㅁㄴㅇㄹㅁㄴㅇㄹㅁㄴㅇㄹㅁㄴㅇㄹㅁㄴㅇㄹㅁㄴㅇㄹㅁㄴㅇㄹ
이런식으로 주어졌다고 하자. 이경우 output file은
- ㅁㄹㅁㄴㅇㄹㄹㄹㄴㅇㄹㅁㄹㄴㄹㅇㄹㅁㄹ..
처럼 예측할 수 없는 글자의 조합으로 출력되게 된다. 이는 부모 프로세스와 자식 프로세스가 같은 FD를 공유함으로써 생기는 race condition이다. 즉, 어떤 프로세스가 파일에 접근하는지 에측할 수 없기에, 파일의 출력이 잘못 출력된 것이다.