A word of warning
Process forking은 매우 강력하고 위험한 툴입니다. 만약 잘못된 코드로 fork bomb을 일으키면 시스템 전부를 망가뜨릴 수 있습니다. 이러한 일을 줄이기 위해서는 생성할수 있는 최대 프로세스의 수를 작은 숫자로 제한하는 것이 좋습니다. ulimit -u 40 과 같이 설정하면 최대 프로세스의 수를 제한할 수 있습니다. 이 제한은 각 user에 해당하는 것을 기억하십시오. 만약 fork bomb이 발생하면 다른 모든 프로세스를 죽이는 것이 불가능해집니다. 이러한 동작을 수행하는 killall이 fork를 필요로 하기 때문입니다. 이런 경우 어떻게 해결해야 할까요?
첫 번째 해결책은 다른 유저로 또 다른 shell instance를 생성해 그곳에서 프로세스를 죽이는 것입니다. 또 다른 방법은 exec command를 시용해 모든 user process를 죽이는 것입니다. 결국 시스템을 리부팅 해야합니다. fork 코드를 테스트할 때에는 속해있는 머신이 루트나 물리적인 접근 권한이 있는지 확인하십시오. 만약 원격으로 fork code를 동작해야 한다면, kill -9 -1을 기억해 두면 긴급 상황에 대처할 수있을 것입니다.
경고했습니다! fork는 준비되지 않은 채 사용하면 매우매우 위험합니다!
Intro to Fork
What does fork do?
Fork는 현재 프로세스를 복재해 새로운 프로세스를 만드는 system call입니다. fork는 현재 프로세스와 조그만 차이만 가지고 있는 새로운 프로세스를 복제해서 만듭니다. child process는 main에서 시작되지 않습니다. child process는 종료시 parent process가 fork를 호출했던 곳으로 return됩니다.
중요하진 않지만, 오래된 UNIX system에서는 부모의 전체 주소가 직접 복사되었습니다. (그 resouce가 수정되었는지 아닌지 상관없이) 요즘에는 Kernel이 복사해 와서 쓰는 copy-on-write로 동작해 많은 리소스를 저장하는 반면에 시간적인 면에서 더 효율적입니다.
What is the simplest fork() example?
printf("I'm printed once!\n"); fork(); // Now there are two processes running // and each process will print out the next line. printf("You see this line twice!\n");
위와 같은 fork코드는 첫 번째 printf문 이후에 두 개의 같은 프로세스가 생기므로 fork 이후에 printf문은 두개의 프로세스가 각각 한번씩 두번 실행하게 됩니다.
Why does this example print 42 twice?
#include <unistd.h> /*fork declared here*/ #include <stdio.h> /* printf declared here*/ int main() { int answer = 84 >> 1; printf("Answer: %d", answer); fork(); return 0; }
위와 같은 코드는 fork 이전에 printf를 호출해 한번만 실행되는데 실제로 실행해 보면 printf문의 내용이 두 번 출력됩니다.
이전에도 나온 내용지만 printf가 항상 wirte를 호출하지는 않습니다. new line이 들어오거나 fflush로 버퍼를 비워주거나 할 때 write가 호출됩니다. 즉 위의 코드에서는 Answer: %d를 printf로 넘겨주어도 아직 새 개행을 만나지 않았기 때문에 write되지 않고 버퍼에 Answer: %d가 남아있는 상태입니다. 그 다음에 fork()가 호출되면 버퍼를 포함한 전체 메모리가 복제되어 새로운 프로세스가 만들어집니다. 이 child process는 버퍼도 복사했기 때문에 Answer: %d가 들어있는 상태로 만들어집니다. 그 다음에 프로그램이 종료될 때 버퍼가 비워지면서 printf문 안의 내용이 두번 출력되게 됩니다.
How do you write code that is different for the parent and child process?
child process와 parent process를 구분해서 코드를 작성하려면 fork()의 return값을 확인해야 합니다.
만약 fork()가 -1을 리턴했다면 새로운 프로세스를 만드는데 무언가 문제가 발생했다는 뜻입니다. 이러한 종류의 에러가 발생하면 errno에 저장된 값을 확인해 어떤 종료의 에러가 발생했는지 알아봐야 합니다.
비슷하게 fork()가 0을 리턴했다면 현재 child process에 있다는 뜻입니다. 반면에 양수의 값이 나오면 parent procee라는 뜻입니다. 양수의 값이 return되었을 경우에는 이 양수값은 child process의 pid값입니다.
다음을 기억해둡시다.
Child process는 getppid()를 호출해 parent process를 찾을 수 있습니다. 그러므로 fork()로부터 얻을 수 있는 retun information이 더 이상 필요하지 않습니다. 반면 parent process는 child process를 찾기 위해서는 fork()의 리턴값을 통해 pid값을 얻어야 합니다.
pid_t id = fork(); if (id == -1) exit(1); // fork failed if (id > 0) { // I'm the original parent and // I just created a child process with id 'id' // Use waitpid to wait for the child to finish } else { // returned zero // I must be the newly made child process }
What is a fork bomb?
Fork bomb은 프로세스를 무한개의 프로세스를 만드려는 시도를 할 때 발생합니다. 가장 단순한 경우는 아래와 같습니다.
while (1) fork();
이러한 fork bomb은 많은 수의 실행할 준비가 된 프로세스를 만드는데 CPU time과 memory를 할당해 시스템이 거의 멈추게 만듭니다.
//시스템 관리자는 이러한 fork bomb이 다른 사용자들의 프로그램을 방해하므로 프로세스 수의 상한선을 두거나 로그인 권한을 취소할 수도 있습니다.
fork bomb이 항상 악의적인 것은 아닙니다. 때때로 학생의 coding 실수로 일어나기도 합니다.
Waiting and Execing
How does the parent process wait for the child to finish?
parent process에서 child procee가 끝나길 기다리기 위해서는 waitpid나 wait을 사용합니다.
pid_t child_id = fork(); if (child_id == -1) { perror("fork"); exit(EXIT_FAILURE);} if (child_id > 0) { // We have a child! Get their exit code int status; waitpid( child_id, &status, 0 ); // code not shown to get exit status from child } else { // In child ... // start calculation exit(123); }
Can I make the child process execute another program?
fork 이후에 Exec function을 사용하면 child process가 다른 프로그램을 실행하게 만들 수 있습니다. exec는 프로세스 이미지를 호출된 프로세스 이미지로 대체시켜줍니다. 즉, exec로 프로그램이 호출되면 exec 이후의 코드들은 모두 대체됩니다. child process에서 다른 작업을 하고 싶다면 exec가 호출되기 전에 완료되어야 합니다.
exec에는 여러 종류가 있는데, https://en.wikipedia.org/wiki/Exec_(system_call)#C_language_prototypes
위의 주소를 통해 exec 뒤에 붙는 글자에 대해 알아볼 수 있습니다.
exec를 base로 한개 이상의 글자를 붙일 수 있는데, 각각의 글자의 뜻은 다음과 같습니다.
e - 새 프로세스 이미지로 전달될 환경 변수를 가리키는 포인터 배열.
l - command line argument가 각각 function으로 전달.
p - 실행하기 위해 전달된 argument의 이름을 가진 파일을 PATH 환경 변수를 사용해 찾는다.
v - command line argument가 function에 array(vector)의 형태로 전달
A simpler way to execute another program
System을 이용하면 더 간단하게 사용할 수 있습니다.
#include <unistd.h> #include <stdlib.h> int main(int argc, char**argv) { system("ls"); return 0; }
아래와 같이 사용하면 ls를 호출할 수 있습니다.
system은 frok를 해서 parameter를 넘겨 실행하고 원래의 parent process는 이 작업이 끝날때까지 기다립니다. 즉, system은 blocking call입니다. system에 의해 시작된 프로세스가 끝나기 전까지 parent process는 동작하지 않습니다. 이러한 동작은 유용할 수도 있고 그렇지 않을수도 있습니다. 또한 system은 사실 또 다른 shell을 만들어 string을 넘겨주어 실행하기 때문에 exec로 직접 실행하는 것 보다 overhead가 더 큽니다. standard shell은 PATH의 environment variable을 사용해 command와 맞는 파일을 찾습니다. System을 사용하는 것은 단순한 명령을 실행하는 문제를 해결하는 데는 충분하지만
더 복잡하거나 미묘한 문제에 대해서는 금방 한계가 올 것입니다. 또한 system은 fork-exec-wait의 패턴을 숨겨주므로 fork exec waitpid를 사용하는 것을 권장합니다.
What is the silliest fork example?
#include <unistd.h> #include <stdio.h> int main(int argc, char **argv) { pid_t id; int status; while (--argc && (id=fork())) { waitpid(id,&status,0); /* Wait for child*/ } printf("%d:%s\n", argc, argv[argc]); return 0; }
위와 같은 코드에 여러개의 parameter를 주면 어떻게 될까요?
만약 parameter로 5 4 3 2 1을 주었다면
5:1
4:2
3:3
2:4
1:5
0:./프로그램이름
이 출력될 것입니다.
int main(int c, char **v) { while (--c > 1 && !fork()); int val = atoi(v[c]); sleep(val); printf("%d\n", val); return 0; }
위 코드는 프로그램의 입력받은 parameter의 갯수만큼 fork로 생성한 다음에 각각의 parameter를 정수로 바꾸어 그 크기만큼 sleep한 이후에 출력하게 되므로 parameter의 숫자가 작은 순서대로 먼저 출력이 되므로 오름차순 정렬처럼 보이게 출력해줍니다..
NOTE : 이 프로그램은 스케쥴러가 어떻게 동작하는지에 따라 결과가 나오므로 사실 O(N)이 아닙니다.
O(logN)에 동작하는 다른 병렬처리 알고리즘도 존재하지만 이 알고리즘은 그 들중 하나가 아닙니다.
What is different in the child process than the parent process?
child process와 parent process의 주요한 차이점은 다음과 같습니다.
- getpid()로 얻을 수 있는 process id의 값. 그리고 getppid()로 얻을 수 있는 parent process ID의 값
- parent process는 SIGCHLD라는 signal을 통해 child process가 끝났는지 아닌지를 알 수 있다.
- child process는 pending signal 또는 timer alarm을 상속받지 않습니다. 더 자세한 사항은 fork man page를 참조하세요.
Do child processes share open filehandles?
child process와 parent process 모두 동일한 file descriptor를 사용합니다. 예를 들어 한 프로세스가 random access position을 rewind해서 파일의 처음으로 돌아간다면 두 프로세스 모두 영향을 받을 것입니다.child 와 parent는 모두 각각 close를 호출해야 합니다.
'Angrave System Programming > Processes' 카테고리의 다른 글
Process Control : Wait macros (0) | 2019.01.11 |
---|---|
Forking: Fork, Exec, Wait(2) (0) | 2019.01.11 |
Forking: Fork, Exec, Wait(1) (0) | 2019.01.11 |
Process Introduction (0) | 2019.01.10 |
Kernel, Shells, Terminals (0) | 2019.01.10 |