09-process1
예외적인 제어 흐름
- 하위 매커니즘
- 예외 (Exception)
- 상위 매커니즘
- 프로세스 컨텍스트 전환
- OS 소프트웨어와 하드웨어 타이머로 구현
- 시그널
- OS 소프트웨어로 구현
- nolocal 점프
- 프로세스 컨텍스트 전환
예외 테이블 (Exception Tables)
- 각 이벤트 타입은 예외번호 k를 갖는다
비동기형 예외 (Interrupt)
-
입출력 인터럽트 (ctrl + c)
-
하드 리셋 인터러트
-
소프트 리셋 인터럽트
동기형 예외
-
Traps : 명령어의 결과로 발생하는 의도적인 예외 (syscall)
-
Faults : 핸들러가 정정할 수 있는 에러의 결과로 발생 (page faults)
-
Aborts : 복구 불가능한 에러의 결과로 발생
Page Fault
-
사용자 메모리의 특정 페이지가 현재 하드디스크에 위치하는 경우
-
오류 처리후에 오류를 발생시킨 명령어를 다시 실행한다
1int a [1000]; 2int main () { 3a[500] = 13; 4}
Process
프로세스 : 운영체제가 만들어 주는 프로그램의 한 실행 예
-
프로그램에 2개의 중요한 추상화 제공
- 논리적인 제어 흐름 : 각 프로그램이 CPU를 독점하는 것처럼 보이게 한다
- 사적인 주소 공간 : 각 프로그램이 주 메모리를 독점하는 것 처럼 보이도록 한다
-
어떻게?
- 프로세스의 실행이 서로 교대로 실행된다
- 주소공간의 가상메모리 시스템에 의해 관리
Multiprocessing (과거)
-
현재 레지스터들을 메모리에 보관
-
다음 프로세스를 실행하기 위해 스케쥴링
-
보관된 레지스터들을 가져오고 주소공간을 전환 (context switch)
Multiprocessing (현대)
-
멀티코어 프로세서
-
각 코어는 별도의 프로세스를 실행 가능
동시성 프로세스
- 두 프로세스의 실행시간이 서로 중첩되면 -> concurrent
- 중첩되지 않고 순차적으로 실행된다면 -> sequential
fork
1int fork(void)
- 호출하는 프로세스와 동일한 새 프로세스 생성
- 자식 프로세스는 0을 리턴
- 부모 프로세스는 자식 프로세스의 pid 리턴
exit
1void exit(int status)
- 종료 상태 status 값을 가지고 종료 (정상이면 0)
- atexit() 함수는 exit 할 때 실행할 함수를 등록
좀비 (Zombies)
-
종료되었지만, 아직 정리되지 않은 프로세스
1void fork8() { 2if (fork() == 0) { 3 printf("Running child, PID = %d\n", getpid()); 4 while (1) 5 ; 6} 7else { 8 printf("Terminating Parent, PID = %d\n", getpid()); 9 exit(0); 10} 11}
10-process2
wait
1int wait (int *child_status)
-
현재 프로세스를 자신의 자식 프로세스들 중에 하나가 종료될 때까지 정지시킨다
-
리턴값은 종료한 자식 프로세스의 PID
-
child_status != NULL인 경우, 자식 프로그램의 종료 이유를 나타내는 상태정보를 갖는다
-
example
1pid_t wpid = wait(&child_status); 2if (WIFEXITED(child_status)) 3 printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); 4else 5 printf("Child %d terminate abnormally\n", wpid);
waitpid
1waitpid(pid, &status, options)
-
pid : 특정 pid의 프로세스를 기다린다. -1이면 wait()과 동일
-
options : 0 (종료된 자식을 기다린다), WNOHANG(==1 한번만 체크), WUNTRACED(==2, 정지되거나 종료된 자식을 기다린다)
sleep
1// 자기 자신을 secs초 동안 정지 2unsigned int sleep(unsigned int secs) 3// 호출하는 프로세스를 시그널 받을 때 까지 잠재운다 4int pause(void)
execve
1int execve(char *filename, char *argv[], char *envp[])
-
실행 파일 filename을 현재 프로세스의 환경변수를 이용하면서 argv로 현재의 code, data, stack을 덮어 씌움
-
example
1if ((pid = fork()) == 0) { 2 if (execve(myargv[0], myargv, environ) < 0) { 3 printf("%s: Command not found.\n", myargv[0]); 4 exit(1); 5 } 6}
shell
사용자의 명령을 처리해주는 응용 프로그램
- Utility : file로 구현된 명령어
- Built-in : 코드로 구현된 명령어
eval
1void eval(char *cmdline) {
2 char *argv[MAXARGS];
3 int bg;
4 pid_t pid;
5
6 bg = parseline(cmdline, argv);
7 if (!builtin_command(argv)) {
8 if ((pid = fork()) == 0) {
9 if (execve(argv[0], argv, environ) < 0) {
10 printf("%s: Command not found.\n", argv[0]);
11 exit(0);
12 }
13 }
14
15 if (!bg) {
16 int status;
17 if (waitpid(pid, &status, 0) < 0 )
18 unix_error("waitfg: waitpid error");
19 }
20 else
21 printf("%d %s", pid, cmdline);
22 }
23}
-> 백그라운드 작업이 종료되면 zombie가 된다
- 해결방법 : signal
11-signal
Signal
어떤 이벤트가 시스템에 발생했다는 것을 프로세스에게 알려주는 짧은 메시지
Receiving a signal
목적지 프로세스가 시그널을 받을 때, 어떤 형태로든 반응을 하도록 커널에 의해 요구될때, 시그널을 받는다고 한다.
- 3가지 반응
- 무시
- 대상 프로세스를 종료
- signal handler라고 부르는 유저레벨 함수를 실행하여 시그널을 잡는다
시그널의 특징
- 프로세스는 특정 시그널의 수신을 블록할 수 있다.
- 대기하는 시그널은 최대 한번만 수신할 수 있다.
- 커널이 context에 가지고 있는 비트벡터
- pending : 대기 시그널들을 표시
- 도착할때마다 pending값의 k번째 비트를 1로 설정
- 수신할때마다 pending값의 k번째 비트를 0으로 설정
- blocked : 블록된 시그널들을 표시
- sigprocmask 함수를 사용하여 응용프로그램이 1또는 0로 설정
- pending : 대기 시그널들을 표시
프로세스 그룹
- 각 프로세스는 하나의 프로세스 그룹에 속한다
- 기본적으로 자식은 부모와 같은 그룹에 속한다
- 쉘은 각 job마다 별도의 프로세스 그룹을 만든다
- getpgrp() : 프로세스의 프로세스 그룹을 리턴
- setpgid() : 프로세스의 그룹을 변경
kill
- kill -9 24818 : SIGKILL을 pid 24818로 보냄
- kill -9 -24817 : pgid 24871의 각 프로세스에 SIGKILL을 보냄
키보드로부터 시그널 보내기
- 키보드로 ctrl+c (ctrl+z)를 누르면 SIGINT(SIGTSTP) 시그널이 포그라운드 프로세스 그룹의 모든 작업으로 전송된다
- SIGINT : 각 프로세스를 모두 종료시킨다.
- SIGTSTP : 기본 동작은 각 프로세스를 정지시킨다.
시그널 받기
- 커널은 pnb = pending & ~blocked 를 계산
- 각 시그널 타입은 사전에 정의된 기본 동작을 가진다.
- 기본 동작은 signal함수를 이용하여 변경이 가능한다 (SIGSTOP과 SIGKILL은 예외)
시그널 핸들러의 설치
1handler_t *signal(int signum, handler_t *handler)
- handler의 값
- SIG_IGN : signum 타입 시그널 무시
- SIG_DFL : signum의 기본동작으로 복귀
- 이외의 경우 : signal handler의 주소
시그널 블록하기와 해제하기
-
sigprocmask 이용
1int sigprocmask(int how, const sigset_t *set, sigset_t *oldest);
-
how값에 따라 동작이 결정된다
- SIG_BLOCK : blocked = (blocked | set)
- SIG_UNBLOCK : blocked = blocked & ~set
- SIG_SETMASK : blocked = set
-
set 관련 지원 함수
- sigemptyset : 모든 시그널이 비어있는 집합 생성
- sigfillset : 모든 시그널 번호를 1로 설정
- sigaddset : 특정 시그널 번호를 1로 설정
- sigdelset : 특정 시그널 번호를 0으로 설정
경주 Race 현상으로 인한 동기화의 문제
1void handler(int sig) {
2 int olderrno = errno;
3 sigset_t mask_all, prev_all;
4 pid_t pid;
5
6 sigfillset(&mask_all);
7 while ((pid = waitpid(-1, NULL, 0)) > 0 ) {
8 sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
9 deletejob(pid);
10 sigprocmask(SIG_SETMASK, &prev_all, NULL);
11 }
12 if (errno != ECHILD)
13 sio_error("waitpid error");
14 errno = olderno;
15}
16
17int main(int argc, char **argv) {
18 int pid;
19 sigset_t mask_all, prev_all;
20 int n = N; // N = 5
21 sigfillset(&mask_all);
22 signal(SIGCHLD, handler);
23 initjobs();
24
25 while (n--) {
26 if ((pid = fork()) == 0) {
27 execve("/bin/date", argv, NULL);
28 }
29 // !! 여기서 SIGCHLD가 발생하면 오류
30
31 // 핸들러에서 job을 access하지 못하도록 모든 signal block
32 sigprocmask(SIG_BLOCK, &mask_all, &prev_all)류
33 addjob(pid);
34 sigprocmask(SIG_SETMASK, &prev_all, NULL);
35 }
36 exit(0);
37}
- 부모가 먼저 SIGCHLD 시그널이 터지기 전에 SIG_BLOCK하고 addjob을 하는 경우 -> OK
- 부모가 SIG_BLOCK하기 전에 자식이 끝나는 경우 handler가 먼저 실행된다 addjob하기전에 deletejob 수행
- 무한 루프 발생
경주현상을 회피하는 동기화 방법
1int main(int argc, char **argv) {
2 int pid;
3 sigset_t mask_all, mask_one, prev_one;
4 int n = N; // N = 5
5 sigfillset(&mask_all);
6 sigemptyset(&mask_one);
7 sigaddset(&mask_one, SIGCHLD);
8 signal(SIGCHLD, handler);
9 initjobs();
10
11 while (n--) {
12 sigprocmask(SIG_BLOCK, &mask_one, &prev_one); // block SIGCHILD
13 if ((pid = fork()) == 0) {
14 // 자식은 SIGCHLD를 block할 필요가 없으므로 다시 unblock
15 sigprocmask(SIG_SETMASK, &prev_one, NULL); // unblock SIGCHLD
16 execve("/bin/date", argv, NULL);
17 }
18 sigprocmask(SIG_BLOCK, &mask_all, NULL);
19 addjob(pid);
20 sigprocmask(SIG_SETMASK, &prev_one, NULL);
21 }
22 exit(0);
23}
- 부모입장에서 fork가 실행되고 나서 SIGCHLD가 unblock 되는 순간이 단 한번도 존재 하지 않음.
- 항상 addjob이 먼저 실행됨 -> 오류 X
명시적으로 핸들러를 기다리는 방식
1volatile sig_atomic_t pid; // pid는 전역변수로 선언
2void sigchld_handler(int s) {
3 int orderrno = errno;
4 // sigchld_handler에서 waitpid 하는 방식
5 pid = waitpid(-1, NULL, 0);
6 errno = olderrno;
7}
8void sigint_handler(int s) {}
9
10int main(int argc, char **argv) {
11 sigset_t mask, prev;
12 int n = N; // N = 10
13 signal(SIGCHLD, sigchld_handler);
14 signal(SIGINT, sigint_handler);
15 sigemptyset(&mask);
16 sigaddset(&mask, SIGCHLD);
17
18 while (n--) {
19 sigprocmask(SIG_BLOCK, &mask, &prev); // block SIGCHILD
20 if (fork() == 0)
21 exit(0);
22
23 pid = 0;
24 sigprocmask(SIG_SETMASK, &prev, NULL);
25
26 // Wait for SIGCHLD to be recieved
27 while (!pid)
28 ;
29
30 // Do some work after receiving SIGCHLD
31 printf(".");
32
33 printf("\n");
34 exit(0);
35}
-
sigsuspend를 사용한 시그널 동기화
1int sigsuspend(const sigset_t *mask); 2// 아래의 코드를 구현한 것과 동일 3sigprocmask(SIG_SETMASK, &mask, &prev); 4pause(); 5sigprocmask(SIG_SETMASK, &prev, NULL);
-
sigsuspend를 이용한 시그널 기다리기
1int main(int argc, char **argv) { 2sigset_t mask, prev; 3int n = N; // N = 10 4signal(SIGCHLD, sigchld_handler); 5signal(SIGINT, sigint_handler); 6sigemptyset(&mask); 7sigaddset(&mask, SIGCHLD); 8 9while (n--) { 10 sigprocmask(SIG_BLOCK, &mask, &prev); // block SIGCHILD 11 if (fork() == 0) 12 exit(0); 13 14 pid = 0; 15 // Wait for SIGCHLD to be recieved 16 while (!pid) 17 sigsuspend(&prev); 18 19 // Optionally unblock SIGCHILD 20 sigprocmask(SIG_SETMASK, &prev, NULL); 21 22 // Do some work after receiving SIGCHLD 23 printf("."); 24} 25printf("\n"); 26exit(0); 27}