제 9 장프로세스제어 리눅스시스템프로그래밍 청주대학교전자공학과 한철수
제 9 장 목차 프로세스생성 프로그램실행 입출력재지정 프로세스그룹 시스템부팅 2
9.1 절 프로세스생성 fork() 시스템호출 새로운프로그램을실행하기위해서는먼저새로운프로세스를생성해야하는데, fork() 시스템호출이새로운프로세스를생성하는유일한방법임. 함수프로토타입 pid_t fork(void); 새로운자식프로세스를생성함. 자식프로세스에서는 0 을반환하고, 부모프로세스에서는자식프로세스의 PID 를반환함. ( 매우중요 ) 3
9.1 절 fork() 시스템호출실행전과후 fork() 시스템호출은부모프로세스를똑같이복제하여자식프로세스를생성함. 자기복제 fork() 시스템호출이실행되면자식프로세스가즉시생성됨. 그후, 부모프로세스와자식프로세스는각각독립적으로실행을계속함. PC (Program Counter): 다음에실행될명령어의주소를가지고있는레지스터로서, 명령어포인터라고도함. 4
9.1 절 fork1.c (p.276) #include <stdio.h> #include <unistd.h> int main() { int pid; printf("[%d] 프로세스시작 \n", getpid()); pid = fork(); printf("[%d] 프로세스 : 반환값 %d\n", getpid(), pid); 부모프로세스가실행. 부모프로세스가실행. 자식프로세스가실행. 5
9.1 절 for() 시스템호출의반환값처리 fork() 시스템호출후에실행되는코드들은부모프로세스와자식프로세스에서모두실행됨. 그러면부모프로세스와자식프로세스가서로다른일을하게하려면어떻게해야할까요? 반환값을이용함. 왜냐하면 fork() 시스템호출은자식프로세스에서는 0 을반환하고, 부모프로세스에서는자식프로세스의 PID 를반환하기때문에. 서로다른일을하도록처리하는코드 pid = fork(); if( pid == 0 ) { 자식프로세스가실행할코드 ; else { 부모프로세스가실행할코드 ; 6
9.1 절 fork2.c (p.277) #include <stdio.h> #include <unistd.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()); 자식프로세스가실행. 부모프로세스가실행. 7
9.1 절 fork3.c (p.278) #include <stdio.h> #include <stdlib.h> #include <unistd.h> 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); 첫번째자식프로세스가종료됨. 두번째자식프로세스가종료됨. 8
9.1 절 wait() 시스템호출 부모프로세스는 wait() 시스템호출을실행하여자식프로세스중의하나가종료될때까지기다릴수있음. wait() 부모프로세스 fork() 자식프로세스 exit() 함수프로토타입 pid_t wait(int *status); 실행하면자식프로세스중하나가종료될때까지기다림. 자식프로세스가종료될때의종료코드가 *status 에저장됨. 종료된자식프로세스의 PID 를반환함. 9
9.1 절 forkwait.c (p.280) #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> 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); 자식프로세스가종료됨. 종료코드 : 1 자식프로세스가종료되기를기다림. 10
9.1 절 waitpid() 시스템호출 부모프로세스는 waitpid() 시스템호출을실행하여자식프로세스중하나를지정하여그것이종료될때까지기다릴수있음. 함수프로토타입 pid_t waitpid(pid_t pid, int *status, int options); PID 가 pid 인자식프로세스가종료될때까지기다림. 자식프로세스가종료될때의종료코드가 *status 에저장됨. options 는부모프로세스의대기방법을나타내며보통 0 을사용함. 종료된자식프로세스의 PID 를반환함. 11
9.1 절 waitpid.c (p.282) #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { int pid1, pid2, child, status; printf("[%d] 부모프로세스시작 \n", getpid( )); pid1 = fork(); if(pid1 == 0) { printf("[%d] 자식프로세스 [1] 시작 \n", getpid( )); sleep(1); printf("[%d] 자식프로세스 [1] 종료 \n", getpid( )); exit(1); pid2 = fork(); if(pid2 == 0) { printf("[%d] 자식프로세스 #2 시작 \n", getpid( )); sleep(2); printf("[%d] 자식프로세스 #2 종료 \n", getpid( )); exit(2); child = waitpid(pid1, &status, 0); printf("[%d] 자식프로세스 #1 %d 종료 \n", getpid( ), child); printf("\t종료코드 %d\n", status>>8); 12
9.2 절 프로그램실행 부모프로세스가 fork() 시스템호출을통해자식프로세스를생성하면, 자식프로세스는부모프로세스와동일한코드를실행함. 부모프로세스와자식프로세스에서 fork() 시스템호출의반환값이서로다르다는점을이용하여, 부모프로세스와자식프로세스가서로다른일을하도록하였음. 그러면자식프로세스에게새로운프로그램을실행시키기위해서는어떻게해야할까요? exec() 시스템호출을실행하여프로세스는새로운프로그램을실행시킬수있음. 13
9.2 절 exec() 시스템호출실행전과후 프로세스가 exec() 시스템호출을실행하면, 그프로세스내의프로세스이미지 ( 코드, 데이터, 힙, 스택등 ) 는새로운프로세스이미지로대치됨 ( 바뀜 ). 자기대치 단, PID 는바뀌지않음. PC (Program Counter): 다음에실행될명령어의주소를가지고있는레지스터로서, 명령어포인터라고도함. 14
9.2 절 exec() 시스템호출의사용예 exec() 시스템호출이성공하면기존프로세스이미지는새로운프로세스이미지로대치됨. exec() 시스템호출이성공한경우에는반환값이반환되지않고, 실패한경우에만반환값이반환됨. fork() 시스템호출을실행한후에 exec() 시스템호출을실행하는것이일반적임. if((pid=fork())==0){ exec(arguments); exit(1); // 부모프로세스는아래를계속실행 자식프로세스에게실행시킬프로그램을 arguments 로전달함. exec() 시스템호출이실패한경우에만실행됨. 15
9.2 절 exec() 시스템호출 함수프로토타입 int execl(char* path, char* arg0, char* arg1,..., NULL); int execv(char* path, char* argv[ ]); int execlp(char* file, char* arg0, char* arg1,..., NULL); int execvp(char* file, char* argv[ ]); 프로세스이미지를 path( 또는 file) 가가리키는새로운프로그램의프로세스이미지로대치한후새코드를실행함. exec( ) 시스템호출이성공하면반환값을반환하지않고, 실패하면 -1 을반환함. 실행예 execl("/bin/echo", "echo", "hello", NULL); execv(argv[1], &argv[1]); 16
9.2 절 execute1.c (p.286) #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main( ) { printf(" 부모프로세스시작 \n"); if(fork( ) == 0) { execl("/bin/echo", "echo", "hello", NULL); fprintf(stderr," 첫번째실패 "); exit(1); printf(" 부모프로세스끝 \n"); 자식프로세스의프로그램이 echo hello 로교체됨. exec() 가실패한경우에만실행됨. 17
9.2 절 execute2.c (p.287) #include <stdio.h> #include <stdlib.h> #include <unistd.h> 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"); 18
9.2 절 execute3.c (p.288) #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <unistd.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); 19
질문 Q&A 20
9.2 절 system() 함수 system() 함수는 C 라이브러리함수임. system() 함수는자식프로세스를생성하고자식프로세스로하여금명령어를실행시키는것을한번에수행해줌. system() 함수는지금까지다룬 fork(), exec(), waitpid() 시스템호출을이용하여구현되었음. 함수프로토타입 int system(const char *cmdstring); 이함수는 /bin/sh c cmdstring 를호출하여 cmdstring 에지정된명령어를실행함. 명령어가끝난후, 명령어의종료코드를반환함. 사용예 system("ls -asl"); 21
9.2 절 system() 함수의구현 (p.290) #include <sys/wait.h> #include <errno.h> #include <unistd.h> int system(const char *cmdstring) { int pid, status; if(cmdstring == NULL) return 1; pid=fork(); if(pid == -1) return -1; if(pid == 0) { execl("/bin/sh", "sh", "-c", cmdstring, NULL); _exit(127); do { if(waitpid(pid, &status, 0) == -1){ if(errno!= EINTR) return -1; else return status; while(1); 명령어가없는경우 자식프로세스의생성이실패한경우 자식프로세스가 cmdstring 을실행 부모프로세스는자식프로세스가종료될때까지기다림. 인터럽트에의한에러가아니라면종료하고, 인터럽트에의한에러라면다시 waitpid() 시스템호출을실행함. 시스템호출중에러가발생되면시스템변수 errno 에는특정값이저장됨. 22
9.2 절 syscall.c (p.292) #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> int main() { int status; if((status = system("date")) < 0) perror("system() 오류 "); printf(" 종료코드 %d\n", WEXITSTATUS(status)); if((status = system("hello")) < 0) perror("system() 오류 "); printf(" 종료코드 %d\n", WEXITSTATUS(status)); system() 함수는명령어의종료코드를반환함. WEXITSTATUS(status) 는앞에서나온 status>>8 과같음. if((status = system("who; exit 44")) < 0) perror("system() 오류 "); printf(" 종료코드 %d\n", WEXITSTATUS(status)); 23
9.3 절 입출력재지정 쉘에서의입출력재지정 $ 명령어 > 텍스트파일 $ ls -l > list.txt dup() 혹은 dup2() 시스템호출을이용하여입출력재지정을구현할수있음. 함수프로토타입 int dup(int oldfd); oldfd 에대한복제본인새로운파일디스크립터를생성하여반환함. 실패하면 1 을반환함. int dup2(int oldfd, int newfd); oldfd 을 newfd 에복제하고, 복제된파일디스크립터를반환함. 실패하면 -1 을반환함. 24
9.3 절 실행예 int dup(int oldfd); int dup2(int oldfd, int newfd); 파일디스크립터 oldfd 와복제된파일디스크립터는같은파일을공유하게된다. 예 FILE *fp; fp=fopen(argv[1], w ); dup2(fp->fd, 1); puts( hello ); 1 번파일디스크립터는표준출력 (STDOUT) 의파일디스크립터임. hello 가파일에저장됨. 25
9.3 절 redirect1.c (p.294) #include <stdio.h> #include <unistd.h> int main(int argc, char* argv[]) { FILE *fp; fp=fopen(argv[1], "w"); dup2(fileno(fp), 1); fclose(fp); printf("hello stdout!\n"); fprintf(stderr, "Hello stderr!\n"); 열린파일 fp 의파일디스크립터를반환함. 파일디스크립터를표준출력 (STDOUT) 에복제함. 26
9.3 절 redirect2.c (p.295) #include <sys/wait.h> #include <stdio.h> #include <unistd.h> int main(int argc, char* argv[]) { int pid, child, status; FILE *fp; pid=fork(); if(pid==0){ fp=fopen(argv[1], "w"); dup2(fileno(fp), 1); fclose(fp); execvp(argv[2], &argv[2]); fprintf(stderr, "%s: 실행불가 \n", argv[1]); else { child=wait(&status); printf("[%d] 자식프로세스 %d 종료 \n", getpid(), child); 27
9.4 절 프로세스그룹 프로세스그룹 (process group) 은여러프로세스들의집함을의미함. 각프로세스는하나의프로세스그룹에속하게됨. 각프로세스는 PID 뿐만아니라자신이속한프로세스그룹 ID(PGID) 도가짐. 보통부모프로세스가생성하는자식프로세스들은부모와하나의프로세스그룹을생성함. 각프로세스그룹에는그그룹을만든프로세스그룹리더가있음. 프로세스그룹리더의 PID 와프로세스그룹 ID(PGID) 는같음. 프로세스 1 프로세스 2 프로세스 3 프로세스그룹 28
9.4 절 getpgrp() 시스템호출 프로세스그룹 ID(PGID) 는 getpgrp() 시스템호출을통해알수있음. 함수프로토타입 pid_t getpgrp(void); 호출한프로세스의프로세스그룹 ID 를반환함. 29
9.4 절 pgrp1.c (p.297) #include <stdio.h> #include <unistd.h> int main() { int pid; printf("[parent] PID=%d GID=%d\n", getpid(), getpgrp()); pid=fork(); if(pid==0) { printf("child: PID=%d GID=%d\n", getpid(), getpgrp()); 30
9.4 절 프로세스그룹의활용 1 프로세스그룹을이용하면그룹내의모든프로세스들을대상으로액션을취할수있음. 주로프로세스그룹내의모든프로세스들에게어떤시그널 (signal) 을보내어그룹내의모든프로세스들을제어하거나종료시킬때사용함. 예 kill -9 pid // kill pid와같음. kill -9 0 // 현재속한프로세스그룹내의모든프로세스들에게 9번시그널 (SIGKILL) 을보냄. kill -9 -pid // -는프로세스그룹을의미. 프로세스그룹 pid에있는모든프로세스들에게 9번시그널 (SIGKILL) 을보냄. 31
9.4 절 프로세스그룹의활용 2 waitpid() 시스템호출도프로세스그룹과관련이있음. pid_t waitpid(pid_t pid, int *status, int options); 첫번째인수 pid 의값에따라다양하게사용될수있음. pid == -1 임의의자식프로세스가종료하기를기다림. (wait() 시스템호출과같음.) pid > 0 PID 가 pid 인자식프로세스가종료하기를기다림. pid == 0 호출자와같은프로세스그룹내의어떤자식프로세스가종료하기를기다림. pid < -1 pid 의절대값과같은프로세스그룹내의어떤자식프로세스가종료하기를기다림. 32
9.4 절 새로운프로세스그룹만들기 setpgid() 시스템호출을통해새로운프로세스그룹을생성하거나다른프로세스그룹에멤버로들어갈수있음. 프로세스가이호출을통해새로운프로세스를생성한경우새로운그룹리더가됨. 함수프로토타입 int setpgid(pid_t pid, pid_t pgid); PID 가 pid 인프로세스의프로세스그룹 ID 를 pgid 로설정함. 성공하면 0 을, 실패하면 -1 을반환함. 예 pid == pgid PID 가 pid 인프로세스가새로운프로세스그룹의리더가됨. pid!= pgid PID 가 pid 인프로세스가프로세스그룹 ID 가 pgid 인그룹의멤버가됨. pid == 0 호출자의 PID 를사용함. pgid == 0 PID 가 pid 인프로세스가새로운프로세스그룹의리더가됨. 33
9.4 절 사용예 int setpgid(pid_t pid, pid_t pgid); 예 pid == pgid PID 가 pid 인프로세스가새로운프로세스그룹의리더가됨. pid!= pgid PID 가 pid 인프로세스가프로세스그룹 ID 가 pgid 인그룹의멤버가됨. pid == 0 호출자의 PID 를사용함. pgid == 0 PID 가 pid 인프로세스가새로운프로세스그룹의리더가됨. setpgid(getpid(), getpid()); setpgid(0, 0); 34
9.4 절 pgrp2.c (p.301) #include <stdio.h> #include <unistd.h> int main() { int pid; printf("[parent] PID=%d GID=%d\n", getpid(), getpgrp()); pid=fork(); if(pid==0) { setpgid(0, 0); printf("child: PID=%d GID=%d\n", getpid(), getpgrp()); 35
9.5 절 시스템부팅 시스템의부팅과정에서여러개의프로세스가생성되는데이과정은어떻게이루어질까? 앞에서배운 fork() 와 exec() 시스템호출을통해시스템부팅이이루어진다. 36
9.5 절 시스템부팅관련프로세스들 swapper ( 스케줄러프로세스 ) 커널내부에서만들어진프로세스로프로세스스케줄링을함. init ( 초기화프로세스 ) /etc/inittab 파일에기술된대로시스템을초기화함. getty 프로세스 로그인프롬프트를내고키보드입력을감지함. login 프로세스 사용자의로그인아이디및패스워드를검사함. shell 프로세스 시작파일을읽고실행한후에쉘프롬프트를내고사용자로부터명령어를기다림. 37
9.5 절 프로세스리스트내용확인 $ ps -ef less ps -ef grep sshd ps -ef grep mingetty 38
질문 Q&A 39