제 10 장프로세스프로그래밍 프로세스시작 / 종료 자식프로세스생성 프로세스내에서새로운프로그램실행 시그널 시스템부팅 10.1 프로그램시작및종료 Unix 에서프로그램은어떻게실행이시작되고종료될까? 프로그램은 10.3 절에서살펴볼 exec 시스템호출에의해실행된다. 이호출은실행될프로그램의시작루틴에게명령줄인수 (command-line arguments) 와환경변수 (environment variables) 를전달한다. C 프로그램을컴파일하면실행파일에는 C 프로그램의코드와더불어 C 시작루틴 (start-up routine) 이포함된다. 이시작루틴은 exec 시스템호출로부터전달받은명령줄인수, 환경변수를다음과같이 main 함수를호출하면서 main 함수에다시전달한다. main 함수에서부터프로그램이실행되고실행이끝나면반환값을받아 exit 한다. exit( main( argc, argv) ); exec 시스템호출에서부터 C 시작루틴, main 함수로프로그램실행이시작되는과정은그림 10.1과같다. 그림 10.1 프로그램실행의시작 exec() 시스템호출은실행되는프로그램에게명령줄인수를전달하는데실행되는프로그램의 main 함수는 argc와 argv 매개변수를통해서명령줄인수의수와명령줄인수에대
한포인터배열을전달받는다. int main(int argc, char *argv[]); argc : 명령줄인수의수 argv[] : 명령줄인수리스트를나타내는포인터배열 명령줄인수리스트를나타내는포인터배열 argv 의구성은그림 10.2 와같다. 그림 10.2 명령중인수리스트 argv 구성 또한전역변수 environ을통해환경변수리스트도전달받는데그구성은그림 10.3과같다. 그림 10.3 환경변수리스트 environ 의구성 프로그램 10.1은모든명령줄인수와환경변수를프린트한다. 포인터변수 ptr을이용하여환경변수리스트의시작위치인 environ에서부터시작하여 1씩증가하면서각환경변수를프린트한다.
프로그램 10.1 printall.c #include <stdio.h> int main(int argc, char *argv[]) { int i char **ptr extern char **environ; for (i = 0; i < argc i++) /* 모든명령줄인수프린트 */ printf("argv[%d]: %s \n", i, argv[i]); for (ptr = environ *ptr!= 0; ptr++) /* 모든환경변수값프린트 */ printf("%s \n", *ptr); exit(0); 이제프로그램이종료하는방법에대해서알아보자. 프로그램의실행을종료하는방법은정상종료 (normal termination), 비정상종료 (abnormal termination) 로크게두가지로나눌수있다. 먼저프로그램이정상적으로종료하는방법부터알아보도록하자. main() 실행을마치고리턴하면 C 시작루틴은이리턴값을가지고 exit() 을호출한다. 프로그램내에서직접 exit() 을호출할수있다. 프로그램내에서직접 _exit() 을호출할수있다. #include <stdlib.h> void exit(int status); exit() 시스템호출은뒷정리 (cleanup processing) 를한후프로세스를정상적으로종료시킨다. exit() 시스템호출은프로세스를정상적으로종료시키는데종료전에모든열려진스트림을닫고 (fclose), 출력버퍼의내용을디스크에쓰는 (fflush) 등의뒷정리 (cleanup processing) 를한다. 프로세스의종료상태를알리는종료코드 (exit code) 를부모프로세스에게전달한다.
#include <stdlib.h> void _exit(int status); exit() 시스템호출은뒷정리 (cleanup processing) 를하지않고프로세스를즉시종료시킨다. _exit() 시스템호출역시프로세스를정상적으로종료시키는데뒷정리를하지않고즉시종료된다는점이 exit() 시스템호출과다르다. 프로그램이비정상적으로종료하는방법은 2 가지가있다. abort() 시스템호출은프로세스에 SIGABRT 시그널을보내어프로세스를비정상적으로종료시킨다. 시그널에의한종료 : 프로세스가실행중에시그널을받으면갑자기비정상적으로종료하게된다. 시그널에대한자세한사항은 10.6 절에서자세히다룬다. 10.2 프로세스구조 프로세스란무엇인가? 프로세스에대한정의혹은설명은여러가지가있지만가장쉬운정의는실행중인프로그램 (executing program) 을프로세스라고생각하는것이다. 다시말하면프로그램이실행되면프로세스가되는것이다. 한프로그램은여러번실행될수있으므로한프로그램으로부터여러개의프로세스를만들수있으며프로그램그자체가프로세스는아니라는점을주의하자. 프로그램을실행즉프로세스를유지하기위해서는무엇이필요할지생각해보자. 먼저프로세스관리를위한커널내의프로세스에대한정보가필요할것이다. 또한프로세스즉프로그램실행을위해서는그림 10.4와같이실행코드, 데이터, 힙, 스택등을메모리내에배치해야한다. 이러한메모리배치를프로세스메모리이미지라고한다. 프로세스메모리이미지를구성하는프로그램의실행코드, 데이터, 스택, 힙등의영역의역할은다음과같다. 코드 (code) 프로세스의실행코드를저장하는영역이다. 데이터 (data) 전역변수 (global variable) 및정적변수 (static variable) 를위한영역이다.
그림 10.4 프로세스메모리이미지 힙 (heap) 동적메모리할당을위한영역이다. 스택 (stack area) 함수호출을구현하기위한 ( 지역변수를포함하는 ) 활성레코드 (activation record) 를저장하기위한실행시간스택 (runtime stack) 을위한영역이다. U-영역 (user-area) 열린파일식별자등과같은시스템관리정보를저장하는영역이다.
10.3 프로세스생성 각프로세스는프로세스를구별하는번호인프로세스 ID를갖고있다. 실행중인프로그램즉프로세스가 getpid() 를호출하면실행중인프로세스의 ID를리턴한다. 또한 getppid() 를호출하면실행중인프로세스의부모프로세스의 ID를리턴한다. int getpid( ) 프로세스의 ID를리턴한다. int getppid( ) 부모프로세스의 ID를리턴한다. Unix에서는필요에따라새로운프로세스를생성해야하는데 fork() 시스템호출이프로세스를생성하는유일한방법이다. #include <sys/types.h> #include <unistd.h> pid_t fork(void); fork() 시스템호출은새로운자식프로세스를생성한다. 자식프로세스에게는 0을리턴하고부모프로세스에게는자식프로세스 ID를리턴한다. fork() 시스템호출은새로운자식프로세스를생성한다. 프로세스생성원리를간단히요약하면자기복제 ( 自己複製 ) 라고할수있다. 자식프로세스는부모프로세스 ( 코드, 데이터, 스택, 힙등 ) 를똑같이복제해만들어진다. fork() 시스템호출을하면새로운자식프로세스가즉시생성되며부모프로세스와자식프로세스에게각각리턴한다. 자식프로세스에게는 0을리턴하고부모프로세스에게는자식프로세스 ID를리턴한다. fork() 시스템호출은한번호출되지만두번리턴된다는점을주의하자. fork() 호출후에부모프로세스와자식프로세스가병행적으로실행을계속한다. 다음예제프로그램을통해 fork() 호출후에리턴값과실행흐름을살펴보자. 실행결과를보면 fork() 뒤에나오는문장은부모프로세스와자식프로세스에의해각각실행되며부모프로세스에게는생성된자식프로세스의 ID(15066) 이리턴되고자식프로세스에게는 0이리턴됨을확인할수있다.
그림 10.5 프로세스생성전후 프로그램 10.2 fork1.c #include <stdio.h> main() { int pid; printf("[%d] 프로세스시작 \n", getpid()); pid = fork(); printf("[%d] 프로세스 : 리턴값 %d\n", getpid(), pid); 실행결과 [15065] 프로세스시작 [15065] 프로세스 : 리턴값 15066 [15066] 프로세스 : 리턴값 0 이예제를통해 fork() 호출뒤에나타나는문장은부모프로세스와자식프로세스가병행적으로모두실행한다는것을확인할수있었다. 그러면부모프로세스와자식프로세스가서로다른일을하려면어떻게하여야할까? fork() 호출후에리턴값이다르므로이리턴값을이용하면부모프로세스와자식프로세스를구별하고서로다른일을하도록할수있을것이다. 따라서다음과같은코드를수
행하면 fork() 호출후에자식프로세스는자식을위한코드부분을실행하고부모프로세스는부모를위한코드부분을실행한다. pid = fork(); if ( pid == 0 ) { 자식프로세스의실행코드 else { 부모프로세스의실행코드 다음예제프로그램은부모프로세스가자식프로세스를생성하며각프로세스가메시지와프로세스 ID를프린트한다. 실행결과를보면자식프로세스는리턴값으로 0을받았으므로 if 문의 then 부분을실행했고부모프로세스는리턴값으로자식프로세스 ID(15800) 을받았으므로 if 문의 else 부분을실행했음을확인할수있다. 프로그램 10.3 fork2.c #include <stdlib.h> #include <stdio.h> main() { int pid; pid = fork(); if(pid ==0){ printf("[child] : Hello, world pid=%d\n",getpid()); else { printf("[parent] : Hello, world pid=%d\n",getpid()); 실행결과 [Parent] : Hello, world pid=15799 [Child] : Hello, world pid=15800 이제하나의부모프로세스가두개의자식프로세스를생성하는다음예제프로그램을살펴보자. 이예제에서부모프로세스는두개의자식프로세스를생성하며각자식프로세스가메시지와프로세스 ID를프린트한다. 프로그램 10.4 fork3.c #include <stdlib.h>
#include <stdio.h> main() { int pid1, pid2; pid1 = fork(); if (pid1 == 0) { printf("[child 1] : Hello, world! pid=%d\n",getpid()); exit(0); pid2 = fork(); if (pid2 == 0) { printf("[child 2] : Hello, world! pid=%d\n",getpid()); exit(0); 실행결과 [Child 1] : Hello, world! pid=15741 [Child 2] : Hello, world! pid=15742 QnA 첫번째자식프로세스부분에서 exit(0) 를왜하지요? int wait(int* status) wait() 시스템호출은자식프로세스중에하나가종료될때가지호출한프로세스를기다리게한다. 종료된자식프로세스의 pid를리턴하고상태코드를 *status에둔다. 이제 wait 시스템호출을이용하여부모프로세스가자식프로세스가끝나기를기다리는다음예제프로그램을살펴보자. 부모프로세스는자식프로세스가끝나기를기다리며끝난후에는자식프로세스종료메시지와자식프로세스로부터받은종료코드값을프린트한다. 프로그램 10.5 forkwait.c #include <stdio.h> main() { int pid, child, status;
printf("[%d] 부모프로세스시작 \n", getpid( )); pid = fork(); if (pid == 0) { printf("[%d] 자식프로세스시작 \n", getpid( )); exit(1); child = wait(&status); printf("[%d] 자식프로세스 %d 종료 \n", getpid(), child); printf("\t종료코드 %d\n", status>>8); 실행결과 [15943] 부모프로세스시작 [15944] 자식프로세스시작 [15943] 자식프로세스 15944 종료종료코드 1
10.4 프로그램실행 앞에서살펴본것처럼부모프로세스가자식프로세스를생성하면자식프로세스는부모프로세스와같은코드를실행한다. 그렇다면자식프로세스에게새로운일을시키려면어떻게하여야할까? 이를위해서는자식프로세스내에서새로운프로그램을실행시킬수있는방법이있어야한다. exec() 시스템호출을이용하여프로세스내에서새로운프로그램을실행시킬수있으며 exec() 시스템호출이프로세스내에서새로운프로그램을실행시키는유일한방법이다. exec() 시스템호출의원리를간단히요약하면자기대치 ( 自己代置 ) 라고할수있다. 프로세스가 exec() 호출을하면, 그프로세스내의프로그램은완전히새로운프로그램 ( 코드, 데이터, 스택등 ) 으로대치된다. 그리고새프로그램의 main() 부터실행이시작한다. 그림 10.6 프로그램실행 exec() 호출이성공하면그프로세스내에기존의프로그램은없어지고새로운프로그램으로대치되므로 exec() 호출은리턴할곳이없어진다. 성공한 exec() 호출은절대리턴하지않는다는점을유의하자. exec() 호출은실패할경우에만리턴한다. int execl(char* path, char* arg0, char* arg1,..., char* argn, NULL) int execv(char* path, char* argv[ ]) 호출한프로세스의코드, 데이터, 힙, 스택등을 path가나타내는새로운프로그램으로대치한후새프로그램을실행한다. 성공한 exec( ) 호출은리턴하지않으며실패하면 -1을리턴한다. exec 시스템호출에는크게 execl() 과 execv() 이있다. execl() 시스템호출은명령줄인수를하나씩나열하고 NULL은인수끝을나타낸다. execv() 시스템호출을할때는
명령줄인수를하나씩나열하지않고명령줄인수리스트를포인터배열로만들어이배열의이름을전달한다. 보통다음과같이 fork() 시스템호출후에 exec() 시스템호출하는경우가일반적이며새로실행할프로그램에대한정보를 arguments로전달한다. exec() 시스템호출이성공하면자식프로세스는새로운프로그램을실행하게되고부모는계속해서다음코드를실행하게된다. exec() 시스템호출이실패하면자식프로세스는 exit(1) 를호출하여종료한다. if ((pid = fork()) == 0 ){ exec( arguments ); exit(1); // 부모계속실행 간단한예제프로그램을살펴보자. 이프로그램은자식프로세스를생성하여자식프로세스로하여금 echo 명령어를실행하게한다. 여기서는 execl() 시스템호출을사용하였으며명령줄인수로 "hello" 스트링을주고 NULL은인수끝을나타낸다. execl("/bin/echo", "echo", "hello", NULL); 자식프로세스는 echo 명령어를실행하여명령줄인수로받은 "hello" 스트링을그대로프린트한다. 프로그램 10.6 execute1.c #include <stdio.h> main( ) { printf(" 부모프로세스시작 \n"); if (fork( ) == 0) { execl("/bin/echo", "echo", "hello", NULL); fprintf(stderr," 첫번째실패 "); exit(1); printf(" 부모프로세스끝 \n"); 실행결과부모프로세스시작 hello 부모프로세스끝
이예제프로그램은세개의자식프로세스를생성하여각각에게서로다른명령어를실행시킨다. 프로그램 10.7 execute2.c #include <stdio.h> main( ) { printf(" 부모프로세스시작 \n"); if (fork( ) == 0) { execl("/bin/echo", "echo", "hello", NULL); fprintf(stderr," 첫번째실패 "); exit(1); if (fork( ) == 0) { execl("/bin/date", "date", NULL); fprintf(stderr," 두번째실패 "); exit(2); if (fork( ) == 0) { execl("/bin/ls","ls", "-l", NULL); fprintf(stderr," 세번째실패 "); exit(3); printf(" 부모프로세스끝 \n"); 실행결과 위프로그램은정해진명령어만실행시킨다. 이제명령줄인수로받은임의의명령어를실행시키는프로그램을작성해보자. 다음예제프로그램은명령줄인수로받은명령어의실행을위해자식프로세스를생성하고자식프로세스로하여금그명령어를실행하게한다. 부모프로세스는자식프로세스가끝날때까지기다리며자식프로세스가종료하면자식프로세스종료메시지와자식프로세스로부터받은종료코드를프린트한다. 이프로그램의실행과정을다음그림과같이표현할수있다. wait() fork() exec() exit()
프로그램 10 execute3.c #include <stdio.h> int main(int argc, char *argv[]) { int child, pid, status; pid = fork( ); if (pid == 0) { execvp(argv[1], &argv[1]); fprintf(stderr, "%s: 실행불가 \n",argv[1]); else { child = wait(&status); printf("[%d] 자식프로세스 %d 종료 \n", getpid(), pid); printf("\t종료코드 %d \n", status>>8); 이프로그램을이용하여명령줄인수로받은임의의명령어를실행시킬수있다. 예를들어다음명령은 ls 명령어를실행시킨다. $ execute ls -l /tmp 기타시스템호출 - 현재디렉토리변경 각프로세스는현재작업디렉토리 (current working directory) 를가지고있다. chdir() 시스템호출은현재작업디렉토리를매개변수가지정한경로 pathnamedm로변경한다. 이시스템호출이성공하려면프로세스는그디렉토리에대한실행권한이있어야한다. 성공하면 0 실패하면 -1를리턴한다. int chdir (char* pathname) 현재작업디렉토리를 pathname 으로변경한다. - 프로세스의사용자 ID 와그룹 ID 각프로세스마다사용자 ID가있는데프로세스의사용자 ID는로그인하여그프로세스를실행시킨사용자를나타낸다. 다음시스템호출은프로세스의소유주의사용자 ID와그룹
ID 를각각리턴한다. int getuid() 프로세스의사용자 ID를리턴한다. int getgid() 프로세스의그룹 ID를리턴한다. 또한다음시스템호출은프로세스의소유주를매개변수로지정해준사용자 ID와그룹 ID로각각변경한다. int setuid(uid_t uid) 프로세스의사용자 ID를 uid로변경한다. int setgid(gid_t gid) 프로세스의그룹 ID를 gid로변경한다.
10.5 시스템부팅 시스템부팅과정을생각해보자. 시스템이부팅되면서여러개의프로세스가생성되는데이과정은어떻게이루어질까? 시스템부팅과정에서앞에서배운 fork/exec는매우유용하게사용된다. 부팅이시작되면커널내부에서프로세스 ID가 0인첫번째프로세스 swapper가만들어진다. swapper는프로세스를스케쥴링하는기능을한다. 이프로세스는 fork/exec를수행하여 1번프로세스인 init 프로세스를생성한다. init 프로세스는역시 fork/exec를반복적으로수행하여시스템운영에필요한다양한프로세스들 ( 주로서버데몬프로세스 ) 을새로생성한다. 이 init 프로세스는모든프로세스의조상이라고할수있다. 그림 10.7은이러한부팅과정을보여주고있는데예를들어 sshd와같은 ssh 데몬프로세스나 getty 프로세스를생성한다. 이그림에서괄호안의수는프로세스 ID를나타낸다. 데몬프로세스중에는 getty(linux 경우에는 mingetty) 프로세스가있는데이프로세스로부터로그인과정이시작된다. 이프로세스는화면에로그인프롬프트를띄우고사용자의 ID가입력되기를기다린다. 입력이들어오면 fork() 시스템호출은하지않고바로 exec() 시스템호출을하여 login 프로그램 (/bin/login) 을실행한다. 이프로그램이패스워드등을검사하고성공하면다시 exec() 시스템호출을하여 shell 프로그램 ( 예를들어 /bin/sh) 을실행한다. 그림 10.7은이러한로그인과정을보여주고있는데 getty 프로세스가 login 프로세스, shell 프로세스로변화하지만 fork() 시스템호출은하지않고 exec() 시스템호출만하기때문에프로세스 ID는모두같다는점을유의하자. 그림 10.7 부팅및로그인과정 각프로세스에대한보다자세한설명은다음과같다. swapper ( 스케줄러프로세스 )
swapper는커널내부에서만들어진프로세스로프로세스스케줄링을한다. 이프로세스는커널내의코드를실행하기때문에별도의실행파일이존재하지않는다. init ( 초기화프로세스 ) init 프로세스 (/etc/init 혹은 /sbin/init ) 는 /etc/inittab 파일에기술된대로시스템을초기화하는데이파일내에서다시 /etc/rc* 즉 rc로시작되는이름의쉘스크립트들을실행한다. 이러한과정을통해서파일시스템마운트, 서버데몬프로세스생성, getty 프로세스생성등의작업을수행하여시스템을초기화한다. getty 프로세스 getty 프로세스 (/etc/getty 혹은 /etc/mingetty) 는로그인프롬프트를내고키보드입력을감지한다. 아이디, 패스워드를입력하면로그인절차를진행하기위해로그인프로그램 (/bin/login) 을실행한다. login 프로세스 login 프로세스는 /etc/passwd 파일을참조하여사용자의로그인아이디및패스워드를검사한다. 로그인절차가성공하면쉘프로그램 (/bin/sh, /bin/csh 등 ) 을실행한다. shell 프로세스 shell 프로세스는시작파일을실행한후에쉘프롬프트를내고사용자로부터명령어를기다린다. 명령어가입력되면해석하여명령어를실행시킨다. 명령어실행후에다시쉘프롬프트를내고이과정을반복한다.
10.6 시그널 (Signal) 프로그램실행도중에예기치않는사건이발생하면이를실행중인프로그램에알려줄수있어야한다. 예를들어연산중에 0으로나누는오류가발생하면재빨리이를프로그램에알려야하고프로그램에서는이를적절히처리해야한다. 이러한의미에서시그널은예기치않은사건이일어날때발생하는소프트웨어인터럽트라고할수있다. 예를들어다음과같은경우에시그널이발생한다. 부동소숫점오류 정전 알람시계울림 자식프로세스종료 키보드로부터종료요청 (Ctrl-C) 키보드로부터정지요청 (Ctrl-Z) Unix에는총 31개의시그널이 /usr/include/signal.h에정의되어있다. 각시그널이름은 SIG로시작된다. 시그널이름 의미 SIGABRT SIGALRM SIGINT SIGSTP SIGFPE SIGSEGV SIGPIPE SIGCHLD SIGCONT SIGHUP SIGILL SIGIO SIGKILL SIGPIPE SIGSTOP SIGSYS SIGTERM abort() 에서발생되는종료시그널알람시계 alarm() 울림터미널에서 CTRL-C 할때발생하는인터럽트시그널터미널에서 CTRL-C 할때발생하는중지시그널 0으로나누기유효하지않은메모리참조끊어진파이프프로세스의종료혹은중지를부모에게알리는시그널중지된프로세스를계속시키는시그널연결끊김잘못된하드웨어명령어수행비동기화 I/O 이벤트알림프로세스종료시키는시그널파이프에쓰려고할때리더가없을때프로세스중지시그널유효하지않은시스템호출 kill() 에서보내는종료시그널
alarm() 시스템호출은매개변수로받은초후에 SIGALRM 시그널을발생시킨다. 프로그램이실행중에이시그널을받으면 Alarm clock" 메시지를출력하고프로그램은종료된다. 다음예제프로그램을살펴보자. 이프로그램에서는 5초후에 SIGALRM 시그널이발생된다. while 루프는 1초에한번씩 1초경과 라는메시지를출력하다가 5초가지나면해당시그널을받아 Alarm clock" 메시지를출력하고프로그램은종료된다. 마지막 printf 문은무한루프뒤에위치해있으며무한루프실행중에 SIGALRM 시그널을받으면프로그램이종료되므로절대로실행되지않음을주의하자. 프로그램 10.9 alarm.c #include <stdio.h> main( ) { alarm(5) printf(" 무한루프 \n"); while (1) { sleep(1); printf( 1초경과 \n"); printf(" 실행되지않음 \n"); 시그널처리 앞의예에서본것처럼발생한시그널을따로처리하지않으면시그널에따라다르지만많은경우에프로그램은거기서종료된다. 따라서시그널이발생하면이를잡아서적절히처리할수있어야한다. 이를위해서다음과같은의미로시그널에대한처리함수를지정할수있다. 이시그널이발생하면이렇게처리하라 시그널에대한처리함수지정은 signal() 시스템호출을통해할수있는데 signal() 시스템호출은다음과같은형태로각시그널에대한처리함수를등록한다. signal(int sigcode, void (*func)( ))) sigcode 에대한처리함수를 func 으로지정한다. func 은 SIG_IGN, SIG_DFL 혹
은사용자정의함수이름이다. 기존의처리함수를리턴한다. 세종류의처리함수의의미는다음과같다. SIG_IGN 발생된시그널을무시하겠다는의미로 SIGKILL, SIGSTOP을제외한시그널은필요하면무시할수있다. SIG_DFL 시그널에대한처리함수로디폴트처리함수를사용하겠다는의미이다. 각시그널마다미리정해진디폴트처리함수가있으며따로지정하지않으면이처리함수가수행된다. 사용자정의함수시그널에대한처리함수로지정한사용자정의함수를사용하겠다는의미이다. 이제시그널처리함수를이용한예제프로그램을살펴보자. 다음예제프로그램은명령줄인수로받은임의의명령어를제한시간내에실행시키는프로그램이다. 이프로그램은프로그램 10.xx를알람시그널을이용하여확장하여작성하였다. 명령어실행에제한시간을두기위해서는자식프로세스가명령어를실행하는동안정해진시간이초과되면 SIGALRM 시그널이발생하고이때자식프로세스를강제종료하면된다. 이를위해서두가지작업을수행한다. 먼저제한시간을입력받아알람시계를동작시킨다. 두번째로 SIGALRM 시그널에대한처리함수 alarmhandler() 를작성하고이를 SIGALRM 시그널에대한처리함수로지정한다. 이처리함수는 SIGALRM 시그널이발생되면자동적으로실행되어자식프로세스를강제적으로종료시킨다. 이예제에서는 kill(pid,sigint) 시스템호출을통해자식프로세스에 SIGINT 시그널을보내어강제적으로종료시켰다. 프로그램 10.10 limit.c #include <stdio.h> #include <signal.h> int pid; void alarmhandler(); main(int argc, char *argv[]) { int child, status, limit; signal(sigalrm,alarmhandler); sscanf(argv[1], "%d", &limit); alarm(limit);
pid = fork( ); if (pid == 0) { execvp(argv[2], &argv[2]); fprintf(stderr, "%s: 실행불가 \n",argv[1]); else { child = wait(&status); printf("[%d] 자식프로세스 %d 종료 \n", getpid(), pid); printf("\t종료코드 %d \n", status>>8); void alarmhandler() { printf("[ 알람 ] 자식프로세스 %d 시간초과 \n", pid); kill(pid,sigint); 프로세스에 Signal 보내기 상황에따라시그널이자동적으로발생되기도하지만앞에예에서도본것처럼필요에따라특정프로세스에임의의시그널을강제적으로보낼필요가있다. 이러한기능은한프로세스가다른프로세스를제어하는데매우유용하게사용될수있다. 다음의 kill() 시스템호출을이용하여특정프로세스에원하는임의의시그널을보낼수있다. int kill(int pid, int sigcode) 프로세스 pid에시그널 sigcode를보낸다. 성공조건 : 보내는프로세스의소유자가프로세스 pid의소유자와같거나혹은보내는프로세스의소유자가슈퍼유저이어야한다. 시그널을이용하여자식프로세스를제어하는예제프로그램을살펴보자. 프로그램 10.11 은자식프로세스를생성하고실행중인자식프로세스에 SIGSTOP 시그널을보내어정지시키고다시 SIGCONT 시그널을보내어실행을계속하게한다. 그후 SIGINT 시그널을보내어자식프로세스를강제종료시킨다. 프로그램 10.11 control.c #include <signal.h> #include <stdio.h>
main( ) { int pid1, pid2; pid1 = fork( ); if (pid1 == 0) { while(1) { printf(" 프로세스 %id 실행 \n", getpid()); sleep(1); sleep(3); kill(pid1, SIGSTOP); sleep(3); kill(pid1, SIGCONT); sleep(3); kill(pid1, SIGINT);