Skip to main content

Command Palette

Search for a command to run...

Processes in Operating Systems

Published
87 min read
Processes in Operating Systems

1️⃣ Concept and Creation of Processors
2️⃣ Process State and Management
3️⃣ Process Execution and Control


1️⃣ Concept and Creation of Processors

How Does an Operating System Manage Running Tasks?

When using a computer, it's perfectly natural to have a web browser, messenger, and music player all open at the same time. From the user's perspective, it looks like several programs are simply running all at once; but the operating system is examining this situation in far greater detail.

So how exactly does the operating system distinguish and manage these executions? Rather than jumping straight to the answer, let's build up the concepts one by one so the answer emerges naturally. Getting a firm grasp of this big picture first will make topics like scheduling and synchronization - which come later, much easier to understand.

운영체제는 실행 중인 작업을 어떤 단위로 관리할까?

컴퓨터를 사용하다 보면 웹브라우저, 메신저, 음악 재생 프로그램을 동시에 켜두는 일이 자연스럽다. 사용자 입장에서는 여러 프로그램이 그냥 한꺼번에 돌아가는 것처럼 보이지만, 운영체제 입장에서는 이 상황을 훨씬 세밀하게 들여다보고 있다.

그렇다면 운영체제는 이 실행들을 도대체 어떤 단위로 구분하고 관리하는 걸까? 이 질문에 바로 답을 내놓기보다는, 답이 자연스럽게 나올 수 있도록 개념을 하나씩 쌓아가 보자. 이 큰 흐름을 먼저 잡아두면, 나중에 배우게 될 스케줄링이나 동기화 같은 주제들도 훨씬 수월하게 이해할 수 있을 것이다.


1. The Basic Concept of a Process

What's the Difference Between a Program and a Process?

In everyday conversation, "program" and "process" are often used interchangeably. In operating systems, however, the two concepts are clearly distinct.

A program is a file stored on a storage device - a static state that has not yet been executed. Simply put, it's a chunk of code that's ready to run but isn't doing anything yet.

A process, on the other hand, is the state in which that program has been loaded into memory and is actually running. From the moment something becomes a process, the operating system allocates system resources such as CPU and memory to that execution and begins treating it as a single, independent unit to manage.

To summarize: in terms of execution status, a program is in a non-running state, while a process is in a state where it has been allocated CPU time and is actively running. There's also a difference in resource usage. A program uses no resources at all, but a process actively consumes system resources like CPU and memory as it executes.

1. 프로세스 기본 개념

프로그램과 프로세스, 무엇이 다를까?

일상적인 대화에서는 '프로그램'과 '프로세스'를 크게 구분하지 않고 쓰는 경우가 많다. 하지만 운영체제에서는 이 두 개념이 명확하게 갈린다.

프로그램은 저장장치에 저장된 파일, 즉 아직 실행되지 않은 정적인 상태를 말한다. 쉽게 말해 실행될 준비는 됐지만 아직 아무것도 하고 있지 않은 코드 덩어리다.

반면 프로세스는 그 프로그램이 메모리에 올라가 실제로 실행되고 있는 상태를 말한다. 프로세스가 되는 순간부터 운영체제는 CPU와 메모리 같은 시스템 자원을 해당 실행에 할당하고, 이를 하나의 독립적인 관리 대상으로 다루기 시작한다.

정리하면 이렇다. 실행 여부를 기준으로 보면, 프로그램은 실행되지 않은 상태이고 프로세스는 CPU를 할당받아 실제로 실행 중인 상태다. 자원 사용 면에서도 차이가 있다. 프로그램은 자원을 전혀 사용하지 않지만, 프로세스는 실행되면서 CPU와 메모리 같은 시스템 자원을 실제로 소비한다.


One Program, Multiple Processes

There's an important point worth highlighting here. Even if it's the same program, running it multiple times creates a different process for each execution.

For example, suppose you launch the same web browser twice. There is only one program file stored on disk, but the operating system recognizes each execution as a separate process and manages them independently. The file is one, but the running instances are two.

This matters because, going forward, you need to remember that the basic unit the operating system manages is not the "program" but the "process." Scheduling, memory allocation, and synchronization all revolve around processes.

하나의 프로그램, 여러 개의 프로세스

여기서 한 가지 중요한 포인트가 있다. 하나의 프로그램이라도 여러 번 실행되면, 그 실행마다 서로 다른 프로세스가 만들어진다는 것이다.

예를 들어 같은 웹브라우저를 두 번 실행했다고 하자. 디스크에 저장된 프로그램 파일은 하나지만, 운영체제는 각각의 실행을 별개의 프로세스로 인식하고 따로따로 관리한다. 파일은 하나여도 실행 중인 인스턴스는 둘인 셈이다.

이 개념이 중요한 이유는, 앞으로 운영체제가 관리하는 기본 단위가 '프로그램'이 아니라 바로 '프로세스'라는 점을 기억해야 하기 때문이다. 스케줄링도, 메모리 할당도, 동기화도 모두 프로세스를 중심으로 돌아간다.


Seeing Processes in Action Through Task Manager

There's a way to make the concept of the operating system managing execution in process units feel more concrete. That's Windows Task Manager.

When you open Task Manager, you see a list of all the processes currently running on your computer. Every single item in that list is an execution unit - " a process " managed by the operating system. Some were launched directly by you; others are tasks the operating system runs internally, invisible to the user.

Here's something interesting: even if you only have a few windows open on screen, opening Task Manager reveals that dozens of processes are actually running simultaneously. This shows that the range of execution the user perceives and the range the operating system actually manages are entirely different things.

Task Manager is a window that lets you see this reality with your own eyes. Through this screen, you can intuitively understand that the operating system's execution is not a single flow, but is divided into multiple execution units that are managed concurrently.

작업 관리자로 확인하는 프로세스의 실체

운영체제가 프로세스 단위로 실행을 관리한다는 개념을 조금 더 실감 나게 확인할 수 있는 방법이 있다. 바로 윈도우의 작업 관리자다.

작업 관리자를 열면 현재 컴퓨터에서 실행 중인 프로세스들이 목록 형태로 쭉 나열된다. 이 목록의 항목 하나하나가 바로 운영체제가 관리하는 실행 단위, 즉 프로세스다. 내가 직접 실행한 프로그램도 있고, 사용자 눈에는 보이지 않지만 운영체제가 내부적으로 돌리고 있는 작업들도 함께 포함되어 있다.

여기서 흥미로운 점이 있다. 화면에 띄워 놓은 창이 몇 개 없더라도, 작업 관리자를 열어보면 실제로는 수십 개의 프로세스가 동시에 동작하고 있다는 걸 확인할 수 있다. 사용자가 인식하는 실행의 범위와 운영체제가 실제로 관리하는 실행의 범위가 전혀 다르다는 뜻이다.

작업 관리자는 이 사실을 눈으로 직접 확인시켜 주는 창구다. 운영체제의 실행은 단순히 하나의 흐름이 아니라, 여러 개의 실행 단위로 나뉘어 동시에 관리되고 있다는 것을 이 화면을 통해 직관적으로 이해할 수 있다.


The Moment a Program Becomes a Process

To understand the relationship between a program and a process, it helps to trace the flow of execution step by step.

It starts on disk. A program is stored there as a file; an executable made up of code and static data. In this state, nothing is running yet. It's not using the CPU, it's not occupying memory; it's simply a file sitting quietly on disk.

When the user runs the program, the situation changes. The program on disk is loaded into memory, and once it is loaded and in an executable state, we call it a process. That process then receives CPU time, and actual computation begins.

To recap: a program is an executable file stored on disk; a process is the state in which that program has been loaded into memory and is running. Even the same program, when executed multiple times, can produce that many different processes in memory.

The core takeaway from this flow is one thing: the very moment a program is loaded into memory, it transforms into a process - a managed entity under the operating system's control. Before that moment it's merely a stored file, but once it's in memory, the operating system recognizes it as an independent execution unit and begins allocating and managing resources like CPU and memory.

프로그램이 프로세스가 되는 순간

프로그램과 프로세스의 관계를 이해하려면 실행이 일어나는 흐름을 단계별로 따라가 보는 것이 좋다.

시작은 디스크다. 디스크에는 프로그램이 파일의 형태로 저장되어 있다. 이 프로그램은 코드와 정적인 데이터로 이루어진 실행 파일로, 이 상태에서는 아직 아무것도 실행되고 있지 않다. CPU를 사용하는 것도 아니고, 메모리를 점유하는 것도 아닌, 그냥 디스크 위에 가만히 놓인 파일일 뿐이다.

여기서 사용자가 프로그램을 실행하면 상황이 바뀐다. 디스크에 있던 프로그램이 메모리로 올라오게 되고, 메모리에 적재되어 실행 가능한 상태가 된 것을 바로 프로세스라고 부른다. 그리고 이 프로세스가 CPU를 할당받으면서 비로소 실제 연산이 시작된다.

다시 한번 정리하면 이렇다. 프로그램은 디스크에 저장된 실행 파일이고, 프로세스는 그 프로그램이 메모리에 적재되어 실행 중인 상태다. 같은 프로그램이라도 여러 번 실행하면 메모리에는 그만큼 서로 다른 프로세스가 생성될 수 있다.

이 흐름에서 핵심은 딱 하나다. 프로그램이 메모리에 적재되는 바로 그 순간, 운영체제의 관리 대상인 프로세스로 전환된다는 것이다. 그 전까지는 그저 저장된 파일에 불과하지만, 메모리에 올라오는 순간부터 운영체제는 이를 독립적인 실행 단위로 인식하고 CPU와 메모리 같은 자원을 할당하며 관리하기 시작한다.


2. Process Structure and Creation

The Memory Structure of a Process

When we say a process is loaded into memory, it's not simply a matter of one chunk of program code being dumped in. The various roles needed for execution are separated and each occupies its own region in a structured layout. Understanding this structure makes it much clearer how the operating system systematically manages running processes.

2.프로세스의 구성과 생성

프로세스의 메모리 구조

프로세스가 메모리에 올라간다고 했을 때, 단순히 프로그램 코드 하나가 통째로 올라가는 것이 아니다. 실행에 필요한 여러 역할들이 구분되어 각자의 영역을 차지하는 구조로 배치된다. 이 구조를 이해하면 운영체제가 실행 중인 프로세스를 어떻게 체계적으로 관리하는지 훨씬 명확하게 보인다.


Code Region

First is the code region. As the name implies, this is where the program's instructions - the commands the CPU reads and executes one by one - are stored. It's the region that defines the program's behavior itself.

Data Region

Next is the data region. This is where data used throughout the entire program is stored - things like global variables and static variables. Rather than being used only within a specific function, these are data that are referenced broadly throughout the program's execution.

Heap Region

Then there's the heap region. The heap is a memory area that is dynamically allocated while the program runs. For example, creating a new object during execution or requesting additional memory on the fly falls into this category. Its defining characteristic is that its size isn't fixed in advance - it's used flexibly according to the flow of execution.

Stack Region

Finally, there's the stack region. This is where information related to function calls, along with local variables and parameters, is stored. Every time a function is called, the necessary information is pushed onto the stack, and when the function returns, it is popped off — so the amount of space used changes continuously with the flow of execution.

Each Process Has an Independent Memory Space

Through this structure ; code, data, heap, and stack - the operating system manages the memory a process needs during execution in a role-based, systematic way.

There's one point that must be emphasized here: each process holds this memory structure independently. Process A's memory space and Process B's memory space do not directly access or mix with each other. As a result, even if a problem occurs in one process, it does not affect others. This independence is a critical foundation that allows the operating system to manage multiple processes simultaneously and stably.

코드 영역

가장 먼저 코드 영역이다. 이름 그대로 실행할 프로그램의 명령어들이 저장되는 공간이다. CPU는 이 영역에 저장된 명령어를 하나씩 읽어가며 실행한다. 프로그램의 동작 자체를 정의하는 영역이라고 볼 수 있다.

데이터 영역

다음은 데이터 영역이다. 전역 변수나 정적 변수처럼 프로그램 전체에 걸쳐 사용되는 데이터들이 이곳에 저장된다. 특정 함수 안에서만 쓰이는 것이 아니라, 프로그램이 실행되는 동안 전반적으로 참조되는 데이터들의 자리다.

힙 영역

그다음은 힙 영역이다. 힙은 프로그램이 실행되는 도중에 동적으로 할당되는 메모리 공간이다. 예를 들어 실행 중에 새로운 객체를 생성하거나, 필요에 따라 메모리를 추가로 요청하는 경우가 여기에 해당한다. 미리 크기를 정해두는 것이 아니라 실행 흐름에 따라 유동적으로 사용된다는 점이 특징이다.

스택 영역

마지막으로 스택 영역이다. 함수 호출과 관련된 정보, 그리고 지역 변수와 매개변수가 저장되는 공간이다. 함수가 호출될 때마다 필요한 정보가 쌓이고, 함수가 종료되면 다시 걷혀나가는 방식으로 동작하기 때문에 사용량이 실행 흐름에 따라 계속해서 변한다.


각 프로세스는 독립된 메모리 공간을 가진다

코드, 데이터, 힙, 스택으로 나뉜 이 구조를 통해 운영체제는 프로세스가 실행되는 데 필요한 메모리를 역할별로 체계적으로 관리한다.

여기서 반드시 짚고 넘어가야 할 점이 하나 있다. 각 프로세스는 이 메모리 구조를 서로 독립적으로 가진다는 것이다. 프로세스 A의 메모리 공간과 프로세스 B의 메모리 공간은 서로 직접 접근하거나 섞이지 않는다. 덕분에 한 프로세스에서 문제가 생기더라도 다른 프로세스에 영향을 주지 않도록 보호된다. 이 독립성이 운영체제가 여러 프로세스를 안정적으로 동시에 관리할 수 있는 중요한 기반이 된다.


The Memory Layout of a Process

When a process is loaded into memory, the data isn't all jumbled together. The data needed for execution is divided into four regions based on its nature and laid out systematically.

Looking at the structure from the bottom up, the code region sits at the very bottom, followed above it by the data region, then the heap region, with the stack region at the very top.

The code region holds the program instructions that the CPU reads and executes one by one. The data region holds data used broadly throughout the program, such as global and static variables. The heap region is for dynamically allocated memory during execution - for instance, when char *cp = malloc(10000) requests memory mid-run, this region is used. The stack region stores local variables and parameters declared inside functions - things like float f or int i belong here.

One thing worth noting in this structure is that the heap and stack expand toward each other. The heap grows upward as more dynamic allocations occur; the stack grows downward as function calls accumulate. The empty space between them serves as a buffer to accommodate this expansion.

The reason memory is divided into regions like this is simple: to keep data of different natures clearly separated so they don't get mixed together during execution. The operating system allocates and manages memory for each process based on exactly this structure.

프로세스의 메모리 배치 구조

프로세스가 메모리에 올라갈 때, 모든 데이터가 한데 뒤섞여 저장되는 것이 아니다. 실행에 필요한 데이터들은 그 성격에 따라 네 개의 영역으로 나뉘어 체계적으로 배치된다.

구조를 아래에서부터 살펴보면, 가장 아래에 코드 영역이 위치하고 그 위로 데이터 영역, 힙 영역, 그리고 가장 위에 스택 영역이 자리한다.

코드 영역에는 CPU가 하나씩 읽어 실행할 프로그램 명령어들이 저장된다. 데이터 영역에는 전역변수나 정적 변수처럼 프로그램 전반에 걸쳐 사용되는 데이터들이 올라간다. 힙 영역은 실행 중에 동적으로 할당되는 메모리 공간으로, char *cp = malloc(10000)처럼 실행 도중 메모리를 요청하는 경우 이 영역이 사용된다. 스택 영역에는 함수 안에서 선언된 지역변수와 매개변수가 저장되는데, float fint i 같은 지역변수가 여기에 해당한다.

이 구조에서 한 가지 눈여겨볼 점은 힙과 스택이 서로를 향해 확장된다는 것이다. 힙은 동적 할당이 늘어날수록 위쪽으로 커지고, 스택은 함수 호출이 쌓일수록 아래쪽으로 확장된다. 두 영역 사이의 빈 공간이 이 확장을 수용하는 여유분 역할을 한다.

이렇게 메모리를 영역별로 구분해서 사용하는 이유는 하나다. 프로세스가 실행되는 동안 성격이 서로 다른 데이터들이 뒤섞이지 않도록 명확히 분리해서 관리하기 위해서다. 운영체제는 바로 이 구조를 기준으로 각 프로세스에 메모리를 할당하고 관리한다.


How Is a Process Created?

A process doesn't spontaneously appear out of nowhere. The entity that creates a new process is always an already-running existing process.

The process that creates a new one is called the parent process; the newly created one is called the child process. A child process, once running, can itself spawn further children. As this relationship repeats, processes form a tree-shaped hierarchical structure.

The operating system performs two roles simultaneously in this process: allocating an independent memory space for the new child process, and completing the preparation work so it can begin executing immediately. From the moment a child process is created, it becomes an independent execution unit with its own memory space.

At this stage, there's no need to dig deep into system calls or specific implementation details. The two core ideas are: processes are created by existing processes, and that relationship forms a parent-child hierarchy. Keeping this concept in mind provides a solid foundation for understanding topics like scheduling and inter-process communication that come later.

프로세스는 어떻게 만들어질까?

프로세스는 갑자기 혼자 생겨나지 않는다. 새로운 프로세스를 만들어내는 주체는 항상 이미 실행 중인 기존의 프로세스다.

이때 새로운 프로세스를 만들어낸 쪽을 부모 프로세스, 그로 인해 새롭게 생성된 쪽을 자식 프로세스라고 부른다. 자식 프로세스 역시 실행되고 나면 또 다른 자식을 낳을 수 있다. 이런 관계가 반복되면서 프로세스들은 나무 모양의 계층 구조를 이루게 된다.

운영체제는 이 과정에서 두 가지 역할을 함께 수행한다. 새로운 자식 프로세스를 위한 독립된 메모리 공간을 할당하고, 곧바로 실행에 들어갈 수 있도록 준비 작업을 마친다. 자식 프로세스는 생성되는 순간부터 자신만의 메모리 공간을 가지는 독립적인 실행 단위가 된다.

지금 단계에서 시스템 호출이나 구체적인 구현 방식까지 깊이 파고들 필요는 없다. 핵심은 두 가지다. 프로세스는 기존 프로세스에 의해 생성된다는 것, 그리고 그 관계는 부모와 자식이라는 계층 구조로 이어진다는 것이다. 이 개념을 머릿속에 잡아두면, 이후에 배울 스케줄링이나 프로세스 간 통신 같은 주제들을 이해하는 데 든든한 발판이 된다.


The Process Tree and the Parent-Child Relationship

Given that parent-child relationships are formed, processes don't exist as scattered, independent entities - they form a hierarchical tree structure. Every process in this tree has some parent process, and ultimately all processes derive from a single root process at the top.

There's an important point here: changes in a parent process's state can affect its child processes. In particular, when a parent process terminates, how its child processes are handled is determined by the operating system's policy.

For reference, in a Linux environment, a process called init serves as the starting point for all processes. No matter which running process you trace upward through its parents, you'll eventually reach init.

Understanding this parent-child relationship makes it easier to naturally connect to topics you'll encounter later, such as process termination and resource cleanup.

프로세스 트리 구조와 부모-자식 관계

부모와 자식 관계가 만들어진다고 했는데, 이 관계를 기준으로 보면 프로세스들은 서로 독립적으로 흩어져 있는 것이 아니라 계층적인 트리 구조를 이룬다. 모든 프로세스는 이 트리 안에서 반드시 어떤 부모 프로세스를 가지고 있으며, 최종적으로는 하나의 최상위 프로세스로부터 파생된다.

여기서 한 가지 중요한 점이 있다. 부모 프로세스의 상태 변화가 자식 프로세스에도 영향을 줄 수 있다는 것이다. 특히 부모 프로세스가 종료되는 경우, 자식 프로세스를 어떻게 처리할지는 운영체제의 정책에 따라 결정된다.

참고로 리눅스 환경에서는 init이라는 프로세스가 모든 프로세스의 시작점 역할을 한다. 실행 중인 어떤 프로세스든 부모를 따라 트리를 거슬러 올라가다 보면 결국 이 init 프로세스와 연결된다.

이렇게 부모와 자식 간의 관계를 이해해두면, 이후에 다루게 될 프로세스 종료나 자원 정리 같은 내용들도 자연스럽게 연결지어 이해할 수 있게 된다.


The Process Tree: A Hierarchy Starting from init

At the top of the process tree in a Linux environment sits init - the process that holds PID 1. This is the very first process to run on the system, and all processes ultimately derive from it.

Below it in the hierarchy sits the Shell process. When a user logs into the system, a shell process is created for that user. The shell receives commands the user types and passes them to the operating system - from the user's perspective, it's the launching point that starts program execution.

Programs like web browsers, chat applications, media players, and text editors are all created as child processes with the shell as their parent. Rather than each user-run program appearing independently out of nowhere, they are created hierarchically through the shell, within a parent-child relationship.

In this way, processes are managed within a tree-structured hierarchy. No matter which process you trace upward, it eventually connects to init. Understanding this entire flow as a single picture makes later topics - process termination, resource cleanup - much easier to connect and comprehend.

프로세스 트리, init에서 시작되는 계층 구조

리눅스 환경에서 프로세스 트리의 최상위에는 init이라는 프로세스가 있다. PID 1번을 가지는 이 프로세스가 시스템에서 가장 먼저 실행되는 출발점으로, 모든 프로세스는 결국 이 init으로부터 파생된다.

그 아래 계층에는 쉘(Shell) 프로세스가 위치한다. 사용자가 시스템에 로그인하면 해당 사용자를 위한 쉘 프로세스가 생성된다. 쉘은 사용자가 입력한 명령어를 받아 운영체제에 전달해주는 역할을 하는데, 사용자 입장에서는 프로그램을 실행할 때 그 실행을 시작해주는 출발점이라고 보면 된다.

웹브라우저, 채팅, 미디어플레이어, 편집기 같은 프로그램들은 모두 이 쉘을 부모로 두는 자식 프로세스로 생성된다. 사용자가 실행하는 프로그램들이 각각 독립적으로 뚝 생겨나는 것이 아니라, 쉘이라는 프로세스를 거쳐 부모-자식 관계 속에서 계층적으로 만들어진다는 뜻이다.

이처럼 프로세스는 트리 구조의 계층 관계 안에서 관리된다. 어떤 프로세스든 부모를 따라 위로 거슬러 올라가다 보면 결국 init과 연결된다. 이 전체 흐름을 하나의 그림으로 이해해두면, 이후에 배울 프로세스 종료나 자원 정리 같은 내용들도 자연스럽게 연결지어 이해할 수 있게 된다.


From Execution Request to Actual Execution

When a user runs a program, several steps proceed in sequence inside the operating system. Let's trace the flow from the moment an icon is clicked or a command is entered to the point where the program actually starts running.

First, when the user's execution request comes in, the operating system creates a new process via a system call. The representative system call function used here is fork(). Through this call, a new child process is born from an existing process.

Once the process is created, the operating system allocates the memory space that process needs to run. The code, data, heap, and stack regions discussed earlier are each secured at this point. After memory allocation, the preparation work of initializing CPU state and execution position follows. Only when this preparation is complete can the process receive CPU time and move into the actual execution phase.

At this stage, there's no need to memorize functions like fork() or the specifics of implementation. What matters is holding the overall flow; execution request → process creation → memory allocation → execution preparation; as a single picture in your mind. Understanding this flow makes subsequent topics like scheduling and process management connect much more naturally.

프로그램 실행 요청부터 실제 실행까지

사용자가 프로그램을 실행하면 운영체제 내부에서는 여러 단계가 순서대로 진행된다. 아이콘을 클릭하거나 명령어를 입력하는 순간부터 실제로 프로그램이 동작하기까지의 흐름을 따라가 보자.

먼저 사용자의 실행 요청이 들어오면 운영체제는 시스템 호출을 통해 새로운 프로세스를 생성한다. 이때 사용되는 대표적인 시스템 호출 함수가 fork()다. 이 호출을 통해 기존 프로세스로부터 새로운 자식 프로세스가 만들어진다.

프로세스가 생성되고 나면 운영체제는 그 프로세스가 실행되는 데 필요한 메모리 공간을 할당한다. 앞서 살펴본 코드, 데이터, 힙, 스택 영역이 이 시점에 각각 확보된다. 메모리 할당이 끝나면 CPU 상태와 실행 위치를 초기화하는 실행 준비 작업이 이어진다. 이 준비가 완료되어야 비로소 프로세스가 CPU를 할당받아 실제 실행 단계로 넘어갈 수 있다.

이 단계에서 fork() 같은 함수나 세부 구현 방식을 외우려 할 필요는 없다. 중요한 것은 프로그램 실행 요청 → 프로세스 생성 → 메모리 할당 → 실행 준비라는 전체 흐름을 하나의 그림으로 머릿속에 담아두는 것이다. 이 흐름을 이해하고 있으면 이후에 배울 스케줄링이나 프로세스 관리 같은 내용들도 훨씬 자연스럽게 연결된다.


3.Condition of Process

The State Changes of a Process

A process doesn't stay in the same state from beginning to end once it's created. It transitions through several states as it runs. Let's trace that flow in order.

The very first is the New state. This is the phase where the process is just being created and is not yet ready to execute.

Once creation is complete, it moves to the Ready state. All preparations for execution are done, but the process hasn't been allocated CPU time yet - it's waiting its turn. It's capable of running, but is waiting because no CPU is available.

When a Ready process is allocated CPU time, it becomes Running. This is the state where instructions are actually being executed. At any given moment, generally only one process can be running on a single CPU. A process can be sent back to Ready if it loses the CPU.

If a situation arises during Running where the process must wait for an external event - like an I/O operation - it transitions to the Waiting state. In this state the process doesn't use the CPU, and once the awaited event completes, it returns to Ready to wait for CPU allocation again.

Finally, when execution is entirely complete, the process reaches the Terminated state. This is where the process's lifecycle ends.

Ultimately, a process operates by continuously changing states within the flow of creation → ready → running → waiting → termination. Understanding this flow of state changes makes it much more natural to later understand how scheduling decides which process to choose at which moment.

프로세스의 상태 변화

프로세스는 한번 생성되면 처음부터 끝까지 같은 상태로 머무는 것이 아니다. 실행되는 동안 여러 상태를 오가며 변화한다. 이 흐름을 순서대로 따라가 보자.

가장 처음은 New 상태다. 프로세스가 막 생성되고 있는 단계로, 아직 실행 준비가 완료되지 않은 상태를 의미한다.

생성이 완료되면 Ready 상태로 넘어간다. 실행에 필요한 준비는 모두 끝났지만 아직 CPU를 할당받지 못해 차례를 기다리는 상태다. 실행 가능한 상태이지만 CPU가 없어서 대기 중인 것이다.

Ready 상태의 프로세스가 CPU를 할당받으면 Running 상태가 된다. 실제로 명령어가 수행되는 상태로, 일반적으로 한 시점에 CPU 하나에서 실행될 수 있는 프로세스는 단 하나뿐이다. 실행 중에 CPU를 빼앗기면 다시 Ready 상태로 돌아가기도 한다.

Running 도중 입출력 작업처럼 외부 사건을 기다려야 하는 상황이 생기면 Waiting 상태로 전환된다. 이 상태에서는 CPU를 사용하지 않으며, 기다리던 사건이 완료되면 다시 Ready 상태로 돌아가 CPU 할당을 기다린다.

마지막으로 실행이 모두 끝나면 Terminated 상태가 된다. 프로세스의 생명 주기가 마무리되는 단계다.

결국 프로세스는 생성 → 준비 → 실행 → 대기 → 종료라는 일련의 흐름 속에서 상태를 끊임없이 바꾸며 동작한다. 이 상태 변화의 흐름을 이해해두면, 이후에 배울 스케줄링이 어떤 시점에 어떤 프로세스를 선택하는지도 훨씬 자연스럽게 이해할 수 있게 된다.


The Flow of Process State Transitions

Let's trace which states a process passes through during execution and what triggers each state change.

프로세스 상태 변화의 흐름

프로세스가 실행되는 동안 어떤 상태를 거치고, 어떤 계기로 상태가 바뀌는지 흐름을 따라가 보자.


Basic Flow

Most simply: after being created, a process waits in a non-running state, then is dispatched and moves to the running state when it receives CPU time. When execution ends, it moves to the terminated state. However, if an interrupt occurs during execution, it can return to the non-running state. This is the most basic skeleton of process state changes.

기본 흐름

가장 단순하게 보면, 프로세스는 생성된 후 비실행 상태에 머물다가 CPU를 할당받는 순간 디스패치되어 실행 상태로 넘어간다. 실행이 끝나면 종료 상태로 이동한다. 단, 실행 중에 인터럽트가 발생하면 다시 비실행 상태로 돌아갈 수 있다. 이것이 프로세스 상태 변화의 가장 기본적인 골격이다.


Detailed State Changes

In practice, when the operating system manages processes, this flow is more granular.

After a process is created, rather than immediately receiving CPU time, it first enters the ready state. The ready state means all preparations for execution are done but the process is still waiting for CPU allocation. The process by which the operating system selects one process from those in the ready state and hands it the CPU is called a dispatch. The dispatch is precisely the transition action that occurs when moving from ready to running.

A process in the running state can be sent back to ready depending on circumstances - for example, when its allocated time runs out, or when a higher-priority process needs the CPU. Meanwhile, if a situation requiring the process to wait for an external event (such as an I/O request) arises during execution, the process moves to the waiting state. It doesn't use the CPU while waiting, and once the awaited event completes, it returns to ready and waits for CPU allocation again.

After repeating this cycle, once execution is fully complete, the process moves to the terminated state.

The two key points here are: processes don't proceed in just one direction - they repeatedly cycle through ready, running, and waiting states depending on circumstances - and at the center of that flow is the dispatch, the transition from ready to running.

구체적인 상태 변화

실제로 운영체제가 프로세스를 관리할 때는 이 흐름이 좀 더 세분화된다.

프로세스가 생성되면 곧바로 CPU를 받는 것이 아니라 먼저 준비 상태로 들어간다. 준비 상태는 실행에 필요한 모든 준비는 끝났지만 CPU를 아직 할당받지 못해 차례를 기다리는 상태다. 여기서 운영체제가 실행할 프로세스를 하나 골라 CPU를 넘겨주는 과정을 디스패치라고 한다. 준비에서 실행으로 넘어가는 바로 그 전환 동작이 디스패치다.

실행 상태에 들어간 프로세스는 상황에 따라 다시 준비 상태로 돌아올 수 있다. 할당된 시간이 끝났거나, 더 우선순위가 높은 프로세스에게 CPU를 넘겨줘야 할 경우가 그 예다. 한편 실행 중에 입출력 요청처럼 외부 사건을 기다려야 하는 상황이 생기면 프로세스는 대기 상태로 이동한다. 대기 상태에서는 CPU를 사용하지 않으며, 기다리던 사건이 완료되면 다시 준비 상태로 돌아와 CPU 할당을 기다린다.

이 과정을 반복하다가 실행이 모두 끝나면 프로세스는 최종적으로 종료 상태로 이동한다.

여기서 핵심은 두 가지다. 프로세스는 한 방향으로만 쭉 진행되는 것이 아니라 상황에 따라 준비, 실행, 대기를 반복해서 오간다는 것, 그리고 그 흐름의 중심에 준비에서 실행으로 전환되는 디스패치라는 과정이 있다는 것이다.


2️⃣ Process State and Management

1. Process State Changes

Why Process State Changes Are Necessary

Although it looks like multiple programs are running simultaneously on a computer, the CPU can only execute one process at a time. Because of this constraint, the operating system must continuously make important decisions: which process to run right now, and when to stop the currently running process and switch to another.

Throughout this process, a process doesn't stay only in the running state - it is managed by moving back and forth between the ready and waiting states. The flow that emerges as the operating system switches and manages processes is called process state change.

This concept goes beyond simply describing what state a process is in. It is a fundamental prerequisite for understanding process management techniques like scheduling and synchronization that we'll explore later, which is why it's important to get a firm grasp of this flow now.

프로세스 상태 변화의 필요성

컴퓨터에서 여러 프로그램이 동시에 실행되는 것처럼 보이지만, CPU는 한 순간에 단 하나의 프로세스만 실행할 수 있다. 이 제약이 있기 때문에 운영체제는 끊임없이 중요한 판단을 내려야 한다. 지금 어떤 프로세스를 실행할 것인지, 언제 실행 중인 프로세스를 멈추고 다른 프로세스로 전환할 것인지를 계속해서 결정해야 하는 것이다.

이 과정에서 프로세스는 실행 상태에만 머무는 것이 아니라 준비와 대기 상태를 오가며 관리된다. 운영체제가 프로세스를 전환하고 관리하는 과정에서 나타나는 이러한 흐름을 프로세스의 상태 변화라고 한다.

이 개념은 단순히 프로세스가 어떤 상태에 있는지를 설명하는 데서 그치지 않는다. 앞으로 살펴볼 스케줄링이나 동기화 같은 프로세스 관리 기법들을 이해하기 위한 기본 전제가 되는 개념이기 때문에, 지금 이 흐름을 확실히 잡아두는 것이 중요하다.


What Would Happen Without Process State Changes?

Without process state changes, the operating system would be unable to properly distinguish between multiple processes. If there's no information about which process should be running right now and which is waiting, there's no way to decide who should receive the CPU.

Ultimately, process state changes don't merely display a process's current situation. They serve as the management standard that allows multiple processes to share the CPU and use system resources efficiently.

From this perspective, process state change can be understood as the most fundamental management mechanism the operating system uses to reclaim and reallocate the CPU. The reason the operating system appears to handle multiple processes simultaneously is precisely because it manages them by rapidly switching the CPU based on these state changes.

프로세스 상태 변화가 없다면 어떻게 될까?

만약 프로세스의 상태 변화가 없다면 운영체제는 여러 프로세스를 제대로 구분할 수 없게 된다. 어떤 프로세스가 지금 실행되어야 하는지, 어떤 프로세스가 대기 중인지에 대한 정보가 없으면 CPU를 누구에게 할당해야 할지 결정할 수 없기 때문이다.

결국 프로세스의 상태 변화는 단순히 프로세스의 현재 상황을 표시하는 것에 그치지 않는다. 여러 프로세스가 CPU를 나눠 쓸 수 있도록 하고, 시스템 자원을 효율적으로 사용하기 위한 관리 기준이 된다.

이 관점에서 보면 프로세스의 상태 변화는 운영체제가 CPU를 회수하고 다시 할당하기 위해 사용하는 가장 기본적인 관리 방식이라고 이해할 수 있다. 운영체제가 여러 프로세스를 동시에 다루는 것처럼 보이는 것도, 결국 이 상태 변화를 기반으로 CPU를 빠르게 전환하며 관리하기 때문에 가능한 일이다.


The Operating System Decides State Changes

Process state changes don't occur arbitrarily at any random time. A state change means the operating system is intervening in the execution flow, and this intervention only happens at specific moments.

In other words, a process does not change its own state. State changes occur only at the moment the operating system determines that intervention is necessary. There's one important point here: state changes take place exclusively in kernel mode. User programs never directly change their own state.

This means the operating system holds complete authority over process management. From the process's perspective, it cannot decide on its own when to run or when to stop — all such decisions are made by the operating system in kernel mode. This structure allows multiple processes to share the CPU in an orderly fashion and prevents any single process from monopolizing or destabilizing the entire system.

상태 변화는 운영체제가 결정한다

프로세스의 상태 변화는 아무 때나 임의로 발생하는 것이 아니다. 상태가 바뀐다는 것은 곧 운영체제가 실행 흐름에 개입하겠다는 의미이며, 이 개입은 항상 특정 시점에서만 일어난다.

다시 말해 프로세스가 스스로 자신의 상태를 바꾸는 것이 아니다. 운영체제가 개입이 필요하다고 판단한 순간에 비로소 상태 변화가 일어난다. 여기서 한 가지 중요한 점이 있다. 이 상태 변화는 반드시 커널 모드에서만 이루어진다는 것이다. 사용자 프로그램이 직접 자신의 상태를 바꾸는 일은 없다.

이 사실은 운영체제가 프로세스 관리의 주도권을 완전히 쥐고 있다는 것을 의미한다. 프로세스 입장에서는 언제 실행되고 언제 멈출지를 스스로 결정할 수 없으며, 그 모든 판단은 운영체제가 커널 모드에서 내린다. 이 구조 덕분에 여러 프로세스가 질서 있게 CPU를 나눠 쓸 수 있고, 한 프로세스가 시스템 전체를 독점하거나 불안정하게 만드는 상황을 막을 수 있다.


When Does the Operating System Intervene?

There are specific triggers that cause the operating system to change a process's state. The most representative cases are as follows.

The first is when a process is allocated CPU time. When a process that was waiting in the ready state receives the CPU, the operating system transitions it to the running state.

The second is when a process has used up its entire allocated CPU time. The operating system limits how long each process can hold the CPU; when that time expires, it stops the running process and switches to another.

The third is when a process requests an I/O operation, or when a previously requested I/O operation completes. When an I/O request comes in, the process is transitioned to the waiting state; when the operation completes, it returns to the ready state. The operating system intervenes directly to change the state at both of these moments.

Ultimately, based on these three triggers, a process cycles repeatedly through the running, ready, and waiting states. Through this flow, the operating system coordinates multiple processes so they can share the CPU in an orderly manner.

운영체제는 어떤 순간에 개입할까?

운영체제가 프로세스의 상태를 변경하는 데는 특정한 계기가 있다. 대표적인 경우를 살펴보면 다음과 같다.

첫 번째는 프로세스가 CPU를 할당받는 순간이다. 준비 상태에서 기다리던 프로세스가 CPU를 받게 되면 운영체제는 해당 프로세스를 실행 상태로 전환한다.

두 번째는 프로세스가 할당된 CPU 사용 시간을 모두 소진했을 때다. 운영체제는 각 프로세스가 CPU를 무한정 점유하지 못하도록 사용 시간을 제한하는데, 그 시간이 끝나면 실행 중인 프로세스를 멈추고 다른 프로세스로 전환한다.

세 번째는 프로세스가 입출력 작업을 요청하거나, 요청했던 입출력 작업이 완료되었을 때다. 입출력 요청이 들어오면 해당 프로세스는 대기 상태로 전환되고, 작업이 완료되면 다시 준비 상태로 돌아온다. 이 두 시점 모두 운영체제가 직접 개입해 상태를 변경한다.

결국 이 세 가지 시점을 기준으로 프로세스는 실행, 준비, 대기 상태를 반복해서 오가게 된다. 운영체제는 이 흐름을 통해 여러 프로세스가 CPU를 질서 있게 나눠 쓸 수 있도록 조율하는 것이다.


Terms for State Transitions

We've already looked at the flow of process state changes. Now let's define the terms used to describe each specific moment of transition. Dispatch and timeout are expressions that refer to the concrete moments when the operating system intervenes and a process's state changes.

Dispatch refers to the moment the operating system selects one process from those in the ready state and transitions it to the running state. Simply put, it's the exact moment the operating system decides "I'm going to run this process now." Dispatch is the transition point from ready to running.

Timeout is the transition in the opposite direction. When a running process has exhausted its allocated CPU time, the operating system sends that process back to the ready state. The transition that occurs at this moment is called a timeout — that is, time expiration.

Looking at the two terms together makes the flow clear. Dispatch takes a process from ready to running; timeout brings it back from running to ready. The operating system coordinates multiple processes taking turns with the CPU by repeating these two transitions.

상태 전환을 표현하는 용어

프로세스의 상태가 바뀌는 흐름은 앞서 살펴봤다. 여기서는 그 상태가 바뀌는 각각의 순간을 부르는 용어를 정리해보자. 디스패치와 타임아웃은 운영체제가 개입해서 프로세스의 상태가 전환되는 구체적인 순간을 가리키는 표현이다.

디스패치는 운영체제가 준비 상태에 있는 프로세스 중 하나를 골라 실행 상태로 전환하는 순간을 가리킨다. 쉽게 말해 운영체제가 "이 프로세스를 이제 실행하겠다"고 결정하는 바로 그 순간이다. 준비 상태에서 실행 상태로 넘어가는 전환점이 디스패치다.

타임아웃은 그 반대 방향의 전환이다. 실행 중인 프로세스가 할당된 CPU 사용 시간을 모두 소진하면, 운영체제는 해당 프로세스를 다시 준비 상태로 돌려보낸다. 이때 발생하는 전환을 타임아웃, 즉 시간 종료라고 한다.

두 용어를 함께 놓고 보면 흐름이 명확해진다. 디스패치는 준비에서 실행으로, 타임아웃은 실행에서 준비로 돌아오는 전환이다. 운영체제는 이 두 가지 전환을 반복하면서 여러 프로세스가 CPU를 번갈아 사용할 수 있도록 조율한다.


Block and Wakeup

While dispatch and timeout were terms for transitions between the ready and running states, let's now look at the transition terms related to the waiting state.

Block refers to the moment a running process transitions to the waiting state — when it requests an I/O operation or must wait for a specific event. The process has reached a point where it can no longer proceed on its own, and the operating system places it in the waiting state. While in this waiting state, the process does not use the CPU.

Wakeup is the opposite. When the awaited I/O operation completes or the specific event is resolved, the operating system wakes up the waiting process and moves it to the ready state. One important note: a wakeup does not immediately put the process into the running state. It returns to the ready state and goes through the normal process of waiting for CPU allocation.

With all four transition terms together, the entire flow of process state changes becomes clear at a glance. Execution begins with a dispatch, returns to ready via a timeout, enters waiting via a block, and comes back to ready via a wakeup. The operating system continuously coordinates the execution of multiple processes through these four transitions.

Block과 Wakeup

앞서 디스패치와 타임아웃이 준비와 실행 사이의 전환을 표현하는 용어였다면, 이번에는 대기 상태와 관련된 전환 용어를 살펴보자.

Block은 실행 중인 프로세스가 입출력 작업을 요청하거나 특정 사건을 기다려야 하는 경우, 실행 상태에서 대기 상태로 전환되는 순간을 가리킨다. 프로세스가 스스로 더 이상 진행할 수 없는 상황이 되어 운영체제에 의해 대기 상태로 보내지는 것이다. 이 대기 상태에 머무는 동안에는 CPU를 사용하지 않는다.

Wakeup은 그 반대다. 기다리던 입출력 작업이 완료되거나 특정 사건이 해결되면, 운영체제는 대기 중이던 프로세스를 다시 깨워 준비 상태로 이동시킨다. 이 전환을 Wakeup이라고 한다. 주의할 점은 Wakeup이 된다고 해서 곧바로 실행 상태로 가는 것이 아니라는 점이다. 다시 준비 상태로 돌아와 CPU 할당을 기다리는 순서를 밟게 된다.

이제 네 가지 전환 용어를 함께 놓고 보면 프로세스 상태 변화의 전체 흐름이 한눈에 정리된다. 디스패치로 실행이 시작되고, 타임아웃으로 준비 상태로 돌아오며, Block으로 대기에 들어가고, Wakeup으로 다시 준비 상태로 복귀한다. 운영체제는 이 네 가지 전환을 통해 여러 프로세스의 실행을 끊임없이 조율한다.


2. Process Control Block (PCB)

We've seen which states a process passes through and what triggers each state change. Thinking carefully about this process, a natural question arises.

For the operating system to manage processes, it needs various information about each one. It needs to know what state a process is currently in — whether it's Ready, Running, or Waiting. It also needs to know where to resume execution when a process that was interrupted starts running again, as well as information about how that process had been using the CPU.

So where exactly is all this information stored? Let's find the answer in what follows.

2. 프로세스 제어 블록(PCB)

지금까지 프로세스가 어떤 상태를 거치고, 어떤 계기로 상태가 바뀌는지 살펴봤다. 그런데 이 과정을 곰곰이 생각해보면 자연스럽게 한 가지 의문이 생긴다.

운영체제가 프로세스를 관리하려면 각 프로세스에 대한 여러 정보가 필요하다. 지금 이 프로세스가 어떤 상태에 있는지, 즉 Ready인지 Running인지 Waiting인지를 알아야 한다. 실행이 중단되었다가 다시 재개될 때 어디서부터 이어서 실행해야 하는지도 알아야 한다. 그 프로세스가 CPU를 어떻게 사용하고 있었는지와 같은 정보도 필요하다.

그렇다면 이런 정보들은 과연 어디에 저장되는 걸까? 다음 내용에서 이 질문의 답을 찾아보자.


What Is a PCB?

The answer to the question posed earlier; where is information about processes stored?; is the Process Control Block (PCB).

The operating system uses a data structure called a PCB to manage each process's state and execution information separately. Each time a process is created, a corresponding PCB is created for it, and this PCB is stored in the kernel area.

One important point to note here: a PCB is a management data structure that contains only the information needed to manage a process's execution. The actual execution content — the process's code, data, and stack — is not included in the PCB. That content exists separately in the process's memory space: the code region, data region, heap, and stack, as we saw earlier.

As an analogy, a PCB is like a management card recording information about a process. Rather than containing actual execution content, it's a structure that gathers only the information the operating system needs to manage and control that process.

PCB란 무엇인가?

앞서 던진 질문, 프로세스에 관한 정보는 어디에 저장되는가에 대한 답이 바로 프로세스 제어 블록(PCB, Process Control Block) 이다.

운영체제는 프로세스의 상태와 실행 정보를 프로세스마다 따로 관리하기 위해 PCB라는 자료구조를 사용한다. 프로세스가 하나 생성될 때마다 그에 대응하는 PCB가 하나씩 만들어지며, 이 PCB는 커널 영역에 저장된다.

여기서 한 가지 중요한 점을 짚고 넘어가야 한다. PCB는 프로세스의 실행을 관리하기 위한 정보만 담고 있는 관리용 자료구조라는 것이다. 프로세스의 코드, 데이터, 스택처럼 실행 내용 자체는 PCB에 포함되지 않는다. 그 내용들은 앞서 살펴본 것처럼 프로세스의 메모리 공간인 코드 영역, 데이터 영역, 힙, 스택에 따로 존재한다.

쉽게 비유하자면 PCB는 프로세스에 대한 정보를 기록해둔 관리 카드 같은 것이다. 실제 실행 내용이 담긴 것이 아니라, 운영체제가 그 프로세스를 관리하고 제어하기 위해 필요한 정보들만 모아둔 구조체다.


The Relationship Between a Process and Its PCB

Each time a process is created, a corresponding PCB is created in the kernel area. When Process A is created, PCB A is created; when Process B is created, PCB B is created. There are as many PCBs in the kernel area as there are running processes.

The key point here is that the operating system doesn't handle processes directly — it manages each process's state and execution information through its PCB. From the operating system's perspective, the PCB is what represents a process. All judgments about which process to run next, what state it's currently in, and how it had been using the CPU are made based on the information contained in the PCB.

Simply put, a process is the actual entity running in memory, while the PCB is the information portal the operating system consults to manage that process. The operating system looks at the PCB before the process itself to understand and control it.

프로세스와 PCB의 관계

프로세스가 생성될 때마다 그에 대응하는 PCB가 커널 영역에 하나씩 만들어진다. 프로세스 A가 생성되면 PCB A가, 프로세스 B가 생성되면 PCB B가 만들어지는 식이다. 실행 중인 프로세스의 수만큼 PCB가 커널 영역에 존재하게 된다.

여기서 중요한 점은 운영체제가 프로세스를 직접 다루는 것이 아니라 PCB를 통해 프로세스의 상태와 실행 정보를 관리한다는 것이다. 운영체제 입장에서는 PCB가 곧 프로세스를 대표하는 존재다. 어떤 프로세스를 다음에 실행할지, 지금 어떤 상태에 있는지, CPU를 어떻게 사용하고 있었는지와 같은 판단을 모두 PCB에 담긴 정보를 기준으로 내린다.

쉽게 말해 프로세스는 메모리에서 실제로 실행되는 실체이고, PCB는 운영체제가 그 프로세스를 관리하기 위해 참조하는 정보 창구라고 볼 수 있다. 운영체제는 프로세스 자체보다 PCB를 먼저 들여다보며 프로세스를 파악하고 제어한다.


Information Stored in a PCB

A PCB contains various pieces of information the operating system needs to manage a process. Let's look at the main items.

First is the process identifier. A unique number identifying each process — the PID (Process ID) — is stored here. The operating system uses this identifier to distinguish and manage multiple processes.

Next is the process state. The PCB records which state the process is currently in: created, ready, running, waiting, or terminated. The operating system uses this information to determine which process to run and which is waiting.

One of the most important items is the Program Counter. When a process is interrupted during execution, the system needs to know where to resume when it runs again. The program counter stores the address of the next instruction to be executed. Because this value is stored, when a process resumes after being interrupted, it doesn't restart from the beginning — it continues precisely from where it left off.

PCB에 저장되는 정보

PCB에는 운영체제가 프로세스를 관리하는 데 필요한 여러 정보들이 담겨 있다. 대표적인 항목들을 살펴보자.

먼저 프로세스 식별자다. 각 프로세스를 구분하기 위한 고유한 숫자, 즉 PID(Process ID)가 저장된다. 운영체제는 이 식별자를 기준으로 여러 프로세스를 서로 구별하고 관리한다.

다음은 프로세스 상태다. 현재 이 프로세스가 생성, 준비, 실행, 대기, 종료 중 어떤 상태에 있는지가 PCB에 기록된다. 운영체제는 이 정보를 보고 어떤 프로세스를 실행할지, 어떤 프로세스가 대기 중인지를 판단한다.

그리고 가장 중요한 항목 중 하나가 프로그램 카운터(Program Counter) 다. 프로세스가 실행되다가 중단되면, 다음에 다시 실행될 때 어디서부터 이어서 실행해야 하는지를 알아야 한다. 프로그램 카운터는 바로 그 다음에 실행해야 할 명령어의 주소를 저장해두는 값이다. 이 값이 있기 때문에 프로세스가 중단되었다가 재개될 때 처음부터 다시 시작하는 것이 아니라 멈췄던 지점에서 정확히 이어서 실행할 수 있다.


Information Stored in a PCB (Continued)

Beyond the identifier, state, and program counter, a few more important pieces of information are stored in the PCB.

Register values. While a process is running, the CPU's registers hold various values needed for the current computation. When an interrupt occurs or a timeout stops the process, the register values at that moment are saved in the PCB. When the process runs again later, those saved register values are restored exactly, allowing it to return to precisely the state it was in before it was interrupted. Because both the program counter and register values are saved together, a process can resume as if it had never been interrupted.

Scheduling-related information is also included in the PCB. This includes the process's priority, its position in the scheduling queue, and other parameters needed for scheduling decisions. The operating system uses this information to determine which process to run next.

Ultimately, the PCB serves as the repository for all information needed to fully restore a process to its previous state when it resumes after being interrupted; losing nothing in the process.

PCB에 저장되는 정보 (계속)

앞서 살펴본 식별자, 상태, 프로그램 카운터 외에도 PCB에는 몇 가지 중요한 정보가 더 저장된다.

레지스터 값이다. 프로세스가 실행되는 동안 CPU의 레지스터에는 현재 연산에 필요한 여러 값들이 담겨 있다. 그런데 인터럽트가 발생하거나 타임아웃으로 프로세스가 중단되면, 그 순간 레지스터에 있던 값들을 PCB에 저장해둔다. 이후 해당 프로세스가 다시 실행될 때 PCB에 저장해둔 레지스터 값을 그대로 복원함으로써, 중단되기 직전의 상태로 정확히 되돌아갈 수 있다. 프로그램 카운터와 레지스터 값이 함께 저장되기 때문에 프로세스는 마치 중단된 적이 없었던 것처럼 이어서 실행될 수 있는 것이다.

스케줄링 관련 정보도 PCB에 포함된다. 이 프로세스의 우선순위가 얼마인지, 스케줄링 큐에서 어느 위치에 있는지, 그 밖에 스케줄링 결정에 필요한 매개변수들이 여기에 저장된다. 운영체제는 이 정보를 바탕으로 다음에 어떤 프로세스를 실행할지 판단한다.

결국 PCB는 프로세스가 중단되었다가 재개될 때 아무것도 잃지 않고 이전 상태로 완전히 복구될 수 있도록 필요한 모든 정보를 보관하는 역할을 한다.


Information Stored in a PCB (Wrap-up)

Beyond the information already covered, a few more items are included in the PCB.

Accounting information. Records of how much CPU time the process has used, its actual usage time, the maximum allowed time, and similar resource usage details are recorded here. Identifying information such as account number and job number is also included.

I/O status information is also stored. What I/O operations the process has currently requested, and what files are currently open ; this information is recorded in the PCB.

Finally, memory management information. Which areas of memory the process is using, and information related to memory allocation, are included in the PCB.

Looking at all the information contained in a PCB together, it becomes clear that the PCB does far more than simply track a process's execution state. Through the PCB, the operating system grasps and manages not just the process's execution flow, but its resource usage as well. In the end, a single PCB concentrates all management information about a given process.

PCB에 저장되는 정보 (마무리)

PCB에는 앞서 살펴본 정보들 외에도 몇 가지가 더 포함된다.

계정 정보다. 해당 프로세스가 CPU를 얼마나 사용했는지, 실제 사용 시간은 얼마인지, 사용 가능한 상한 시간은 얼마인지와 같은 자원 사용 내역이 기록된다. 계정 번호나 작업 번호 같은 식별 정보도 함께 포함된다.

입출력 상태 정보도 저장된다. 현재 이 프로세스가 어떤 입출력 작업을 요청했는지, 현재 열려 있는 파일은 무엇인지와 같은 정보가 PCB에 기록된다.

마지막으로 메모리 관리 정보다. 이 프로세스가 메모리의 어느 영역을 사용하고 있는지, 메모리 할당과 관련된 정보들이 PCB에 포함된다.

이렇게 PCB에 담긴 정보들을 종합해서 보면, PCB가 단순히 프로세스의 실행 상태만 관리하는 것이 아님을 알 수 있다. 운영체제는 PCB를 통해 프로세스의 실행 흐름은 물론, 자원 사용 현황까지 함께 파악하고 관리한다. 결국 PCB 하나에 해당 프로세스에 관한 모든 관리 정보가 집약되어 있는 셈이다.


The Key Role of the PCB in Context Switching

Among the various pieces of information stored in the PCB, the ones most critically used when a context switch occurs are register values and the program counter.

The moment a running process is interrupted, the operating system saves the register values and program counter at that point into the process's PCB. When another process runs and then switches back to this process, the operating system restores exactly the values that were saved in the PCB. As a result, the process can resume precisely from where it was interrupted, as if nothing had happened.

Because these two pieces of information are safely stored in the PCB, the operating system can rapidly switch among dozens of processes while fully maintaining each process's execution flow.

So what order are the PCB-stored values actually saved and restored in when a process switch occurs? Let's look at that step-by-step in the next section.

문맥 교환과 PCB의 핵심 역할

PCB에 저장되는 여러 정보 중에서 문맥 교환이 발생할 때 가장 핵심적으로 활용되는 것은 레지스터 값프로그램 카운터다.

프로세스가 실행되다가 중단되는 순간, 운영체제는 그 시점의 레지스터 값과 프로그램 카운터를 해당 프로세스의 PCB에 저장해둔다. 이후 다른 프로세스가 실행되다가 다시 이 프로세스로 전환되면, 운영체제는 PCB에 저장해뒀던 값들을 그대로 복원한다. 덕분에 프로세스는 마치 아무 일도 없었던 것처럼 중단된 지점부터 정확히 이어서 실행될 수 있다.

이 두 가지 정보가 PCB에 안전하게 보관되어 있기 때문에, 운영체제는 수십 개의 프로세스를 빠르게 전환하면서도 각 프로세스의 실행 흐름을 온전히 유지할 수 있는 것이다.

그렇다면 실제로 프로세스가 전환될 때 PCB에 저장된 정보들이 구체적으로 어떤 순서로 저장되고 복원되는지, 다음 내용에서 그 과정을 단계별로 살펴보자.


3. Context Switching

What Is a Context Switch?

In an environment where multiple processes run on a single CPU, there inevitably comes a moment when the currently running process must be stopped and the CPU handed to another process - for example, when a time slice expires due to timeout, or when a process must transition to the waiting state due to an I/O request.

At this moment, the operating system must handle two things simultaneously: safely interrupting the current process's execution flow, and preparing the next process to resume precisely from where it previously left off.

The operation that handles both of these at once is the context switch. A context switch is not simply a matter of handing the CPU to another process — it refers to the entire sequence of saving the current process's execution state to its PCB and restoring the previous state from the next process's PCB. This process is what allows multiple processes to take turns using a single CPU while each maintains its own execution flow.

문맥 교환이란?

하나의 CPU에서 여러 프로세스가 실행되는 환경에서는 현재 실행 중인 프로세스를 멈추고 다른 프로세스에게 CPU를 넘겨야 하는 순간이 반드시 생긴다. 타임아웃으로 사용 시간이 끝났거나, 입출력 요청으로 대기 상태로 전환되어야 하는 경우가 대표적인 예다.

이 순간 운영체제는 두 가지를 동시에 처리해야 한다. 현재 실행 중인 프로세스의 실행 흐름을 안전하게 중단시키는 것, 그리고 다음에 실행할 프로세스가 이전에 멈췄던 지점부터 정확히 이어서 실행될 수 있도록 준비하는 것이다.

이 두 가지를 한꺼번에 처리하는 동작이 바로 문맥 교환(Context Switch) 이다. 문맥 교환은 단순히 CPU를 다른 프로세스에게 넘기는 것이 아니라, 현재 프로세스의 실행 상태를 PCB에 저장하고 다음 프로세스의 PCB에서 이전 상태를 복원하는 일련의 과정 전체를 가리킨다. 이 과정이 있기 때문에 여러 프로세스가 하나의 CPU를 번갈아 사용하면서도 각자의 실행 흐름을 유지할 수 있는 것이다.


When Does a Context Switch Occur?

A context switch doesn't happen arbitrarily at any time. It only occurs at the moment the operating system determines it needs to change which process the CPU is executing.

There are three representative triggers. When an interrupt occurs in the running process; when the allocated time slice expires; and when an I/O request makes it impossible for the current process to continue executing. At these moments, the operating system decides whether to switch the CPU's execution target.

There's an important point here: an interrupt doesn't necessarily cause a context switch. Even if an interrupt occurs, if the operating system determines that the current process can continue running, no context switch takes place.

In other words, a context switch is not the interrupt itself - it is the result of the decision the operating system makes after the interrupt. An interrupt is merely a signal giving the operating system an opportunity to intervene; whether an actual context switch happens is a decision the operating system makes based on the circumstances.

문맥 교환은 언제 발생할까?

문맥 교환은 임의로 아무 때나 발생하는 것이 아니다. 운영체제가 CPU의 실행 대상을 바꿔야 한다고 판단한 순간에만 일어난다.

대표적인 계기는 세 가지다. 실행 중인 프로세스에 인터럽트가 발생했을 때, 할당된 타임 슬라이스가 종료되었을 때, 그리고 입출력 요청으로 인해 현재 프로세스가 더 이상 실행을 이어갈 수 없는 상황이 됐을 때다. 이런 시점에 운영체제는 CPU 실행 대상을 전환할지 여부를 결정한다.

여기서 한 가지 중요한 점이 있다. 인터럽트가 발생했다고 해서 반드시 문맥 교환이 일어나는 것은 아니라는 것이다. 인터럽트가 발생하더라도 운영체제가 현재 프로세스를 계속 실행해도 된다고 판단하면 문맥 교환은 일어나지 않는다.

즉 문맥 교환은 인터럽트 그 자체가 아니라, 인터럽트 이후 운영체제가 내린 판단의 결과다. 인터럽트는 운영체제에게 개입할 기회를 주는 신호일 뿐이고, 실제로 문맥 교환을 할지 말지는 운영체제가 상황을 보고 결정하는 것이다.


The Actual Sequence of a Context Switch

When a context switch occurs, the operating system proceeds in the following order.

First, it saves the execution position and CPU state of the currently running process. The program counter and register values discussed earlier are recorded in that process's PCB. This save must be completed so that when the process runs again later, it can resume exactly from where it stopped.

Once saving is complete, it retrieves and restores the execution position and CPU state stored in the next process's PCB. Through this restoration, the CPU reaches a state where it can pick up where the new process previously left off.

These two steps - saving the previous process's state to its PCB and restoring the next process's state from its PCB to switch the CPU's execution target - constitute the entire operation called a context switch.

Ultimately, context switching is only possible because of the PCB. Because each process's execution state is safely preserved in the PCB, the operating system can rapidly switch among dozens of processes while fully maintaining each process's execution flow.

문맥 교환이 발생하면 운영체제는 다음과 같은 순서로 동작한다.

먼저 현재 실행 중이던 프로세스의 실행 위치와 CPU 상태를 저장한다. 앞서 살펴본 프로그램 카운터와 레지스터 값들이 해당 프로세스의 PCB에 기록되는 것이다. 이 저장이 완료되어야 나중에 이 프로세스가 다시 실행될 때 중단된 지점부터 정확히 이어갈 수 있다.

저장이 끝나면 다음에 실행할 프로세스의 PCB에 저장되어 있던 실행 위치와 CPU 상태를 꺼내어 복원한다. 이 복원 과정을 통해 CPU는 새로운 프로세스가 이전에 멈췄던 지점부터 실행을 이어받을 수 있는 상태가 된다.

이 두 단계, 즉 이전 프로세스의 상태를 PCB에 저장하고 다음 프로세스의 상태를 PCB에서 복원하여 CPU의 실행 대상을 전환하는 전체 과정을 문맥 교환이라고 한다.

결국 문맥 교환은 PCB가 있기 때문에 가능한 동작이다. 각 프로세스의 실행 상태가 PCB에 안전하게 보관되어 있기 때문에, 운영체제는 수십 개의 프로세스를 빠르게 전환하면서도 각 프로세스의 실행 흐름을 온전히 유지할 수 있다.


The Step-by-Step Sequence of a Context Switch

Let's trace the specific sequence of a context switch through the transition between P1 and P2.

While P1 is running, its time slice expires and the operating system intervenes. The operating system first saves P1's current state ; its program counter and register values ; into PCB1. This is the moment P1's execution flow is safely preserved.

Once saving is complete, the operating system retrieves P2's PCB2 and restores the previously saved execution state. Through this restoration, the CPU reaches a state where it can resume P2 from where it previously stopped. The control of the CPU passes from P1 to P2, and P2 begins executing.

The same pattern repeats every time the process switches. The current process's state is saved to its PCB, and the next process's state is restored from its PCB.

The key here is that a context switch does not swap the processes themselves. The essence of a context switch is the repeated cycle of saving and restoring via the PCB. The processes remain in memory as they are; the operating system transfers execution states through the PCBs to switch which process the CPU is running.

문맥 교환의 실제 진행 순서

문맥 교환이 구체적으로 어떤 순서로 진행되는지 P1과 P2의 전환 과정을 통해 살펴보자.

P1이 실행 중이다가 타임 슬라이스가 종료되면 운영체제가 개입한다. 운영체제는 가장 먼저 현재 실행 중이던 P1의 상태, 즉 프로그램 카운터와 레지스터 값을 PCB1에 저장한다. P1의 실행 흐름이 안전하게 보관되는 순간이다.

저장이 완료되면 운영체제는 다음에 실행할 P2의 PCB2를 가져와 이전에 저장해뒀던 실행 상태를 복원한다. 이 복원 과정을 통해 CPU는 P2가 이전에 멈췄던 지점부터 이어서 실행할 수 있는 상태가 된다. 이렇게 CPU의 제어권이 P1에서 P2로 넘어가고 P2가 실행을 시작한다.

이후에도 프로세스가 바뀔 때마다 동일한 방식이 반복된다. 현재 프로세스의 상태는 PCB에 저장되고, 다음 프로세스의 상태는 PCB에서 복원된다.

여기서 핵심은 문맥 교환이 프로세스 자체를 바꾸는 것이 아니라는 점이다. PCB를 기준으로 저장과 복원을 반복하는 과정이 문맥 교환의 본질이다. 프로세스들은 메모리에 그대로 있고, 운영체제가 PCB를 통해 실행 상태를 주고받으며 CPU의 실행 대상을 전환하는 것이다.


Summarizing the Relationship Between PCBs and Context Switching

Let's pull together everything we've covered.

All of a process's state information is stored in the PCB. When a context switch occurs, the current process's state is saved to its PCB, and the next process's previous state is restored from its PCB — that is how process switching is carried out.

From this perspective, the PCB's role becomes clear. The PCB is not simply a place to record process information - it is the management standard that maintains the information needed to resume an interrupted process's execution. Without the PCB, there would be no way to know where a process stopped or what state it was in, making context switching impossible altogether.

In conclusion, a context switch can be summarized as the process of switching the execution target based on the PCB. Each time a process switches, saving and restoring via the PCB repeats, and through this process the operating system manages multiple processes sharing a single CPU without interruption.

PCB와 문맥 교환의 관계 정리

지금까지 살펴본 내용을 한 번 정리해보자.

프로세스의 상태 정보는 모두 PCB에 저장된다. 문맥 교환이 발생하면 현재 프로세스의 상태를 PCB에 저장하고, 다음 프로세스의 PCB에서 이전 상태를 복원하는 방식으로 프로세스 전환이 이루어진다.

이 관점에서 보면 PCB의 역할이 명확해진다. PCB는 단순히 프로세스 정보를 기록해두는 공간이 아니라, 중단된 프로세스의 실행을 다시 이어서 수행할 수 있도록 필요한 정보를 유지해주는 관리 기준이다. PCB가 없다면 프로세스가 어디서 멈췄는지, 어떤 상태였는지를 알 수 없기 때문에 문맥 교환 자체가 불가능하다.

결론적으로 문맥 교환은 PCB를 기준으로 실행 대상을 바꾸는 과정이라고 정리할 수 있다. 프로세스가 전환될 때마다 PCB에 저장과 복원이 반복되고, 운영체제는 이 과정을 통해 여러 프로세스가 하나의 CPU를 끊김 없이 나눠 쓸 수 있도록 관리한다.


The Overhead of Context Switching

Context switching is a necessary process that allows multiple processes to share the CPU. But it comes with a cost: while a context switch is taking place, the CPU cannot perform any actual work and comes to a momentary halt.

During this time, all the CPU does is save the previous process's state to the PCB and load the next process's state from the PCB. No actual computation takes place. This — consuming CPU time without performing real work — is what we call overhead.

The more frequently context switches occur, the more this overhead accumulates, ultimately affecting overall system performance. The more often processes switch, the less time the CPU has available for actual work.

Therefore, the operating system should not trigger context switches unconditionally — it must manage them so they occur only when necessary and as infrequently as possible. The number of context switches is directly linked to system performance. The specifics of how the operating system selects which process to run and how it moderates the number of switches will be explored in depth in the CPU scheduling chapter.

문맥 교환의 오버헤드

문맥 교환은 여러 프로세스가 CPU를 나눠 쓰기 위해 반드시 필요한 과정이다. 하지만 여기에는 한 가지 비용이 따른다. 문맥 교환이 일어나는 동안 CPU는 실제 작업을 수행하지 못하고 멈추게 된다는 것이다.

이 시간 동안 CPU가 하는 일은 이전 프로세스의 상태를 PCB에 저장하고, 다음 프로세스의 상태를 PCB에서 불러오는 것뿐이다. 실제 연산은 전혀 이루어지지 않는다. 이처럼 실제 작업은 하지 않으면서 CPU 시간이 소모되는 부분을 오버헤드라고 한다.

문맥 교환이 자주 발생할수록 이 오버헤드는 누적되고, 결과적으로 전체 시스템 성능에 영향을 미치게 된다. 프로세스 전환이 잦을수록 CPU가 실제 작업에 쓸 수 있는 시간이 그만큼 줄어드는 셈이다.

따라서 운영체제는 문맥 교환을 무조건 발생시키는 것이 아니라, 필요한 경우에만 최소한으로 일어날 수 있도록 관리해야 한다. 문맥 교환 횟수는 시스템 성능과 직접적으로 연결되어 있기 때문이다. 이와 관련된 구체적인 내용, 즉 운영체제가 어떤 기준으로 CPU 실행 대상을 선택하고 전환 횟수를 조율하는지는 CPU 스케줄링 챕터에서 본격적으로 다루게 된다.


The Relationship Between Time Slice Size and Context Switching

Let's examine how frequently context switches occur depending on time slice size, and how that affects system performance, through three cases.

Case A is when the time slice is large. A single process occupies the CPU for a long time, and context switches occur relatively infrequently. Because more time is spent on actual work, overhead is low. However, if one process monopolizes the CPU for too long, other processes may have to wait a long time.

Case B is when the time slice is appropriately sized. Process execution time and context switching are balanced. Multiple processes share the CPU evenly while overhead doesn't become excessive — an ideal state.

Case C is when the time slice is excessively small. Context switches occur repeatedly almost the instant a process starts running. The time spent saving and restoring state exceeds the time the CPU spends on actual work. Overhead accumulates significantly and overall system performance degrades.

In the end, if the time slice is too small, context switches occur excessively and overhead grows; if it's too large, a particular process ends up monopolizing the CPU. The number of context switches is directly linked to system performance, and finding the right balance is a central challenge in CPU scheduling.

타임 슬라이스 크기와 문맥 교환의 관계

타임 슬라이스의 크기에 따라 문맥 교환이 얼마나 자주 발생하는지, 그리고 그것이 시스템 성능에 어떤 영향을 주는지 세 가지 경우를 통해 살펴보자.

A는 타임 슬라이스가 큰 경우다. 하나의 프로세스가 오랜 시간 CPU를 점유하고, 문맥 교환은 상대적으로 드물게 발생한다. 실제 작업에 쓰이는 시간이 길기 때문에 오버헤드는 낮다. 다만 한 프로세스가 CPU를 너무 오래 독점하면 다른 프로세스가 오래 기다려야 하는 문제가 생길 수 있다.

B는 타임 슬라이스가 적당한 경우다. 프로세스 실행 시간과 문맥 교환이 균형을 이루고 있다. 여러 프로세스가 고르게 CPU를 나눠 쓰면서도 오버헤드가 과도하지 않은 이상적인 상태다.

C는 타임 슬라이스가 지나치게 작은 경우다. 프로세스가 실행을 시작하자마자 문맥 교환이 반복적으로 발생한다. CPU가 실제 작업을 수행하는 시간보다 상태를 저장하고 복원하는 데 쓰는 시간이 더 많아지는 상황이다. 오버헤드가 크게 누적되어 시스템 전체 성능이 떨어진다.

결국 타임 슬라이스가 너무 작으면 문맥 교환이 과도하게 발생해 오버헤드가 커지고, 너무 크면 특정 프로세스가 CPU를 독점하는 문제가 생긴다. 문맥 교환 횟수는 시스템 성능과 직접적으로 연결되어 있으며, 이 균형을 어떻게 맞출 것인가가 CPU 스케줄링의 핵심 과제가 된다.


The Next Challenges in Process Management

We've confirmed that context switching allows the operating system to switch between processes. But in an environment where multiple processes exist simultaneously, simply being able to switch is not enough.

When there are multiple processes, new problems naturally arise. When several processes are waiting, which one should run first? When multiple processes try to use the same resource at the same time, how should that be coordinated?

Ultimately, switching between processes is just the beginning. Maintaining the entire system stably and efficiently requires additional management beyond the switching itself — scheduling to determine execution order, and synchronization to coordinate resource use. What we'll explore going forward is precisely how these problems are solved.

프로세스 관리의 다음 과제

문맥 교환을 통해 운영체제가 프로세스 간의 전환을 수행할 수 있다는 것을 확인했다. 그런데 여러 프로세스가 동시에 존재하는 환경에서는 단순히 전환이 가능하다는 것만으로는 충분하지 않다.

프로세스가 여럿 존재한다면 자연스럽게 새로운 문제들이 생겨난다. 대기 중인 프로세스가 여러 개일 때 그 중 어떤 프로세스를 먼저 실행할 것인가, 여러 프로세스가 같은 자원을 동시에 사용하려 할 때 이를 어떻게 조율할 것인가와 같은 문제들이다.

결국 프로세스를 전환하는 것 자체는 시작에 불과하다. 시스템 전체를 안정적이고 효율적으로 유지하기 위해서는 전환 이후의 관리, 즉 어떤 순서로 실행할지를 결정하는 스케줄링과 자원 사용을 조율하는 동기화 같은 추가적인 관리 기법이 필요하다. 앞으로 살펴볼 내용들이 바로 이 문제들을 어떻게 해결하는지에 대한 것이다.


The Core Challenges of Process Management Ahead

In an environment where multiple processes run together, there are additional problems the operating system must solve. Let's lay out the key challenges we'll be examining one by one.

The first is CPU scheduling. Among multiple waiting processes, which one gets the CPU? In what order and by what criteria is execution order determined?

The second is memory management. When multiple processes must share limited memory, how is memory allocated to each process and managed efficiently?

The third is inter-process communication. When processes with independent memory spaces need to exchange information, how is that handled?

The fourth is synchronization. When multiple processes try to access the same data simultaneously, conflicts can occur. Managing this situation to maintain data consistency and prevent conflicts requires dedicated techniques.

These four are the core issues in process management we'll be covering going forward. For now, get a broad sense that these problems exist — the specific details of each will be examined one by one in the chapters ahead.

앞으로 다룰 프로세스 관리의 핵심 과제들

여러 프로세스가 함께 실행되는 환경에서 운영체제가 추가로 해결해야 할 문제들이 있다. 앞으로 하나씩 살펴볼 핵심 과제들을 먼저 정리해두자.

첫 번째는 CPU 스케줄링이다. 대기 중인 여러 프로세스 중에서 어떤 프로세스에게 CPU를 줄 것인지, 어떤 순서와 기준으로 실행 순서를 결정할 것인지의 문제다.

두 번째는 메모리 관리다. 한정된 메모리를 여러 프로세스가 나눠 써야 하는 상황에서 각 프로세스에게 메모리를 어떻게 할당하고 효율적으로 관리할 것인지의 문제다.

세 번째는 프로세스 간 통신이다. 서로 독립된 메모리 공간을 가진 프로세스들이 필요에 따라 정보를 주고받아야 할 때, 이를 어떤 방식으로 처리할 것인지의 문제다.

네 번째는 동기화다. 여러 프로세스가 동일한 데이터에 동시에 접근하려 할 때 충돌이 발생할 수 있다. 이런 상황에서 데이터의 일관성을 유지하고 충돌을 막기 위한 관리 방법이 필요하다.

이 네 가지가 앞으로 다루게 될 프로세스 관리의 핵심 이슈다. 지금은 이런 문제들이 존재한다는 것을 큰 그림으로 파악해두고, 각 항목에 대한 구체적인 내용은 이후에 하나씩 살펴보게 될 것이다.


3️⃣ Process Execution and Control


Process Execution System Calls: fork()

Before directly observing process execution and control in a Ubuntu environment, let's first define the relevant terminology. There are representative system calls the operating system uses to manage processes from creation to termination. The first one we'll look at is fork().

fork() is a system call that creates one new process based on the currently running process. The newly created process is called the child process, and the original process is called the parent process.

When fork() is called, the parent process's memory space is logically duplicated to create the new process. The two processes created this way have different PIDs and run completely independently after creation.

There's an important point here. Code written after the fork() call is executed in both the parent and the child process. In other words, the execution flow splits into two branches after fork(), with each process independently continuing its own flow. Understanding this characteristic is essential for accurately grasping how code behaves after fork().

프로세스 실행 관련 시스템 호출; fork()

우분투 환경에서 프로세스의 실행과 제어를 직접 확인하기에 앞서, 먼저 관련 용어를 정리해두자. 운영체제가 프로세스의 생성부터 종료까지 관리하는 데 사용하는 대표적인 시스템 호출들이 있다. 그 중 첫 번째로 살펴볼 것이 fork()다.

fork()는 현재 실행 중인 프로세스를 기준으로 새로운 프로세스를 하나 더 생성하는 시스템 호출이다. 이때 새롭게 만들어진 프로세스를 자식 프로세스, 기존의 프로세스를 부모 프로세스라고 부른다.

fork()가 호출되면 부모 프로세스의 메모리 공간이 논리적으로 복제되어 새로운 프로세스가 만들어진다. 이렇게 생성된 두 프로세스는 서로 다른 PID를 가지게 되며, 생성 이후에는 완전히 독립적으로 실행된다.

여기서 중요한 점이 있다. fork() 호출 이후에 작성된 코드는 부모 프로세스와 자식 프로세스 모두에서 실행된다는 것이다. 즉 fork() 이후의 실행 흐름이 두 갈래로 나뉘어, 각 프로세스가 자신의 흐름을 독립적으로 이어서 실행하게 된다. 이 특성을 이해하고 있어야 fork() 이후의 코드 동작을 정확히 파악할 수 있다.


Distinguishing Parent from Child Using fork()'s Return Value

When fork() is called, both the parent and child processes execute the same code simultaneously. So how can you tell whether the currently executing code is running in the parent or the child? The answer is through fork()'s return value.

The return value falls into three cases. In the parent process, the PID of the newly created child process is returned. In the child process, 0 is returned. And if process creation fails, -1 is returned.

Using this difference in return values, you can clearly determine within the code after fork() whether the current execution context is the parent or the child. If the return value is 0, it's running in the child process; if the value is greater than 0, it's running in the parent. By using this return value as a condition to branch the code inside the program, you can control the parent and child to perform different behaviors.

fork()의 반환값으로 부모와 자식 구분하기

fork()를 호출하면 부모와 자식 프로세스가 동시에 같은 코드를 실행하게 된다. 그렇다면 현재 실행 중인 코드가 부모 프로세스에서 돌아가고 있는지, 자식 프로세스에서 돌아가고 있는지를 어떻게 구분할 수 있을까? 바로 fork()의 반환값을 통해 구분할 수 있다.

반환값은 세 가지 경우로 나뉜다. 부모 프로세스에서는 새로 생성된 자식 프로세스의 PID가 반환된다. 자식 프로세스에서는 0이 반환된다. 그리고 프로세스 생성에 실패한 경우에는 -1이 반환된다.

이 반환값의 차이를 이용하면 fork() 이후의 코드 안에서 지금 실행 중인 주체가 부모인지 자식인지를 명확하게 구분할 수 있다. 반환값이 0이면 자식 프로세스에서 실행 중인 것이고, 0보다 큰 값이면 부모 프로세스에서 실행 중인 것이다. 프로그램 안에서 이 반환값을 조건으로 분기를 나누면 부모와 자식이 서로 다른 동작을 수행하도록 제어할 수 있다.


The Execution Flow After fork()

Let's trace the process by which a single process splits into two execution flows via fork().

When the program starts, the main function executes, and the moment fork() is called, a new child process is created. From this point on, both the parent and child process exist simultaneously, and both continue executing from the code immediately after fork().

After that, the two processes execute different code blocks based on the return value. Because the child process receives 0, it executes the child's code block; the parent process receives the child's PID and executes the parent's code block. After each executes its respective code block, the two processes independently finish and terminate.

There's one thing that must be remembered here: it is not predetermined which process - parent or child - will run first. The parent might run first, or the child might. Because this depends on the operating system's scheduling decisions, the output order of the program can change every time it runs.

Ultimately, after fork(), a single piece of code runs separately in both the parent and child processes, and the order of execution is decided by the operating system.

fork() 이후의 실행 흐름

fork()를 통해 하나의 프로세스가 두 개의 실행 흐름으로 나뉘는 과정을 살펴보자.

프로그램이 시작되면 메인 함수가 실행되고, fork()가 호출되는 순간 새로운 자식 프로세스가 생성된다. 이 시점부터 부모 프로세스와 자식 프로세스가 동시에 존재하게 되며, 둘 다 fork() 다음 코드부터 실행을 이어간다.

이후 두 프로세스는 반환값을 기준으로 서로 다른 코드 블록을 실행한다. 자식 프로세스는 반환값이 0이기 때문에 자식 프로세스용 코드 블록을 실행하고, 부모 프로세스는 자식의 PID를 반환받아 부모 프로세스용 코드 블록을 실행한다. 각자의 코드 블록을 실행한 후 두 프로세스는 서로 독립적으로 실행을 마치고 종료된다.

여기서 반드시 기억해야 할 점이 있다. 부모와 자식 중 어느 프로세스가 먼저 실행될지는 정해져 있지 않다는 것이다. 부모가 먼저 실행될 수도 있고, 자식이 먼저 실행될 수도 있다. 이는 운영체제의 스케줄링 결정에 따라 달라지기 때문에 프로그램의 출력 결과는 실행할 때마다 순서가 바뀔 수 있다.

결국 fork() 이후에는 하나의 코드가 부모와 자식 프로세스에서 각각 실행되며, 그 실행 순서는 운영체제가 결정한다는 점을 이해해두어야 한다.


exec() — The System Call That Changes What a Process Runs

The system call used when an already-created process needs to switch to running a different program is exec(). It replaces the currently running process entirely with a new program.

The key here is that no new process is created. The process itself — its PID — remains the same. However, the code region, data region, and stack region that the process was running are all completely replaced with the contents of the new program. Think of it as keeping the process's shell intact while swapping out everything running inside it.

The outcome of an exec() call is also clearly defined. If the call succeeds, the original program's execution flow is completely terminated, and any code written after exec() does not execute — because there is no flow to return to once the replacement has occurred. Conversely, if the call fails, no replacement takes place, so the original program's flow continues and the code on the next line after exec() executes.

exec() is commonly used together with fork(). The typical pattern is to create a child process with fork() and then call exec() within the child to run an entirely different program.

exec() — 실행 내용을 바꾸는 시스템 호출

이미 생성된 프로세스가 실행할 프로그램을 바꿔야 할 때 사용하는 시스템 호출이 exec()다. 현재 실행 중인 프로세스를 완전히 새로운 프로그램으로 교체하는 역할을 한다.

여기서 핵심은 새로운 프로세스가 만들어지는 것이 아니라는 점이다. 프로세스 자체, 즉 PID는 그대로 유지된다. 하지만 그 프로세스가 실행하던 코드 영역, 데이터 영역, 스택 영역이 모두 새로운 프로그램의 내용으로 완전히 교체된다. 프로세스라는 껍데기는 그대로 두고 안에서 실행되는 내용만 통째로 바꾸는 것이라고 이해하면 된다.

exec()의 동작 결과도 명확하게 구분된다. 호출이 성공하면 기존 프로그램의 실행 흐름은 완전히 종료되고, exec() 이후에 작성된 코드는 실행되지 않는다. 이미 새로운 프로그램으로 교체되었기 때문에 돌아올 흐름 자체가 없어지는 것이다. 반대로 호출이 실패한 경우에는 교체가 이루어지지 않았기 때문에 기존 프로그램의 흐름이 그대로 유지되어 exec() 다음 줄의 코드가 실행된다.

exec()는 보통 fork()와 함께 사용되는 경우가 많다. fork()로 자식 프로세스를 생성한 뒤, 자식 프로세스에서 exec()를 호출해 전혀 다른 프로그램을 실행시키는 패턴이 대표적인 활용 방식이다.


Analyzing the exec() Execution Flow Example

This example is code that lets you directly observe how a process's execution flow changes before and after exec() is called. Let's follow the flow step by step.

First, a message is printed via printf before exec() is called. Up to this point, the original program's flow is executing normally.

Next, execl("/bin/ls", "ls", NULL) is called. The moment this single line executes, the current process is completely replaced by /bin/ls — the ls program that outputs a directory's file listing. The meaning of each argument is as follows.

The first argument, /bin/ls, is the full path of the file the operating system will actually execute. The operating system looks at this path to decide which file to run. The second argument, ls, specifies the name the executed program will recognize itself by. Even if you change the second argument to hello, the operating system will still execute /bin/ls, but internally the program will recognize its own name as hello. To avoid confusion, it is conventional to pass the same name as the executable file. The final NULL is a marker telling the operating system that the argument list ends here. Because exec-family functions can accept multiple arguments, the end must be explicitly indicated.

Once execl() succeeds, the original program's flow is completely gone. That's why the printf("exec 호출 후\n") written below it does not execute — there is no flow to return to.

The core takeaway from this example is one thing: exec() does not create a new process — it completely replaces the current process's execution content.

exec() 실행 흐름 예제 분석

이 예제는 exec() 호출 전후로 프로세스의 실행 흐름이 어떻게 바뀌는지를 직접 확인할 수 있는 코드다. 흐름을 단계별로 따라가보자.

먼저 printf를 통해 exec() 호출 전 메시지가 출력된다. 여기까지는 기존 프로그램의 흐름이 그대로 실행되는 구간이다.

그다음 execl("/bin/ls", "ls", NULL)이 호출된다. 이 한 줄이 실행되는 순간 현재 프로세스는 /bin/ls, 즉 디렉토리 파일 목록을 출력하는 ls 프로그램으로 완전히 교체된다. 각 인자의 의미를 살펴보면 다음과 같다.

첫 번째 인자 /bin/ls는 운영체제가 실제로 실행할 파일의 전체 경로다. 운영체제는 이 경로를 보고 어떤 파일을 실행할지 결정한다. 두 번째 인자 ls는 실행된 프로그램이 자기 자신을 어떤 이름으로 인식할지를 지정하는 값이다. 만약 두 번째 인자를 hello로 바꿔도 운영체제는 여전히 /bin/ls를 실행하지만, 실행된 프로그램 내부에서는 자신을 hello라는 이름으로 인식하게 된다. 혼란을 피하기 위해 실행 파일의 이름과 프로그램이 인식하는 이름을 동일하게 전달하는 것이 관례다. 마지막 NULL은 인자 목록이 여기서 끝났음을 운영체제에게 알려주는 표식이다. exec 계열 함수는 여러 인자를 받을 수 있기 때문에 끝을 명시적으로 표시해줘야 한다.

execl() 호출이 성공하면 기존 프로그램의 흐름은 완전히 사라진다. 그래서 그 아래에 작성된 printf("exec 호출 후\n")는 실행되지 않는다. 돌아올 흐름 자체가 없어지기 때문이다.

이 예제에서 확인할 수 있는 핵심은 하나다. exec()는 새로운 프로세스를 만드는 것이 아니라, 현재 프로세스의 실행 내용을 완전히 교체한다는 것이다.


The Combination of fork() and exec()

In practice, exec() is rarely used alone — it is almost always used together with fork(). Let's look at how this combination works.

First, fork() is called, creating parent and child processes. The child process initially executes the same code as the parent, but the moment execl("/bin/ls", "ls", NULL) is called on the child's side, the child's execution content is completely replaced by the ls program. Afterward, the parent continues executing its own code while the child, now replaced by ls, outputs the directory file listing. As a result, the parent's output and the ls output from the child both appear together.

This pattern is important because it is the fundamental way commands are executed in a shell. When a user types a command in the terminal, the shell creates a child process with fork(), then calls exec() in that child to replace it with the program corresponding to the entered command. The parent shell process remains intact and prepares to receive the next command, while the child process handles command execution. This structure is exactly why the shell becomes the parent process of user programs in the process tree we examined earlier.

fork()와 exec()의 조합

실제 운영체제에서 exec()는 단독으로 사용되기보다 대부분 fork()와 함께 사용된다. 이 조합이 어떻게 동작하는지 살펴보자.

먼저 fork()가 호출되면 부모 프로세스와 자식 프로세스가 생성된다. 자식 프로세스는 처음에는 부모와 동일한 코드를 실행하다가, 자식 프로세스 쪽에서 execl("/bin/ls", "ls", NULL)이 호출되는 순간 자식 프로세스의 실행 내용이 ls 프로그램으로 완전히 교체된다. 이후 부모 프로세스는 자신의 코드를 그대로 이어서 실행하고, 자식 프로세스는 ls 프로그램으로 교체되어 디렉토리 파일 목록을 출력한다. 결과적으로 부모 프로세스의 출력과 자식 프로세스에서 실행된 ls의 결과가 함께 나타나게 된다.

이 패턴이 중요한 이유는 바로 쉘에서 명령어를 실행할 때 사용되는 기본 방식이기 때문이다. 사용자가 터미널에 명령어를 입력하면 쉘은 fork()로 자식 프로세스를 생성하고, 그 자식 프로세스에서 exec()를 호출해 입력한 명령어에 해당하는 프로그램으로 교체한다. 부모인 쉘 프로세스는 그대로 유지되면서 다음 명령어를 받을 준비를 하고, 자식 프로세스가 명령어를 실행하는 것이다. 앞서 프로세스 트리에서 쉘이 사용자 프로그램들의 부모 프로세스가 된다고 했던 것이 바로 이 구조 때문이다.


exit() — The System Call for Terminating a Process

exit() is the system call used to terminate a running process. The moment it is called, the current process terminates immediately, and no code executes afterward. Like exec() examined earlier, it is a function from which execution does not continue after it's called.

When a process calls exit(), it notifies the operating system that this process is terminating. In this process, the operating system cleans up the resources the process had been using — memory, open files, and so on — and returns them to the system. The resources the process had been occupying are reclaimed at this point.

There's one more thing to note here. The termination information passed through exit() is delivered to the operating system as well. This information is stored so that the parent process can retrieve it later. A structure exists that allows the parent process to find out how the child process terminated. How this is actually handled in practice will be seen concretely through the wait() system call we'll look at next.

exit() — 프로세스 종료 시스템 호출

exit()는 실행 중인 프로세스를 종료할 때 사용하는 시스템 호출이다. 호출되는 순간 현재 프로세스는 즉시 종료되며, 이후 어떤 코드도 실행되지 않는다. 앞서 살펴본 exec()와 마찬가지로 호출 이후 실행 흐름이 이어지지 않는 함수다.

프로세스가 exit()를 호출하면 운영체제에게 이 프로세스가 종료됨을 알린다. 이 과정에서 운영체제는 해당 프로세스가 사용하던 메모리, 열려 있던 파일 같은 자원들을 정리해서 다시 시스템으로 돌려보낸다. 프로세스가 점유하고 있던 자원이 이 시점에 회수되는 것이다.

여기서 한 가지 더 짚고 넘어갈 점이 있다. exit()를 통해 전달되는 종료 정보가 운영체제에 함께 전달된다는 것이다. 이 정보는 부모 프로세스가 나중에 확인할 수 있도록 보관된다. 자식 프로세스가 어떻게 종료되었는지를 부모 프로세스가 알 수 있는 구조가 마련되어 있는 셈이다. 이 부분이 실제로 어떻게 처리되는지는 다음에 살펴볼 wait() 시스템 호출을 통해 구체적으로 확인하게 될 것이다.


Analyzing the exit() Example

The moment exit(0) is called, the process terminates immediately. As a result, the printf("이 문장은 실행되지 않는다.") written below it does not execute. Even though the code exists, the flow after exit() is completely blocked.

Let's also note the meaning of the number passed to exit() — the exit status value. 0 means the process terminated normally without any problems. Values other than 0, such as 1 or 2, are used to distinguish what kind of error occurred. As mentioned earlier, this exit status value is passed to the operating system so the parent process can retrieve it later.

The core takeaway from this example is one thing: once exit() is called, no subsequent code ever executes, and the program's execution ends immediately on the spot.

exit() 예제 분석

exit(0)이 호출되는 순간 프로세스는 즉시 종료된다. 그렇기 때문에 그 아래에 작성된 printf("이 문장은 실행되지 않는다.")는 실행되지 않는다. 코드가 존재하더라도 exit() 이후의 흐름은 완전히 차단되는 것이다.

여기서 exit()에 전달되는 숫자, 즉 종료 상태값의 의미도 짚어두자. 0은 프로세스가 문제 없이 정상적으로 종료되었음을 뜻한다. 반면 1이나 2 같은 0 이외의 값들은 어떤 종류의 오류가 발생했는지를 구분하는 데 사용된다. 이 종료 상태값은 앞서 언급했듯이 부모 프로세스가 나중에 확인할 수 있도록 운영체제에 전달된다.

이 예제를 통해 확인할 수 있는 핵심은 하나다. exit()가 호출되면 그 이후의 코드는 절대 실행되지 않으며, 프로그램의 실행이 그 자리에서 즉시 끝난다는 것이다.


wait() — The System Call for Waiting on a Child Process to Terminate

wait() is a system call that causes a parent process to wait until its child process terminates.

When a parent process calls wait(), if the child process is still running, the parent enters the waiting state. Only when the child process terminates does the parent process resume execution. In this process, the parent process can also retrieve the child's exit status value. The exit status value we saw earlier with exit() is delivered to the parent process at this moment.

One of wait()'s important roles is controlling execution order. Using wait() guarantees that the parent process won't terminate before the child. We said that after fork(), which runs first — parent or child — depends on the operating system's scheduling; by using wait(), you can clearly establish that the parent only moves on to the next step after the child has fully terminated. For this reason, wait() is also a system call used for synchronization between parent and child processes.

wait() — 자식 프로세스의 종료를 기다리는 시스템 호출

wait()는 부모 프로세스가 자식 프로세스가 종료될 때까지 기다리도록 만드는 시스템 호출이다.

부모 프로세스가 wait()를 호출하면 자식 프로세스가 아직 실행 중인 경우 부모는 대기 상태로 들어간다. 자식 프로세스가 종료되면 그제서야 부모 프로세스가 다시 실행을 이어간다. 이 과정에서 부모 프로세스는 자식 프로세스의 종료 상태값도 함께 회수할 수 있다. 앞서 exit()에서 살펴봤던 종료 상태값이 바로 이 시점에 부모 프로세스에게 전달되는 것이다.

wait()의 중요한 역할 중 하나는 실행 순서의 제어다. wait()를 사용하면 부모 프로세스가 자식보다 먼저 종료되지 않는다는 것이 보장된다. fork() 이후에는 부모와 자식 중 누가 먼저 실행될지 운영체제의 스케줄링에 따라 달라진다고 했는데, wait()를 사용하면 자식이 완전히 종료된 이후에 부모가 다음 단계로 넘어간다는 순서를 명확히 정할 수 있다. 이런 이유로 wait()는 부모와 자식 프로세스 간의 동기화에 사용되는 시스템 호출이기도 하다.


Analyzing the wait() Example

This example shows how fork(), exit(), and wait() are used together to control the execution order of parent and child processes.

First, a child process is created via pid_t pid = fork(). The child process then prints a "child process running" message, waits briefly with sleep(2), and terminates with exit(0).

Meanwhile, after fork(), the parent process doesn't immediately execute the next code — it calls wait(NULL) and stays in the waiting state until the child process fully terminates. Only after the child terminates with exit(0) does the parent wake up from the waiting state, print the "parent running after child terminated" message, and continue executing the subsequent code.

This example makes wait()'s role clear. Without wait(), there's no guarantee which of the parent and child runs first; but by using wait(), it is solidly guaranteed that the parent runs after the child terminates. Ultimately, wait() should be understood as the means by which a parent process waits for its child's termination and handles the result.

wait() 예제 분석

이 예제는 fork(), exit(), wait()가 함께 사용되어 부모와 자식 프로세스의 실행 순서가 어떻게 제어되는지를 보여준다.

먼저 pid_t pid = fork()를 통해 자식 프로세스가 생성된다. 이후 자식 프로세스는 "자식 프로세스 실행" 메시지를 출력하고, sleep(2)로 잠시 대기한 뒤 exit(0)으로 종료된다.

한편 부모 프로세스는 fork() 이후 곧바로 다음 코드를 실행하지 않고 wait(NULL)을 호출하여 자식 프로세스가 완전히 종료될 때까지 대기 상태로 머문다. 자식 프로세스가 exit(0)으로 종료되고 나서야 부모 프로세스가 대기 상태에서 깨어나 "자식 종료 후 부모 실행" 메시지를 출력하며 이후 코드를 이어서 실행한다.

이 예제를 통해 wait()의 역할이 명확하게 드러난다. wait()가 없다면 부모와 자식 중 누가 먼저 실행될지 보장할 수 없지만, wait()를 사용함으로써 자식이 종료된 이후에 부모가 실행된다는 순서가 확실하게 보장된다. 결국 wait()는 부모 프로세스가 자식 프로세스의 종료를 기다리고 그 결과를 처리하는 방법이라고 이해하면 된다.


Zombie Processes

exit() and wait() must be used as a matched pair. When the two don't properly interlock, a state called a zombie process occurs.

A zombie process is a process whose execution has already ended but which hasn't been fully cleaned up and remains in the system as a trace. When a child process calls exit() to terminate, the operating system temporarily holds onto the exit status value so the parent process can retrieve it. But if the parent process never calls wait(), no one retrieves that information. Even though the child's execution is over, its termination information hasn't been collected, so it can't be fully cleaned up and remains in the system.

A zombie process itself doesn't use the CPU, so it might not seem like a major problem at first. But as zombie processes accumulate, they continue to occupy system resources like PIDs — and over time, this leads to waste of system resources. Therefore, when writing a program that creates child processes, the parent process must always properly handle the child's termination through wait().

좀비 프로세스

exit()wait()는 짝을 이루어 사용되어야 한다. 이 둘이 제대로 맞물리지 않으면 좀비 프로세스라는 상태가 발생한다.

좀비 프로세스란 자식 프로세스의 실행은 이미 끝났지만 완전히 정리되지 않고 시스템에 흔적으로 남아있는 상태의 프로세스를 말한다. 자식 프로세스가 exit()를 호출해 종료되면 운영체제는 그 종료 상태값을 부모 프로세스가 확인할 수 있도록 잠시 보관해둔다. 그런데 부모 프로세스가 wait()를 호출하지 않으면 이 정보를 아무도 회수하지 않는 상황이 된다. 자식 프로세스는 실행이 끝났음에도 불구하고 종료 정보가 회수되지 않아 완전히 정리되지 못한 채 시스템에 남게 되는 것이다.

좀비 프로세스 자체는 CPU를 사용하지 않기 때문에 당장 큰 문제처럼 보이지 않을 수 있다. 하지만 좀비 프로세스가 누적되면 PID와 같은 시스템 자원을 계속 점유하게 되어 장기적으로 시스템 자원 낭비로 이어질 수 있다. 따라서 자식 프로세스를 생성하는 프로그램을 작성할 때는 반드시 부모 프로세스에서 wait()를 통해 자식의 종료를 올바르게 처리해주어야 한다.


How Zombie Processes Are Created

Let's look specifically at the path through which a zombie process comes into being.

When a child process calls exit(), its execution ends. At this point, the operating system must hold onto the child's termination information so the parent can retrieve it. But if the parent process never calls wait(), there's no way to deliver that information. And the operating system can't simply discard it either. The result is that the operating system leaves the child process's PCB in the kernel area as-is.

This is exactly the zombie process state. The execution is over, but the PCB hasn't been reclaimed, so the process remains in the system as a trace.

To summarize: a zombie process occurs when exit() and wait() fail to pair up. The child has terminated via exit(), but the parent hasn't retrieved the termination information via wait(). Only when these two system calls properly interlock is the child process's PCB fully cleaned out of the kernel and the process's lifecycle completely concluded.

좀비 프로세스가 만들어지는 과정

좀비 프로세스가 어떤 경로로 생겨나는지 그 과정을 구체적으로 살펴보자.

자식 프로세스가 exit()를 호출하면 실행은 끝난다. 이때 운영체제는 자식 프로세스의 종료 정보를 부모 프로세스가 확인할 수 있도록 보관해두어야 한다. 그런데 부모 프로세스가 wait()를 호출하지 않으면 이 종료 정보를 전달할 방법이 없다. 그렇다고 운영체제가 이 정보를 그냥 버릴 수도 없다. 결국 운영체제는 자식 프로세스의 PCB를 커널 영역에 그대로 남겨두게 된다.

바로 이 상태가 좀비 프로세스다. 실행은 이미 끝났지만 PCB가 회수되지 않아 시스템에 흔적으로 남아있는 프로세스인 것이다.

정리하면 좀비 프로세스는 exit()wait()가 짝을 이루지 못할 때 발생한다. 자식은 exit()로 종료했지만 부모가 wait()로 종료 정보를 회수하지 않은 상황이다. 이 두 시스템 호출이 제대로 맞물려야만 자식 프로세스의 PCB가 커널에서 완전히 정리되고, 프로세스의 생명 주기가 온전히 마무리된다.


The Impact of Zombie Processes on the System

Because a zombie process has finished executing, it doesn't use the CPU or perform any computation. So it can appear as though there's no immediate problem.

However, a zombie process's PCB remains in the kernel area, meaning it continues to occupy system resources like PIDs. PIDs are a limited resource in the system. As zombie processes begin to accumulate one by one, the available PID pool shrinks, and in extreme cases, the system can reach a point where new processes can no longer be created.

In the end, as zombie processes accumulate, PID resources are wasted and the operating system's management efficiency degrades ; a state where resources are being held without any execution taking place. This is why the parent process must always properly handle child process termination through wait().

좀비 프로세스가 시스템에 미치는 영향

좀비 프로세스는 실행이 끝난 상태이기 때문에 CPU를 사용하거나 어떤 연산을 수행하지는 않는다. 그래서 당장 눈에 띄는 문제가 없는 것처럼 보일 수 있다.

하지만 좀비 프로세스는 PCB가 커널 영역에 그대로 남아있기 때문에 PID와 같은 시스템 자원을 계속 점유하고 있다. PID는 시스템에서 사용할 수 있는 개수가 한정되어 있는 자원이다. 좀비 프로세스가 하나둘 쌓이기 시작하면 사용 가능한 PID가 그만큼 줄어들고, 극단적인 경우에는 새로운 프로세스를 생성할 수 없는 상황까지 이어질 수 있다.

결국 좀비 프로세스가 누적될수록 PID 자원이 낭비되고 운영체제의 관리 효율도 떨어지게 된다. 실행도 하지 않으면서 자원만 붙들고 있는 상태가 계속되는 것이다. 이것이 부모 프로세스에서 반드시 wait()를 통해 자식 프로세스의 종료를 제대로 처리해주어야 하는 이유다.


Analyzing the Zombie Process Example

This example is code that lets you directly observe how a zombie process is created when wait() is absent.

The child process prints a "Child process terminated" message and immediately terminates. Meanwhile, the parent process, without calling wait(), prints a "Parent process running" message and enters the waiting state via sleep(2).

The output order can change every time the program runs, because after fork(), which of the parent or child runs first is determined by the operating system's scheduling. But what's important in this example is not the output order. The key is that a situation has been created where the child process can terminate first while the parent hasn't called wait().

Even after the child terminates, if the parent never calls wait(), the operating system cannot immediately clean up the child's termination information. So it leaves part of the child's PCB in the kernel temporarily. This state — execution finished but termination information not yet collected — is exactly the condition that produces a zombie process.

Ultimately, this example illustrates what goes wrong when exit() and wait() fail to pair up. Any program that creates child processes must use wait() to properly handle the child's termination.

좀비 프로세스 발생 예제 분석

이 예제는 wait()가 없을 때 좀비 프로세스가 어떻게 만들어지는지를 직접 확인할 수 있는 코드다.

자식 프로세스는 "Child process 종료" 메시지를 출력한 뒤 바로 종료된다. 반면 부모 프로세스는 wait()를 호출하지 않은 채 "Parent process 실행중" 메시지를 출력하고 sleep(2)로 대기 상태에 들어간다.

출력 순서는 실행할 때마다 달라질 수 있다. fork() 이후에는 부모와 자식 중 누가 먼저 실행될지 운영체제의 스케줄링에 따라 결정되기 때문이다. 하지만 이 예제에서 중요한 것은 출력 순서가 아니다. 핵심은 부모 프로세스가 wait()를 호출하지 않은 상태에서 자식 프로세스가 먼저 종료될 수 있는 상황이 만들어졌다는 점이다.

자식이 종료된 이후에도 부모가 wait()를 호출하지 않으면 운영체제는 자식 프로세스의 종료 정보를 바로 정리하지 못한다. 그래서 커널에 자식 프로세스의 PCB 일부를 잠시 남겨두게 된다. 실행은 끝났지만 종료 정보가 회수되지 않은 이 상태가 바로 좀비 프로세스가 발생하는 조건이다.

결국 이 예제는 exit()wait()가 짝을 이루지 못했을 때 어떤 문제가 생기는지를 보여주는 사례다. 자식 프로세스를 생성하는 프로그램에서는 반드시 wait()를 통해 자식의 종료를 올바르게 처리해주어야 한다.


Ubuntu Practice

fork() Lab

The core takeaway from this lab is that after fork(), the parent and child processes have completely independent execution flows.

The moment fork() is called, the two processes go their separate ways. Which one runs first afterward is not determined by the program code ;it is decided by the operating system's scheduling. That's why even running the same code can produce output in a different order each time.

This is something that must always be kept in mind when writing programs that deal with processes. You should never try to control the execution order after fork() through the order in which code is written. If a guaranteed order is required, synchronization mechanisms like wait() must be used explicitly. In the end, this lab is an example that simultaneously demonstrates process independence, the non-determinism of scheduling, and the necessity of system calls to control it.

이번 실습을 통해 확인할 수 있는 핵심은 fork() 이후 부모와 자식 프로세스가 완전히 독립적인 실행 흐름을 가진다는 점이다.

fork()가 호출되는 순간 두 프로세스는 각자의 길을 간다. 이후 어느 쪽이 먼저 실행될지는 프로그램 코드가 결정하는 것이 아니라 운영체제의 스케줄링에 의해 결정된다. 그렇기 때문에 같은 코드를 실행하더라도 실행할 때마다 출력 순서가 달라질 수 있다.

이 점은 앞으로 프로세스를 다루는 프로그램을 작성할 때 반드시 염두에 두어야 할 부분이다. fork() 이후의 실행 순서를 코드 작성 순서로 제어하려 해서는 안 된다. 순서를 보장해야 하는 상황이라면 wait()와 같은 동기화 수단을 명시적으로 사용해야 한다. 결국 이번 실습은 프로세스의 독립성과 스케줄링의 비결정성, 그리고 그것을 제어하기 위한 시스템 호출의 필요성을 한꺼번에 보여주는 예제라고 할 수 있다.


exec() Lab

This example is code that lets you directly observe how a process's execution flow changes when exec() is called.

Looking at the output, the content written before the exec() call appears normally, but the code written after the exec() call does not produce output. The moment exec() succeeds, the current process's code and execution content are completely replaced by a new program. Once the replacement occurs, there is no flow to return to the original program, so the code written below it absolutely does not execute.

The core takeaway from this example is one thing: exec() does not create a new process — it replaces the entire execution content of the current process. The process itself is preserved; only the program running inside it is replaced. This example clearly demonstrates the distinction between fork() ; a call that creates a new process ; and exec(); a call that changes the content of an existing one.

이 예제는 exec()가 호출되었을 때 기존 프로그램의 실행 흐름이 어떻게 바뀌는지를 직접 확인할 수 있는 코드다.

실행 결과를 보면 exec() 호출 전에 작성된 출력은 정상적으로 나타나지만, exec() 호출 후에 작성된 코드는 출력되지 않는다. exec()가 성공하는 순간 현재 프로세스의 코드와 실행 내용이 완전히 새로운 프로그램으로 교체되기 때문이다. 교체가 이루어진 이후에는 기존 프로그램으로 돌아올 흐름 자체가 사라지기 때문에 그 아래에 작성된 코드는 절대 실행되지 않는다.

이 예제를 통해 확인할 수 있는 핵심은 하나다. exec()는 새로운 프로세스를 만드는 것이 아니라 현재 프로세스의 실행 내용을 통째로 바꾸는 것이라는 점이다. 프로세스 자체는 그대로 유지되고 그 안에서 실행되는 프로그램만 교체된다. fork()가 프로세스를 새로 만드는 호출이라면, exec()는 기존 프로세스의 내용을 바꾸는 호출이라는 차이를 이 예제가 명확하게 보여준다.


fork() + exec() Combined Lab

This example demonstrates the typical usage flow of fork() and exec() — the most commonly used combination in actual operating systems.

One thing that stands out is that the output order differs every time the program runs. The parent process prints its own message, while the child process is replaced via exec() with the ls program and outputs the directory listing. Which of the two runs first is determined by the operating system's scheduling, which is why the order of results changes with each run.

The core takeaway from this example is the basic process creation pattern in which fork() and exec() are used together. Creating a new process with fork() and then calling exec() in the child to replace it with an entirely different program — this structure is the most fundamental way programs are executed in an operating system. The pattern the shell uses to execute user commands follows exactly this approach. In a single example, you can confirm both concepts: the division of roles between fork() and exec(), and the non-determinism of execution order.

이 예제는 실제 운영체제에서 가장 많이 사용되는 fork()exec()의 전형적인 사용 흐름을 보여준다.

실행할 때마다 출력 순서가 매번 달라진다는 점이 눈에 띈다. 부모 프로세스는 자신의 메시지를 출력하고, 자식 프로세스는 exec()를 통해 ls 프로그램으로 교체되어 디렉토리 목록을 출력한다. 둘 중 누가 먼저 실행될지는 운영체제의 스케줄링이 결정하기 때문에 실행할 때마다 결과의 순서가 바뀌는 것이다.

이 예제를 통해 확인할 수 있는 핵심은 fork()exec()가 함께 사용되는 기본적인 프로세스 생성 패턴이다. fork()로 새로운 프로세스를 생성하고, 자식 프로세스에서 exec()를 호출해 전혀 다른 프로그램으로 교체하는 이 구조가 운영체제에서 프로그램을 실행하는 가장 기본적인 방식이다. 앞서 살펴봤던 쉘이 사용자의 명령어를 실행하는 방식도 바로 이 패턴을 따른다. fork()exec()의 역할 분담, 그리고 실행 순서의 비결정성이라는 두 가지 개념을 이 예제 하나로 함께 확인할 수 있다.


exit() Lab

This example is code that lets you directly observe that a process's execution terminates immediately when exit() is called.

Looking at the output, only the content written before exit(0) appears; the printf("이 문장은 실행되지 않습니다\n") written below it does not. The moment exit() is called, the process terminates on the spot instantly, and the subsequent code never even gets the chance to execute.

The core takeaway from this example is that exit() is the definitive termination point of a process. Even without reaching the end of a function or the last line of the program, the exact moment exit() is called, is when the process ends. Just like exec() examined earlier, this example clearly demonstrates that exit() is also a function from which execution flow is completely blocked after it is called.

이 예제는 exit()가 호출되었을 때 프로세스의 실행이 즉시 종료됨을 직접 확인할 수 있는 코드다.

실행 결과를 보면 exit(0) 이전에 작성된 내용만 출력되고, 그 아래에 작성된 printf("이 문장은 실행되지 않습니다\n")는 출력되지 않는다. exit()가 호출되는 순간 프로세스의 실행이 그 자리에서 즉시 종료되고, 이후의 코드는 실행될 기회조차 얻지 못하기 때문이다.

이 예제를 통해 확인할 수 있는 핵심은 exit()가 프로세스의 명확한 종료 지점이라는 것이다. 함수의 끝이나 프로그램의 마지막 줄까지 도달하지 않더라도, exit()가 호출된 바로 그 순간이 프로세스가 끝나는 시점이 된다. 앞서 살펴본 exec()와 마찬가지로 exit() 역시 호출 이후의 흐름이 완전히 차단되는 함수라는 점을 이 예제가 명확하게 보여준다.


wait() Lab

This example is code that lets you directly observe the behavior of a parent process waiting for a child process to terminate when wait(NULL) is called.

Looking at the output, the "child process running" message appears first, followed by a brief pause, then the "parent running after child terminated" message. The parent process's output only appears after the child process has run first and fully terminated.

This is possible because the parent process called wait(NULL) and entered the waiting state until the child terminated. We said that after fork(), there's no guarantee which of the parent or child runs first — but by using wait(), the execution order is clearly guaranteed: the parent only moves to the next step after the child has fully terminated.

The core takeaway from this example is that wait() does more than simply wait for the child to terminate — it plays the role of synchronizing the execution order between the parent and child processes.

이 예제는 부모 프로세스가 wait(NULL)을 호출했을 때 자식 프로세스의 종료를 기다리는 동작을 직접 확인할 수 있는 코드다.

실행 결과를 보면 "자식 프로세스 실행" 메시지가 먼저 출력되고, 잠시 텀이 생긴 뒤에 "자식 종료 후 부모 실행" 메시지가 출력된다. 자식 프로세스가 먼저 실행되고 완전히 종료된 이후에야 부모 프로세스의 출력이 나타나는 것이다.

이것이 가능한 이유는 부모 프로세스가 wait(NULL)을 호출하며 자식 프로세스가 종료될 때까지 대기 상태에 들어갔기 때문이다. fork() 이후에는 부모와 자식 중 누가 먼저 실행될지 보장할 수 없다고 했는데, wait()를 사용함으로써 자식이 완전히 종료된 이후에 부모가 다음 단계로 넘어간다는 실행 순서가 명확하게 보장된다.

이 예제를 통해 확인할 수 있는 핵심은 wait()가 단순히 자식의 종료를 기다리는 것을 넘어, 부모와 자식 프로세스 사이의 실행 순서를 동기화하는 역할을 한다는 점이다.


이 예제는 부모 프로세스가 wait()를 호출하지 않았을 때 좀비 프로세스가 발생하는 상황을 직접 확인할 수 있는 코드다.

파일을 실행하면 자식 프로세스는 메시지를 출력하고 종료된다. 부모 프로세스는 sleep()으로 대기 상태에 들어간다. 겉으로 보기에는 자식 프로세스가 정상적으로 종료된 것처럼 보인다. 출력도 나왔고 실행도 끝났으니 아무 문제가 없어 보이는 것이다.

하지만 부모 프로세스 코드에 wait()가 없다. 자식 프로세스가 exit()로 종료되었지만 부모가 wait()를 호출하지 않았기 때문에 운영체제는 자식의 종료 정보를 부모에게 전달하지 못한 채 커널에 그대로 남겨두게 된다. 실행은 끝났지만 PCB가 완전히 정리되지 않은 상태, 즉 좀비 프로세스가 만들어진 것이다.

이 예제가 보여주는 핵심은 좀비 프로세스는 겉으로 드러나지 않는다는 점이다. 출력 결과만 봐서는 정상적으로 종료된 것과 구별이 되지 않는다. 하지만 내부적으로는 커널에 자식 프로세스의 정보가 남아 자원을 점유하고 있다. 이것이 wait()를 반드시 짝을 맞춰 사용해야 하는 이유다.


More from this blog

Writing Documents in a Linux Environment

1️⃣ Text file2️⃣ Use a text editor 들어가며 오늘은 리눅스 환경에서 문서를 작성하는 방법을 살펴본다. 깊이 파고드는 내용은 아니지만, "이런 방법도 있구나" 정도로 알아두면 언젠가 분명 유용하게 쓸 날이 온다. 특히 문서를 단순하고 다양한 방식으로 배포해야 할 상황이 온다면, 오늘 배운 내용이 좋은 선택지가 되어줄 것이다. 1차

Apr 15, 202613 min read
Writing Documents in a Linux Environment

My dev journey

128 posts