620 likes | 822 Views
12 장 프로세스. 12.1 프로그램 시작 및 종료. 프로그램 실행 시작. exec 시스템 호출 C 시작 루틴에 명령줄 인수와 환경 변수를 전달하고 프로그램을 실행시킨다 C 시작 루틴 (start-up routine) main 함수를 호출하면서 명령줄 인수 , 환경 변수를 전달 exit( main( argc, argv ) ); 실행이 끝나면 반환값을 받아 OS 로 exit 한다. 프로그램 실행 시작. exit( main( argc, argv ) );. 명령줄 인수 / 환경 변수.
E N D
프로그램 실행 시작 • exec 시스템 호출 • C 시작 루틴에 명령줄 인수와 환경 변수를 전달하고 • 프로그램을 실행시킨다 • C 시작 루틴(start-up routine) • main 함수를 호출하면서 명령줄 인수, 환경 변수를 전달 exit( main( argc, argv ) ); • 실행이 끝나면 반환값을 받아 OS로 exit 한다
프로그램 실행 시작 exit( main( argc, argv ) );
명령줄 인수/환경 변수 argc: 3 argv: 0 1 2 3 myecho\0 hello\0 world!\0 0
printall.c $ gcc –o printall printall.c $ printall hello world! $ env #include <stdio.h> #include <stdlib.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) • main() 실행을 마치고 리턴하면 C 시작 루틴은 이 리턴값을 가지고 exit()을 호출 • 프로그램 내에서 직접 exit()을 호출 • 프로그램 내에서 직접 _exit()을 호출 • 비정상 종료(abnormal termination) • abort() • 프로세스에 SIGABRT 시그널을 보내어 프로세스를 비정상적으로 종료 • 시그널에 의한 종료
프로그램 종료 • exit() • 모든 열려진 스트림을 닫고(fclose), 출력 버퍼의 내용을 디스크에 쓰는(fflush) 등의 뒷정리 후 프로세스를 정상적으로 종료 • 종료 코드(exit code)를 부모 프로세스에게 전달한다 • _exit()
프로세스 • 프로세스는 실행중인 프로그램이다 • 프로그램 실행을 위해서는 • 프로그램의 코드, 데이터, 스택, 힙, U-영역 등이 필요하다 • 프로세스 이미지(구조)는 메모리 내의 프로세스 레이아웃 • 프로그램 자체가 프로세스는 아니다 !
프로세스 구조 • 프로세스 구조 • 코드(code): executable objectcode • 데이터(data) : global variable & static variable • 힙(heap): dymanic memory allocation • 스택 (stack): runtime stack • U-영역(user-area): open files 교재 373 참조 (root 권한으로) $ cat /proc/1/maps
(참고) Linux 가상 메모리 구조 • 하나의 태스크마다 4GB주소 공간을 할당 • 각 태스크에게 3GB 할당 • 나머지 1GB는 모든 태스크들의 공통 영역으로서 커널 공간으로 할당 $ cat/proc/1/maps BSS(Block Started by Symbol) 운영체제
(참고) Linux 메모리 관리 자료구조 • task_struct구조체내의 struct mm_struct *mm • /usr/src/kernels/linux-2.6.35.6/include/linux/sched.h (1193행 CentOSLinux 2.6.32.71) • /usr/src/kernels/linux-2.6.35.6/include/linux/mm_types.h (222행,131행) • vm_area_struct 안의 structmm_struct * vm_mm은 메모리 디스크립터 포인터 운영체제
프로세스 ID • 각 프로세스는 프로세스를 구별하는 번호인 프로세스 ID를 갖고 있다
프로세스 생성 • fork() 시스템 호출 • 부모 프로세스를 똑같이 복제하여 새로운 자식 프로세스를 생성 • 자기복제(自己複製) • fork()는 한 번 호출되면 두 번 리턴한다 • 자식 프로세스에게는 0을 리턴하고 • 부모 프로세스에게는 자식 프로세스 ID를 리턴한다 • 부모 프로세스와 자식 프로세스는 병행적으로 각각 실행을 계속한다
PC PC PC (예) fork 시스템 호출: onetwo.c #include <stdio.h> main() { int pid; printf("One\n"); pid = fork(); printf("Two\n"); } $ gcc –o onetwo onetwo.c BEFORE fork AFTER 부모 프로세스 자식 프로세스
fork1.c #include <stdio.h> /* 자식 프로세스를 생성한다 */ int main() { int pid; printf("[%d] 프로세스 시작 \n", getpid()); pid = fork(); printf("[%d] 프로세스 : fork() 리턴값 pid=%d\n", getpid(),pid); }
부모 프로세스와 자식 프로세스 구분 • fork() 호출 후에 리턴값이 다르므로 이 리턴값을 이용하여 • 부모 프로세스와 자식 프로세스를 구별하고 • 서로 다른 일을 하도록 할 수 있다 pid = fork(); if ( pid == 0 ) { 자식 프로세스의 실행 코드 } else { 부모 프로세스의 실행 코드 }
fork2.c: 자식 프로세스 생성 #include <stdlib.h> #include <stdio.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()); } }
fork3.c: 두 개의 자식 프로세스 생성 #include <stdlib.h> #include <stdio.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); /* ② */ } } • ①만 없애보세요! • ②만 없애보세요! • ①&② 모두 없애보세요!
프로세스 기다리기: wait() • 자식 프로세스 중의 하나가 끝날 때까지 기다린다 • 끝난 자식 프로세스의 종료 코드는 status에 저장 • 끝난 자식 프로세스의 번호를 리턴 • 하나 이상의 자식이 이미 좀비라면 좀비 중 하나의 상태를 반환 • (예) zombie.c
zombie.c #include <stdio.h> #include <stdlib.h> main () { int pid; pid = fork (); /* Duplicate */ if (pid != 0) /* Branch based on return value from fork () */ { while (1) /* Never terminate, and never execute a wait () */ sleep (1000); } else { exit (42); /* Exit with a silly number */ } } $ gcc –o zombie zombie.c $ zombie & $ ps 재부팅후 $ ps $ ps ux 또는 $ ps –ef | grep 자기id $ killall zombie
wait(&status) • wait(&status) /* int status */ • status :자식이 exit으로 종료될 때의 상태정보 • 정상종료 경우 : 하위 8 비트는 0, 상위 8 비트는 exit status(자식 프로세스가 exit 명령으로 전달한 값) • signal로 종료된 경우 : 하위 8 비트는 signal 번호, 상위 8 비트는 0 • (하위 8 비트 추출) status & 0x00FF; • (상위 8 비트 추출) status >> 8 & >> 8
forkwait.c: 자식 프로세스 기다리기 #include <stdio.h> #include <stdlib.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); } • 자식 프로세스 종료 코드 하위8비트/상위8비트 구분 출력하기 • 상위8비트/하위8비트로 출력 순서 바꾸면?
프로그램 실행 • fork() 후 • 자식 프로세스는 부모 프로세스와 똑같은 코드 실행 • 자식 프로세스에게 새로운 프로그램을 시키려면 어떻게 하여야 할까? • 프로세스 내의 프로그램을 새 프로그램으로 대치 • exec() 시스템 호출 사용 • 보통 fork() 후에 exec( )
프로그램 실행: exec() • 프로세스가 exec() 호출을 하면, • 그 프로세스 내의 프로그램은 완전히 새로운 프로그램으로 대치 • 자기대치(自己代置) • 새 프로그램의 main()부터 실행이 시작한다
프로그램 실행: exec() • exec() 호출이 성공하면 리턴할 곳이 없어진다 • 성공한 exec() 호출은 절대 리턴하지 않는다
fork/exec • 보통 fork() 호출 후에 exec() 호출 • 새로 실행할 프로그램에 대한 정보를 arguments로 전달한다 • . • exec() 호출이 성공하면 • 자식 프로세스는 새로운 프로그램을 실행하게 되고 • 부모는 계속해서 다음 코드를 실행하게 된다 if ((pid = fork()) == 0 ){ exec( arguments ); exit(1); } // 부모 계속 실행
PC PC PC PC PC fork와 exec호출의 조합 BEFORE FORK A AFTER FORK A B AFTER FORK AFTER EXEC A B (now runs ls)
execute1.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> /* 자식 프로세스를 생성하여 echo 명령어를 실행한다 */ int main( ) { printf("부모 프로세스 시작\n"); if (fork( ) == 0) { fprintf(stdout, "자식 프로세스 시작\n"); execl("/bin/echo", "echo", "hello", "world", NULL); fprintf(stderr, "자식 프로세스 실패\n"); exit(1); } printf("부모 프로세스 끝\n"); } "ls -l"실행하도록 수정하세요!
(응용) execute11.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> /* 자식 프로세스를 생성하여 echo 명령어를 실행한다 */ int main( ) { char*av[3]; av[0]=“ls"; av[1]=“-l"; av[2]=NULL; printf("부모 프로세스 시작\n"); if (fork( ) == 0) { fprintf(stdout, "자식 프로세스 시작\n"); execv("/bin/ls", av); fprintf(stderr, "자식 프로세스 실패\n"); exit(1); } printf("부모 프로세스 끝\n"); } 0xbffff5c4 0x8048644 av ls\0 0x8048644 0 1 2 0x8048647 -l\0 0x0 "ls -l -i"실행하도록 수정하세요!
execute2.c #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"); }
execute3.c: 자식 프로세스 명령어 실행 $ vi myexit.c #include <stdio.h> #include <stdlib.h> main () { printf ("I'm going to exit with return code\n"); exit (33); } #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("[parent: %d] 자식 프로세스 %d 종료 \n", getpid(), pid); printf("[child : %d] 종료 코드 하위: %d 상위: %d \n", child, status & 0x00FF, status >> 8); } } $ gcc –g –o execute3 execute3.c $ execute3 ls –l $ execute3 myexit Q: Why &argv[1] ?
Why &argv[1] ? Q: execvp( argv[1], &argv[1] ); =execvp( ??, ?? ); argc 2 0xbffff684 0xbffff7c5 $ gcc –g –o execute3 execute3.c $ gdb execute3 (gdb) b 6 Breakpoint 1 at 0x80484dd: file execute3.c, line 6. (gdb) b 13 Breakpoint 2 at 0x8048559: file execute3.c, line 13. (gdb) r myexit Starting program: /home/mysung/ulprog/cprog/execute3 myexit Breakpoint 1, main (argc=2, argv=0xbffff684) at execute3.c:6 6 pid = fork( ); (gdb) p argc $1 = 2 (gdb) p argv $2 = (char **) 0xbffff684 (gdb) p &argc $3 = (int *) 0xbffff5e0 (gdb) p &argv $4 = (char ***) 0xbffff5e4 (gdb) p argv[0] $5 = 0xbffff7c5 "/home/mysung/ulprog/cprog/execute3" (gdb) p argv[1] $6 = 0xbffff7e8 "myexit" (gdb) p argv[2] $7 = 0x0 (gdb) p &argv[0] $8 = (char **) 0xbffff684 (gdb) p &argv[1] $9 = (char **) 0xbffff688 (gdb) p &argv[2] $10 = (char **) 0xbffff68c argv 0xbffff684 0 1 2 execute3\0 0xbffff7c5 0xbffff7e8 myexit\0 0x0 (gdb) c Continuing. Detaching after fork from child process 11288. I'm going to exit with return code [parent: 11246] 자식 프로세스 11288 종료 Breakpoint 2, main (argc=2, argv=0xbffff684) at execute3.c:13 13 printf("[child : %d] 종료 코드 하위: %d 상위: %d \n", child, status & 0x00FF, status >> 8 ); (gdb) p status & 0xFF $11 = 0 (gdb) p status >> 8 $12 = 77 (gdb) c Continuing. [child : 11288] 종료 코드 하위: 0 상위: 77 Program exited with code 054. (gdb) quit
(응용) execute33.c: 자식 프로세스 kill #include <stdio.h> #include <stdlib.h> #include <signal.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 { // 부모 프로세스 printf("SIGINT=%d, SIGKILL=%d, SIGTERM=%d\n", SIGINT, SIGKILL, SIGTERM); kill(pid, SIGINT); child = wait(&status); printf("[parent: %d] 자식 프로세스 %d 종료 \n", getpid(), pid); printf("[child : %d] 종료 코드 하위: %d 상위: %d \n", child, status & 0x00FF, status>>8); } } $ gcc -g –o execute33 execute33.c $ execute33 sleep 100 GDB 실행하여확인해 보세요!
$ gdb execute33 0xbffff684 0xbffff7c0 argc 3 argv 0 1 2 3 0xbffff684 execute33\0 0xbffff7c0 $ gcc -g -o execute33 execute33.c $ gdb execute33 (gdb) b 8 Breakpoint 1 at 0x804850d: file execute33.c, line 8. (gdb) b 14 Breakpoint 2 at 0x8048583: file execute33.c, line 14. (gdb) r sleep 100 Starting program: /home/mysung/ulprog/cprog/execute33 sleep 100 Breakpoint 1, main (argc=3, argv=0xbffff684) at execute33.c:8 8 pid = fork( ); (gdb) p &argc $1 = (int *) 0xbffff5e0 (gdb) p &argv $2 = (char ***) 0xbffff5e4 (gdb) p argv[0] $3 = 0xbffff7c0 "/home/mysung/ulprog/cprog/execute33" (gdb) p argv[1] $4 = 0xbffff7e4 "sleep" (gdb) p argv[2] $5 = 0xbffff7ea "100" (gdb) p argv[3] $6 = 0x0 (gdb) p pid $7 = 8065012 (gdb) n Detaching after fork from child process 11893. 9 if (pid == 0) { // 자식 프로세스 (gdb) p pid $8 = 11893 (gdb) n 13 printf("SIGINT=%d, SIGKILL=%d, SIGTERM=%d\n", SIGINT, SIGKILL, SIGTERM); (gdb) c Continuing. SIGINT=2, SIGKILL=9, SIGTERM=15 Breakpoint 2, main (argc=3, argv=0xbffff684) at execute33.c:14 14 kill(pid, SIGKILL); 0xbffff7e4 sleep\0 0xbffff7ea 100\0 0x0 (gdb) p pid $7 = 11893 (gdb) p SIGKILL No symbol "SIGKILL" in current context. (gdb) n 15 child = wait(&status); (gdb) p child $8 = 134514203 (gdb) p status $9 = 134513744 (gdb) n 16 printf("[parent: %d] 자식 프로세스 %d 종료 \n", getpid(), pid); (gdb) p getpid() $10 = 11888 (gdb) p pid $11 = 11893 (gdb) n [parent: 11888] 자식 프로세스 11893 종료 17 printf("[child : %d] 종료 코드 상위: %d 하위: %d \n", child, status>>8, status & 0x00FF); (gdb) p child $12 = 11893 (gdb) p status $13 = 9 (gdb) p status >> 8 $14 = 0 (gdb) p status & 0x00ff $15 = 9 (gdb) quit
(시험예시) run.c ① argc: #include <stdio.h> #include <stdlib.h> #include <signal.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 { // 부모 프로세스 /* kill(pid, SIGKILL); */ child = wait(&status); printf("[parent: %d] 자식 프로세스 %d 종료 \n", getpid(), pid); printf("[child : %d] 종료 코드 하위: %d 상위: %d \n", child, status & 0x00FF, status>>8); } } ③ ② run\0 argv: ④ sleep\0 ⑤ 100\0 0 $ gcc –g –o run.c run $ run
(시험 예시) $ gdb run 0xbffff674 0xbffff7b7 argc 3 $ gcc -g -o run run.c $ gdb run (gdb) b 8 Breakpoint 1 at 0x804850d: file run.c, line 8. (gdb) r sleep 100 Starting program: /home/mysung/59bu/mytest/x2013ul/run sleep 100 Breakpoint 1, main (argc=3, argv=0xbffff674) at run.c:8 8 pid = fork( ); (gdb) p &argc $1 = (int *) 0xbffff5d0 (gdb) p &argv $2 = (char ***) 0xbffff5d4 (gdb) p argv[0] $3 = 0xbffff7b7 "/home/mysung/59bu/mytest/x2013ul/run" (gdb) p argv[1] $4 = 0xbffff7dc "sleep" (gdb) p argv[2] $5 = 0xbffff7e2 "100" (gdb) p argv[3] $6 = 0x0 (gdb) p &argv[0] $7 = (char **) 0xbffff674 (gdb) p &argv[1] $8 = (char **) 0xbffff678 (gdb) p &argv[2] $9 = (char **) 0xbffff67c (gdb) p &argv[3] $10 = (char **) 0xbffff680 (gdb) n Detaching after fork from child process 28803. 9 if (pid == 0) { // 자식 프로세스 argv (gdb) p pid $11 = 28803 (gdb) n 13 kill(pid, SIGKILL); (gdb) p pid $12 = 28803 (gdb) p SIGKILL No symbol "SIGKILL" in current context. (gdb) n 14 child = wait(&status); (gdb) n 15 printf("[parent: %d] 자식 프로세스 %d 종료 \n", getpid(), pid); (gdb) p getpid() $13 = 28660 (gdb) p pid $14 = 28803 (gdb) n [parent: 28660] 자식 프로세스 28803 종료 16 printf("[child : %d] 종료 코드 상위: %d 하위: %d \n", child, status>>8, status & 0x00FF); (gdb) p child $15 = 28803 (gdb) p status>>8 $16 = 0 (gdb) p status & 0x00FF $17 = 9 (gdb) p status $18 = 9 (gdb) quit 0 1 2 3 0xbffff7b7 0xbffff674 run\0 0xbffff7dc sleep\0 0xbffff7e2 100\0 0x0
fd1 파일 fd2 fd3 입출력 재지정 • 명령어의 표준 출력이 파일에 저장 $ 명령어 > 파일 • 출력 재지정 기능 구현 dup() 혹은 dup2() 시스템 호출 • 파일 디스크립터 fd를 표준출력(1)에 dup2() fd = open(argv[1], O_CREAT|O_TRUNC|O_WRONLY, 0600); dup2(fd, 1);
(참고) inode (indexnode) 구조 • Linux CentOS 2.6.32-71.el6.i686 #1 SMP • /usr/include/linux/ext2_fs.h • struct ext2_inode /* ext2_fs.h 208행 */ • /* ext2_fs.h 77행 reserved inode bumbers */ #define EXT2_BAD_INO 1 /* Bad blocks inode */ #define EXT2_ROOT_INO 2 /* Root inode */ #define EXT2_BOOT_LOADER_INO 5 /* Boot loader inode */ #define EXT2_UNDEL_DIR_INO 6 /* Undelete directory inode */ /* First non-reserved inode for old ext2 filesystems */ #define EXT2_GOOD_OLD_FIRST_INO 11 • /usr/src/kernels/2.6.43.8-1.fc15.i686/include/linux/fs.h • struct file /* fs.h 971행 */ • struct address_space /* fs.h 641행 */ • struct inode /* fs.h 756행 */ • Solaris 10 • /usr/include/sys/file.h 및 vnode.h • typedef struct file /* file.h 43행 */ • typedefstruct vnode /* vnode.h 243행
redirect1.c: 표준출력 재지정 #include <stdio.h> #include <fcntl.h> /* 표준 출력을 파일에 재지정하는 프로그램 */ int main(int argc, char* argv[]) { int fd, status; fd = open(argv[1], O_CREAT | O_TRUNC | O_WRONLY, 0600); dup2(fd, 1); /* 파일을 표준출력에 복제 */ close(fd); printf("Hello stdout !\n"); fprintf(stderr,"Hello stderr !\n"); } $ gcc –o redirect1 redirect1.c $ redirect1 xx
redirect2.c: 자식 프로세스 표준출력 재지정 #include <stdio.h> #include <fcntl.h> /* 자식 프로세스의 표준 출력을 파일에 재지정한다 */ int main(int argc, char* argv[]) { int child, pid, fd, status; pid = fork( ); if (pid == 0) { fd = open(argv[1],O_CREAT | O_TRUNC| O_WRONLY, 0600); dup2(fd, 1); // 파일을 표준출력에 복제 close(fd); execvp(argv[2], &argv[2]); fprintf(stderr, "%s:실행 불가\n",argv[1]); } else { child = wait(&status); printf("[%d] 자식 프로세스 %d 종료 \n", getpid(), child); } } $ gcc –o redirect2 redirect2.c $ redirect2 xx ls –l $ ls –l > xx (redirect out) 원리
시스템 부팅 • 시스템 부팅은 fork/exec 시스템 호출을 통해 이루어진다 $ psalx | more $ ps–ef | more (root 권한으로) $ cat /proc/1/maps 같은 pid!
시스템 부팅 • swapper(스케줄러 프로세스) • 커널 내부에서 만들어진 프로세스로 프로세스 스케줄링을 한다 • init(초기화 프로세스) • /etc/inittab 파일에 기술된 대로 시스템을 초기화 • getty 프로세스 • 로그인 프롬프트를 내고 키보드 입력을 감지한다 • login 프로세스 • 사용자의 로그인 아이디 및 패스워드를 검사 • shell 프로세스 • 시작 파일을 실행한 후에 쉘 프롬프트를 내고 사용자로부터 명령어를 기다린다