1 / 56

Week5~7 : 운영체제 Chapter 4~6: 프로세스와 쓰레드 fork() – wait() – exec()

Week5~7 : 운영체제 Chapter 4~6: 프로세스와 쓰레드 fork() – wait() – exec(). 담당교수 : 최 윤 정. 병행처리 를 위한 Basic Function. fork spawns new process Called once, returns twice exit terminates own process Called once, never returns Puts it into “zombie” status

Télécharger la présentation

Week5~7 : 운영체제 Chapter 4~6: 프로세스와 쓰레드 fork() – wait() – exec()

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Week5~7 : 운영체제Chapter 4~6: 프로세스와쓰레드fork() – wait() – exec() 담당교수 : 최 윤 정

  2. 병행처리를 위한 Basic Function • forkspawns new process • Called once, returns twice • exitterminates own process • Called once, never returns • Puts it into “zombie” status • waitand waitpidwait for and reap terminated children • execveruns new program in existing process • Called once, (normally) never returns

  3. fork: Creating New Processes pid_tpid = fork(); if (pid== 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); } • int fork(void) • creates a new process (child process) that is identical to the calling process (parent process) • returns 0 to the child process • returns child’s pid to the parent process • Fork is interesting (and often confusing) because it is called oncebut returns twice

  4. Understanding fork Process n Child Process m pid_tpid = fork(); if (pid== 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); } pid_tpid = fork(); if (pid== 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); } pid_tpid = fork(); if (pid== 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); } pid_tpid = fork(); if (pid== 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); } pid = m pid = 0 pid_tpid = fork(); if (pid== 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); } pid_tpid = fork(); if (pid== 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); } Which one is first? hello from parent hello from child

  5. Fork Example #1 void fork1() { int x = 1; pid_tpid = fork(); if (pid == 0) { printf("Child has x = %d\n", ++x); } else { printf("Parent has x = %d\n", --x); } printf("Bye from process %d with x = %d\n", getpid(), x); } • Parent and child both run same code • Distinguish parent from child by return value from fork • Start with same state, but each has private copy • Including shared output file descriptor • Relative ordering of their print statements undefined

  6. Bye L1 Bye Bye L0 L1 Bye Fork Example #2 void fork2() { printf("L0\n"); fork(); printf("L1\n"); fork(); printf("Bye\n"); } Both parent and child can continue forking

  7. Bye L2 Bye Bye L1 L2 Bye Bye L2 Bye Bye L0 L1 L2 Bye Fork Example #3 void fork3() { printf("L0\n"); fork(); printf("L1\n"); fork(); printf("L2\n"); fork(); printf("Bye\n"); } Both parent and child can continue forking

  8. Bye Bye Bye L0 L1 L2 Bye Fork Example #4 void fork4() { printf("L0\n"); if (fork() != 0) { printf("L1\n"); if (fork() != 0) { printf("L2\n"); fork(); } } printf("Bye\n"); } Both parent and child can continue forking

  9. Bye L2 Bye L1 Bye L0 Bye Fork Example #5 void fork5() { printf("L0\n"); if (fork() == 0) { printf("L1\n"); if (fork() == 0) { printf("L2\n"); fork(); } } printf("Bye\n"); } Both parent and child can continue forking

  10. exit: Ending a process void cleanup(void) { printf("cleaning up\n"); } void fork6() { atexit(cleanup); fork(); exit(0); } • void exit(int status) • exits a process • Normally return with status 0 • atexit()registers functions to be executed upon exit

  11. Zombies • Idea • When process terminates, still consumes system resources • Various tables maintained by OS • Called a “zombie” • Living corpse, half alive and half dead • Reaping • Performed by parent on terminated child • Parent is given exit status information • Kernel discards process • What if parent doesn’t reap? • If any parent terminates without reaping a child, then child will be reaped by init process • So, only need explicit reaping in long-running processes • e.g., shells and servers

  12. ZombieExample void fork7() { if (fork() == 0) { /* Child */ printf("Terminating Child, PID = %d\n", getpid()); exit(0); } else { printf("Running Parent, PID = %d\n", getpid()); while (1) ; /* Infinite loop */ } } linux> ./forks 7 & [1] 6639 Running Parent, PID = 6639 Terminating Child, PID = 6640 linux> ps PID TTY TIME CMD 6585 ttyp9 00:00:00 tcsh 6639 ttyp9 00:00:03 forks 6640 ttyp9 00:00:00 forks <defunct> 6641 ttyp9 00:00:00 ps linux> kill 6639 [1] Terminated linux> ps PID TTY TIME CMD 6585 ttyp9 00:00:00 tcsh 6642 ttyp9 00:00:00 ps ps shows child process as “defunct” Killing parent allows child to be reaped by init

  13. void fork8() { if (fork() == 0) { /* Child */ printf("Running Child, PID = %d\n", getpid()); while (1) ; /* Infinite loop */ } else { printf("Terminating Parent, PID = %d\n", getpid()); exit(0); } } NonterminatingChild Example linux> ./forks 8 Terminating Parent, PID = 6675 Running Child, PID = 6676 linux> ps PID TTY TIME CMD 6585 ttyp9 00:00:00 tcsh 6676 ttyp9 00:00:06 forks 6677 ttyp9 00:00:00 ps linux> kill 6676 linux> ps PID TTY TIME CMD 6585 ttyp9 00:00:00 tcsh 6678 ttyp9 00:00:00 ps Child process still active even though parent has terminated Must kill explicitly, or else will keep running indefinitely

  14. wait: Synchronizing with Children • int wait(int *child_status) • suspends current process until one of its children terminates • return value is the pid of the child process that terminated • if child_status!= NULL, then the object it points to will be set to a status indicating why the child process terminated

  15. HC Bye HP CT Bye wait: Synchronizing with Children void fork9() { int child_status; if (fork() == 0) { printf("HC: hello from child\n"); } else { printf("HP: hello from parent\n"); wait(&child_status); printf("CT: child has terminated\n"); } printf("Bye\n"); exit(); }

  16. wait() Example void fork10() { pid_t pid[N]; int i; int child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) exit(100+i); /* Child */ for (i = 0; i < N; i++) { pid_t wpid = wait(&child_status); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminate abnormally\n", wpid); } } If multiple children completed, will take in arbitrary order Can use macros WIFEXITED and WEXITSTATUS to get information about exit status

  17. waitpid(): Waiting for a Specific Process void fork11() { pid_t pid[N]; int i; int child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) exit(100+i); /* Child */ for (i = N-1; i >= 0; i--) { pid_t wpid = waitpid(pid[i], &child_status, 0); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminated abnormally\n", wpid); } } • waitpid(pid, &status, options) • suspends current process until specific process terminates • various options (see textbook)

  18. execve:Loading and Running Programs Stack bottom Null-terminated envvar strings Null-terminated cmd line arg strings unused envp[n] == NULL envp[n-1] … envp[0] environ argv[argc] == NULL argv[argc-1] … argv[0] Linker vars envp argv argc Stack frame for main • intexecve( char *filename, char *argv[], char *envp[]) • Loads and runs in current process: • Executable filename • With argument list argv • And environment variable listenvp • Does not return (unless error) • Overwrites code, data, and stack • keeps pid, open files and signal context • Environment variables: • “name=value” strings • getenv and putenv Stack top

  19. execve Example argv[argc] = NULL “/usr/include” argv[argc-1] “-lt” … “ls” argv[0] argv envp[n] = NULL “PWD=/usr/droh” envp[n-1] “PRINTER=iron” … “USER=droh” envp[0] if ((pid = Fork()) == 0) { /* Child runs user job */ if (execve(argv[0], argv, environ) < 0) { printf("%s: Command not found.\n", argv[0]); exit(0); } } environ

  20. Unix Process Hierarchy [0] init [1] Daemon e.g. httpd Login shell Child Child Child Grandchild Grandchild

  21. Shell Programs : MultiTasking Execution is a sequence of read/evaluate steps int main() { char cmdline[MAXLINE]; while (1) { /* read */ printf("> "); Fgets(cmdline, MAXLINE, stdin); if (feof(stdin)) exit(0); /* evaluate */ eval(cmdline); } } • A shell is an application program that runs programs on behalf of the user. • sh Original Unix shell (Stephen Bourne, AT&T Bell Labs, 1977) • cshBSD Unix C shell (tcsh:enhanced csh at CMU and elsewhere) • bash “Bourne-Again” Shell

  22. Simple Shell eval Function void eval(char *cmdline) { char *argv[MAXARGS]; /* argv for execve() */ int bg; /* should the job run in bg or fg? */ pid_t pid; /* process id */ bg = parseline(cmdline, argv); if (!builtin_command(argv)) { if ((pid = Fork()) == 0) { /* child runs user job */ if (execve(argv[0], argv, environ) < 0) { printf("%s: Command not found.\n", argv[0]); exit(0); } } if (!bg) { /* parent waits for fg job to terminate */ int status; if (waitpid(pid, &status, 0) < 0) unix_error("waitfg: waitpid error"); } else /* otherwise, don’t wait for bg job */ printf("%d %s", pid, cmdline); } }

  23. What Is a “Background Job”? • unix> sleep 7200; rm /tmp/junk # shell stuck for 2 hours • unix> (sleep 7200 ; rm /tmp/junk) & • [1] 907 • unix> # ready for next command • Users generally run one command at a time • Type command, read output, type another command • Some programs run “for a long time” • Example: “delete this file in two hours” • A “background” job is a process we don't want to wait for

  24. Problem with Simple Shell Example • unix> limit maxproc # csh syntax • maxproc 202752 • unix> ulimit -u # bash syntax • 202752 • Our example shell correctly waits for and reaps foreground jobs • But what about background jobs? • Will become zombies when they terminate • Will never be reaped because shell (typically) will not terminate • Will create a memory leak that could run the kernel out of memory • Modern Unix: once you exceed your process quota, your shell can't run any new commands for you: fork() returns -1

  25. ECF to the Rescue! • Problem • The shell doesn't know when a background job will finish • By nature, it could happen at any time • Regular control flow is “wait until running job completes, then reap it” • Solution: Exceptional control flow • The kernel will interrupt regular processing to alert us when a background process completes • In Unix, the alert mechanism is called a signal

  26. Signals • A signal is a small message that notifies a process that an event of some type has occurred in the system • akin to exceptions and interrupts • sent from the kernel (sometimes at the request of another process) to a process • signal type is identified by small integer ID’s (1-30) • only information in a signal is its ID and the fact that it arrived

  27. Sending Signal Kernel sends (delivers) a signal to a destination processby updating some state in the context of the destination process

  28. Receiving a Signal • A destination process receives a signal when it is forced by the kernel to react in some way to the delivery of the signal • Three possible ways to react: • Ignore the signal (do nothing) • Terminate the process (with optional core dump) • Catchthe signal by executing a user-level function called signal handler • Akin to a hardware exception handler being called in response to an asynchronous interrupt

  29. GO!

  30. 들어가기 전에..동기와비동기의 의미를 실행으로 알아봅니다. • 그동안의 C code는 • 1. 위에서 아래로 흘러내려오는 흐름. 즉, flow 가 있었습니다. • 폭포처럼 한번 떨어져 내려온 이상 for(),while(), goto등 jump하는 기능이 없으면 다시는 위로 올라갈 수 없는 구조였습니다. • 2. 게다가 동기적인 흐름을 따랐습니다. printf(“데이터를 입력하세요 : ”); scanf(“%d”, &data); printf(“ 입력을 받기 전까지 이후 구문은 출력되지 않습니다.\n”); printf(“ ######\n”); • 그러나, system call은, fork()나 signal() 은 달랐습니다. 동기/비동기적으로 수행됩니다. • 동기적 : wait(), sleep(), pause(), fopen()..등 • 비동기적 : signal() // 시그널 받을 때까지 기다리지 않아요. • 다음의 코드를 다운받아 Visual C++에서 수행해보도록 하세요. • http://home.konkuk.ac.kr/~cris/Class/2011/sp/fork-code/block.zip • 방향키를 눌러서 bar를 움직여 보세요.

  31. while(1) • { • …(중략) • if( GetAsyncKeyState(LEFT) ) bar_x--; • else if(GetAsyncKeyState(RIGHT)) bar_x++; • else if(GetAsyncKeyState(ESCAPE)) • { • system("cls"); • gotoxy(25,10); • system("종료합니다."); • break; • } …. • } • return 0; • source : http://home.konkuk.ac.kr/~cris/Class/2011/sp/fork-code/block.zip 이 소스의 동작은 별을 출력하면서도 Bar를 욺직이고 있어요. Bar을 욺직이기 위해 방향 key를 입력하는 동안 별을 출력하는 부분이 멈추어 있지 않습니다. 만약 키를 입력받고자scanf(); 나getchar()를 썼다면? 입력받기전 출력부분도 ‘대기’합니다. Why?! while 루프 내부를 보면, GetAsyncKetState()라는 함수가 쓰여져 있어요. 비동기식으로 키를 입력받아라. 하는 함수죠. (Khbit()과 마찬가지) 해당 키가 눌려졌는지 아닌지를 catch하여 눌려졌다면 동작합니다. 동작상으로는 system call에서 signal과 유사한 부분이 있죠? 과제를 통해 조사해 보았겠지만, 실행예를 통해 확인해 보세요.

  32. 실습 전알아두어야할 것 fork 함수 호출을 통한 프로세스 생성 • process 에 대한 이해 • 프로세스 상태 : Ready , Running, Waiting, Terminated • ps 명령어로 status 보기 (#man ps로 확인하세요) • fork() – exec계열함수() 의 동작 • 부모-자식프로세스 • 자식은.. 기본적으로 부모가 관리해야 한다. (Reaping.) • 만약, 외부에서 부모프로세스만 kill-9 하면부모만 종료. 자식은 부모를 잃었으므로ppid도 함께 잃음. (후 ppid = 1)따라서 강제종료 시에는 가장 하위자식부터 kill -9. (이제 하지 마세요^^) • 부모는 자식을 작업 종료시 까지 기다려주고, 자식프로세스는 부모프로세스에게 종료 요청을 해주자. 자식은 낳는 것보다 잘 키우는 것이 더 어렵고도 중요합니다. 자식이 어떻게 자라는지(status) 죽음에 대해서도 관심을 가져야 해요. • SIGNAL을 이용하여 process 에 신호(message)를 보낼 수 있습니다. • (리눅스: /usr/include/bits/signum.h에 signal macro 정의) • process가죽는 경우는다양합니다. • 예) Kill -9 pid //kill –SIGKILL pid(강제종료) vs. Kill -SIGTERM pid// terminated • System call • 일반 C-라이브러리함수와 systemcall 함수는 다릅니다.! fork(), execl(), wait(), signal( ) 등 ..

  33. ps : process status START(START) : 프로세스가 시작된 시간 TIME(TIME) : CPU가 사용한 시간 USER(USER) : 사용자의 이름 COMMAND(COMMAND) : 사용자가 실행한 명령어 UID(User ID) : 사용자의 ID (Parent Group ID) : 사용자 부모 프로세스의 그룹 ID SID(Session ID) : 세션 ID PRI(PRIority) : 실행하는 우선 순위에 따른 프로세스 NI(Nice) : nice에 의한 우선 순위에 따른 프로세스 RSS(Resident Set Size) : 프로세스가 사용하는 메모리의 크기 SZ(SiZe) : 프로세스가 사용하는 자료와 스택의 크기 SHRD(ShaReD) : 프로세스가 사용하는 공유 메모리 %CPU : 프로세스가 사용하는 CPU 점유율 %MEM : 프로세스가 사용하고 있는 메모리 점유율 WCHAN : 프로세스가 실행하고 있는 커널 루틴 • Option : • -a : 모든 프로세스 상태 출력 • -e : 현재 실행중인 모든 프로세서 상태 출력 • -f : 프로세스 상태 full list로 출력 • -l : 프로세스 상태 long list로 출력 • -m : 메모리 정보를 출력 • -t TTY : 지정한 TTY를 가진 프로세스 정보 출력 • -u : 사용자 이름, 시작시간을 보여준다 • -p PID : 지정한 PID를 가진 프로세스 정보 출력 • -u UID : 지정한 UID를 가진 프로세스 정보 출력 • -g GID : 지정한 GID를 가진 프로세스 정보 출력 • 프로세스 상태보기 • PID(Process ID) : 프로세스마다 주어지는 번호 • TTY(Tele TYpewrite) : 명령어가 실행되는 터미널의 번호 • STAT(STATe) : 실행되고 있는 프로세스 상태 • R : 실행 중 혹은 실행될 수 있는 상태 • S : sleep • I : idle (비활동 상태 : BSD / 중간적 상태 : sysV) • T : 정지된 상태 (suspend) • Z : 좀비(zombie) 프로세스 • D : 디스크 관련 대기 상태 (BSD) • P : 페이지 관련 대기 상태 (BSD) • X : 메모리 확보를 위해 대기 중 (sys V) • K : 사용 가능한 커널 프로세스 (aix) • W : 스왑out된 상태 • N : nice 되어진 상태 • > : 우선 순위가 인위적으로 높아진 상태

  34. kill : 현재 수행중인 프로세서에게 시그널을 보낸다 • # kill [ -signal ID ] PID // 보통 kill 명령은 프로세서를 죽이는 데에 사용된다. • # kill –L // 시그널 종류 나열 , /usr/include/bits/signum.h에 정의됨 • 시그널의 종류를 지정하지 않으면 프로세서를 종료시키는 의미로 디폴트 시그널 ID 15번을 보내게 된다. • 1. SIGHUP(HUP) : 연결 끊기. 프로세스의 설정파일을 다시 읽는데 사용된다. • 2. SIGINT(INT) : 인터럽트 • 3. SIGQUIOT(QUIT) : 종료 • 4. SIGILL(ILL) : 잘못된 명령 • 5. SIGTRAP(TRAP) : • 6. SIGIOT(IOT) : IOT 명령 • 7. SIGBUS(BUS) : 버스 에러 • 8. SIGFPE(FPE) : 고정 소수점 예외 • 9. SIGKILL(KILL) : 죽이기. 이 시그널은 잡히지 않는다. • 10. SIGUSR1(USR1) : 사용자 정의 시그널 1 • 11. SIGSEGV(SEGV) : 세그멘테이션 위반 • 12. SIGUSR2(USR2) : 사용자 정의 시그널 2 • 13. SIGPIPE(PIPE) : 읽을 것이 없는 파이프에 대한 시그널 • 14. SIGALRM(ALRM) : 경고 클럭 • 15. SIGTERM(TERM) : 소프트웨어 종료 시그널, 일반적으로 kill 시그널이 전송되기 전에 전송된다. • 16. SIGKFLT : 코프로세서 스택 실패 • 17. SIGCHLD(CHLD) : 자식 프로세스의 상태변화 • 18. SIGCONT(CONT) : STOP 시그널 이후 계속 진행할 때 사용 • 19. SIGSTOP(STOP) : 정지. 이 시그널 역시 잡을 수 없다. • 20. SIGTSTP(TSTP) : 키보드에 의해 발생하는 시그널로 Ctrl+Z로 생성된다.

  35. kill 시스템 콜 : Sending Signals void fork12() { pid_tpid[N]; inti, child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) while(1); /* Child infinite loop */ /* Parent terminates the child processes */ for (i = 0; i < N; i++) { printf("Killing process %d\n", pid[i]); kill(pid[i], SIGINT); } /* Parent reaps terminated children */ for (i = 0; i < N; i++) { pid_twpid = wait(&child_status); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminated abnormally\n", wpid); } }

  36. 부모보다 먼저(아무 말없이) 종료되어 status가 전달되지 않아서, 자식프로세스를 reaping 하지 못하는 경우 발생. -  자식 process를기다려주거나 SIGNAL을이용하여 처리한다. ! Zombie 만들기 : //부모는 종료했으나, 죽지 않는 child, 이 child는 어떻게 될가요? void fork8() { if (fork() == 0) { /* Child */ printf("Running Child, PID = %d\n",getpid()); while (1) ; /* Infinite loop */ } else { printf("Terminating Parent, PID = %d\n",getpid()); exit(0); } } 자식 프로세스는 종료되면서 성공적인 종료를 알리기 위해 0 값을 커널에리턴합니다. 그러나, 이 때, 커널은 자식 프로세스를 바로 종료시키지 않고, 부모 프로세스에게 자식이 죽으며 전해준 값(0)이 전달될 때까지 기다려줘요. 자식의 상태정보값은 부모 프로세스가 커널에게 요청할 때에만 전달됩니다. 부모가 요청없이 종료했으므로 자식의 신호는 부모에게 전달될 수 없고, 이 때 자식은 작업은 종료했으나 메모리는 남아있는 zombie 상태가 됩니다. // 부모는 종료되기 전, 종료하는 child - zombie가 되죠. void fork7() { if (fork() == 0) { /* Child */ printf("Terminating Child, PID = %d\n", getpid()); exit(0); } else { printf("Running Parent, PID = %d\n", getpid()); while(1) //do something ; /* Infinite loop */ } }

  37. 앞 페이지의 두 예제 소스를 보면 부모와 자식간 오고 가는 대화가 없어도 너무 없어요. 자식이 무엇을 하는지 말든지에관심없이 자기일 끝났다고 그냥 종료하는 부모와.. -> 자식은 ppid()부모를 잃고 떠돌다가 init의 자식으로 들어가죠 부모가 있는 자식이되 일련의 허락없이(제멋대로) 종료해버리는 자식이 있죠. 무릇, 참된 부모는.. 자식이 어떤 상황에 놓여있는지 파악해야 하며 자식을 기다릴 줄 알아야 하고 무릇, 자식이라면 자신의 현재 상황을 부모에게 알려주어야 하겠습니다. 부모에게는 기다림을 권고하고 자식의 학교생활이 궁금할 때 선생님의 도움을 받아 자식 프로세스의 상황을 알아볼 수 있듯이 시스템의 도움을 받습니다.  system call ( C 라이브러리 함수가 아니에요 !) wait() 와signal()

  38. 모르는 명령어가 있다면 무작정 모른다. 버티지 말고 # man2 wait를 실행해보도록.! wait() 시스템 콜 • * fork를 사용하여 child process를 생성하는 경우 parent process는 child process를 wait하여 child process가 zombie가 되는 것을 막아야 한다. • 자식 프로세스가 종료되면, 리눅스커널은 그것의 메모리를 해제하고 파일을 닫는다. 종료상태는 프로세스의 프로세스 테이블에 저장된다. • but, 부모프로세스가 자식프로세스의 종료에 대한 관심없이 계속 동작을 수행하는 동안 하나 이상의 이유로 자식프로세스가 종료됐을때, 부모프로세스가 이 정보를 가져갈 때까지의 상태에 있는 자식프로세스를 좀비라고 한다. (Reaping되지 않은) • 일반적으로는 SIGCHLD 처리 함수안에서wait, waitpid함수를 사용하여 child process가 zombie가 되는 것을 막는다. • wait(), waitpid() : process의 종료나 signal함수호출신호를 기다리는 경우 • 호출한 영역에서 일시중지 • pause() : signal을 기다리는 경우

  39. #man 사용법 보는 방법 • # manman : man 명령어에 대한 manual. • The table below shows the section numbers of the manual followed by the types of pages they contain. • 1 Executable programs or shell commands • 2 System calls (functions provided by the kernel) • 3 Library calls (functions within program libraries) • 4 Special files (usually found in /dev) • 5 File formats and conventions eg /etc/passwd • 6 Games • 7 Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7) • 8 System administration commands (usually only for root) • 9 Kernel routines [Non standard] • 따라서 쉘명령어wait에 대한 manual • # man 1 wait • 시스템 콜 wait에 대한 manual • # man 2 wait

  40. wait(), waitpid()함수를 통해 알 수 있는 child 정보 (앞으로는 헤매지만 말고 스스로 찾아보세요….!!!!!!!!!!) 상태정보를 담아오기 위한 그릇. 주소! 를보내야 겠죠? pid_t wait(int *status) pid_twaitpid(pid_tpid, int *status, int options); (#man 2 wait 중 .) wait 함수 : 자식이 종료될 때까지, 또는 현재 프로세스를 종료시키거나 시그널 처리 함수를 호출하는 행동을 하는 신호가 전달될 때까지 현재 프로세스의 실행을 일시 중지시킨다. 만일 자식이 호출 시간에 이미 종료되었다면(좀비 프로세스), 함수는 즉시 리턴한다. 자식이 사용한 시스템 자원들은 모두 풀어진다. waitpid함수 : pid인자가 가리키는 자식이 종료될 때 까지, 또는 현재 프로세스를 종료시키거나 시그널 처리 함수를 호출하는 행동을 하는 신호가 전달될 때까지 현재 프로세스의 실행을 일시 중지시킨다. 만일 pid로 지정된 자식이 호출 시간에 이미 종료되었다면(좀비 프로세스), 함수는 즉시 리턴한다. status, 와 option ( 뒷장)

  41. pid_t wait(int *status) pid_twaitpid(pid_tpid, int *status, int options); • … (이어서 ) • 리턴되는pid 값은 다음중 하나이다. • < -1 : 프로세스 그룹 ID가 pid의 절대 값과 같은 어떤 자식 프로세스를 기다리라는 의미이다. • -1 : 어떤 자식 프로세스를 기다리라는 의미이다; 이것은 wait 에서 나타난 것과 같은 행동을 한다. • 0 : 프로세스 그룹 ID가 호출 프로세스의 ID와 같은 어떤 자식 프로세스를 기다리라는 의미이다. • > 0 : 프로세스 ID가 pid의 값과 같은 자식을 기다리라는 의미이다. • options 의 값은 0 이거나 다음 상수의 어떤 것과 OR 이다. • WNOHANG : 이것은 어떤 자식도 종료되지 않았다면 즉시 리턴하라는 의미이다. • WUNTRACED : 이것은 멈추거나 상태가 보고되지 않은 자식들을 위해 역시 리턴하라는 의미이다. 만일 status 가 NULL이 아니라면wait 또는 waitpid는 status가 가리키는 위치에 상태 정보를 저장한다. 이 상태는 다음 매크로들로 평가된다.(이들 매크로는 인자로써 stat 버퍼(int값!)를 가지고 있다. -- 버퍼에 대한 포인터가 아니다!) • WIFEXITED(status) : 자식이 정상적으로 종료되었다면 non-zero 이다. • WEXITSTATUS(status) : exit() 를 호출하기 위한 인자나 주 프로그램에서 return 문장을 위한 인자로써 설정되고 종료된 자식의 반환 코드의 최하위 8비트를 평가한다. 이 매크로는 WIFEXITED 가 non-zero 를 반환할 때만 평가된다. • WIFSIGNALED(status) : 만일 자식 프로세스가 잡혀지지 않은 신호때문에 종료되었다면 참을 반환한다. • WTERMSIG(status) : 자식 프로세스를 종료하도록 야기한 신호의 숫자를 반환한다. 이 매크로는 만일 WIFSIGNALED 가 non-zero 를 반환할 경우만 평가된다. • WIFSTOPPED(status) : 반환의 원인이 된 자식 프로세스가 현재 정지되어 있다면 참을 반환한다.; 이것은 이 함수가 WUNTRACED를 사용했을 때만 가능하다. • WSTOPSIG(status) : 자식을 정지하도록 야기한 신호의 숫자를 반환한다. 이 매크로는 WIFSTOPPED 가 non-zero 를 반환할 경우만 평가된다.

  42. 이제 wait( )를 적용해 봅시다. (1): int wait(int *child_status). // 부모는 종료했으나, 죽지 않는 child, 이 child는 어떻게 될가요? void fork8() { inti = 0; if (fork() == 0) { /* Child */ printf("Running Child, PID = %d\n",getpid()); while (i < 20) printf(“I want to play~!”); } else { printf("Terminating Parent, PID = %d\n", getpid()); exit(0); } } 무한루프를 마냥 기다릴 수는 없으므로 변경 여기서, 자식이 종료되기를 wait.! //자식이 종료되기를 기다렸다가 부모도 종료합니다. void fork8-1() { intstatus; if (fork() == 0) { /* Child */ printf("Running Child, PID = %d\n",getpid()); while (i < 20) printf(“[%d] : I want to play~! \n”, i); } else { printf(“Good Parent, PID = %d\n", getpid()); wait(&status);printf(“Terminating.! \n”); exit(0); //이 라인이 없어도 종료는 됩니다. } }

  43. 이제 wait( )를 적용해 봅시다. (2): int wait(int *child_status). parent[0]:11136 parent[1]:11136 parent[2]:11136 parent[3]:11136 parent[4]:11136 child : 11137 child : 11139 child : 11141 Child 11137 terminated with exit status 100 Child 11139 terminated with exit status 102 Child 11141 terminated with exit status 104 child : 11138 Child 11138 terminated with exit status 101 child : 11140 Child 11140 terminated with exit status 103 #define N 5 int main() { pid_tpid[N]; // 여러 개의 자식을 만들어 pid를 저장하기 위한 배열. int i, child_status; for (i = 0; i < N; i++){ if ((pid[i] = fork()) == 0){ sleep(1); printf("child : %d \n", getpid()); exit(100+i); /* Child가 종료되면서 100+i 를 들고갑니다. } else printf("parent[%d]:%d \n", i, getpid()); } for (i = 0; i < N; i++) { wpid = wait(&child_status); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminate abnormally\n", wpid); } }

  44. wait() 로는 부족한 일 • 언제 자식 프로세스가 종료될 것인지를 커널이 예측할 수 있을가? • 무작정 기다리말고, 자식 프로세스가 종료되는 순간에 • 커널이부모 프로세스에게 자식 프로세스가 종료됨을 알려주도록 하자! • 시그널 핸들링 개념의 동기 : code 에 여러 시그널 핸들러를 설치하여 테스트해 봅니다. • 시그널이 발생했을 때, 미리 준비해 놓은 함수가 호출되도록 연결해주는 작업 • signal 함수 / sigaction함수를 통한 핸들링 • (sigaction함수가 시스템 입장에서는 보다 안정적.)

  45. 이제 Signal을 받아 처리해봅시다.SIGNAL 과 Handler 의 간단한 예 : • // 부모와 자식 둘 다 무한 루프 상태, wait()는적합하지 않아요. Why?! • // signal을 이용하여 어떤 처리를 할 수 있을까. • // 일단, 백그라운드 동작 상태, 혹은 다른 쉘에서SIGNAL을 보내주어야 합니다.// 1. kill -9 를 이용하여 각각의 프로세스를 강제종료가 가능하다. 단 자식먼저. • // 2. SIGNALhandler를 설치하여 처리한다. // 또는 , 소스 내부에서 kill() 함수를 통해 SIGNAL을 전달해야 합니다.(slide 18, 22) #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/stat.h> #include <signal.h> void handler1(int sig) { printf(” process(%d)에 signal[%d]이 전달됨 \n”, getpid(), sig); // exit(0);  이 부분을 주석처리 할 때와 하지 않을 때의 차이를 알 수 있겠지요? } intmain() { pid_tpid, ppid; signal(SIGINT, handler1); //SIGINT가 들어오면 handler를 호출 signal(SIGCHLD, handler1); //SIGCHLD 가 들어오면 handler를 호출 , handler2를 만들어 호출할 수도 있겠죠? // ….. if (fork() == 0) { printf("Running Child, PID = %d\n", getpid()); while(1) ; //무한루프 } else { printf("Running Parent, PID = %d\n", getpid()); while(1) ; //무한루프 } }

  46. 이제 wait()와 SIGNAL()을 적용해 봅시다. • int main() • { • intpid; • int status; • intspid; • int i; • // SIGCHLD에 대해서 시그널핸들러를 설치한다. • signal(SIGCHLD, zombie_handler); • for (i = 0; i < 3; i++) • { • pid = fork(); • intrandom_value = (random()%5)+3; • if (pid == 0) • { • // 랜덤 값을 이용하여 기다린 후 종료한다. • printf("I will be back %d %d\n", random_value, getpid()); • sleep(random_value); • return random_value; • } • } • // (동기함수) 키보드 대기, (모든자식이 죽을 때 즈음 키 입력)getchar(); // 너무 빨리 치면 어떤 상황이 올까요? • printf("종료합니다.\n\n"); • } #include <unistd.h> #include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #include <signal.h> void zombie_handler() { int status; intspid; spid = wait(&status); printf("자식프로세스 wait 성공 \n"); printf("========================\n"); printf("PID : %d\n", spid); printf("Exit Value : %d\n", WEXITSTATUS(status)); printf("Exit Stat : %d\n", WIFEXITED(status)); }

  47. wait(&status) 만으로도 상태정보를 읽어와 해석할 수 있는데 waitpid()는 왜 사용할까요? 자식이 여러 개인 경우라면 wait() 로는 곤란하겠죠? 이미종료했는데 마냥 임의의자식을 wait()하라고 하면 이제 blocking상태에 이릅니다. waitpid()의적용예는 여러분이 조사해보도록 하세요. (중간/기말에 반영하도록 하겠습니다.)

  48. 이제 다양한 SIGNAL을 보내봅니다. SIGSEGV 유발하는scanf.c [root@localhost test]# ./scanf scanf명령 중 SIGSEGV, SIGABRT 1346798abcdefgad;kja;dksfja;kdsfadsfsdafadsfsdfadsfasdfasdf str = % Error : Segmentation Fault !! [root@localhost test]# ^C void handler(int sig) { printf(” process(%d)에 signal[%d]이 전달됨 \n”, getpid(), sig); exit(0); } intmain() { int i=0; inta[5]={0}; char str[5]; printf("scanf명령 중 SIGSEGV, SIGABRT \n"); signal(SIGSEGV, handler); signal(SIGABRT, handler); while(1) { scanf("%s", str); printf("str = %s \n", str); a[i] = i; printf("a[%d] = %d \n", i, i); i++; } }

  49. Kill()를 통해 Signal을보내봅니다. • void int_handler(int sig) { • printf("Process %d received signal %d\n", getpid(), sig); • exit(0); • } • void fork13() { • pid_tpid[N]; • int i, child_status; • signal(SIGINT, int_handler); • for (i = 0; i < N; i++) { • if ((pid[i] = fork()) == 0) while(1) ; /* child infinite loop • } • for (i = 0; i < N; i++) { • printf("Killing process %d\n", pid[i]); • kill(pid[i], SIGINT); • } • for (i = 0; i < N; i++) { • pid_twpid = wait(&child_status); • if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", , WEXITSTATUS(child_status)); • else printf("Child %d terminated abnormally\n", wpid); • } • } # ./forks Killing process 25417 Killing process 25418 Killing process 25419 Killing process 25420 Killing process 25421 Process 25417 received signal 2 Process 25418 received signal 2 Process 25420 received signal 2 Process 25421 received signal 2 Process 25419 received signal 2 Child 25417 terminated with exit status 0 Child 25418 terminated with exit status 0 Child 25420 terminated with exit status 0 Child 25419 terminated with exit status 0 Child 25421 terminated with exit status 0 #

  50. Internal Signal 의예 : Alarm (동기식signal) • int main() { • printf(" alarm(0) 일 때 종료"); • signal(SIGALRM, handler); • alarm(2); /* send SIGALRM in 1 second */ • while (1) { • sleep(1); • printf("."); • if(beeps > 3) alarm(0); // 알람끄기 • /* handler returns here */ • } • } /* * internal.c - A program that responds to interally generated events (SIGALARM signals) */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> int beeps = 0; /* SIGALRM handler */ void handler(int sig) { printf("BEEP\n"); fflush(stdout); if (++beeps < 10) alarm(2); //2초마다 SIGALRM 전송 else { printf("BOOM!\n"); exit(0); } }

More Related