Operating System: The Process

LeeYun·2025년 4월 1일

1. The Abstraction: A Process

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는 지능적으로 시스템을 운영한다.

  • [ Scheduling Policy의 결정 방법 ]
    운영체제의 스케줄링 정책은 일반적으로 다음과 같은 정보를 활용한다.
    1. 과거 정보 : 지난 1분 동안 어떤 프로그램이 더 많이 실행되었는가?
    2. 작업 부하에 대한 지식 : 어떤 종류의 프로그램이 실행되고 있는가?
    3. 성능 지표 : 시스템이 대화형 성능(interactive performance)을 최적화하려는가, 아니면 처리량(Throughput)을 최적화하려는가?

즉, OS는 단순히 무작위로 어떤 프로그램을 실행할지 고르는게 아니라, 다양한 요소를 고려해서 "지금 누구를 실행시키는 게 가장 좋을까?"를 판단한다.

[ TIP: Separate Policy and Mechanism ]

운영체제뿐 아니라 소프트웨어 공학 전반의 핵심 설계 원칙은
"정책과 메커니즘을 분리하라"는 것이다.
Mechanism: 시스템이 어떻게 동작하는가를 담당하며 기능 구현에
초점을 두고 있다. 예로 인터럽트 처리 루틴, 스케줄링 큐, Context Switch가 있다.
Policy: 시스템이 어떤 기준으로 동작을 선택하는가를 담당하며 전략 결정에 초점을 두고 있다.
예를 들어 어떤 프로세스를 먼저 실행하는가, 어떤 페이지를 교체하는가를 생각할 수 있다.

이 둘을 분리하면 시스템의 유연성과 재사용성이 높아지며, 특정 환경에 맞게 정책만
바꾸는 것도 쉬워진다. 즉, 정책은 바뀔 수 있어도 메커니즘은 바꾸기 어렵기 때문에,
설계 초기부터 이들을 분리해서 구성하는 것이 중요하다는 뜻이다.

Machine State


프로세스가 무엇으로 구성되어 있는지를 이해하려면, 그 프로세스의 머신 상태(machine state)를 이해해야 한다. 프로세스는 단순히 코드 덩어리가 아니라, 현재 실행 중인 프로그램이 사용하는 모든 하드웨어 자원(상태)의 집합이다.

  • [ Definition of Machine State ]
    프로세스에서 머신 상태란 CPU나 메모리 등에서 프로세스가 접근하거나 수정할 수 있는 모든 정보를 뜻한다.

  • [ Components of Machine State ]

    • Memory(주소 공간): 실행 코드, 변수, 데이터, 스택, 힙 등..
    • Registers: CPU 내부 값들(PC, SP, General-purpose register 등)

    이 둘을 포함한 머신 상태가 있어야, 프로세스는 언제든 중단됐다가 다시 정확히 같은 상태로 재개될 수 있다. 이것은 나중에 나올 Context Switch의 핵심 원리이다.

2. Process API

Interface of an OS


운영체제를 구성하는데 있어 포함해야하는 몇가지 인터페이스들이 존재한다.

  • Create
    운영체제는 명령어를 Shell에 넣거나 GUI를 통해 아이콘을 누를 때 새로운 프로세스를 생성하여 지정된 프로그램을 실행할 수 있도록 해야한다.
  • Destroy
    운영체제가 프로세스를 생성할 수 있다면 제거 또한 할 수 있어야 한다. 프로세스는 실행된 후 정상 종료(exit() 호출)를 한다. 만약 비정상적인 상태(오류)가 발생하면 오류를 발생했다고 판단 후 종료시킨다(Segfault). 또한 부모 프로세스가 종료시키거나 OS가 자원 부족이나 스케줄링으로 종료시킬 수도 있다.
  • Wait
    운영체제는 자식 프로세스가 끝날 때까지 기다리는 기능을 제공한다.
  • Miscellaneous Control
    운영체제는 Wait기능 뿐만 아니라 프로세스를 일시 중지시켜 멈춰있는 상태로 전환(Suspend/SIGSTOP)하거나 멈춘 프로세스를 다시 실행 상태로 전환(resume/SIGCONT)와 같은 추가적인 프로세스 제어 기능을 제공한다.
  • Status
    운영체제는 프로세스에 대한 상태 정보를 조회할 수 있는 인터페이스를 제공한다. 예를 들어 그 프로세스가 얼마나 오래 실행되었는지, 현재 어떤 상태(state)인지를 알 수 있다. 이를 통해 성능 분석, 문제 진단, 자원 감시 등을 할 수 있다.

Loading From Program To Process


프로그램이 디스크에 저장된 상태에서 실제 실행 가능한 프로세스로 메모리에 로드되는 과정을 알아보자

  • [ 디스크(Disk) ]
    디스크에는 프로그램(Program)이 저장되어 있다. 프로그램은 실행 파일 형태로 저장되어 코드(code)와 정적 데이터(static data)를 포함한다.
  • [ 메모리(Memory) ]
    프로그램이 실행되기 위해선 메모리로 로딩(load)되어야 한다.
    메모리에 올라가면 프로세스가 생성되며 프로세스는 다음과 같은 구조를 갖는다.
    • 코드(code): 실행 명령어
    • 정적 데이터(static data): 전역 변수 등
    • 힙(heap): 동적 메모리(예: malloc, new 등의 할당된 메모리)
    • 스택(stack): 함수 호출 시 사용되는 전역 변수 리턴 주소 등
      이들은 각각의 영역에서 분리된다.
  • [ CPU ]
    메모리에 로딩된 프로세스의 명령어를 CPU가 실행한다.

3. Process Creation: A Little More Detail

운영체제가 프로그램을 실행하려면, 먼저 그 프로그램을 단순한 파일 상태에서 하나의 프로세스(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() 함수로 넘겨줌으로써 프로그램의 본격적인 실행을 시작하게 된다.

4. Process States

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)

  • [ Ready → Running ] : 스케쥴링되어 CPU를 할당받은 상태 (Scheduled)
  • [ Running → Ready ] : 디스케쥴링되어 CPU에 내려진 상태 (descheduled)
  • [ Running → Blocked ] : I/O 같은 이벤트 요청으로 대기 상태
  • [ Blocked → Ready] : 이벤트 완료(I/O 등) 후 다시 준비 상태

Tracing Process Example

그림에서 Process 0Process 1의 시간별 상태를 나타낸다.
여기서 주의깊게 보아야 할 점은 Time 3에서 Process 0Blocked 상태가 되기 전
Process 0 initiates I/O라고 되어 있는데 이것은 Process 0이 I/O 작업을 요청하여 (Ex.read(), write()) OS는 이를 감지하고 트리거 지점을 만든다. 즉, OS는 어떤 작업을 요청하면 트리거 지점을 만들고 이후에 어떤 상태로 바꿔야 하는지 관리한다.

5. Data Structures

운영체제도 하나의 프로그램으로써, 프로세스 상태를 포함한 중요한 정보를 데이터구조로 관리한다.

운영체제의 데이터 구조


OS는 각각의 프로세스를 추적하기 위해 프로세스 제어 블록(Process Control Block, PCB) 또는 Process structure를 사용한다.

  • [ 프로세스 제어 블록(PCB)의 데이터 구조 ]
    • 프로세스 상태 : 현재 프로세스의 상태값(Running, Ready, Blocked 등)
    • PID (Process ID) : 프로세스를 식별하는 고유 번호
    • Program Counter (PC) : 다음에 실행할 명령어의 메모리 주소
    • Stack Pointer (SP) : 현재 함수 호출 스택의 위치
    • 레지스터 상태 (thread) : 문맥 교환을 위해 레지스터 값 전체 저장
    • 메모리 맵 정보 : 어떤 주소에 무엇이 있는지 (코드, 데이터, 스택 등)
    • 열린 파일 정보 : 파일 디스크립터 목록 (open 된 파일들)
    • 스케쥴링 정보 : 우선순위, 스케쥴러 큐의 포인터 등
  • [ Example : xv6 Process Sturcture ]
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 : 초기 상태 ]
    프로세스가 막 생성되고 있는 중인 상태
[Initial State Example]

1. 부모 프로세스가 fork()를 호출하여 자식 프로세스가 생김
2. 자식 프로세스는 메모리, 스택, PCB 등을 초기하여 준비를 시작
3. 이 준비 과정을 Initial(생성 중) 상태
  • [ Zombie State : 좀비 상태 ]
    프로세스가 끝났지만 완전히 정리되지 않은 상태
[Zombie State Example]

1. 자식 프로세스가 일을 끝내고 exit()을 호출
2. 
profile
AI/Network

0개의 댓글