bong-u/til

221207 SYSP

수정일 : 2023-05-22

09-process1

예외적인 제어 흐름

  • 하위 매커니즘
    1. 예외 (Exception)
  • 상위 매커니즘
    1. 프로세스 컨텍스트 전환
      • OS 소프트웨어와 하드웨어 타이머로 구현
    2. 시그널
      • OS 소프트웨어로 구현
    3. 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개의 중요한 추상화 제공

    1. 논리적인 제어 흐름 : 각 프로그램이 CPU를 독점하는 것처럼 보이게 한다
    2. 사적인 주소 공간 : 각 프로그램이 주 메모리를 독점하는 것 처럼 보이도록 한다
  • 어떻게?

    1. 프로세스의 실행이 서로 교대로 실행된다
    2. 주소공간의 가상메모리 시스템에 의해 관리

Multiprocessing (과거)

  1. 현재 레지스터들을 메모리에 보관

  2. 다음 프로세스를 실행하기 위해 스케쥴링

  3. 보관된 레지스터들을 가져오고 주소공간을 전환 (context switch)

    Multiprocessing (현대)

  4. 멀티코어 프로세서

  5. 각 코어는 별도의 프로세스를 실행 가능

동시성 프로세스

  1. 두 프로세스의 실행시간이 서로 중첩되면 -> concurrent
  2. 중첩되지 않고 순차적으로 실행된다면 -> 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로 설정

프로세스 그룹

  • 각 프로세스는 하나의 프로세스 그룹에 속한다
  • 기본적으로 자식은 부모와 같은 그룹에 속한다
  • 쉘은 각 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}