티스토리 뷰
이번 포스팅에서는 Process에 대한 전반적인 개념들에 대해 다룬다.
Process 란?
- Disk에서 올라와 메모리와 CPU에서 동작하고 있는 프로그램
- scheduling 객체
*프로그램(program) : Disk에 있는 ‘수행하기 전’의 상태, 수동적이며 ‘바이너리’라고 불린다.
*scheduling : 프로세스가 메모리에서 CPU로 번갈아 올라가는 것을 말한다. 한 개의 CPU에는 한 개의 프로세스밖에 올라가지 못하므로 순서를 결정해 CPU로 올리는 스케줄링이 필요하다. 이후에 자세히 다룰 예정
Process States
프로세스는 현재 어떤 상황인지에 따른 ‘상태’를 가진다. 프로세스의 **State(상태)**는 5가지이며, 상태에서 상태로 전이되는 Transition은 6가지 이다. 추가로 Memory에서 디스크의 swap공간으로 전이하는 suspend와 resume도 존재한다.
아래 그림에서 동그라미는 State, 화살표는 Transition이다.
State
1. new(created, embryo)
- 프로세스가 새로 생성된 상태
2. ready
- 프로세스가 run할 준비가 된 상태
- new와의 가장 큰 차이점은 메모리에 자신의 address space가 모두 구성이 된 상태라는 것이다.
3. running
- 수행 상태
- ready와의 가장 큰 차이점은 running은 CPU를 가지고 있는 상태, ready는 CPU를 가지려고 하는 상태(아직 갖지 못한 상태)라는 것이다.
4. waiting(blocked)
- 사건 발생 대기 상태
- I/O가 끝나거나 이벤트가 발생하기를 기다리는 상태이다.
5. terminated(zombie)
- 종료된 상태
Transition
1. admitted
- new → ready
- 프로그램이 메모리로 올라오는 것을 말한다.(우리가 PPT와 같은 프로그램을 클릭 했을 때 발생한다.)
2. dispatch(schedule)
- ready → running
- 메모리에 있던 프로세스가 CPU로 올라가는 것이다.
- 스케줄링 떠올리면 용어 외우기 쉬울듯
3. timeout(preemptive, descheduled)
- running → ready
- 프로세스가 CPU에서 다시 메모리로 내려오는 것
- 선점을 의미, 시분할 시스템의 기본 아이디어
- 스케줄링 정책 중 선점형은 이러한 선점을 이용하여 수행이 끝나지 않은 프로세스를 CPU에서 메모리로 내리며 스케줄링한다.
4. wait(sleep, I/O initate)
- running → wait
- ex) 키보드에서 입력이 될 때까지 기다린다.
5. wakeup(I/O Done)
- wait → ready
- I/O 또는 이벤트가 완료될 경우 전이
6. exit
- running → terminated
- 프로세스의 수행이 끝나고 종료된 것
디스크-메모리 사이에서의 Transition
프로세스가 계속 메모리에 올라갈 경우 DRAM의 메모리가 부족해지는 현상이 나타날 수 있다. 이를 방지하기 위해 ready나 waiting상태의 프로세스 일부를 Disk의 Swap 공간으로 내리는 작업을 한다.
suspend
- ready → swap OR waiting → swap
- 프로세스를 swap 공간으로 내린다.
resume
- swap → ready OR swap → waiting
- 프로세스를 swap 공간에서 다시 올린다.
PCB(Process Control Block)
PCB란?
OS가 프로세스를 관리하기 위해서는 각 프로세스에 대한 정보가 필요하다. 이때 프로세스를 관리하기 위한 정보 자료구조를 PCB라고 한다. PCB는 각 프로세스마다 하나씩 존재하며 kernel의 data segment에서 관리한다. PCB의 자원은 다음과 같다.
- Process State : 위에 설명한 프로세스의 상태들을 저장한다.
- Process ID(pid) : 새로 생성되면 각 프로세스는 자신의 고유한 pid를 가진다.
- Program Counter, CPU register : context switch 하는 동안 사용
- CPU scheduling information
- ex) process의 CPU 사용량, 프로세스 우선순위
- Memory(address space)
- Text : 명령어
- Data : 전역 변수
- Stack : 지역 변수, 함수 인자, 복귀 주소, …(메모리공간상 위에서 아래로 이동)
- Heap : 동적할당(메모리 공간 상 아래에서 위로 이동)
- Open된 파일들
- I/O status information
- ex) I/O를 얼마나 했는지(네트워크를 얼마나 사용했는지)
- Accounting information
- ex) CPU, Memory를 얼마나 사용하는지
PCB의 예
그렇다면 실제 OS에서 PCB는 어떻게 구현되어 있을까?
리눅스는 오픈소스이기 때문에 어느 누구든 코드를 볼 수 있다. 위에서 볼 수 있듯이 리눅스에서의 PCB는 task structure로 구현되어 있으며, 그 안에 CPU, state, memory 등의 정보가 들어있다.
혹시 직접 보고싶다면 여기로 들어가면 된다.
프로그램이 수행 될 때 OS 내부 동작 과정
1. Load
- 디스크 상에 있던 코드/데이터 부분을 메모리로 올리는 과정
- 로딩하려면 디스크상에 존재하는 program의 정확한 형식을 알아야 하므로 수행 포맷을 갖는다.
- ex) ELF(리눅스에서의 포맷), PE(윈도우 포맷) …
2. Dynamic allocation 동적할당
- Heap, Stack 동적할당
- 메인함수에서 사용하는 인자들(argc, argv)을 stack에 초기화한다.
3. Initialization
- main함수 시작하기 전에 미리 초기화해야 할 부분들을 처리한다.(전처리)
- 파일 디스크립터(0 - 표준 입력, 1 - 표준 출력, 2 - 표준 에러)
- I/O, signal
- 이 과정이 있기에 main함수가 명시적으로 open하지 않아도 바로 사용이 가능하다.
<——————>
*여기까지가 수행할 준비를 완료하는 과정이다.
4. Jump to the entry point
- entry point로 점프한다.
- 다수의 언어가 main이 entry point이다.
Process System Call
프로세스의 생성, 수행 등과 관련하여 여러 시스템콜이 존재한다. 대표적인 fork()부터 차례차례 알아보자~
1. fork()
- 새로운 프로세스를 생성하는 시스템콜이다. 기존에 있던 프로세스를 parent, 새로 만들어진 프로세스를 child라고 부른다.
- 리턴 값은 두 개이다. 하나는 parent를 위한 것, 하나는 child를 위한것
- parent : child의 pid(항상 0보다 큰 값)
- child : 0
- parent와 child 중 어느 것이 먼저 수행 되는지는 우리가 알 수 없다. (내부 스케줄링 정책에 의해 결정되므로)
따라서 non-determinism 하다. 하지만 wait()를 쓰면 순서를 보장할 수 있다! wait() 설명은 바로 아래에~
2. wait()
- 프로세스의 상태를 wait으로 바꿔준다.(프로세스 상태는 위에서 설명했음~)
- parent 프로세스를 child 프로세스 중 하나가 완전히 끝날 때까지 block시키는 것이다. (자식 중 누구일지는 또 모름)
- wait()을 사용함으로써 부모 자식간의 순서를 개발자가 결정할 수 있게되었다! 따라서 deterministic 하다.
3. exec()
- 새로운 프로그램을 수행하기 위한 시스템콜이다.
- 로딩, 초기화, main 함수 수행 등을 작업하는 역할
- never return! exec()를 수행하면 지정한 프로세스로 넘어간다. 즉, exec()를 호출한 프로세스는 새로운 프로세스에 의해 덮어 쓰여지게 된다.
- fork()와 구분하는 이유는? 좀 더 유연하고 구조적인 프로그램 만들기 위해서, 즉 fork와 exec 사이에 새로운 기능을 쉽게 추가할 수 있게 하기 위해서이다.
더 쉽게 설명하자면, fork를 할 경우 parent와 child 프로세스로 나뉜다. 이 때 wait()을 수행한 후에 child 프로세스에서 exec()를 호출할 경우 child 프로세스는 새로운 프로세스에 의해 덮여쓰여지게 되지만 부모 프로세스는 남아있게 된다.
따라서 wait이 끝난 후에 부모 프로세스는 resume하게 된다.
그림으로 설명하면 다음과 같다.
- exec() 의 종류
- execl(const char *path, const char *arg, ...) : 나열형 인자
- execv(const char *path, char *const argv[]) : 벡터형(string array) 인자
- execle(const char *path, const char *arg ,..., char * const envp[]) : +환경변수
- execve(const char *path, char *const argv [], char *const envp[]) : +환경변수
- execlp(const char *file, const char *arg, ...)
- execvp(const char *file, char *const argv[])
4. 그 외
- getpid() : get process id
- kill() : 프로세스에 시그널을 보낸다.(인자로 signal number를 주면 해당 프로세스 종료)
- signal() : signal을 받으면 그 signal을 처리할 수 있는 handler(catch function)를 등록할 수 있다.
- nice() : 스케줄링때 자신의 우선순위를 높이거나 낮출 수 있다.
다음 포스팅에서는 이러한 프로세스들이 CPU에서 어떻게 동작하는지에 대해 다룰 예정이다~
'학교수업 > 운영체제' 카테고리의 다른 글
[운영체제] 4. Scheduling (2) | 2024.02.21 |
---|---|
[운영체제] 3. Limited Direct Execution (1) | 2024.02.02 |
[운영체제] 1. OS Introduction (0) | 2024.01.18 |
[운영체제] 정리 전 회고 (2) | 2024.01.18 |
가상화 (Virtualization) (0) | 2021.04.07 |