The Process and CPU
[ The definition of a Process, informally ]
프로세스(Process)란 간단하게 실행중인 프로그램을 뜻한다.
프로그램은 디스크 위에 놓여져 있는 Instruction들의 묶음이라고 볼 수 있다.
이러한 바이트들을 실행하여 프로그램을 유용하게 변환하는 것을 "운영체제"라고 한다.
[ Virtualizing the CPU ]
하나 이상의 프로그램을 돌리기 위해서는 어떻게 해야할까?
CPU가 실행가능한지 항상 체크하며 프로그램을 쓰는 것은 굉장히 귀찮은 일이다.
이런 문제들을 해결하기 위해 OS는 CPU를 가상화(Virtualizing)를 통해 환상(illusion)을 만든다.
OS는 이러한 환상을 만들기 위해 각각의 프로세스들을 실행/정지한다.
이러한 기술을 시분할(Time Sharing)이라고 한다.
Time Sharing & Space Sharing
[ Time Sharing이란 ]
자원을 시간 단위로 나눠서 여러 사용자가 번갈아 가며 사용하는 방식
예로 CPU나 네트워크 링크처럼 한 번에 한 사람만 자원을 아주 짦은 시간 동안 한 프로그램이 사용하고, 곧바로 다른 프로그램이 사용하도록 하는 것이다. 이렇게 하면 여러 프로그램이 마치 동시에 실행되는 것처럼 보인다.
[ Space Sharing이란 ]
자원을 공간적으로 나눠서 동시에 여러 사용자가 각자의 공간을 가지게 하는 방식
예를 들어 디스크 공간이나 메모리 등을 쪼개서 각각 나눠 가지는 것을 뜻한다.
Scheduling Policy
Time Sharing과 Space Sharting과 같은 메커니즘들은 단순히 기능을 구현하는 방식이다. 이 기능을 어떻게 사용할지 결정하는 전략(Policy)을 통해 OS는 지능적으로 시스템을 운영한다.
즉, OS는 단순히 무작위로 어떤 프로그램을 실행할지 고르는게 아니라, 다양한 요소를 고려해서 "지금 누구를 실행시키는 게 가장 좋을까?"를 판단한다.
[ TIP: Separate Policy and Mechanism ]
운영체제뿐 아니라 소프트웨어 공학 전반의 핵심 설계 원칙은
"정책과 메커니즘을 분리하라"는 것이다.
Mechanism: 시스템이 어떻게 동작하는가를 담당하며 기능 구현에
초점을 두고 있다. 예로 인터럽트 처리 루틴, 스케줄링 큐, Context Switch가 있다.
Policy: 시스템이 어떤 기준으로 동작을 선택하는가를 담당하며 전략 결정에 초점을 두고 있다.
예를 들어 어떤 프로세스를 먼저 실행하는가, 어떤 페이지를 교체하는가를 생각할 수 있다.
이 둘을 분리하면 시스템의 유연성과 재사용성이 높아지며, 특정 환경에 맞게 정책만
바꾸는 것도 쉬워진다. 즉, 정책은 바뀔 수 있어도 메커니즘은 바꾸기 어렵기 때문에,
설계 초기부터 이들을 분리해서 구성하는 것이 중요하다는 뜻이다.
Machine State
프로세스가 무엇으로 구성되어 있는지를 이해하려면, 그 프로세스의 머신 상태(machine state)를 이해해야 한다. 프로세스는 단순히 코드 덩어리가 아니라, 현재 실행 중인 프로그램이 사용하는 모든 하드웨어 자원(상태)의 집합이다.
[ Definition of Machine State ]
프로세스에서 머신 상태란 CPU나 메모리 등에서 프로세스가 접근하거나 수정할 수 있는 모든 정보를 뜻한다.
[ Components of Machine State ]
이 둘을 포함한 머신 상태가 있어야, 프로세스는 언제든 중단됐다가 다시 정확히 같은 상태로 재개될 수 있다. 이것은 나중에 나올 Context Switch의 핵심 원리이다.
Interface of an OS
운영체제를 구성하는데 있어 포함해야하는 몇가지 인터페이스들이 존재한다.
Loading From Program To Process
프로그램이 디스크에 저장된 상태에서 실제 실행 가능한 프로세스로 메모리에 로드되는 과정을 알아보자
malloc, new 등의 할당된 메모리)운영체제가 프로그램을 실행하려면, 먼저 그 프로그램을 단순한 파일 상태에서 하나의 프로세스(process)로 변환해주는 과정이 필요하다. 가장 먼저 운영체제는 디스크(또는 SSD)에 저장된 프로그램의 코드와 정적 데이터를 메모리로 로드하는 작업을 수행한다. 이때 프로그램은 일반적으로 실행 가능한 파일 형식(exe, elf 등)으로 저장되어 있어 운영체제는 이 형식에 맞춰 필요한 부분을 파악하고 해당 내용을 프로세스의 주소 공간(address space)에 적절히 배치시킨다. 여기서 주소 공간이란, 프로세스가 실행되는 동안 사용할 수 있는 자기만의 메모리 영역을 의미한다.
과거의 단순한 운영체제들은 프로그램 실행 전에 모든 코드와 데이터를 한 번에 미리 로딩(eager loading)하는 방식을 사용했지만, 현대의 운영체제들은 더 효율적인 메모리 사용을 위해 필요한 부분만 점진적으로 불러오는 지연 로딩(lazy loading) 기법을 사용한다. 이는 페이지 단위로 메모리를 관리하는 페이징(paging), 스와핑(swapping) 기술과 밀접한 관련이 있다.
프로그램의 코드와 데이터가 메모리에 적재된 이후에는, 운영체제가 스택(stack) 메모리를 설정해준다. C언어 프로그램에서 스택은 함수 호출 시 사용되는 지역 변수, 함수 인자, 반환 주소 등을 저장하는 데에 사용된다. 따라서 운영체제는 스택 영역을 할당하고, main() 함수가 실행될 수 있도록 그 인자들인 argc, argv 배열을 스택에 미리 채워 넣는다.
이와 함께 운영체제는 힙(heap) 영역도 초기화해준다. 힙은 동적 메모리 할당을 위한 공간으로, C언어에서는 malloc()이나 free() 같은 함수로 접근한다. 초기에는 작게 시작하지만, 프로그램이 실행되는 동안 필요한 만큼 점차 확장될 수 있도록 구성된다. 운영체제는 이 메모리 요구에 따라 힙 공간을 유동적으로 늘려줄 수 있다.
입출력 초기화도 중요한 부분 중 하나이다. 특히 유닉스 계열 운영체제에서는, 프로그램이 실행될 때 기본적으로 표준 입력(stdin), 표준 출력(stdout), 표준 에러(stderr)를 위한 파일 디스크립터(file descriptor) 3개를 자동으로 열어준다. 덕분에 프로그램은 터미널로부터 입력을 받고, 화면에 출력하거나 에러 메시지를 표시할 수 있다.
이러한 모든 준비가 끝난 후에야, 운영체제는 프로그램의 실행을 시작할 수 있다. 가장 먼저 실행되는 진입점(entry point)은 보통 main() 함수이며, 운영체제는 특수한 방식으로 CPU 제어권을 main() 함수로 넘겨줌으로써 프로그램의 본격적인 실행을 시작하게 된다.
Running, Ready, Blocked
운영체제는 프로세스를 관리할 때, 해당 프로세스가 현재 어떤 상태에 있는지 추적한다.
[DV66], [V+65]은 OS 구조와 프로세스 개념 정립에 매우 큰 기여를 한 고전문헌이다.
[ Running ]
CPU에서 실행 중인 상태 즉, 명령어를 수행 중인 상태
[ Ready ]
실행할 준비는 됐지만, CPU를 아직 할당받지 못한 상태
[ Blocked ]
I/O 요청 등으로 대기 중인 상태 (Ex. 디스크 요청에 의해 완료될 때까지 기다림)
Ready와 Blocked의 차이점
Ready는 OS 스케쥴링 대상으로 CPU가 없어서 준비를 완료한 상태이며
Blocked는 OS 스케쥴링 대상에서 벗어나 I/O 등의 이벤트 대기를 한 상태로
CPU 할당이 필요없는 상태를 나타낸다.
프로세스 상태 전이 (Process State Transitions)
![]()
Tracing Process Example
![]()
그림에서 Process 0와 Process 1의 시간별 상태를 나타낸다.
여기서 주의깊게 보아야 할 점은 Time 3에서 Process 0 이 Blocked 상태가 되기 전
Process 0 initiates I/O라고 되어 있는데 이것은 Process 0이 I/O 작업을 요청하여 (Ex.read(), write()) OS는 이를 감지하고 트리거 지점을 만든다. 즉, OS는 어떤 작업을 요청하면 트리거 지점을 만들고 이후에 어떤 상태로 바꿔야 하는지 관리한다.
운영체제도 하나의 프로그램으로써, 프로세스 상태를 포함한 중요한 정보를 데이터구조로 관리한다.
운영체제의 데이터 구조
OS는 각각의 프로세스를 추적하기 위해 프로세스 제어 블록(Process Control Block, PCB) 또는 Process structure를 사용한다.
프로세스 상태 : 현재 프로세스의 상태값(Running, Ready, Blocked 등)PID (Process ID) : 프로세스를 식별하는 고유 번호Program Counter (PC) : 다음에 실행할 명령어의 메모리 주소Stack Pointer (SP) : 현재 함수 호출 스택의 위치레지스터 상태 (thread) : 문맥 교환을 위해 레지스터 값 전체 저장메모리 맵 정보 : 어떤 주소에 무엇이 있는지 (코드, 데이터, 스택 등)열린 파일 정보 : 파일 디스크립터 목록 (open 된 파일들)스케쥴링 정보 : 우선순위, 스케쥴러 큐의 포인터 등struct context {
int eip;
int esp;
int ebx;
int ecx;
int edx;
int esi;
int edi;
int ebp;
};
// the different states a process can be in
enum proc_state { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };
// the information xv6 tracks about each process
// including its register context and state
struct proc {
char *mem; // Start of process memory
uint sz; // Size of process memory
char *kstack; // Bottom of kernel stack
// for this process
enum proc_state state; // Process state
int pid; // Process ID
struct proc *parent; // Parent process
void *chan; // If !zero, sleeping on chan
int killed; // If !zero, has been killed
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
struct context context; // Switch here to run process
struct trapframe *tf; // Trap frame for the
// current interrupt
};
Context Switch : 문맥 교환
CPU가 하나의 프로세스 실행을 중단하고, 다른 프로세스를 실행하기 위해 내부 상태를 저장하고 복원하는 과정 즉, OS는 PCB를 통해 프로세스 상태를 저장/복원하며 CPU 실행 대상을 바꾸는 역할을 한다.
추가 프로세스 상태 : 초기 상태, 좀비 상태
[Initial State Example]
1. 부모 프로세스가 fork()를 호출하여 자식 프로세스가 생김
2. 자식 프로세스는 메모리, 스택, PCB 등을 초기하여 준비를 시작
3. 이 준비 과정을 Initial(생성 중) 상태
[Zombie State Example]
1. 자식 프로세스가 일을 끝내고 exit()을 호출
2.