-
Exceptional Control Flow 1 - Process대학/시스템소프트웨어 2022. 10. 22. 21:03
- Control Flow
코드는 위에서 아래로 순차적으로 흐른다. 하지만, 다음의 방법으로 이 실행 프름을 바꿀 수 있다.
- Jumps (c: goto)
- Branches (c: if)
- Stack 방식의 call - return (c: 함수 호출)
보통은 CPU가 이런 흐름을 처리하지만, 0으로 나누기, 데이터 입출력, ctrl+c 등
시스템 상태가 변경되는 경우 그 반응을 처리하기는 어렵다.
따라서 이런 경우는 OS가 exceptional control flow를 담당한다.
- Exceptional Control Flow
Low-level 메커니즘으로 system state의 변화가 일어나는 경우, 시스템 이벤트의 형태로 OS에 전달하게 된다.
그럼 OS는 이 이벤트를 이용해서 코드의 실행 흐름을 변경하게 된다.
이 때 발생하는 시스템 이벤트는 Exception의 형태로 알려주게 된다.
High-level 메커니즘으로는 다음의 시스템 이벤트로 처리된다.
- Process context switch
- Signals
- Nonlocal jumps (clang runtime library)
- ...
- Process
실행중인 프로그램을 지칭하는데 각 프로세스는 마치 CPU를 독점적으로 사용하는 것 처럼 느끼게 하고, 메모리를 무한대로 독립적으로 사용하도록 느끼게 한다.
이런 마법같은 일이 가능한 이유는, 여러 프로세스들이 동시에 멀티 테스킹 환경에서 실행되고, 가상 메모리 공간에서 관리되기 때문이다.
사용자가 느끼기에는 A - B, A - C 프로세스가 동시에 실행되는 것 처럼 느끼게 되고,
B - C 프로세스가 순자적으로 실행되는 것 처럼 느껴지지만, 사실은 작은 time-slice로 쪼개어져 실행되고 있다.
이 때, 프로세스의 흐름을 변경하는 작업을 kernel에서 실행하는데, 이를 context switch 라고 한다.
fork() 시스템 콜 함수를 이용해서 부모 프로세스에서 자식 프로세스를 생성할 수 있다.
자식 프로세스를 생성할 때, fork()의 리턴값은 부모 프로세스에겐 자식 프로세스의 pid를, 자식 프로세스에겐 0을 반환한다.
중요한 점은, 자식 프로세스는 부모 프로세스의 code, 메모리를 물려 받아 새로운 메모리 공간에 저장한다.
그리고, 자식 프로세스는 fork() 다음 줄부터 코드를 실행하게 된다.
exit() 함수를 통해서 현재 프로세스를 종료할 수 있다.
만약, atexit() 함수를 통해서 인자로 함수를 넣어주게 된다면, exit 함수 호출시 인자로 넣어준 함수가 호출되고 프로세스가 종료된다.
- Process state
new: 생성이 아직 이루어지지 않았고, 프로세스가 만들어지고 있는 상태
ready: 생성 완료되었지만, CPU에 적재되지 않은 대기상태. Ready queue에서 대기 상태를 관리한다.
running: CPU에 적재되어 코드가 실행되고 있는 상태.
waiting: 특정 이벤트를 기다리는 상태
zombie: 프로세스가 종료 되었지만, OS가 아직 처리하지 못한 정보를 담은 객체가 있는 상태.
- Zombie
프로세스별로 정보를 담는 객체가 있는데, 이 객체는 수량에 제한이 있다.
프로세스 종료시 메모리등의 자원을 다 반납해도 위의 객체가 반납이 안되는 경우가 있다.
그런경우 이를 좀비 프로세스라고 한다.
좀비 프로세스는 보통 부모 프로세스가 처리하는데, 부모 프로세스가 종료될 때, 자식 좀비 프로세스도 처리하고 종료된다.
부모 프로세스가 없는 프로세스는 최상단 프로세스(init process)가 처리해준다. (좀비 상태로도 안가고 바로 처리된다)
- Synchronizing
wait() 시스템 콜 함수를 이용해서 부모 프로세스는 자식 프로세스가 종료될 때 까지 wait 부분에서 코드 실행흐름이 정지된다.
waitpid() 함수를 사용하면 특정 자식 프로세스가 종료될 때 까지 부모 프로세스의 실행 흐름이 정지된다.
pid_t pid[N]; int child_status; for (int i=0; i<N; i++) if ((pid[i] = fork()) == 0) exit(100 + i); for (int i=0; i<N; i++) { pid_t wpid = waitpid(pid[i], &child_Status, 0); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else {...} } 실행 결과 Child 4234 terminated with exit status 100 Child 4235 terminated with exit status 101 Child 4236 terminated with exit status 102 Child 4237 terminated with exit status 103 ...
여기서 눈여겨 봐야 할 점은, 각 프로세스들은 병렬적으로 동작하고, 프로세스의 실행 및 종료 시점은 무작위이다.
하지만, i번째 프로세스가 종료되는 것을 메인에서 순차적으로 기다리고 있기 때문에,
순차적으로 실행되는 것 처럼 보이게 되는 것이다.
- Running new program
execl() 함수를 이용해서 프로세스가 다른 프로그램의 실행으로 덮어씌워진다.
즉, 내부적으로 새로운 자식 프로세스를 만드는 것이 아닌, loader가 구동되며 프로세스 자체가 프로그램(명령어)를 실행하게 된다.
(= pid가 변경되지 않는다)
system()이란 함수도 있는데, 이 함수는 fork, exec, waitpid를 내부적으로 한 번에 처리하는 동작을 수행한다.
'대학 > 시스템소프트웨어' 카테고리의 다른 글
Threads (0) 2022.12.11 Inter-Process Communication (2) 2022.10.23 Timer (0) 2022.10.22 Exceptional Control Flow 2 - Signal (0) 2022.10.22 System-Level I/O (0) 2022.10.22