chap12(process).hwp

Similar documents
<C1A63130C0E5C7C1B7CEBCBCBDBA2E687770>

제9장 프로세스 제어

제1장 Unix란 무엇인가?

제1장 Unix란 무엇인가?

1장. 유닉스 시스템 프로그래밍 개요

ABC 11장

10.

제8장 프로세스

좀비프로세스 2

제8장 프로세스

<4D F736F F F696E74202D FC7C1B7CEBCBCBDBA20BBFDBCBAB0FA20BDC7C7E0205BC8A3C8AF20B8F0B5E55D>

2009년 상반기 사업계획

제1장 Unix란 무엇인가?

<4D F736F F F696E74202D20B8AEB4AABDBA20BFC0B7F920C3B3B8AEC7CFB1E22E BC8A3C8AF20B8F0B5E55D>

Microsoft PowerPoint - Lecture_Note_7.ppt [Compatibility Mode]

리눅스 프로세스 관리

Microsoft PowerPoint - 10_Signal

6주차.key

제12장 파일 입출력

Microsoft PowerPoint - 10_Process

PowerPoint 프레젠테이션

Microsoft PowerPoint - chap9 [호환 모드]

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

Microsoft PowerPoint - 09-Pipe

/chroot/lib/ /chroot/etc/

System Programming Lab

슬라이드 1

<4D F736F F F696E74202D BDC3B1D7B3CEB0FA20BDC3B1D7B3CE20C3B3B8AE2E707074>

Microsoft PowerPoint - SP6장-시그널.ppt [호환 모드]

본 강의에 들어가기 전

<4D F736F F F696E74202D FB8DEB8F0B8AE20B8C5C7CE205BC8A3C8AF20B8F0B5E55D>

2009년 상반기 사업계획

The Pocket Guide to TCP/IP Sockets: C Version

제1장 Unix란 무엇인가?

PowerPoint 프레젠테이션

임베디드시스템설계강의자료 6 system call 2/2 (2014 년도 1 학기 ) 김영진 아주대학교전자공학과

Microsoft PowerPoint - chap6 [호환 모드]

Microsoft PowerPoint - ch09_파이프 [호환 모드]

2009년 상반기 사업계획

Microsoft PowerPoint - chap13-입출력라이브러리.pptx

슬라이드 1

슬라이드 1

금오공대 컴퓨터공학전공 강의자료

Microsoft PowerPoint oshw1.ppt [호환 모드]

Microsoft PowerPoint oshw1.ppt [호환 모드]

PowerPoint 프레젠테이션

학번 : 이름 : 1. 다음파일트리구조를가진유닉스시스템이있다고가정하자. / /bin/ /home/ /home/taesoo/ /usr/ /usr/lib/ /usr/local/lib /media 모든폴더에파일이하나도없다고가정했을때사용자가터미널에서다음 ls 명령입력시화면출력

Microsoft PowerPoint oshw1&2.ppt [호환 모드]

PowerPoint 프레젠테이션

고급 프로그래밍 설계

C++ Programming

슬라이드 1

제 14 장포인터활용 유준범 (JUNBEOM YOO) Ver 본강의자료는생능출판사의 PPT 강의자료 를기반으로제작되었습니다.

Microsoft PowerPoint - chap06-5 [호환 모드]

Chap04(Signals and Sessions).PDF

슬라이드 1

PowerPoint 프레젠테이션

2009년 상반기 사업계획

PowerPoint 프레젠테이션

금오공대 컴퓨터공학전공 강의자료

chap7.key

<322EBCF8C8AF28BFACBDC0B9AEC1A6292E687770>

Microsoft PowerPoint APUE(File InO).pptx

Microsoft PowerPoint - chap10-함수의활용.pptx

학번 : 이름 : 1. 다음파일트리구조를가진유닉스시스템이있다. / /bin/ /home/ /home/taesoo/ /home/taesoo/downloads /usr/ /usr/lib/ /usr/local/lib /media 모든폴더에파일이하나도없다고가정했을때사용자 (t

Chapter #01 Subject

3. 다음장에나오는 sigprocmask 함수의설명을참고하여다음프로그램의출력물과그출력물이화면이표시되는시점을예측하세요. ( 힌트 : 각줄이표시되는시점은다음 4 가지중하나. (1) 프로그램수행직후, (2) kill 명령실행직후, (3) 15 #include <signal.

vi 사용법

슬라이드 1

<4D F736F F F696E74202D FC7C1B7CEBCBCBDBABFCD20BBE7BFEBC0DA20B8EDB7C920C0CDC8F7B1E22E >

Microsoft PowerPoint UNIX Shell.ppt

<4D F736F F F696E74202D20BFEEBFB5C3BCC1A6BDC7BDC D31C7D0B1E229202D20BDA92E BC8A3C8AF20B8F0B5E55D>

<4D F736F F F696E74202D B3E22032C7D0B1E220C0A9B5B5BFECB0D4C0D3C7C1B7CEB1D7B7A1B9D620C1A638B0AD202D20C7C1B7B9C0D320BCD3B5B5C0C720C1B6C0FD>

Microsoft PowerPoint - chap06-2pointer.ppt

C 프로그램의 기본

10 강. 쉘스크립트 l 쉘스크립트 Ÿ 쉘은명령어들을연속적으로실행하는인터프리터환경을제공 Ÿ 쉘스크립트는제어문과변수선언등이가능하며프로그래밍언어와유사 Ÿ 프로그래밍언어와스크립트언어 -프로그래밍언어를사용하는경우소스코드를컴파일하여실행가능한파일로만들어야함 -일반적으로실행파일은다

슬라이드 1

11장 포인터

슬라이드 1

Microsoft PowerPoint - chap4 [호환 모드]

JVM 메모리구조

PowerPoint 프레젠테이션

슬라이드 1

Microsoft PowerPoint UNIX Shell.pptx

Microsoft PowerPoint APUE(File InO)

설계란 무엇인가?

The OSI Model

제2장 리눅스 사용

PowerPoint Presentation

Microsoft PowerPoint - [2009] 02.pptx

쉽게 풀어쓴 C 프로그래밍

OCW_C언어 기초

BMP 파일 처리

PowerPoint 프레젠테이션

chap2

vi 사용법

11장 포인터

Microsoft PowerPoint APUE(File InO).ppt

PowerPoint Presentation

Microsoft PowerPoint - chap2

Transcription:

제 12 장프로세스 프로세스는파일과더불어유닉스운영체제가제공하는핵심개념중의하나이다. 유닉스시스템을깊이있게이해하기위해서는프로세스에대해정확히이해해야한다. 이장에서는프로그램이시작되는과정, 프로세스의구조, 프로세스생성및프로그램실행메커니즘, 프로세스사이의시그널등에대해서자세히살펴본다. 12.1 프로그램시작및종료 유닉스에서프로그램은어떻게실행이시작되고종료될까? 프로그램은 12.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 함수로프로그램실행이시작되는과정은그림 12.1과같다. 그림 12.1 프로그램실행의시작 exec() 시스템호출은실행되는프로그램에게명령줄인수를전달하는데실행되는프로그램의 main 함수는 argc와 argv 매개변수를통해서명령줄인수의수와명령줄인수에대한포인터배열을전달받는다. - 1 -

int main(int argc, char *argv[]); argc : 명령줄인수의수 argv[] : 명령줄인수리스트를나타내는포인터배열 명령줄인수리스트를나타내는포인터배열 argv 의구성은그림 12.2 와같다. 그림 12.2 명령중인수리스트 argv 구성 또한전역변수 environ을통해환경변수리스트도전달받는데그구성의예는그림 12.3과같다. 그림 12.3 환경변수리스트 environ 의구성 예제 12.1 프로그램은모든명령줄인수와환경변수를프린트한다. 명령줄인수의개수 argc와 for 루프를이용하여명령줄인수를하나씩프린트한다. 포인터변수 ptr을이용하여환경변수리스트의시작위치인 environ에서부터시작하여 1씩증가하면서각환경변수를프린트한다. 예제 12.1 printall.c - 2 -

/* 모든명령줄인수와환경변수를프린트한다. */ 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); $ printall hello world argv[0]: printall argv[1]: hello argv[2]: world HOME=/user/faculty/chang PATH=.:/usr/local/bin:/bin:/sbin:/usr/bin:/usr/ucb:/etc:/usr/sbin:/ usr/ccs/bin... 이제프로그램이종료하는방법에대해서알아보자. 프로그램의실행을종료하는방법은정상종료 (normal termination), 비정상종료 (abnormal termination) 로크게두가지로나눌수있다. 먼저프로그램이정상적으로종료하는방법부터알아보도록하자. main() 실행을마치고리턴하면 C 시작루틴은이리턴값을가지고 exit() 을호출한다. 프로그램내에서직접 exit() 을호출할수있다. 프로그램내에서직접 _exit() 을호출할수있다. exit() 시스템호출은프로세스를정상적으로종료시키는데종료전에모든열려진스트림을닫고 (fclose), 출력버퍼의내용을디스크에쓰는 (fflush) 등의뒷정리 (cleanup processing) 를한다. 프로세스의종료상태를알리는종료코드 (exit code) 를부모프로세스에게전달한다. - 3 -

#include <stdlib.h> void exit(int status); 뒷정리를한후프로세스를정상적으로종료시킨다. _exit() 시스템호출역시프로세스를정상적으로종료시키는데뒷정리를하지않고즉시종료된다는점이 exit() 시스템호출과다르다. #include <stdlib.h> void _exit(int status); 뒷정리를하지않고프로세스를즉시종료시킨다. 프로그램이비정상적으로종료하는방법은 2 가지가있다. abort() 시스템호출은프로세스에 SIGABRT 시그널을보내어프로세스를비정상적으로종료시킨다. 시그널에의한종료 : 프로세스가실행중에시그널을받으면갑자기비정상적으로종료하게된다. 시그널에대한자세한사항은 12.6 절에서자세히다룬다. 12.2 프로세스구조 프로세스 (process) 란무엇인가? 프로세스에대한정의혹은설명은여러가지가있지만가장쉬운정의는실행중인프로그램 (executing program) 을프로세스라고생각하는것이다. 다시말하면프로그램이실행되면프로세스가되는것이다. 한프로그램은여러번실행될수있으므로한프로그램으로부터여러개의프로세스를만들수있으며프로그램그자체가프로세스는아니라는점을주의하자. 프로세스는실행중인프로그램이다. 프로그램을실행즉프로세스를유지하기위해서는무엇이필요할지생각해보자. 먼저프로세스관리를위한커널내의프로세스에대한정보가필요할것이다. 또한프로그램실행을위해서는그림 12.4와같이실행코드, 데이터, 힙, 스택등을메모리내에배치해야한다. 이러한메모리배치를프로세스메모리이미지라고한다. 프로세스메모리이미지를구성하는프로그램의실행코드, 데이터, 스택, 힙등의영역의역할은다음과같다. - 4 -

그림 12.4 프로세스메모리이미지 코드 (code) 프로세스의실행코드를저장하는영역이다. 데이터 (data) 전역변수 (global variable) 및정적변수 (static variable) 를위한영역이다. 힙 (heap) 동적메모리할당을위한영역이다. 스택 (stack area) 함수호출을구현하기위한 ( 지역변수를포함하는 ) 활성레코드 (activation record) 를저장하기위한실행시간스택 (runtime stack) 을위한영역이다. U- 영역 (user-area) 열린파일디스크립터등과같은시스템관리정보를저장하는영역이다. 12.3 프로세스생성 각프로세스는프로세스를구별하는번호인프로세스 ID를갖고있다. 실행중인프로그램즉프로세스가 getpid() 를호출하면실행중인프로세스의 ID를리턴한다. 또한 getppid() 를호출하면실행중인프로세스의부모프로세스의 ID를리턴한다. - 5 -

int getpid( ); 프로세스의 ID를리턴한다. int getppid( ); 부모프로세스의 ID를리턴한다. 유닉스에서는필요에따라새로운프로세스를생성해야하는데 fork() 시스템호출이프로세스를생성하는유일한방법이다. #include <sys/types.h> #include <unistd.h> pid_t fork(void); 새로운자식프로세스를생성한다. 자식프로세스에게는 0을리턴하고부모프로세스에게는자식프로세스 ID를리턴한다. 부모프로세스 (parent process) 는 fork() 시스템호출을통해새로운자식프로세스 (child process) 를생성한다. 프로세스생성원리를간단히요약하면자기복제 ( 自己複製 ) 라고할수있다. 자식프로세스는부모프로세스 ( 코드, 데이터, 스택, 힙등 ) 를똑같이복제해만들어진다 ( 그림 12.5 참조 ). 그림 12.5 프로세스생성전과후 fork() 시스템호출을하면새로운자식프로세스가즉시생성되며부모프로세스와 자 - 6 -

식프로세스에게각각리턴한다. 자식프로세스에게는 0을리턴하고부모프로세스에게는자식프로세스 ID를리턴한다. fork() 시스템호출은한번호출되지만두번리턴된다는점을주의하자. fork() 호출후에부모프로세스와자식프로세스가병행적으로실행을계속한다. fork() 시스템호출은부모프로세스를똑같이복제하여새로운자식프로세스를생성한다. 예제 12.2 프로그램을통해 fork() 호출후에리턴값과실행흐름을살펴보자. 실행결과를보면 fork() 뒤에나오는문장은부모프로세스와자식프로세스에의해각각실행되며부모프로세스에게는생성된자식프로세스의 ID(15066) 이리턴되고자식프로세스에게는 0이리턴됨을확인할수있다. 예제 12.2 fork1.c /* 자식프로세스를생성한다. */ int main() int pid; printf("[%d] 프로세스시작 \n", getpid()); pid = fork(); printf("[%d] 프로세스 : 리턴값 %d\n", getpid(), pid); $fork1 [15065] 프로세스시작 [15065] 프로세스 : 리턴값 15066 [15066] 프로세스 : 리턴값 0 이예제를통해 fork() 호출뒤에나타나는문장은부모프로세스와자식프로세스가병행적으로모두실행한다는것을확인할수있었다. 그러면부모프로세스와자식프로세스가서로다른일을하려면어떻게하여야할까? fork() 호출후에리턴값이다르므로이리턴값을이용하면부모프로세스와자식프로세스를구별하고서로다른일을하도록할수있을것이다. 따라서다음과같은코드를수행하면 fork() 호출후에자식프로세스는자식을위한코드부분을실행하고부모프로세스는부모를위한코드부분을실행한다. - 7 -

pid = fork(); if ( pid == 0 ) 자식프로세스의실행코드 else 부모프로세스의실행코드 다음예제프로그램은부모프로세스가자식프로세스를생성하며각프로세스가메시지와프로세스 ID를프린트한다. 실행결과를보면자식프로세스는리턴값으로 0을받았으므로 if 문의 then 부분을실행했고부모프로세스는리턴값으로자식프로세스 ID(15800) 을받았으므로 if 문의 else 부분을실행했음을확인할수있다. 예제 12.3 fork2.c #include <stdlib.h> /* 부모프로세스가자식프로세스를생성하고서로다른메시지를프린트한다. */ int 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()); $ fork2 [Parent] : Hello, world pid=15799 [Child] : Hello, world pid=15800 이제하나의부모프로세스가두개의자식프로세스를생성하는다음예제프로그램을살펴보자. 이예제에서부모프로세스는두개의자식프로세스를생성하며각자식프로세스가메시지와프로세스 ID를프린트한다. 예제 12.4 fork3.c #include <stdlib.h> - 8 -

/* 부모프로세스가두개의자식프로세스를생성한다. */ int 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); $fork3 [Child 1] : Hello, world! pid=15741 [Child 2] : Hello, world! pid=15742 QnA 첫번째자식프로세스부분에서 exit(0) 를왜하지요? 만약첫번째자식프로세스가 exit() 를하지않으면 if 문이후에실행을계속하여 fork() 호출을하게됩니다. 결과적으로자식프로세스가또자식프로세스를생성하게되겠지요. 부모프로세스는 wait() 시스템호출을이용하여자식프로세스중의하나가끝나기를기다릴수있다. 자식프로세스가끝나면끝난자식프로세스의종료코드가 *status에저장되며끝난자식프로세스의번호를리턴한다. #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); 자식프로세스중의하나가끝날때까지기다린다. 끝난자식프로세스의종료코드가 status에저장되며끝난자식프로세스의번호를리턴한다. - 9 -

이제 wait() 시스템호출을이용하여부모프로세스가자식프로세스가끝나기를기다리는다음예제프로그램을살펴보자. 부모프로세스는자식프로세스를생성하고자식프로세스가끝나기를기다리며끝난후에는자식프로세스종료메시지와자식프로세스로부터받은종료코드값을프린트한다. 예제 12.5 forkwait.c /* 부모프로세스가자식프로세스를생성하고끝나기를기다린다. */ int 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); $ forkwait [15943] 부모프로세스시작 [15944] 자식프로세스시작 [15943] 자식프로세스 15944 종료종료코드 1 12.4 프로그램실행 앞에서살펴본것처럼부모프로세스가자식프로세스를생성하면자식프로세스는부모프로세스와같은코드를실행한다. 그렇다면자식프로세스에게새로운일 ( 프로그램 ) 을시키려면어떻게하여야할까? 이를위해서는자식프로세스내에서새로운프로그램을실행시킬수있는방법이있어야한다. exec() 시스템호출을이용하여프로세스내에서새로운프로그램을실행시킬수있으며 - 10 -

exec() 시스템호출이프로세스내에서새로운프로그램을실행시키는유일한방법이다. exec() 시스템호출의원리를간단히요약하면자기대치 ( 自己代置 ) 라고할수있다. 프로세스가 exec() 호출을하면, 그프로세스내의프로그램은완전히새로운프로그램 ( 코드, 데이터, 힙, 스택등 ) 으로대치된다. 그리고새프로그램의 main() 부터실행이시작한다. 그림 12.6 프로그램실행 exec() 시스템호출은프로세스내의프로그램을새로운프로그램으로대치하여새로운프로그램을실행시킨다. exec() 호출이성공하면그프로세스내에기존의프로그램은없어지고새로운프로그램으로대치되므로 exec() 호출은리턴할곳이없어진다. 따라서성공한 exec() 호출은절대리턴하지않는다는점을유의하자. exec() 호출은실패할경우에만리턴한다. #include <unistd.h> int execl(char* path, char* arg0, char* arg1,..., char* argn, NULL) int execv(char* path, char* argv[ ]) int execlp(char* file, char* arg0, char* arg1,..., char* argn, NULL) int execvp(char* file, char* argv[ ]) 호출한프로세스의코드, 데이터, 힙, 스택등을 path가나타내는새로운프로그램으로대치한후새프로그램을실행한다. 성공한 exec( ) 호출은리턴하지않으며실패하면 -1을리턴한다. exec() 시스템호출에는 execl() 과 execv() 가있다. execl() 시스템호출은명령줄인수를하나씩나열하고 NULL은인수끝을나타낸다. execv() 시스템호출을할때는명령줄인수를하나씩나열하지않고명령줄인수리스트를포인터배열로만들어이배열의 - 11 -

이름을전달한다. execlp() 와 execvp() 는각각 execl() 과 execv() 와같으며실행할파일을환경변수 PATH가지정한디렉터리들에서자동으로찾는다는점만다르다. 보통다음과같이 fork() 시스템호출후에 exec() 시스템호출하는경우가일반적이며새로실행할프로그램에대한정보를 arguments로전달한다. exec() 시스템호출이성공하면자식프로세스는새로운프로그램을실행하게되고부모는계속해서다음코드를실행하게된다. exec() 시스템호출이실패하면자식프로세스는 exit(1) 를호출하여종료한다. if ((pid = fork()) == 0 ) exec( arguments ); exit(1); // 부모계속실행 예제 12.6 프로그램을살펴보자. 이프로그램은자식프로세스를생성하여자식프로세스로하여금 echo 명령어를실행하게한다. 여기서는 execl() 시스템호출을사용하였으며명령줄인수로 "hello" 스트링을주고 NULL은인수끝을나타낸다. execl("/bin/echo", "echo", "hello", NULL); 자식프로세스는 echo 명령어를실행하여명령줄인수로받은 "hello" 스트링을그대로프린트한다. 예제 12.6 execute1.c /* 자식프로세스를생성하여 echo 명령어를실행한다. */ int main( ) printf(" 부모프로세스시작 \n"); if (fork( ) == 0) execl("/bin/echo", "echo", "hello", NULL); fprintf(stderr," 첫번째실패 "); exit(1); printf(" 부모프로세스끝 \n"); $ execute1 부모프로세스시작 - 12 -

hello 부모프로세스끝 예제 12.7 프로그램은세개의자식프로세스를생성한다. 첫번째프로세스는 echo 명령어를두번째프로세스는 date 명령어를세번째프로세스는 ls 명령어를실행한다. 예제 12.7 execute2.c /* 세개의자식프로세스를생성하여각각다른명령어를실행한다. */ int 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"); $ execute2 부모프로세스시작부모프로세스끝 hello 2012년 2월 28일화요일오후 08시 43분 47초총 50 -rwxr-xr-x 1 chang faculty 24296 2월 28일 20:43 execute2 -rw-r--r-- 1 chang faculty 556 2월 28일 20:42 execute2.c... - 13 -

지금까지살펴본프로그램은정해진명령어만실행시킨다. 이제명령줄인수로받은임의의명령어를실행시키는프로그램을작성해보자. 예제 12.8 프로그램은명령줄인수로받은명령어의실행을위해자식프로세스를생성하고자식프로세스로하여금그명령어를실행하게한다. 부모프로세스는자식프로세스가끝날때까지기다리며자식프로세스가종료하면자식프로세스종료메시지와자식프로세스로부터받은종료코드를프린트한다. 이러한실행과정을그림 12.7과같이표현할수있다. wait() fork() exec() exit() 그림 12.7 프로그램실행및기다리는과정 예제 12.8 execute3.c /* 명령줄인수로받은명령을실행시킨다. */ 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); 이프로그램을이용하여명령줄인수로받은임의의명령어를실행시킬수있다. 예를들어다음과같이 wc 명령어를실행시킬수있다. $ execute3 wc execute2.c - 14 -

25 68 556 execute2.c [26470] 자식프로세스 26471 종료 종료코드 0 12.5 입출력재지정 쉘은다음과같이명령어를실행하면명령어의표준출력이파일에저장되는입출력재지정기능을제공한다. $ 명령어 > 파일 이러한입출력재지정기능을어떻게구현할수있을까? dup() 혹은 dup2() 시스템호출을이용하여입출력재지정을구현할수있다. #include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd); 성공하면복사된새로운파일디스크립터를리턴하다. 실패하면 -1을리턴한다. dup() 는파일디스크립터 oldfd에대한복사본을생성하여리턴한다. dup2() 는파일디스크립터 oldfd을 newfd에복사한다. 파일디스크립터 oldfd와복사된새로운디스크립터는같은파일을공유하게된다. 두시스템호출모두성공하면복사된새로운디스크립터를리턴하다. 에러가발생하면 -1을리턴한다. 출력재지정을어떻게구현할수있을까? 그기본원리는다음과같이파일디스크립터 fd 를표준출력을나타내는 1번파일디스크립터에 dup2() 하는것이다. fd = open(argv[1], O_CREAT O_TRUNC O_WRONLY, 0600); dup2(fd, 1); 이제 fd가나타내는파일을 1번파일디스크립터도나타내게된다. 따라서 1번파일디스크립터를통해나오는출력 ( 표준출력 ) 은이제모두파일에저장될것이다. 이러한원리를예제 12.9 프로그램을통해확인해보자. 이예제는표준출력을통해프린트되는간단한인사말을명령줄인수로받은파일에저장한다. 일단명령줄인수로받은파일을 7번줄에서연다. 그리고 8번줄에서 dup2() 시스템호출을통해이파일디스크립터를 1번파일디스크립터에복사한다. 이제표준출력은모두이파일에저장될것이다. 10째줄에서프린트하는문자열은이파일에저장된다. 그러나 11번째줄에서출력되 - 15 -

는문자열은표준오류를통해출력되므로파일에저장되지않고모니터에출력된다. 예제 12.9 redirect1.c 1 2 #include <fcntl.h> 3 4 /* 표준출력을파일에재지정하는프로그램 */ 5 int main(int argc, char* argv[]) 6 7 int fd, status; 8 fd = open(argv[1], O_CREAT O_TRUNC O_WRONLY, 0600); 9 dup2(fd, 1); /* 파일을표준출력에복제 */ 10 close(fd); 11 printf("hello stdout!\n"); 12 fprintf(stderr,"hello stderr!\n"); 13 $ redirect1 out Hello stderr! $ cat out Hello stdout! 이제자식프로세스가실행하는명령어의표준출력이모두파일에재지정되도록하려면어떻게하여야할까? 기본원리는위의예제와같다. 단지명령어를실행하기전에해당파일디스크립터를 1번파일디스크립터에 dup2() 하면된다. 그이후에실행된명령어의표준출력은모두해당파일에저장될것이다. 예제 12.10 프로그램은자식프로세스로하여금명령어를실행하게하는예제 12.8 프로그램을표준출력을파일에재지정하도록수정한것이다. 첫번째명령줄인수로받은파일에두번째명령줄인수로받은명령어의표준출력이모두저장된다. 이를위해 11번째줄에서 dup2() 시스템호출을한후에명령줄인수로받은명령어를실행시키기위해 13 번째줄에서 exec() 시스템호출을하였다. 이제새로실행된프로그램의표준출력내용은모두파일에저장된다. 예제 12.10 redirect2.c 1 2 #include <fcntl.h> 3 4 /* 자식프로세스의표준출력을파일에재지정한다. */ - 16 -

5 int main(int argc, char* argv[]) 6 7 int child, pid, fd, status; 8 9 pid = fork( ); 10 if (pid == 0) 11 fd = open(argv[1], O_CREAT O_TRUNC O_WRONLY, 0600); 12 dup2(fd, 1); /* 파일을표준출력에복제 */ 13 close(fd); 14 execvp(argv[2], &argv[2]); 15 fprintf(stderr, "%s: 실행불가 \n",argv[1]); 16 else 17 child = wait(&status); 18 printf("[%d] 자식프로세스 %d 종료 \n", getpid(), child); 19 20 $ redirect2 out wc execute2.c [26882] 자식프로세스 26883 종료 $ cat out 25 68 556 execute2.c 기타시스템호출 각프로세스는현재작업디렉터리를가지고있다. chdir() 시스템호출은현재작업디렉토리를매개변수가지정한경로 pathname으로변경한다. 이시스템호출이성공하려면프로세스는그디렉토리에대한실행권한이있어야한다. #include <unistd.h> int chdir (char* pathname); 현재작업디렉토리를 pathname으로변경한다. 성공하면 0 실패하면 -1를리턴한다. 각프로세스마다사용자 ID가있는데프로세스의사용자 ID는로그인하여그프로세스를실행시킨사용자를나타낸다. 다음시스템호출은프로세스의소유주의사용자 ID와그룹 ID를각각리턴한다. #include <sys/types.h> - 17 -

#include <unistd.h> int getuid(); 프로세스의사용자 ID를리턴한다. int getgid(); 프로세스의그룹 ID를리턴한다. 또한다음시스템호출은프로세스의소유주를매개변수로지정해준사용자 ID와그룹 ID로각각변경한다. #include <sys/types.h> #include <unistd.h> int setuid(uid_t uid); 프로세스의사용자 ID를 uid로변경한다. int setgid(gid_t gid); 프로세스의그룹 ID를 gid로변경한다. 12.6 시스템부팅 시스템부팅과정을생각해보자. 시스템이부팅되면서여러개의프로세스가생성되는데이과정은어떻게이루어질까? 시스템부팅과정에서앞에서배운 fork/exec 시스템호출은매우유용하게사용된다. 시스템부팅은 fork/exec 시스템호출을통해이루어진다. 부팅이시작되면커널내부에서프로세스 ID가 0인첫번째프로세스 swapper가만들어진다. swapper는프로세스를스케쥴링하는기능을한다. 이프로세스는 fork/exec를수행하여 1번프로세스인 init 프로세스를생성한다. init 프로세스는역시 fork/exec를반복적으로수행하여시스템운영에필요한다양한프로세스들 ( 주로서버데몬프로세스 ) 을새로생성한다. 이 init 프로세스는모든프로세스의조상이라고할수있다. 그림 10.7은이러한부팅과정을보여주고있는데예를들어 sshd와같은 ssh 데몬프로세스나 getty 프로세스를생성한다. 이그림에서괄호안의수는프로세스 ID를나타낸다. 데몬프로세스중에는 getty( 리눅스경우에는 mingetty) 프로세스가있는데이프로세스로부터로그인과정이시작된다. 이프로세스는화면에로그인프롬프트를띄우고사용자의 ID가입력되기를기다린다. 입력이들어오면 fork() 시스템호출은하지않고바로 exec() 시스템호출을하여 login 프로그램 (/bin/login) 을실행한다. 이프로그램이패스워드등을검사하고성공하면다시 exec() 시스템호출을하여 shell 프로그램 ( 예를들어 /bin/sh) 을실행한다. - 18 -

그림 12.8은이러한로그인과정을보여주고있는데 getty 프로세스가 login 프로세스, shell 프로세스로변화하지만 fork() 시스템호출은하지않고 exec() 시스템호출만하기때문에프로세스 ID는모두같다는점을유의하자. 그림 12.8 부팅및로그인과정 각프로세스에대한보다자세한설명은다음과같다. 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 프로세스 - 19 -

shell 프로세스는시작파일을실행한후에쉘프롬프트를내고사용자로부터명령어를기다린다. 명령어가입력되면해석하여명령어를실행시킨다. 명령어실행후에다시쉘프롬프트를내고이과정을반복한다. 12.7 시그널 시그널종류 프로그램실행도중에예기치않는사건이발생하면이를실행중인프로그램에알려줄수있어야한다. 예를들어연산중에 0으로나누는오류가발생하면재빨리이를프로그램에알려야하고프로그램에서는이를적절히처리해야한다. 이렇게예기치않은사건이발생할때그림 12.9와같이해당프로세스에게이를알리는시그널이보내진다. 이러한의미에서시그널 (signal) 은예기치않은사건이발생할때이를알리는소프트웨어인터럽트라고할수있다. 시그널은예기치않은사건이발생할때이를알리는소프트웨어인터럽트이다. 예를들어다음과같은경우에시그널이발생한다. 부동소수점오류 정전 알람시계울림 자식프로세스종료 키보드로부터종료요청 (Ctrl-C) 키보드로부터정지요청 (Ctrl-Z) 그림 12.9 프로세스에시그널전달 유닉스에는총 31개의시그널이 /usr/include/signal.h에정의되어있다. 각시그널이름은 SIG로시작되며주요시그널은표 12.1과같다. - 20 -

시그널이름의미기본동작 SIGABRT abort() 에서발생되는종료시그널종료 ( 코어덤프 ) SIGALRM 알람시계 alarm() 울림종료 SIGCHLD 프로세스의종료혹은중지를부모에게알리는시그널무시 SIGCONT 중지된프로세스를계속시키는시그널무시 SIGFPE 0 으로나누기종료 ( 코어덤프 ) SIGHUP 연결끊김종료 SIGILL 잘못된하드웨어명령어수행종료 ( 코어덤프 ) SIGIO 비동기화 I/O 이벤트알림종료 SIGINT 터미널에서 CTRL-C 할때발생하는인터럽트시그널종료 SIGKILL 잡을수없는프로세스종료시키는시그널종료 SIGPIPE 파이프에쓰려는데리더가없을때종료 SIGPIPE 끊어진파이프종료 SIGPWR 전원고장종료 SIGSEGV 유효하지않은메모리참조종료 ( 코어덤프 ) SIGSTOP 프로세스중지시그널종료 ( 코어덤프 ) SIGSTP 터미널에서 CTRL-C 할때발생하는중지시그널정지 SIGSYS 유효하지않은시스템호출종료 ( 코어덤프 ) SIGTERM 잡을수있는프로세스종료시그널종료 SIGTTIN 후면프로세스가제어터미널을읽기정지 SIGTTOU 후면프로세스가제어터미널에쓰기정지 SIGUSR1 사용자정의시그널종료 SIGUSR2 사용자정의시그널종료 표 12.1 주요시그널 alarm() 시스템호출은매개변수로받은초후에 SIGALRM 시그널을발생시킨다. 프로그램이실행중에이시그널을받으면 경보시계 (Alarm clock)" 메시지를출력하고프로그램은종료된다. 예제 12.11 프로그램을살펴보자. 이프로그램에서는 5초후에 SIGALRM 시그널이발생된다. while 루프는 1초에한번씩 1초경과 라는메시지를출력하다가 5초가되면해당시그널을받아 경보시계 (Alarm clock)" 메시지를출력하고프로그램은종료된다. 마지막 printf 문은무한루프뒤에위치해있으며무한루프실행중에 SIGALRM 시그널을받으면프로그램이종료되므로절대로실행되지않음을주의하자. 예제 12.11 alarm.c - 21 -

/* 알람시그널을보여주는프로그램 */ int main( ) alarm(6); printf(" 무한루프 \n"); while (1) sleep(1); printf( 1초경과 \n"); printf(" 실행되지않음 \n"); $ alarm 무한루프 1초경과 1초경과 1초경과 1초경과 1초경과경보시계 (Alarm Clock) 시그널처리 앞의예에서본것처럼발생한시그널을따로처리하지않으면시그널에따라다르지만많은경우에프로그램은거기서종료된다. 따라서시그널이발생하면이를잡아서적절히처리할수있어야한다. 이를위해서다음과같은의미로시그널에대한처리함수를지정할수있다. 이시그널이발생하면이렇게처리하라 시그널에대한처리함수지정은 signal() 시스템호출을통해할수있는데 signal() 시스템호출은다음과같은형태로각시그널에대한처리함수를지정한다. func은 SIG_IGN, SIG_DFL 혹은사용자정의함수이름이다. #include <signal.h> signal(int signo, void (*func)( ))) signo에대한처리함수를 func으로지정한다. 기존의처리함수를리턴한다. - 22 -

세종류의처리함수의의미는다음과같다. SIG_IGN 발생된시그널을무시하겠다는의미로 SIGKILL, SIGSTOP을제외한시그널은필요하면무시할수있다. SIG_DFL 시그널에대한처리함수로디폴트처리함수를사용하겠다는의미이다. 각시그널마다미리정해진디폴트처리함수가있으며따로지정하지않으면이처리함수가수행된다. 사용자정의함수 시그널에대한처리함수로지정한사용자정의함수를사용하겠다는의미이다. 이제시그널처리함수를이용한예제 12.12 프로그램을살펴보자. 이프로그램은알람시그널이발생하면 alarmhandler() 가수행되도록등록하고무한루프에들어간다. 그사이에알람시그널이발생하면디폴트처리함수가수행되지않고등록된 alarmhandler() 함수가실행되어메시지를프린트하고프로그램을종료한다. 예제 12.12 almhandler.c #include <signal.h> void alarmhandler(); /* 알람시그널을처리한다. */ int main( ) signal(sigalrm,alarmhandler); alarm(5); /* 알람시간설정 */ printf(" 무한루프 \n"); while (1) sleep(1); printf("1초경과 \n"); printf(" 실행되지않음 \n"); void alarmhandler() printf(" 일어나세요 \n"); - 23 -

exit(0); $ almhanlder 무한루프 1초경과 1초경과 1초경과 1초경과일어나세요 pause() 시스템호출은시그널을받을때까지해당프로세스를정지시키는데이시그널은무시되지않는것이어야한다. pause() 는시그널을받았을때만반환하는데이경우해당시그널이처리되고나서 -1을리턴하며오류를나타내는전역변수인 errno는 EINTR로설정된다. 무시되는시그널을받은경우에는해당프로세스가깨어나지않는다. #include <signal.h> pause() pause() 시스템호출은시그널을받을때까지해당프로세스를잠들게만든다. 예제 12.13 프로그램은인터럽트시그널이발생하면 inthandler() 가수행되도록등록하고 pause() 시스템호출을하여정지된다. 인터럽트시그널 ( 보통 Ctrl-C에의해발생 ) 이발생하면이프로세스는깨어나서등록된 inthandler() 함수가실행되어메시지를프린트하고프로그램은종료된다. 예제 12.13 inthandler.c #include <signal.h> void inthandler(); /* 인터럽트시그널을처리한다. */ int main( ) signal(sigint,inthandler); while (1) pause(); printf(" 실행되지않음 \n"); - 24 -

void inthandler() printf(" 인터럽트시그널처리 \n"); exit(0); $ inthandler 무한루프 ^C인터럽트시그널처리 예제 12.14 프로그램은명령줄인수로받은임의의명령어를제한시간내에실행시키는프로그램이다. 이프로그램은명령줄인수로받은임의의명령어를실행시키는예제 12.8 프로그램을알람시그널을이용하여확장하여작성하였다. 명령어실행에제한시간을두기위해서는자식프로세스가명령어를실행하는동안정해진시간이초과되면 SIGALRM 시그널이발생하고이때자식프로세스를강제종료하면된다. 이를위해서두가지작업을수행한다. 먼저 SIGALRM 시그널에대한처리함수 alarmhandler() 를작성하고이를 SIGALRM 시그널에대한처리함수로지정한다. 다음에첫번째명령줄인수 (argv[1]) 로부터제한시간을입력받아알람시계를동작시킨다. 이알람시계로부터 SIGALRM 시그널이발생되면 alarmhandler() 처리함수가자동으로실행되어자식프로세스를강제적으로종료시킨다. 이예제에서는 kill(pid,sigint) 시스템호출을통해자식프로세스에 SIGINT 시그널을보내어강제적으로종료시킨다. 만약 SIGALRM 시그널이발생하기전에자식프로세스가종료하면이프로그램은정상적으로끝나게된다. 예제 12.14 tlimit.c #include <signal.h> int pid; void alarmhandler(); /* 명령줄인수로받은명령어실행에제한시간을둔다. */ int main(int argc, char *argv[]) int child, status, limit; signal(sigalrm, alarmhandler); sscanf(argv[1], "%d", &limit); alarm(limit); pid = fork( ); - 25 -

if (pid == 0) execvp(argv[2], &argv[2]); fprintf(stderr, "%s: 실행불가 \n",argv[1]); else child = wait(&status); printf("[%d] 자식프로세스 %d 종료 \n", getpid(), pid); void alarmhandler() printf("[ 알람 ] 자식프로세스 %d 시간초과 \n", pid); kill(pid,sigint); $ tlimit 3 sleep 5 [ 알람 ] 자식프로세스 27260 시간초과 [27259] 자식프로세스 27260 종료 프로세스에시그널보내기 상황에따라시그널이자동적으로발생되기도하지만앞에예에서도본것처럼필요에따라특정프로세스에임의의시그널을강제적으로보낼필요가있다. 이러한기능은한프로세스가다른프로세스를제어하는데매우유용하게사용될수있다. 다음의 kill() 시스템호출을이용하여특정프로세스 pid에원하는임의의시그널 signo를보낼수있다. 이시그널보내기가성공하기위해서는보내는프로세스의소유자가프로세스 pid의소유자와같거나혹은보내는프로세스의소유자가슈퍼유저이어야한다. #include <sys/types.h> #include <signal.h> int kill(int pid, int signo); 프로세스 pid에시그널 signo를보낸다. 성공하면 0 실패하면 -1를리턴한다. 시그널을이용하여자식프로세스를제어하는예제 12.15 프로그램을살펴보자. 프로그램 10.11은두개의자식프로세스를생성하고실행중인자식프로세스에 SIGSTOP 시그널을보내어정지시키고다시 SIGCONT 시그널을보내어실행을계속하게한다. 이과정을첫번째자식프로세스와두번째자식프로세스에대해서한다. 그후 SIGKILL 시그널을보내어자식프로세스들을강제종료시킨다. - 26 -

실행결과를통해서첫번째자식프로세스가정지되었을때는두번째자식프로세스만실행중이고두번째자식프로세스가정지되었을때는첫번째자식프로세스만실행중인것을확인할수있다. 예제 12.15 control.c #include <signal.h> /* 시그널을이용하여자식프로세스들을제어한다. */ int main( ) int pid1, pid2; pid1 = fork( ); if (pid1 == 0) while (1) sleep(1); printf(" 프로세스 [1] 실행 \n"); pid2 = fork( ); if (pid2 == 0) while (1) sleep(1); printf(" 프로세스 [2] 실행 \n"); sleep(2); kill(pid1, SIGSTOP); sleep(2); kill(pid1, SIGCONT); sleep(2); kill(pid2, SIGSTOP); sleep(2); kill(pid2, SIGCONT); sleep(2); kill(pid1, SIGKILL); kill(pid2, SIGKILL); - 27 -

$ control 프로세스 [1] 실행프로세스 [2] 실행프로세스 [1] 실행프로세스 [2] 실행프로세스 [2] 실행프로세스 [2] 실행프로세스 [1] 실행프로세스 [2] 실행프로세스 [1] 실행프로세스 [1] 실행프로세스 [1] 실행프로세스 [1] 실행프로세스 [2] 실행프로세스 [1] 실행프로세스 [2] 실행 raise() 시스템호출을이용하여프로세스가자기자신에게시그널을보낼수있다. 사실 raise(signo) 시스템호출은 kill(getpid(), signo) 시스템호출과같다. #include <signal.h> int raise(int sigcode); 프로세스가자신에게시그널 signo를보낸다. 성공하면 0 실패하면 -1를리턴한다. - 28 -

핵심개념 프로세스는실행중인프로그램이다. fork() 시스템호출은부모프로세스를똑같이복제하여새로운자식프로세스를생성한다. exec() 시스템호출은프로세스내의프로그램을새로운프로그램으로대치하여새로운프로그램을실행시킨다. 시스템부팅은 fork/exec 시스템호출을통해이루어진다. 시그널은예기치않은사건이발생할때이를알리는소프트웨어인터럽트이다. - 29 -

실습문제 1. 알람시그널과 pause() 시스템호출을이용하여 sleep() 함수를구현하시오. 2. 다음과같은기능을포함하는쉘인터프리터를작성하시오. (1) 명령어실행 [shell] cmd (2) 명령어순차적실행 [shell] cmd1; cmd2; cmd3 (3) 후면실행 [shell] cmd & (4) 입출력리디렉션 [shell] cmd > outfile [shell] cmd < infile - 30 -

연습문제 1. 다음프로그램의출력은무엇인가? [ 가정 ] 이프로그램은 100번프로세스에의해수행되고새로생성된자식프로세스의번호는생성된순서에따라 1씩증가한다. #include <stdlib.h> int main( ) int pid1, pid2; pid1 = fork(); printf("hello, world! pid=%d\n",pid1); pid2 = fork(); printf("goodbye, world! pid=%d\n",pid2); 2. 다음프로그램의출력은무엇인가? #include <stdlib.h> int main( ) int pid1, pid2; if ((pid1 = fork()) == 0) printf("hello, world pid=%d\n", getpid()); if ((pid2 = fork()) == 0) printf("goodbye, world pid=%d\n", getpid()); 3. 다음프로그램의출력을무엇인가. #include <stdlib.h> int main() int pid; - 31 -

printf("1: pid %d \n", getpid()); if ((pid = fork())==0) printf("2: ppid %d -> pid %d \n", getppid(), getpid() ); else printf("3: pid %d \n", getpid() ); if ((pid = fork()) ==0) printf("4: ppid %d -> pid %d \n, getppid(), getpid() ); else printf("5: pid %d \n", getpid() ); 4. 다음프로그램의출력을무엇인가. #include <stdlib.h> int main() int pid; printf("1: pid %d \n", getpid()); pid = fork(); if (pid == 0) printf("2: pid %d \n", getpid() ); else execl("/bin/echo", "echo", "3: 100", NULL); pid = fork(); if (pid == 0) printf("4: pid %d \n", getpid() ); else execl("/bin/echo", "echo", "5: 101", NULL); 5. 다음프로그램은몇개의자식프로세스를생성하는가? 그이유를설명하시오. #include <stdlib.h> int main( ) int pid; pid = fork(); pid = fork(); pid = fork(); - 32 -

- 33 -