Operating System 2 - Process Control & Suspend
각 프로세스는 요청 받은 작업을 처리하기 위해 resource가 필요하다. 이는 CPU, memory, I/O, file 등등이 해당된다.
실행되기 위해 메인 메모리로 올라간 process의 메모리 구조는 stack, heap, data, text section으로 구분된다. (stack이 high memory address, text가 low memory address)
- text section : program source code (for/while 등)
- data section : global variable
- stack section : parameter, return address, local variable
- heap section : dynamically allocated memory space
일반적으로 stack은 grow down, heap은 grow up이라 둘이 충돌할 수 도 있다. 따라서 이들이 충돌하지 않도록 OS가 컨트롤 해주어야 한다.
Process State (interleaving)
- Trace : 각 프로세스의 실행될 인스트럭션의 나열. 이를 통해 프로세스의 흐름을 추적할 수 있다.
- Dispatcher : interleaving을 하며 프로세스를 교환할 때 프로세스가 각 프로세스를 교환할 수 있도록 해주는 프로그램.
fig3.4의 경우, 프로세서는 한 프로세스의 6개의 instruction이 지나가면 timer에 의해 time interrupt가 발생한다. dispatcher의 작업이 파란색으로 표현되어 있으며 dispatch의 instruction도 6개이다.
Two - State Process Model : 실행/실행 중지 의 두 가지 state를 통해 프로세스를 표현.
Enter - Not Running -(Dispatch)- Running -Exit / Pause 의 순서를 따른다.
이 때 비활성 상태에서 실행으로 dispatcher에 의해 실행되는 것이 dispatch가 되며, 다시 interrupt에 의해 실행 중지 상태로 넘어가는 것이 Pause이다.
새로운 프로세스는 생성되면 Queue에 입력된다. 이후 dispatch되어 processor에서 실행된다. 만약 종료되면 exit으로 나가며 interrupt에 의해 추가적인 실행이 필요한 경우 다시 queue로 들어간다.
그러나 이런 두 가지 상태만 가지는 모델을 사용할 경우, I/O 작업 때문에 Interrupt 되는 프로세스의 경우, queue를 다 기다렸음에도 I/O가 다 실행되지 않아 대기 상태인 경우일 수 있다. 이렇게 되면 Dispatcher에 의해 프로세스 교환 할 때 오버헤드가 발생되어 비효율적이라는 것을 알 수 있다.
Five- State Process Model : 위의 문제점을 해결하기 위해 I/O interrupt
- New : 생성되고, 메모리로는 아직 안들어간 상태
- Ready : 이제 메모리에 올라가 실행을 기다리는 상태, (time interrupt / I/O interrupt)
- Running : 프로세스가 실행되고 있는 상태
- Exit(Terminate) : 프로세스의 실행이 끝난 상태
- Blocked(Waiting) : I/O 등 오랜 시간이 걸리는 interrupt에 걸릴 경우 기다리는 상태.
time interrupt에 의해 끊겼을 경우 추가적인 대기가 필요 없을 경우 running에서 바로 다시 ready로 간다. (timeout)
그러나 I/O 등 대기가 더 필요한 이벤트가 발생하면 running에서 Blocked 상태로 보내진다. 그리고 Blocked에서 대기가 완료되면 다시 ready로 올라간다.
이를 통해 process는 항상 실행이 필요한 프로세스들만 실행할 수 있다.
이런 것들이 프로세스 스케쥴링이 되며, 이를 잘 실행하려면 각 프로세스의 상태를 잘 파악해야 한다.
Process Control Block (PCB/TCB) : 각 프로세스의 실행 상태를 저장하여 dispatch 된 여러 프로세스들을 시간적으로 불연속적으로 실행함에도 상태적으로 연속적으로 연산될 수 있게 하는 것. 제어를 하는데 필요한 정보를 모두 저장.
*Linux의 경우 프로세스를 Task라 부르므로 Task Control Block이라 한다.
- Process Identifier(PID) : 각 프로세스의 고유한 식별 ID
- Processor State information : 프로세서의 연산 상태를 저장
- Process Control Information : 프로세스의 컨트롤 상태를 저장
Processor State information
- Program counter(PC) : 다음에 실행되어야 할 instruction 주소
- CPU Register : 연산한 레지스터의 상태
Process Control information
- Process state : 프로세스의 실행 정보 (running/waiting ...)
- Priorities : 우선순위 정보
- memory : 각 프로세스에 할당된 메모리 정보
- Accounting Information : resource 점유율 정보
- I/O status information : 할당된 I/O 기기 정보, 열려진 파일 정보
이런 정보를 가지는 PCB가 앞서 봤던 각 Process의 메모리 저장 공간의 위쪽에 붙어 각 프로세스를 가리키게 된다. 즉, 관리를 위해 PCB가 사용되고, 각 프로세스를 실행할 때는 아래의 data, stack, heap 등을 사용한다. (일반적으로 PCB가 훨씬 작은 portion을 차지함)
Process Scheduling : CPU가 다음에 실행할 연산을 결정하는 작업
- Ready & Wait Queues : PCB의 process state를 판별하여 Linked List 형태로 관리.
이 때, Blocked Queue는 각 event가 끝날 때 마다 모든 프로세스에다가 물어봐야 한다. 이런 작업을 방지하기 위해 각 event (I/O device 등등)에 대한 Queue를 모두 만들어 해당 event의 업데이터가 발생할 경우 기다리고 있는 프로세스들에게 한번에 알리고 다음 상태로 움직이도록 할 수 있다.
결국 이 모든 작업은 CPU의 사용률을 올리기 위한 것들이다. CPU가 쉬지 않고 연산을 지속할 수 있도록 보조하는 것이다.
Suspended Process
- Swapping : 위의 경우에서도 만약 모든 프로세스가 I/O event를 기다려 block queue가 가득 차는 상태가 발생할 수 있다. 이 때 CPU는 또 아무런 작업을 하지 않고 기다려야 한다.
이런 점을 해결하기 위해서는 메모리를 물리적으로 늘리거나, 혹은 해당 큐의 일부분을 disk의 가상 메모리를 이용해 옮겨 (Suspend queue) 큐의 공간을 더 확보하여 해결할 수 있다. 이를 통해 프로세스를 더 실행할 수 있다.
이런 작업은 OS가 blocked 큐의 프로세스를 가상 메모리로 옮기고, Suspend 시키는 것이다. 따라서 새로운 process의 state인 Suspend가 새로 생겨난다.
이런 작업을 할 때 스케쥴을 잘못 사용하면 느린 disk의 속도만큼 성능이 저하될 수 있다. 그러나 메인 메모리보다 훨씬 큰 storage를 가상화하여 사용할 수 있기 때문에 큐의 크기가 더 커지고, running/ready 인 프로세스의 개수를 늘려 속도를 늘릴 수 있다.
그리고, 이런 Suspend 상태에서 block이 해제된 프로세스를 찾아내 다시 running으로 올리도록 하면 빠른 실행이 가능하게 된다.
단, suspend 된 상태는 바로 실행이 안되므로 가상 메모리에서 다시 메인 메모리로 올리는 작업이 필요하다.
종합적으로 이런 스케쥴링을 효율적으로 잘 하는 정책이 중요하다.
Suspend-state Process Model : Block/suspend, ready/suspend 상태가 새로 추가됨.
긴급한 우선순위를 가진 프로세스가 발생해서 ready queue가 가득 찰 경우, timeout 된 프로세스는 ready 큐에 다시 들어가지 못하고, ready/suspend로 빠진다. 마찬가지로, 높은 우선순위 프로세스가 ready에 들어올 경우, ready에 있던 프로세스 역시 ready/suspend로 빠진다.
그리고 blocked queue에 빈공간이 날 경우 blocked/suspend의 프로세스를 다시 blocked 로 옮겨 메인 메모리에 먼저 올리는 작업을 할 수도 있다.
Process Description
임의의 프로세스가 점유한 resource를 보면 해당 프로세스의 상태를 확인할 수 있다.
ex ) processor를 점유 = running, I/O 점유 = blocked, main memory를 점유 = Ready 등등...
이런 정보들은 모두 memory table, Process table, file table, I/O table 등등에 저장되어 있다. 이들은 모두 연관되어 직간접적으로 참조되고 있다.
Context Switch
Dispatcher에 의해 프로세스가 교환되는 작업을 문맥 교환이라 한다. State save, state restore 등등이 발생하고 cpu core를 다른 프로세스로 할당하는 모든 작업을 총칭한다.
그리고 각 프로세스의 context는 PCB에 저장되어 있다. 따라서 PCB의 크기가 커질 수록 오버 헤드가 커진다. 또한 하드웨어의 성능에 따라 속도가 크게 영향 받기도 한다.
결국 context switch는 작업을 하기 위한 준비시간이므로 많을 수록 성능이 저하된다. (pure Overhead) 따라서 timeout, I/O 등을 잘 스케쥴 하여 최소한 발생하도록 해야 한다.
Operations On Process
Process Creation
- New batch job : 여러 개의 프로세스가 한 번에 들어옴(프로그램 실행)
- interactive logon : user의 terminal 실행
- OS service : 프린터 등 I/O device 연결
- Spawn : 프로세스가 프로세스를 만드는 경우
Process Spawn : Parent / child process로 구별된다.
각 child는 새로 Parent가 되어 child를 만들 수 있고, 하나의 Parent는 여러 child를 만들 수 있다. 이는 트리 구조로 구성되어 표현할 수 있다.
리눅스의 login, bash shell, ps 등등 하나의 프로세스에서 계속 연결하여 프로세스를 생성할 수 있다.
- 이 때, 부모와 자식 프로세스 간의 관계는 부모의 리소스를 모두 공유/ 일부 공유/ 새로 할당 의 세 가지가 존재한다.
- 부모와 자식의 프로세스는 서로 독립적으로 수행될 수 있다. 혹은 부모가 자식의 프로세스가 끝날 때 까지 기다릴 수도 있다.(wait())
- 자식은 부모의 복제일 수도 있고(fork()), 새로 load 된 program (exec() - 부모와는 다른 작업 수행 가능)일 수도 있다.
#35P의 코드 : 부모는 자식의 pid를 return 값으로 받아오고, 자식은 자신이 만든 것이 아니기 때문에 pid는 0이 된다. 부모는 자식의 프로세스 종료까지 wait.
#36p의 코드 : 결과값은 여전히 5이다. 왜냐하면 fork()를 통하면 부모의 전체 복제를 생성하므로 child 자신의 전역변수 값이 20이 되는 것이지, 부모의 전역변수는 변하지 않는다.
#37p의 코드 : n개의 fork()를 하면 2^n개의 child process가 생성된다. 왜냐하면 생성된 프로세스는 순서대로 소스코드를 따라가면서 자신의 복제를 생성하므로 한 번의 fork()를 만날 때 마다 프로세스의 개수가 두 배가 되기 때문이다.
Process Termination
exit(), abort() 등의 명령어로 프로세스를 종료시킬 수 있다. 프로세스가 종료되면 사용되던 리소스는 모두 반납된다.
cascading : 부모가 종료될 경우, 자식도 모두 종료되는 것
- Zombie : 부모는 자식의 종료를 wait를 사용해 기다린다. 이 wait가 실행되면 부모는 실행이 완료된 자식 프로세스를 테이블에서 삭제한다.이 때 자식의 프로세스가 완료되었음에도 아직 부모의 wait 명령어가 실행되지 않아 이름만 남아있는 상태를 zombie 상태라 한다.
- Orphan : 또한 만약 parent가 wait()를 부르지 않고 child보다 먼저 종료되는 경우도 있다. 이 경우를 orphan이라 하며, linux에서는 이런 프로세스의 부모 pid를 강제로 1로 변경해버린다. (systemd의 PID, 주기적으로 wait()를 호출함)
*이 두 경우는 비슷하지만, zombie는 리소스를 모두 반납하고 실행해야 할 파일이 없는 경우고 orphan은 아직 실행해야 하는 파일이 남아있는 경우이다. 또한 이런 상태가 중첩될 수도 있다.
PID도 유한한 값이므로 주기적으로 회수해주어야 새로운 프로세스에 할당할 수 있다.
댓글
댓글 쓰기