<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[My dev journey ]]></title><description><![CDATA[Hi there, I'm a full time software engineering student, and full time mum based in Brisbane, QLD, Australia. 

Korean is my native language and English is my second, but I love learning software in English. 

My posts are a mix of both Korean and English, so there's something for everyone! 

All my posts come straight from my lecture notes, so follow along my study journey with me <3]]></description><link>https://heesu.tech</link><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Apr 2026 23:34:59 GMT</lastBuildDate><atom:link href="https://heesu.tech/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[CPU Scheduling: From Basics to Execution Flow]]></title><description><![CDATA[1️⃣ Basic Concepts and Necessity of CPU Scheduling2️⃣ Management Structure and Selection Criteria of CPU Scheduling3️⃣ CPU Scheduling Execution Flow

1️⃣ Basic Concepts and Necessity of CPU Scheduling]]></description><link>https://heesu.tech/cpu-scheduling-from-basics-to-execution-flow</link><guid isPermaLink="true">https://heesu.tech/cpu-scheduling-from-basics-to-execution-flow</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Thu, 09 Apr 2026 14:00:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/21fd488f-a09f-47c8-9aa1-644fb64087e4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>1️⃣ Basic Concepts and Necessity of CPU Scheduling<br />2️⃣ Management Structure and Selection Criteria of CPU Scheduling<br />3️⃣ CPU Scheduling Execution Flow</p>
<hr />
<h1>1️⃣ Basic Concepts and Necessity of CPU Scheduling</h1>
<h3>CPU Scheduling; Who Does the OS Give the CPU to First?</h3>
<p>Last week, we examined the concepts of processes and threads, looking at how execution flows are divided and how the operating system manages execution units.</p>
<p>Once you understand these concepts, a natural question arises: when multiple processes and threads exist simultaneously, who should the CPU be allocated to first? If all processes want to run at the same time, what criteria does the operating system use to make that decision?</p>
<p>This week's topic, CPU scheduling, starts from exactly these questions.</p>
<h3>CPU 스케줄링; 운영체제는 누구에게 먼저 CPU를 줄까?</h3>
<p>지난주에는 프로세스와 스레드의 개념을 살펴보며, 실행의 흐름이 어떻게 나뉘는지 그리고 운영체제가 실행 단위를 어떻게 관리하는지 확인했다.</p>
<p>개념을 익히고 나면 자연스럽게 이런 의문이 생긴다. 여러 프로세스와 스레드가 동시에 존재할 때, CPU는 과연 누구에게 먼저 할당되어야 할까? 모든 프로세스가 동시에 실행되길 원한다면, 운영체제는 어떤 기준으로 CPU를 나눠줄까?</p>
<p>이번 주제인 <strong>CPU 스케줄링</strong>은 바로 이 질문에서 출발한다.</p>
<hr />
<h3>Why Things Appear to Run Simultaneously</h3>
<p>While listening to music, web pages still open instantly and messenger notifications keep coming through. To our eyes, all of these tasks seem to be happening at the same time.</p>
<p>In reality, the CPU can only process one task at a single moment. So are these multiple tasks truly running simultaneously?</p>
<p>The answer lies in rapid <strong>context switching</strong>. The operating system allocates the CPU to each task in extremely short intervals, taking turns. Because these switches happen so fast, it feels to the human eye as though everything is running at once.</p>
<p>So how does the operating system decide which task to run next? This is the core problem of <strong>CPU scheduling</strong>.</p>
<h3>동시에 실행되는 것처럼 보이는 이유</h3>
<p>음악을 듣는 동안에도 웹페이지는 바로 열리고, 메신저 알림은 끊기지 않는다. 우리 눈에는 이 작업들이 모두 동시에 이루어지는 것처럼 보인다.</p>
<p>그런데 사실 CPU는 한 순간에 단 하나의 작업만 처리할 수 있다. 그렇다면 이 여러 작업들은 정말로 동시에 실행되고 있는 걸까?</p>
<p>답은 <strong>빠른 전환(Context Switching)</strong> 이다. 운영체제는 각 작업에 CPU를 아주 짧은 시간 동안 번갈아 가며 할당한다. 이 전환이 너무 빠르기 때문에 사람의 눈에는 마치 모든 작업이 동시에 돌아가는 것처럼 느껴지는 것이다.</p>
<p>그렇다면 운영체제는 어떤 기준으로 다음 실행할 작업을 결정할까? 이것이 바로 <strong>CPU 스케줄링</strong>의 핵심 문제다.</p>
<hr />
<h3>The Core Problem of CPU Scheduling</h3>
<p>Let's look at the situation more concretely. Opening a web page requires the CPU to process data. Displaying a messenger notification also uses the CPU. Even playing music involves CPU processing.</p>
<p>If all three of these tasks demand the CPU at the same moment, what choice should the operating system make? Should it run whichever task arrived first? Should it process the task that finishes fastest? Should it prioritize tasks that directly interact with the user?</p>
<p>The problem of deciding who gets the CPU, when, and for how long is the essence of CPU scheduling. The criteria chosen will significantly affect the system's response time, processing efficiency, and user experience.</p>
<h3>CPU 스케줄링의 핵심 문제</h3>
<p>상황을 조금 더 구체적으로 살펴보자. 웹페이지를 열 때는 데이터 처리를 위해 CPU가 필요하고, 메신저 알림을 표시할 때도, 음악이 재생되는 동안에도 CPU 처리가 이루어진다.</p>
<p>만약 이 세 가지 작업이 동시에 CPU를 요구하는 순간이 온다면 운영체제는 어떤 선택을 해야 할까?</p>
<ul>
<li><p>먼저 도착한 작업을 실행할까?</p>
</li>
<li><p>가장 빨리 끝나는 작업을 먼저 처리할까?</p>
</li>
<li><p>사용자와 직접 상호작용하는 작업을 우선시할까?</p>
</li>
</ul>
<p>이처럼 <strong>CPU를 누구에게, 언제, 얼마나 줄 것인가</strong>를 결정하는 문제가 바로 CPU 스케줄링의 핵심이다. 어떤 기준을 선택하느냐에 따라 시스템의 반응속도, 처리 효율, 사용자 경험이 크게 달라질 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8c83b854-dbed-42fc-b14a-d53ee2878c2b.png" alt="" style="display:block;margin:0 auto" />

<h3>The Multi-Programming Environment</h3>
<p>Modern operating systems keep multiple programs loaded in memory at the same time. Browsers, messengers, music players, and document editors all exist in a ready state simultaneously- meaning there are always multiple candidates requesting execution.</p>
<p>However, the CPU cannot process multiple programs at once. It can only handle one execution flow at a time, so even when many programs exist, the CPU must take turns processing them in order.</p>
<p>The operating system selects one of the ready processes and assigns the CPU to it. In a multi-programming environment, the process of deciding in what order the CPU is used — that is, <strong>scheduling</strong> — becomes essential.</p>
<h3>다중 프로그래밍 환경</h3>
<p>현대 운영체제는 여러 프로그램을 동시에 메모리에 올려둔다. 브라우저, 메신저, 음악 재생기, 문서 편집기 등 여러 프로세스가 동시에 준비 상태에 존재한다는 뜻이다. 즉, 실행을 요구하는 대상이 여러 개인 상황이 항상 발생한다.</p>
<p>그러나 CPU는 동시에 여러 프로그램을 처리할 수 있는 장치가 아니다. 한 시점에는 하나의 실행 흐름만 처리할 수 있기 때문에, 여러 프로그램이 존재하더라도 CPU는 순서를 정해 번갈아 가며 처리할 수밖에 없다.</p>
<p>이때 운영체제는 준비 상태에 있는 여러 프로세스 중 하나를 선택해 CPU를 할당한다. 결국 다중 프로그래밍 환경에서는 <strong>CPU를 어떤 순서로 사용할지 결정하는 과정</strong>, 즉 스케줄링이 필수적으로 요구된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d23b30ce-25bb-480d-8077-9b69c1ff3e47.png" alt="" style="display:block;margin:0 auto" />

<h3>The Illusion of Simultaneous Execution</h3>
<p>The execution order is never directly exposed to the user. As a result, each program appears to run independently. The browser opens pages on its own; the messenger displays notifications on its own.</p>
<p>But in reality, the operating system is coordinating all of these execution flows behind the scenes. It continuously decides which process to keep in the running state and when to switch to another.</p>
<p>Because this process is invisible to the user, we feel as though multiple programs are running simultaneously. This is what we call the <strong>illusion of concurrent execution</strong> — and making this illusion feel natural is one of the key roles of CPU scheduling.</p>
<h3>사용자가 느끼는 동시 실행의 착시</h3>
<p>실행 순서는 사용자에게 직접 노출되지 않는다. 그 결과 각 프로그램은 마치 독립적으로 실행되는 것처럼 보인다. 브라우저는 브라우저대로 페이지를 열고, 메신저는 메신저대로 알림을 표시한다.</p>
<p>하지만 실제로는 운영체제가 이 모든 실행 흐름을 보이지 않는 곳에서 조율하고 있다. 어떤 프로세스를 실행 상태로 둘 것인지, 언제 다른 프로세스로 전환할 것인지를 운영체제가 끊임없이 결정한다.</p>
<p>이 과정이 사용자 눈에 보이지 않기 때문에, 우리는 여러 프로그램이 동시에 실행되는 것처럼 느끼게 된다. 이것이 바로 <strong>동시 실행의 착시</strong>다. 그리고 이 착시를 자연스럽게 만드는 것이 곧 CPU 스케줄링의 역할이기도 하다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/384982a8-7db6-4218-9d44-8b9c711cf24e.png" alt="" style="display:block;margin:0 auto" />

<h3>The Secret of Apparent Simultaneous Execution</h3>
<p>Why does it look like multiple programs are running at the same time? As mentioned, the CPU handles only one task at a time. Execution targets are processed one by one in order, not simultaneously.</p>
<p>The secret is in execution switching. The operating system pauses the currently running process and switches to another. When needed, it returns to the previous process. Because this switching repeats at very high speed, it feels to the user as though multiple programs are operating at once.</p>
<p>The key point is that multiple processes are not truly running at the same time. The operating system rapidly swaps out which process is using the CPU — this is the actual mechanism behind multi-programming environments.</p>
<h3>실행 전환: 동시 실행의 비밀</h3>
<p>왜 여러 프로그램이 동시에 실행되는 것처럼 보이는 걸까? 앞서 말했듯 CPU는 한 시점에 하나의 작업만 처리한다. 실행 대상은 동시에 처리되는 것이 아니라 순서를 정해 하나씩 처리된다.</p>
<p>그 비밀은 <strong>실행 전환</strong>에 있다. 운영체제는 현재 실행 중인 프로세스를 잠시 멈추고 다른 프로세스로 전환한다. 그리고 다시 필요해지면 이전에 실행하던 프로세스로 돌아온다. 이 전환이 매우 빠른 속도로 반복되기 때문에, 사용자 입장에서는 여러 프로그램이 동시에 작동하는 것처럼 느껴지는 것이다.</p>
<p>여기서 중요한 점은, 여러 프로세스를 진짜로 동시에 실행하는 것이 아니라는 사실이다. 운영체제가 실행 대상을 빠르게 바꾸면서 CPU를 나눠 쓰는 방식, 이것이 다중 프로그래밍 환경의 실제 동작 원리다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/95780c82-5ec6-4628-b8ad-97174ee2bb5b.png" alt="" style="display:block;margin:0 auto" />

<h3>How Is the Next Process Selected?</h3>
<p>We now know the CPU alternates between multiple execution targets. A natural question follows: among the many processes in the ready state, who gets the CPU first?</p>
<p>This is not simply a matter of ordering. Which task runs first directly affects the overall behavior and performance of the system. If a heavy task occupies the CPU for a long time, lighter tasks the user is waiting for will be delayed accordingly.</p>
<p>So who makes this decision? The operating system does. It judges what criteria to use when selecting the next process to run, and this judgment directly affects the response speed and efficiency that users experience.</p>
<p>Ultimately, how execution order is determined governs the system's performance and capability. This is why CPU scheduling is considered a core responsibility of the operating system, not just a routine management task.</p>
<h3>실행 대상은 어떻게 선택될까?</h3>
<p>CPU가 여러 실행 대상을 번갈아 처리한다는 것을 알았다. 그렇다면 자연스럽게 다음 질문이 생긴다. <strong>준비 상태에 있는 여러 프로세스 중 누가 먼저 CPU를 사용할 것인가?</strong></p>
<p>이 선택은 단순한 순서의 문제가 아니다. 어떤 작업을 먼저 실행하느냐에 따라 시스템 전체의 동작 방식과 성능이 직접적으로 달라진다. 예를 들어 무거운 작업이 CPU를 오래 점유하면, 사용자가 기다리는 가벼운 작업은 그만큼 늦게 처리된다.</p>
<p>그렇다면 이 순서는 누가 결정하는 걸까? 바로 운영체제다. 운영체제는 어떤 기준으로 다음 실행할 프로세스를 선택할지 판단하며, 이 판단이 사용자가 체감하는 반응 속도와 시스템 효율에 직결된다.</p>
<p>결국 <strong>실행 순서를 어떻게 정하느냐</strong>가 시스템의 성능과 가능성을 좌우한다. 이것이 CPU 스케줄링이 단순한 관리 기법을 넘어 운영체제의 핵심 역할로 다뤄지는 이유다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/507c93b8-e8b1-41c4-aaed-dcf6c5ec58cd.png" alt="" style="display:block;margin:0 auto" />

<h3>The Ready Queue and CPU Scheduling</h3>
<p>A process does not stay in one place — it moves through a flow of states.</p>
<p>At the center of that flow is the <strong>Ready Queue</strong>. The ready queue is where processes waiting to be assigned the CPU gather. Processes that are prepared to execute but have not yet received the CPU wait here.</p>
<p>When a process requests I/O, it leaves the CPU and moves to an <strong>I/O device queue</strong>. Once the I/O completes, it returns to the ready queue to wait for the CPU again. Processes continuously move between these two queues based on their state.</p>
<p>The important point is that CPU scheduling happens in the <strong>ready queue</strong>. The operating system selects one process from the ready queue and assigns it the CPU — and this selection process is CPU scheduling itself.</p>
<h3>준비 큐와 CPU 스케줄링</h3>
<p>프로세스는 한 곳에 머무르는 것이 아니라, 상태에 따라 이동하는 흐름으로 구성되어 있다.</p>
<p>그 흐름의 중심에 <strong>준비 큐(Ready Queue)</strong> 가 있다. 준비 큐는 CPU를 할당받기 위해 기다리는 프로세스들이 모여 있는 공간이다. 실행 준비는 완료되었지만 아직 CPU를 받지 못한 상태의 프로세스들이 여기에 대기한다.</p>
<p>한편 프로세스가 입출력을 요청하면 CPU를 떠나 <strong>입출력 장치 큐</strong>로 이동한다. 입출력 작업이 끝나면 다시 준비 큐로 돌아와 CPU 할당을 기다린다. 이처럼 프로세스는 준비 큐와 입출력 장치 큐 사이를 상태에 따라 오가는 구조다.</p>
<p>중요한 점은 <strong>CPU 스케줄링은 준비 큐에서 일어난다</strong>는 것이다. 운영체제는 준비 큐에 있는 프로세스 중 하나를 선택해 CPU를 할당하며, 이 선택 과정 자체가 바로 CPU 스케줄링이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5770bd1e-ae8e-4c60-a070-34c8f75a50a3.png" alt="" style="display:block;margin:0 auto" />

<h3>What CPU Scheduling Means</h3>
<p>In an environment where multiple processes share a single CPU, many processes may be ready to execute. But only one can actually use the CPU at any given moment. The operating system must therefore choose which of the candidate processes to transition into the running state.</p>
<p>This process of deciding which process to run next among many candidates is CPU scheduling. Put simply, CPU scheduling is the operating system's decision-making process for determining which process executes first.</p>
<h3>CPU 스케줄링의 의미</h3>
<p>지금까지의 내용을 바탕으로 CPU 스케줄링의 의미를 정리해보자.</p>
<p>하나의 CPU를 여러 프로세스가 함께 사용하는 환경에서는 실행 가능한 프로세스가 여러 개 존재한다. 하지만 실제로 CPU를 사용하는 대상은 한 시점에 단 하나다. 따라서 운영체제는 여러 프로세스 후보 중에서 어떤 프로세스를 실행 상태로 전환할지 선택해야 한다.</p>
<p>이처럼 <strong>여러 프로세스 중에서 실행할 대상을 결정하는 과정이 바로 CPU 스케줄링</strong>이다. 다시 말해, CPU 스케줄링이란 어떤 프로세스를 먼저 실행할지를 판단하는 운영체제의 결정 과정이라고 할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b46b620d-dd14-4f60-bd12-492d6e2aadb0.png" alt="" style="display:block;margin:0 auto" />

<h3>When Does CPU Scheduling Occur?</h3>
<p>A process stays in the ready state until it is assigned the CPU. The operating system selects one ready process and transitions it to the running state. In other words, CPU scheduling occurs at the moment a process transitions from the ready state to the running state.</p>
<p>An important nuance: scheduling does not happen at every state change. It only occurs when the CPU needs to be newly assigned — when a new process must be chosen to run.</p>
<h3>CPU 스케줄링은 언제 발생할까?</h3>
<p>CPU 스케줄링이 무엇인지 알았다면, 이제 그것이 <strong>언제</strong> 일어나는지를 살펴보자.</p>
<p>프로세스는 CPU를 할당받기 전까지 준비 상태에 머문다. 운영체제는 준비 상태에 있는 프로세스 중 하나를 선택해 실행 상태로 전환한다. 즉, <strong>CPU 스케줄링은 프로세스가 준비 상태에서 실행 상태로 전환되는 순간에 발생</strong>한다.</p>
<p>여기서 중요한 점은, 모든 상태 변화에서 스케줄링이 발생하는 것이 아니라는 것이다. CPU를 새로 할당해야 하는 시점, 즉 실행할 프로세스를 새로 골라야 하는 순간에만 스케줄링이 이루어진다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b5ddb803-b83d-4606-98e1-26f7ffd70feb.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>When Does Scheduling Repeat?</strong></h3>
<p>After a process is created, it moves through various states. For example, when a running process transitions from the running state to the waiting state due to an I/O request or time expiration, the CPU becomes idle. The operating system then selects another process from the ready state and assigns the CPU to it. Once the I/O completes, the process returns to the ready state and becomes a candidate for the next execution.</p>
<p>In this way, scheduling occurs repeatedly every time a process undergoes a state change.</p>
<p>It is worth noting that throughout this process, the long-term, medium-term, and short-term schedulers each intervene at different points. For now, however, it is enough to understand the overall structure. The specific role of each scheduler will be covered separately in the next session.</p>
<h3>스케줄링은 언제 반복되는가?</h3>
<p>프로세스는 생성 이후 여러 상태를 오가게 된다. 예를 들어 실행 중이던 프로세스가 입출력 요청이나 시간 만료로 인해 실행 상태에서 대기 상태로 이동하면, CPU는 비게 된다. 그러면 운영체제는 준비 상태에 있는 다른 프로세스를 선택해 CPU를 할당한다. 입출력이 끝난 프로세스는 다시 준비 상태로 돌아와 다음 실행 대상의 후보가 된다.</p>
<p>이처럼 프로세스의 상태 변화가 일어날 때마다 스케줄링은 반복적으로 발생한다.</p>
<p>한편 이 과정에는 <strong>단기, 중기, 장기 스케줄러</strong>가 각각 서로 다른 지점에서 개입한다. 다만 이번 내용에서는 전체적인 구조만 이해하는 것으로 충분하다. 각 스케줄러의 구체적인 역할은 다음 시간에 따로 다룰 예정이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8ea6fdbe-0b11-467e-a1e0-98f1c7ebaa93.png" alt="" style="display:block;margin:0 auto" />

<h3>The Repeated Cycle of Process State Changes and Scheduling</h3>
<p>After creation, a process continuously cycles between ready, running, and waiting states. Each time a state change occurs, scheduling happens again.</p>
<p>Scheduling is not a one-time event. It is a continuous decision process that repeats every time a process changes state. When a running process transitions to a waiting state due to an I/O request or time expiration, the CPU becomes free, and a process that returns to the ready state becomes a candidate again.</p>
<p>Ultimately, scheduling is required every time the CPU becomes idle. The operating system selects the next process from the ready queue at each such moment, and this cycle repeats continuously for as long as the system runs.</p>
<h3>프로세스 상태 변화와 스케줄링</h3>
<p>프로세스는 생성 이후 준비, 실행, 대기 상태 사이를 계속해서 오간다. 그리고 이 상태 변화가 일어날 때마다 스케줄링은 반복적으로 발생한다.</p>
<p>즉, 스케줄링은 한 번으로 끝나는 과정이 아니다. 프로세스의 상태 전환이 일어날 때마다 계속 이루어지는 결정 과정이다. 실행 중이던 프로세스가 입출력 요청이나 시간 만료로 인해 상태가 바뀌면 CPU는 비게 되고, 준비 상태로 돌아온 프로세스는 다시 선택 대상이 된다.</p>
<p>결국 <strong>CPU가 비는 순간마다 스케줄링이 필요하다.</strong> 운영체제는 그 순간마다 준비 큐에서 다음 실행할 프로세스를 선택하고, 이 과정이 시스템이 동작하는 내내 끊임없이 반복된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/39213160-6c05-4589-a89d-cde46f0965c5.png" alt="" style="display:block;margin:0 auto" />

<h3>Why Is CPU Scheduling Necessary?</h3>
<p>The CPU can only process one task at a time. Yet execution requests continuously arrive from multiple sources simultaneously. Many tasks demand the CPU at once, and the CPU cannot handle them all immediately.</p>
<p>Therefore, a process for deciding which task to handle first — determining execution order — is absolutely necessary. Without establishing execution order, the system cannot function normally.</p>
<p>Deciding execution order is not optional. CPU scheduling is a structurally necessary process in any multi-programming environment.</p>
<h3>CPU 스케줄링은 왜 필요한가?</h3>
<p>CPU는 한 시점에 하나의 작업만 처리할 수 있다. 그러나 실행 요청은 계속해서 여러 곳에서 동시에 발생한다. 여러 작업이 동시에 CPU를 요구하지만, CPU는 그 모든 요청을 즉시 처리할 수 없다.</p>
<p>따라서 어떤 작업을 먼저 처리할 것인지, 실행 순서를 결정하는 과정이 반드시 필요하다. 만약 실행 순서를 정하지 않는다면 시스템은 정상적으로 동작할 수 없게 된다.</p>
<p>결국 실행 순서를 정한다는 것은 선택의 문제가 아니다. <strong>CPU 스케줄링은 다중 프로그래밍 환경에서 구조적으로 반드시 필요한 과정</strong>이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/46e9fc4d-bf4d-4c80-90f7-82c9c9663655.png" alt="" style="display:block;margin:0 auto" />

<h3>Why Execution Order Matters</h3>
<p>The result of scheduling ultimately manifests as waiting time. Depending on which task is selected first, the time other tasks must wait before receiving the CPU changes — and so does when they get a response.</p>
<p>Users have no visibility into which algorithm the system uses internally. Instead, they evaluate the system based on how quickly the screen responds and how fast requests are answered. Even with the same resources and tasks, the performance users perceive can vary significantly depending on execution order.</p>
<p>In other words, scheduling is not merely about setting an order of execution. It is a critical factor that determines the performance users experience.</p>
<h2>실행 순서가 중요한 이유</h2>
<p>그렇다면 실행 순서는 왜 중요할까? 스케줄링의 결과는 결국 <strong>대기 시간</strong>으로 나타난다. 어떤 작업을 먼저 선택하느냐에 따라 다른 작업이 CPU를 받을 때까지 기다려야 하는 시간이 달라지고, 그에 따라 응답 시점도 달라지기 때문이다.</p>
<p>사용자는 시스템 내부에서 어떤 알고리즘이 사용되는지 알지 못한다. 대신 화면이 얼마나 빨리 반응하는지, 요청에 얼마나 빠르게 응답하는지를 기준으로 시스템을 평가한다. 같은 자원, 같은 작업이라도 실행 순서에 따라 사용자가 체감하는 성능은 크게 달라질 수 있다.</p>
<p>즉, 스케줄링은 단순히 실행 순서를 정하는 문제가 아니다. <strong>사용자가 느끼는 시스템의 성능을 결정하는 핵심 요소</strong>다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/784c0c1b-124c-4b19-a646-e8a71660d0f1.png" alt="" style="display:block;margin:0 auto" />

<h3>The Purpose of CPU Scheduling</h3>
<p>The CPU is the busiest resource in any system. Since multiple tasks are constantly competing for it, minimizing idle CPU time is essential. How execution opportunities are divided among tasks is equally important.</p>
<p>The goal is not simply to finish any particular task quickly. How execution opportunities are distributed directly determines the overall performance of the system.</p>
<p>The purpose of CPU scheduling is to use the CPU efficiently while improving total system performance. Scheduling is the operating system's strategy for maximizing performance without wasting CPU resources.</p>
<h3>CPU 스케줄링의 목적</h3>
<p>지금까지 왜 실행 순서를 정해야 하는지 살펴보았다. 그렇다면 스케줄링은 무엇을 잘하기 위해 존재하는 걸까?</p>
<p>CPU는 시스템에서 가장 바쁜 자원이다. 여러 작업이 동시에 CPU를 기다리는 상황이 반복되기 때문에, CPU가 쉬고 있는 상황을 최소화하는 것이 중요하다. 동시에 여러 작업에게 실행 기회를 어떻게 나눌지도 핵심적인 문제다.</p>
<p>여기서 중요한 점은, 단순히 어떤 작업을 빨리 끝내는 것이 목표가 아니라는 것이다. 실행 기회를 어떻게 분배하느냐에 따라 전체 시스템의 성능이 달라지기 때문이다.</p>
<p>결국 CPU 스케줄링의 목적은 <strong>CPU를 효율적으로 사용하면서 전체 시스템의 성능을 향상시키는 것</strong>이다. 즉, 스케줄링은 CPU를 낭비하지 않으면서 시스템 성능을 높이기 위한 운영체제의 전략이라고 볼 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b05779fb-62d0-4261-b7e4-fbf8a3a8a580.png" alt="" style="display:block;margin:0 auto" />

<h3>Another Goal: Fairness and Stability</h3>
<p>The goal of CPU scheduling is not performance alone. Efficiency matters, but in an environment where many processes share the CPU, fairness is also critical.</p>
<p>No single process should be allowed to monopolize the CPU indefinitely. Nor should any process go unselected for so long that it waits forever — this situation is called <strong>starvation</strong>, and when it recurs, trust in the system erodes.</p>
<p>As the number of competing tasks grows, competition intensifies. If scheduling criteria are designed to favor certain tasks, others will consistently be pushed back. When this happens repeatedly, it becomes difficult to predict when any given task will execute, and responsiveness suffers.</p>
<p>Scheduling is therefore not just a matter of increasing throughput. It is equally a matter of maintaining fairness and stable responsiveness.</p>
<h3>스케줄링의 또 다른 목표: 공정성과 안정성</h3>
<p>CPU 스케줄링의 목표는 성능 향상만이 아니다. CPU를 효율적으로 사용하는 것도 중요하지만, 여러 프로세스가 함께 사용하는 환경에서는 <strong>공정성</strong>도 중요한 요소다.</p>
<p>특정 프로세스가 CPU를 계속 독점하는 상황은 발생해선 안 된다. 또한 어떤 프로세스가 오랫동안 선택받지 못해 계속 대기하는 상황도 문제가 된다. 이를 <strong>기아 상태(Starvation)</strong> 라고 하며, 이 상태가 반복되면 시스템에 대한 신뢰가 떨어질 수밖에 없다.</p>
<p>실행을 요구하는 작업이 많아질수록 작업 간 경쟁도 심해진다. 이때 스케줄링 기준이 특정 작업에 유리하게 설계되어 있다면, 일부 작업은 계속 밀릴 수밖에 없다. 이런 상황이 반복되면 어떤 작업이 언제 실행될지 예측하기 어려워지고, 응답성에도 부정적인 영향을 미치게 된다.</p>
<p>따라서 스케줄링은 단순히 처리 속도를 높이는 문제가 아니라, <strong>공정성과 안정적인 응답성을 함께 유지하는 문제</strong>이기도 하다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/7be56b06-ad3f-41d0-931c-202dae89cb1c.png" alt="" style="display:block;margin:0 auto" />

<h3>Why Multiple Scheduling Approaches Are Needed</h3>
<p>Can a single execution-order criterion apply to every situation? The priorities vary by system purpose. Sometimes fast response is critical; sometimes total throughput matters more; sometimes fairness is the top priority. The most appropriate scheduling approach depends on which criterion takes precedence.</p>
<p>The operating system selects an execution-order criterion to match the system's purpose, and the system's performance characteristics change accordingly. This is why many different scheduling approaches exist rather than just one.</p>
<p>The specific behavior of each scheduling algorithm will be examined in detail in the next session.</p>
<h3>왜 다양한 스케줄링 방식이 필요한가?</h3>
<p>지금까지 스케줄링이 CPU의 효율적 사용뿐 아니라 공정성과 안정성도 함께 추구해야 한다는 것을 살펴보았다. 여기서 한 가지 질문이 생긴다. <strong>모든 상황에 동일한 실행 순서 기준을 적용할 수 있을까?</strong></p>
<p>시스템의 목적에 따라 우선순위는 달라진다. 빠른 응답이 중요한 경우도 있고, 전체 처리량이 더 중요한 경우도 있으며, 공정성이 최우선인 경우도 있다. 어떤 기준을 우선하느냐에 따라 더 적합한 스케줄링 방식이 달라지는 것이다.</p>
<p>따라서 운영체제는 시스템의 목적에 맞는 실행 순서 기준을 선택하게 되고, 그 기준에 따라 시스템의 성능 특성도 달라진다. 이것이 스케줄링 방식이 하나가 아니라 여러 가지 존재하는 이유다.</p>
<p>각각의 스케줄링 알고리즘이 구체적으로 어떻게 동작하는지는 다음 시간에 자세히 살펴볼 예정이다.</p>
<hr />
<h1>2️⃣ Management Structure and Selection Criteria of CPU Scheduling</h1>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9b22c8a0-becc-4e59-aa76-a1f32ebfebc3.png" alt="" style="display:block;margin:0 auto" />

<h3>Why the Operating System Manages Execution Order</h3>
<p>Multiple processes simultaneously request CPU execution. But because the CPU is a single resource, it cannot handle all requests at once. The result is that processes compete for one CPU.</p>
<p>Without a criterion for which process to run first, certain processes may monopolize the CPU and execution order becomes unpredictable. The operating system must therefore directly select and manage which ready process runs next.</p>
<p>What criterion guides that selection? This is the central question of CPU scheduling, and the various algorithms that answer it will be explored in the next session.</p>
<h3>CPU 실행 순서는 왜 운영체제가 관리하는가?</h3>
<p>현재 시스템에는 여러 프로세스가 동시에 CPU 실행을 요청한다. 그러나 CPU는 하나의 자원이기 때문에 모든 요청을 동시에 처리할 수 없다. 결국 여러 프로세스가 CPU 하나를 두고 경쟁하는 구도가 만들어진다.</p>
<p>이때 어떤 프로세스를 먼저 실행할지에 대한 기준이 없다면, 특정 프로세스가 CPU를 독점하거나 실행 순서를 예측하기 어려운 상황이 발생한다. 따라서 운영체제가 준비 상태에 있는 프로세스 중 어떤 것을 먼저 실행할지를 직접 선택하고 관리해야 한다.</p>
<p>그렇다면 그 선택은 어떤 기준으로 이루어질까? 이것이 바로 CPU 스케줄링의 핵심 질문이며, 다음 시간에는 이 기준들, 즉 다양한 스케줄링 알고리즘을 구체적으로 살펴볼 예정이다.</p>
<hr />
<h3><strong>Why Execution Order Must Be Managed</strong></h3>
<p>In a modern system, multiple processes can request CPU execution at the same time. However, because the CPU is a single shared resource, all processes must take turns using it. Furthermore, execution requests can overlap in time — meaning situations where multiple processes simultaneously compete for the same CPU can arise at any moment.</p>
<p>In such an environment, conflicts over execution order arise naturally. Without a criterion for deciding which process runs first, certain processes may end up holding the CPU for an extended period, and execution results become difficult to predict.</p>
<p>For this reason, a structure that systematically manages CPU execution order is absolutely necessary. This is the fundamental reason why the operating system is responsible for CPU scheduling.</p>
<h3>실행 순서가 관리되어야 하는 이유</h3>
<p>현재 시스템에서는 여러 프로세스가 동시에 CPU 실행을 요청할 수 있다. 그러나 CPU는 하나의 자원이기 때문에 여러 프로세스가 함께 공유해서 사용해야 한다. 더불어 실행 요청은 시간적으로 겹쳐서 발생할 수 있다. 즉, 여러 프로세스가 같은 CPU를 두고 동시에 실행을 요구하는 상황이 언제든 생길 수 있다.</p>
<p>이런 환경에서는 자연스럽게 실행 순서 충돌이 발생한다. 어떤 프로세스를 먼저 실행할지에 대한 기준이 없다면 특정 프로세스가 CPU를 오래 독점하게 되고, 실행 결과 역시 예측하기 어려워진다.</p>
<p>따라서 이러한 환경에서는 <strong>CPU 실행 순서를 체계적으로 관리하는 구조</strong>가 반드시 필요하다. 이것이 운영체제가 CPU 스케줄링을 담당하는 근본적인 이유다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0cd089d8-75e2-41af-94b0-845ef3e60cce.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>What Does the Operating System Manage, and How?</strong></h3>
<p>We established that a structure for managing CPU execution order is necessary. So what exactly does the operating system manage, and how does it do so?</p>
<p>First, the <strong>subject of management</strong> is the pool of candidate processes registered in the ready queue - processes that are in an executable state but have not yet been assigned the CPU. These processes are not simply waiting; they are candidates that the operating system can select as the next execution target.</p>
<p>Next, looking at the <strong>method of management</strong>: the operating system maintains a waiting order based on the ready queue and selects one process from it to transition into the running state. The entity that performs this selection is the <strong>scheduler</strong>.</p>
<p>To summarize, CPU scheduling is the process of selecting the next execution target from among the candidates in the ready queue.</p>
<h3>운영체제는 무엇을, 어떻게 관리하는가?</h3>
<p>CPU 실행 순서를 관리하는 구조가 필요하다고 했다. 그렇다면 운영체제는 구체적으로 무엇을, 어떻게 관리하는 걸까?</p>
<p>먼저 <strong>관리 대상</strong>은 준비 큐에 등록된 실행 후보 프로세스들이다. 실행 가능한 상태에 있으면서도 아직 CPU를 할당받지 못한 프로세스들이 여기에 해당한다. 이 프로세스들은 단순히 대기하는 것이 아니라, 운영체제가 다음 실행 대상으로 선택할 수 있는 후보들이다.</p>
<p>다음으로 <strong>관리 방식</strong>을 살펴보면, 운영체제는 준비 큐를 기반으로 대기 순서를 유지하고 그 중 하나의 프로세스를 선택해 실행 상태로 전환한다. 이 선택을 수행하는 주체가 바로 <strong>스케줄러</strong>다.</p>
<p>정리하면, CPU 스케줄링이란 준비 큐에 있는 실행 후보 중에서 다음 실행 대상을 선택하는 과정이다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5e3d7090-053f-450d-bf4f-41112f9b3479.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>The Scheduling Queue</h3>
<p>The <strong>scheduling queue</strong> is a data structure that collects and manages processes waiting to execute. In data structures, a queue follows <strong>FIFO (First In, First Out)</strong> — the first element in is the first one out. The operating system uses this structure to manage process waiting states.</p>
<p>There are two fundamental types of scheduling queues. The <strong>ready queue</strong> holds processes in an executable state that are waiting for CPU allocation — CPU scheduling operates on these processes. The <strong>I/O device queue</strong> is where processes go when they issue an I/O request during execution; once I/O completes, they return to the ready queue.</p>
<p>Both are queues in structure, but they are managed separately based on what is being waited for and what role each plays.</p>
<h2>스케줄링 큐란 무엇인가?</h2>
<p>앞서 실행 후보 프로세스들이 준비 큐를 중심으로 관리된다고 했다. 이처럼 실행을 기다리는 프로세스들을 모아서 관리하는 자료구조를 일반적으로 <strong>스케줄링 큐(Scheduling Queue)</strong> 라고 부른다.</p>
<p>스케줄링 큐에는 CPU를 바로 사용할 수는 없지만 실행 가능한 상태에 있는 프로세스들이 등록된다. 즉, 스케줄링 큐는 CPU 할당 대상이 되는 프로세스들의 집합이며, 운영체제는 이 큐에 있는 프로세스들을 대상으로 다음 실행 대상을 선택한다.</p>
<p>정리하면, 스케줄링 큐는 <strong>실행 대기 프로세스를 체계적으로 관리하고 실행 순서를 결정하기 위한 기준이 되는 구조</strong>다. CPU 스케줄링은 결국 이 큐를 어떻게 운용하느냐의 문제라고도 볼 수 있다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/668bab52-69c1-413b-a762-37bbfa6feb99.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dc81f576-62fa-4cbf-843a-83447eb68a62.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Process Execution Flow Based on the Scheduling Queue</strong></h3>
<p>Let's trace how a process flows through the system centered on the scheduling queue.</p>
<p>When one of the processes waiting in the ready queue is selected, it is assigned the CPU and transitions to the running state. If the process requires I/O during execution, it temporarily leaves the CPU and moves to the I/O device queue. Once the I/O completes, the process returns to the ready queue and becomes a candidate for execution again. When all of its work is finished, the process moves to the terminated state.</p>
<p>Throughout this flow, the operating system continuously selects the next execution target from the processes in the ready queue. CPU scheduling is ultimately the core mechanism that sustains this entire cyclical flow.</p>
<h3>스케줄링 큐 기반 프로세스 실행 흐름</h3>
<p>스케줄링 큐를 중심으로 프로세스가 어떻게 흘러가는지 정리해보자.</p>
<p>준비 큐에서 대기 중인 프로세스 중 하나가 선택되면 CPU를 할당받아 실행 상태로 전환된다. 실행 중에 입출력이 필요해지면 프로세스는 잠시 CPU를 떠나 입출력 장치 큐로 이동한다. 입출력이 완료되면 해당 프로세스는 다시 준비 큐로 돌아와 실행 후보가 된다. 그리고 모든 작업이 끝나면 프로세스는 종료 상태로 이동한다.</p>
<p>이 흐름 속에서 운영체제는 준비 큐에 있는 프로세스 중 다음 실행 대상을 끊임없이 선택한다. 결국 CPU 스케줄링은 이 순환적인 흐름 전체를 지탱하는 핵심 메커니즘이라고 할 수 있다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/11fc43a9-c257-46b2-afea-f62ed0291937.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Types of Scheduling Queues</strong></h3>
<p>In data structures, a queue follows a <strong>First In, First Out (FIFO)</strong> principle — the first element to enter is the first to leave. The operating system uses this structure to manage the waiting states of processes.</p>
<p>However, there is not just one queue. There are two fundamental types.</p>
<p>The first is the <strong>Ready Queue</strong>. This is where processes in an executable state wait for CPU allocation. CPU scheduling operates on the processes held in this queue.</p>
<p>The second is the <strong>I/O Device Queue</strong>. This is where a process goes when it issues an I/O request during execution. It waits here until the I/O completes, at which point it returns to the ready queue.</p>
<p>Both share the same queue structure, but the key point is that they are managed as separate queues based on what is being waited for and what role each serves.</p>
<h3>스케줄링 큐의 종류</h3>
<p>자료구조에서 큐(Queue)는 먼저 들어온 데이터가 먼저 나가는 <strong>선입선출(FIFO, First In First Out)</strong> 구조다. 운영체제도 이 큐를 활용해 프로세스의 대기 상태를 관리한다.</p>
<p>다만 이 큐가 하나만 존재하는 것이 아니다. 대표적인 기본 큐는 두 가지다.</p>
<p>첫 번째는 <strong>준비 큐(Ready Queue)</strong> 다. CPU 할당을 기다리는 실행 가능한 상태의 프로세스들이 모여 있는 공간이며, CPU 스케줄링은 바로 이 준비 큐에 있는 프로세스들을 대상으로 이루어진다.</p>
<p>두 번째는 <strong>입출력 장치 큐(I/O Device Queue)</strong> 다. 실행 도중 입출력을 요청한 프로세스가 이동하는 공간이다. 입출력이 완료될 때까지 여기서 대기하다가, 완료되면 다시 준비 큐로 돌아온다.</p>
<p>구조는 모두 큐이지만, 기다리는 대상과 역할에 따라 서로 다른 큐로 나누어 관리된다는 점이 핵심이다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4ea5eff1-915a-4f8d-b17d-54740f902f52.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Scheduling Is Not a Single Decision</strong></h3>
<p>Scheduling does not occur only once. The operating system manages execution at multiple points in time, each serving a different purpose.</p>
<p>For example, when a new job enters the system, there is a stage for deciding whether to accept it right now. When the number of executable processes grows large, there is a stage for deciding which processes to keep in memory. And when the CPU actually becomes idle, a stage is needed to select which ready process to run immediately.</p>
<p>Scheduling is therefore not a single decision — it is a structure composed of multiple management stages. Each stage intervenes at a different point in time and for a different purpose, collectively coordinating the overall execution flow of the system.</p>
<h3>스케줄링은 하나의 결정이 아니다</h3>
<p>스케줄링은 하나의 순간에만 이루어지는 과정이 아니다. 운영체제는 시스템의 여러 시점에서 서로 다른 목적을 가지고 실행을 관리한다.</p>
<p>예를 들어 시스템에 새로운 작업이 들어올 때, 이 작업을 지금 받아들일지 결정하는 단계가 있다. 실행 가능한 프로세스가 많아지면 어떤 프로세스를 메모리에 유지할지 조절하는 단계도 존재한다. 그리고 실제로 CPU가 비는 순간이 되면, 준비 상태에 있는 프로세스 중 지금 당장 실행할 대상을 선택하는 단계가 필요하다.</p>
<p>이처럼 스케줄링은 단일한 결정이 아니라, <strong>여러 관리 단계로 나누어 이루어지는 구조</strong>다. 각 단계는 서로 다른 시점에, 서로 다른 목적을 위해 개입하며 전체 시스템의 실행 흐름을 함께 조율한다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/7fbbdd26-ad01-44db-a12b-584846710739.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>The Long-Term Scheduler</strong></h3>
<p>When a program requests execution, the job first waits on disk. However, because memory capacity is limited, not all jobs can be loaded into memory immediately. This is where the <strong>long-term scheduler</strong> intervenes.</p>
<p>The long-term scheduler selects which jobs on disk to load into memory. Rather than deciding who gets the CPU, it decides which jobs to admit into the system as execution candidates. Because the number of processes loaded into memory determines how many processes can exist in the ready state at any given time, the long-term scheduler effectively controls the degree of multi-programming.</p>
<p>It is also worth noting that this decision does not happen frequently — it occurs intermittently, only as new jobs arrive.</p>
<h3>장기 스케줄러</h3>
<p>프로그램이 실행을 요청하면 해당 작업은 우선 디스크에서 대기한다. 그러나 메모리 용량에는 한계가 있기 때문에 모든 작업을 바로 메모리에 올릴 수는 없다. 이때 개입하는 것이 <strong>장기 스케줄러(Long-term Scheduler)</strong> 다.</p>
<p>장기 스케줄러는 디스크에 존재하는 작업 중 어떤 작업을 메모리에 올릴지 선택한다. 즉, CPU를 누구에게 줄지 결정하는 것이 아니라, <strong>어떤 작업을 실행 후보로 시스템 안에 들여보낼 것인가</strong>를 결정하는 단계다. 메모리에 올라온 프로세스의 수에 따라 동시에 준비 상태로 존재하는 프로세스의 수가 달라지기 때문에, 장기 스케줄러는 곧 다중 프로그래밍의 정도를 결정하는 역할을 한다.</p>
<p>또한 이 결정은 자주 일어나는 것이 아니라, 새로운 작업이 유입될 때마다 간헐적으로 발생한다는 점도 기억해두자.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0d0d72d4-0e78-4531-a35f-0494aa2d3184.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Summary of the Long-Term Scheduler's Role</strong></h3>
<p>Let's trace the long-term scheduler's flow of operation.</p>
<p>When a job arrives on disk, the long-term scheduler intervenes and selects which jobs to load into memory. Selected jobs are loaded into memory and registered as processes in the ready queue. From there, the <strong>short-term scheduler</strong> selects one of the processes in the ready queue and has it executed by the CPU.</p>
<p>In summary, the long-term and short-term schedulers divide their responsibilities across different stages. The long-term scheduler decides which jobs enter the system, while the short-term scheduler selects the actual CPU execution target. The specific behavior of the short-term scheduler will be examined in a later session.</p>
<h3>장기 스케줄러의 역할 정리</h3>
<p>장기 스케줄러의 동작 흐름을 정리해보자.</p>
<p>작업이 디스크에 도착하면 장기 스케줄러가 개입해 디스크에 있는 작업 중 어떤 것을 메모리에 올릴지 선택한다. 선택된 작업은 메모리에 올라와 준비 큐에 등록된 프로세스가 된다. 이후 <strong>단기 스케줄러(Short-term Scheduler)</strong> 가 준비 큐에 있는 프로세스 중 하나를 선택해 CPU에 의해 실행되도록 한다.</p>
<p>결론적으로 장기 스케줄러와 단기 스케줄러는 서로 다른 단계에서 역할을 나눠 담당한다. 장기 스케줄러는 시스템에 들어올 작업을 결정하고, 단기 스케줄러는 실제 CPU 실행 대상을 선택한다. 단기 스케줄러의 구체적인 동작은 추후에 살펴볼 예정이다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/deba7b9d-60b5-485f-9596-9c10f39a833c.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>The Medium-Term Scheduler</strong></h3>
<p>Even after the long-term scheduler loads jobs into memory, the state of memory continues to change. As the number of running or ready processes grows, memory can become scarce. This is where the <strong>medium-term scheduler</strong> intervenes.</p>
<p>The medium-term scheduler temporarily suspends some of the processes currently in memory, a process known as <strong>swap-out</strong>. Conversely, when memory space becomes available, it reloads suspended processes back into memory — a process known as <strong>swap-in</strong>.</p>
<p>In other words, the medium-term scheduler is responsible for regulating the number of processes maintained in memory. If the long-term scheduler controls how much work enters the system, the medium-term scheduler balances memory resources among the jobs already inside.</p>
<h3>중기 스케줄러</h3>
<p>장기 스케줄러가 작업을 메모리로 들여보낸 이후에도 메모리 상태는 계속 변한다. 실행 중이거나 준비 중인 프로세스가 많아지면 메모리가 부족해질 수 있는데, 이때 개입하는 것이 <strong>중기 스케줄러(Medium-term Scheduler)</strong> 다.</p>
<p>중기 스케줄러는 메모리에 올라와 있는 프로세스 중 일부를 일시적으로 중단 상태로 전환한다. 이 과정을 <strong>스왑 아웃(Swap-out)</strong> 이라고 한다. 반대로 메모리 공간에 여유가 생기면 중단된 프로세스를 다시 메모리로 불러오는데, 이 과정을 <strong>스왑 인(Swap-in)</strong> 이라고 한다.</p>
<p>즉, 중기 스케줄러는 메모리에 유지되는 프로세스의 수를 조절하는 역할을 담당한다. 장기 스케줄러가 시스템에 들어올 작업의 양을 결정한다면, 중기 스케줄러는 이미 들어온 작업들 사이에서 메모리 자원을 균형 있게 유지하는 역할을 한다고 이해하면 된다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/68b7ed37-95c0-4cd0-9bf3-40ffd1be11a6.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>Summary of the Medium-Term Scheduler's Role</strong></h3>
<p>The medium-term scheduler manages processes that are already in memory — including both running and ready processes. When the number of processes causes memory to become overloaded, it suspends some of them and removes them from memory.</p>
<p>The defining characteristic of the medium-term scheduler is that it acts as a <strong>balancer</strong> between the long-term and short-term schedulers. While the long-term scheduler controls the volume of incoming work and the short-term scheduler determines which process gets the CPU, the medium-term scheduler adjusts the number of processes kept in memory to relieve system load.</p>
<h3>중기 스케줄러의 역할 정리</h3>
<p>중기 스케줄러의 관리 대상은 이미 메모리에 올라와 있는 프로세스다. 실행 중이거나 준비 상태에 있는 프로세스 모두가 여기에 포함된다. 시스템의 프로세스가 많아져 메모리가 과부하 상태가 되면, 중기 스케줄러는 이들 중 일부를 중단 상태로 전환해 메모리에서 내보낸다.</p>
<p>중기 스케줄러의 핵심 특징은 장기 스케줄러와 단기 스케줄러 사이에서 <strong>균형을 맞추는 조정자 역할</strong>을 한다는 점이다. 장기 스케줄러가 시스템에 들어오는 작업의 규모를 조절하고, 단기 스케줄러가 CPU 실행 대상을 결정한다면, 중기 스케줄러는 메모리에 유지되는 프로세스 수를 조정해 시스템 부하를 완화하는 역할을 담당한다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a0eeb9f3-1097-47c4-920e-49aff035cfdd.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>The Short-Term Scheduler</strong></h3>
<p>The short-term scheduler selects which process in the ready queue to assign the CPU to. In other words, it is the stage that decides which of the many ready processes to transition into the running state right now.</p>
<p>This decision occurs very frequently. Every time a process terminates, an I/O request is made, or a time quantum expires, the short-term scheduler intervenes and selects the next execution target.</p>
<p>Among the three schedulers, the short-term scheduler has the most direct impact on system responsiveness and performance. All of the scheduling criteria and algorithms covered in later sessions are applied to this scheduler.</p>
<h3>단기 스케줄러</h3>
<p>단기 스케줄러(Short-term Scheduler)는 준비 큐에 있는 프로세스 중에서 CPU를 할당할 프로세스를 선택하는 역할을 한다. 즉, 준비 상태에 있는 여러 프로세스 중 지금 당장 실행 상태로 전환할 하나를 결정하는 단계다.</p>
<p>이 결정은 매우 자주 발생한다. 프로세스가 종료되거나, 입출력 요청이 발생하거나, 시간 할당량이 끝나는 순간마다 단기 스케줄러가 개입해 다음 실행 대상을 선택한다.</p>
<p>단기 스케줄러는 세 가지 스케줄러 중 시스템의 응답성과 성능에 <strong>가장 직접적인 영향을 미치는 스케줄러</strong>다. 이후에 배우게 될 다양한 스케줄링 기준과 알고리즘도 바로 이 단기 스케줄러에 적용된다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8d90d80a-43b9-4da1-9835-a0dbc660fb5b.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3><strong>The Dispatcher: Actually Handing Over the CPU</strong></h3>
<p>Once the short-term scheduler selects which process will use the CPU, the <strong>dispatcher</strong> intervenes in the next step. The dispatcher is the execution module responsible for actually transferring CPU control to the selected process. During this process, a <strong>context switch</strong> takes place, and the system transitions to user mode to begin actual execution.</p>
<p>To summarize the roles: the short-term scheduler decides <em>who</em> runs, and the dispatcher handles the <em>transition</em> to the running state. In other words, at the moment a process moves from the ready state to the running state, the short-term scheduler and the dispatcher work together.</p>
<h3>디스패처: CPU를 실제로 넘기는 역할</h3>
<p>단기 스케줄러가 준비 큐에 있는 프로세스 중 CPU를 사용할 프로세스를 선택하면, 그 다음 단계에서 <strong>디스패처(Dispatcher)</strong> 가 개입한다. 디스패처는 선택된 프로세스에게 CPU 제어를 실제로 넘기는 실행 담당 모듈이다. 이 과정에서 문맥 교환(Context Switching)이 이루어지며, 사용자 모드로 전환해 실제 실행이 시작된다.</p>
<p>역할을 정리하면 다음과 같다. 단기 스케줄러는 <strong>누구를 실행할지 결정</strong>하고, 디스패처는 <strong>실제 실행 상태로 전환</strong>하는 역할을 담당한다. 즉, 준비 상태에서 실행 상태로 넘어가는 순간에 단기 스케줄러와 디스패처가 함께 작동한다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2677b893-998b-41a1-a202-360827627e3b.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f48f81e8-1059-49f1-9ba7-0a00a79d1ebf.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>When the Short-Term Scheduler Intervenes</strong></h3>
<p>The short-term scheduler intervenes every time a specific event occurs. Concretely, it operates in the following situations.</p>
<p>When a running process terminates, when a process relinquishes the CPU due to an I/O request, when I/O completes and a process returns to the ready state, and when a time quantum expires or an interrupt occurs.</p>
<p>In this way, the short-term scheduler intervenes at every moment a process undergoes a state transition. Among the three schedulers — long-term, medium-term, and short-term — the short-term scheduler operates by far the most frequently.</p>
<h3>단기 스케줄러의 개입 시점</h3>
<p>단기 스케줄러는 특정 사건이 발생하는 순간마다 개입한다. 구체적으로는 다음과 같은 상황에서 작동한다.</p>
<p>실행 중인 프로세스가 종료될 때, 프로세스가 입출력 요청으로 인해 CPU를 반납할 때, 입출력이 완료되어 프로세스가 다시 준비 상태로 돌아올 때, 그리고 시간 할당량이 만료되거나 인터럽트가 발생할 때가 이에 해당한다.</p>
<p>이처럼 단기 스케줄러는 프로세스의 상태 전환이 이루어지는 순간마다 개입한다. 장기, 중기, 단기 세 가지 스케줄러 중에서 <strong>가장 빈번하게 동작하는 것이 바로 단기 스케줄러</strong>다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cf6d9b3b-2460-4854-bc4e-8bfcd3dad212.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Summary: Who Manages CPU Scheduling</strong></h3>
<p>Let's take a consolidated look at where each of the three schedulers intervenes in the process state transition flow.</p>
<p>The long-term scheduler decides which jobs to admit into memory at the creation stage — it selects which jobs on disk to load. The medium-term scheduler regulates the number of processes kept in memory by temporarily suspending some and reloading others. The short-term scheduler intervenes at the moment a process transitions from the ready state to the running state and selects which process to assign the CPU to.</p>
<p>These three schedulers intervene at different points in time, but they all operate together on a single process state transition flow. The system as a whole can only run stably when each scheduler's role interlocks properly with the others.</p>
<h3>CPU 스케줄링의 관리 주체 정리</h3>
<p>지금까지 배운 세 가지 스케줄러가 프로세스 상태 전이 흐름 속에서 어디에 개입하는지 한눈에 정리해보자.</p>
<p><strong>장기 스케줄러</strong>는 생성 단계에서 메모리 진입을 결정한다. 디스크에 있는 작업 중 어떤 것을 메모리에 올릴지 선택하는 역할이다. <strong>중기 스케줄러</strong>는 메모리 안에서 프로세스를 일시 중단하거나 다시 불러오는 방식으로 메모리에 유지되는 프로세스 수를 조절한다. <strong>단기 스케줄러</strong>는 준비 상태에서 실행 상태로 넘어가는 순간에 개입해 CPU를 할당할 프로세스를 선택한다.</p>
<p>이 세 스케줄러는 서로 다른 시점에 개입하지만, 하나의 프로세스 상태 전이 흐름 위에서 함께 작동한다. 각자의 역할이 맞물려야 비로소 시스템 전체가 안정적으로 동작할 수 있다.</p>
<hr />
<h3><strong>Preemptive vs. Non-Preemptive Scheduling</strong></h3>
<p>We said that the short-term scheduler intervenes every time a process state change occurs. This raises an important question: can the short-term scheduler forcibly stop a running process at any time?</p>
<p>The answer to this question divides scheduling into two major types. <strong>Preemptive scheduling</strong> can forcibly halt a running process and hand the CPU to another, while <strong>non-preemptive scheduling</strong> waits until the running process voluntarily relinquishes the CPU on its own.</p>
<p>Which approach is chosen significantly affects the system's responsiveness, fairness, and processing efficiency.</p>
<h3>선점 vs 비선점 스케줄링</h3>
<p>단기 스케줄러는 프로세스 상태 변화가 발생할 때마다 개입한다고 했다. 여기서 한 가지 중요한 질문이 생긴다. <strong>단기 스케줄러는 실행 중인 프로세스를 언제든지 강제로 중단시킬 수 있을까?</strong></p>
<p>이 질문에 대한 답에 따라 스케줄링 방식은 크게 두 가지로 나뉜다. 실행 중인 프로세스를 강제로 중단하고 다른 프로세스에게 CPU를 넘길 수 있는 <strong>선점 스케줄링(Preemptive Scheduling)</strong> 과, 실행 중인 프로세스가 스스로 CPU를 반납할 때까지 기다리는 <strong>비선점 스케줄링(Non-preemptive Scheduling)</strong> 이다.</p>
<p>어떤 방식을 선택하느냐에 따라 시스템의 응답성, 공정성, 그리고 처리 효율이 달라진다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/51152cc6-181e-4c40-96cb-38ded52f317a.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Non-Preemptive Scheduling</strong></h3>
<p>In non-preemptive scheduling, a process that has been assigned the CPU continues running until it terminates on its own or transitions to a waiting state due to an I/O request. The operating system does not intervene midway to forcibly stop the running process.</p>
<p>The advantage of this approach is that it is structurally simple and incurs low management overhead. Because context switches do not occur frequently, overall overhead remains low.</p>
<p>However, if a long-running task claims the CPU first, all other processes must wait until that task finishes. This can result in response delays, which is the key drawback.</p>
<p>Ultimately, non-preemptive scheduling can be understood as a structure that allows a process to keep running until it voluntarily gives up the CPU.</p>
<h3>비선점 스케줄링</h3>
<p>비선점 스케줄링(Non-preemptive Scheduling)은 CPU를 할당받은 프로세스가 스스로 종료되거나 입출력 요청으로 인해 대기 상태로 전환될 때까지 계속 실행되는 방식이다. 즉, 운영체제가 중간에 개입해 실행 중인 프로세스를 강제로 중단시키지 않는다.</p>
<p>이 방식의 장점은 구조가 단순하고 관리 부담이 적다는 것이다. 문맥 교환이 자주 발생하지 않기 때문에 오버헤드도 낮다.</p>
<p>그러나 실행 시간이 긴 작업이 먼저 CPU를 점유하면, 그 작업이 끝날 때까지 다른 프로세스는 계속 기다려야 한다. 이로 인해 응답 지연이 발생할 수 있다는 것이 단점이다.</p>
<p>결국 비선점 방식은 <strong>프로세스가 CPU를 자발적으로 반납할 때까지 실행을 계속 허용하는 구조</strong>라고 이해하면 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d6df5353-32a8-4a5f-8636-aed976b97b2d.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Preemptive Scheduling</strong></h3>
<p>In preemptive scheduling, the operating system can reclaim the CPU from a running process whenever it judges this to be necessary. If the time quantum expires or a higher-priority process becomes ready, the currently running task can be halted and replaced with another process.</p>
<p>Rather than waiting for a process to give up the CPU on its own, the operating system directly intervenes and changes the execution flow. This approach prevents any single task from monopolizing the CPU for too long, making it well-suited for improving system responsiveness. The downside is that a context switch occurs every time a process is replaced, which can introduce additional overhead.</p>
<p>To summarize the difference simply: non-preemptive scheduling never takes the CPU away, while preemptive scheduling can reclaim it when needed. The various CPU scheduling algorithms that determine actual execution order are built on top of these two approaches, and they will be examined one by one in the next session.</p>
<h3>선점 스케줄링</h3>
<p>선점 스케줄링(Preemptive Scheduling)에서는 운영체제가 필요하다고 판단하면 실행 중인 프로세스의 CPU를 중간에 회수할 수 있다. 시간 할당량이 만료되었거나 더 높은 우선순위의 프로세스가 준비 상태가 되면, 현재 실행 중인 작업을 중단시키고 다른 프로세스로 교체할 수 있다.</p>
<p>즉, 프로세스가 스스로 CPU를 반납하기를 기다리는 것이 아니라, 운영체제가 직접 개입해 실행 흐름을 바꿀 수 있는 구조다. 이 방식은 특정 작업이 CPU를 오래 독점하는 것을 막을 수 있어 <strong>시스템의 응답성을 높이는 데 유리</strong>하다. 다만 프로세스가 교체될 때마다 문맥 교환이 발생하기 때문에 추가적인 오버헤드가 생길 수 있다는 단점이 있다.</p>
<p>두 방식의 차이를 간단히 정리하면, <strong>비선점은 CPU를 빼앗지 않고, 선점은 필요하다면 CPU를 회수할 수 있는 방식</strong>이다. 이 두 방식을 토대로 실제 실행 순서를 결정하는 다양한 CPU 스케줄링 알고리즘이 만들어지며, 이에 대해서는 다음 시간에 하나씩 살펴볼 예정이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/19a2d0ff-876c-44dd-aefb-321ef4d11862.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>CPU Scheduling Execution Flow</strong></h3>
<p>We have now examined the structure and management entities of CPU scheduling. Let's look at actual execution examples to see how the execution flow of processes plays out in practice.</p>
<p>In particular, we will explore why execution order in situations that appear to involve simultaneous multi-process operation can differ from what we might expect.</p>
<h3>CPU 스케줄링 실행 흐름</h3>
<p>지금까지 CPU 스케줄링의 구조와 관리 주체를 살펴보았다. 이번에는 실제 실행 예제를 통해 프로세스의 실행 흐름이 어떻게 나타나는지 확인해보자.</p>
<p>특히 여러 프로세스가 동시에 작동하는 것처럼 보이는 상황에서, 실행 순서가 왜 우리가 예상하는 것과 다르게 나타나는지도 함께 살펴볼 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4c57b32f-1c29-4810-9be8-a23c80d81264.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>We Do Not Decide Execution Order</strong></h3>
<p>Although multiple processes appear to run simultaneously, the CPU actually handles only one execution at a time. The CPU alternates between processes in very short time intervals, which makes it feel to the user as though everything is running concurrently.</p>
<p>The important point here is that we do not directly specify this execution order. The operating system decides it. As a result, execution outcomes may not always be identical. Even when running the same program, the operating system may make different choices depending on the state of the system at that moment.</p>
<h3>실행 순서는 우리가 정하지 않는다</h3>
<p>여러 프로세스가 동시에 실행되는 것처럼 보이지만, 실제 CPU는 한 번에 하나의 실행만 처리한다. CPU는 매우 짧은 시간 단위로 프로세스를 번갈아 실행하기 때문에, 사용자 입장에서는 동시에 실행되는 것처럼 느껴지는 것이다.</p>
<p>여기서 중요한 점은, 우리가 이 실행 순서를 직접 지정하지 않는다는 것이다. 실행 순서는 운영체제가 결정한다. 따라서 실행 결과가 항상 동일하게 나타나지 않을 수 있다. 같은 프로그램을 실행하더라도 그 순간의 시스템 상태에 따라 운영체제가 다른 선택을 할 수 있기 때문이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a3369f35-11a6-44d9-bd91-46a5afdf41d2.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Apparent Concurrency in Practice</strong></h3>
<p>When <code>fork()</code> creates parent and child processes, both processes execute a loop, print output, and wait briefly.</p>
<p>Let's look at <code>usleep(1000000)</code> used in the example. While the familiar <code>sleep()</code> waits in units of seconds, <code>usleep()</code> waits in microseconds. <code>usleep(1000000)</code> means 1,000,000 microseconds — that is, 1 second. If the wait time is shortened further, the parent and child processes alternate CPU usage more frequently, causing their output to appear more densely interleaved. This example deliberately uses a short wait time to make the effect of scheduling-driven execution order changes more clearly visible.</p>
<p>The key thing to notice is the output order. In some runs, <code>parent</code> appears first; in others, <code>child</code> appears first. We did not specify this order, yet the operating system alternates between the two processes and their output becomes mixed.</p>
<p>This is an example of <strong>apparent concurrency</strong>. In reality the processes take turns, but the output looks as though they are running simultaneously.</p>
<h3>실행 예제로 보는 겉보기 동시성</h3>
<p><code>fork()</code>를 통해 부모 프로세스와 자식 프로세스가 생성되면, 두 프로세스는 각각 반복문을 실행하며 출력하고 잠시 대기한다.</p>
<p>여기서 사용된 <code>usleep(1000000)</code>을 살펴보자. 기존에 사용하던 <code>sleep()</code>은 초 단위로 대기하지만, <code>usleep()</code>은 마이크로초 단위로 대기한다. <code>usleep(1000000)</code>은 1,000,000 마이크로초, 즉 1초를 의미한다. 만약 대기 시간을 더 짧게 줄이면 부모와 자식 프로세스가 더 자주 CPU를 번갈아 사용하게 되어 출력이 더 촘촘하게 섞여 나온다. 이 예제는 의도적으로 짧은 대기 시간을 활용해 스케줄링에 의해 실행 순서가 바뀌는 모습을 더 명확하게 보여주기 위한 것이다.</p>
<p>여기서 주목해야 할 점은 <strong>출력 순서</strong>다. 어떤 경우에는 <code>parent</code>가 먼저 출력되고, 어떤 경우에는 <code>child</code>가 먼저 출력된다. 우리가 이 순서를 지정하지 않았음에도 운영체제가 두 프로세스를 번갈아 실행하면서 결과가 섞여 나타나는 것이다.</p>
<p>이것이 바로 <strong>겉보기 동시성</strong>의 예다. 실제로는 번갈아 실행되지만, 출력은 마치 동시에 실행되는 것처럼 보인다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cd0e75e5-b976-4669-acd8-8a25deb45e36.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>The Non-Determinism of Execution Order</strong></h3>
<p>Apparent concurrency reveals an important characteristic: even with an identical program, the output order of execution results may differ from run to run.</p>
<p>Why does this happen? Because we did not directly specify which process receives the CPU first. Among the processes in the ready state, which one runs first depends on the scheduling decision made at that moment. In other words, the selection of the execution target is not fixed.</p>
<p>As a result, running the same program again may produce a different order of results. This property is called the <strong>non-determinism of execution order</strong>.</p>
<h3>실행 순서의 비결정성</h3>
<p>겉보기 동시성에서 한 가지 중요한 특징이 드러난다. 동일한 프로그램이더라도 실행 결과의 출력 순서가 매번 같지 않을 수 있다는 것이다.</p>
<p>왜 이런 일이 발생할까? CPU를 먼저 할당받을 프로세스를 우리가 직접 지정하지 않았기 때문이다. 준비 상태에 있는 프로세스 중 누가 먼저 실행될지는 그 순간의 스케줄링 판단에 따라 달라진다. 즉, 실행 대상의 선택은 고정되어 있지 않다.</p>
<p>따라서 동일한 프로그램을 다시 실행하면 결과 순서가 달라질 수 있다. 이를 <strong>실행 순서의 비결정성</strong>이라고 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2c0ea280-17c8-4382-b259-fe464bda5f32.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Observing Non-Determinism Through an Example</strong></h3>
<p>Consider an example that uses <code>fork()</code> to create parent and child processes. This time, rather than focusing on the fact that each process has an independent execution flow, we pay attention to the fact that output order differs between runs even with the same program.</p>
<p>The child process prints <code>child</code> and the parent process prints <code>parent</code>. The code has no loops and no wait functions. Yet in some runs <code>parent</code> appears first, and in others <code>child</code> appears first.</p>
<p>This is not because the code differs. It is because the process that receives the CPU first can vary at each execution. The selection of the execution target is not fixed — it is determined by the scheduling decision made at that moment.</p>
<p>This property — whereby execution result order can differ from run to run even with an identical program — is called the <strong>non-determinism of execution order.</strong> This example demonstrates that non-determinism in its simplest possible form.</p>
<h3>실행 순서의 비결정성 — 예제로 확인하기</h3>
<p><code>fork()</code>를 이용해 부모와 자식 프로세스를 생성하는 예제를 살펴보자. 이번에는 두 프로세스가 각각 독립적인 실행 흐름을 가진다는 점보다, <strong>같은 프로그램을 실행해도 출력 순서가 매번 같지 않다</strong>는 점에 주목한다.</p>
<p>자식 프로세스는 <code>child</code>를 출력하고, 부모 프로세스는 <code>parent</code>를 출력한다. 이 코드에는 반복문도 없고 대기 함수도 없다. 그럼에도 어떤 경우에는 <code>parent</code>가 먼저, 어떤 경우에는 <code>child</code>가 먼저 출력될 수 있다.</p>
<p>이 현상은 코드가 달라서가 아니다. CPU를 먼저 할당받는 대상이 실행 시점마다 달라질 수 있기 때문이다. 실행 대상의 선택은 고정되어 있지 않으며, 그 순간의 스케줄링 판단에 따라 결정된다.</p>
<p>이처럼 동일한 프로그램이더라도 실행 결과의 순서가 매번 달라질 수 있는 성질을 <strong>실행 순서의 비결정성</strong>이라고 한다. 이 예제는 그 비결정성을 가장 단순한 형태로 보여주는 코드다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d4f6773b-c9f1-4e27-8d0e-faaacecb7302.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>The Repeated Cycle of Execution and Waiting</strong></h3>
<p>To understand why execution order is not fixed, we need to revisit process state changes.</p>
<p>A process that enters the running state does not continue using the CPU indefinitely. When an I/O request occurs, the running process transitions to the waiting state. Once the wait completes, it returns to the ready state and becomes a candidate for execution again. The flow of running → waiting → running is not a one-time sequence — it is a repeating structure.</p>
<p>The key point is that the moment a process transitions to the waiting state, it can no longer use the CPU. As a result, the CPU becomes idle and the operating system selects another ready process.</p>
<p>In a structure where execution and waiting repeat like this, the entity using the CPU constantly changes. That is why CPU execution order is not fixed and continues to vary.</p>
<h3>실행과 대기의 반복</h3>
<p>실행 순서가 고정되어 있지 않은 이유를 이해하려면 프로세스의 상태 변화를 다시 생각해볼 필요가 있다.</p>
<p>프로세스는 한 번 실행 상태가 되었다고 해서 계속 CPU를 사용하는 것이 아니다. 실행 중이던 프로세스는 입출력 요청이 발생하면 대기 상태로 전환된다. 대기 작업이 끝나면 다시 준비 상태로 돌아와 실행 대상 후보가 된다. 즉, 실행 → 대기 → 실행의 흐름이 한 번으로 끝나는 것이 아니라 반복되는 구조다.</p>
<p>여기서 중요한 점은, 프로세스가 대기 상태로 전환되는 순간 해당 프로세스는 더 이상 CPU를 사용할 수 없게 된다는 것이다. 그 결과 CPU는 비게 되고, 운영체제는 준비 상태에 있는 다른 프로세스를 선택한다.</p>
<p>이처럼 실행과 대기가 반복되는 구조에서는 CPU를 사용하는 주체가 계속 바뀔 수밖에 없다. 그래서 <strong>CPU 실행 순서 역시 고정되지 않고 계속 달라질 수 있는 것</strong>이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c66b3ee9-2ea4-4961-92df-b1e4da6076f6.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Execution and Waiting in Practice</strong></h3>
<p>In the example code, the process prints <code>CPU running</code>, waits briefly, then prints <code>I/O waiting</code>, and waits again — this flow repeats.</p>
<p>The <code>sleep()</code> that follows <code>printf("CPU running\n")</code> does not mean the process keeps holding the CPU — it means the process transitions to a waiting state for a period. The repeating structure is: print <code>CPU running</code> → wait → print <code>I/O waiting</code> → wait.</p>
<p>This example simply illustrates the alternating structure of process execution and waiting. Each time such a state change occurs, the CPU can select a different execution target, and as a result execution order continues to vary.</p>
<h3>예제로 보는 실행과 대기의 반복</h3>
<p>예제 코드에서는 <code>CPU running</code>을 출력한 뒤 잠시 대기하고, 이어서 <code>I/O waiting</code>을 출력한 뒤 다시 대기하는 흐름이 반복된다.</p>
<p>여기서 <code>printf("CPU running\n")</code> 뒤에 오는 <code>sleep()</code>은 프로세스가 CPU를 계속 점유하는 것이 아니라, 잠시 대기 상태로 전환되는 것을 의미한다. 즉, CPU running 출력 → 대기 → I/O waiting 출력 → 대기의 흐름이 반복되는 구조다.</p>
<p>이 예제는 프로세스의 실행과 대기가 번갈아 나타나는 구조를 단순하게 보여준다. 이러한 상태 변화가 발생할 때마다 CPU는 다른 실행 대상을 선택할 수 있고, 그 결과 실행 순서는 계속 달라질 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dabf445b-6c4b-4626-97f3-bbf0ed947354.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Preemptive Scheduling: Switching Without Waiting</strong></h3>
<p>The examples seen so far involved the CPU passing to another process when a process transitioned to the waiting state. In preemptive scheduling, however, the CPU can be switched even when a process is not in the waiting state.</p>
<p>In preemptive scheduling, the operating system can reclaim the CPU and reassign it to another process even if the current task has not finished. This allows shorter tasks or higher-priority tasks to be handled more quickly, making it well-suited for improving system responsiveness.</p>
<p>The downside is that a context switch occurs every time a process is swapped out, which can introduce additional overhead.</p>
<p>The key point here is that the operating system can intervene and reassign the CPU even when a process is not in the waiting state. This is the most significant difference from the non-preemptive approach.</p>
<h3>선점 스케줄링: 대기 없이도 교체된다</h3>
<p>앞서 살펴본 예제는 프로세스가 대기 상태로 전환될 때 CPU가 다른 프로세스에게 넘어가는 구조였다. 그런데 선점 스케줄링에서는 프로세스가 대기 상태가 아니더라도 CPU가 교체될 수 있다.</p>
<p>선점 스케줄링에서는 작업이 아직 끝나지 않았더라도 운영체제가 판단에 따라 CPU를 회수하고 다른 프로세스에게 재할당할 수 있다. 이 방식은 짧은 작업이나 높은 우선순위의 작업을 더 빠르게 처리할 수 있어 <strong>시스템의 응답성을 향상시키는 데 유리</strong>하다.</p>
<p>다만 실행 중이던 작업을 중단하고 다른 작업으로 전환할 때마다 문맥 교환이 발생하기 때문에 추가적인 오버헤드가 생길 수 있다.</p>
<p>여기서 핵심은 <strong>대기 상태가 아니어도 운영체제가 개입해 CPU를 재할당할 수 있다</strong>는 점이다. 이것이 비선점 방식과의 가장 큰 차이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/341192a1-dbfc-49f2-9af7-1864627479e9.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Preemptive Scheduling Example Code</strong></h3>
<p>This example does not directly implement preemptive scheduling. It is an example designed to observe execution results in an environment where preemptive scheduling is operating.</p>
<p>Looking at the code, the parent and child processes each run a <code>while</code> loop and continuously print their own messages. Neither process terminates on its own, and no instruction to yield the CPU has been written. In other words, the code contains no command to stop execution or hand off to another process.</p>
<p>Yet the execution results show output alternating between the two. This switching is not caused by the code — it is caused by the operating system's preemptive behavior. The operating system uses timer interrupts to reclaim the CPU from the running process after a set interval and switch to another.</p>
<p>In short, this example demonstrates that even when a process does not voluntarily relinquish the CPU, the operating system can intervene and swap execution.</p>
<h3>선점 스케줄링 예제 코드</h3>
<p>이 예제는 선점 스케줄링을 직접 구현한 코드가 아니다. 선점 스케줄링이 동작하는 환경에서 실행 결과를 관찰하기 위한 예제다.</p>
<p>코드를 보면 부모와 자식 프로세스가 각각 <code>while</code> 반복문을 수행하며 계속해서 자신의 메시지를 출력한다. 두 프로세스 모두 스스로 종료하지 않으며, CPU를 반납하는 명령도 작성되어 있지 않다. 즉, 코드 안에는 실행을 중단하거나 다른 프로세스로 넘기라는 명령이 전혀 없다.</p>
<p>그럼에도 실행 결과를 보면 출력이 번갈아 나타난다. 이 교체는 코드 때문이 아니라 <strong>운영체제의 선점 동작</strong> 때문이다. 운영체제는 타이머 인터럽트를 통해 일정 시간이 지나면 실행 중인 프로세스의 CPU를 회수하고 다른 프로세스로 전환한다.</p>
<p>즉, 이 예제는 <strong>프로세스가 스스로 CPU를 내려놓지 않더라도 운영체제가 개입해 실행을 교체할 수 있다</strong>는 것을 보여주는 코드다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/123758bb-95e1-4e83-90a8-907361143f49.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Non-Preemptive Scheduling: Only Voluntary Yielding</strong></h3>
<p>In contrast to preemptive scheduling, non-preemptive scheduling does not allow the operating system to forcibly stop a running process. Once a process is assigned the CPU, it continues running until its task finishes or it voluntarily transitions to a waiting state.</p>
<p>This structure is simple and incurs low management overhead. However, if a long-running task claims the CPU first, all other tasks must wait until it completes. This can lead to response delays that directly affect the performance users perceive.</p>
<h3>비선점 스케줄링: 자발적 반납만 허용</h3>
<p>선점 스케줄링과 대비되는 비선점 스케줄링을 살펴보자. 비선점 스케줄링에서는 운영체제가 실행 중인 프로세스를 강제로 중단시키지 않는다. 한 번 CPU를 할당받은 프로세스는 작업이 끝나거나 스스로 대기 상태로 전환될 때까지 계속 실행된다.</p>
<p>이 구조는 단순하고 관리 부담이 적다는 장점이 있다. 그러나 실행 시간이 긴 작업이 먼저 CPU를 점유하면, 그 작업이 끝날 때까지 다른 작업은 계속 기다려야 한다는 단점이 있다. 결국 응답 지연이 발생할 수 있으며, 이는 사용자 체감 성능에도 직접적인 영향을 미친다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4859c38b-da99-43ac-8802-7d028a7903ff.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Non-Preemptive Scheduling Example Code</strong></h3>
<p>This example also does not directly implement non-preemptive scheduling. It is designed to observe the execution flow under non-preemptive conditions by creating a situation where forced switching does not occur.</p>
<p>After using <code>fork()</code> to create parent and child processes, the parent calls <code>sleep(6)</code> so that it waits until the child finishes. The important point is that <code>sleep()</code> is not simply a function that passes time — it is a call that transitions the process to a waiting state for a period. In other words, the parent does not continue using the CPU; it voluntarily enters a waiting state and becomes unable to use the CPU. During that time, the child process runs its loop and completes all of its output.</p>
<p>The execution results show all of the child's output appearing first, followed by the parent's output. This is not because the operating system forcibly stopped a running process — it is because the parent voluntarily transitioned to the waiting state, causing the CPU to pass to the child.</p>
<p>In short, this example shows how execution flow changes when a process voluntarily relinquishes the CPU.</p>
<h3>비선점 스케줄링 예제 코드</h3>
<p>이 예제 역시 비선점 스케줄링을 직접 구현한 코드가 아니다. 강제 교체가 발생하지 않는 상황을 만들어 비선점 방식에서의 실행 흐름을 관찰하는 예제다.</p>
<p><code>fork()</code>를 이용해 부모와 자식 프로세스를 생성한 뒤, 부모 프로세스는 <code>sleep(6)</code>을 호출해 자식이 끝날 때까지 기다리도록 구성했다. 여기서 중요한 점은 <code>sleep()</code>이 단순히 시간을 보내는 함수가 아니라, 해당 프로세스를 일정 시간 동안 <strong>대기 상태로 전환시키는 호출</strong>이라는 것이다. 즉, 부모는 CPU를 계속 사용하는 것이 아니라 스스로 대기 상태로 들어가 CPU를 사용할 수 없게 된다. 그 사이에 자식 프로세스는 반복문을 통해 자신의 출력을 끝까지 수행한다.</p>
<p>실행 결과를 보면 자식의 출력이 모두 나타난 뒤 부모의 출력이 이어지는 것을 확인할 수 있다. 운영체제가 실행 중인 프로세스를 강제로 중단시킨 것이 아니라, 부모가 스스로 대기 상태로 전환되었기 때문에 CPU가 자식에게 넘어간 것이다.</p>
<p>즉, 이 예제는 <strong>프로세스가 CPU를 자발적으로 반납할 때 실행 흐름이 어떻게 달라지는지</strong>를 보여주는 코드다.</p>
<hr />
<h3><strong>Summary</strong></h3>
<p>Process execution is not controlled by the user — it is managed by the operating system. Which process uses the CPU and when is not fixed in advance; it varies with the scheduling policy applied, and that policy determines the responsiveness users experience.</p>
<p>CPU scheduling is the mechanism that decides and controls the execution flow of processes. In the next session, we will examine the specific criteria used to determine execution order — the various scheduling algorithms — one by one.</p>
<h3>정리</h3>
<p>프로세스의 실행은 사용자가 직접 제어하는 것이 아니라 운영체제가 관리한다. 어떤 프로세스가 언제 CPU를 사용할지는 고정되어 있지 않으며, 적용되는 스케줄링 방식에 따라 실행 순서가 달라진다. 그리고 그 순서에 따라 사용자가 느끼는 응답성도 달라진다.</p>
<p>이처럼 <strong>프로세스의 실행 흐름을 결정하고 제어하는 것이 바로 CPU 스케줄링</strong>이다. 다음 시간에는 이 실행 순서를 결정하는 구체적인 기준, 즉 다양한 스케줄링 알고리즘을 하나씩 살펴볼 예정이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d5384f49-29cb-4cf8-8483-fa94ec45eadd.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Practice: Observing Apparent Concurrency</strong></p>
<p>Let's directly observe what happens when <code>fork()</code> creates parent and child processes that each run independently.</p>
<p>The execution results show the messages of the parent and child interleaved in the output. Although it looks as though the two processes are running simultaneously, the CPU handles only one task at a time. The reason output alternates is that the operating system switches execution targets at very high speed.</p>
<p>What this practice demonstrates is that the two processes are not truly running at the same time, what we see is <strong>apparent concurrency</strong>, produced by rapid execution switching.</p>
<h3>실습으로 확인하는 겉보기 동시성</h3>
<p><code>fork()</code>를 호출했을 때 부모 프로세스와 자식 프로세스가 각각 생성되어 실행되는 것을 직접 확인해보자.</p>
<p>실행 결과를 보면 부모와 자식의 메시지가 섞여서 출력되는 것을 볼 수 있다. 겉으로 보기엔 두 프로세스가 동시에 실행되는 것처럼 보이지만, CPU는 한 번에 하나의 작업만 처리한다. 출력이 번갈아 나오는 이유는 운영체제가 실행 대상을 매우 빠르게 전환하기 때문이다.</p>
<p>이 실습을 통해 확인할 수 있는 것은, 실제로 두 프로세스가 동시에 실행되는 것이 아니라 <strong>빠른 실행 전환에 의해 만들어지는 겉보기 동시성</strong>이라는 점이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2dd7bc0f-1a27-4ad9-bccc-e2f170a3849e.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Practice: Observing Non-Determinism of Execution Order</strong></h3>
<p>This example is a simple program with no loops and no wait functions — the parent and child each print one message.</p>
<p>Running it shows that sometimes <code>child</code> appears first, and sometimes <code>parent</code> appears first. The code is identical, but who receives the CPU first is not fixed.</p>
<p>What this practice demonstrates is that even with an identical program, the order of execution results can differ from run to run. The selection of the execution target is not predetermined — non-determinism is inherent in CPU scheduling.</p>
<h3>실습으로 확인하는 실행 순서의 비결정성</h3>
<p>이 예제는 반복문도, 대기 함수도 없이 부모와 자식 프로세스가 각각 한 번씩 메시지를 출력하는 단순한 코드다.</p>
<p>실행해보면 어떤 경우에는 <code>child</code>가 먼저 출력되고, 어떤 경우에는 <code>parent</code>가 먼저 출력된다. 코드는 동일하지만 누가 먼저 CPU를 할당받는지는 고정되어 있지 않기 때문이다.</p>
<p>이 실습을 통해 확인할 수 있는 것은, 동일한 프로그램이더라도 실행 결과의 순서가 매번 달라질 수 있다는 점이다. 실행 대상의 선택은 미리 결정되어 있지 않으며, <strong>CPU 스케줄링에는 비결정성이 존재한다.</strong></p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/588b80ae-e2b7-448d-926e-df6c0c3dc685.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Practice: Observing Repeated Execution and Waiting</strong></h3>
<p>This example includes a call to <code>sleep()</code>. Remember that <code>sleep()</code> does not merely delay time — it transitions the process to a waiting state for a period.</p>
<p>Running it shows that after one process prints a message and appears to pause, the other process's output follows. This is because the process transitions to a waiting state via <code>sleep()</code> and can no longer use the CPU, at which point the operating system selects another ready process.</p>
<p>What this lab demonstrates is that processes repeat cycles of execution and waiting, and after each wait period, they pass through the ready state before running again.</p>
<h3>실습으로 확인하는 실행과 대기의 반복</h3>
<p>이 예제는 <code>sleep()</code> 호출을 포함한다. <code>sleep()</code>은 단순히 시간을 지연시키는 것이 아니라, 해당 프로세스를 일정 시간 동안 <strong>대기 상태로 전환시키는 호출</strong>이라는 점을 기억하자.</p>
<p>실행해보면 한 프로세스가 메시지를 출력한 뒤 잠시 멈추는 것처럼 보이고, 그 사이에 다른 프로세스의 출력이 이어지는 것을 확인할 수 있다. 실행 중이던 프로세스가 <code>sleep()</code>과 함께 대기 상태로 전환되면서 CPU를 사용할 수 없게 되고, 그 순간 운영체제는 준비 상태에 있는 다른 프로세스를 선택하기 때문이다.</p>
<p>이 실습을 통해 확인할 수 있는 것은, <strong>프로세스는 실행과 대기를 반복하며 대기 이후에는 준비 상태를 거쳐 다시 실행된다</strong>는 흐름이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/412f4db7-0f81-4a11-a725-3fe5155e60c2.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Practice: Observing Preemptive Scheduling</strong></p>
<p>This example has both parent and child processes running <code>while</code> loops. The code contains no instruction to stop execution. Yet the execution results show the parent and child output alternating infinitely. Press <code>Ctrl+C</code> to terminate.</p>
<p>This switching does not happen because the processes voluntarily give up the CPU. It happens because the operating system forcibly swaps the running process at fixed time intervals.</p>
<p>What this lab demonstrates is the core characteristic of preemptive scheduling: even when execution is not finished, the operating system can reclaim the CPU when it judges this necessary.</p>
<h3>실습으로 확인하는 선점 스케줄링</h3>
<p>이 예제는 부모와 자식 프로세스가 모두 <code>while</code> 반복문을 수행하는 구조다. 코드 안에는 실행을 중단하라는 명령이 없다. 그럼에도 실행 결과를 보면 부모와 자식의 출력이 무한히 번갈아가며 나타난다. 종료하려면 <code>Ctrl+C</code>를 누르면 된다.</p>
<p>이 교체는 프로세스가 스스로 CPU를 내려놓은 것이 아니다. 운영체제가 일정 시간 단위로 실행 중인 프로세스를 강제로 교체하기 때문에 나타나는 결과다.</p>
<p>이 실습을 통해 확인할 수 있는 것은, <strong>실행이 끝나지 않았더라도 운영체제가 필요하다고 판단하면 CPU를 회수할 수 있다</strong>는 선점 스케줄링의 핵심 특징이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/fc7c71ee-f6ab-4171-a104-fe79318ebad0.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Practice: Observing Non-Preemptive Scheduling</strong></p>
<p>This example is configured so that one process calls <code>sleep()</code> and temporarily enters a waiting state.</p>
<p>Running it shows that one side finishes first, and then the other side executes. No forced switching occurs mid-execution. This is because the next process only runs once the running process voluntarily transitions to a waiting state or terminates.</p>
<p>What this practice allows us to directly observe is the defining characteristic of non-preemptive scheduling: execution flow only transfers when a process voluntarily relinquishes the CPU.</p>
<h3>실습으로 확인하는 비선점 스케줄링</h3>
<p>이 예제는 한 프로세스가 <code>sleep()</code>을 호출해 일시적으로 대기 상태로 들어가도록 구성한 코드다.</p>
<p>실행해보면 한쪽 작업이 먼저 끝난 뒤 다른 쪽 작업이 실행되는 것을 확인할 수 있다. 실행 도중 강제로 교체되는 모습은 나타나지 않는다. 실행 중인 프로세스가 스스로 대기 상태로 전환되거나 종료되어야 비로소 다음 프로세스의 실행이 이루어지기 때문이다.</p>
<p>이 실습을 통해 <strong>프로세스가 자발적으로 CPU를 반납할 때만 실행 흐름이 전환되는 비선점 스케줄링의 특징</strong>을 직접 확인할 수 있다.</p>
]]></content:encoded></item><item><title><![CDATA[Linux Text File & Editor: Vim]]></title><description><![CDATA[1️⃣ Text File2️⃣ Use a Text Editor

1️⃣ Text File
Linux and Unix: Are They the Same?
Linux and Unix are not completely identical, but they can be considered very similar from a user's perspective. Loo]]></description><link>https://heesu.tech/linux-text-file-editor-vim</link><guid isPermaLink="true">https://heesu.tech/linux-text-file-editor-vim</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Wed, 08 Apr 2026 13:03:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/765264f7-a07d-4ceb-9359-b85338fab40b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>1️⃣ Text File<br />2️⃣ Use a Text Editor</p>
<hr />
<h1>1️⃣ Text File</h1>
<h3>Linux and Unix: Are They the Same?</h3>
<p>Linux and Unix are not completely identical, but they can be considered very similar from a user's perspective. Looking at their internal structure, there are real differences, and there is also the distinction that Unix is paid while Linux is free. However, since they are not significantly different in terms of actual usage, learning how to use Linux will allow you to adapt to a Unix environment without much difficulty.</p>
<p>In this session, we will look at how to edit text files in a Linux environment.</p>
<h3>텍스트 파일 편집: 리눅스와 유닉스는 같은가?</h3>
<p>리눅스와 유닉스는 완전히 동일하지는 않지만, 사용자 관점에서는 매우 유사하다고 할 수 있다. 내부 구조를 들여다보면 실질적인 차이가 존재하고, 유닉스가 유료인 반면 리눅스는 무료라는 차이점도 있다. 하지만 실제 사용법 측면에서는 크게 다르지 않기 때문에, 리눅스를 중심으로 운영체제 사용법을 익혀두면 유닉스 환경에서도 무리 없이 적응할 수 있다.</p>
<p>이번 시간에는 실제 리눅스 환경에서 텍스트 파일을 어떻게 편집하는지 살펴본다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0a0a4864-fa3e-4dad-9fdd-72a488240faa.png" alt="" style="display:block;margin:0 auto" />

<h3>What is a Text File?</h3>
<p>The most familiar example of a text file is the <code>.txt</code> file. By definition, a text file is a file that stores electronically represented characters arranged in sequence.</p>
<p>The difference becomes clear when compared to a PPT file. PPT requires a specific company's editor which is PowerPoint - to open it, whereas text files are not tied to any specific format. There is a dedicated viewer for text files called a text editor, but since it comes pre-installed on computers at no additional cost, it is referred to as a basic editor.</p>
<p>So why is this basic editor necessary? The simplest reason is that it is needed for handling program inputs and modifying various system settings. Since text files are stored in a human-readable format, opening one in an editor lets you see characters like A, B, and C directly.</p>
<p>Early computers were developed around the alphabet, so non-alphabetic characters like Korean were not supported. Today, however, the underlying structure has evolved to support a wide range of languages.</p>
<h3>텍스트 파일이란?</h3>
<p>텍스트 파일은 평소에 익숙하게 접해온 <code>.txt</code> 파일이 대표적인 예다. 정의하자면, 전자적인 문자가 나열된 형태로 저장된 파일을 텍스트 파일이라고 한다.</p>
<p>PPT 파일과 비교하면 차이가 명확해진다. PPT는 파워포인트라는 특정 회사의 편집기가 있어야만 열람이 가능한 반면, 텍스트 파일은 특정 형식에 종속되지 않는다. 텍스트 파일을 열기 위한 전용 뷰어, 즉 텍스트 편집기가 존재하지만, 추가 비용 없이 컴퓨터에 기본으로 설치되어 있기 때문에 '기본 편집기'라고 부른다.</p>
<p>그렇다면 이 기본 편집기는 왜 필요할까? 가장 간단한 이유는 프로그램의 입력값을 다루거나 각종 설정을 변경할 때 필요하기 때문이다. 텍스트 파일은 사람이 읽기 쉬운 형식으로 되어 있어, 편집기로 열면 A, B, C와 같은 문자를 그대로 확인할 수 있다.</p>
<p>초기 컴퓨터는 알파벳 중심으로 발전해왔기 때문에 한국어와 같은 비알파벳 문자는 지원되지 않았다. 그러나 현재는 기본 구조 수준에서 다양한 언어를 지원할 수 있도록 발전하였다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dbd16169-757f-4223-8fc5-5ac4f1b7927e.png" alt="" style="display:block;margin:0 auto" />

<h3>How to Check a Text File</h3>
<p>To check whether a file is in text format, use the <code>file</code> command.</p>
<pre><code class="language-shell">file ~/.profile
</code></pre>
<p>This will return something like <code>.profile: ASCII text</code>, confirming it is a text file. On the other hand,</p>
<pre><code class="language-bash">file /usr/bin/ls
</code></pre>
<p>returns something like <code>ELF 64-bit LSB pie executable ...</code>. This is an executable file, commonly referred to as a <strong>binary file</strong>. A binary file is represented in binary and is formatted for computers to process rather than for humans to read.</p>
<h3>텍스트 파일 확인 방법</h3>
<p>현재 다루는 파일이 텍스트 형식인지 확인하려면 <code>file</code> 명령어를 사용한다.</p>
<pre><code class="language-bash">file ~/.profile
</code></pre>
<p>위 명령어를 입력하면 <code>.profile: ASCII text</code>와 같이 텍스트 형식임을 알려준다. 반면,</p>
<pre><code class="language-bash">file /usr/bin/ls
</code></pre>
<p>를 입력하면 <code>ELF 64-bit LSB pie executable ...</code>과 같은 결과가 출력된다. 이는 실행 가능한 파일로, 통상 <strong>바이너리 파일</strong>이라고 부른다. 바이너리 파일은 이진수로 표현된 파일로, 사람이 읽기보다는 컴퓨터가 처리하기에 적합한 형식이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3c3bf151-6421-416c-8243-92b12f25f770.png" alt="" style="display:block;margin:0 auto" />

<h3>ASCII and Encoding</h3>
<p>As mentioned earlier, Korean was added to computers later, which relates to ASCII - the early character encoding system. ASCII is composed of 7 bits and divides characters into two categories:</p>
<ul>
<li><p><strong>Printable characters</strong>: 32 ~ 126</p>
</li>
<li><p><strong>Control characters</strong> (non-printable): 0 ~ 31, 127</p>
</li>
</ul>
<p>Computers recognize everything as numbers. For example, uppercase <code>A</code> is stored as 65 and lowercase <code>a</code> as 97. These numbers are converted into human-readable characters according to ASCII-based encoding and decoding rules. Both 65 and 97 fall within the printable character range.</p>
<h3>ASCII와 인코딩</h3>
<p>한글은 컴퓨터에 나중에 적용되었다고 언급했는데, 이는 초기 컴퓨터 문자 체계인 ASCII와 관련이 있다. ASCII는 7비트로 구성되며, 문자는 크게 두 종류로 나뉜다.</p>
<ul>
<li><p><strong>출력 가능한 문자</strong>: 32 ~ 126</p>
</li>
<li><p><strong>제어 문자</strong> (출력되지 않는 문자): 0 ~ 31, 127</p>
</li>
</ul>
<p>컴퓨터는 모든 것을 숫자로 인식한다. 예를 들어 대문자 <code>A</code>는 65, 소문자 <code>a</code>는 97로 저장되며, ASCII 기반의 인코딩·디코딩 규칙에 따라 이 숫자들이 사람이 읽을 수 있는 문자로 변환된다. 65와 97은 모두 출력 가능한 문자 범위에 해당한다.</p>
<hr />
<h3>Viewing File Contents: <code>head</code></h3>
<p><code>head</code> is a command that outputs the beginning (head) of a file. It is used when you only want to check the first part of a text file that is organized line by line.</p>
<p>The reason for viewing only the beginning goes back to the limitations of early monitors. While modern monitors can display large amounts of text on a single screen, early monitors could only display around 40–80 characters wide and about 20 lines tall. Before that, there were no screens at all — punch card devices were used to output one line at a time. Because what could be shown on screen was so limited, and like a TV where content that has scrolled past cannot be seen again, commands were needed to view just a portion of a file.</p>
<p>Usage: <code>head ~/.profile</code> outputs the default 10 lines, and <code>head -n 7 ~/.profile</code> outputs only 7 lines using the <code>-n</code> option. The <code>-n</code> can be omitted and it will work the same way.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/38808167-a401-4dd8-80a5-87d302e3ba3c.png" alt="" style="display:block;margin:0 auto" />

<h3>텍스트 파일 내용 보기: <code>head</code></h3>
<p><code>head</code>는 파일의 앞부분(머리 부분)을 출력하는 명령어다. 텍스트 파일은 문자들이 줄 단위로 구성되어 있는데, 그 중 앞부분만 확인하고 싶을 때 사용한다.</p>
<p>굳이 앞부분만 보는 이유는 초기 모니터의 한계에서 비롯된다. 현재의 모니터는 많은 양의 텍스트를 한 화면에 표시할 수 있지만, 초기 모니터는 가로 40~80자, 세로 20줄 내외만 표현 가능했다. 그 이전에는 화면 자체가 없었고 펀치카드 장치로 한 줄씩 출력하는 방식을 사용했다. 이처럼 한 화면에 표시할 수 있는 내용이 제한적이었기 때문에, TV 화면처럼 한번 지나간 내용은 다시 볼 수 없는 환경에서 파일의 일부분만 확인하기 위한 명령어가 필요했다.</p>
<p>사용법은 다음과 같다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ef94e71f-698f-4fd3-8ace-fde4d659581b.png" alt="" style="display:block;margin:0 auto" />

<p><code>head ~/.profile</code>을 입력하면 기본값인 10줄이 출력되며, <code>head -n 7 ~/.profile</code>과 같이 <code>-n</code> 옵션으로 출력할 줄 수를 직접 지정할 수도 있다. <code>-n</code>은 생략해도 동일하게 동작한다.</p>
<hr />
<h3>Viewing File Contents: <code>tail</code></h3>
<p><code>tail</code> is the counterpart to <code>head</code> - just like the relationship between a head and a tail, it outputs the end of a file. Entering <code>tail -n 7 ~/.profile</code> outputs the last 7 lines of the file. The usage is the same as <code>head</code>, but the result is different. In summary, <code>head</code> outputs from the beginning and <code>tail</code> outputs from the end.</p>
<p>Note that depending on the user's environment, the <code>.profile</code> file may not exist or its contents may differ. If the file has not been modified from its initial state, the results from <code>head</code> and <code>tail</code> may look nearly the same, but if changes have been made, the results will differ.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ceb9c54b-743a-418a-97d3-59e9f54684ba.png" alt="" style="display:block;margin:0 auto" />

<h3>텍스트 파일 내용 보기: <code>tail</code></h3>
<p><code>tail</code>은 <code>head</code>와 상응하는 명령어로, 머리와 꼬리의 관계처럼 파일의 뒷부분을 출력한다. <code>tail -n 7 ~/.profile</code>을 입력하면 파일의 마지막 7줄을 출력한다는 점에서 <code>head</code>와 사용법은 같지만 결과는 다르다. 정리하자면 <code>head</code>는 앞에서부터, <code>tail</code>은 뒤에서부터 지정한 줄 수만큼 출력한다고 이해하면 된다.</p>
<p>단, 사용자 환경에 따라 <code>.profile</code> 파일이 없거나 내용이 다를 수 있다. 초기 설정 그대로라면 <code>head</code>와 <code>tail</code>의 결과가 거의 동일하게 보일 수 있지만, 중간에 내용을 수정했다면 결과에 차이가 생긴다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/aa514cc9-c764-4497-b0da-99bbeb445cc0.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>The Meaning of <code>~</code></h3>
<p>Why do we use <code>~</code> in commands? The <code>~</code> symbol represents the home directory of the currently logged-in user. It is both a relative and absolute reference. In most cases, users continue with the account they initially logged in with, but administrators can switch to another user without logging out. In that case, <code>~</code> points to the home directory of the switched user. In other words, <code>~</code> always refers to the home directory of the currently active user.</p>
<h3><code>~</code>의 의미</h3>
<p>명령어에서 <code>~</code>를 사용하는 이유는 무엇일까? <code>~</code>는 현재 로그인한 사용자의 홈 디렉토리를 의미한다. 상대적인 표시이면서도 절대적인 위치를 가리킨다는 특징이 있다. 대부분의 경우 처음 로그인한 계정을 계속 사용하지만, 관리자는 로그아웃 없이도 다른 사용자로 전환할 수 있다. 이 경우 <code>~</code>는 전환된 사용자의 홈 디렉토리를 가리키게 된다. 즉, <code>~</code>는 항상 현재 활성화된 사용자의 홈 디렉토리를 의미한다.</p>
<hr />
<h3>Viewing File Contents: <code>cat</code></h3>
<p><code>cat</code> stands for "concatenate files and print on the standard output." It is a command that joins files together and outputs their contents to the standard output device.</p>
<p>What is the standard output device? In modern terms, it is natural to think of a monitor, but this has not always been the case. In the early days of computing, the standard output device was paper — a printer. Therefore, saying "output to the monitor" is specific to a particular situation. The more accurate expression is "output to the standard output device." More details on standard output will be covered later.</p>
<p>Usage: entering <code>cat ~/.profile</code> outputs the entire contents of that file to the standard output device. In a typical environment, this will appear on the monitor.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d4a694f3-ba10-4226-8a6d-f90ebf83d0cb.png" alt="" style="display:block;margin:0 auto" />

<h3>텍스트 파일 내용 보기: <code>cat</code></h3>
<p><code>cat</code>은 "concatenate files and print on the standard output" 파일을 이어 붙여 표준 출력장치로 내용을 출력하는 명령어다.</p>
<p>여기서 표준 출력장치란 무엇일까? 현재 기준으로는 모니터라고 생각하면 자연스럽지만, 이것이 항상 당연한 것은 아니다. 컴퓨터 초창기에는 표준 출력장치가 모니터가 아닌 종이, 즉 프린터였다. 따라서 "모니터에 출력된다"는 표현은 특정 상황에 한정된 표현이며, 보다 정확하게는 "표준 출력장치로 출력된다"고 하는 것이 맞다. 표준 출력에 대한 자세한 내용은 이후에 다룰 예정이다.</p>
<p>사용법은 다음과 같다. <code>cat ~/.profile</code>을 입력하면 해당 파일의 전체 내용이 표준 출력장치로 출력된다. 일반적인 환경에서는 모니터에 출력되는 것을 확인할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0cbb9991-39d4-4745-8fae-0c4a5066c706.png" alt="" style="display:block;margin:0 auto" />

<h3>Viewing File Contents: <code>more</code></h3>
<p><code>more</code> is a command that displays file contents divided according to the terminal screen size. Unlike <code>cat</code>, which outputs everything at once, <code>more</code> shows only as much as the current screen can display.</p>
<p>Running <code>more ~/.profile</code> shows <code>--More-- (32%)</code> at the bottom of the screen, meaning 32% of the total document is currently displayed. The amount shown varies depending on the terminal window size — whether it shows 24 lines or 50 lines depends on the current screen. Since it allows you to read through the entire content in order rather than cutting off a portion like <code>head</code> or <code>tail</code>, it is more useful when you actually want to read a file.</p>
<h3>텍스트 파일 내용 보기: <code>more</code></h3>
<p>Display the contents of a file in a terminal</p>
<p><code>more</code>는 파일의 내용을 터미널 화면 크기에 맞춰 나눠서 출력하는 명령어다. <code>cat</code>처럼 파일 전체를 한꺼번에 출력하는 것이 아니라, 현재 화면에 표시할 수 있는 만큼만 보여준다.</p>
<p><code>more ~/.profile</code>을 실행하면 화면 하단에 <code>--More-- (32%)</code>와 같은 표시가 나타나는데, 이는 전체 문서 중 32%가 현재 화면에 표시되었다는 의미다. 화면 크기는 사용자마다 다를 수 있어 24줄짜리 화면이든 50줄짜리 화면이든 현재 터미널 창의 크기에 따라 표시되는 양이 달라진다. <code>head</code>나 <code>tail</code>처럼 일부만 잘라서 보는 것이 아니라 전체 내용을 순서대로 읽어나갈 수 있기 때문에, 파일 내용을 실제로 읽어볼 때 더 유용한 명령어다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/80f98dc0-37d5-4d4c-92ba-4c9f17fefed6.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/480aaa68-32ae-4c69-886e-1eb88ce5307c.png" alt="" style="display:block;margin:0 auto" />

<h3>Viewing File Contents: <code>less</code></h3>
<p>Despite its name, <code>less</code> has more features than <code>more</code>. While <code>more</code> only allows forward navigation, <code>less</code> allows you to move both forward and backward freely. Like the scrollbar on the right side of a web browser, <code>less</code> lets you scroll up and down through the file content, making it more convenient for reading long files.</p>
<p>To exit <code>less</code>, press <code>q</code>.</p>
<h3>텍스트 파일 내용 보기: <code>less</code></h3>
<p><code>less</code>는 이름과 달리 <code>more</code>보다 기능이 많은 명령어다. <code>more</code>가 앞으로만 이동 가능한 반면, <code>less</code>는 앞뒤로 자유롭게 이동할 수 있다. 웹 브라우저 오른쪽의 스크롤 바처럼 파일 내용을 위아래로 자유롭게 탐색할 수 있어 긴 파일을 읽을 때 더욱 편리하다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1cedf88e-bd30-46c7-9794-1ba6f830c57d.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/40cb3340-c0ca-494f-a408-86f6ac958dd3.png" alt="" style="display:block;margin:0 auto" />

<h3>Using <code>cat</code> with <code>&lt;&lt;</code> (Here Document)</h3>
<p>In addition to standalone use, <code>cat</code> can be used together with the <code>&lt;&lt;</code> operator. <code>&lt;&lt;</code> is called the here document operator and allows you to input multiple lines of text at once.</p>
<p>Usage: type <code>cat &lt;&lt; end</code> and press Enter, then input the desired content line by line. When finished, type <code>end</code> on the last line and press Enter to terminate input.</p>
<h3><code>cat</code>과 <code>&lt;&lt;</code> (Here Document) 활용</h3>
<p><code>cat</code>은 단독으로 사용하는 것 외에도 <code>&lt;&lt;</code> 연산자와 함께 복합적으로 활용할 수 있다. <code>&lt;&lt;</code>는 here document 연산자라고 부르며, 여러 줄의 텍스트를 한 번에 입력할 수 있게 해준다.</p>
<p>사용법은 다음과 같다. <code>cat &lt;&lt; end</code>를 입력하고 엔터를 누른 뒤, 원하는 내용을 한 줄씩 입력한다. 입력이 끝나면 마지막 줄에 <code>end</code>를 입력하고 엔터를 누르면 입력이 종료된다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/649a42d5-c1b2-4489-843e-0894b665898e.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b5c9ea41-4284-4570-b1d4-08d71f1f3cb5.png" alt="" style="display:block;margin:0 auto" />

<h3>Using <code>cat</code>, <code>&lt;&lt;</code>, and <code>&gt;</code> Together</h3>
<p>Adding <code>&gt;</code> (redirect output) to the here document operator <code>&lt;&lt;</code> allows you to save the input content to a file.</p>
<p>The <code>&gt;</code> is the redirect output operator, meaning send the output to the specified file instead of the monitor. So <code>&gt; new.txt</code> means save the input content to a file named <code>new.txt</code>. By using <code>cat</code>, <code>&lt;&lt;</code>, and <code>&gt;</code> together, you can easily create a text file.</p>
<h3><code>cat</code>, <code>&lt;&lt;</code>, <code>&gt;</code>를 함께 활용하기</h3>
<p>앞서 살펴본 here document 연산자 <code>&lt;&lt;</code>에 <code>&gt;</code>(redirect output)를 추가하면 입력한 내용을 파일로 저장할 수 있다.</p>
<p>여기서 <code>&gt;</code>는 redirect output 연산자로, 출력 결과를 모니터 대신 지정한 파일로 보내라는 의미다. 즉 <code>&gt; new.txt</code>는 입력한 내용을 <code>new.txt</code>라는 이름의 텍스트 파일로 저장하라는 뜻이 된다. 이처럼 <code>cat</code>, <code>&lt;&lt;</code>, <code>&gt;</code>를 함께 사용하면 간단하게 텍스트 파일을 생성할 수 있다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6db0aa81-1d26-4ffc-aea5-8d59404bfcc7.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dc30c804-09b1-4128-bf86-13a66eba647d.png" alt="" style="display:block;margin:0 auto" />

<h3>Using <code>echo</code> with <code>&gt;</code> and <code>&gt;&gt;</code></h3>
<p><code>echo</code> is a command that outputs the text you type. When used with <code>&gt;</code>, it saves the content to a file.</p>
<p>Here, <code>&gt;</code> overwrites any existing file content. In contrast, <code>&gt;&gt;</code> appends to the existing content. If <code>&gt;</code> is used instead of <code>&gt;&gt;</code>, the existing <code>sample-text</code> would be gone and only <code>another-line</code> would remain. In summary, <code>&gt;</code> means overwrite and <code>&gt;&gt;</code> means append. One thing to note: if <code>&gt;&gt;</code> is used when there is no existing content, since there is nothing to append to, a new file will be created with the input as the first line.</p>
<h3><code>echo</code>와 <code>&gt;</code>, <code>&gt;&gt;</code> 활용</h3>
<p>echo는 입력한 텍스트를 출력하는 명령어로, &gt;와 함께 사용하면 내용을 파일로 저장할 수 있다.</p>
<p>여기서 &gt;는 기존 파일 내용을 덮어쓴다. 반면 <code>&gt;&gt;</code>는 기존 내용에 이어붙이는 역할을 한다. 만약 <code>&gt;&gt;</code>대신 <code>&gt;</code>를 사용했다면 기존의 sample-text는 사라지고 another-line만 출력된다. 정리하자면 &gt;는 덮어쓰기, &gt;&gt;는 이어붙이기라고 이해하면 된다. 한 가지 주의할 점은, 아무 내용도 없는 상태에서 &gt;&gt;를 사용하면 이어붙일 내용이 없으므로 새 파일이 생성되며 입력한 내용이 첫 줄로 저장될 것이다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/53db88ce-3b45-43c3-8c5a-d1e35e16d614.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b52420c9-80fe-4e07-8788-b51acf73a913.png" alt="" style="display:block;margin:0 auto" />

<h3>Editing Text Files: <code>sed</code></h3>
<p>Once a file is created, editing becomes necessary. As content grows, it is not practical to recreate the file every time a single character needs to be fixed. This is where <code>sed</code> comes in, defined as "stream editor for filtering and transforming text."</p>
<pre><code class="language-bash">cat sample.txt | sed 's/-/*/'
</code></pre>
<p>The meaning of <code>'s/-/*/'</code> is as follows: <code>s</code> stands for substitute, meaning replace <code>-</code> with <code>*</code>. Running the command outputs the following:</p>
<pre><code class="language-plaintext">sample*text
another*line
</code></pre>
<p>Importantly, the original file is not changed. Running <code>cat sample.txt</code> again still outputs <code>sample-text</code> and <code>another-line</code>. The result of <code>sed</code> is only shown on the standard output device and is not applied to the original file.</p>
<h3>텍스트 파일 편집: <code>sed</code></h3>
<p>파일을 만들었다면 이제 편집이 필요하다. 내용이 많아질수록 한 글자 때문에 파일을 매번 새로 만들 수는 없기 때문이다. 이때 사용하는 것이 <code>sed</code>로, "stream editor for filtering and transforming text"로 정의된다. 즉 텍스트를 걸러내고 변환하는 스트림 편집기다.</p>
<p>여기서 <code>'s/-/*/'</code>의 의미는 다음과 같다. <code>s</code>는 substitute(치환)를 뜻하며, <code>-</code>를 <code>*</code>로 바꾸라는 의미다. 위 명령어를 실행하면 결과는 아래와 같이 출력된다.</p>
<pre><code class="language-shell">sample*text
another*line
</code></pre>
<p>단, 중요한 점은 원본 파일은 변경되지 않는다는 것이다. <code>cat sample.txt</code>를 다시 실행하면 여전히 <code>sample-text</code>, <code>another-line</code>으로 출력된다. <code>sed</code>의 결과는 표준 출력장치에 보여지기만 할 뿐, 원본 파일에 적용되지 않기 때문이다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/99951356-b017-4c25-9317-738bbc0aa87b.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/62d96235-2acb-42fc-b80d-fa0115cf73c5.png" alt="" style="display:block;margin:0 auto" />

<h3>Saving to a File: <code>tee</code></h3>
<p>To save the result of <code>sed</code> to a file, use the <code>tee</code> command. <code>tee</code> is defined as "read from standard input and write to standard output and files," meaning it can simultaneously write to both the standard output device and a file. In other words, the result is displayed on screen and saved to a file at the same time — two benefits in one.</p>
<p>Running <code>cat sample.txt</code> outputs <code>sample-text</code> and <code>another-line</code>. Piping this to <code>tee smpl.txt</code> displays the same content on screen while also saving it to a new file called <code>smpl.txt</code>. Running <code>cat smpl.txt</code> afterward will show the same result as <code>cat sample.txt</code>.</p>
<h3>파일로 저장하기: <code>tee</code></h3>
<p><code>sed</code>의 결과를 파일에 저장하려면 <code>tee</code> 명령어를 활용한다. <code>tee</code>는 "read from standard input and write to standard output and files"로 정의되며, 표준 입력에서 읽어온 내용을 표준 출력장치와 파일 양쪽에 동시에 쓸 수 있다. 즉, 화면에도 결과가 출력되면서 파일에도 저장되는 일석이조의 명령어다.</p>
<p><code>cat sample.txt</code>를 실행하면 <code>sample-text</code>, <code>another-line</code>이 출력된다. 여기에 <code>tee smpl.txt</code>를 연결하면 같은 내용이 화면에 출력됨과 동시에 <code>smpl.txt</code>라는 새 파일에도 저장된다. 이후 <code>cat smpl.txt</code>를 실행하면 <code>sample.txt</code>의 결과와 동일하게 <code>sample-text</code>, <code>another-line</code>이 출력되는 것을 확인할 수 있다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a6aef40c-d384-415c-9631-cfdd34143100.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h1>2️⃣ Use a Text Editor</h1>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1f534984-e63d-4a65-ac86-558d3b2c1e34.png" alt="" style="display:block;margin:0 auto" />

<h3>Installing <code>vim</code></h3>
<p>Before installing <code>vim</code>, the package list needs to be updated. When Ubuntu was first installed, it may have only known about version 1.0, but version 1.5 with bug fixes may have since been released. Running <code>sudo apt update</code> refreshes the package list to its latest state. Then install vim with <code>sudo apt install vim</code>, and check the installed version by running <code>vim --version</code>.</p>
<h3><code>vim</code> 설치</h3>
<p><code>vim</code>을 설치하기 전에 먼저 패키지 목록을 최신화해야 한다. 우분투를 처음 설치했을 때는 1.0 버전만 알고 있었더라도, 이후 버그 패치 등을 거쳐 1.5 버전이 출시되었을 수 있기 때문이다. <code>sudo apt update</code>를 실행하면 패키지 목록이 최신 상태로 갱신된다. 이후 <code>sudo apt install vim</code>으로 vim을 설치하고, <code>vim --version</code>을 입력하면 현재 설치된 vim의 버전을 확인할 수 있다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/859370f9-e90c-41eb-8fdb-b021d9181865.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h3>The <code>vim</code> Launch Screen</h3>
<p>Type <code>vi</code> or <code>vim</code> at the prompt and press Enter to launch the editor. On the launch screen, each line begins with a <code>~</code> symbol — but be careful here. The <code>~</code> used in file paths means the current user's home directory, while the <code>~</code> at the start of each line in the vim screen means an empty line with no content. They look the same but mean completely different things, so be careful not to confuse them.</p>
<p>The numbers displayed on the right side of the screen indicate the current cursor position, representing the line (row) and column. In the center of the screen, a brief explanation and manual for using vim is displayed.</p>
<h3><code>vim</code> 실행 화면</h3>
<p>프롬프트에서 <code>vi</code> 또는 <code>vim</code>을 입력하고 엔터를 누르면 편집기 화면이 실행된다. 실행 화면에서 각 줄 맨 앞에 <code>~</code> 표시가 보이는데, 여기서 주의할 점이 있다. 경로에서 사용하는 <code>~</code>는 현재 사용자의 홈 디렉토리를 의미하지만, vim 화면에서 줄 맨 앞에 표시되는 <code>~</code>는 아무 내용도 없는 빈 줄을 의미한다. 같은 모양이지만 전혀 다른 의미이므로 혼동하지 않도록 주의해야 한다.</p>
<p>화면 오른쪽에 표시되는 숫자는 현재 커서의 위치를 나타내며, 줄(row, line)과 칸(column)을 의미한다. 화면 가운데에는 vim 사용 시 참고할 수 있는 간단한 설명과 매뉴얼이 표시된다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5435d4e5-7b56-4e06-901c-014cf6519fa1.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6270df63-d631-49e4-a82d-7de28347a63a.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1baef450-b8f7-4e79-b997-1c5a327d81a0.png" alt="" style="display:block;margin:0 auto" />

<h3><code>vim</code>'s Two Modes</h3>
<p><code>vim</code> is divided into two main modes. One is <strong>INSERT mode</strong>, where you can directly type text or program code, indicated by <code>-- INSERT --</code> at the bottom of the screen. The other is <strong>NORMAL mode</strong>, where instead of typing, you navigate - moving the cursor and finding specific locations.</p>
<p>When first encountering <code>vim</code>, this mode concept can be very confusing. Pressing keys does not type characters; the cursor just moves. However, this mode distinction is actually one of <code>vim</code>'s strengths.</p>
<h3><code>vim</code>의 두 가지 모드</h3>
<p><code>vim</code>은 크게 두 가지 모드로 구분된다. 하나는 <strong>INSERT 모드</strong>로, 텍스트나 프로그램 코드를 직접 입력할 수 있는 상태다. 화면 하단에 <code>-- INSERT --</code>라고 표시된다. 다른 하나는 <strong>NORMAL 모드</strong>로, 텍스트를 입력하는 것이 아니라 커서를 이동하거나 원하는 위치를 찾는 등의 탐색을 하는 상태다.</p>
<p>처음 <code>vim</code>을 접하면 이 모드 개념이 굉장히 혼란스럽게 느껴진다. 키보드를 눌러도 글자가 입력되지 않고 커서만 움직이기 때문이다. 그러나 이 모드 구분이 오히려 <code>vim</code>의 장점으로 작용하기도 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a8b9b586-83cd-4b50-aa16-e99696324bae.png" alt="" style="display:block;margin:0 auto" />

<h3>NORMAL Mode and EX Mode</h3>
<p>NORMAL mode, as mentioned, is a state for navigation — moving the cursor, scrolling the screen, deleting content, pasting, and so on. Pressing <code>:</code> in NORMAL mode switches to EX mode, where you can enter commands.</p>
<p>EX mode originates from the concept of a line editor, derived from the era before visual interfaces when text was processed one line at a time.</p>
<p>To return to NORMAL mode from INSERT mode, press <code>ESC</code>. Pressing <code>ESC</code> twice guarantees a return to NORMAL mode regardless of the current state.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/271d6770-e8ce-4c1d-8bfa-433ce94375d6.png" alt="" style="display:block;margin:0 auto" />

<h3>Entering INSERT Mode</h3>
<p>INSERT mode is the state where text can be directly entered. From NORMAL mode, pressing any of <code>i</code>, <code>I</code>, <code>a</code>, <code>A</code>, <code>o</code>, <code>O</code> activates INSERT mode, shown by <code>-- INSERT --</code> at the bottom of the screen. All of them enter INSERT mode, but each key differs in where the cursor is positioned when input begins.</p>
<ul>
<li><p><code>i</code> — starts input at the current cursor position</p>
</li>
<li><p><code>I</code> — starts input at the beginning of the current line</p>
</li>
<li><p><code>a</code> — starts input one position after the current cursor</p>
</li>
<li><p><code>A</code> — starts input at the end of the current line</p>
</li>
<li><p><code>o</code> — creates a new line below the current line and starts input there</p>
</li>
<li><p><code>O</code> — creates a new line above the current line and starts input there</p>
</li>
</ul>
<p>The cursor shape may appear as an underline, a block, or other forms. It is good practice to press <code>ESC</code> twice to ensure you are in NORMAL mode before trying each key to observe the differences in cursor placement.</p>
<h3><code>vim</code> INSERT 모드 진입 방법</h3>
<p>INSERT 모드는 텍스트를 직접 입력할 수 있는 상태로, NORMAL 모드에서 <code>i</code>, <code>I</code>, <code>a</code>, <code>A</code>, <code>o</code>, <code>O</code> 중 하나를 누르면 화면 하단에 <code>-- INSERT --</code>가 표시되며 진입할 수 있다. 모두 INSERT 모드로 전환된다는 공통점이 있지만, 각 키마다 커서가 시작되는 위치가 다르다.</p>
<p>각 키의 동작 차이는 다음과 같다. <code>i</code>는 현재 커서 위치에서, <code>I</code>는 현재 줄의 맨 앞에서 입력이 시작된다. <code>a</code>는 현재 커서의 다음 글자 위치에서 입력이 시작되며, <code>A</code>는 현재 줄의 맨 끝에서 시작된다. <code>o</code>는 현재 줄의 다음 줄에 새 줄을 만들어 입력할 수 있고, <code>O</code>는 현재 줄의 이전 줄에 새 줄을 만들어 입력할 수 있다.</p>
<p>커서 모양도 밑줄, 네모 등 여러 형태로 표시될 수 있다. <code>ESC</code>를 습관적으로 두 번 눌러 NORMAL 모드로 확실히 전환한 뒤 각 키를 눌러보면 커서 위치가 달라지는 것을 직접 확인할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2f078a41-d9d1-42a3-80c1-8c4c80df10cb.png" alt="" style="display:block;margin:0 auto" />

<h3>Creating an Example File and UTF-8 Encoding</h3>
<p>Entering <code>man ls &gt; vimLS.txt</code> saves the manual for the <code>ls</code> command into a file called <code>vimLS.txt</code>. <code>man ls</code> outputs the manual, and <code>&gt;</code> redirects that output to a file. Running <code>file vimLS.txt</code> afterward shows the file's attributes — the result will show UTF-8 text.</p>
<p>This differs from earlier when <code>file ~/.profile</code> returned ASCII. ASCII is composed of 7 bits and can only represent 128 characters, which is not enough to include Korean and many other languages. UTF-8 was developed to overcome this limitation. It is a newly established encoding standard that accommodates languages with large character sets, such as Korean, Japanese, and Chinese, and is now used almost universally. When Korean characters fail to display properly, it is often referred to as a CJK problem - an abbreviation for Chinese, Japanese, Korean - indicating the system cannot represent those characters. The saved file can be opened in vim by running <code>vim vimLS.txt</code>.</p>
<h3><code>vim</code> 예시 파일 생성과 UTF-8 인코딩</h3>
<p><code>man ls &gt; vimLS.txt</code>를 입력하면 <code>ls</code> 명령어의 설명서 내용이 <code>vimLS.txt</code> 파일로 저장된다. <code>man ls</code>는 <code>ls</code> 명령어의 매뉴얼을 출력하는 명령어이고, <code>&gt;</code>로 리다이렉트하여 그 내용을 파일에 저장하는 것이다. 이후 <code>file vimLS.txt</code>를 입력하면 해당 파일의 속성을 확인할 수 있는데, 결과로 UTF-8 text가 출력된다.</p>
<p>앞서 <code>file ~/.profile</code>에서는 ASCII로 출력되었던 것과 차이가 있다. ASCII는 7비트로 구성되어 128가지 문자만 표현할 수 있기 때문에 한글을 비롯한 다양한 언어를 표현하지 못한다. 이러한 한계를 극복하기 위해 등장한 것이 UTF-8 인코딩 방식이다. UTF-8은 한글, 일본어, 중국어처럼 표현해야 할 문자 수가 많은 언어를 수용하기 위해 새롭게 제정된 인코딩 방식으로, 요즘은 거의 대부분 UTF-8을 사용한다. 한글이 제대로 표시되지 않을 때 CJK 문제라는 표현을 쓰는데, 이는 Chinese, Japanese, Korean의 약자로 해당 언어들의 문자를 표현하지 못하는 상태를 의미한다. 이렇게 저장된 파일은 <code>vim vimLS.txt</code>를 입력하면 저장된 내용을 vim 편집기에서 확인할 수 있다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/828fa059-677c-40ce-ba43-c343c8db89f8.png" alt="" style="display:block;margin:0 auto" />

<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c1dce0d9-a168-4c6c-b4fd-7b91539b2237.png" alt="" style="display:block;margin:0 auto" />

<h3><code>vim</code> Cursor Movement</h3>
<p>Opening <code>vim vimLS.txt</code> displays information at the bottom such as <code>"vimLS.txt" 249L, 8383B</code>. Here, <code>249L</code> is the total number of lines including blank lines, and <code>8383B</code> is the number of bytes the document occupies. The <code>1,1</code> in the bottom right indicates the current cursor position — line (row) and column.</p>
<p>One thing to note about bytes: CJK characters — Korean, Japanese, Chinese — typically occupy 2 bytes. For example, the word "한글" appears as two characters visually, but internally it is stored as more bytes than that.</p>
<p>Cursor movement can be done with <code>h</code>, <code>j</code>, <code>k</code>, <code>l</code> instead of the arrow keys — left, down, up, and right respectively. This is a legacy from early keyboards that lacked arrow keys. Since computers at that time were paper-based, directional movement was unnecessary. In the modern era, arrow keys are available, but the <code>h</code>, <code>j</code>, <code>k</code>, <code>l</code> convention remains a <code>vim</code> tradition and is still fully supported.<code>vim</code> 커서 이동</p>
<p><code>vim vimLS.txt</code>를 입력하면 편집기 화면이 열리며, 화면 하단에 <code>"vimLS.txt" 249L, 8383B</code>와 같은 정보가 표시된다. 여기서 <code>249L</code>은 빈 줄을 포함한 전체 줄 수를 의미하고, <code>8383B</code>는 문서가 차지하는 바이트 수를 의미한다. 화면 오른쪽 하단의 <code>1,1</code>은 현재 커서의 위치로, 줄(row)과 칸(column)을 나타낸다.</p>
<p>바이트 수와 관련하여 한 가지 알아둘 점이 있다. CJK 문자, 즉 한국어, 일본어, 중국어는 보통 2바이트를 차지한다. 예를 들어 "한글"은 눈에 보이기엔 두 글자지만 컴퓨터 내부에서는 그보다 더 많은 바이트로 처리된다.</p>
<p>커서 이동은 방향키 대신 <code>h</code>, <code>j</code>, <code>k</code>, <code>l</code> 키로 할 수 있다. 각각 왼쪽, 아래, 위, 오른쪽에 해당한다. 이는 초기 키보드에 방향키가 없었던 시절의 흔적이다. 당시 컴퓨터는 종이 기반으로 작동했기 때문에 상하좌우 이동이 필요 없었고, 별도의 키 매핑이 필요했다. 현대에는 키보드가 발전하여 방향키를 사용할 수 있지만, <code>h</code>, <code>j</code>, <code>k</code>, <code>l</code> 방식은 vim의 전통으로 남아 있으며 vim improved에서도 동일하게 사용 가능하다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/772c631d-5997-449a-88d6-ad3140f6212c.png" alt="" style="display:block;margin:0 auto" />

<h3>Cursor Movement: Beginning and End of a Line</h3>
<p>In NORMAL mode, <code>0</code>, <code>\(</code>, and <code>^</code> allow quick movement within a line. <code>0</code> moves to the very beginning of the line, <code>\)</code> moves to the very end, and <code>^</code> moves to the first non-blank character of the line. For example, if there are spaces at the start of a line, <code>0</code> moves to the absolute beginning including those spaces, while <code>^</code> moves to where the actual content starts. This can also help identify whether the leading space was created with the spacebar or the tab key.</p>
<p>Note that cursor movement commands including <code>h</code>, <code>j</code>, <code>k</code>, <code>l</code> only work in NORMAL mode. Always confirm you have pressed <code>ESC</code> to switch to NORMAL mode before using them.</p>
<h3><code>vim</code> 커서 이동: 줄의 시작과 끝</h3>
<p>NORMAL 모드에서 <code>0</code>, <code>\(</code>, <code>^</code> 키를 사용하면 커서를 줄 단위로 빠르게 이동할 수 있다. <code>0</code>을 누르면 줄의 맨 앞으로, <code>\)</code>를 누르면 줄의 맨 끝으로 이동한다. <code>^</code>를 누르면 줄에서 내용이 시작되는 첫 번째 문자로 이동한다. 예를 들어 줄 앞에 빈칸이 있는 경우, <code>0</code>은 빈칸을 포함한 맨 앞으로 이동하지만 <code>^</code>는 실제 내용이 시작되는 위치로 이동한다. 이를 통해 해당 빈칸이 스페이스바로 만들어진 것인지 탭 키로 만들어진 것인지도 확인할 수 있다.</p>
<p>주의할 점은 <code>h</code>, <code>j</code>, <code>k</code>, <code>l</code>을 비롯한 커서 이동 명령은 반드시 NORMAL 모드에서만 작동한다는 것이다. <code>ESC</code> 키를 눌러 NORMAL 모드로 전환된 것을 확인한 뒤 사용하도록 하자.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d37106e1-4aaa-4e34-bcbd-958e4ade6e0e.png" alt="" style="display:block;margin:0 auto" />

<h3>Screen-Level Movement</h3>
<p>Beyond line-by-line movement, you can jump by entire screens. In NORMAL mode, <code>Ctrl+f</code> moves one full screen down, <code>Ctrl+b</code> moves one full screen up, <code>Ctrl+d</code> moves half a screen down, and <code>Ctrl+u</code> moves half a screen up. This is useful for navigating long files much faster than moving one line at a time.</p>
<h3><code>vim</code> 화면 단위 이동</h3>
<p>한 줄씩 이동하는 것 외에도 화면 단위로 한 번에 이동할 수 있다. NORMAL 모드에서 <code>Ctrl</code>키와 함께 사용하며, <code>Ctrl+f</code>는 한 화면 아래로, <code>Ctrl+b</code>는 한 화면 위로 이동한다. <code>Ctrl+d</code>는 화면의 절반만큼 아래로, <code>Ctrl+u</code>는 화면의 절반만큼 위로 이동한다. 한 줄씩 이동하는 것보다 빠르게 원하는 위치로 점프할 수 있어 긴 파일을 탐색할 때 유용하다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d4224c9d-70e8-481c-8b2d-93deeb2d05f1.png" alt="" style="display:block;margin:0 auto" />

<h3>Jumping to a Specific Line</h3>
<p>For files with tens of thousands of lines, even screen-level movement can take a long time. In NORMAL mode, pressing <code>gg</code> jumps to the first line, and pressing <code>G</code> jumps to the last line. Typing a line number followed by <code>G</code> jumps directly to that line.</p>
<p>This is one of the most powerful reasons to use <code>vim</code> — no need to scroll with a mouse to reach a specific location. While the experience varies by user, many people use <code>vim</code> not just because it looks impressive, but because it genuinely meets their needs.</p>
<h3><code>vim</code> 특정 줄로 이동</h3>
<p>페이지가 수만 줄에 달하는 파일이라면 화면 단위 이동으로도 한참을 이동해야 한다. 이때 특정 줄로 바로 이동하는 기능을 활용할 수 있다. NORMAL 모드에서 <code>gg</code>를 누르면 첫 번째 줄로, 대문자 <code>G</code>를 누르면 맨 마지막 줄로 이동한다. 이동하고 싶은 줄 번호를 입력한 뒤 <code>G</code>를 누르면 해당 줄로 바로 이동할 수 있다.</p>
<p>이것이 vim을 사용하는 가장 강력한 이유 중 하나다. 마우스로 스크롤하지 않아도 원하는 위치로 즉시 이동할 수 있기 때문이다. 사용자에 따라 경험은 다르겠지만, vim을 사용하는 사람들은 단순히 멋있어서가 아니라 실제로 필요하기 때문에 사용하는 경우가 많다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dbc06a0c-9413-469c-8808-5e523b3f81f6.png" alt="" style="display:block;margin:0 auto" />

<h3>Word-Level Movement</h3>
<p>Movement is also possible at the word level. In NORMAL mode, pressing <code>w</code> moves to the next word and <code>b</code> moves to the previous word.</p>
<p>One important thing to keep in mind is that case matters. For example, <code>|</code>, <code>l</code>, and <code>I</code> look visually similar but are entirely different characters. Catching these differences quickly comes from a developer's eye, and the more you work with computers from a developer's perspective, the more naturally this awareness develops.</p>
<h3><code>vim</code> 단어 단위 이동</h3>
<p>한 문자씩 혹은 화면 단위 이동 외에도 단어 단위로 이동하는 것도 가능하다. NORMAL 모드에서 <code>w</code>를 누르면 다음 단어로, <code>b</code>를 누르면 이전 단어로 이동한다.</p>
<p>여기서 주의해야 할 점은 대소문자를 반드시 구분해야 한다는 것이다. 예를 들어 <code>|</code>, <code>l</code>, <code>I</code>는 시각적으로 비슷해 보이지만 모두 다른 문자다. 이러한 차이를 빠르게 캐치하는 것은 개발자적 감각에서 비롯되며, 컴퓨터를 개발자 입장에서 많이 다뤄볼수록 자연스럽게 익숙해진다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d816dcdb-9d57-4691-beb8-b5422b9c44fd.png" alt="" style="display:block;margin:0 auto" />

<h3>Word-Level Movement 2: <code>B</code> and <code>W</code></h3>
<p>Unlike lowercase <code>b</code> and <code>w</code>, uppercase <code>B</code> and <code>W</code> use whitespace as the word boundary. Lowercase versions recognize special characters and punctuation as word boundaries, while uppercase versions only recognize spaces. Therefore, <code>W</code> moves to the word after the next space, and <code>B</code> moves to the word before the previous space. Since the movement range is broader than the lowercase versions, navigation is faster.</p>
<h2><code>vim</code> 단어 단위 이동 2: 대문자 <code>B</code>, <code>W</code></h2>
<p>앞서 살펴본 소문자 <code>b</code>, <code>w</code>와 달리 대문자 <code>B</code>, <code>W</code>는 빈칸을 기준으로 단어 단위 이동을 한다. 소문자는 특수문자나 구두점 등을 단어의 경계로 인식하지만, 대문자는 오직 빈칸만을 기준으로 삼는다. 따라서 <code>W</code>는 다음 빈칸 너머의 단어로, <code>B</code>는 이전 빈칸 너머의 단어로 이동한다. 이동 범위가 소문자보다 넓기 때문에 더 빠르게 이동할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3f150d12-258f-48d8-a34c-275732730e96.png" alt="" style="display:block;margin:0 auto" />

<h3>Entering INSERT Mode 1: <code>i</code> and <code>a</code></h3>
<p>To type actual text or code in NORMAL mode, you need to switch to INSERT mode. The two most common keys are lowercase <code>i</code> and <code>a</code>. Both enter INSERT mode, but they differ in where input begins.</p>
<p>For example, if the cursor is on the second character of a 5-character text, pressing <code>i</code> starts input at the current position (second character), while pressing <code>a</code> starts input one position after (third character). In short, <code>i</code> starts at the current position and <code>a</code> starts one character to the right.</p>
<h3><code>vim</code> INSERT 모드 진입 1: <code>i</code>와 <code>a</code></h3>
<p>NORMAL 모드에서 실제 문자나 코드를 입력하려면 INSERT 모드로 전환해야 한다. 대표적인 방법이 소문자 <code>i</code>와 <code>a</code>이며, 둘 다 INSERT 모드로 진입하지만 커서 위치에 차이가 있다.</p>
<p>예를 들어 5칸짜리 텍스트에서 커서가 두 번째 칸에 위치해 있다고 가정하면, <code>i</code>를 누르면 현재 커서 위치인 두 번째 칸부터 입력이 시작된다. 반면 <code>a</code>를 누르면 현재 커서의 바로 다음 위치인 세 번째 칸부터 입력이 시작된다. 즉 <code>i</code>는 현재 위치에서, <code>a</code>는 현재 위치의 한 칸 뒤에서 입력이 시작된다는 차이가 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/773deb3d-d6a5-40a1-b4b5-12008abedecd.png" alt="" style="display:block;margin:0 auto" />

<h3>Entering INSERT Mode 2: <code>o</code> and <code>O</code></h3>
<p>Lowercase <code>o</code> creates a new line below the current cursor position and begins input at the first character of that new line. Uppercase <code>O</code> creates a new line above the current cursor position and begins input at the first character of that new line.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c6b56d0a-cc57-48ef-bf02-297b3efb6e3c.png" alt="" style="display:block;margin:0 auto" />

<h3>Deleting and Undoing</h3>
<p>In NORMAL mode, pressing <code>x</code> deletes the single character at the current cursor position. Pressing uppercase <code>D</code> deletes everything from the current cursor position to the end of the line. Pressing <code>dd</code> deletes the entire line.</p>
<p>For undoing, this is similar to <code>Ctrl+Z</code> in Windows. Pressing <code>u</code> in NORMAL mode undoes one action at a time, stepping back through previous states with each press. Uppercase <code>U</code> restores the entire current line to its state before any changes were made.</p>
<h2><code>vim</code> 삭제와 되돌리기</h2>
<p>NORMAL 모드에서 <code>x</code>를 누르면 현재 커서 위치의 한 글자가 삭제된다. 대문자 <code>D</code>를 누르면 현재 커서 위치부터 줄의 끝까지 한 번에 삭제된다. <code>dd</code>를 누르면 커서가 있는 줄 전체가 삭제된다.</p>
<p>되돌리기는 윈도우의 <code>Ctrl+Z</code>와 유사한 개념이다. NORMAL 모드에서 <code>u</code>를 누르면 누를 때마다 이전 상태로 한 단계씩 되돌아간다. 대문자 <code>U</code>는 커서가 있는 줄 전체를 변경 이전 상태로 되돌린다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0aa5f315-a50c-49d8-9038-0ba3bb43e0b2.png" alt="" style="display:block;margin:0 auto" />

<h3>Replacing a Single Character</h3>
<p>As learned, <code>x</code> deletes a single character at the cursor position. To replace rather than delete a character, there are two methods.</p>
<p>Pressing <code>~</code> toggles the case of the character at the cursor position between uppercase and lowercase. This is useful in alphabetic language environments and does not apply to CJK characters like Korean, Japanese, or Chinese.</p>
<p>In NORMAL mode, pressing <code>r</code> followed by a desired key replaces the character at the cursor with the newly pressed character — a quick way to swap a single character without the extra step of deleting and retyping.</p>
<h3><code>vim</code> 한 글자 바꾸기</h3>
<p>앞서 <code>x</code>는 현재 커서 위치의 한 글자를 삭제한다고 배웠다. 삭제가 아닌 한 글자만 바꾸고 싶을 때는 두 가지 방법을 사용할 수 있다.</p>
<p><code>~</code>를 누르면 커서 위치의 문자 대소문자가 전환된다. 알파벳 기반의 언어권에서 유용하게 활용할 수 있으며, 한국어, 일본어, 중국어와 같은 CJK 문자에는 해당되지 않는다.</p>
<p>NORMAL 모드에서 <code>r</code>을 누른 뒤 원하는 키를 입력하면 현재 커서 위치의 글자가 새로 입력한 문자로 바뀐다. 삭제 후 다시 입력하는 번거로움 없이 한 글자를 빠르게 교체할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/871c2170-6bf2-4e56-933c-05ea200b454f.png" alt="" style="display:block;margin:0 auto" />

<h3>Searching for Content</h3>
<p>In NORMAL mode, pressing <code>/</code> moves the cursor to the bottom of the screen. Type the search term and press Enter to jump to the matching location. <code>/</code> searches in the forward (downward) direction from the current cursor position. Pressing <code>n</code> continues searching in the same direction, while <code>N</code> searches in the opposite direction.</p>
<p>Pressing <code>?</code> instead searches in the backward (upward) direction. In this case, <code>n</code> continues in the same direction (upward) and <code>N</code> goes in the opposite direction (downward). Note that forward and backward here refer to the direction of the search, not the visual position on screen.</p>
<h3><code>vim</code> 내용 찾기</h3>
<p>NORMAL 모드에서 <code>/</code>를 누르면 화면 맨 아래로 커서가 이동한다. 찾고자 하는 문구를 입력하고 엔터를 누르면 해당 내용이 있는 위치로 이동한다. <code>/</code>는 현재 커서 위치에서 뒤쪽 방향으로 탐색을 시작한다. 이때 <code>n</code>을 누르면 같은 방향(뒤쪽)으로 계속 탐색하고, <code>N</code>을 누르면 반대 방향(앞쪽)으로 탐색한다.</p>
<p>반대로 <code>?</code>를 누르고 문구를 입력하면 앞쪽 방향으로 탐색을 시작한다. 이 경우 <code>n</code>은 진행 방향 그대로 앞쪽으로, <code>N</code>은 반대 방향인 뒤쪽으로 탐색한다. 여기서 앞쪽과 뒤쪽은 화면 기준이 아닌 탐색이 진행되는 방향을 기준으로 한다는 점에 주의해야 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/587413b6-60b1-427e-9d22-6a437a836ee7.png" alt="" style="display:block;margin:0 auto" />

<h3>Other <code>vim</code> Commands</h3>
<p>Commands used after input can be referenced from the ed editor documentation. There are a great many commands, and knowing regular expressions will be an advantage in understanding and using them.</p>
<p>A notable example is <code>:s/short/SHORT/gc</code>, which replaces all instances of <code>short</code> with <code>SHORT</code> in the file. This is similar to the <code>sed 's/-/*/'</code> covered earlier. Here, <code>gc</code> stands for global confirm — it finds and replaces throughout the entire document while asking for confirmation each time.</p>
<p>Regular expressions are a vast topic — broad enough to be a subject on their own — so it is not possible to cover everything in this session. It is recommended to explore them separately when time allows, or through a related course.</p>
<p>For learning <code>vim</code>, the built-in help tutorial is a good resource, and <a href="https://vim-adventures.com/">https://vim-adventures.com/</a> offers a game-based approach to learning vim commands. Note that the site is paid.</p>
<h3><code>vim</code> 기타 명령어</h3>
<p>입력 후 사용하는 명령어는 ed 편집기 사용법을 참고하면 된다. 사용법이 매우 많으며, regular expression(정규 표현식)을 알고 있다면 이해와 활용에 유리하다.</p>
<p>대표적인 예로 <code>:s/short/SHORT/gc</code>는 파일 안의 모든 <code>short</code>를 <code>SHORT</code>로 변경하는 명령어다. 앞서 배운 <code>sed 's/-/*/'</code>와 유사한 방식이다. 여기서 <code>gc</code>는 global confirm의 약자로, 문서 전체에서 찾아 바꾸되 매번 확인을 거친다는 의미다.</p>
<p>정규 표현식은 그 자체로 하나의 과목이 될 만큼 방대한 주제이므로 이번 시간에 모두 다루기는 어렵다. 관련 과목을 수강하거나 시간이 날 때 별도로 찾아보는 것을 권장한다.</p>
<p><code>vim</code> 학습에 도움이 되는 자료로는 vim 내장 help의 튜토리얼이 있으며, <a href="https://vim-adventures.com/">https://vim-adventures.com/</a> 에서 게임 형식으로 사용법을 익힐 수도 있다. 단, 해당 사이트는 유료임을 참고하자.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/16c260a5-d346-494f-8f84-0fba146c44fe.png" alt="" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[Test Design: Concepts, Techniques, and Black Box Testing]]></title><description><![CDATA[1️⃣ Core Concepts of Test Design2️⃣ Classification of Test Design Techniques3️⃣ Black Box Test Preview



What is Test Design?
Test design is an activity performed after test planning is complete. It ]]></description><link>https://heesu.tech/test-design-concepts-techniques-and-black-box-testing</link><guid isPermaLink="true">https://heesu.tech/test-design-concepts-techniques-and-black-box-testing</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Sat, 04 Apr 2026 02:50:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cc3d7c03-543b-42b0-9d80-889b5409659e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>1️⃣ Core Concepts of Test Design<br />2️⃣ Classification of Test Design Techniques<br />3️⃣ Black Box Test Preview</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ffe0a529-ce62-4ff9-b68a-e7509ead5dd8.png" alt="" style="display:block;margin:0 auto" />

<h2>What is Test Design?</h2>
<p>Test design is an activity performed after test planning is complete. It is the stage where you define <em>what</em> to test and <em>how</em> to test it. During this process, the test targets and scope are determined, enabling more efficient test execution.</p>
<p>In software testing, it is practically impossible to verify every possible combination. For example, with just 2 variables that hold True/False values, there are already 4 combinations. As the number of variables grows to 20 or 100, the number of possible combinations increases exponentially, making exhaustive testing completely infeasible. This is precisely why an efficient test strategy is needed — and test design plays that central role.</p>
<p>From a PDCA cycle perspective, test design falls under the <strong>P (Plan)</strong> phase. This is because it is preparatory work that must be completed before tests are actually executed dynamically. Specifically, this phase includes test case development, test procedure definition, and test environment preparation. Each of these activities will be covered in detail in subsequent posts.</p>
<h3>테스트 설계란 무엇인가?</h3>
<p>테스트 설계는 테스트 계획이 완료된 이후에 수행되는 활동으로, 무엇을 어떻게 테스트할지를 구체화하는 단계다. 이 과정에서 테스트 대상과 범위가 결정되며, 이를 바탕으로 보다 효율적인 테스트를 수행할 수 있게 된다.</p>
<p>소프트웨어 테스트에서 모든 경우의 수를 전부 검증하는 것은 현실적으로 불가능하다. 예를 들어 True/False 값을 가지는 변수가 단 2개만 있어도 조합은 4가지가 되는데, 변수가 20개, 100개로 늘어나면 가능한 조합의 수는 기하급수적으로 증가해 완전한 테스트 자체가 불가능해진다. 바로 이 때문에 효율적인 테스트 전략이 필요하며, 테스트 설계가 그 핵심 역할을 담당한다.</p>
<p>PDCA 사이클 관점에서 테스트 설계는 <strong>P(Plan)</strong> 단계에 해당한다. 실제 테스트를 동적으로 실행하기 이전에 사전에 준비해야 하는 작업이기 때문이다. 구체적으로는 테스트 케이스 개발, 테스트 절차 정의, 테스트 환경 준비가 이 단계에 포함된다. 각 세부 활동에 대한 내용은 이후 강의에서 다룰 예정이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/12a39753-d7ee-435f-9e87-b28aee8ff6d0.png" alt="" style="display:block;margin:0 auto" />

<h3>Test Design as Defined by ISO/IEC/IEEE 29119</h3>
<p>ISO/IEC/IEEE 29119 is an international standard for software testing. Let's look at how test design is positioned within this standard.</p>
<p>The standard divides the test process into two main areas: the <strong>Test Management Process</strong> and the <strong>Dynamic Test Process</strong>. Test planning takes place within the test management process, and its outputs are passed on to the dynamic test process where actual testing activities occur.</p>
<p>Within the dynamic test process, there are two areas that correspond to test design.</p>
<p>The first is <strong>Test Design and Implementation</strong>. This stage takes the test plan as input and concretizes what and how to test, with <strong>Test Basis</strong> analysis as its starting point.</p>
<p>The second is <strong>Test Environment Setup and Maintenance</strong>. Based on the environment requirements identified during test design and implementation, this activity involves building and managing the actual test environment.</p>
<p>The outputs of these two stages; the test cases and the prepared test environment; are passed together to the <strong>Test Execution</strong> stage, where dynamic testing finally takes place.</p>
<p>In summary, the key point is that within the 29119 standard, test design is defined as a concept that encompasses not just writing test cases, but also setting up the test environment.</p>
<h3>ISO/IEC/IEEE 29119에서 정의하는 테스트 설계</h3>
<p>ISO/IEC/IEEE 29119는 소프트웨어 테스팅에 관한 국제 표준으로, 이 표준에서 테스트 설계가 어떻게 위치하는지를 살펴보자.</p>
<p>표준에서 테스트 프로세스는 크게 <strong>테스트 관리 프로세스</strong>와 <strong>동적 테스트 프로세스</strong>로 구분된다. 테스트 계획은 테스트 관리 프로세스에서 진행되며, 그 결과가 동적 테스트 프로세스로 전달되어 실제 테스트 활동이 이루어진다.</p>
<p>동적 테스트 프로세스 내에서 테스트 설계에 해당하는 영역은 두 가지다.</p>
<p>첫째, <strong>테스트 설계 및 구현</strong>이다. 테스트 계획을 입력으로 받아 테스트 베이시스(Test Basis)를 도출하는 단계로, 무엇을 어떻게 테스트할지 구체화된다.</p>
<p>둘째, <strong>테스트 환경 구성 및 유지</strong>다. 테스트 설계 및 구현 단계에서 도출된 환경 요구사항을 바탕으로 실제 테스트 환경을 구성하고 관리하는 활동이다.</p>
<p>이 두 단계의 결과물, 즉 테스트 케이스와 준비된 테스트 환경이 함께 <strong>테스트 수행</strong> 단계로 전달되어 비로소 동적 테스트가 실제로 실행된다.</p>
<p>정리하면, 29119 표준에서 테스트 설계는 단순히 테스트 케이스를 작성하는 것에 그치지 않고, 테스트 환경 구성까지 포함하는 개념으로 정의된다는 점이 핵심이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/568608ec-9351-44bd-9ef4-dd3879f3fe5f.png" alt="" style="display:block;margin:0 auto" />

<h3>Test Design and Implementation / Test Environment Setup and Maintenance</h3>
<p>ISO/IEC/IEEE 29119 divides test design into two major activities.</p>
<p><strong>Test Design and Implementation</strong> is the stage where test cases and procedures are defined based on the test scope and strategy identified in the test plan. The starting point is <strong>Test Basis</strong> analysis. The Test Basis refers to the artifacts that serve as the foundation for creating test cases. In the V-model, for example, architecture design artifacts serve as the basis for creating integration test cases - the left-side (development) artifacts that are referenced to build right-side (test) cases. By analyzing the Test Basis, the following items are derived: Test requirements, Test conditions, Test coverage criteria and Test cases.</p>
<p><strong>Test Environment Setup and Maintenance</strong> requires that an appropriate environment and data be ready before tests can actually be executed. Taking automotive software as an example, testing may begin in a PC environment, gradually expand to a laboratory setting, and ultimately extend to a real road environment where actual vehicles operate. Constructing these various test environments and preparing the necessary data for each is the core activity of this stage.</p>
<h3>테스트 설계 및 구현 / 테스트 환경 구성 및 유지</h3>
<p>ISO/IEC/IEEE 29119 표준에서 테스트 설계는 크게 두 가지 활동으로 나뉜다.</p>
<p><strong>테스트 설계 및 구현 테스트</strong>: 계획에서 식별된 테스트 범위와 전략을 바탕으로 테스트 케이스와 절차를 구체화하는 단계다. 이 과정의 출발점은 테스트 베이시스(Test Basis) 분석이다. 테스트 베이시스란 테스트 케이스를 만들기 위한 기반이 되는 산출물을 말한다. V모델을 예로 들면, 통합 테스트 케이스를 만들 때 아키텍처 설계 산출물이 그 기반이 된다. 즉, 오른쪽(테스트 단계)의 케이스를 만들기 위해 참조하는 왼쪽(개발 단계)의 산출물이 테스트 베이시스에 해당한다. 이 테스트 베이시스를 분석함으로써 다음 항목들을 도출하게 된다.</p>
<ul>
<li><p>테스트 요구사항</p>
</li>
<li><p>테스트 조건</p>
</li>
<li><p>테스트 커버리지</p>
</li>
<li><p>기준 테스트 케이스</p>
</li>
</ul>
<p><strong>테스트 환경 구성 및 유지:</strong> 테스트를 실제로 실행하려면 적절한 환경과 데이터가 준비되어야 한다. 자동차 소프트웨어를 예로 들면, 초기에는 PC 환경에서 테스트하지만 점차 실험실 환경으로 확장되고, 최종적으로는 실제 차량이 주행하는 도로 환경에서까지 테스트가 이루어질 수 있다. 이처럼 다양한 테스트 환경을 구성하고, 각 환경에서 필요한 데이터를 준비하는 것이 이 단계의 핵심 활동이다.</p>
<hr />
<h3>The Need for Test Design</h3>
<p>Why is test design necessary? It can be summarized into two main purposes.</p>
<p>First, to perform <strong>efficient and effective testing</strong>.</p>
<p>Second, to properly <strong>verify software across diverse platforms and environments</strong>.</p>
<p>More detailed coverage of each purpose follows below.</p>
<h3>테스트 설계의 필요성</h3>
<p>테스트 설계는 왜 필요한 걸까? 크게 두 가지 목적으로 정리할 수 있다.</p>
<p>첫째, <strong>효율적이면서 효과적인 테스트를 수행하기 위해서다.</strong></p>
<p>둘째, <strong>다양한 플랫폼과 환경에서 소프트웨어를 제대로 검증하기 위해서다.</strong></p>
<p>각 목적에 대한 더 자세한 내용은 이어지는 내용에서 다룰 예정이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3f8f1dce-0f15-4ece-9a08-9402a0b6820d.png" alt="" style="display:block;margin:0 auto" />

<h3>Two Reasons Why Test Design is Necessary</h3>
<ol>
<li><p>Efficient and Effective Test Execution Verifying every single test case is simply not realistic. Projects always operate under limited resources — people, budget, tools, and equipment. Therefore, a systematic approach is needed to secure sufficient coverage with a minimal number of test cases. What matters is not the sheer volume of test cases, but the ability to derive cases with a high probability of detecting defects. Even with tens of thousands of test cases, if only a handful of defects are found, it cannot be considered good testing. Test design provides the systematic techniques to achieve this.</p>
</li>
<li><p>Verification Across Diverse Environments and Platforms No matter how well software runs on a developer's PC, it is meaningless if it does not operate reliably in the actual user environment. To verify behavior across various operating systems, browsers, hardware, and devices, the corresponding environments must be set up in advance. From the V-model perspective, starting to build the test environment only after coding is finished is already too late. Test environments, equipment, and tool configurations must be prepared in parallel with the left-side activities of writing test specifications and specs.</p>
</li>
</ol>
<p>Key Point: Test design does not happen after coding — it proceeds in parallel with the development activities of the V-model.</p>
<h3>테스트 설계의 필요성; 두 가지 이유</h3>
<ol>
<li><p><strong>효율적이고 효과적인 테스트 수행:</strong> 모든 테스트 케이스를 일일이 검증하는 것은 현실적으로 불가능하다. 프로젝트에서는 인력, 비용, 도구, 장비 등 자원이 항상 제한되어 있기 때문이다. 따라서 적은 수의 테스트 케이스로 충분한 커버리지를 확보하는 체계적인 접근이 필요하다. 이때 중요한 것은 단순히 테스트 케이스의 수가 아니라 결함을 발견할 확률이 높은 테스트 케이스를 도출하는 것이다. 테스트 케이스가 수만 개라도 발견한 결함이 극히 일부에 불과하다면 좋은 테스트라고 할 수 없다. 테스트 설계는 이를 위한 체계적인 기법을 제공한다.</p>
</li>
<li><p><strong>다양한 환경과 플랫폼에서의 검증:</strong> 개발자 PC에서 아무리 잘 동작하는 소프트웨어라도, 실제 사용자 환경에서 안정적으로 동작하지 않으면 의미가 없다. 다양한 운영체제, 브라우저, 하드웨어, 디바이스에서의 동작 여부를 검증하려면 해당 환경이 미리 구축되어 있어야 한다. V모델 관점에서 보면, 테스트 환경 구축은 코딩이 끝난 후에 시작해서는 너무 늦다. 테스트 명세와 테스트 스펙을 작성하는 왼쪽 활동과 병행해서 실제 테스트 환경, 장비, 도구 세팅을 함께 준비해 나가야 한다.</p>
</li>
</ol>
<p>핵심 포인트: 테스트 설계는 코딩 이후가 아닌, V모델의 개발 활동과 함께 진행된다는 점을 반드시 기억하자.</p>
<hr />
<h3>Test Design Activity ① Test Case Development</h3>
<p>The first major activity in test design is <strong>test case development</strong>. Based on the Test Basis; the left-side V-model artifacts such as requirements documents, design documents, and code. This involves writing test cases to verify that the software properly satisfies its requirements.</p>
<p>A test case is a document that systematically records test conditions, input values, expected output values, and actual results. Let's look at a concrete example.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4d2e43e4-efd3-4725-a7a2-4595562d40bf.png" alt="" style="display:block;margin:0 auto" />

<p>In TC_1, since the lowercase "cuk" is stored in the DB, entering "CUK" in uppercase should produce an "ID not found" warning. If the actual result is "Login successful," it means case sensitivity is not properly implemented, and debugging is required. Once resolved, if "ID not found" warning appears correctly, the defect is fixed.</p>
<p>TC_2's expected output and actual result both show "Incorrect password warning," so it is determined to be functioning correctly.</p>
<p>In this way, test cases are written based on the Test Basis; test specs, design documents, etc. — to systematically verify that the software meets its requirements.</p>
<h3>테스트 설계의 주요 활동 ① 테스트 케이스 개발</h3>
<p>테스트 설계의 첫 번째 주요 활동은 <strong>테스트 케이스 개발</strong>이다. 요구사항, 설계서, 코드 등 V모델 왼쪽의 산출물인 테스트 베이시스를 기반으로, 소프트웨어가 요구사항을 제대로 만족하는지 확인하기 위한 테스트 케이스를 작성하는 작업이다.</p>
<p>테스트 케이스는 테스트 조건, 입력값, 예상 출력값, 실행 결과 등을 체계적으로 기록한 문서다. 아래 예시를 통해 구체적으로 살펴보자.</p>
<p>TC_1의 경우 DB에 소문자 cuk로 저장되어 있으므로 대문자 CUK 입력 시 "아이디 없음 경고"가 예상 출력값이다. 그런데 실행 결과가 "정상 로그인"이라면 대소문자 구분이 제대로 구현되지 않은 것이므로 디버깅이 필요하다. 조치 후 "아이디 없음 경고"가 정상 출력되면 결함이 해결된 것이다.</p>
<p>TC_2는 예상 출력값과 실행 결과가 모두 "비밀번호 틀림 경고"로 일치하므로 정상 동작으로 판단한다.</p>
<p>이처럼 테스트 케이스는 테스트 스펙이나 설계서 등 테스트 베이시스를 기반으로, 소프트웨어가 요구사항을 만족하는지를 체계적으로 검증하기 위한 형태로 작성된다.</p>
<hr />
<h3>[Reference] Test Case Components</h3>
<p>There are certain components that must be included when writing a test case.</p>
<p><strong>ID</strong> is a naming convention used to identify each test case. When there are hundreds or thousands of test cases, a systematic ID system is essential for distinguishing them. For example, unit tests might use UT-1, UT-2, and system tests might use ST-1, ST-2.</p>
<p><strong>Conditions</strong> are the preconditions required for the test to run. These specify what must be in place before execution — such as a specific environment being set up, or certain variable values being pre-stored.</p>
<p><strong>Expected output</strong> is the anticipated result when the test runs correctly. Just as "ID not found warning" is the expected output when an uppercase ID is entered in the earlier example, each test case must have a clearly defined expected result defined in advance.</p>
<p>In addition, the <strong>test purpose</strong>, <strong>test execution date</strong>, and <strong>assigned tester</strong> should also be recorded when writing test cases.</p>
<h3>[참고] 테스트 케이스의 구성 항목</h3>
<p>테스트 케이스를 작성할 때 반드시 포함되어야 하는 구성 항목들이 있다.</p>
<p><strong>ID</strong>는 테스트 케이스를 식별하기 위한 명명 규칙이다. 테스트 케이스가 수백, 수천 개에 달하는 경우 각각을 구분할 수 있어야 하므로 체계적인 ID 부여가 필수다. 예를 들어 유닛 테스트라면 UT-1, UT-2, 시스템 테스트라면 ST-1, ST-2와 같은 형식으로 작성한다.</p>
<p><strong>조건</strong>은 테스트가 수행되기 위한 사전 조건이다. 특정 환경이 갖춰져 있어야 한다거나, 변수값이 미리 저장되어 있어야 하는 등 테스트 실행 전에 반드시 정의되어 있어야 하는 항목들을 명시한다.</p>
<p><strong>예상 출력값</strong>은 테스트가 정상적으로 수행될 때 기대되는 결과값이다. 앞선 예시에서 대문자 아이디 입력 시 "아이디 없음 경고"가 출력되어야 하는 것처럼, 테스트 케이스마다 명확한 기대값이 사전에 정의되어 있어야 한다.</p>
<p>이 외에도 <strong>테스트 목적</strong>, <strong>테스트 수행 날짜</strong>, <strong>담당 테스터</strong> 등의 항목도 테스트 케이스 작성 시 함께 기록되어야 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a3677d59-9990-449b-b611-ebc6b4c237ac.png" alt="" style="display:block;margin:0 auto" />

<h3>Test Design Activity ② Test Procedure Definition</h3>
<p>The second major activity in test design is <strong>test procedure definition</strong>. This involves determining the specific <strong>order</strong> in which various tests — unit, integration, system, acceptance, etc. — will be performed.</p>
<p><strong>Integration Test Procedure Example</strong></p>
<p>In the V-model, integration testing verifies that interfaces between modules operate correctly, based on the software architecture. A representative artifact used here is the <strong>sequence diagram</strong> — a diagram that represents the flow of function calls between modules from top to bottom.</p>
<p>For example, when integration testing software composed of Mod_A through Mod_G, the procedure can be defined incrementally as follows. Rather than integrating all modules at once, you first verify that Function_A() between A and D operates correctly, then add E in step 2, followed by F, G, and so on. When software contains dozens or hundreds of modules, integrating everything at once is impossible — which is why defining a step-by-step procedure starting from small units is essential.</p>
<p><strong>Key Point</strong></p>
<p>Test procedure definition is not a concept exclusive to integration testing. Systematically defining the execution order applies to all test types, including unit testing and acceptance testing. From the V-model perspective, test procedure definition must be carried out in parallel with the left-side development activities.</p>
<h3>테스트 설계의 주요 활동 ② 테스트 절차 정의</h3>
<p>테스트 설계의 두 번째 주요 활동은 <strong>테스트 절차 정의</strong>다. 단위, 통합, 시스템, 인수 등 다양한 테스트를 어떤 순서로 수행할지 구체적인 절차를 결정하는 활동이다.</p>
<h3>통합 테스트 절차 예시</h3>
<p>V모델에서 통합 테스트는 소프트웨어 아키텍처를 기반으로 모듈 간 인터페이스가 적절히 동작하는지를 검증한다. 이때 활용되는 대표적인 산출물이 <strong>시퀀스 다이어그램</strong>이다. 시퀀스 다이어그램은 모듈 간에 수행되는 함수 호출 흐름을 위에서 아래로 표현한 다이어그램이다.</p>
<p>예를 들어 Mod_A ~ Mod_G로 구성된 소프트웨어를 통합 테스트한다면, 절차는 다음과 같이 단계적으로 정의할 수 있다. 한 번에 모든 모듈을 통합하는 것이 아니라, 1단계에서 A와 D 간의 Function_A() 호출이 정상 동작하는지 확인한 뒤, 2단계에서 E를 추가하고, 이후 F, G를 순차적으로 붙여가며 검증하는 방식이다. 소프트웨어 내에 수십, 수백 개의 모듈이 존재할 때 한 번에 통합하는 것은 불가능하므로, 이처럼 작은 단위부터 순서대로 통합해 나가는 절차가 반드시 필요하다.</p>
<h3>핵심 포인트</h3>
<p>테스트 절차 정의는 통합 테스트에만 해당하는 개념이 아니다. 단위 테스트, 인수 테스트 등 모든 테스트 유형에서 수행 순서를 체계적으로 정의하는 것이 필요하다. 또한 V모델 관점에서 테스트 절차 정의는 개발의 왼쪽 활동과 함께 병행하여 수행되어야 한다는 점을 반드시 기억하자.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6df4afa4-2280-4ea8-8301-853d80413aff.png" alt="" style="display:block;margin:0 auto" />

<h3>Test Design Activity ③ Test Environment Preparation</h3>
<p>The third major activity in test design is <strong>test environment preparation</strong>. This involves deciding in which environment each test; unit, integration, system, acceptance - will actually be executed, and building that environment in advance.</p>
<p>Test environment preparation requires not just software, but also hardware, tools, and equipment. Taking an automotive electronic control system as an example, the environment expands incrementally as integration progresses:</p>
<ol>
<li><p>Application Software — standalone test</p>
</li>
<li><p>Platform Software — integration and test</p>
</li>
<li><p>Hardware (ECU) — integration and test</p>
</li>
<li><p>Sensor / Actuator — connection and test</p>
</li>
<li><p>Mechanics — full system test including mechanical components</p>
</li>
</ol>
<p>Software development alone does not make testing possible. Sensors that provide input to the controller, actuators that handle actual operation, and mechanical components must all be prepared at each stage well in advance.</p>
<p>For this reason, test environment preparation should not begin after development is complete. It must be systematically planned and built in parallel with the left-side activities of the V-model.</p>
<h3>테스트 설계의 주요 활동 ③ 테스트 환경 준비</h3>
<p>테스트 설계의 세 번째 주요 활동은 <strong>테스트 환경 준비</strong>다. 단위, 통합, 시스템, 인수 등 각 테스트를 실제로 어떤 환경에서 수행할지 결정하고, 그에 맞는 환경을 미리 구축하는 활동이다.</p>
<p>테스트 환경 준비에는 소프트웨어뿐만 아니라 하드웨어, 도구, 장비 등이 함께 갖춰져야 한다. 자동차 전자 제어 시스템을 예로 들면, 전체 구조는 다음과 같이 단계적으로 통합되며 테스트 환경도 이에 맞춰 확장된다.</p>
<ol>
<li><p><strong>Application Software</strong> 단독 테스트</p>
</li>
<li><p><strong>Platform Software</strong> 통합 후 테스트</p>
</li>
<li><p><strong>Hardware(ECU)</strong> 통합 후 테스트</p>
</li>
<li><p><strong>Sensor / Actuator</strong> 연결 후 테스트</p>
</li>
<li><p><strong>Mechanics(기계적 구성 요소)</strong> 포함한 전체 시스템 테스트</p>
</li>
</ol>
<p>이처럼 소프트웨어만 개발한다고 테스트가 가능한 것이 아니다. 제어기에 입력을 제공하는 센서, 실제 구동을 담당하는 액추에이터, 그리고 기계적 구성 요소까지 각 단계에서 필요한 환경이 미리미리 준비되어 있어야 한다.</p>
<p>이러한 이유로 테스트 환경 준비는 개발이 완료된 후에 시작하는 것이 아니라, V모델의 왼쪽 활동과 병행하여 사전에 체계적으로 계획하고 구축해 나가야 한다.</p>
<hr />
<h1>2️⃣ Classification of Test Design Techniques</h1>
<h3>Classification of Test Design Techniques</h3>
<p>When creating test cases, systematic test design techniques are applied. Before exploring the types of techniques, let's take a moment to think about it with the V-model in mind.</p>
<blockquote>
<p><strong>How can test design techniques be classified?</strong></p>
</blockquote>
<p>In the V-model, unit, integration, system, and acceptance tests correspond to each stage of development. Just as the target and purpose of testing differ at each stage, the techniques used to derive test cases can also be classified in various ways depending on the criteria and perspective. The specific classification will be explored in the content that follows.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/145b376a-2719-4ec7-9281-67355ef1ddce.png" alt="" style="display:block;margin:0 auto" />

<h3>테스트 설계 기법의 분류</h3>
<p>테스트 케이스를 만들 때는 체계적인 <strong>테스트 설계 기법</strong>을 적용한다. 기법의 종류를 살펴보기 전에, 먼저 V모델을 떠올리며 스스로 생각해보자.</p>
<blockquote>
<p><strong>테스트 설계 기법은 어떤 기준으로 분류할 수 있을까?</strong></p>
</blockquote>
<p>V모델에서는 개발 단계에 따라 단위, 통합, 시스템, 인수 테스트가 대응된다. 각 단계마다 테스트의 대상과 목적이 다르듯, 테스트 케이스를 도출하는 기법도 그 기준과 관점에 따라 다양하게 분류될 수 있다. 구체적인 분류 방법은 이어지는 내용에서 살펴보도록 하자.</p>
<hr />
<h3>Classification of Test Design Techniques — Black Box vs. White Box</h3>
<p>Test design techniques are broadly divided into two categories: <strong>Black Box Testing</strong> and <strong>White Box Testing</strong>.</p>
<h3>Black Box Testing (Specification-Based Testing)</h3>
<p>A "black box" refers to a state where the internal structure and details are not visible. Black box testing focuses solely on whether the output for a given input is appropriate, without considering the software's internal logic. For example, the goal is to verify that inputting 1 and 2 produces 3; not to examine what logic internally calculated that result.</p>
<p>Test cases are derived not from source code, but from the left-side artifacts of the V-model — requirements documents and design specifications. Because it is based on specifications rather than code, it is also known as <strong>Specification-Based Testing</strong>.</p>
<h3>White Box Testing (Structure-Based Testing)</h3>
<p>A "white box" refers to a state where the internal structure and details are fully visible. White box testing creates test cases from the source code itself, with the goal of covering as many of the program's internal flows and paths as possible.</p>
<p>Since test cases are derived from the algorithms and logic defined in the source code or detailed design documents, it is also called <strong>Structure-Based Testing</strong>, as it involves examining the entire structure of the program.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/145c230b-3ec7-432a-b8d8-b0397c9e153b.png" alt="" style="display:block;margin:0 auto" />

<h3>테스트 설계 기법의 분류; 블랙박스 vs 화이트박스</h3>
<p>테스트 설계 기법은 크게 <strong>블랙박스 테스트</strong>와 <strong>화이트박스 테스트</strong> 두 가지로 나뉜다.</p>
<h3><strong>블랙박스 테스트 (명세 기반 테스트)</strong></h3>
<p>블랙박스란 내부 구조나 세부 내용이 보이지 않는 상태를 의미한다. 블랙박스 테스트는 소프트웨어의 내부 로직은 고려하지 않고, <strong>입력에 대해 적절한 출력이 나오는지</strong>에만 집중한다. 예를 들어 1과 2를 입력했을 때 3이 출력되는지를 확인하는 것이지, 내부적으로 어떤 로직을 통해 3이 계산되었는지는 보지 않는다.</p>
<p>테스트 케이스를 만들 때도 소스코드가 아닌 V모델의 왼쪽 산출물, 즉 요구사항 명세서나 설계 스펙을 기반으로 도출한다. 코드가 아닌 명세를 기반으로 한다는 점에서 <strong>명세 기반 테스트(Specification-based Test)</strong> 라고도 불린다.</p>
<h3>화이트박스 테스트 (구조 기반 테스트)</h3>
<p>화이트박스는 내부 구조와 세부 내용이 보이는 상태를 의미한다. 화이트박스 테스트는 소스코드 그 자체를 대상으로 테스트 케이스를 만들며, <strong>프로그램 내부의 다양한 흐름과 경로를 최대한 커버</strong>하는 것을 목표로 한다.</p>
<p>소스코드 또는 상세 설계서에 정의된 알고리즘과 로직을 기반으로 테스트 케이스를 도출하기 때문에, 프로그램의 전체 구조를 들여다본다는 의미에서 <strong>구조 기반 테스트(Structure-based Test)</strong> 라고도 불린다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c87f5646-8e8a-4081-8cd3-4aa24bcf1654.png" alt="" style="display:block;margin:0 auto" />

<h3>Black Box Testing (Specification-Based Testing) — In Depth</h3>
<p>Black box testing excludes the software's internal logic entirely and focuses only on whether the output for a given input is correct. Because test cases are derived from specifications such as requirements documents and design documents — rather than source code — it is also referred to as specification-based or spec-based testing.</p>
<p>For example, consider a program that outputs the largest number among several inputs. The internal comparison algorithm used is irrelevant. Based on the specification, the following test cases are derived:</p>
<ul>
<li><p>Case where A is the largest</p>
</li>
<li><p>Case where B is the largest</p>
</li>
<li><p>Case where C is the largest</p>
</li>
<li><p>Case where A is entered as a negative number</p>
</li>
</ul>
<p>The essence of black box testing is verifying that the program produces the correct output in each of these situations.</p>
<h3>블랙박스 테스트 (명세 기반 테스트) 심화</h3>
<p>블랙박스 테스트는 소프트웨어 내부의 로직은 배제하고, <strong>입력에 대한 출력 값에만 초점을 두는 테스트 방법</strong>이다. 테스트 케이스는 소스코드가 아닌 요구사항 명세서나 설계서 등의 스펙을 기반으로 도출하기 때문에 명세 기반 테스트, 스펙 기반 테스트라고도 불린다.</p>
<p>예를 들어 여러 숫자 중 가장 큰 수를 출력하는 프로그램이 있다고 하자. 내부적으로 어떤 비교 알고리즘을 사용하는지는 관심 밖이다. 스펙을 기반으로 다음과 같은 테스트 케이스를 도출한다.</p>
<ul>
<li><p>A가 가장 큰 경우</p>
</li>
<li><p>B가 가장 큰 경우</p>
</li>
<li><p>C가 가장 큰 경우</p>
</li>
<li><p>A를 음수로 입력하는 경우</p>
</li>
</ul>
<p>이처럼 프로그램이 각 상황에서 올바른 출력을 내는지를 확인하는 것이 블랙박스 테스트의 핵심이다.</p>
<hr />
<h3>White Box Testing (Structure-Based Testing) — In Depth</h3>
<p>White box testing aims to test as many independent paths within the source code as possible. Test cases are derived from the source code itself, or equivalent detailed design documents and algorithm logic.</p>
<p>While the form of inputs and outputs is the same as in black box testing, white box testing directly examines the internal logic, creating a test case for each individual execution path within the program. Using the "largest number output" program as an example:</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/53bdd975-d4c2-4fc3-bdaf-ee871327b641.png" alt="" style="display:block;margin:0 auto" />

<p>Because it examines the entire internal structure and aims to cover every independent path without omission, this approach is also called <strong>Structure-Based Testing</strong>.</p>
<h3>화이트박스 테스트 (구조 기반 테스트) 심화</h3>
<p>화이트박스 테스트는 소스코드 내의 모든 독립적인 경로를 최대한 테스트하는 방법이다. 소스코드, 혹은 이에 준하는 상세 설계서와 알고리즘 로직을 기반으로 테스트 케이스를 도출한다.</p>
<p>입력과 출력의 형태는 블랙박스 테스트와 동일하지만, 내부 로직을 직접 들여다보기 때문에 프로그램 안에 존재하는 다양한 실행 경로 하나하나를 테스트 케이스로 만든다. 앞서 살펴본 큰 수 출력 프로그램을 예로 들면 다음과 같다.</p>
<p>이처럼 내부 구조를 모두 들여다보며 독립적인 경로를 빠짐없이 커버하려 한다는 점에서 <strong>구조 기반 테스트(Structure-based Test)</strong> 라고도 부른다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5d990bd5-3e64-46ab-85f5-fbd5e42d546c.png" alt="" style="display:block;margin:0 auto" />

<h3>[Reference] Test Approaches by V-Model Stage</h3>
<p>The test target, environment, and applicable technique differ at each stage of the V-model.</p>
<p><strong>Unit Testing</strong> targets individual modules such as functions and files, and is conducted in the developer's own development environment. Since test cases are derived directly from source code, <strong>white box testing</strong> is applied.</p>
<p><strong>Integration Testing</strong> verifies the interfaces between internal and external modules, and is typically conducted in a laboratory environment. Because both source code and specifications are often referenced together, it can be applied in a <strong>gray box</strong> form that combines white box and black box approaches.</p>
<p><strong>System Testing</strong> covers the entire integrated software, verifying not only functional aspects but also non-functional qualities such as performance, usability, maintainability, and compatibility. It is conducted in an environment similar to the actual operating environment, and can also be applied in <strong>gray box</strong> form.</p>
<p><strong>Acceptance Testing</strong> verifies the final software from the user's perspective in a real-world environment, confirming that it operates according to the requirements defined by the user. Since users have no need to examine internal code, <strong>black box testing</strong> is applied, using requirements definition documents and similar specs as the Test Basis.</p>
<h3>[참고] V모델 단계별 테스트 방안</h3>
<p>V모델의 각 테스트 단계별로 테스트 대상, 환경, 적용 기법이 달라진다.</p>
<p><strong>단위 테스트 ㅡ</strong> 단위 모듈(함수, 파일 등)을 대상으로 하며, 개발자의 개발 환경에서 진행된다. 소스코드 자체를 기반으로 테스트 케이스를 도출하기 때문에 <strong>화이트박스 테스트</strong>가 적용된다.</p>
<p><strong>통합 테스트 ㅡ</strong> 내부 및 외부 모듈 간의 인터페이스를 검증하는 단계로, 보통 실험실 환경에서 진행된다. 소스코드와 스펙을 함께 참조하는 경우가 많아 <strong>화이트박스와 블랙박스를 혼합한 그레이박스</strong> 형태로 적용이 가능하다.</p>
<p><strong>시스템 테스트</strong> ㅡ 통합된 소프트웨어 전체를 대상으로 기능적 측면뿐만 아니라 성능, 사용성, 유지보수성, 호환성 등 <strong>비기능적인 부분까지 검증</strong>하는 단계다. 실제 운영환경과 유사한 환경에서 진행되며, 그레이박스 형태로도 적용 가능하다.</p>
<p><strong>인수 테스트 ㅡ</strong> 사용자 관점에서 최종 소프트웨어를 실제 환경에서 검증하는 단계로, 사용자가 제시한 요구사항대로 동작하는지를 확인한다. 사용자가 내부 코드를 볼 필요가 없으므로 요구사항 정의서 등의 스펙을 테스트 베이시스로 삼는 <strong>블랙박스 테스트</strong>가 적용된다.</p>
<hr />
<h1>3️⃣ Block box testing Preview</h1>
<h3>Black Box Test Design Techniques</h3>
<p>Black box testing is an approach that excludes the internal logic of source code and verifies whether the output for a given input is appropriate. Test cases are derived from specifications such as requirements documents and architecture design documents. ISO/IEC/IEEE 29119 defines a variety of black box test techniques. Representative techniques include:</p>
<ul>
<li><p><strong>Syntax Testing</strong></p>
</li>
<li><p>Equivalence Partitioning</p>
</li>
<li><p>Boundary Value Analysis</p>
</li>
<li><p>And many more</p>
</li>
</ul>
<p>In this post, we will use <strong>Syntax Testing</strong> to understand the fundamental concept of black box test techniques. The remaining techniques will be covered in subsequent posts.</p>
<h3>블랙박스 테스트 설계 기법</h3>
<p>블랙박스 테스트는 소스코드의 내부 로직은 배제하고, 입력에 대한 출력이 적절한지를 검증하는 방식이다. 테스트 케이스는 요구사항 명세서, 아키텍처 설계서 등의 스펙을 기반으로 도출된다. ISO/IEC/IEEE 29119 표준에서는 다양한 블랙박스 테스트 기법을 정의하고 있다. 대표적인 기법으로는 다음과 같은 것들이 있다.</p>
<ul>
<li><p><strong>신택스 테스팅 (Syntax Testing)</strong></p>
</li>
<li><p><strong>동등 분할 (Equivalence Partitioning)</strong></p>
</li>
<li><p><strong>경계값 분석 (Boundary Value Analysis)</strong></p>
</li>
<li><p>그 외 다수</p>
</li>
</ul>
<p>이번에는 <strong>신택스 테스팅(Syntax Testing)</strong> 을 통해 블랙박스 테스트 기법의 기본 컨셉을 살펴보고, 나머지 기법들은 이어지는 내용에서 다룰 예정이다.</p>
<hr />
<h3>Black Box Test Technique ① Syntax Testing</h3>
<p>Syntax Testing is one of the easiest black box test techniques to apply. The core concept is to divide input values into <strong>Valid</strong> and <strong>Invalid</strong> categories and create test cases accordingly. Test cases are derived from requirements documents or design specifications.</p>
<p><strong>Example: User Registration ㅡ Username Input</strong></p>
<p>Username condition: Korean characters only, minimum 2 characters, maximum 8 characters</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/50fe8def-105e-43c6-92bc-9be7b97beaed.png" alt="" style="display:block;margin:0 auto" />

<p>When "홍길동 99" is entered, the expected output is "Number included — invalid," but if the actual result shows "Normal," it means the program failed to properly detect the number, indicating a defect.</p>
<p>In this way, Syntax Testing verifies only the output result — without looking at the internal logic at all — by creating test cases based on the valid/invalid conditions defined in the specification. The same approach can be applied to all input fields such as username, password, and email address.</p>
<h3>블랙박스 테스트 기법 ① 신택스 테스팅 (Syntax Testing)</h3>
<p>신택스 테스팅은 블랙박스 테스트 기법 중 가장 쉽게 적용할 수 있는 방법이다. 핵심 개념은 입력값을 <strong>적합(Valid)</strong> 과 <strong>부적합(Invalid)</strong> 으로 구분하여 테스트 케이스를 만드는 것이다. 테스트 케이스는 요구사항 명세서나 설계 스펙을 기반으로 도출된다.</p>
<p><strong>예시: 회원가입 — 사용자 이름 입력</strong></p>
<p>사용자 이름 조건: <strong>2자리 이상 8자리 이하의 한글만 허용</strong></p>
<p>"홍길동 99" 입력 시 예상 출력값은 "숫자 포함 부적합 경고"이지만 실행 결과가 "정상"으로 출력된다면, 프로그램이 숫자를 제대로 인식하지 못한 것이므로 결함이 존재한다는 것을 알 수 있다.</p>
<p>이처럼 신택스 테스팅은 내부 로직은 전혀 보지 않고, 스펙에 정의된 적합/부적합 조건을 기준으로 테스트 케이스를 만들어 출력 결과만을 검증하는 방식이다. 아이디, 비밀번호, 이메일 주소 등 모든 입력 항목에 동일한 방식으로 적용할 수 있다.</p>
<hr />
<h3>Syntax Testing Applied — Shopping Mall Product Search Feature</h3>
<p>Let's apply Syntax Testing to a product search feature in a shopping mall system. This feature allows users to enter a product name or product number as a keyword, and the system searches for and displays the matching product.</p>
<p>All valid/invalid conditions are derived from the <strong>specification</strong> (requirements documents, design documents, etc.). Test cases can only be accurately created when these conditions are clearly defined.</p>
<h3>The Importance of Specifications</h3>
<p>There is a critical point that must be emphasized here. From the V-model perspective, test cases are built from the left-side artifacts — requirements definitions and design documents. If these artifacts are not properly written, it becomes difficult to accurately derive test cases. In the worst case, testers may end up creating test cases based on guesswork rather than a solid specification.</p>
<p>Ultimately, <strong>good testing starts with good specs</strong>. Never forget that the thoroughness with which left-side artifacts are written determines the overall quality of the entire test design activity.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/bb349a51-591c-48e7-8a59-ead2fd8c70f2.png" alt="" style="display:block;margin:0 auto" />

<h3>신택스 테스팅 적용 예시; 쇼핑몰 상품 검색 기능</h3>
<p>쇼핑몰 시스템의 상품 검색 기능을 예시로 신택스 테스팅을 적용해보자. 이 기능은 사용자가 상품명 또는 상품 번호를 키워드로 입력하면 해당 상품을 검색하여 표시하는 기능이다. 이 적합/부적합 조건은 모두 <strong>스펙(요구사항 명세서, 설계서 등)</strong> 으로부터 도출된다. 조건이 명확히 정의되어 있어야 테스트 케이스를 정확하게 만들 수 있다.</p>
<h3><strong>스펙의 중요성</strong></h3>
<p>여기서 반드시 짚고 넘어가야 할 점이 있다. V모델 관점에서 테스트 케이스는 왼쪽의 산출물, 즉 요구사항 정의서나 설계서를 기반으로 만들어진다. 따라서 이 산출물들이 제대로 작성되어 있지 않으면 테스트 케이스를 정확히 도출하기가 어려워진다. 최악의 경우 테스터가 스펙 없이 추측에 의존해 테스트 케이스를 만들게 되는 상황이 발생할 수 있다.</p>
<p>결국 좋은 테스트는 좋은 스펙에서 시작된다. 왼쪽 산출물을 충실히 작성하는 노력이 테스트 설계 활동 전체의 품질을 좌우한다는 점을 반드시 기억하자.</p>
<hr />
<h3>Syntax Testing Applied — Writing Test Cases for the Product Search Feature</h3>
<p>Let's write actual test cases based on the valid/invalid conditions defined earlier.</p>
<p><strong>Product Name Test Cases</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c1238626-80e3-4c5c-9bb7-ab23c1a7c938.png" alt="" style="display:block;margin:0 auto" />

<p>When "노트북^^" is entered, the special characters are ignored and laptop search results are displayed. Since this differs from the expected output defined in the spec, it is identified as a defect and debugging is required.</p>
<p>When "AA12345678" is entered, the letters are not properly recognized and the result shows "Normal." This is likewise identified as a defect and the code must be corrected.</p>
<h3>Key Takeaway</h3>
<p>Every step of this process — identifying input variables, defining valid/invalid conditions, and deriving test cases — is based not on source code, but on <strong>specifications such as requirements documents and architecture design documents</strong>. Ultimately, the quality of test cases depends on the quality of the specifications. Always remember: how thoroughly the left-side artifacts of the V-model are written determines the overall completeness of the entire test effort.</p>
<h3>신택스 테스팅 적용 예시; 쇼핑몰 상품 검색 기능의 테스트 케이스 작성</h3>
<p>앞서 정의한 적합/부적합 조건을 바탕으로 실제 테스트 케이스를 작성해보자.</p>
<p>"노트북^^" 입력 시 특수 기호를 무시하고 노트북 검색 결과가 출력되었다. 스펙에 명시된 예상 출력값과 다르므로 결함으로 판단하고 디버깅이 필요하다.</p>
<p>"AA12345678" 입력 시 영문을 제대로 인식하지 못하고 정상으로 출력되었다. 마찬가지로 결함으로 판단하고 코드를 수정해야 한다.</p>
<h3>핵심 정리</h3>
<p>이 모든 과정 ~ 입력 변수를 찾고, 적합/부적합 조건을 정의하고, 테스트 케이스를 도출하는 것 은 소스코드가 아닌 <strong>요구사항 명세서, 아키텍처 설계서 등의 스펙</strong>을 기반으로 이루어진다. 결국 테스트 케이스의 품질은 스펙의 품질에 달려 있다. V모델의 왼쪽 산출물을 얼마나 충실하게 작성하느냐가 테스트 전체의 완성도를 결정한다는 점을 반드시 기억하자.</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[Threads: Concepts, Implementation, and Practice]]></title><description><![CDATA[1️⃣Concept of Threads2️⃣ Implementation of Threads3️⃣ Process / Thread Practice

1️⃣Concept of Threads


Execution Flows Inside a Process — The Story of Threads
When you open a messenger app, multiple]]></description><link>https://heesu.tech/threads-concepts-implementation-and-practice</link><guid isPermaLink="true">https://heesu.tech/threads-concepts-implementation-and-practice</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Thu, 02 Apr 2026 19:55:14 GMT</pubDate><content:encoded><![CDATA[<p>1️⃣Concept of Threads<br />2️⃣ Implementation of Threads<br />3️⃣ Process / Thread Practice</p>
<hr />
<h1>1️⃣Concept of Threads</h1>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/271fbcc5-783c-4ceb-a2d6-d7caca0540bf.png" alt="" style="display:block;margin:0 auto" />

<h3>Execution Flows Inside a Process — The Story of Threads</h3>
<p>When you open a messenger app, multiple things happen at once. You can type a message, notifications pop up, and a file download continues in the background. So are all these tasks handled by a single execution flow?</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f160601c-b80e-474f-a564-47c832142bae.png" alt="" style="display:block;margin:0 auto" />

<p>What if only one flow existed? You'd have to wait for the file download to finish before sending a message, and the screen would freeze until a notification was processed. The reason we rarely experience such inconvenience is that threads are working behind the scenes, dividing and handling each role.</p>
<p>Today's lesson starts from exactly this point — <strong>"How does execution flow split inside a single program?"</strong></p>
<hr />
<h3>프로세스 안에서 흐름이 나뉜다; 스레드(Thread) 이야기</h3>
<p>메신저 프로그램을 켜면 동시에 여러 일이 일어난다. 메시지를 입력할 수 있고, 알림이 울리고, 파일 다운로드도 진행된다. 그렇다면 이 모든 작업은 하나의 실행 흐름으로 처리되는 걸까?</p>
<p>만약 하나의 흐름만 존재한다면 어떻게 될까? 파일 다운로드가 끝나야 메시지를 보낼 수 있고, 알림이 처리되기 전까지 화면이 멈춰 있을 것이다. 우리가 프로그램을 쓸 때 이런 불편함을 거의 느끼지 못하는 건, 내부에서 스레드가 역할을 나눠 처리하고 있기 때문이다.</p>
<p>오늘 수업은 바로 이 지점, <strong>"하나의 프로그램 안에서 실행 흐름이 어떻게 나뉘는가"</strong> 라는 질문에서 출발한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cea54c55-45a0-4e03-8f98-e9f00352c17d.png" alt="" style="display:block;margin:0 auto" />

<h3>The Basic Unit of Execution, and the Flows Within It</h3>
<p>In the last session, we covered the <strong>process</strong> as the basic unit through which the OS manages execution. A process is a running program itself, and the OS allocates memory and resources to each process, managing them independently.</p>
<p>So when execution splits into multiple flows inside a process, how does the OS handle it? Does it manage the whole process as one unit, or does it treat each split flow as a separate entity?</p>
<p>The answer is the latter. The OS distinguishes each individual execution flow inside a process as a <strong>thread</strong>. In other words, when determining scheduling or execution order, the unit the OS actually works with is not the process, but the thread.</p>
<p>If a process is "the space where a program lives," then a thread is "the actual execution flow moving within that space." This session will carefully work through this relationship — how the OS breaks execution down into manageable units.</p>
<hr />
<h3>실행의 기본 단위, 그리고 그 안에서 나뉘는 흐름</h3>
<p>지난 시간에는 운영체제가 실행을 관리하는 기본 단위로 <strong>프로세스(Process)</strong> 를 다뤘다. 프로세스는 실행 중인 프로그램 그 자체이며, 운영체제는 각 프로세스에 메모리와 자원을 할당하고 독립적으로 관리한다.</p>
<p>그렇다면 프로세스 안에서 실행이 여러 갈래로 나뉠 때, 운영체제는 이를 어떻게 다룰까? 하나의 프로세스로 묶어서 통째로 관리할까, 아니면 나뉜 흐름 각각을 별도의 단위로 구분해서 관리할까?</p>
<p>답은 후자다. 운영체제는 프로세스 내부에서 나뉜 각각의 실행 흐름을 <strong>스레드(Thread)</strong> 라는 단위로 구분하여 관리한다. 즉, 스케줄링이나 실행 순서를 결정할 때 운영체제가 실질적으로 다루는 단위는 프로세스가 아니라 스레드인 셈이다.</p>
<p>프로세스가 "프로그램이 살아있는 공간"이라면, 스레드는 "그 공간 안에서 실제로 움직이는 실행 흐름"이라고 볼 수 있다. 오늘 수업에서는 바로 이 관계를, 즉 <strong>운영체제가 실행을 어떤 단위로 쪼개어 다루는지</strong>를 차근차근 정리해나갈 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/029f7f24-3a5e-48cf-9eca-63a22b841321.png" alt="" style="display:block;margin:0 auto" />

<h3>What Is a Thread? — An Execution Flow Inside a Process</h3>
<p>A thread is the <strong>unit of execution flow</strong> operating inside a process. More precisely, it is the <strong>minimum execution unit</strong> that actually receives CPU allocation and runs.</p>
<p>Consider an internet banking application. A user can check their balance while simultaneously requesting a transfer or searching transaction history. All of these functions happen within a s ingle program, but each is handled by a different execution flow. The flow handling the balance, the flow handling the transfer, the flow handling the transaction history — each of these is a thread.</p>
<p>Two key points matter here.</p>
<p>First, <strong>one or more threads can exist inside a single process</strong>. If a process represents the entire execution environment, a thread is the minimal execution unit actually moving within it.</p>
<p>Second, <strong>threads share resources but execute independently</strong>. Resources like the code region, global data, and heap are shared among threads within the same process. However, each thread's execution flow proceeds independently.</p>
<p>This is the essence of a thread. <strong>Share resources, execute separately.</strong></p>
<h3>스레드란 무엇인가? 프로세스 안의 실행 흐름</h3>
<p>스레드(Thread)는 프로세스 내부에서 동작하는 <strong>실행 흐름의 단위</strong>다. 좀 더 정확히 말하면, CPU를 실제로 할당받아 동작하는 <strong>최소 실행 단위</strong>라고 할 수 있다.</p>
<p>인터넷 뱅킹 프로그램을 예로 들어보자. 사용자는 잔액 조회를 하면서 동시에 이체를 요청하거나 거래 내역을 검색할 수 있다. 이 모든 기능이 하나의 프로그램 안에서 이루어지지만, 각 기능은 서로 다른 실행 흐름으로 처리된다. 잔액을 처리하는 흐름, 이체를 처리하는 흐름, 거래 내역을 처리하는 흐름, 이 각각이 바로 스레드다.</p>
<p>여기서 중요한 점은 두 가지다.</p>
<p>첫째, 하나의 프로세스 안에는 <strong>하나 이상의 스레드</strong>가 존재할 수 있다. 프로세스가 실행 환경 전체를 의미한다면, 스레드는 그 안에서 실제로 움직이는 최소한의 실행 단위다.</p>
<p>둘째, 스레드는 <strong>자원을 공유하되, 실행은 독립적</strong>으로 이루어진다. 코드 영역, 전역 데이터, 힙(Heap) 영역과 같은 자원은 같은 프로세스 안의 스레드들이 함께 공유한다. 그러나 각 스레드의 실행 흐름 자체는 서로 독립적으로 진행된다.</p>
<p>이것이 스레드의 핵심이다. 자원은 나눠 쓰고, 실행은 따로 간다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/724c86b6-3317-4dfe-91ce-ada16c024b9f.png" alt="" style="display:block;margin:0 auto" />

<h3>Single-Thread vs Multi-Thread; Four Execution Structures</h3>
<p>The number of processes and the number of threads are independent concepts. Their combination determines the execution structure, which can be broken into four forms.</p>
<p><strong>① Single Process + Single Thread</strong></p>
<p>Only one execution flow exists, so tasks proceed sequentially. One task must finish before the next begins. If one task takes a long time, everything else waits. The simplest structure.</p>
<p><strong>② Single Process + Multiple Threads</strong></p>
<p>One process, but execution splits into multiple flows. One thread handles file downloads while another handles user input; multiple tasks appear to run simultaneously within the same program. The messenger and internet banking examples from earlier fall into this category.</p>
<p><strong>③ Multiple Processes + Single Thread</strong></p>
<p>Multiple processes exist, each with only one execution flow. Running a web browser and a music player at the same time is a typical example. The reason execution appears divided here is not threads, but the fact that multiple independent processes are running.</p>
<p><strong>④ Multiple Processes + Multiple Threads</strong></p>
<p>Multiple processes exist, and each contains multiple threads. For example, running a browser and a messenger simultaneously; the browser runs multiple threads per tab, and the messenger has separate threads for message handling and file downloading. This is the most common structure in modern operating systems.</p>
<p>The number of processes and the number of threads operate on different dimensions. How you combine them determines a program's execution structure.</p>
<h3>단일 스레드와 다중 스레드: 실행 구조의 네 가지 형태</h3>
<p>프로세스의 수와 스레드의 수는 서로 독립적인 개념이다. 이 두 요소의 조합에 따라 실행 구조가 달라지며, 크게 네 가지 형태로 나눠볼 수 있다.</p>
<p><strong>① 단일 프로세스 + 단일 스레드</strong></p>
<p>실행 흐름이 하나뿐이므로 작업이 순차적으로 진행된다. 한 작업이 끝나야 다음 작업으로 넘어가기 때문에, 어떤 작업이 오래 걸리면 나머지는 그동안 기다려야 한다. 가장 단순한 구조다.</p>
<p><strong>② 단일 프로세스 + 다중 스레드</strong></p>
<p>프로세스는 하나지만 실행 흐름이 여러 갈래로 나뉘어 동작한다. 한 스레드는 파일 다운로드를, 다른 스레드는 사용자 입력을 처리하는 식으로, 같은 프로그램 안에서 여러 작업이 동시에 수행되는 것처럼 보인다. 앞서 살펴본 메신저나 인터넷 뱅킹이 이 구조에 해당한다.</p>
<p><strong>③ 다중 프로세스 + 단일 스레드</strong></p>
<p>프로세스 자체가 여러 개 존재하고, 각 프로세스는 하나의 실행 흐름만 가진다. 웹 브라우저와 음악 재생 프로그램을 동시에 실행한 경우가 대표적인 예다. 이때 실행이 나뉘어 보이는 이유는 스레드 때문이 아니라, 서로 독립된 프로세스가 여러 개 동작하고 있기 때문이다.</p>
<p><strong>④ 다중 프로세스 + 다중 스레드</strong></p>
<p>여러 프로세스가 존재하고, 각 프로세스 안에서도 여러 스레드가 동작하는 구조다. 예를 들어 웹 브라우저와 메신저를 동시에 실행했을 때, 브라우저 안에서는 탭마다 여러 스레드가 돌아가고, 메신저 안에서도 메시지 처리와 파일 다운로드를 담당하는 스레드가 각각 존재한다. 이것이 현대 운영체제에서 가장 일반적으로 사용되는 구조다.</p>
<p>결국 프로세스의 개수와 스레드의 개수는 서로 다른 차원의 개념이다. 이 둘을 어떻게 조합하느냐에 따라 프로그램의 실행 구조가 결정된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0b751843-356e-4ea8-997b-d553ec286dfc.png" alt="" style="display:block;margin:0 auto" />

<h3>Process Internal Structure; What Threads Share and What They Keep Separate</h3>
<p>A process holds common resources: the code region, global data region, and heap region. In a multi-threaded environment, multiple threads within the same process share these resources.</p>
<p>Consider the following code:</p>
<pre><code class="language-c">int count = 0;

void process() {
    int x = 0;
}
</code></pre>
<p>The global variable <code>count</code> is stored in the data region, so all threads within the same process can access it. If two threads simultaneously perform <code>count++</code>, then <code>count</code> becomes a shared resource. The code region, global data, and heap exist at the process level and are shared by all threads.</p>
<p>However, not everything is shared. Each thread independently holds its own <strong>stack region</strong>. Even if two threads call the same function simultaneously, the local variable <code>x</code> inside that function is created separately on each thread's stack. Thread A's <code>x</code> and Thread B's <code>x</code> occupy different memory locations despite having the same name, so changes in one do not affect the other.</p>
<p>In summary: the code region, global data, and heap are <strong>shared</strong> between threads, while the stack is <strong>independently maintained</strong> per thread. This structure allows multiple threads to efficiently share resources while each executes without interfering with the others.</p>
<h3>프로세스 내부 구조: 스레드는 무엇을 공유하고 무엇을 따로 가지는가</h3>
<p>프로세스는 코드 영역, 전역 데이터 영역, 힙 영역과 같은 공통 자원을 가지고 있다. 다중 스레드 환경에서는 같은 프로세스 안에 있는 여러 스레드가 이 자원들을 함께 사용하게 된다.</p>
<p>예를 들어 아래와 같은 코드가 있다고 해보자.</p>
<pre><code class="language-c">int count = 0;

void process() {
    int x = 0;
}
</code></pre>
<p>여기서 전역 변수 <code>count</code>는 데이터 영역에 저장되기 때문에, 같은 프로세스 안의 모든 스레드가 함께 접근할 수 있다. 두 스레드가 동시에 <code>count++</code> 연산을 수행한다면, <code>count</code>는 두 스레드가 공유하는 자원이 된다. 코드 영역, 전역 데이터, 힙 영역은 이처럼 프로세스 단위로 존재하며 스레드들이 공유한다.</p>
<p>그러나 모든 자원을 공유하는 것은 아니다. 각 스레드는 실행에 필요한 <strong>스택(Stack) 영역을 독립적으로</strong> 가진다. 예를 들어 두 스레드가 같은 함수를 동시에 호출하더라도, 그 함수 안의 지역 변수 <code>x</code>는 각 스레드의 스택에 따로 생성된다. 스레드 A의 <code>x</code>와 스레드 B의 <code>x</code>는 이름은 같아도 서로 다른 메모리 공간에 존재하기 때문에, 한쪽의 변경이 다른 쪽에 영향을 주지 않는다.</p>
<p>정리하면 다음과 같다. 코드 영역, 전역 데이터, 힙 영역은 스레드 간에 <strong>공유</strong>되고, 실행 흐름을 위한 스택은 스레드마다 <strong>독립적으로</strong> 존재한다. 이 구조 덕분에 여러 스레드가 자원을 효율적으로 나눠 쓰면서도, 각자의 실행은 서로 간섭 없이 독립적으로 이루어질 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f4840ab4-af9f-433f-9157-c8763a5a0cdb.png" alt="" style="display:block;margin:0 auto" />

<h3>Thread Address Space; A Structure That Shares Yet Separates</h3>
<p>When a process runs, the OS allocates a memory space exclusively for that process. Within this space, the code, data, and heap regions are placed at distinct locations. "Distinct locations" means they sit at different addresses — not that they use separate memory. They all exist within the same memory range belonging to one process, just partitioned.</p>
<p>In a multi-threaded environment, multiple threads share this memory space. The same function in the code region can be executed by multiple threads simultaneously, and global variables are accessible to all threads.</p>
<p>Yet each thread has its own separate stack. This means that even when executing the same function concurrently, each thread independently maintains its own parameter values and local variables. One thread's execution cannot affect another thread's local variables.</p>
<p>To summarize: code, data, and heap are <strong>shared</strong> between threads; the stack <strong>exists independently</strong> per thread. The ability to share memory while maintaining independent execution flows is made possible by this structure. <strong>This shared-yet-separated design is the defining characteristic of a multi-threaded environment.</strong></p>
<h3>스레드의 주소 공간: 공유하되 분리된 구조</h3>
<p>프로세스가 실행되면 운영체제는 그 프로세스만의 메모리 공간을 할당한다. 이 공간 안에는 코드 영역, 데이터 영역, 힙 영역이 각각 구분된 위치에 배치된다. 여기서 "각각 다른 위치"란 이 영역들이 서로 다른 주소에 놓인다는 뜻이지, 별개의 메모리를 쓴다는 의미가 아니다. 모두 하나의 프로세스에 속한 동일한 메모리 범위 안에 구분되어 존재한다.</p>
<p>다중 스레드 환경에서는 이 메모리 공간을 여러 스레드가 함께 사용한다. 코드 영역에 있는 같은 함수를 여러 스레드가 동시에 실행할 수 있고, 전역 변수 역시 모든 스레드가 함께 접근할 수 있다.</p>
<p>그러나 각 스레드는 자신만의 <strong>스택 영역</strong>을 따로 가진다. 덕분에 같은 함수를 동시에 실행하더라도 각 스레드는 서로 다른 매개변수 값과 지역 변수를 독립적으로 유지할 수 있다. 한 스레드의 실행이 다른 스레드의 지역 변수에 영향을 주지 않는 것도 이 때문이다.</p>
<p>정리하면, 코드·데이터·힙은 스레드 간에 공유되고, 스택은 스레드마다 독립적으로 존재한다. 같은 메모리를 나눠 쓰면서도 각자의 실행 흐름을 유지할 수 있는 것은 바로 이 구조 덕분이다. <strong>공유하되 분리된 이 구조</strong>가 멀티스레드 환경의 핵심 특징이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/376fa2d5-258c-4824-8518-05ccc5459bbd.png" alt="" style="display:block;margin:0 auto" />

<h3>Thread Characteristics; Resource Sharing and Execution Separation</h3>
<p>The core characteristics of threads can be summarized in two points.</p>
<p><strong>① Resource Sharing</strong></p>
<p>Threads within the same process share the code, data, and heap regions. In a shopping mall website, one thread fetches the product list, another calculates the cart total, and another handles user input. Since product information and user data all reside in the same memory, there is no need to copy data when passing it between threads, they are all looking at the same memory. This structure enables efficient cooperation between threads inside a process.</p>
<p><strong>② Separation of Execution Flows</strong></p>
<p>Because multiple execution flows can exist within one process, each thread has its own execution order and operates independently. Think of video streaming: one thread downloads video data, another renders it on screen, and another handles user input. The execution is divided, yet all flows run within a single program.</p>
<p>Ultimately, threads operate on two principles: <strong>share resources, execute separately.</strong></p>
<h3>스레드의 특징; 자원 공유와 실행 흐름의 분리</h3>
<p>스레드의 핵심 특징은 크게 두 가지로 정리할 수 있다.</p>
<p><strong>① 자원 공유</strong></p>
<p>같은 프로세스 안에 있는 스레드들은 코드, 데이터, 힙 영역을 함께 사용한다. 쇼핑몰 웹사이트를 예로 들면, 한 스레드는 상품 목록을 불러오고, 다른 스레드는 장바구니를 계산하고, 또 다른 스레드는 사용자 입력을 처리한다. 이때 상품 정보나 사용자 데이터는 모두 같은 메모리에 존재하기 때문에, 스레드 간에 데이터를 주고받을 때 굳이 복사할 필요가 없다. 같은 메모리를 함께 바라보고 있기 때문이다. 이 구조 덕분에 프로세스 내부에서 스레드 간 협력이 효율적으로 이루어질 수 있다.</p>
<p><strong>② 실행 흐름의 분리</strong></p>
<p>하나의 프로세스 안에 여러 실행 흐름이 존재할 수 있기 때문에, 각 스레드는 자신만의 실행 순서를 가지면서 독립적으로 동작한다. 영상 스트리밍을 생각해보면, 한 스레드는 영상 데이터를 다운로드하고, 다른 스레드는 화면에 출력하고, 또 다른 스레드는 사용자 입력을 받는다. 실행은 나뉘어 있지만 이 모든 흐름이 하나의 프로그램 안에서 돌아가고 있는 것이다.</p>
<p>결국 스레드는 자원은 함께 쓰고, 실행은 따로 간다는 두 가지 원칙 위에서 동작한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5352237d-58ee-44b9-8356-de068d432f9f.png" alt="" style="display:block;margin:0 auto" />

<h3>Process vs Thread — A Comparison</h3>
<p>Processes and threads are often mentioned together, but they are distinct concepts.</p>
<p>A <strong>process</strong> represents the entire running program. Each process has its own independent memory space, so running a web browser and Notepad simultaneously means the two programs operate in separate memory spaces with no direct access to each other's memory.</p>
<p>A <strong>thread</strong> is an execution flow running inside one process. Threads belonging to the same process share memory, and multiple threads can exist within a single process.</p>
<p>In one sentence: <strong>a process is the entire execution environment, and a thread is a single execution flow performing actual work within it.</strong></p>
<h3>스레드와 프로세스의 비교</h3>
<p>프로세스와 스레드는 자주 함께 언급되지만 서로 다른 개념이다.</p>
<p><strong>프로세스</strong>는 실행 중인 프로그램 전체를 의미한다. 각 프로세스는 독립적인 메모리 공간을 가지기 때문에, 웹 브라우저와 메모장을 동시에 실행하면 두 프로그램은 서로 다른 메모리 공간에서 각각 동작하며 서로의 메모리에 직접 접근할 수 없다.</p>
<p><strong>스레드</strong>는 하나의 프로세스 안에서 실행되는 흐름이다. 같은 프로세스에 속한 스레드들은 메모리를 공유하며, 하나의 프로세스 안에 여러 스레드가 존재할 수 있다.</p>
<p>한 문장으로 정리하면, 프로세스는 실행 환경 전체이고 스레드는 그 내부에서 실제로 작업을 수행하는 하나의 실행 흐름이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/22da8990-b876-4a1f-83c8-1513d912e438.png" alt="" style="display:block;margin:0 auto" />

<h3>Why Use Threads?</h3>
<p><strong>① Improved Responsiveness</strong></p>
<p>If user input goes unprocessed while a file is uploading, the program feels frozen. But if one thread handles the upload and another handles user input, the screen continues to respond while uploading proceeds. Whether the tasks truly run simultaneously or not, the user perceives the program as uninterrupted.</p>
<p><strong>② Efficiency</strong></p>
<p>Since threads share the same process memory, creating a new thread is far less costly than creating a new process. A new process requires a separate memory allocation, whereas a thread reuses existing memory. When a browser handles multiple tasks, splitting them into threads within one process is far more resource-efficient than spawning a new process for each task.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6d949edb-0730-49d2-84f4-6d17129a4829.png" alt="" style="display:block;margin:0 auto" />

<p><strong>③ Structural Separation</strong></p>
<p>Threads make it easy to divide work by role within a single program. In a game, one thread handles user input, another handles physics calculations, and another handles rendering. Dividing execution flows by function makes the program's behavior easier to understand and simplifies maintenance; fixing a specific feature only requires modifying its corresponding thread.</p>
<hr />
<p>Taken together, these three reasons point to the core of threads: <strong>the combination of sharing and separation</strong>. The ability to share memory while splitting execution independently is the most fundamental reason to use threads — and their greatest defining trait.</p>
<h3>스레드를 사용하는 이유</h3>
<p><strong>① 응답성 향상</strong></p>
<p>파일을 업로드하는 동안 사용자 입력이 전혀 처리되지 않는다면, 사용자 입장에서는 프로그램이 멈춘 것처럼 느껴진다. 하지만 업로드 작업을 한 스레드가 맡고, 사용자 입력 처리를 다른 스레드가 맡는다면 업로드가 진행되는 동안에도 화면은 계속 반응하게 된다. 실제로 작업이 동시에 이루어지든 아니든, 사용자 입장에서는 프로그램이 멈추지 않은 것처럼 느껴지는 것이다.</p>
<p><strong>② 효율성</strong></p>
<p>스레드는 같은 프로세스의 메모리를 공유하기 때문에, 새로운 프로세스를 생성하는 것보다 부담이 훨씬 적다. 프로세스를 새로 만들면 독립적인 메모리 공간을 별도로 할당해야 하지만, 스레드는 기존 메모리를 그대로 활용한다. 예를 들어 브라우저에서 여러 작업을 처리할 때, 각 작업마다 프로세스를 새로 만드는 것보다 같은 프로세스 안에서 스레드로 나누어 처리하는 것이 자원 사용 면에서 훨씬 효율적이다.</p>
<p><strong>③ 구조적 분리</strong></p>
<p>세 번째 이유는 <strong>구조적인 분리</strong>다. 스레드를 활용하면 하나의 프로그램 안에서 역할별로 작업을 나누기가 쉬워진다. 게임 프로그램을 예로 들면, 사용자 입력을 처리하는 스레드, 물리 연산을 담당하는 스레드, 화면 출력을 맡는 스레드로 역할을 구분할 수 있다. 이렇게 기능별로 실행 흐름을 나누면 프로그램의 동작 구조를 이해하기 쉬워지고, 특정 기능을 수정하거나 개선할 때도 해당 스레드만 손보면 되기 때문에 유지보수가 훨씬 수월해진다.</p>
<p>지금까지 살펴본 세 가지 이유를 종합하면, 스레드의 핵심은 <strong>공유와 분리의 결합</strong>에 있다. 같은 메모리를 공유하면서도 실행은 독립적으로 나누어 처리할 수 있는 이 구조가 스레드를 사용하는 가장 근본적인 이유이자, 스레드의 가장 큰 특징이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/eb30b2ee-e237-4155-ba47-65250001d795.png" alt="" style="display:block;margin:0 auto" />

<h3>Real-World Examples of Thread Usage</h3>
<p><strong>① Video Player</strong></p>
<p>Smooth playback requires multiple tasks to run simultaneously. Processing screen output, audio playback, and user input (play/pause) sequentially in one flow would cause the video to freeze or the audio to cut out. Assigning each function to a separate thread allows video, audio, and input handling to proceed concurrently, keeping the overall experience seamless.</p>
<p><strong>② Game Program</strong></p>
<p>A game must continuously refresh the screen, respond instantly to user input, and simultaneously calculate character positions and collision detection. Doing this sequentially would cause the screen to stutter or responses to lag. Splitting tasks by role into independent execution flows solves this problem.</p>
<p><strong>③ Why Not Just Use Multiple Processes?</strong></p>
<p>Separating these tasks into processes might seem feasible on the surface. However, separate processes have independent memory spaces, requiring inter-process communication (IPC) to share state and data — adding complexity and overhead. For closely related tasks, splitting into threads within one process is far more appropriate, since they can share memory while keeping execution separate.</p>
<h3>스레드의 실제 사용 예시</h3>
<p>스레드가 왜 필요한지는 실제 프로그램 사례를 보면 더 명확하게 이해할 수 있다.</p>
<p><strong>① 동영상 재생 프로그램</strong></p>
<p>영상이 끊기지 않으려면 여러 작업이 동시에 이루어져야 한다. 화면 출력, 음성 재생, 사용자의 재생·일시정지 입력 처리를 하나의 흐름으로 순차적으로 실행한다면 영상이 멈추거나 소리가 끊기는 문제가 생긴다. 각 기능을 별도의 스레드로 나누어 처리하면 영상, 음성, 입력 처리가 동시에 이루어지면서 전체 흐름이 자연스럽게 유지된다.</p>
<p><strong>② 게임 프로그램</strong></p>
<p>게임은 화면을 끊임없이 갱신하면서, 사용자 입력에 즉각 반응하고, 동시에 캐릭터 위치 계산이나 충돌 처리도 수행해야 한다. 이를 순차적으로 처리하면 화면이 멈추거나 반응이 느려질 수밖에 없다. 역할별로 스레드를 나누어 각 기능을 독립적인 실행 흐름으로 처리하면 이 문제를 해결할 수 있다.</p>
<p><strong>③ 프로세스로 나누면 안 될까?</strong></p>
<p>같은 작업을 프로세스로 분리하는 것도 겉으로는 가능해 보인다. 그러나 프로세스를 분리하면 각각 독립된 메모리 공간을 가지게 되어, 작업 간에 상태 정보를 주고받거나 데이터를 공유하기 위해 별도의 프로세스 간 통신이 필요해진다. 이는 구조를 복잡하게 만들고 관리 부담도 커진다. 서로 밀접하게 연관된 작업이라면 하나의 프로세스 안에서 스레드로 나누어 처리하는 것이 훨씬 적합하다. 같은 메모리를 공유하면서 실행만 분리할 수 있기 때문이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/198e6879-b5e1-41eb-a0cc-aa3ce690525e.png" alt="" style="display:block;margin:0 auto" />

<h3>Threads and Asynchronous Behavior; Word Processor Example</h3>
<p>Consider a word processor. Typing on the keyboard should instantly display characters on screen, images should load in the background, and auto-save should run at regular intervals. Handling all of this sequentially in one flow would cause input delays or make the screen appear frozen.</p>
<p>Threads solve this. Assigning input handling, screen rendering, and auto-save to separate threads lets all three proceed without waiting for each other. The user can keep typing without waiting for auto-save to complete, and the program runs naturally.</p>
<p>This structure — where multiple tasks proceed independently without waiting for each other — is called <strong>asynchronous behavior</strong>. More precisely, it is closer to concurrent execution via threads, but it carries asynchronous characteristics in the sense that tasks proceed without blocking one another.</p>
<h3>스레드로 구현하는 비동기적 동작; 워드 편집기 예시</h3>
<p>워드 편집기를 생각해보자. 사용자가 키보드로 입력하면 즉시 화면에 글자가 나타나야 하고, 동시에 이미지 로딩이 이루어지고, 일정 시간마다 자동 저장도 실행된다. 이 작업들을 하나의 흐름으로 순차적으로 처리한다면 입력이 지연되거나 화면이 멈춘 것처럼 보일 수 있다.</p>
<p>이를 해결하는 방법이 바로 스레드다. 입력 처리, 화면 갱신, 자동 저장을 각각 별도의 스레드로 나누면 세 작업이 서로를 기다리지 않고 독립적으로 진행된다. 사용자는 자동 저장이 끝날 때까지 기다릴 필요 없이 계속 타이핑할 수 있고, 프로그램 전체는 자연스럽게 동작한다.</p>
<p>이처럼 여러 작업이 서로를 기다리지 않고 독립적으로 진행되는 구조를 <strong>비동기적 동작</strong>이라고 부른다. 정확히는 스레드를 통한 동시적 실행에 가깝지만, 작업들이 서로 블로킹 없이 진행된다는 점에서 비동기적 특성을 띤다고 볼 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/76bb6451-7cbc-4895-bbdf-0edae1f0366d.png" alt="" style="display:block;margin:0 auto" />

<h3>Web Browser and Threads;Another Example of Asynchronous Behavior</h3>
<p>A web browser works the same way. It simultaneously fetches data from the network, loads images, and handles user interaction. Processing all of this sequentially in one flow would make the page appear to freeze. Browsers are internally structured to use multiple threads for each task simultaneously, allowing users to experience pages that respond continuously without interruption.</p>
<h3>웹 브라우저와 스레드; 비동기적 동작의 또 다른 예</h3>
<p>웹 브라우저도 마찬가지다. 브라우저는 네트워크에서 데이터를 받아오고, 이미지를 로딩하고, 사용자와의 상호작용을 처리하는 작업을 동시에 수행한다. 이 모든 작업을 하나의 흐름으로 순차적으로 처리한다면 페이지가 멈춘 것처럼 보일 수 있다. 브라우저는 내부적으로 여러 스레드를 활용해 각 작업을 동시에 처리하도록 구성되어 있고, 그 결과 사용자는 페이지가 끊김 없이 계속 반응하는 것으로 느끼게 된다.</p>
<hr />
<h2>2️⃣ Implementation of Threads</h2>
<h3>How Does the OS Manage Threads?</h3>
<p>We learned earlier that a thread is the minimum execution unit that receives CPU allocation and runs, and that multiple threads can exist within a single process. This naturally leads to the following question.</p>
<p>If multiple threads exist simultaneously, how does the OS determine their execution order, and what information does it use to manage them?</p>
<p>Just as the OS uses a data structure called the <strong>PCB (Process Control Block)</strong> to manage processes, it must also systematically maintain information about each thread. This section examines that structure; specifically, how the OS actually tracks and manages threads.</p>
<h3>운영체제는 스레드를 어떻게 관리할까?</h3>
<p>앞서 스레드는 CPU를 할당받아 실행되는 최소 실행 단위이며, 하나의 프로세스 안에 여러 스레드가 존재할 수 있다는 것을 배웠다. 여기서 자연스럽게 다음 질문이 생긴다.</p>
<p>여러 스레드가 동시에 존재한다면, 운영체제는 어떤 기준으로 실행 순서를 정하고 어떤 정보를 바탕으로 스레드를 관리할까?</p>
<p>프로세스를 관리할 때 운영체제가 PCB(Process Control Block)라는 자료구조를 사용했던 것처럼, 스레드를 관리할 때도 운영체제는 각 스레드에 대한 정보를 체계적으로 유지해야 한다. 이번 시간에는 바로 그 구조, 즉 운영체제가 스레드를 실제로 어떻게 파악하고 관리하는지를 살펴본다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/386d93bf-a43c-4a93-9027-b9d229cacb9a.png" alt="" style="display:block;margin:0 auto" />

<h3>Thread Execution States; Why the OS Tracks Them</h3>
<p>When multiple threads exist within a single process, each thread can be in a different execution state. One thread may currently be running on the CPU, another may be waiting for its turn, and yet another may be paused waiting for a specific event.</p>
<p>Because each thread has a different state, the OS must continuously decide which thread to run next and when to hand the CPU over to another thread. In other words, a thread is not simply a flow of execution, it is something the OS must constantly track and manage.</p>
<h3>스레드의 실행 상태: 운영체제가 추적하는 이유</h3>
<p>하나의 프로세스 안에 여러 스레드가 존재하면, 각 스레드는 서로 다른 실행 상태를 가질 수 있다. 어떤 스레드는 현재 CPU를 사용해 실행 중이고, 어떤 스레드는 실행 순서를 기다리고 있으며, 또 어떤 스레드는 특정 이벤트를 기다리며 멈춰 있을 수 있다.</p>
<p>스레드마다 상태가 다르기 때문에 운영체제는 지금 어떤 스레드를 실행시킬지, 언제 CPU를 다른 스레드에게 넘길지를 계속해서 판단해야 한다. 즉 스레드는 단순히 실행되는 흐름이 아니라, 운영체제가 끊임없이 추적하고 관리해야 하는 대상이 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2a6e4fdb-2b18-44f4-86e0-7a1d90230f57.png" alt="" style="display:block;margin:0 auto" />

<h3>TCB - The Data Structure for Managing Threads</h3>
<p>To manage threads, the OS must know the following about each one: its current state, where execution should resume next, what values are stored in the CPU registers, and where the stack is located.</p>
<p>To store and manage this information, the OS uses the <strong>TCB (Thread Control Block)</strong>. The TCB holds information about a single thread and serves as the management unit used to track and switch between threads.</p>
<p>Just as the PCB exists to manage processes, the TCB exists to manage threads. The OS uses the TCB to determine which thread to run and when, and to restore the necessary information when switching from one thread to another.</p>
<h3>TCB: 스레드를 관리하는 자료구조</h3>
<p>운영체제가 스레드를 관리하려면 각 스레드에 대해 다음과 같은 정보를 파악하고 있어야 한다. 현재 어떤 상태인지, 다음에 어디서부터 실행을 이어가야 하는지, CPU 레지스터에는 어떤 값이 저장되어 있는지, 스택은 어디에 위치해 있는지가 그것이다.</p>
<p>이 정보를 저장하고 관리하기 위해 운영체제는 <strong>TCB(Thread Control Block, 스레드 제어 블록)</strong> 를 사용한다. TCB는 스레드 하나에 대한 정보를 담고 있으며, 스레드를 추적하고 전환하기 위해 사용되는 관리 단위다.</p>
<p>프로세스를 관리하기 위해 PCB가 존재하듯, 스레드 단위의 관리를 위해 TCB가 존재하는 것이다. 운영체제는 이 TCB를 바탕으로 어떤 스레드를 언제 실행할지 판단하고, 실행 중인 스레드를 다른 스레드로 전환할 때 필요한 정보를 복원한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/49422dca-7854-40dd-beb4-1ef2d3f5e08a.png" alt="" style="display:block;margin:0 auto" />

<h3>The Role of the TCB; A Data Structure That Remembers Execution Flow</h3>
<p>A thread is an execution flow that receives CPU allocation and runs. The OS must be able to pause this flow and resume it later.</p>
<p>Consider a scenario where Thread A is running, then pauses and switches to Thread B, and eventually returns to Thread A. For Thread A to resume correctly, it must remember exactly where it stopped. To enable this, the OS stores each thread's execution state and the information needed to resume it in the <strong>TCB</strong>.</p>
<p>Ultimately, the TCB is a data structure that preserves execution flow so that a thread can be resumed at any time. The key takeaway is this: <strong>a thread is an execution flow, and the TCB is the structure that remembers it.</strong></p>
<h3>TCB의 역할: 실행 흐름을 기억하는 자료구조</h3>
<p>스레드는 CPU를 할당받아 실행되는 흐름이다. 운영체제는 이 실행 흐름을 중단했다가 나중에 다시 이어서 실행할 수 있어야 한다.</p>
<p>예를 들어 스레드 A가 실행되다가 멈추고 스레드 B로 전환되었다가 다시 스레드 A로 돌아온다고 해보자. 이때 스레드 A는 어디까지 실행했는지를 기억하고 있어야 정확한 위치부터 이어서 실행할 수 있다. 이를 위해 운영체제는 각 스레드의 실행 상태와 실행을 재개하는 데 필요한 정보를 <strong>TCB(Thread Control Block)</strong> 에 저장한다.</p>
<p>결국 TCB는 스레드가 언제든 다시 실행될 수 있도록 실행 흐름을 보존하는 자료구조다. 핵심만 짚으면 이렇다. <strong>스레드는 실행 흐름이고, TCB는 그 흐름을 기억하는 구조다.</strong></p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e6ec54b3-14a2-49cf-bb36-bd1a7b9a8141.png" alt="" style="display:block;margin:0 auto" />

<h3>The Structure of the TCB</h3>
<p>Let's look at what information the TCB contains. A process's memory space includes code, data, and heap regions, all of which are shared by every thread within that process. The stack, however, exists separately for each thread. Since each thread has an independent execution flow, the local variables and return addresses generated during function calls are stored on each thread's own stack.</p>
<p>Each thread's TCB holds three key pieces of information:</p>
<ul>
<li><p><strong>Thread ID</strong>: A unique identifier that distinguishes this thread from others.</p>
</li>
<li><p><strong>PC (Program Counter)</strong>: Indicates how far the thread has executed.</p>
</li>
<li><p><strong>SP (Stack Pointer)</strong>: Indicates where the stack is currently pointing.</p>
</li>
</ul>
<p>When a thread is paused and needs to resume, these values must be accurately restored. The PC must be restored so execution can continue from where it stopped, and the SP must be restored so the local variables and return addresses on the stack can be correctly referenced.</p>
<p>In short, the TCB stores everything the OS must know to pause and resume a thread — <strong>who this thread is, how far it has executed, and where its stack is.</strong></p>
<h3>스레드 제어 블록(TCB)의 구조</h3>
<p>TCB가 어떤 정보를 담고 있는지 구조적으로 살펴보자. 프로세스의 메모리 공간에는 코드, 데이터, 힙 영역이 있고, 이 영역들은 프로세스 안의 모든 스레드가 함께 공유한다. 반면 스택은 스레드마다 별도로 존재한다. 각 스레드가 독립적인 실행 흐름을 가지기 때문에, 함수 호출 시 생성되는 지역 변수와 반환 주소도 각자의 스택에 따로 저장된다.</p>
<p>각 스레드의 TCB에는 크게 세 가지 정보가 담긴다.</p>
<ul>
<li><p><strong>스레드 ID</strong>: 이 스레드가 누구인지를 식별하는 고유 번호다.</p>
</li>
<li><p><strong>PC (Program Counter)</strong>: 스레드가 현재 어디까지 실행되었는지를 나타낸다.</p>
</li>
<li><p><strong>SP (Stack Pointer)</strong>: 스택이 현재 어디를 가리키고 있는지를 나타낸다.</p>
</li>
</ul>
<p>스레드가 중단되었다가 다시 실행되려면 이 값들이 정확히 복원되어야 한다. PC가 복원되어야 중단된 위치부터 이어서 실행할 수 있고, SP가 복원되어야 스택에 저장된 지역 변수와 반환 주소를 올바르게 참조할 수 있다.</p>
<p>결국 TCB는 운영체제가 스레드를 멈췄다가 다시 실행하기 위해 반드시 알아야 할 정보, 즉 <strong>이 스레드가 누구인지, 어디까지 실행됐는지, 스택은 어디에 있는지</strong>를 저장하는 자료구조다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/19496a4f-ef7e-413a-ae55-5a204eff4097.png" alt="" style="display:block;margin:0 auto" />

<h3>The Relationship Between PCB and TCB</h3>
<p>Once you understand the relationship between processes and threads, the relationship between PCB and TCB follows naturally.</p>
<p>The <strong>PCB</strong> manages information about a process's resources and its entire execution environment. The <strong>TCB</strong> stores information about the execution flow of each thread within that process.</p>
<p>Taking a web browser as an example: information about the entire browser process is stored in the PCB, while information about the threads handling individual tabs or tasks is stored in each thread's TCB.</p>
<p>In other words, PCB and TCB form a structure where <strong>one PCB is linked to multiple TCBs</strong>. If a process is the container of the execution environment, then each thread moving within it is individually tracked and managed by the OS through its own TCB.</p>
<h3>PCB와 TCB의 관계</h3>
<p>프로세스와 스레드의 관계를 이해했다면, 이를 관리하는 자료구조인 PCB와 TCB의 관계도 자연스럽게 이해할 수 있다.</p>
<p><strong>PCB(Process Control Block)</strong> 는 프로세스의 자원과 실행 환경 전체에 대한 정보를 관리한다. <strong>TCB(Thread Control Block)</strong> 는 그 프로세스 안에 존재하는 각 스레드의 실행 흐름에 대한 정보를 저장한다.</p>
<p>웹 브라우저를 예로 들면, 브라우저 프로세스 전체에 대한 정보는 PCB에 저장되고, 각 탭이나 개별 작업을 처리하는 스레드의 정보는 각각의 TCB에 저장된다.</p>
<p>즉 PCB와 TCB는 <strong>하나의 PCB에 여러 개의 TCB가 연결된 구조</strong>다. 프로세스가 실행 환경의 그릇이라면, 그 안에서 움직이는 각 스레드는 자신만의 TCB를 통해 운영체제에 의해 개별적으로 추적되고 관리된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5c010d15-8875-464b-b5ec-ee35fbe5eabe.png" alt="" style="display:block;margin:0 auto" />

<h3>Distinguishing the Roles of PCB and TCB</h3>
<p>The relationship between PCB and TCB can be summarized in one sentence:</p>
<p><strong>The PCB manages a process's resources and execution environment; the TCB manages the threads executing within it.</strong></p>
<p>Process-level management information is handled by the PCB, and thread-level execution information is handled by the TCB. The OS uses both data structures together to manage processes and threads hierarchically. If the PCB holds the big picture, the TCB tracks the fine details of each individual execution flow moving within it.</p>
<h3>PCB와 TCB의 역할 구분</h3>
<p>PCB와 TCB의 관계를 한 문장으로 정리하면 다음과 같다.</p>
<p><strong>PCB는 프로세스의 자원과 실행 환경을 관리하고, TCB는 그 안에서 실행되는 스레드를 관리한다.</strong></p>
<p>프로세스 단위의 관리 정보는 PCB가 담당하고, 스레드 단위의 실행 정보는 TCB가 담당한다. 운영체제는 이 두 자료구조를 함께 활용해 프로세스와 스레드를 계층적으로 관리한다. PCB가 큰 그림을 잡아준다면, TCB는 그 안에서 실제로 움직이는 각각의 실행 흐름을 세밀하게 추적하는 역할을 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4b86ea3b-c889-404a-8001-4a7647da2cbc.png" alt="" style="display:block;margin:0 auto" />

<h3>Thread Implementation; It Depends on Who Manages Them</h3>
<p>We established earlier that a thread is an execution flow. This raises an important question: the way threads are implemented varies depending on how far the OS recognizes and manages them.</p>
<p>Based on who manages the threads, implementations fall into three categories:</p>
<ul>
<li><p><strong>User-Level Threads</strong></p>
</li>
<li><p><strong>Kernel-Level Threads</strong></p>
</li>
<li><p><strong>Hybrid Threads</strong></p>
</li>
</ul>
<p>Let's examine how each works and what distinguishes them.</p>
<h3>스레드의 구현 방식: 누가 관리하느냐에 따라 달라진다</h3>
<p>앞서 스레드는 실행 흐름이라는 것을 배웠다. 여기서 중요한 질문이 하나 생긴다. 운영체제가 스레드를 어디까지 인식하고 관리하느냐에 따라 스레드의 구현 방식이 달라진다는 것이다.</p>
<p>스레드를 관리하는 주체가 누구냐에 따라 크게 세 가지로 구분할 수 있다.</p>
<ul>
<li><p><strong>사용자 수준 스레드</strong></p>
</li>
<li><p><strong>커널 수준 스레드</strong></p>
</li>
<li><p><strong>혼합형 스레드</strong></p>
</li>
</ul>
<p>각각의 방식이 어떤 구조로 동작하고, 어떤 차이가 있는지 하나씩 살펴보자.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8bdd05d0-d0be-4ae4-a072-67fc80c4d1d8.png" alt="" style="display:block;margin:0 auto" />

<h3>Implementation ① — User-Level Threads</h3>
<p>In the user-level thread model, thread creation, scheduling, and management are performed not by the OS, but by a <strong>thread library</strong>. The OS does not recognize the existence of threads at the kernel level and views the process as a single execution unit. Therefore, even if multiple threads are taking turns executing within a process, the OS sees it as one process using one CPU.</p>
<p>The <strong>advantages</strong> are threefold. First, no kernel call is needed when switching threads, so context switch overhead is low. Second, thread creation and management is fast. Third, applications can freely design their own scheduling policies without depending on the OS.</p>
<p>The <strong>disadvantage</strong> is significant. Parallel execution in multi-core environments is not possible. Because the OS treats the entire process as a single execution unit, it allocates only one CPU. No matter how many threads exist internally, they cannot run on multiple cores simultaneously.</p>
<h3>스레드의 구현 방식 ① 사용자 수준 스레드</h3>
<p>사용자 수준 스레드 방식에서는 스레드의 생성, 스케줄링, 관리가 운영체제가 아닌 <strong>스레드 라이브러리</strong>에 의해 수행된다. 운영체제는 커널 수준에서 스레드의 존재 자체를 인식하지 못하며, 해당 프로세스를 하나의 실행 단위로만 바라본다. 따라서 프로세스 내부에서 여러 스레드가 번갈아 실행되더라도, 운영체제 입장에서는 하나의 프로세스가 하나의 CPU를 사용하는 것처럼 보인다.</p>
<p><strong>장점</strong>은 세 가지로 정리할 수 있다. 첫째, 스레드 전환 시 커널 호출이 필요 없기 때문에 문맥 교환 오버헤드가 적다. 둘째, 스레드의 생성과 관리가 빠르다. 셋째, 운영체제에 의존하지 않고 프로그램 내부에서 자체적인 스케줄링을 자유롭게 설계할 수 있다.</p>
<p>반면 <strong>단점</strong>도 분명하다. 가장 큰 문제는 멀티코어 환경에서 병렬 실행이 어렵다는 점이다. 운영체제는 이 프로세스를 하나의 실행 단위로만 인식하기 때문에 CPU를 하나만 할당한다. 내부에 여러 스레드가 존재하더라도 실제로는 여러 코어에서 동시에 실행되지 못하는 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/81b68a9a-92fa-4792-855d-a5f46c97d826.png" alt="" style="display:block;margin:0 auto" />

<h3>N:1 Mapping in User-Level Threads</h3>
<p>The structure of user-level threads can be understood from two perspectives.</p>
<p>From a <strong>structural position</strong> standpoint: the user space sits at the top, the kernel space below it, and hardware at the bottom. Multiple user-level threads exist inside a process in user space, and they are created and scheduled by a thread library in user space — not by the kernel. Crucially, these threads are invisible in the kernel space. The kernel sees the process as a single execution unit.</p>
<p>From a <strong>mapping</strong> standpoint: multiple user-level threads (N threads) are connected to a single kernel thread. This is called <strong>N:1 mapping</strong>. Even if there are multiple threads in user space, the kernel sees only one execution flow. If a program contains five threads, the kernel assigns only one kernel thread to the CPU.</p>
<p>This structure makes the previously mentioned disadvantage clear. Even in a multi-core environment, parallel execution across multiple cores is impossible. No matter how many threads exist internally, the kernel's single-unit view prevents simultaneous use of multiple cores.</p>
<h3>사용자 수준 스레드의 다대일(N:1) 매핑 구조</h3>
<p>사용자 수준 스레드의 구조는 두 가지 관점에서 살펴볼 수 있다.</p>
<p><strong>구조적 위치 관계</strong>를 보면, 위쪽에는 사용자 영역, 아래에는 커널 영역, 맨 아래에는 하드웨어 영역이 위치한다. 사용자 영역 안의 프로세스 내부에 여러 개의 사용자 수준 스레드가 존재하며, 이 스레드들은 커널이 아닌 사용자 영역의 스레드 라이브러리에 의해 생성되고 스케줄링된다. 중요한 점은 커널 영역에서는 이 스레드들이 보이지 않는다는 것이다. 커널은 해당 프로세스를 그저 하나의 실행 단위로만 인식한다.</p>
<p><strong>매핑 관계</strong>를 보면, 여러 개의 사용자 수준 스레드(N개)가 하나의 커널 스레드에 연결되는 구조를 취한다. 이를 <strong>N:1 매핑</strong>이라고 부른다. 사용자 영역에는 스레드가 여러 개 존재하더라도, 커널 입장에서는 하나의 실행 흐름으로만 보인다. 프로그램 안에 스레드가 다섯 개 있더라도 커널은 하나의 커널 스레드만 CPU에 할당한다.</p>
<p>이 구조에서 앞서 언급한 단점이 명확하게 드러난다. 멀티코어 환경에서도 여러 코어에 병렬로 실행되지 못한다는 것이다. 커널이 프로세스 전체를 하나의 실행 단위로만 보기 때문에, 내부의 스레드가 아무리 많아도 동시에 여러 코어를 활용할 수 없다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/94bd7d43-ecc8-4de2-83a8-aad5fa40f7eb.png" alt="" style="display:block;margin:0 auto" />

<h3>Implementation ② Kernel-Level Threads</h3>
<p>In the kernel-level thread model, the OS directly recognizes and manages threads. Thread creation, scheduling, and state management all occur inside the kernel, and CPU scheduling is performed at the <strong>thread level</strong> rather than the process level. Each thread is treated as an independent execution unit from the OS's perspective.</p>
<p>There are three <strong>advantages</strong>. First, if one thread enters a waiting state, other threads can continue running. For example, if a thread is waiting for file I/O, the OS places that thread in a waiting state and assigns the CPU to another thread. Second, parallel processing is possible in multi-core environments, since each thread is independently scheduled by the kernel and can run on multiple CPU cores simultaneously. Third, because the kernel manages resources at the thread level, scheduling and protection are handled consistently.</p>
<p>The <strong>disadvantage</strong> is that kernel involvement is required every time a thread is created or switched. This means context switch overhead is higher than with user-level threads, and the overall management cost increases due to the need for kernel mode transitions.</p>
<h3>스레드의 구현 방식 ② 커널 수준 스레드</h3>
<p>커널 수준 스레드 방식에서는 운영체제가 스레드를 직접 인식하고 관리한다. 스레드의 생성, 스케줄링, 상태 관리가 모두 커널 내부에서 이루어지며, CPU 스케줄링도 프로세스 단위가 아닌 <strong>스레드 단위</strong>로 수행된다. 운영체제 입장에서 각 스레드는 독립적인 실행 단위로 취급된다.</p>
<p><strong>장점</strong>은 세 가지다. 첫째, 한 스레드가 대기 상태가 되더라도 다른 스레드는 계속 실행될 수 있다. 예를 들어 한 스레드가 파일 입출력을 기다리고 있다면, 운영체제는 해당 스레드를 대기 상태로 두고 다른 스레드에 CPU를 할당한다. 둘째, 멀티코어 환경에서 병렬 처리가 가능하다. 각 스레드가 커널에 의해 독립적으로 스케줄링되기 때문에 여러 CPU 코어에서 동시에 실행될 수 있다. 셋째, 커널이 스레드 단위로 자원을 관리하기 때문에 스케줄링과 보호가 일관되게 이루어진다.</p>
<p><strong>단점</strong>도 존재한다. 스레드를 생성하거나 전환할 때마다 커널의 개입이 필요하기 때문에, 사용자 수준 스레드에 비해 문맥 교환 오버헤드가 크다. 커널 모드 전환이 필요한 만큼 전반적인 관리 비용도 증가한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/7d908a0f-c6c2-4a1d-aacf-e300f6f4b01e.png" alt="" style="display:block;margin:0 auto" />

<h3>1:1 Mapping in Kernel-Level Threads</h3>
<p>Kernel-level threads use a <strong>1:1 mapping structure</strong>, where each user thread maps exactly to one kernel thread.</p>
<p>Unlike N:1 mapping, where multiple user threads are bound to a single kernel thread, 1:1 mapping allows the kernel to recognize each thread individually and schedule them independently. This enables true parallel execution in multi-core environments, where multiple threads can be assigned to different cores simultaneously.</p>
<p>The contrast with N:1 is clear. In N:1, the kernel sees the process as a single unit, making parallel execution impossible. In 1:1, the kernel directly tracks every thread, allowing full utilization of multi-core hardware.</p>
<h3>커널 수준 스레드의 일대일(1:1) 매핑 구조</h3>
<p>커널 수준 스레드는 <strong>1:1 매핑 구조</strong>를 취한다. 사용자 스레드 하나가 커널 스레드 하나와 정확히 대응되는 구조다.</p>
<p>N:1 매핑에서는 여러 사용자 스레드가 하나의 커널 스레드에 묶여 있었던 것과 달리, 1:1 매핑에서는 커널이 각 스레드를 개별적으로 인식하고 독립적으로 스케줄링할 수 있다. 덕분에 멀티코어 환경에서 여러 스레드가 서로 다른 코어에 동시에 할당되어 진정한 병렬 실행이 가능해진다.</p>
<p>앞서 살펴본 사용자 수준 스레드의 N:1 구조와 비교하면 차이가 명확하다. N:1에서는 커널이 프로세스를 하나의 실행 단위로만 보기 때문에 병렬 실행이 불가능했지만, 1:1 구조에서는 커널이 스레드 하나하나를 직접 파악하고 있기 때문에 멀티코어의 이점을 온전히 활용할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1f621ac0-9f5f-4e33-a5ea-e5209240a717.png" alt="" style="display:block;margin:0 auto" />

<h3>Why Do Modern OSes Use Kernel-Level Threads?; Multi-core and Kernel Threads</h3>
<p>Most modern computers use multi-core CPUs. Kernel-level threads demonstrate their true value in this environment. Because the kernel independently recognizes and schedules each thread, multiple threads can be distributed across different CPU cores. This effectively improves parallel processing performance.</p>
<p>This is the most decisive difference between user-level and kernel-level threads. User-level threads cannot take advantage of multi-core hardware because the kernel sees the process as a single unit. Kernel-level threads, on the other hand, can run as many threads simultaneously as there are cores, a far more suitable structure for modern hardware.</p>
<h3>왜 현대 운영체제는 커널 수준 스레드를 사용하는가? 멀티코어 환경과 커널 수준 스레드</h3>
<p>현대 컴퓨터는 대부분 멀티코어 CPU를 사용한다. 커널 수준 스레드는 이 환경에서 진가를 발휘한다. 커널이 각 스레드를 독립적으로 인식하고 스케줄링하기 때문에, 여러 스레드를 서로 다른 CPU 코어에 분산하여 실행할 수 있다. 그 결과 병렬 처리 성능을 효과적으로 끌어올릴 수 있다.</p>
<p>이것이 사용자 수준 스레드와 커널 수준 스레드의 가장 결정적인 차이다. 사용자 수준 스레드는 커널이 프로세스를 하나의 실행 단위로만 보기 때문에 멀티코어의 이점을 살리지 못하지만, 커널 수준 스레드는 코어 수만큼 스레드를 동시에 실행할 수 있어 현대 하드웨어 환경에 훨씬 적합한 구조다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/fddc98ea-f28d-4afa-976b-2829e66e06b6.png" alt="" style="display:block;margin:0 auto" />

<h3>Why Do Modern OSes Use Kernel-Level Threads? Additional Reasons</h3>
<p>Beyond parallel processing, there are further reasons to use kernel-level threads.</p>
<p>First, if one thread enters a waiting state due to I/O, other threads can keep running. Because the kernel manages state at the thread level, one thread stalling does not freeze the entire program.</p>
<p>Second, because the kernel manages resources and performs CPU scheduling at the thread level, resource protection and execution control are handled consistently.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d7b85c63-82b0-4f3f-8e24-48eb2ca17215.png" alt="" style="display:block;margin:0 auto" />

<p>Third, if an error occurs in a specific thread, the impact on the overall system is reduced, making it much easier to isolate and manage problems.</p>
<p>For these reasons, modern operating systems adopt kernel-level threads as their default structure. From parallel processing performance to stable resource management and fault isolation, kernel-level threads meet all the demands of the modern computing environment.</p>
<h3>왜 현대 운영체제는 커널 수준 스레드를 사용하는가? 커널 수준 스레드를 사용하는 이유</h3>
<p>앞서 살펴본 병렬 처리 외에도, 커널 수준 스레드를 사용하는 이유는 더 있다.</p>
<p>첫째, 한 스레드가 입출력 때문에 대기 상태가 되더라도 다른 스레드는 계속 실행될 수 있다. 커널이 스레드 단위로 상태를 관리하기 때문에, 하나의 스레드가 멈춰도 프로그램 전체가 멈추지 않는다.</p>
<p>둘째, 커널이 스레드 단위로 자원을 관리하고 CPU 스케줄링을 직접 수행하기 때문에 자원 보호와 실행 제어가 일관되게 이루어진다.</p>
<p>셋째, 특정 스레드에서 오류가 발생하더라도 시스템 전체에 미치는 영향이 줄어들어 문제를 통제하고 관리하기가 훨씬 수월하다.</p>
<p>이러한 이유들로 인해 현대 운영체제는 커널 수준 스레드를 기본 구조로 채택하고 있다. 병렬 처리 성능, 안정적인 자원 관리, 오류 격리까지, 커널 수준 스레드는 현대 컴퓨팅 환경이 요구하는 조건을 고루 충족하는 구조다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/bf1a9afc-b3b4-4807-a600-c14c8e501daa.png" alt="" style="display:block;margin:0 auto" />

<h3>When Are User-Level Threads Still Useful?</h3>
<p>If kernel-level threads are the standard in modern OSes, when are user-level threads still worth using? The key is when you want to <strong>control threads quickly in user space without kernel involvement</strong>.</p>
<p><strong>① When thread creation and switching happen very frequently</strong></p>
<p>Repeated kernel calls increase overhead. Since user-level threads handle switching without a kernel mode transition, they can operate relatively faster in such scenarios.</p>
<p><strong>② When an application wants direct control over thread behavior</strong></p>
<p>When you want to implement a custom scheduling policy tailored to specific task characteristics, a library-level design offers much greater flexibility — without being constrained by the kernel's scheduling approach.</p>
<p><strong>③ When OS-independent implementation is needed</strong></p>
<p>Kernel thread implementations vary across operating systems. User-level threads, being library-based, can maintain relatively consistent behavior across different environments.</p>
<p>In summary, user-level threads are a suitable choice when <strong>performance optimization and scheduling flexibility</strong> are priorities.</p>
<h3>사용자 수준 스레드는 언제 사용할까?</h3>
<p>커널 수준 스레드가 현대 운영체제의 기본 구조라면, 사용자 수준 스레드는 언제 유용할까? 핵심은 <strong>커널의 개입 없이 사용자 영역에서 빠르게 스레드를 제어하고 싶을 때</strong>다.</p>
<p><strong>① 스레드 생성과 전환이 매우 자주 발생하는 경우</strong></p>
<p>커널 호출이 반복될수록 오버헤드가 커진다. 사용자 수준 스레드는 커널 모드 전환 없이 처리되기 때문에 이런 상황에서 상대적으로 빠르게 동작할 수 있다.</p>
<p><strong>② 응용 프로그램이 스레드 동작을 직접 제어하고 싶은 경우</strong></p>
<p>특정 작업 특성에 맞는 스케줄링 정책을 직접 구현하고자 할 때, 라이브러리 수준에서 보다 유연하게 설계할 수 있다. 커널의 스케줄링 방식에 얽매이지 않아도 된다는 것이 장점이다.</p>
<p><strong>③ 운영체제에 독립적인 구현이 필요한 경우</strong></p>
<p>운영체제마다 커널 스레드의 구현 방식이 다를 수 있다. 반면 사용자 수준 스레드는 라이브러리 기반으로 동작하기 때문에 여러 환경에서 비교적 일관된 동작을 유지할 수 있다.</p>
<p>결론적으로 성능 최적화와 제어 유연성이 중요한 상황에서는 사용자 수준 스레드가 적합한 선택이 될 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5cffc436-cc1b-4e26-9363-cab7d7d4bdc8.png" alt="" style="display:block;margin:0 auto" />

<h3>Thread Implementation in Modern OSes Linux and Windows</h3>
<p>So what approach do real operating systems take? Looking at two representative OSes, both <strong>Linux and Windows</strong> operate fundamentally on kernel-level threads.</p>
<p><strong>Linux</strong> manages threads as the basic unit of execution, with the kernel directly performing scheduling per thread.</p>
<p><strong>Windows</strong> likewise treats threads as the basic unit of execution, clearly distinguishing between processes as resource management units and threads as execution units.</p>
<p>Both operating systems operate on the same principle. Modern OSes clearly separate processes (resource management units) from threads (execution units) and adopt a structure where the kernel directly manages threads. The advantages of kernel-level threads covered earlier — parallel processing performance, stable resource management, and consistent execution control; are directly reflected in the design of real operating systems.</p>
<h3>현대 운영체제의 스레드 구현 방식; 리눅스와 윈도우</h3>
<p>그렇다면 실제 운영체제는 어떤 방식을 사용할까? 대표적인 두 운영체제인 리눅스와 윈도우를 살펴보면, 둘 다 기본적으로 <strong>커널 수준 스레드</strong>를 기반으로 동작한다.</p>
<p><strong>리눅스</strong>는 스레드를 기본 실행 단위로 관리하며, 스레드마다 커널이 직접 스케줄링을 수행한다.</p>
<p><strong>윈도우</strong> 역시 스레드를 기본 실행 단위로 관리한다. 프로세스는 자원 관리 단위, 스레드는 실행 단위로 명확히 구분하여 운영한다.</p>
<p>두 운영체제 모두 같은 원칙 위에서 동작하는 셈이다. 현대 운영체제는 <strong>프로세스는 자원 관리 단위, 스레드는 실행 단위</strong>로 명확히 구분하고, 커널 수준에서 스레드를 직접 관리하는 구조를 채택하고 있다. 앞서 배운 커널 수준 스레드의 장점, 즉 병렬 처리 성능, 안정적인 자원 관리, 일관된 실행 제어가 실제 운영체제 설계에도 그대로 반영된 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/38bcd626-75b6-45d9-bb12-64d881ec57b8.png" alt="" style="display:block;margin:0 auto" />

<h3>Why Hybrid (M:N) Threads Are Rarely Used</h3>
<p>Hybrid threads combine user-level and kernel-level threads. The goal is to map M user threads to N kernel threads, gaining both the fast switching of user-level threads and the parallel execution of kernel-level threads.</p>
<p>In theory this sounds attractive, but in practice it is rarely used. There are two main reasons.</p>
<p>First, complex coordination between the user-level library and the kernel is required. Second, since scheduling responsibility is split between user space and the kernel, implementation and debugging become extremely difficult.</p>
<p>The attempt to combine the advantages of both approaches ends up making the structure overly complex. And since modern OSes have already adopted kernel-level threads as their default, adequately solving the parallel processing problem; there is little reason to accept the added complexity of a hybrid model.</p>
<h3>혼합형(M:N) 스레드가 잘 사용되지 않는 이유</h3>
<p>혼합형 스레드는 사용자 수준 스레드와 커널 수준 스레드를 함께 사용하는 방식이다. M개의 사용자 스레드를 N개의 커널 스레드에 연결하여, 사용자 수준의 빠른 전환과 커널 수준의 병렬 실행이라는 두 가지 장점을 동시에 얻는 것이 목표다.</p>
<p>이론적으로는 매력적인 구조처럼 보이지만, 실제로는 잘 사용되지 않는다. 이유는 크게 두 가지다.</p>
<p>첫째, 사용자 수준 라이브러리와 커널 사이의 복잡한 연동이 필요하다. 둘째, 스케줄링 책임이 사용자 영역과 커널에 이중으로 분산되기 때문에 구현과 디버깅이 매우 어려워진다.</p>
<p>결국 두 방식의 장점을 합치려다 오히려 구조가 지나치게 복잡해지는 결과를 낳는다. 현대 운영체제가 커널 수준 스레드를 기본으로 채택하면서 병렬 처리 문제가 충분히 해결된 만큼, 굳이 복잡한 혼합형 구조를 감수할 필요가 없어진 것도 한 이유다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ce0402bc-5111-4049-a071-c8a2e347c72d.png" alt="" style="display:block;margin:0 auto" />

<h3>Comparing the Three Thread Implementation Models</h3>
<p>Here is a summary of the three models covered:</p>
<p><strong>User-Level Threads (N:1)</strong> managed by a user library. Context switch cost is low and performance is fast, but the entire process can stall during I/O waits, and multi-core utilization is impossible. Rarely used today.</p>
<p><strong>Kernel-Level Threads (1:1)</strong> managed directly by the OS kernel. Context switch cost is relatively higher, but other threads can continue running during I/O waits, and multi-core utilization is fully supported. The model adopted by most modern operating systems.</p>
<p><strong>Hybrid Threads (M:N)</strong> managed jointly by user space and the kernel. Context switch cost is moderate and multi-core utilization is possible, but implementation complexity is very high. Rarely used in practice.</p>
<p>In conclusion, user-level threads are fast but limited in scalability, while kernel-level threads accept modest overhead in exchange for multi-core utilization and stability. Modern operating systems use the <strong>1:1 kernel-level thread model</strong> as their default — the practical sweet spot between performance and reliability.</p>
<h3>세 가지 스레드 구현 방식 비교</h3>
<p>지금까지 살펴본 세 가지 스레드 구현 방식을 한눈에 정리해보자.</p>
<p><strong>사용자 수준 스레드(N:1)</strong> 는 사용자 라이브러리가 스레드를 관리한다. 문맥 교환 비용이 낮아 빠르게 동작하지만, I/O 대기 시 전체 스레드가 정지될 수 있고 멀티코어 활용이 불가능하다. 현재는 거의 사용되지 않는다.</p>
<p><strong>커널 수준 스레드(1:1)</strong> 는 운영체제 커널이 직접 스레드를 관리한다. 문맥 교환 비용이 상대적으로 높지만, I/O 대기 시에도 다른 스레드가 계속 실행될 수 있고 멀티코어 활용이 가능하다. 현재 대부분의 운영체제가 채택하고 있는 방식이다.</p>
<p><strong>혼합형 스레드(M:N)</strong> 는 사용자와 커널이 함께 스레드를 관리한다. 문맥 교환 비용은 중간 수준이며 멀티코어 활용도 가능하지만, 구현 복잡도가 매우 높아 실제로는 거의 사용되지 않는다.</p>
<p>결론적으로 사용자 수준 스레드는 빠르지만 확장성이 제한적이고, 커널 수준 스레드는 약간의 오버헤드를 감수하는 대신 멀티코어 활용과 안정성을 확보할 수 있다. 현대 운영체제는 이 현실적인 균형점으로 <strong>1:1 커널 수준 스레드 모델</strong>을 기본 구조로 사용하고 있다.</p>
<hr />
<h1>3️⃣ Process / Thread Practice</h1>
<h3>Working with Threads Directly in Linux</h3>
<p>So far we have examined the concepts and implementation of threads from a theoretical perspective. Now let's directly observe thread creation and execution flow in an Ubuntu environment.</p>
<p>In Linux, threads can be handled using the <strong>Pthread (POSIX Thread)</strong> library. Through this library, we will confirm the process of creating threads, controlling execution, and waiting for termination through hands-on practice.</p>
<p>Before diving into the exercises, let's first review the core Pthread functions and related concepts that will be used throughout.</p>
<h3>리눅스에서 스레드를 직접 다뤄보자</h3>
<p>지금까지 스레드의 개념과 구현 방식을 이론적으로 살펴봤다. 이번에는 우분투 환경에서 스레드의 생성과 실행 흐름을 직접 확인해본다.</p>
<p>리눅스에서는 <strong>Pthread(POSIX Thread)</strong> 라이브러리를 사용해 스레드를 다룰 수 있다. 이 라이브러리를 통해 스레드를 생성하고, 실행을 제어하고, 종료를 대기하는 과정을 실습으로 확인할 것이다.</p>
<p>본격적인 실습에 앞서, 실습 과정에서 사용하게 될 Pthread의 핵심 함수와 관련 개념을 먼저 정리해보자.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/db125788-4d69-484a-9dd7-27f8f9838436.png" alt="" style="display:block;margin:0 auto" />

<h3>Preparation — What Is Pthread?</h3>
<p><strong>Pthread (POSIX Thread)</strong> is a thread library that follows the POSIX standard, used for handling threads in Linux and Unix environments.</p>
<p>An important point here: the C language itself does not natively include syntax or keywords for threads. Rather than supporting threads directly at the language level, C uses a structure where thread functionality provided by the OS is brought in as a library.</p>
<p>This is exactly why the Pthread library is used when implementing threads in C on Linux. The thread functionality itself is provided by the OS kernel, and Pthread serves as an interface that wraps that functionality so it can be conveniently called from a C program.</p>
<h3>실습 준비 - Pthread란 무엇인가?</h3>
<p><strong>Pthread(POSIX Thread)</strong> 는 POSIX 표준을 따르는 스레드 라이브러리로, 리눅스와 유닉스 환경에서 스레드를 다룰 때 사용하는 방식이다.</p>
<p>여기서 중요한 점이 있다. C 언어 자체에는 스레드를 위한 문법이나 키워드가 기본적으로 존재하지 않는다. 즉 C 언어 차원에서 스레드를 직접 지원하는 것이 아니라, 운영체제가 제공하는 스레드 기능을 라이브러리 형태로 가져와서 사용하는 구조다.</p>
<p>리눅스 환경에서 C로 스레드를 구현할 때 Pthread 라이브러리를 사용하는 것도 바로 이 때문이다. 스레드 기능 자체는 운영체제 커널이 제공하고, Pthread는 그 기능을 C 프로그램에서 편리하게 호출할 수 있도록 감싸놓은 인터페이스인 셈이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/aa8eb102-b1df-4932-81cc-b4834f626673.png" alt="" style="display:block;margin:0 auto" />

<h3>Pthread Core Function ① — pthread_create()</h3>
<p><code>pthread_create()</code> is the function for creating a new thread. It creates an additional execution flow separate from the existing main flow.</p>
<p>An important point here is that when creating a thread, you also specify the function that thread will execute. In other words, calling <code>pthread_create()</code> does not merely create a thread, the specified function immediately begins running as a new execution flow.</p>
<p>Another key thing to remember is that <strong>the main function itself is also a thread</strong>. Therefore, the moment <code>pthread_create()</code> is called, both the main thread and the newly created thread simultaneously have their own execution flows. This is the moment when two or more execution flows come into existence within a single process.</p>
<h3>Pthread 핵심 함수 ① pthread_create()</h3>
<p><code>pthread_create()</code>는 새로운 스레드를 생성하는 함수다. 기존의 메인 실행 흐름과는 별도로 새로운 실행 흐름을 하나 더 만드는 역할을 한다.</p>
<p>여기서 중요한 점은 스레드를 생성할 때 그 스레드가 실행할 함수를 함께 지정한다는 것이다. 즉 <code>pthread_create()</code>를 호출하면 단순히 스레드만 만드는 것이 아니라, 지정한 함수가 새로운 실행 흐름으로 즉시 실행되기 시작한다.</p>
<p>또 한 가지 기억해야 할 점은 <strong>메인 함수 자체도 하나의 스레드</strong>라는 것이다. 따라서 <code>pthread_create()</code>를 호출하는 순간, 메인 스레드와 새로 생성된 스레드가 동시에 각자의 실행 흐름을 가지게 된다. 하나의 프로세스 안에서 두 개 이상의 실행 흐름이 만들어지는 순간이 바로 이 시점이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6dcee4bd-3301-4924-a6cd-489c517bb85f.png" alt="" style="display:block;margin:0 auto" />

<h3>Pthread Core Function ② — pthread_exit()</h3>
<p><code>pthread_exit()</code> is the function that terminates the currently running thread. The important point is that <strong>only that one thread is terminated</strong>. Calling <code>pthread_exit()</code> does not terminate the entire process; termination can be controlled at the thread level.</p>
<p>For example, you can terminate only the thread that has finished its task while letting the remaining threads continue running.</p>
<p>This is clearly distinct from the regular <code>exit()</code> function. While <code>exit()</code> terminates the entire process, <code>pthread_exit()</code> terminates only the calling thread. Think of it as the function to use when fine-grained, per-thread termination control is needed.</p>
<h3>Pthread 핵심 함수 ② pthread_exit()</h3>
<p><code>pthread_exit()</code>은 현재 실행 중인 스레드를 종료하는 함수다. 여기서 중요한 점은 <strong>스레드 하나만 종료된다</strong>는 것이다. <code>pthread_exit()</code>을 호출해도 프로세스 전체가 종료되지 않으며, 스레드 단위로 종료 처리가 가능하다.</p>
<p>예를 들어 특정 작업을 마친 스레드만 종료하고, 나머지 스레드는 계속 실행되도록 만들 수 있다.</p>
<p>이는 일반적인 <code>exit()</code> 함수와 명확히 구분된다. <code>exit()</code>은 프로세스 전체를 종료하지만, <code>pthread_exit()</code>은 호출한 스레드만 종료한다. 스레드 단위의 세밀한 종료 제어가 필요할 때 사용하는 함수라고 이해하면 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/db8c28b5-55df-40e6-9ecd-44116c67c242.png" alt="" style="display:block;margin:0 auto" />

<h3>Pthread Core Function ③ pthread_join()</h3>
<p><code>pthread_join()</code> is the function that makes the current thread wait until a specified thread has terminated. It is most commonly used when the main thread needs to wait for worker threads to finish.</p>
<p>The reason this function matters is that without calling <code>pthread_join()</code>, the main thread may terminate first, which can force-terminate other threads that are still working. Creating a thread is important, but so is properly waiting for its work to complete.</p>
<p>To summarize, <code>pthread_join()</code> serves two roles: it controls the execution order of threads, and it ensures all tasks complete normally. Think of it as a function that must always be paired with thread creation.</p>
<h3>Pthread 핵심 함수 ③ pthread_join()</h3>
<p><code>pthread_join()</code>은 특정 스레드가 종료될 때까지 현재 스레드가 대기하도록 만드는 함수다. 주로 메인 스레드가 작업 스레드의 종료를 기다릴 때 사용한다.</p>
<p>이 함수가 중요한 이유는 <code>pthread_join()</code>을 호출하지 않으면 메인 스레드가 먼저 종료될 수 있고, 그 경우 아직 작업 중인 다른 스레드들이 강제로 종료될 수 있기 때문이다. 스레드를 생성하는 것만큼, 작업이 끝날 때까지 제대로 기다려주는 과정도 중요하다.</p>
<p>정리하면 <code>pthread_join()</code>은 두 가지 역할을 한다. 스레드의 실행 순서를 제어하고, 모든 작업이 정상적으로 마무리될 수 있도록 보장한다. 스레드를 생성했다면 반드시 짝을 이루어 호출해야 하는 함수라고 이해하면 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d35024d4-e2d7-43d6-b746-da5d193ded22.png" alt="" style="display:block;margin:0 auto" />

<h3>Pthread Core Concept ④ pthread_t (Thread Identifier)</h3>
<p><code>pthread_t</code> is a unique identifier for distinguishing threads; a thread ID. When a thread is created, the OS assigns it a unique ID, through which a specific thread can be waited on, controlled, or have its state checked.</p>
<p><code>pthread_t</code> serves as the most fundamental identifier for managing threads in the Pthread library. Functions like <code>pthread_join()</code> and <code>pthread_create()</code> covered earlier all rely on this identifier to specify and control particular threads.</p>
<p>Internally, this identifier is one of the key pieces of information stored in the TCB (Thread Control Block). It is the starting point from which the OS tracks and manages threads.</p>
<h3>Pthread 핵심 개념 ④ pthread_t (스레드 식별자)</h3>
<p><code>pthread_t</code>는 스레드를 구분하기 위한 고유 식별자, 즉 스레드 ID다. 스레드를 생성하면 운영체제는 각 스레드를 관리하기 위해 고유한 ID를 부여하며, 이 ID를 통해 특정 스레드를 기다리거나 제어하거나 상태를 확인할 수 있다.</p>
<p><code>pthread_t</code>는 Pthread 라이브러리에서 스레드를 관리하기 위한 가장 기본적인 식별자 역할을 한다. 앞서 살펴본 <code>pthread_join()</code>이나 <code>pthread_create()</code> 같은 함수들도 모두 이 식별자를 기반으로 특정 스레드를 지정하고 제어한다.</p>
<p>내부적으로 보면 이 식별자는 TCB(스레드 제어 블록)에 저장되는 핵심 정보 중 하나다. 운영체제가 스레드를 추적하고 관리하는 출발점이 바로 이 ID라고 이해하면 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c485d822-2b68-4203-85c0-176892542f51.png" alt="" style="display:block;margin:0 auto" />

<h3>Pthread Core Concept ⑤ Passing Data Between Threads</h3>
<p>In Pthread, thread functions always receive an argument of type <code>void *</code>. This means data is passed <strong>by address, not by value</strong>. Whether passing an integer, a struct, or a string, the memory address where the data is stored is what gets handed to the thread.</p>
<p>Why is it designed this way? To <strong>unify the form of all thread functions</strong>. Regardless of what type of data is passed, the function receives a single address and reinterprets it as the needed type internally. Threads also return their results as <code>void *</code> upon termination, and when multiple values need to be passed, it is common practice to bundle them into a struct and pass the address of that struct.</p>
<p>There is a critical warning here. Suppose you create a local variable inside a function and pass its address to a thread. When that function returns, the local variable disappears from memory. But the thread may still be running. In this case, the thread ends up using an address pointing to memory that no longer exists, causing an invalid memory access.</p>
<p>Therefore, whenever passing data to a thread, always ask: <strong>"Will this memory still be alive when the thread finishes?"</strong> The core principle of Pthread data passing is that it works by address rather than by value, and the <strong>lifetime and scope of that memory must always be considered.</strong></p>
<h3>Pthread 핵심 개념: 스레드 간 데이터 전달</h3>
<p>Pthread에서 스레드 함수는 항상 <code>void *</code> 형태의 인자를 받는다. 이는 값 자체를 넘기는 구조가 아닌 <strong>주소를 넘기는 구조</strong>라는 뜻이다. 정수든 구조체든 문자열이든, 스레드에 데이터를 전달할 때는 그 데이터가 저장된 메모리의 주소를 넘겨주게 된다.</p>
<p>왜 이렇게 설계했을까? 스레드 함수의 형태를 하나로 통일하기 위해서다. 어떤 타입의 데이터가 오더라도 일단 주소 하나를 받고, 함수 내부에서 필요한 타입으로 다시 해석하도록 만든 구조다. 스레드가 종료할 때도 마찬가지로 <code>void *</code> 형태로 결과를 반환하며, 여러 값을 전달할 경우에는 구조체로 묶어서 넘기는 방식을 많이 사용한다.</p>
<p>여기서 반드시 주의해야 할 점이 있다. 어떤 함수 안에서 지역 변수를 만들고 그 주소를 스레드에 넘겼다고 가정해보자. 함수가 끝나면 그 지역 변수는 메모리에서 사라진다. 하지만 스레드는 아직 실행 중일 수 있다. 이 경우 스레드는 이미 사라진 메모리를 가리키는 주소를 사용하게 되고, 결국 잘못된 메모리를 참조하는 문제가 발생한다.</p>
<p>따라서 스레드에 데이터를 넘길 때는 항상 <strong>"이 메모리가 스레드가 끝날 때까지 살아있는가?"</strong> 를 확인해야 한다. Pthread는 데이터를 값이 아닌 주소로 주고받는 구조이며, 그 메모리의 수명과 범위를 반드시 고려해야 한다는 점이 핵심이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ef0c7a47-32a1-4c32-987f-5040bfd1d316.png" alt="" style="display:block;margin:0 auto" />

<h3>Example Code ① pthread_create()</h3>
<p>Let's start with the code structure. Since C has no built-in thread syntax, <code>#include</code> <code>&lt;pthread.h&gt;</code> must be included to use thread functionality. <code>thread_func</code> is the function the newly created thread will execute. In Pthread, thread functions are defined with a <code>void *</code> signature. This function simply prints "새로운 스레드 실행 중" (New thread running) and terminates with <code>return NULL</code>. Looking at the main function: <code>pthread_t tid</code> is a variable that stores the identifier for the created thread. After printing "main 스레드 시작" (main thread start), calling <code>pthread_create(&amp;tid, NULL, thread_func, NULL)</code> creates a new execution flow, and the new thread begins executing <code>thread_func</code>. The key here is that this code has no <code>pthread_join()</code>. The main thread creates the new thread and immediately moves to the next line without waiting, printing "main 스레드 종료" (main thread end). This leads to two possible outcomes. First, the main thread may hold the CPU and terminate before the new thread gets a chance to run, in which case only "main 스레드 시작 → main 스레드 종료" is printed. Second, if the OS allocates the CPU to the new thread first, the output becomes "main 스레드 시작 → 새로운 스레드 실행 중 → main 스레드 종료." Which thread runs first is determined by the OS's scheduling. This example demonstrates exactly that. <code>pthread_create()</code> creates a new execution flow, but without <code>pthread_join()</code>, the main thread does not wait — so the output can differ every time the program runs.</p>
<p>Through this hands-on example, you can observe that the order of output results varies between runs.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e19fabe4-b779-49d8-b8bb-412645862ec9.png" alt="" style="display:block;margin:0 auto" />

<h3>예제 코드 ① pthread_create()</h3>
<p>코드 구조부터 살펴보자. C 언어에는 기본적으로 스레드 문법이 없기 때문에 <code>#include &lt;pthread.h&gt;</code>를 반드시 포함해야 스레드 기능을 사용할 수 있다. <code>thread_func</code>은 새로 생성된 스레드가 실행할 함수다. Pthread에서는 스레드 함수의 형태가 <code>void *</code>로 정해져 있다. 이 함수 안에서는 단순히 "새로운 스레드 실행 중"을 출력하고 <code>return NULL</code>로 종료한다. 메인 함수를 살펴보자. <code>pthread_t tid</code>는 생성된 스레드를 구분하기 위한 식별자를 저장하는 변수다. "main 스레드 시작"을 출력한 뒤, <code>pthread_create(&amp;tid, NULL, thread_func, NULL)</code>을 호출하는 순간 새로운 실행 흐름이 만들어지고, 생성된 스레드는 <code>thread_func</code>을 실행하게 된다. 여기서 핵심은 이 코드에 <code>pthread_join()</code>이 없다는 점이다. 메인 스레드는 새 스레드를 만들기만 하고 끝날 때까지 기다려주지 않은 채 바로 다음 줄로 내려가 "main 스레드 종료"를 출력한다. 이 때문에 실행 결과가 두 가지로 나타날 수 있다. 첫째, 메인 스레드가 계속 CPU를 점유하다가 새 스레드가 실행되기 전에 종료해버리는 경우로, "main 스레드 시작 → main 스레드 종료"만 출력된다. 둘째, 운영체제가 새 스레드에 CPU를 먼저 할당하는 경우로, "main 스레드 시작 → 새로운 스레드 실행 중 → main 스레드 종료" 순으로 출력된다. 어떤 스레드가 먼저 실행될지는 운영체제의 스케줄링이 결정한다. 이 예제는 바로 그 점을 보여준다. <code>pthread_create()</code>는 새로운 실행 흐름을 만들어주지만, <code>pthread_join()</code>이 없으면 메인 스레드는 새 스레드를 기다려주지 않기 때문에 실행할 때마다 출력 결과가 달라질 수 있다.</p>
<p>위의 실습 예제를 통해 실행 결과값의 순서가 달라지는 것을 확인할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a92105a2-b4d4-40ca-ae9b-d3dd5da2aab7.png" alt="" style="display:block;margin:0 auto" />

<h3>Example Code ② pthread_join()</h3>
<p>In the previous example, using only <code>pthread_create()</code> meant execution order was not guaranteed. This example adds <code>pthread_join()</code> to clearly show the difference. Looking at <code>thread_func</code>: it loops from 1 to 3, printing "스레드 작업 1, 2, 3" (Thread task 1, 2, 3), pausing for 1 second with <code>sleep(1)</code> between each iteration. In the main function, after creating the thread with <code>pthread_create()</code>, <code>pthread_join(tid, NULL)</code> is called immediately. <code>pthread_join()</code> is the key. This function puts the current thread into a waiting state until the specified thread has fully terminated. The main thread does not move to the next line until the new thread has printed 1, 2, 3 and terminated with <code>return NULL</code>. Only after that is "main 종료" (main end) printed. Therefore, the output is always fixed: "스레드 작업 1 → 스레드 작업 2 → 스레드 작업 3 → main 종료." The difference from the previous example is clear. Without <code>pthread_join()</code>, execution order varies by scheduling; with it, the main thread is guaranteed to wait for the new thread to finish. To summarize: <code>pthread_create()</code> adds an execution flow, and pthread_join() synchronizes that flow. Join means "wait", it is the core function that guarantees execution order between threads.</p>
<p>Compared to the previous example, you can observe the clear difference between waiting and not waiting.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3e5639a9-c5f5-437a-90f9-ed8efc7b2efd.png" alt="" style="display:block;margin:0 auto" />

<h3>예제 코드 ② pthread_join()</h3>
<p>앞선 예제에서는 <code>pthread_create()</code>만 사용했기 때문에 실행 순서가 보장되지 않았다. 이번 예제는 여기에 <code>pthread_join()</code>을 추가해 그 차이를 명확히 보여준다. <code>thread_func</code>을 보면 1부터 3까지 반복하며 "스레드 작업 1, 2, 3"을 출력하고, 각 반복마다 <code>sleep(1)</code>으로 1초씩 잠시 멈춘다. 메인 함수에서는 <code>pthread_create()</code>로 스레드를 생성한 뒤 곧바로 <code>pthread_join(tid, NULL)</code>을 호출한다. <code>pthread_join()</code>이 핵심이다. 이 함수는 지정한 스레드가 완전히 종료될 때까지 현재 스레드를 대기 상태로 만든다. 즉 메인 스레드는 새 스레드가 1, 2, 3을 모두 출력하고 <code>return NULL</code>로 종료될 때까지 다음 줄로 내려가지 않는다. 그 이후에야 비로소 <code>"main 종료"</code>가 출력된다. 따라서 실행 결과는 항상 "스레드 작업 1 → 스레드 작업 2 → 스레드 작업 3 → main 종료" 순서로 고정된다. 앞 예제와 비교하면 차이가 분명하다. <code>pthread_join()</code>이 없으면 실행 순서가 스케줄링에 따라 달라지지만, <code>pthread_join()</code>이 있으면 메인 스레드가 반드시 새 스레드의 종료를 기다린다. 정리하면, <code>pthread_create()</code>는 실행 흐름을 추가하고, <code>pthread_join()</code>은 그 흐름을 동기화한다. join은 "기다려라"는 의미이며, 스레드 간 실행 순서를 보장하는 핵심 함수다.</p>
<p>앞의 예제와 비교했을때 기다림이 있는 경우와 없는 경우의 차이를 확인할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/02312218-c9ca-426b-a46d-6dee41332532.png" alt="" style="display:block;margin:0 auto" />

<h3>Example Code ③ pthread_join() Not Used</h3>
<p>This example is nearly identical to the previous one, but the critical difference is the absence of <code>pthread_join()</code>. The main thread creates the new thread and immediately drops to the next line without waiting, printing "main 종료."</p>
<p>In this situation, the result depends on which thread the OS allocates the CPU to first. If the main thread finishes execution first, only "main 종료" is printed, and the process terminates before the worker thread completes its work. Even if the new thread runs first, the main may terminate in the middle of printing "스레드 작업 1," and only if lucky will all three outputs appear. The result differs every time.</p>
<p>Two reasons explain this behavior. First, the main thread does not wait for the new thread. Second, when the main thread terminates, the process itself may terminate, forcibly ending any still-running threads along with it.</p>
<p>In summary: without <code>pthread_join()</code>, execution order is subject to scheduling, and thread tasks may not complete fully. This example demonstrates exactly why <code>pthread_join()</code> is essential.</p>
<blockquote>
<p>Worker thread tasks may not run to completion. This confirms that threads are not automatically synchronized just because they were created.</p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f67b911a-ae63-445a-b69d-bdf8504f4192.png" alt="" style="display:block;margin:0 auto" />

<h3>예제 코드 ③ pthread_join() 미사용</h3>
<p>이번 예제는 앞선 코드와 거의 동일하지만 <code>pthread_join()</code>이 없다는 점이 결정적인 차이다. 메인 스레드는 새 스레드를 생성한 뒤 기다리지 않고 곧바로 다음 줄로 내려가 "main 종료"를 출력한다.</p>
<p>이 상황에서는 운영체제가 어떤 스레드에 CPU를 먼저 할당하느냐에 따라 결과가 달라진다. 메인 스레드가 먼저 실행을 마쳐버리면 "main 종료"만 출력되고 프로세스가 종료되면서 작업 스레드가 끝까지 실행되지 못한다. 새 스레드가 먼저 실행되더라도 "스레드 작업 1" 출력 도중 메인이 끝나버릴 수 있고, 운이으면 1, 2, 3이 모두 출력될 수도 있다. 실행할 때마다 결과가 달라지는 것이다.</p>
<p>이런 차이가 생기는 이유는 두 가지다. 첫째, 메인 스레드가 새 스레드를 기다려주지 않기 때문이다. 둘째, 메인 스레드가 종료되면 프로세스 자체가 종료될 수 있어 아직 실행 중인 스레드도 함께 강제 종료될 수 있기 때문이다.</p>
<p>정리하면, <code>pthread_join()</code>이 없으면 실행 순서는 스케줄링에 따라 달라지고, 경우에 따라 스레드 작업이 끝까지 수행되지 못할 수 있다. 이 예제는 바로 그 이유로 <code>pthread_join()</code>이 반드시 필요하다는 것을 보여주는 코드다.</p>
<p>woker작업이 끝까지 실행되지 않을수도 있다. 스레드는 만들었다고해서 자동으로 동기화되진 않는다는 점을 확인할 수 있다.</p>
<hr />
<h3>Example Code ④ pthread_exit()</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ab38e9dc-79e3-445f-aeaa-7ebafc86e65c.png" alt="" style="display:block;margin:0 auto" />

<p>The key of this example is <code>pthread_exit(NULL)</code>. This function terminates only the current thread, not the entire process. The main thread terminates at this point, but the worker thread continues running. Therefore, the <code>printf("이 문장은 출력되지 않음\n")</code> below <code>pthread_exit()</code> is never printed; the main thread has already terminated. Looking at the output, it appears that "main에서 pthread_exit 호출" (pthread_exit called from main) prints first, followed by "worker 스레드 작업 1, 2, 3." But strictly speaking, this order is not completely fixed. After <code>pthread_create()</code>, the worker thread could receive the CPU first. The order of the first line of output can vary by scheduling. What matters, however, is that the moment the main thread calls <code>pthread_exit()</code>, the process as a whole remains alive - so the worker thread is guaranteed to complete tasks 1, 2, and 3 in full. This is the biggest difference from the previous example without <code>pthread_join()</code>. Ending with <code>return 0</code> can terminate the entire process when main exits, but using <code>pthread_exit()</code> terminates only the main thread while other threads continue running. To summarize: <code>return 0</code> can terminate the entire process; <code>pthread_exit()</code> terminates only the current thread. It is the function that allows termination to be controlled at the thread level, ensuring other threads complete their work even after main exits first.</p>
<p>Even after main terminates, the worker thread continues printing. Note that the order of the first output line is not guaranteed.</p>
<h3>예제 코드 ④ pthread_exit()</h3>
<p>이번 예제의 핵심은 <code>pthread_exit(NULL)</code>이다. 이 함수는 프로세스 전체가 아닌 현재 스레드만 종료시킨다. 메인 스레드는 이 시점에서 종료되지만, 워커 스레드는 계속 실행된다. 따라서 <code>pthread_exit()</code> 아래의 <code>printf("이 문장은 출력되지 않음\n")</code>은 실제로 출력되지 않는다. 메인 스레드가 이미 종료되었기 때문이다. 실행 결과를 보면 "main에서 pthread_exit 호출"이 먼저 출력되고 이후 "worker 스레드 작업 1, 2, 3"이 이어지는 것처럼 보이지만, 엄밀히 말하면 이 순서가 완전히 고정된 것은 아니다. <code>pthread_create()</code> 이후 워커 스레드가 먼저 CPU를 할당받을 수도 있기 때문이다. 첫 줄의 출력 순서는 스케줄링에 따라 달라질 수 있다. 그러나 중요한 점은 메인 스레드가 <code>pthread_exit()</code>을 호출하는 순간 프로세스 전체는 유지되기 때문에, 워커 스레드는 반드시 작업 1, 2, 3을 끝까지 수행하게 된다는 것이다. 이것이 앞선 <code>pthread_join()</code> 미사용 예제와의 가장 큰 차이다. join 없이 return 0으로 끝내면 메인 종료 시 프로세스 전체가 종료될 수 있지만, <code>pthread_exit()</code>을 사용하면 메인 스레드만 종료되고 다른 스레드는 계속 실행된다. 정리하면 return 0은 프로세스 전체 종료 가능, <code>pthread_exit()</code>은 현재 스레드만 종료다. <code>pthread_exit()</code>은 스레드 단위로 종료를 제어할 수 있는 함수이며, 메인이 먼저 끝나더라도 다른 스레드의 작업을 끝까지 보장하고 싶을 때 사용한다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6ff0fd98-e38d-4d5d-829d-fdb7cf60c417.png" alt="" style="display:block;margin:0 auto" />

<p>메인이 종료되도 worker 스레드는 계속 출력이 된다. 첫 출력 순서는 보장되지 않는다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f03455f7-30d4-4902-a544-f10e03e44146.png" alt="" style="display:block;margin:0 auto" />

<h3>Example Code ⑤ pthread_self()</h3>
<p>Earlier examples covered how to create, terminate, and wait for threads. This one focuses on how to <strong>identify</strong> a thread.</p>
<p><code>pthread_self()</code> is a function that returns the ID of the currently running thread. Calling it inside the main function prints the ID of the main thread. After creating a worker thread with <code>pthread_create()</code>, calling <code>pthread_self()</code> inside the worker thread prints that thread's own ID.</p>
<p>The output shows that the main thread's ID and the worker thread's ID are different. This means each thread holds an independent identifier, and the OS manages threads based on these IDs.</p>
<p>The reason for casting to <code>unsigned long</code> is that <code>pthread_t</code> may not be internally identical to a standard integer type, so the cast is needed to print it in a readable numeric format.</p>
<p>One more important point: <strong>the main function is also a thread</strong>. We tend to think of main simply as the program's entry point, but from Pthread's perspective, main is a thread with its own unique ID.</p>
<p>To summarize, <code>pthread_self()</code> returns the ID of the currently running thread, and this example confirms that every thread — including main — holds a distinct unique ID.</p>
<h3>예제 코드 ⑤ 분석 pthread_self()</h3>
<p>앞에서는 스레드를 생성하고, 종료하고, 기다리는 방법을 살펴봤다. 이번에는 스레드를 식별하는 방법을 확인해보자. <code>pthread_self()</code>는 현재 실행 중인 스레드의 ID를 반환하는 함수다. 예제에서 메인 함수 안에서 이를 호출하면 현재 실행 중인 메인 스레드의 ID가 출력된다. 이후 <code>pthread_create()</code>로 워커 스레드를 생성하면, 워커 스레드 안에서도 <code>pthread_self()</code>를 통해 자신의 ID를 출력한다. 실행 결과를 보면 메인 스레드의 ID와 워커 스레드의 ID가 서로 다른 것을 확인할 수 있다. 이것은 각 스레드가 독립적인 식별자를 가지고 있으며, 운영체제가 이 ID를 기준으로 스레드를 관리한다는 것을 의미한다. 여기서 unsigned long으로 형변환을 하는 이유는, pthread_t가 내부적으로 정수형과 완전히 동일하지 않을 수 있기 때문에 출력을 위해 정수형으로 변환해주는 것이다. 또 한 가지 중요한 점은 메인도 하나의 스레드라는 것이다. 우리는 보통 메인을 프로그램의 시작점으로만 생각하지만, Pthread의 관점에서 보면 메인도 고유한 ID를 가진 하나의 스레드다. 정리하면, <code>pthread_self()</code>는 현재 실행 중인 스레드의 ID를 반환하는 함수이며, 메인을 포함한 각 스레드는 서로 다른 고유한 ID를 가진다는 점을 확인할 수 있는 예제다.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ac876da8-0e1b-4506-87f0-aee038d46de0.png" alt="" style="display:block;margin:0 auto" />

<hr />
]]></content:encoded></item><item><title><![CDATA[Linux Basics : Files and Filesystems]]></title><description><![CDATA[1️⃣ Everything is a file2️⃣ Using various file systems

1️⃣ Everything is a file
1. File


What is a File in Linux?
In Linux/Unix, everything is considered a file. Documents, executables, and even har]]></description><link>https://heesu.tech/linux-basics-files-and-filesystems</link><guid isPermaLink="true">https://heesu.tech/linux-basics-files-and-filesystems</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Wed, 01 Apr 2026 14:43:39 GMT</pubDate><content:encoded><![CDATA[<p>1️⃣ Everything is a file<br />2️⃣ Using various file systems</p>
<hr />
<h1>1️⃣ Everything is a file</h1>
<h2>1. File</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/25d85561-c8f7-45f7-b459-f028630840f7.png" alt="" style="display:block;margin:0 auto" />

<h2>What is a File in Linux?</h2>
<p>In Linux/Unix, everything is considered a file. Documents, executables, and even hardware devices are all treated as files. This is because unifying everything under the single concept of a "file" makes management and usage much more convenient. Files are broadly divided into three categories: <strong>Directory</strong>, <strong>Regular File</strong>, and <strong>Special File</strong>.</p>
<p>A directory is similar to a folder in Windows, often translated as "list." It does not contain actual data itself, but is composed of the names and location information of the files within it. Since files are distinguished by name, two files with the same name cannot exist within the same directory.</p>
<p>A regular file is the kind of file most people are familiar with, such as Word documents or PowerPoint files. It is divided into <strong>text files</strong>, which are encoded in a human-readable format, and <strong>binary files</strong>, which are closer to machine language.</p>
<p>Among special files, there is the <strong>Device File</strong>. Similar to a driver in Windows that connects hardware to the operating system, Linux treats the device (driver) itself as a file. The <code>file</code> command can be used to check a file's attributes. For example, entering <code>file /dev/input/event0</code> outputs <code>character special (13/64)</code>. Here, <code>character special</code> indicates that it is a character device file, <code>13</code> is the Major number representing the type of driver managing the device, and <code>64</code> is the Minor number representing which device it is within the same driver.</p>
<h3>리눅스에서 파일이란 무엇인가?</h3>
<p>리눅스/유닉스에서는 모든 것을 파일로 간주한다. . 문서, 실행 파일은 물론이고 하드웨어 장치까지도 파일로 간주한다. 이렇게 모든 것을 파일이라는 하나의 개념으로 통일하면 관리와 사용이 훨씬 편리해지기 때문이다. 파일은 크게 <strong>디렉토리</strong>, <strong>일반 파일(Regular File)</strong>, <strong>특별한 파일(Special File)</strong> 세 가지로 나뉜다.</p>
<p>디렉토리는 윈도우의 폴더와 비슷한 개념으로, 흔히 '목록'이라고 번역한다. 실제 데이터가 담겨있는 것이 아니라 파일의 이름과 위치 정보로 구성된다. 파일은 이름으로 구분되기 때문에 같은 디렉토리 안에 동일한 이름의 파일이 두 개 이상 존재할 수 없다.</p>
<p>일반 파일은 워드, PPT처럼 흔히 접하는 파일이다. 사람이 읽을 수 있는 형태로 인코딩된 <strong>텍스트 파일</strong>과, 기계어에 가까운 <strong>바이너리 파일</strong>로 구분된다.</p>
<p>특별한 파일에는 <strong>장치 파일(Device File)</strong> 이 있다. 윈도우에서 하드웨어와 운영체제를 연결하는 드라이버와 비슷한 개념으로, 리눅스에서는 이 장치(드라이버)도 파일로 간주한다. <code>file</code> 명령어로 파일의 속성을 확인할 수 있는데, 예를 들어 <code>file /dev/input/event0</code>을 입력하면 <code>character special (13/64)</code>와 같이 출력된다. 여기서 <code>character special</code>은 문자 장치 파일임을 의미하고, <code>13</code>은 이 장치를 관리하는 드라이버의 종류(Major 번호), <code>64</code>는 같은 드라이버 내에서의 장치 번호(Minor 번호)를 나타낸다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a2bc2d0c-2178-4515-b119-d2dbb67c2258.png" alt="" style="display:block;margin:0 auto" />

<h3>Handling Files - System Call</h3>
<p>Files can be handled through system calls. To understand system calls, one must first understand the structure of Linux/Unix.</p>
<p>Linux/Unix is composed of a <strong>Kernel</strong> and a <strong>Shell</strong>. The kernel is located at the innermost layer, like the core of the Earth, while the shell wraps around it like a outer layer. Because the kernel is covered by the shell, it cannot be easily accessed from the outside. The means to access the kernel is the <strong>System Call</strong>.</p>
<p>Representative system calls are as follows. <strong>open/close</strong> literally opens and closes a file, marking the start and end of file handling. It is possible to prepare to handle an existing file or to create a new one. <strong>read</strong> is used to retrieve data from a successfully opened file, and <strong>write</strong> is used to record data to a successfully opened file. <strong>lseek</strong> is used to change the position at which read or write is performed within a successfully opened file. In addition, there are many other system calls such as <code>access</code>, <code>chdir</code>, <code>chmod</code>, and <code>chown</code>.</p>
<h3>파일을 다루는 방법 - 시스템 콜(System Call)</h3>
<p>파일은 시스템 콜을 통해 다룰 수 있다. 시스템 콜을 이해하려면 먼저 리눅스/유닉스의 구조를 알아야 한다.</p>
<p>리눅스/유닉스는 <strong>커널(Kernel)</strong> 과 <strong>쉘(Shell)</strong> 로 구성된다. 커널은 지구의 핵처럼 가장 안쪽에 위치하고, 쉘은 껍질처럼 커널을 감싸고 있다. 커널은 쉘로 덮여있기 때문에 외부에서 쉽게 접근할 수 없으며, 이 커널에 접근하기 위한 수단이 바로 <strong>시스템 콜(System Call)</strong> 이다.</p>
<p>대표적인 시스템 콜은 다음과 같다.</p>
<p><strong>open / close</strong>는 말 그대로 파일을 열고 닫는 것으로, 파일 다루기의 시작과 종료를 의미한다. 이미 존재하는 파일을 다루기 위한 준비를 하거나, 새로운 파일을 생성하는 것도 가능하다.</p>
<p><strong>read</strong>는 open에 성공한 파일의 데이터를 읽어올 때 사용하고, <strong>write</strong>는 open에 성공한 파일에 데이터를 기록할 때 사용한다.</p>
<p><strong>lseek</strong>는 open에 성공한 파일에서 read 또는 write를 수행할 위치를 변경할 때 사용한다.</p>
<p>이 외에도 <code>access</code>, <code>chdir</code>, <code>chmod</code>, <code>chown</code> 등 다양한 시스템 콜이 존재한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/54eda1fb-7620-48c8-a596-6b96f2366ab1.png" alt="" style="display:block;margin:0 auto" />

<h3>Filesystem Hierarchy Standard (FHS)</h3>
<p>FHS, or the Filesystem Hierarchy Standard, is a standard that defines the locations of files and directories. The standard was created for two main reasons: to allow <strong>users</strong> and <strong>software</strong> to predict the location of files.</p>
<p>From a user's perspective, one must know where a desired file is located in order to use it. Not knowing the location means having to search for it manually, which leads to a waste of resources. The standard emerged to solve this problem.</p>
<p>The same applies from a software perspective. Software needs to know the location of files in order to use them flexibly. When installing software, there is a process of specifying the installation location, and for the software to correctly find and use the files it needs, the file locations must be predictable.</p>
<h3>Filesystem Hierarchy Standard (FHS)</h3>
<p>FHS, 즉 파일시스템 계층 표준은 파일과 디렉토리의 위치를 규정하는 표준이다. 이 표준이 만들어진 이유는 크게 두 가지로, <strong>사용자</strong>와 <strong>소프트웨어</strong>가 파일의 위치를 예측할 수 있도록 하기 위해서다.</p>
<p>사용자 입장에서는 찾고자 하는 파일이 어디에 있는지 알아야 사용할 수 있다. 위치를 모르면 일일이 찾아야 하고, 이는 곧 자원의 낭비로 이어진다. 이러한 문제를 해결하기 위해 표준이 등장한 것이다.</p>
<p>소프트웨어 입장에서도 마찬가지다. 파일의 위치를 알아야 소프트웨어가 해당 파일을 유연하게 활용할 수 있다. 소프트웨어를 설치할 때 설치 위치를 지정하는 과정이 있는데, 이때 소프트웨어가 필요한 파일을 올바르게 찾아 사용하려면 파일이 어디에 위치하는지 예측 가능해야 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a1393096-b5f9-4f55-a2bc-372725280970.png" alt="" style="display:block;margin:0 auto" />

<h3>Root Filesystem</h3>
<p>The root filesystem has a defined basic structure. It must support system boot, reverting to a previous state, recovering removed or lost data, and repairing damaged components.</p>
<p>The root filesystem starts with <code>/</code> and is organized in a tree structure, consisting of many directories such as <code>/bin</code>, <code>/boot</code>, <code>/dev</code>, <code>/etc</code>, and <code>/lib</code>. The name of each directory alone gives a basic understanding of what files it contains. Looking at the main directories: <code>/bin</code> contains essential command binaries, <code>/boot</code> contains static files used by the boot loader, <code>/dev</code> contains device files, and <code>/etc</code> contains system configuration files.</p>
<h3>루트 파일시스템(Root Filesystem)</h3>
<p>루트 파일시스템은 기본 틀이 정해져 있다. 시스템 부트, 이전 상태로 되돌리기, 제거되거나 손실된 것을 복구하기, 손상된 것을 수리하기 등이 가능해야 한다.</p>
<p>루트 파일시스템은 <code>/</code>로 시작하여 트리 구조로 이루어져 있으며, <code>/bin</code>, <code>/boot</code>, <code>/dev</code>, <code>/etc</code>, <code>/lib</code> 등 많은 디렉토리로 구성된다. 디렉토리 이름만 보아도 해당 디렉토리가 어떤 파일로 구성되어 있는지 기본적으로 파악할 수 있다. 주요 디렉토리를 살펴보면, <code>/bin</code>에는 필수 명령어 이진 파일이, <code>/boot</code>에는 부트 로더가 사용하는 변화없는 파일이, <code>/dev</code>에는 장치 파일이, <code>/etc</code>에는 시스템 설정 파일이 포함된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1c521d6b-eb89-4457-ac15-b1f1e051dc44.png" alt="" style="display:block;margin:0 auto" />

<h3>File Redirection : Output to File</h3>
<p>In Linux, the result of a command is by default printed to the screen. However, in Linux, the screen (standard output) is also considered a file. Therefore, the direction of output can be redirected to a file instead of the screen, and this is called <strong>Redirection</strong>.</p>
<p>For example, running <code>ls -l /etc/adduser.conf</code> prints the file information to the screen. However, using the <code>&gt;</code> symbol as in <code>ls -l /etc/adduser.conf &gt; redirect</code> saves the output to a file called <code>redirect</code> instead. Running <code>ls -l redirect</code> confirms that the file has been created, and <code>cat redirect</code> shows that the output that would have appeared on screen has been saved to the file.</p>
<p>Redirection is critically important in practice. When operating a server, there are many situations where program outputs or error messages need to be saved to a file, and redirection solves this with a simple <code>&gt; filename</code>. Since Linux servers often run tasks automatically without anyone at the monitor, saving results to a file for later review is very useful. Redirection is also a prime example of the Linux philosophy "everything is a file" in action.</p>
<h3>파일 방향 변경 : 파일로 출력</h3>
<p>리눅스에서 명령어의 실행 결과는 기본적으로 화면에 출력된다. 그런데 리눅스에서는 이 화면(표준 출력)도 파일로 간주한다. 따라서 출력의 방향을 화면이 아닌 다른 파일로 돌릴 수 있는데, 이것이 <strong>리다이렉션(Redirection)</strong> 이다.</p>
<p>위 예시를 보면, <code>ls -l /etc/adduser.conf</code>를 실행하면 해당 파일의 정보가 화면에 출력된다. 그런데 <code>ls -l /etc/adduser.conf &gt; redirect</code>와 같이 <code>&gt;</code> 기호를 사용하면 화면에 출력되어야 할 결과가 <code>redirect</code>라는 파일로 저장된다. 실제로 <code>ls -l redirect</code>로 확인해보면 <code>redirect</code> 파일이 생성된 것을 볼 수 있고, <code>cat redirect</code>로 파일의 내용을 확인하면 원래 화면에 출력되었어야 할 결과가 그대로 저장되어 있는 것을 확인할 수 있다.</p>
<p>리다이렉션은 실무에서 매우 중요하게 활용된다. 서버를 운영하다 보면 프로그램의 실행 결과나 오류 메시지를 파일로 저장해야 할 일이 많은데, 리다이렉션을 활용하면 <code>&gt; 파일명</code> 하나로 간단히 해결된다. 또한 리눅스 서버는 사람이 항상 모니터 앞에 있지 않아도 자동으로 작업이 실행되는 경우가 많기 때문에, 실행 결과를 파일로 저장해두면 나중에 확인할 수 있어 유용하다. 이처럼 리다이렉션은 "모든 것은 파일이=다"라는 리눅스 철학이 실제로 적용되는 대표적인 예시이기도 하다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/25a1da91-7532-4df7-929d-e5956e7d0516.png" alt="" style="display:block;margin:0 auto" />

<h3>File Redirection : Input from File, Output to File</h3>
<p>While <code>&gt;</code> redirects output to a file, <code>&lt;</code> does the opposite — it <strong>receives input from a file</strong>.</p>
<p>Running <code>ls -l /dev &gt; dev_file</code> saves the contents of the <code>/dev</code> directory to a file called <code>dev_file</code> without displaying anything on screen. Then, running <code>cat -v &lt; dev_file &gt; cat_dev_file</code> takes <code>dev_file</code> as input via <code>&lt;</code>, processes it with <code>cat -v</code>, and saves the result to <code>cat_dev_file</code> via <code>&gt;</code>. Since both input and output are directed to files, nothing appears on screen. Note that <code>cat -v</code>, unlike regular <code>cat</code>, displays special and control characters in a human-readable form.</p>
<p>Using <code>&lt;</code> and <code>&gt;</code> together allows an entire workflow of reading from a file, processing it, and saving the result to another file, all in a single command line. For example, when processing large log files on a server, one can use <code>&lt;</code> to receive the log file as input, process it as needed, and save the result to a new file with <code>&gt;</code>. In this way, <code>&lt;</code> and <code>&gt;</code> are important concepts that enable powerful automation in Linux.</p>
<h3>파일 방향 변경 : 파일에서 입력, 파일로 출력</h3>
<p>앞서 배운 <code>&gt;</code>가 출력의 방향을 파일로 돌리는 것이었다면, <code>&lt;</code>는 반대로 <strong>입력의 방향을 파일에서 받아오는 것</strong>이다.</p>
<p>먼저 <code>ls -l /dev &gt; dev_file</code>을 실행하면 <code>/dev</code> 디렉토리의 내용이 화면에 출력되지 않고 <code>dev_file</code>이라는 파일로 저장된다. 이후 <code>cat -v &lt; dev_file &gt; cat_dev_file</code>을 실행하면 <code>&lt;</code>를 통해 <code>dev_file</code>의 내용을 입력으로 받아 <code>cat -v</code>로 처리한 뒤, 그 결과를 <code>&gt;</code>를 통해 <code>cat_dev_file</code>이라는 파일로 저장한다. 입력과 출력 모두 파일로 방향이 지정되어 있기 때문에 화면에는 아무것도 출력되지 않는다. 참고로 <code>cat -v</code>는 일반 <code>cat</code>과 달리 특수문자나 제어문자도 눈에 보이는 형태로 출력해주는 옵션이다.</p>
<p><code>&lt;</code>와 <code>&gt;</code>를 함께 활용하면 파일을 입력으로 받아 처리한 뒤 결과를 다시 파일로 저장하는 흐름을 명령어 한 줄로 처리할 수 있다. 예를 들어 서버에서 대용량 로그 파일을 처리할 때, <code>&lt;</code>로 로그 파일을 입력받아 필요한 처리를 한 뒤 <code>&gt;</code>로 결과를 새로운 파일로 저장하는 식으로 활용할 수 있다. 이처럼 <code>&lt;</code>와 <code>&gt;</code>는 리눅스의 강력한 자동화 처리를 가능하게 하는 중요한 개념이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ab072ea7-e8c5-4040-b0d5-ac24939e556a.png" alt="" style="display:block;margin:0 auto" />

<h3>Counting Lines, Words, and Characters in a File (wc)</h3>
<p>Counting Lines, Words, and Characters in a File (wc) wc stands for word count. Running man wc confirms that it is a command that outputs "print newline, word, and byte counts for each file." Running wc cat_dev_file outputs 195 1935 10668 cat_dev_file, representing the line count (195), word count (1935), and byte count (10668) in that order. The same result can be obtained in three ways: specifying the filename directly with wc cat_dev_file, passing the file as input with wc &lt; cat_dev_file, or using a pipe with cat cat_dev_file | wc. All three methods produce the same result of 195 1935 10668. The pipe (|) passes the output of the preceding command directly as input to the following command. This allows complex tasks to be handled in a single line by combining multiple commands. For example, extracting only lines containing a specific word from a large log file, sorting them, and removing duplicates can all be done at once with cat filename | grep word | sort | uniq. In this way, the pipe, along with &lt; and &gt;, is a core feature that enables automation and efficient data processing in Linux.</p>
<h3>파일에서 라인 수, 단어 수, 문자 수 확인하기</h3>
<p>(wc) wc는 word count의 약자로, man wc 명령을 통해 확인하면 "print newline, word, and byte counts for each file", 즉 파일의 라인 수, 단어 수, 바이트 수를 출력하는 명령어임을 알 수 있다. wc cat_dev_file을 실행하면 195 1935 10668 cat_dev_file이 출력되는데, 순서대로 라인 수(195), 단어 수(1935), 바이트 수(10668) 를 의미한다. 위 예시에서 세 가지 방법으로 동일한 결과를 얻는 것을 볼 수 있다. wc cat_dev_file처럼 파일명을 직접 지정하거나, wc &lt; cat_dev_file처럼 &lt;를 통해 파일을 입력으로 넘겨주거나, cat cat_dev_file | wc처럼 |(파이프)를 사용하는 방법이다. 세 방법 모두 결과는 195 1935 10668로 동일하다. 파이프(|)는 앞 명령어의 출력 결과를 뒤 명령어의 입력으로 바로 넘겨주는 역할을 한다. 이를 활용하면 여러 명령어를 조합해 복잡한 작업을 한 줄로 처리할 수 있다. 예를 들어 대용량 로그 파일에서 특정 단어가 포함된 줄만 골라내고, 정렬하고, 중복을 제거하는 작업을 cat 파일명 | grep 단어 | sort | uniq처럼 명령어를 연결하는 것만으로 한 번에 처리할 수 있다. 이처럼 파이프는 앞서 배운 &lt;, &gt;와 함께 리눅스의 자동화와 효율적인 데이터 처리를 가능하게 하는 핵심 기능이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e29fc020-e755-43f8-8493-3fb8a307b64a.png" alt="" style="display:block;margin:0 auto" />

<h3>Searching File Contents by Pattern (grep)</h3>
<p><code>grep</code> stands for <strong>global regular expression print</strong>, and is a command used to search for specific patterns within file contents. For example, when looking for words starting with 'g' and ending with 'p', or characters meeting specific conditions between two letters, grep allows for convenient pattern-based searching without having to type out every possible case.</p>
<p>Running <code>grep kvm cat_dev_file</code> finds and outputs all lines containing <code>kvm</code> within <code>cat_dev_file</code>. Adding the <code>-n</code> flag also outputs the line numbers of matching lines. In the example, <code>kvm</code> is found on lines 162, 192, and 193.</p>
<p>The main grep flags are as follows. <code>-n</code> outputs the line numbers of matching lines, <code>-r</code> searches recursively through directories, <code>-c</code> outputs only the count of matching lines, and <code>-l</code> outputs only the names of matching files. <code>-i</code> searches without case sensitivity — keep in mind that Linux is case-sensitive by default.</p>
<h3>파일 내용에서 패턴으로 검색하기 (grep)</h3>
<p><code>grep</code>은 <strong>global regular expression print</strong>의 약자로, 파일 내용에서 특정 패턴을 검색할 때 사용하는 명령어다. 예를 들어 'g'로 시작하고 'p'로 끝나는 단어, 또는 두 글자 사이에 특정 조건을 만족하는 문자가 있는 것을 찾고 싶을 때, 일일이 모든 경우를 입력하지 않고 패턴으로 간편하게 검색할 수 있다.</p>
<p><code>grep kvm cat_dev_file</code>을 실행하면 <code>cat_dev_file</code> 안에서 <code>kvm</code>이 포함된 줄을 찾아 출력한다. 여기에 <code>-n</code> 플래그를 추가하면 일치하는 줄의 번호도 함께 출력된다. 예시에서 162번째 줄, 192번째 줄, 193번째 줄에 <code>kvm</code>이 포함되어 있음을 확인할 수 있다.</p>
<p>grep의 주요 플래그는 다음과 같다. <code>-n</code>은 일치하는 줄의 번호를 출력하고, <code>-r</code>은 디렉토리 안을 반복해서 검색하며, <code>-c</code>는 일치하는 줄의 개수만 출력하고, <code>-l</code>은 일치하는 파일 이름만 출력한다. <code>-i</code>는 대소문자를 구분하지 않고 검색하는 플래그인데, 리눅스는 기본적으로 대소문자를 구분한다는 점을 기억해두자.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/abaa9655-00ad-4be0-a0e0-36e35f103d40.png" alt="" style="display:block;margin:0 auto" />

<h3>Wildcard</h3>
<p>A wildcard is a feature that allows multiple targets to be specified at once using a pattern when searching for files or directories. Detailed information can be checked with the <code>man 7 glob</code> command.</p>
<p><code>*</code> matches anything regardless of the number of characters. For example, running <code>echo /dev/v*</code> outputs all files in the <code>/dev</code> directory that start with <code>v</code>. <code>ls /dev/v*</code> similarly lists all files starting with <code>v</code>.</p>
<p><code>?</code> substitutes for exactly one character. For example, searching <code>/dev/vcs?</code> returns only files where exactly one character follows <code>vcs</code>. Using <code>??</code> means exactly two characters, and the number of characters is determined by the number of question marks used.</p>
<p>In this way, wildcards make it very convenient to handle multiple files matching a pattern at once without having to type each filename individually.</p>
<h3>와일드 카드 (Wildcard)</h3>
<p>와일드 카드는 파일이나 디렉토리를 검색할 때 패턴을 사용해 여러 대상을 한 번에 지정할 수 있는 기능이다. <code>man 7 glob</code> 명령으로 자세한 내용을 확인할 수 있다.</p>
<p><code>*</code>는 아무 문자나 몇 글자든 상관없이 일치하는 것을 모두 찾는다. 예를 들어 <code>echo /dev/v*</code>를 실행하면 <code>/dev</code> 디렉토리에서 <code>v</code>로 시작하는 모든 파일을 출력한다. <code>ls /dev/v*</code>도 마찬가지로 <code>v</code>로 시작하는 모든 파일을 나열한다.</p>
<p><code>?</code>는 딱 한 글자만 대체한다. 예를 들어 <code>/dev/vcs?</code>를 검색하면 <code>vcs</code> 뒤에 한 글자만 오는 파일만 검색된다. <code>??</code>처럼 물음표를 두 개 쓰면 두 글자를 의미하며, 물음표 개수만큼 글자 수가 정해진다.</p>
<p>이처럼 와일드 카드를 활용하면 일일이 파일 이름을 입력하지 않고도 패턴에 맞는 여러 파일을 한 번에 다룰 수 있어 매우 편리하다.</p>
<hr />
<h1>2️⃣ Using various file systems</h1>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/39448560-791b-408d-9db4-0af252727b5f.png" alt="" style="display:block;margin:0 auto" />

<h3>Using Various File Systems : Mount</h3>
<p>We learned that in Linux, everything is considered a file. A concept closely connected to this is <strong>Mount</strong>.</p>
<p>In Windows, plugging a USB into a PC automatically assigns a drive letter such as <code>C:\</code>, <code>D:\</code>, or <code>E:\</code>. In Linux, however, a mount process is required instead. Recalling the root filesystem covered earlier, the USB device must be mounted somewhere within the tree structure that starts from <code>/</code>. In other words, if a device such as <code>/dev/usb</code> has a filesystem, mounting means adding that filesystem to the existing file hierarchy.</p>
<p>Mounting requires <strong>administrator (root) privileges</strong>. When checking file information with the <code>ls</code> command, permissions are displayed in a format such as <code>rwx rwx</code>. Only users with administrator privileges can execute the mount command; those without cannot.</p>
<h3>다양한 파일시스템 사용 : 마운트(Mount)</h3>
<p>리눅스에서는 모든 것을 파일로 간주한다고 배웠다. 이와 연결되는 개념으로 <strong>마운트(Mount)</strong> 에 대해 알아보자.</p>
<p>윈도우에서는 USB를 PC에 꽂으면 <code>C:\</code>, <code>D:\</code>, <code>E:\</code> 와 같이 알파벳 드라이브 문자가 자동으로 할당된다. 그러나 리눅스에서는 이와 다르게 마운트 과정이 필요하다. 앞서 배운 루트 파일시스템을 떠올려보면, <code>/</code>로 시작하는 트리 구조 안의 어딘가에 USB 장치를 마운트해주어야 한다. 즉, <code>/dev/usb</code>와 같은 장치에 파일시스템이 있다면 그 파일시스템을 기존의 파일 계층 구조에 추가해주는 것이 마운트다.</p>
<p>마운트를 하기 위해서는 <strong>관리자(root) 권한</strong>이 필요하다. <code>ls</code> 명령으로 파일 정보를 확인하면 <code>rwx rwx</code> 와 같은 형태로 권한이 표시되는데, 관리자 권한을 가진 사용자만 마운트 명령을 실행할 수 있으며 그렇지 않은 사용자는 사용할 수 없다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5c296437-9200-4e4e-8215-e706e6f22722.png" alt="" style="display:block;margin:0 auto" />

<h2>How to Use the Mount Command</h2>
<p>The basic form of the mount command is <code>mount -t filesystem device_name mount_point</code>.</p>
<p>Looking at each element: a <strong>filesystem</strong> defines how data is stored and managed on a storage device. As the number of files grows, they need to be managed systematically, and that management method is the filesystem. There are many types of filesystems, and they differ by operating system. Windows primarily uses NTFS, while Linux primarily uses ext. Therefore, when mounting, one must know what filesystem the device uses. The <strong>device name</strong> is the path of the device to be mounted, specified in the form <code>/dev/device_name</code>. The <strong>mount point</strong> is the path that will be accessed after mounting, specified as a location within the root filesystem such as <code>/home</code> or <code>/linux/usb</code>.</p>
<p>Unmounting is performed with the <code>umount mount_point</code> command, which detaches the device from the added file hierarchy. However, a device cannot be unmounted while it is in use, and a <code>busy</code> warning message will be displayed. This is similar to what happens in Windows when a USB is pulled out without clicking the eject button, causing files to become inaccessible. Removing a device before synchronization is complete can corrupt files, so it is essential to unmount the device only after finishing its use.</p>
<h3>마운트 명령어 사용법</h3>
<p>마운트 명령어의 기본 형태는 <code>mount -t 파일시스템 장치이름 사용위치</code>이다.</p>
<p>각 항목을 살펴보면, 먼저 <strong>파일시스템</strong>이란 저장장치에 데이터를 어떻게 저장하고 관리하느냐를 정의하는 방식이다. 파일이 많아지면 이를 체계적으로 관리해야 하는데, 그 관리 방식이 바로 파일시스템이다. 파일시스템의 종류는 매우 다양하며 운영체제마다 다르다. 윈도우는 NTFS를, 리눅스는 ext를 주로 사용한다. 따라서 마운트를 할 때는 해당 장치가 어떤 파일시스템을 사용하는지 반드시 알아야 한다. <strong>장치이름</strong>은 마운트할 장치의 경로로, <code>/dev/장치이름</code>의 형태로 지정한다. <strong>사용위치</strong>는 마운트 후 실제로 접근하게 될 경로로, <code>/home</code> 이나 <code>/linux/usb</code>와 같이 루트 파일시스템 안의 위치를 지정한다.</p>
<p>마운트 해제는 <code>umount 사용위치</code> 명령으로 수행하며, 추가된 파일 계층 구조에서 해당 장치를 떼어내는 것이다. 단, 해당 장치가 사용 중일 때는 해제할 수 없으며 <code>busy</code>라는 경고 메시지가 출력된다. 이는 윈도우에서 USB를 꺼내기 버튼을 누르지 않고 바로 뽑았을 때 파일에 접근이 안 되는 경우와 비슷한 개념이다. 동기화가 완료되지 않은 상태에서 장치를 제거하면 파일이 손상될 수 있기 때문에, 반드시 사용이 끝난 후 마운트를 해제해야 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e784f4b7-91c2-4f45-ae87-c8c6940caa8c.png" alt="" style="display:block;margin:0 auto" />

<h2>SMB (Server Message Block)</h2>
<p>SMB is a <strong>client/server protocol</strong> used in mounting.</p>
<p>The mounting covered so far has been local, meaning it takes place within a single machine. However, to use storage located remotely, one would have to copy and retrieve the data files, which is a cumbersome and complex process. SMB resolves this inconvenience.</p>
<p>In SMB, the server provides a filesystem to be shared, and the client uses the server's files over the network. Not only files but also resources such as printers can be provided by the server, and from the client's perspective, remote files can be used just as if they were local.</p>
<h3>SMB (Server Message Block)</h3>
<p>SMB는 마운트에서 사용되는 <strong>클라이언트/서버 방식의 프로토콜</strong>이다.</p>
<p>지금까지 배운 마운트는 로컬, 즉 하나의 기계 안에서 이루어지는 것이었다. 그런데 원격에 있는 스토리지를 사용하려면 데이터 파일을 복사해서 가져와야 하는데, 이 과정이 번거롭고 복잡하다. SMB는 이러한 불편함을 해결해준다.</p>
<p>SMB에서 서버는 공유할 파일시스템을 제공하고, 클라이언트는 네트워크를 통해 서버의 파일을 사용한다. 파일뿐만 아니라 프린터와 같은 자원도 서버에서 제공받을 수 있으며, 클라이언트 입장에서는 로컬에 있는 파일을 사용하는 것과 동일하게 사용할 수 있다.</p>
<hr />
<h3>NFS (Network File System)</h3>
<p>NFS is a <strong>distributed filesystem protocol</strong> developed by Sun Microsystems.</p>
<p>When filesystems were discussed earlier, the focus was on how data is stored and managed on physical storage. However, despite having "filesystem" in its name, NFS does not serve that role. NFS is a <strong>network protocol</strong> that defines how files are exchanged and shared between a server and a client.</p>
<p>Like SMB, it is a client/server protocol where the server provides a filesystem to be shared and the client uses it over the network. One notable characteristic of NFS is that it can be used across multiple operating systems.NFS (Network File System)</p>
<p>NFS는 Sun Microsystems에서 개발한 <strong>분산 파일시스템 프로토콜</strong>이다.</p>
<p>앞서 파일시스템을 설명할 때는 물리적인 스토리지에 데이터를 어떻게 저장하고 관리할 것인가에 관한 것이었다. 그러나 NFS는 이름에 파일시스템이 들어가 있더라도 그 역할을 하지 않는다. NFS는 서버와 클라이언트 사이에서 파일을 어떻게 주고받으며 공유할 것인지를 정의하는 <strong>네트워크 프로토콜</strong>이다.</p>
<p>SMB와 마찬가지로 클라이언트/서버 방식의 프로토콜로, 서버는 공유할 파일시스템을 제공하고 클라이언트는 네트워크를 통해 이를 사용한다. NFS의 특징 중 하나는 여러 운영체제에서 사용 가능하다는 점이다.</p>
]]></content:encoded></item><item><title><![CDATA[Software Test Planning and Risk Management]]></title><description><![CDATA[1️⃣ Core Concepts of Test Planning2️⃣ Risk Management Overview3️⃣ Risk-based testing strategy

1️⃣ Core Concepts of Test Planning


What is a Test Plan?
To perform software testing systematically, pri]]></description><link>https://heesu.tech/software-test-planning-and-risk-management</link><guid isPermaLink="true">https://heesu.tech/software-test-planning-and-risk-management</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Sat, 28 Mar 2026 15:19:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d5e9b137-6bbd-4cec-873e-6796c0a232b4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>1️⃣ Core Concepts of Test Planning<br />2️⃣ Risk Management Overview<br />3️⃣ Risk-based testing strategy</p>
<hr />
<h1>1️⃣ Core Concepts of Test Planning</h1>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0ba02124-b603-45bf-b5c9-1c4d4b533659.png" alt="" style="display:block;margin:0 auto" />

<h3>What is a Test Plan?</h3>
<p>To perform software testing systematically, prior planning is essential. Regardless of what is being managed, proceeding without a plan leads to losing direction, and testing is no exception. This is why we have consistently emphasized from previous weeks that a <strong>Test Plan</strong> must be established before testing begins.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/190a6f88-c81c-4354-a258-d32fb2bc6a83.png" alt="" style="display:block;margin:0 auto" />

<p>A concept studied alongside this is the <strong>PDCA cycle</strong>. A continuous loop of <em><strong>Plan → Do → Check → Act.</strong></em> The test plan corresponds to the <strong>Plan phase</strong>, which is the starting point of this cycle. In this phase, we clearly define what to test, how far to test, and what goals we aim to achieve through testing at a high level.</p>
<p>The reason a test plan goes beyond mere preparation is that all subsequent activities; test design, execution, evaluation, and improvement; are carried out based on this plan. In other words, the test plan serves as the foundation that sets the direction and criteria for all downstream activities.</p>
<p>In summary, the test plan is the compass of the entire test process. The clearer the plan, the more consistently and efficiently all subsequent testing activities can be carried out.</p>
<h3>테스트 계획(Test Plan)이란?</h3>
<p>소프트웨어 테스트를 체계적으로 수행하기 위해서는 반드시 사전 계획이 필요하다. 어떤 대상을 관리하든 계획 없이 진행하면 방향을 잃기 쉽고, 테스트도 마찬가지다. 그래서 우리는 테스트를 시작하기 전에 반드시 <strong>테스트 계획(Test Plan)</strong> 을 세워야 한다는 점을 이전 주차부터 꾸준히 강조해왔다.</p>
<p>이와 관련하여 함께 학습한 개념이 바로 <strong>PDCA 사이클</strong>이다. PDCA란 Plan(계획) → Do(실행) → Check(평가) → Act(개선)의 순환 구조를 말하며, 테스트 계획은 이 사이클의 출발점인 <strong>Plan 단계</strong>에 해당한다. 이 단계에서는 무엇을 테스트할 것인지, 어디까지 테스트할 것인지, 그리고 상위 수준에서 테스트를 통해 달성하고자 하는 목표가 무엇인지를 명확하게 정의한다.</p>
<p>테스트 계획이 단순한 준비 작업에 그치지 않는 이유는, 이후에 이루어지는 테스트 설계, 수행, 평가 및 개선의 모든 활동이 바로 이 계획을 기준으로 전개되기 때문이다. 즉, 테스트 계획은 다음 작업들의 방향과 기준을 잡아주는 토대 역할을 한다.</p>
<p>정리하자면, 테스트 계획은 테스트 전체 프로세스의 나침반이다. 계획이 명확할수록 이후의 모든 테스트 활동이 더 일관성 있고 효율적으로 이루어질 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2abb4498-9d93-41ef-8623-223b9ba5562a.png" alt="" style="display:block;margin:0 auto" />

<h3>Test Planning in the ISO/IEC/IEEE 29119 Standard</h3>
<p>ISO/IEC/IEEE 29119 is an international standard covering software testing as a whole, structured into <strong>three layers.</strong> At the top is the <strong>Organizational Test Process</strong>, which establishes test policies and strategies applicable across the entire organization. Below that is the <strong>Test Management Process</strong>, responsible for planning and managing testing at the individual project level. Finally, the <strong>Dynamic Test Process</strong> is where actual test design, execution, and result processing take place. The test plan corresponds to the first activity within the Test Management Process.</p>
<p>There is one important principle to note here. A test plan is not created independently without context ; it must reflect the test policies and strategies established in the Organizational Test Process. Only when the direction of the higher layer is naturally embedded into the lower-level plan can consistent testing be achieved throughout the project.</p>
<p>So what exactly should a test plan document contain? First, the <strong>scope and test items</strong> must be clearly identified, defining what and how far to test. Next, the <strong>test objectives</strong> must be set, defining what the testing aims to achieve. Finally, a <strong>test strategy</strong> must be developed based on the Organizational Test Process, outlining how to achieve those objectives.</p>
<p>Throughout this process, the word <strong>strategy</strong> appears frequently. This refers not simply to "what to do," but to the specific methodology for "how to proceed systematically." Without a strategy, testing can easily lose its direction. Therefore, strategy is a core component that must be included in the test plan document.</p>
<p>Ultimately, the 29119 standard positions the test plan within a hierarchical flow of <strong>Organizational Policy → Test Strategy → Execution</strong>, and the test plan document is the tangible output that formalizes this flow.</p>
<h3>ISO/IEC/IEEE 29119 표준에서의 테스트 계획</h3>
<p>ISO/IEC/IEEE 29119는 소프트웨어 테스트 전반을 다루는 국제 표준으로, 크게 세 개의 계층으로 구성되어 있다. 가장 상위에는 조직 전체에 적용되는 테스트 정책과 전략을 수립하는 <strong>조직 차원의 테스트 프로세스</strong>가 있고, 그 아래에는 개별 프로젝트 수준에서 테스트를 계획하고 관리하는 <strong>테스트 관리 프로세스</strong>, 그리고 실제 테스트 설계와 실행, 결과 처리가 이루어지는 <strong>동적 테스트 프로세스</strong>가 순서대로 위치한다. 테스트 계획은 이 중 테스트 관리 프로세스의 첫 번째 활동에 해당한다.</p>
<p>여기서 한 가지 중요한 원칙이 있다. 테스트 계획은 아무런 맥락 없이 독립적으로 세워지는 것이 아니라, 반드시 상위 계층인 조직 차원의 테스트 프로세스에서 만들어진 테스트 정책과 전략을 반영해야 한다는 점이다. 상위 계층의 방향성이 하위 계획에 자연스럽게 녹아들어야만 프로젝트 전반에 걸쳐 일관성 있는 테스트가 가능하기 때문이다.</p>
<p>그렇다면 테스트 계획서에는 구체적으로 무엇이 담겨야 할까. 먼저 무엇을 어디까지 테스트할 것인지 <strong>대상과 범위를 명확히 식별</strong>해야 하고, 이번 테스트를 통해 달성하고자 하는 바를 <strong>테스트 목표로 설정</strong>해야 한다. 그리고 조직 차원의 테스트 프로세스를 기반으로 그 목표를 달성하기 위한 <strong>테스트 전략을 수립</strong>해야 한다.</p>
<p>이 과정에서 유독 <strong>전략(Strategy)</strong> 이라는 단어가 자주 등장하는데, 이는 단순히 "무엇을 할 것인가"에 머무르지 않고 "어떻게 체계적으로 해나갈 것인가"에 대한 구체적인 방법론을 의미한다. 목표만 있고 전략이 없다면 테스트는 쉽게 방향을 잃을 수 있기 때문에, 전략은 테스트 계획서의 핵심 구성 요소로 반드시 포함되어야 한다.</p>
<p>결국 29119 표준은 테스트 계획을 <strong>조직의 정책 → 테스트 전략 → 실행</strong>으로 이어지는 계층적 흐름 속에 위치시키고 있으며, 테스트 계획서는 바로 이 흐름을 하나의 문서로 구체화한 결과물이라고 할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/bf07ac0a-08e3-487d-9c84-e8905477c454.png" alt="" style="display:block;margin:0 auto" />

<h3>Detailed Process of Test Planning in 29119</h3>
<p>To develop a test plan, the process begins with <strong>understanding the project context</strong>. Only by grasping the overall picture; including the development scope and overall schedule - can the test scope be defined and a high-level test concept be formed. This concept then shapes the rough outline of the test plan and development schedule.</p>
<p>Once the schedule takes shape, the next step is <strong>risk identification and analysis</strong>; identifying and analyzing potential risk factors across the project. The methods identified to mitigate these risks are then incorporated into the <strong>test strategy design</strong>. Afterward, specific human resources and schedules are determined; who will perform the testing, what resources will be used, when and how; and these are documented in the <strong>test plan document</strong>. The completed document goes through consensus with stakeholders and is shared, completing the test planning process.</p>
<p>The most critical part of this entire flow is the three-step sequence of <strong>risk identification and analysis → risk mitigation identification → test strategy design</strong>. Planning is fundamentally an act of preparing for future events in advance. Therefore, risks that could affect test execution must always be included in the test plan. Ultimately, the core principle emphasized by the 29119 standard is that <strong>test planning must be risk-based</strong>.</p>
<h3>29119 테스트 계획의 세부 프로세스</h3>
<p>테스트 계획을 수립하기 위해서는 가장 먼저 프로젝트의 <strong>컨텍스트를 이해</strong>하는 것에서 출발한다. 개발하고자 하는 범위나 전체 일정 등 프로젝트의 전반적인 맥락을 파악해야 비로소 테스트의 범위가 정해지고, 테스트에 대한 <strong>전체적인 구상</strong>이 가능해진다. 이 구상을 바탕으로 테스트 계획과 개발 일정의 큰 틀이 만들어진다.</p>
<p>일정의 윤곽이 잡히면 그 다음으로는 <strong>위험 식별 및 분석</strong> 단계가 이어진다. 프로젝트 전반에 걸쳐 어떤 위험 요소가 존재하는지 찾아내고 이를 분석하는 과정이다. 이렇게 분석된 위험을 완화하기 위한 방법을 도출하고, 그 방법을 반영하여 <strong>테스트 전략을 설계</strong>하게 된다. 이후 누가 수행할 것인지, 어떤 자원을 사용할 것인지, 언제 어떻게 진행할 것인지와 같은 구체적인 인적 자원과 일정을 결정하여 <strong>테스트 계획서로 문서화</strong>한다. 완성된 계획서는 관련자들과 합의를 거쳐 공유되며 테스트 계획 수립이 마무리된다.</p>
<p>이 전체 흐름에서 가장 중요하게 짚어야 할 부분은 <strong>위험 식별 및 분석, 위험 완화 방법 식별, 테스트 전략 설계</strong>로 이어지는 세 단계다. 계획이란 본질적으로 미래에 일어날 일들을 미리 대비하는 행위다. 그렇기 때문에 테스트 계획을 세울 때도 앞으로의 테스트 수행에 영향을 미칠 수 있는 리스크를 반드시 계획 안에 포함시켜야 한다. 결국 <strong>테스트 계획은 위험을 기반으로 수립된다</strong>는 것이 29119 표준이 강조하는 핵심 원칙이다.</p>
<p>그렇다면 구체적으로 어떤 기준으로 위험을 식별하고 분석하는 것인지가 자연스러운 다음 질문이 된다. 다음 시간에는 바로 이 위험을 식별하고 분석하는 기준과 방법에 대해 자세히 살펴볼 예정이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0d76803d-1c0f-4e83-af9d-57ea9c92233f.png" alt="" style="display:block;margin:0 auto" />

<h3>Structure of the Test Plan Document; Master Test Plan and Level Test Plans</h3>
<p>Once test planning is complete, the resulting artifact is the <strong>test plan document</strong>, which is divided into two types: the <strong>Master Test Plan</strong> and <strong>Level Test Plans</strong>.</p>
<p>The <strong>Master Test Plan</strong> is a comprehensive document that consolidates and manages all the subordinate level test plans. Its purpose is to oversee and control multiple test levels and non-functional testing from a holistic perspective — it is essentially the top-level plan that coordinates the entire testing effort.</p>
<p>Beneath the Master Test Plan are individual <strong>Level Test Plans</strong>, each specific to a particular test level. These detail the test strategy, specific activities, detailed schedule, test owners, execution methods, and tools for each test level. The test types covered correspond to the right side of the <strong>V-model</strong>; <strong>unit testing, integration testing, system testing, acceptance testing</strong> — as well as <strong>non-functional testing</strong> such as load and performance testing.</p>
<p>In summary, if the Master Test Plan is the big picture that provides an overview of all testing, then the Level Test Plans are the detailed blueprints that specify exactly how each test will be conducted within that big picture.</p>
<h3>테스트 계획서의 구성; 총괄 테스트 계획과 단계별 테스트 계획</h3>
<p>테스트 계획 수립이 완료되면 그 결과물로 <strong>테스트 계획서</strong>가 만들어진다. 테스트 계획서는 크게 <strong>총괄 테스트 계획</strong>과 <strong>단계별 테스트 계획</strong> 두 가지로 나뉜다.</p>
<p>먼저 <strong>총괄 테스트 계획</strong>은 하위에 존재하는 여러 단계별 테스트 계획들을 하나로 묶어 종합적으로 관리하는 계획서다. 여러 테스트 단계와 비기능 테스트 등을 전체적인 시각에서 관리하고 통제하는 것이 목적이며, 말 그대로 테스트 전반을 조율하는 최상위 계획서라고 볼 수 있다.</p>
<p>그리고 이 총괄 테스트 계획 아래에는 각 단계별로 세분화된 <strong>단계별 테스트 계획</strong>이 존재한다. 단계별 테스트 계획에서는 각 테스트 단계에서 수행할 테스트 전략, 구체적인 활동, 세부 일정, 테스트 담당자, 수행 방법, 사용 도구 등을 상세하게 계획한다. 대상이 되는 테스트 유형은 V 모델의 오른쪽에 해당하는 <strong>단위 테스트, 통합 테스트, 시스템 테스트, 인수 테스트</strong>, 그리고 사용량이나 성능 등을 검증하는 <strong>비기능 테스트</strong>까지 포함된다.</p>
<p>정리하자면, 총괄 테스트 계획이 전체 테스트를 조망하는 큰 그림이라면, 단계별 테스트 계획은 그 큰 그림 안에서 각 테스트를 어떻게 실제로 수행할 것인지를 구체적으로 담아낸 세부 설계도라고 할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/64ccf653-b51f-4728-936c-f4d812f915c3.png" alt="" style="display:block;margin:0 auto" />

<h3>Contents of the Master Test Plan and Level Test Plans</h3>
<p>Since the test plan document is divided into master and level plans, the depth and specificity of content in each naturally differs.</p>
<p>The <strong>Master Test Plan</strong> contains high-level content: the <strong>test purpose and scope</strong> explaining why testing is being conducted; the <strong>test item definition</strong> clarifying what will be tested; the <strong>test strategy and approach</strong> describing how testing will proceed; the overall <strong>schedule</strong>; the <strong>organizational structure and roles</strong> defining who is responsible for what; and <strong>assumptions and constraints</strong> that may affect test execution. In short, the Master Test Plan is a document capturing the big picture of all testing.</p>
<p>Based on this master plan, individual <strong>Level Test Plans</strong> are created — separate documents for each test level such as unit, integration, system, and acceptance testing. These contain much <em>more specific content:</em> the <strong>test scope and strategy</strong> for that level, the <strong>activities and objectives</strong> to be performed, the <strong>characteristics of the test items</strong>, <strong>test design methods</strong>, <strong>constraints</strong> during execution, <strong>input/output work products</strong>, <strong>test tools</strong> to be used, the <strong>detailed schedule</strong>, and the final <strong>deliverables</strong>.</p>
<p>Ultimately, while the Master Test Plan provides the overall direction and criteria, the Level Test Plans serve as detailed execution guides explaining exactly how each test level will be carried out. Only when the two documents are organically connected can systematic and consistent test execution be achieved.</p>
<h3>[예시] 총괄 및 단계별 테스트 계획서의 구성 항목</h3>
<p>테스트 계획서는 총괄과 단계별로 나뉘는 만큼, 각각에 담기는 내용의 수준과 세부성도 자연스럽게 달라진다.</p>
<p><strong>총괄 테스트 계획서</strong>에는 상위 수준의 내용들이 담긴다. 테스트를 왜 수행하는지에 대한 <strong>테스트 목적 및 범위</strong>, 무엇을 테스트할 것인지를 정의하는 <strong>테스트 대상 시스템 정의</strong>, 테스트를 어떻게 진행할 것인지에 대한 <strong>테스트 전략과 수행 절차</strong>, 전체적인 <strong>일정</strong>, 누가 어떤 역할을 맡을 것인지에 대한 <strong>조직 구성 및 역할</strong>, 그리고 테스트 수행에 영향을 줄 수 있는 <strong>가정 및 제약사항</strong> 등이 포함된다. 즉, 총괄 테스트 계획서는 테스트 전반을 조망하는 큰 그림을 담은 문서라고 할 수 있다.</p>
<p>이 총괄 계획서를 기반으로 <strong>단계별 테스트 계획서</strong>가 만들어진다. 단위 테스트, 통합 테스트, 시스템 테스트, 인수 테스트와 같이 각 테스트 단계별로 세분화된 계획서가 따로 작성되며, 여기에는 훨씬 구체적인 내용들이 담긴다. 해당 단계에서의 <strong>테스트 범위 및 전략</strong>, 수행해야 할 <strong>활동과 목적</strong>, 테스트 <strong>대상의 특성</strong>, <strong>테스트 설계 방법</strong>, 수행 시의 <strong>제약사항</strong>, 테스트의 <strong>입출력 산출물</strong>, 활용할 <strong>테스트 도구</strong>, <strong>세부 일정</strong>, 그리고 최종적으로 만들어지는 <strong>산출물</strong> 등이 해당된다.</p>
<p>결국 총괄 테스트 계획서가 전체 방향과 기준을 제시하는 문서라면, 단계별 테스트 계획서는 그 기준 아래에서 각 테스트 단계를 실제로 어떻게 수행할 것인지를 상세하게 풀어낸 실행 지침서라고 볼 수 있다. 두 문서가 유기적으로 연결될 때 비로소 체계적이고 일관성 있는 테스트 수행이 가능해진다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/45048221-b7b0-447c-a2ab-6fe2d42d82aa.png" alt="" style="display:block;margin:0 auto" />

<h3>Input/Output Work Products and Exit Criteria for Unit and System Test Plans</h3>
<p>Let us examine how a Level Test Plan is structured in practice, using <strong>unit testing</strong> and <strong>system testing</strong> as examples.</p>
<h3>Unit Test Plan</h3>
<p>To conduct unit testing, the <strong>input work products</strong> required are the unit test plan and unit test cases. After executing the test cases, the <strong>output work product</strong>; the test results report; is produced. Tools such as <strong>JUnit</strong>, a Java-based testing framework, are used in this process.</p>
<p>A key aspect of unit testing is the <strong>exit criteria</strong>. Since unit testing involves directly examining the source code, <strong>coverage</strong> - a measure of how much code has been tested - is used as the exit criterion. The two most common types are <strong>statement coverage</strong> (the ratio of executed statements to total statements) and <strong>branch coverage</strong> (the ratio of tested branches or decision points). A target value is set based on these metrics, and the test is considered complete when the target is met.</p>
<h3>System Test Plan</h3>
<p>System testing is performed based on the results of requirements analysis in the V-model. Like unit testing, the <strong>input work products</strong> are the system test plan and test cases, and the <strong>output work product</strong> after execution is the test results report. Tools such as <strong>JMeter</strong> may be used at this level.</p>
<p>The exit criteria for system testing are based on <strong>requirements coverage</strong> — the percentage of total requirements for which testing has been performed. A target percentage is set and serves as the completion benchmark.</p>
<p>While detailed schedules, test owners, and specific execution methods are also included in the plan, even the input/output work products and exit criteria alone illustrate how concretely and measurably a Level Test Plan must be written.</p>
<h3>[예시] 단위 테스트 및 시스템 테스트 계획서의 입/출력 산출물과 완료 기준</h3>
<p>단계별 테스트 계획서가 실제로 어떻게 구성되는지를 <strong>단위 테스트</strong>와 <strong>시스템 테스트</strong>를 예시로 살펴보자.</p>
<h3>단위 테스트 계획서</h3>
<p>단위 테스트를 수행하기 위해서는 먼저 <strong>입력 산출물</strong>로 단위 테스트 계획서와 단위 테스트 케이스가 필요하다. 테스트 케이스를 기반으로 테스트를 수행하고 나면 <strong>출력 산출물</strong>인 테스트 결과서가 만들어진다. 이 과정에서는 Java 기반의 <strong>JUnit</strong>과 같은 테스트 도구가 활용된다.</p>
<p>단위 테스트에서 주목해야 할 부분은 <strong>완료 기준</strong>이다. 단위 테스트는 실제 코드를 직접 들여다보며 수행하기 때문에, 얼마나 많은 코드를 테스트했는지를 나타내는 <strong>커버리지(Coverage)</strong> 를 완료 기준으로 활용한다. 대표적으로 <strong>문장 커버리지</strong>와 <strong>분기 커버리지</strong>가 있는데, 문장 커버리지는 전체 문장 수 대비 테스트가 수행된 문장 수의 비율로, 분기 커버리지는 조건문이나 분기문이 얼마나 테스트되었는지의 비율로 산출된다. 이 수치를 기반으로 목표치를 설정하고, 그 목표를 달성했을 때 테스트가 완료된 것으로 판단한다.</p>
<h3>시스템 테스트 계획서</h3>
<p>시스템 테스트는 V 모델 기준으로 요구사항 분석 결과를 바탕으로 수행된다. 단위 테스트와 마찬가지로 테스트를 실제로 수행하기 위한 <strong>입력 산출물</strong>로 시스템 테스트 계획서와 테스트 케이스가 필요하며, 수행 후에는 <strong>출력 산출물</strong>인 테스트 결과서가 생성된다. 이 단계에서는 <strong>JMeter</strong>와 같은 도구가 활용될 수 있다.</p>
<p>시스템 테스트의 완료 기준은 <strong>요구사항 커버리지</strong>를 기반으로 한다. 전체 요구사항 중 실제로 테스트가 수행된 요구사항이 몇 퍼센트인지를 산출하여 목표치를 설정하고, 이를 완료 기준으로 삼는 것이다.</p>
<p>물론 이 외에도 테스트 수행을 위한 세부 일정, 담당자, 구체적인 수행 방법 등 다양한 요소들이 계획서에 포함되지만, 입출력 산출물과 완료 기준만 보더라도 단계별 테스트 계획서가 얼마나 구체적이고 측정 가능한 형태로 작성되어야 하는지를 잘 알 수 있다.</p>
<hr />
<h3>Why the Test Plan Matters</h3>
<p>There is a clear reason why the test plan is developed so meticulously. A test plan is not merely about creating a document. It becomes the <strong>baseline for all test design and execution</strong> that follows. Testing is designed and executed based on the scope, strategy, and exit criteria defined in the plan, and the test plan also serves as the <strong>foundation for monitoring</strong> whether testing is proceeding as planned.</p>
<p>Without a plan, it is nearly impossible to judge whether testing is heading in the right direction. No matter how skilled the testers are, their efforts are unlikely to yield proper results without a clear plan. This is precisely why the importance of the <strong>Plan</strong> phase is repeatedly emphasized in software testing. The test plan is both the starting point and the backbone of successful testing.</p>
<h3>테스트 계획, 왜 중요한가</h3>
<p>테스트 계획을 이렇게 꼼꼼하게 수립하는 데는 분명한 이유가 있다. 테스트 계획은 단순히 문서를 만드는 작업에 그치는 것이 아니라, 이후에 이루어지는 <strong>테스트 설계와 수행 전반의 기준점</strong>이 되기 때문이다. 계획서에 정의된 범위, 전략, 완료 기준 등을 토대로 테스트가 설계되고 실행되며, 테스트가 계획대로 제대로 진행되고 있는지를 <strong>모니터링하는 기반</strong> 역할도 바로 테스트 계획이 담당한다.</p>
<p>결국 계획 없이는 테스트가 올바른 방향으로 가고 있는지조차 판단하기 어렵다. 아무리 뛰어난 테스터가 있더라도 명확한 계획이 없다면 그 노력이 제대로 된 결과로 이어지기 힘들다. 이것이 바로 소프트웨어 테스트에서 <strong>Plan의 중요성</strong>을 거듭 강조하는 이유이며, 테스트 계획은 성공적인 테스트의 시작이자 전체를 관통하는 근간이라고 할 수 있다.</p>
<hr />
<h1>2️⃣ Risk Management Overview</h1>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9f150db0-3250-4bd4-9476-66e3501ed358.png" alt="" style="display:block;margin:0 auto" />

<h2>What is Risk?</h2>
<p>Earlier, we learned that the ISO/IEC/IEEE 29119 standard emphasizes a <strong>risk-based test strategy</strong> in the test planning process. Let us now examine what risk management is and how its process unfolds.</p>
<p>First, what is <strong>Risk</strong>? It is actually a concept we encounter in everyday life ; "there is a risk of rain today," "this investment carries a high risk," "there are safety risks on a construction site." As these examples show, risk is not a special concept; it refers to an <strong>uncertain event or situation that may occur in the future</strong>.</p>
<p>The same applies to software testing. Unexpected events can occur during test execution, and such uncertainties can affect the quality and outcomes of testing. This is why identifying and managing risks in advance is a core element of test planning.</p>
<h3>위험(Risk)이란 무엇인가</h3>
<p>앞서 테스트 계획을 수립하는 과정에서 ISO/IEC/IEEE 29119 표준이 <strong>위험 기반의 테스트 전략</strong>을 강조한다는 것을 배웠다. 그렇다면 본격적으로 위험 관리란 무엇인지, 그리고 그 프로세스는 어떻게 진행되는지 살펴보자.</p>
<p>먼저 <strong>위험(Risk)</strong> 이란 무엇일까. 사실 위험이라는 단어는 우리가 일상생활에서도 흔하게 접하는 개념이다. 예를 들어 "오늘 비가 올 위험이 있다", "이 투자는 위험 부담이 크다", "공사 현장에는 안전 위험이 존재한다"와 같이, 우리는 이미 다양한 맥락에서 위험이라는 개념을 자연스럽게 사용하고 있다. 이처럼 위험은 특별한 개념이 아니라, <strong>미래에 발생할 수 있는 불확실한 사건이나 상황</strong>을 가리키는 말이다.</p>
<p>소프트웨어 테스트에서도 마찬가지다. 테스트를 수행하는 과정에서도 예상치 못한 일들이 발생할 수 있고, 이러한 불확실성이 테스트의 품질과 결과에 영향을 미칠 수 있다. 그렇기 때문에 위험을 미리 파악하고 관리하는 것이 테스트 계획의 핵심이 되는 것이다. 이어서 위험 관리의 구체적인 프로세스를 살펴보도록 하자.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a7dbd22d-6b9f-47e7-995e-e59582c803fc.png" alt="" style="display:block;margin:0 auto" />

<h3>Risk vs. Issue. Potential Problem vs. Actual Problem</h3>
<p>To understand risk more clearly, let us compare two contrasting concepts: <strong>Potential Problem</strong> and <strong>Actual Problem</strong>.</p>
<p>A <strong>Potential Problem</strong> is something that has not yet occurred but may happen in the future; this is <strong>Risk</strong>. An <strong>Actual Problem</strong>, on the other hand, is a problem that has already occurred; this is an <strong>Issue</strong>. Schedule delays, budget overruns, major scope changes, quality defects discovered in production, and customer complaints are all examples of Issues that have already materialized.</p>
<p>In practice, many organizations and individuals operate primarily in <strong>Issue mode</strong>; reacting to problems as they arise, only to be consumed by the next issue in an endless cycle. We see this pattern repeatedly in real-world disasters and accidents: countermeasures are developed only after the incident has occurred, after lives have been lost and economic damage has been done. This is the hallmark of Issue-driven work.</p>
<p>Working more systematically means operating in <strong>Risk mode</strong>; anticipating potential problems before they occur and preparing countermeasures in advance. As discussed, the act of planning is inherently about preparing for future events that have not yet happened, which means <strong>plans must always incorporate Risk</strong>.</p>
<p>Ultimately, <strong>risk-based planning</strong> is not a concept limited to testing. In any project or work environment, the key to systematic management is shifting from reacting to Issues after they erupt to proactively identifying and preparing for Risks.</p>
<h3>Risk vs Issue, 잠재적 문제와 실제 문제</h3>
<p>위험(Risk)을 좀 더 명확하게 이해하기 위해 상반되는 두 개념을 비교해보자. 바로 <strong>Potential Problem(잠재적 문제)</strong> 과 <strong>Actual Problem(실제 문제)</strong> 이다.</p>
<p><strong>Potential Problem</strong>은 지금 당장 발생하지는 않았지만, 미래에 발생할 수도 있는 문제를 의미한다. 이것이 바로 <strong>Risk</strong>다. 반면 <strong>Actual Problem</strong>은 이미 발생한 문제, 즉 <strong>Issue</strong>를 가리킨다. 일정 지연, 예산 초과, 프로젝트의 대규모 변경, 품질 문제 발생, 고객 클레임 접수 등이 모두 이미 터진 Issue에 해당한다.</p>
<p>많은 조직과 개인이 실제로는 <strong>Issue 중심으로 일을 한다.</strong> 문제가 터지고 나서야 부랴부랴 대응하고, 또 다른 문제가 터지면 다시 그것을 처리하느라 바쁜 악순환이 반복되는 것이다. 우리 주변에서 일어나는 각종 재난이나 사고를 돌아봐도 마찬가지다. 사고가 발생하고 나서야 대책을 마련하고, 이미 소중한 생명과 막대한 경제적 손실이 발생한 뒤에야 제도가 바뀌는 모습을 우리는 너무나 자주 목격한다. 이것이 전형적인 Issue 중심의 일 처리 방식이다.</p>
<p>반면 보다 체계적으로 일을 한다는 것은 <strong>Risk 중심으로 일을 한다</strong>는 것을 의미한다. 문제가 터지기 전에 미리 잠재적인 위험을 예측하고, 그에 대한 대책을 사전에 마련하는 것이다. 앞서 배운 것처럼 계획을 세운다는 행위 자체가 아직 발생하지 않은 미래의 일들을 대비하는 것이기 때문에, <strong>계획 안에는 반드시 Risk가 포함되어 있어야 한다.</strong></p>
<p>결국 <strong>위험 기반으로 계획을 세우는 것</strong>은 단순히 테스트에만 국한된 이야기가 아니다. 어떤 프로젝트든, 어떤 업무든 체계적으로 관리하고자 한다면 Issue가 터난 후에 반응하는 방식에서 벗어나, Risk를 미리 식별하고 대비하는 방식으로 일하는 것이 핵심임을 반드시 기억하자.</p>
<h3>프로젝트에서의 위험(Risk) 정의</h3>
<p>프로젝트가 성공적으로 완료되기 위해서는 내외부 참여자, 예산, 시스템, 기술, 고객 등 수많은 구성 요소들이 유기적으로 잘 맞물려 돌아가야 한다. 그리고 일반적으로 프로젝트의 성공 여부는 <strong>품질(Quality), 비용(Cost), 납기(Delivery)</strong> 세 가지를 모두 만족했는지를 기준으로 판단한다.</p>
<p>여기서 위험(Risk)의 정의가 자연스럽게 도출된다. <strong>이 세 가지 요소 중 적어도 하나에라도 영향을 줄 수 있는 잠재적인(Potential) 이벤트 또는 상태</strong>를 바로 위험, 즉 Risk라고 정의한다.</p>
<p>중요한 것은 "줄 수 있는"이라는 표현에 담긴 <strong>잠재성</strong>이다. 실제로 영향을 준 것이 아니라, 영향을 줄 수도 있는 가능성만으로도 Risk로 간주한다는 점이다. 예를 들어 핵심 개발자의 이탈 가능성, 기술적 난이도로 인한 일정 지연 가능성, 요구사항의 잦은 변경 가능성 등이 모두 Risk에 해당한다. 아직 아무 일도 일어나지 않았지만, 그것이 현실이 되었을 때 프로젝트의 품질, 비용, 납기에 영향을 미칠 수 있다면 그 자체로 Risk인 것이다.</p>
<p>결국 프로젝트에서 위험을 관리한다는 것은, 이처럼 프로젝트의 성공을 위협할 수 있는 잠재적 요소들을 미리 식별하고 대비하는 활동이라고 할 수 있다.</p>
<hr />
<h3>2. Risk Management Process</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a5a8c57e-6fb2-4fc9-9b3d-3664d2c59bcc.png" alt="" style="display:block;margin:0 auto" />

<h3>Definition of Risk in a Project Context</h3>
<p>For a project to be completed successfully, numerous components, internal and external stakeholders, budget, systems, technology, and customers - must work together in harmony. Generally, the success of a project is judged by whether it satisfies all three of the following criteria: <strong>Quality (Q), Cost (C), and Delivery (D)</strong>.</p>
<h3>위험 관리 프로세스</h3>
<p>위험 관리 프로세스란 소프트웨어 프로젝트의 목표인 <strong>품질, 비용, 일정</strong>을 성공적으로 만족시키기 위해, 프로젝트에 존재하는 위험을 미리 식별하고 분석하여 대비해나가는 일련의 활동을 말한다.</p>
<p>이 프로세스는 크게 세 단계로 진행된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/438985c4-5435-4a8f-8335-71e0f0edaeb4.png" alt="" style="display:block;margin:0 auto" />

<h3>Risk Identification</h3>
<p>Risk identification is the activity of finding <strong>potential problems</strong> that could affect the achievement of project goals — quality, cost, and schedule. It involves continuously asking, "What risks could arise when performing this activity?" as the project plan is developed.</p>
<p>This is easier said than done, because without it being a habit, it is easy to skip over. Many people are accustomed to working through a to-do list and lack the practice of proactively looking for potential problems. However, failing to identify risks allows small risks to grow into Issues — what could have been contained at 1 grows into 10 and then 100.</p>
<h3>Methods for Identifying Risks</h3>
<p>There are three commonly used methods for systematically identifying risks.</p>
<p>The first is using a <strong>Risk Database</strong>. Organizations that manage risks well maintain a risk database containing records of past risks and how they were resolved. Referencing this database makes it easier to identify risks that could arise in the current project.</p>
<p>The second is using an <strong>Issue Database</strong>. Even organizations without a risk database typically have records of past issues — in the form of spreadsheets or meeting minutes. These past issues can recur in the next project, making them potential risks. An issue database alone can be sufficient for deriving current project risks.</p>
<p>The third is using a <strong>Risk Checklist</strong>. By reviewing a checklist item by item with Yes/No responses, potential risks for the project can be identified. Items marked "No" represent risk factors that could materialize.</p>
<h3>Risk Examples in a Testing Project</h3>
<p>To illustrate how risk identification works in practice, consider the following examples from a testing project.</p>
<p>The first is a <strong>sudden change in customer priorities</strong>. A situation may arise where, after a test strategy and test cases have already been developed, a customer suddenly requests that a specific feature be tested first. Although it has not happened yet, it is entirely plausible, and a response strategy should be prepared in advance.</p>
<p>The second is the <strong>risk associated with an external test outsourcing vendor</strong>. When an external vendor is contracted because internal resources are insufficient for testing, the vendor may go bankrupt or fail to fulfill the contract. This can lead to schedule delays and cost overruns, so contingency plans must be prepared.</p>
<p>The third is <strong>insufficient tester competency</strong>. A test team may include both experienced professionals and junior employees with limited testing experience. Testing performed without adequate competency can directly affect quality and must therefore be identified as a significant risk factor.</p>
<h3>Risk Identification Must Become a Habit</h3>
<p>Ultimately, the starting point of risk identification is simple: <strong>habitually asking "What risks could arise as I carry out this work?" whenever planning</strong>. By repeatedly leveraging past issue databases and checklists to uncover as many risks as possible, the capability to execute projects on a risk-based foundation will naturally develop over time.</p>
<h3>위험 식별(Risk Identification)</h3>
<p>위험 식별이란 프로젝트의 목표인 <strong>품질, 비용, 일정</strong>의 달성에 영향을 줄 수 있는 잠재적인 문제를 찾아내는 활동이다. 프로젝트 계획을 수립해나가면서 "이 활동을 할 때는 어떤 위험이 있을 수 있을까?"를 끊임없이 자문하는 과정이 바로 위험 식별이다.</p>
<p>이것이 말처럼 쉽지 않은 이유는 <strong>습관이 되어있지 않으면 자연스럽게 넘어가기 쉽기 때문이다.</strong> 많은 사람들이 해야 할 일의 목록만 생각하며 일을 처리하는 데 익숙하다 보니, 잠재적인 문제를 미리 찾는 연습이 부족한 경우가 많다. 하지만 위험 식별을 놓치면 작은 위험이 Issue로 번져 1로 막을 수 있었던 것이 10이 되고 100이 되는 상황을 맞이하게 된다.</p>
<h3>위험을 찾아내는 방법</h3>
<p>위험을 체계적으로 식별하기 위해 일반적으로 활용하는 방법은 크게 세 가지다.</p>
<p>첫 번째는 <strong>위험 DB 활용</strong>이다. 위험 관리를 잘 하는 조직이라면 과거에 발생했던 위험과 그 해결 방법이 기록된 위험 DB를 보유하고 있다. 이 DB를 참고하면 현재 프로젝트에서 발생할 수 있는 위험을 보다 수월하게 찾아낼 수 있다.</p>
<p>두 번째는 <strong>이슈 DB 활용</strong>이다. 위험 DB가 없는 조직이라도 과거에 발생한 문제들을 기록한 이슈 DB는 갖고 있는 경우가 많다. 엑셀 파일이나 회의록 형태로 남아있는 과거의 이슈들은 다음 프로젝트에서도 똑같이 발생할 수 있는 잠재적 위험이 된다. 즉, 이슈 DB만으로도 현재 프로젝트의 위험을 충분히 도출해낼 수 있다.</p>
<p>세 번째는 <strong>위험 체크리스트 활용</strong>이다. 체크리스트를 항목별로 Yes/No로 점검하며 이 프로젝트에서 발생 가능한 위험을 확인하는 방식이다. No로 표시된 항목들은 곧 발생할 수 있는 위험 요소가 된다.</p>
<h3>실제 테스트 프로젝트에서의 위험 예시</h3>
<p>위험 식별이 실제로 어떻게 이루어지는지 테스트 프로젝트를 예시로 살펴보자. 먼저 <strong>고객 우선순위의 갑작스러운 변경</strong>이다. 테스트 전략과 테스트 케이스를 이미 만들어 놓은 상황에서 고객이 갑자기 특정 기능을 먼저 테스트해달라고 요청하는 경우가 생길 수 있다. 아직 발생하지는 않았지만 충분히 일어날 수 있는 위험이며, 이에 대한 대응 전략을 미리 마련해두어야 한다.</p>
<p>다음으로 <strong>외부 테스트 아웃소싱 업체의 리스크</strong>다. 내부 인력만으로 테스트를 수행하기 어려워 외부 업체와 계약했을 때, 그 업체가 파산하거나 계약을 이행하지 못하는 상황이 발생할 수 있다. 이 경우 일정 지연과 비용 낭비로 이어질 수 있기 때문에 사전에 대처 방안을 준비해야 한다.</p>
<p>마지막으로 <strong>테스터의 역량 부족</strong>이다. 테스트 팀 안에는 전문가도 있지만 경험이 부족한 신입 직원도 있을 수 있다. 역량이 충분히 갖추어지지 않은 상태에서 테스트를 수행하면 품질에 직접적인 영향을 미칠 수 있으므로, 이 역시 중요한 위험 요소로 식별해야 한다.</p>
<h3>위험 식별, 습관이 되어야 한다</h3>
<p>결국 위험 식별의 출발점은 단순하다. <strong>계획을 세울 때 "이 일을 수행하다 보면 어떤 위험이 생길 수 있을까?"라는 질문을 습관적으로 던지는 것이다.</strong> 과거의 이슈 DB나 체크리스트를 적극 활용하여 최대한 많은 위험을 찾아내려는 노력을 반복하다 보면, 자연스럽게 위험에 기반하여 프로젝트를 수행하는 역량이 갖추어지게 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8ce69478-e302-4c13-a8e7-44eea8983eea.png" alt="" style="display:block;margin:0 auto" />

<h3>Risk Analysis</h3>
<p>Once risk identification has produced a large number of risk factors, the next step is <strong>risk analysis</strong>. It is not feasible to prepare countermeasures for all 20–30 identified risks, given the constraints of cost, schedule, and resources that every project faces. The core of risk analysis is therefore evaluating the magnitude of each identified risk to determine <strong>which risks to prioritize for management</strong>.</p>
<h3>Two Evaluation Criteria for Risk Analysis</h3>
<p>Risk magnitude is measured using two factors: <strong>probability of occurrence</strong> and <strong>impact</strong>.</p>
<p>Probability of occurrence refers to the likelihood that the risk will actually materialize. It is typically scored as 1 (Low) for below 30%, 2 (Medium) for 30%–80%, and 3 (High) for above 80%. Impact refers to how significantly the risk would affect Quality, Cost, and Delivery if it occurred, and is similarly scored as 1 (Low) for below 10%, 2 (Medium) for 10%–20%, and 3 (High) for above 20%.</p>
<h3>Deriving Risk Levels via a Risk Matrix</h3>
<p>By quantifying both criteria, they can be represented in a <strong>Risk Matrix</strong>. Risk levels are derived by multiplying or combining the probability and impact scores. For instance, a probability of 1 (Low) and impact of 1 (Low) results in a low-level risk, while a probability of 2 (Medium) and impact of 2 (Medium) results in a level 4 (Medium) risk. The example from the risk identification phase — a sudden change in customer priorities — would be evaluated as probability 2 (Medium) and impact 3 (High), resulting in a level 6 (High) risk.</p>
<h3>Why Risk Analysis is Essential</h3>
<p>Once the risk level hierarchy is established, it becomes clear which of the 20 identified risks require priority management. High-level risks are managed first, followed by medium-level risks. While it would be ideal to address every risk if time, cost, and resources were unlimited, projects always operate within constraints. Risk analysis is therefore an <strong>essential activity</strong> in systematic project management, ensuring that limited resources are focused on the most critical risks.</p>
<h3>위험 분석(Risk Analysis)</h3>
<p>위험 식별을 통해 수많은 위험 요소들이 도출되고 나면, 그 다음 단계는 <strong>위험 분석</strong>이다. 20~30개에 달하는 위험 요소 모두에 대해 대책을 마련하는 것은 현실적으로 불가능하다. 프로젝트에는 항상 비용, 일정, 자원과 같은 제약이 존재하기 때문이다. 따라서 식별된 위험들의 크기를 평가하여 어떤 위험을 중점적으로 관리할 것인지 <strong>우선순위를 정하는 것</strong>이 위험 분석의 핵심이다.</p>
<h3>위험 분석의 두 가지 평가 기준</h3>
<p>위험의 크기를 측정하기 위해서는 두 가지 요소를 평가한다. 바로 <strong>발생 확률</strong>과 <strong>영향도</strong>다.</p>
<p>발생 확률은 해당 위험이 실제로 일어날 가능성을 의미하며, 일반적으로 30% 이하는 1(하), 30%<del>80%는 2(중), 80% 이상은 3(상)과 같이 점수로 구분한다. 영향도는 위험이 발생했을 때 품질(Quality), 비용(Cost), 납기(Delivery)에 얼마나 큰 영향을 미치는지를 나타내며, 10% 이하는 1(하), 10%</del>20%는 2(중), 20% 이상은 3(상)으로 동일하게 점수화한다.</p>
<h3>위험 매트릭스를 통한 등급 산출</h3>
<p>이렇게 두 가지 기준을 점수화하면 이를 <strong>매트릭스</strong> 형태로 표현할 수 있다. 발생 확률과 영향도를 곱하거나 조합하여 위험의 등급을 산출하는 방식이다. 예를 들어 발생 확률이 1(하)이고 영향도가 1(하)이면 전체적으로 낮은 수준의 위험이 되고, 발생 확률이 2(중)이고 영향도가 2(중)이면 4(중) 수준의 위험이 된다. 앞서 위험 식별 단계에서 예시로 들었던 <strong>고객 우선순위의 갑작스러운 변경</strong>의 경우, 발생 확률 2(중)에 영향도 3(상)으로 평가되어 6(상) 수준의 <strong>고위험</strong>에 해당한다.</p>
<h3>위험 분석이 필수적인 이유</h3>
<p>이와 같이 위험 등급 체계를 정하고 나면 20개의 위험 요소 중 어떤 것을 중점적으로 관리해야 할지가 명확해진다. 고수준으로 분류된 위험들을 최우선으로 관리하고, 그 다음 순위로 중간 수준의 위험들을 관리하는 방식이다. 시간과 비용, 자원이 충분하다면 모든 위험에 대응할 수 있겠지만, 현실적으로 프로젝트는 항상 한정된 자원 안에서 운영된다. 그렇기 때문에 위험 분석을 통해 우선순위를 정하고 한정된 자원을 가장 중요한 위험에 집중적으로 투입하는 이 과정은 체계적인 프로젝트 관리에 있어 <strong>필수적인 활동</strong>이라고 할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/12be2760-a4fd-4372-8aeb-0e26c8a94d81.png" alt="" style="display:block;margin:0 auto" />

<h3>Risk Mitigation Identification</h3>
<p>Having identified and prioritized risks, the final step is <strong>developing countermeasures</strong>. Risk mitigation means preparing responses that either reduce the probability of a risk occurring or minimize its impact if it does occur, bringing it down to an acceptable level. There are four categories of risk mitigation strategies.</p>
<h3>1) Risk Avoidance</h3>
<p>This approach <strong>completely eliminates the possibility</strong> of a risk occurring. For example, performing integration testing using the <strong>Big Bang</strong> approach; integrating dozens or hundreds of modules all at once; makes it extremely difficult to trace which module caused a defect. To eliminate this risk from the outset, adopting an <strong>incremental integration test strategy</strong> instead of Big Bang is a classic example of risk avoidance. The key is creating an environment where the risk cannot occur in the first place.</p>
<h3>2) Risk Mitigation</h3>
<p>Rather than eliminating a risk entirely, this approach <strong>reduces it to an acceptable level</strong>. For example, when there is a risk that customer requirement priorities may suddenly change, the goal is to reduce the probability of that change from 50% to 10–20%. This can be achieved by <strong>engaging the customer actively from the early stages</strong> of development to lock down requirements as much as possible, thereby reducing the probability of change at the source.</p>
<h3>3) Risk Transference</h3>
<p>This approach does not solve the risk directly but instead <strong>transfers the responsibility or impact to another party</strong>. It is most commonly used when the probability is low but the potential damage is high. For example, to guard against the possibility of a test outsourcing vendor failing to fulfill the contract, requiring the vendor to purchase <strong>performance bond insurance</strong>, even at additional cost; transfers the financial risk to a third party. In practice, many organizations mandate performance bond insurance for inter-agency contracts. This is not an evasion of responsibility but a rational strategy for reducing the impact of risks that are difficult to manage internally.</p>
<h3>4) Risk Acceptance</h3>
<p>This approach <strong>accepts risks that fall within a tolerable level</strong>. For example, if testers lack sufficient competency but must be included in order to complete testing successfully, the risk can be accepted by investing in <strong>test training</strong> to build their skills over time. If it is judged that they will be able to perform adequately once trained, then the risk is accepted and managed accordingly.</p>
<hr />
<p>These four mitigation strategies must be selected appropriately based on the magnitude and nature of each risk identified and analyzed. Most importantly, the key is to understand what risk is, practice the three-phase <strong>risk management process of Identification → Analysis → Mitigation</strong> systematically, and use it to prevent small risks from becoming large Issues. The risk management concepts learned here will serve as a critical foundation when developing the <strong>risk-based test strategy</strong> covered in future sessions.</p>
<h3>위험 완화 방안 식별(Risk Mitigation Identification)</h3>
<p>위험을 식별하고 우선순위를 분석했다면, 이제 그 위험들에 대한 <strong>대책을 마련하는 단계</strong>가 남아있다. 위험 완화란 위험이 발생할 확률을 낮추거나, 발생하더라도 그 영향도를 최소화하여 허용 가능한 수준으로 낮추기 위한 대책을 마련하는 것이다. 위험 완화 방안은 크게 네 가지로 구분된다.</p>
<h3>1) 위험 회피 (Avoidance)</h3>
<p>위험이 발생할 가능성을 <strong>애초에 완전히 제거</strong>하는 방법이다. 예를 들어 통합 테스트를 빅뱅(Big Bang) 방식으로 진행하면, 수십~수백 개의 모듈을 한꺼번에 통합하기 때문에 문제가 발생했을 때 어느 모듈에서 비롯된 것인지 추적하기가 매우 어렵다. 이러한 위험을 처음부터 없애기 위해 빅뱅 방식 대신 <strong>점진적 통합 테스트 전략</strong>을 채택하는 것이 위험 회피의 대표적인 예다. 처음부터 위험이 발생할 수 없는 환경을 만드는 것이 핵심이다.</p>
<h3>2) 위험 완화 (Mitigation)</h3>
<p>위험을 완전히 없애는 것이 아니라, <strong>허용 가능한 수준 이하로 낮추는</strong> 방법이다. 예를 들어 고객의 요구사항 우선순위가 갑자기 변경될 위험이 있을 때, 그 변경 가능성을 50%에서 10~20%로 줄이는 것을 목표로 한다. 이를 위해 개발 후반부가 아닌 <strong>초기 단계부터 고객을 적극적으로 참여시켜 요구사항을 최대한 확정</strong>짓는 방식으로 위험의 발생 확률 자체를 낮출 수 있다.</p>
<h3>3) 위험 전이 (Transference)</h3>
<p>위험을 내가 직접 해결하는 것이 아니라, <strong>외부의 힘을 빌려 위험의 책임이나 영향을 다른 주체에게 이전</strong>하는 방법이다. 발생 확률은 낮지만 피해가 클 경우에 주로 활용된다. 예를 들어 테스트 아웃소싱 업체가 계약을 이행하지 못하는 상황에 대비하여, 비용이 다소 들더라도 업체가 <strong>이행보증보험에 가입</strong>하도록 하여 피해를 최소화하는 것이 대표적인 사례다. 실제 현업에서도 기관 간 계약 시 이행보증보험 가입을 의무화하는 경우가 많다. 이는 책임 회피가 아니라, 감당하기 어려운 위험의 영향도를 줄이기 위한 합리적인 선택이다.</p>
<h3>4) 위험 수용 (Acceptance)</h3>
<p><strong>허용 가능한 수준의 위험을 그대로 받아들이는</strong> 방법이다. 예를 들어 테스터의 역량이 부족하더라도, 해당 인력을 포함하여 테스트를 성공적으로 수행해야 하는 상황이라면 <strong>테스트 교육을 통해 역량을 키우는 것</strong>을 선택할 수 있다. 당장은 부족하더라도 시간이 지나면 충분히 수행 가능하다고 판단한다면, 그 위험을 감내하고 수용하는 것이다.</p>
<hr />
<p>이 네 가지 위험 완화 방안은 앞서 식별하고 분석한 위험의 크기와 성격에 따라 적절하게 선택되어야 한다. 무엇보다 중요한 것은 위험이 무엇인지 이해하고, 식별 → 분석 → 완화의 세 단계로 이루어진 위험 관리 프로세스를 체계적으로 실천하는 것이다. 이를 통해 나중에 큰 Issue로 번질 수 있는 문제들을 사전에 차단하고, 프로젝트를 안정적으로 이끌어 나갈 수 있다. 앞으로 학습할 <strong>위험 기반의 테스트 전략</strong> 수립에서도 오늘 배운 위험 관리의 개념과 프로세스가 핵심적인 토대로 활용될 것이다.</p>
<hr />
<h1>3️⃣ Risk-based testing strategy</h1>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/208c18c5-8c5a-46bd-8375-f271a430c92e.png" alt="" style="display:block;margin:0 auto" />

<h3>What is Risk-Based Testing?</h3>
<p><strong>Risk-Based Testing</strong> is a strategy that identifies potential problems that could arise in a project and applies the risk management process; <strong>Risk Identification → Analysis → Mitigation</strong>; integrated into the testing effort.</p>
<p>Just as resources are constrained in a project, testing resources - cost, tools, equipment, and personnel — are always limited. In this environment, the essence of the risk-based test strategy is to <strong>invest available resources most efficiently based on risk</strong>, in order to achieve the testing objective of discovering as many defects as possible.</p>
<h3>위험 기반 테스트(Risk-Based Test)란?</h3>
<p>위험 기반 테스트란 프로젝트에서 발생할 수 있는 잠재적인 문제들을 찾아내고, 위험 관리 프로세스인 <strong>위험 식별 → 분석 → 완화</strong>의 과정을 테스트에 융합하여 적용하는 전략이다.</p>
<p>프로젝트에서 자원이 제한되어 있듯이, 테스트에서도 비용, 도구, 장비, 인원은 항상 제한되어 있다. 이러한 환경에서 가지고 있는 자원을 가장 효율적으로 활용하기 위해, 다양한 결함을 발견한다는 테스트의 목표를 달성하기 위해 위험에 기반하여 자원을 집중적으로 투입하는 것이 바로 위험 기반 테스트 전략의 본질이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f48ada15-0c3f-4f81-97dd-8ba69eddd1b6.png" alt="" style="display:block;margin:0 auto" />

<h3>Goals Achievable Through Risk-Based Testing</h3>
<p>There are three key benefits expected from risk-based testing.</p>
<p>The first is <strong>improved software product quality</strong>. By identifying and analyzing risks and establishing priorities, the factors most likely to contribute to product defects are naturally surfaced. Focusing testing efforts on these factors naturally elevates product quality.</p>
<p>The second is <strong>improved overall test coverage</strong>. Concentrating on higher-priority areas means testing the features that customers and stakeholders care about most first. This ensures meaningful test coverage where it matters most.</p>
<p>The third is <strong>improved test efficiency</strong>. By directing limited resources toward high-risk areas, greater test effectiveness is achieved with the same resources. Risk-based testing is ultimately the most rational test strategy for <strong>achieving maximum quality impact with limited resources</strong>.</p>
<h3>위험 기반 테스트를 통해 얻을 수 있는 목표</h3>
<p>위험 기반 테스트를 통해 기대할 수 있는 효과는 크게 세 가지다.</p>
<p>첫 번째는 <strong>소프트웨어 제품 품질 향상</strong>이다. 위험을 식별하고 분석하여 우선순위를 정하다 보면 자연스럽게 제품의 결함에 가장 큰 영향을 미칠 수 있는 요소를 찾게 된다. 이 요소들을 중점적으로 테스트함으로써 제품의 품질이 자연스럽게 높아진다.</p>
<p>두 번째는 <strong>전체 테스트 커버리지 개선</strong>이다. 우선순위가 낮은 부분보다 높은 부분에 집중한다는 것은, 고객과 이해관계자들이 가장 중요하게 생각하는 영역을 먼저 충분히 테스트한다는 의미다. 이를 통해 실질적으로 의미 있는 테스트 커버리지를 확보할 수 있다.</p>
<p>세 번째는 <strong>테스트 효율성 향상</strong>이다. 한정된 자원을 위험 수준이 높은 영역에 집중 투입함으로써, 같은 자원으로 더 큰 테스트 효과를 얻을 수 있다. 결국 위험 기반 테스트는 "적은 자원으로 최대의 품질 효과를 내기 위한" 가장 합리적인 테스트 전략이라고 할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4004e2fe-11d2-4efe-b915-8e095194334e.png" alt="" style="display:block;margin:0 auto" />

<h3>Risk Analysis Criteria from a Testing Perspective</h3>
<p>Risk analysis involves evaluating the <strong>probability of occurrence</strong> and <strong>impact</strong> of identified risks to establish priorities. Let me examine how these two criteria are applied specifically from a testing perspective.</p>
<h3>Probability of Occurrence; Likelihood of Defects</h3>
<p>In a testing context, probability of occurrence refers to the <strong>likelihood that defects will arise during testing</strong>. The following factors contribute to this likelihood.</p>
<p>First is <strong>source code complexity</strong>. Code with nested loops inside conditionals, or so-called spaghetti code, is structurally complex and more prone to defects. <strong>Inter-module coupling complexity</strong> is also significant — the more tightly interdependent the modules, the more likely unexpected defects are to emerge. <strong>Implementation technology difficulty</strong> is another contributing factor. <strong>Lines of Code (LOC)</strong> also matter — the more lines of code, the greater the likelihood of defects. Finally, <strong>developer competency</strong> cannot be overlooked; the difference in skill between junior, mid-level, and senior developers directly affects defect probability.</p>
<h3>Impact ; Effect on Business When a Defect Occurs</h3>
<p>Impact refers to <strong>how significantly a functional failure would affect the business</strong>. The factors that compose impact include the following.</p>
<p>First is <strong>user criticality</strong> — how important the function is to the user determines the degree of impact. <strong>Economic and safety damage</strong> is also a key criterion; if a failure leads to financial loss or safety incidents, the impact is correspondingly high. <strong>Damage to the organization's reputation</strong> must also be considered — incidents involving hacking, payment failures, or authentication issues can severely undermine organizational credibility. Finally, <strong>frequency of use</strong> contributes to impact; the more frequently a function is used, the more people are affected when a defect occurs.</p>
<h3>Risk Exposure</h3>
<p>By comprehensively evaluating the factors composing probability and impact, the magnitude of each risk is classified as Low, Medium, or High. This is referred to as <strong>Risk Exposure</strong>, and it determines the priority of which areas to focus testing on. By concentrating test resources on areas with high Risk Exposure, the most effective testing possible is achieved within the constraints of available resources.</p>
<h3>테스트 관점에서의 위험 분석 기준</h3>
<p>위험 분석은 식별된 위험들에 대해 <strong>발생 확률</strong>과 <strong>영향도</strong>를 평가하여 우선순위를 정하는 과정이다. 테스트 측면에서 이 두 가지 기준을 어떻게 적용하는지 구체적으로 살펴보자.</p>
<h3>발생 확률; 결함이 생길 가능성</h3>
<p>테스트에서의 발생 확률이란 <strong>테스트를 수행할 때 결함이 발생할 가능성</strong>을 의미한다. 즉, 어떤 프로그램이나 모듈에서 결함이 많이 생길 것인가를 따져보는 것이다. 이에 영향을 미치는 요소들은 다음과 같다.</p>
<p>먼저 <strong>소스코드의 복잡성(Complexity)</strong> 이다. 분기문 안에 반복문이 중첩되어 있거나 이른바 스파게티 코드처럼 구조가 복잡할수록 결함이 발생할 가능성이 높아진다. 또한 <strong>모듈 간 상호 관계의 복잡성</strong>도 중요한 요소인데, 여러 모듈이 얽히고 설킨 구조일수록 예상치 못한 결함이 생기기 쉽다. <strong>구현 기술의 난이도</strong>가 높은 경우도 마찬가지다. 그리고 <strong>코드의 규모(Lines of Code)</strong> 도 중요한데, 코드 라인이 많아질수록 그만큼 결함이 발생할 가능성도 함께 커진다. 마지막으로 <strong>개발자의 역량</strong>도 빼놓을 수 없다. 초급, 중급, 고급 개발자의 실력 차이는 결함 발생 확률에 직접적인 영향을 미친다.</p>
<h3>영향도; 결함 발생 시 비즈니스에 미치는 영향</h3>
<p>영향도란 <strong>기능 장애가 발생했을 때 비즈니스 전반에 얼마나 큰 영향을 미치는가</strong>를 평가하는 기준이다. 영향도를 구성하는 요소들도 다양하다.</p>
<p>먼저 <strong>사용자의 취급 중요도</strong>다. 사용자가 해당 기능을 얼마나 중요하게 여기는지에 따라 영향도가 달라진다. <strong>경제적·안전적 피해</strong> 또한 중요한 기준이다. 기능 고장이 발생했을 때 금전적 손실이나 안전 문제로 이어진다면 그만큼 영향도가 높다. <strong>조직의 대외 이미지 피해</strong>도 빼놓을 수 없는데, 해킹이나 결제·인증 문제가 발생하면 조직의 신뢰도에 큰 타격을 줄 수 있다. 마지막으로 <strong>기능 사용 빈도</strong>도 영향도에 기여한다. 사용자가 자주 쓰는 기능일수록 결함이 발생했을 때 더 많은 사람들에게 영향을 미치기 때문이다.</p>
<h3>위험 노출도(Risk Exposure)</h3>
<p>이처럼 발생 확률과 영향도를 구성하는 요소들을 종합적으로 평가하면, 각 위험 요소의 크기가 <strong>저, 중, 고 수준</strong>으로 산출된다. 이를 <strong>위험 노출도(Risk Exposure)</strong> 라고 하며, 이 수치를 기반으로 어떤 영역을 중점적으로 테스트할 것인지 우선순위가 결정된다. 결국 테스트 자원을 위험 노출도가 높은 영역에 집중 투입함으로써, 한정된 자원 안에서 가장 효과적인 테스트를 수행할 수 있게 되는 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4046b471-43cc-4896-b274-ecaf7a0689d2.png" alt="" style="display:block;margin:0 auto" />

<h2>Example: Risk Analysis; Vaccine Appointment System</h2>
<p>Let me examine how risk analysis works in practice using a <strong>vaccine appointment booking system</strong>.</p>
<p>The main requirements of this system include functions such as member registration, member withdrawal, login, logout, and vaccine appointment booking. For each function, the probability of defects occurring and the impact on the business if they do are evaluated on a scale of 1–5, and the two values are multiplied to derive the risk magnitude. It is important to note that this evaluation must be performed by <strong>domain experts</strong> familiar with the system.</p>
<p>For example, the <strong>member registration</strong> function is evaluated with a probability of 3 and impact of 4, resulting in a risk magnitude of <strong>12</strong>. The <strong>member withdrawal</strong> function scores a probability of 2 and impact of 1, yielding a risk magnitude of only <strong>2</strong>. The <strong>login</strong> function, on the other hand, scores a probability of 4 and impact of 5, resulting in the highest risk magnitude of <strong>20</strong>. This is because if login is unavailable, all core system functions — appointment booking, inquiry, and cancellation — become completely inaccessible, warranting the highest impact rating.</p>
<p>By deriving risk magnitudes for all requirements in this way, it becomes clearly apparent which functions are high-risk and which are low-risk. This enables the development of a <strong>differentiated test strategy</strong> — concentrating more test resources on high-risk functions and allocating relatively fewer resources to low-risk ones. Risk analysis is ultimately a rational decision-making tool for distributing limited test resources most effectively.</p>
<h3>[예시] 위험 분석 - 백신 접종 예약 시스템</h3>
<p>위험 분석이 실제로 어떻게 이루어지는지 <strong>백신 접종 예약 시스템</strong>을 예시로 살펴보자.</p>
<p>이 시스템의 주요 요구사항으로는 회원 가입, 회원 탈퇴, 로그인, 로그아웃, 접종 예약 등의 기능이 있다. 각 기능에 대해 결함이 발생할 확률과 발생 시 비즈니스에 미치는 영향도를 1~5점 척도로 평가하고, 두 값을 곱하여 위험의 크기를 산출한다. 이 평가는 반드시 해당 시스템과 관련된 <strong>전문가가 수행</strong>해야 한다는 점이 중요하다.</p>
<p>예를 들어 <strong>회원 가입</strong> 기능은 발생 확률 3, 영향도 4로 평가되어 위험의 크기가 <strong>12</strong>가 된다. <strong>회원 탈퇴</strong> 기능은 발생 확률 2, 영향도 1로 위험의 크기가 <strong>2</strong>에 그친다. 반면 <strong>로그인</strong> 기능은 발생 확률 4, 영향도 5로 위험의 크기가 <strong>20</strong>으로 가장 높게 산출된다. 로그인이 불가능하면 예약, 조회, 취소 등 시스템의 모든 핵심 기능을 아예 사용할 수 없게 되기 때문에 영향도가 최고 수준으로 평가된 것이다.</p>
<p>이처럼 모든 요구사항 항목에 대해 동일한 방식으로 위험의 크기를 산출하면, 어떤 기능이 고위험이고 어떤 기능이 저위험인지가 명확하게 드러난다. 이를 토대로 위험의 크기가 큰 기능에는 더 많은 테스트 자원을 집중하고, 위험의 크기가 작은 기능에는 상대적으로 적은 자원을 투입하는 <strong>차별화된 테스트 전략</strong>을 수립할 수 있다. 결국 위험 분석은 한정된 테스트 자원을 가장 효과적으로 배분하기 위한 합리적인 의사결정 도구인 셈이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/637fcff4-ed7c-4d83-8625-6328baa17a0f.png" alt="" style="display:block;margin:0 auto" />

<h3>Example: Unit Test Planning Based on Risk Analysis</h3>
<p>Let me examine how risk analysis results are applied to actual test planning, again using the vaccine appointment booking system.</p>
<h3>Risk Level Classification</h3>
<p>Requirements are classified into four risk levels based on the scores derived from risk analysis. <strong>Level 1</strong> represents the highest risk area with both high probability and high impact. <strong>Level 2</strong> has low probability but high impact. <strong>Level 3</strong> has high probability but low impact. <strong>Level 4</strong> has both low probability and low impact.</p>
<p>Applying this to the vaccine appointment system: <strong>login, vaccine appointment, member registration, and appointment inquiry</strong> are classified as <strong>Level 1 (High Risk)</strong> with both high probability and high impact. <strong>Appointment cancellation</strong> falls under <strong>Level 2</strong>, while <strong>member withdrawal</strong> and <strong>logout</strong> are classified as <strong>Level 3</strong> due to their relatively low risk magnitude.</p>
<h3>Exit Criteria by Risk Level</h3>
<p>These risk classifications are directly used to <strong>differentiate the exit criteria and test methods</strong> when developing the test plan.</p>
<p>For <strong>Level 1 high-risk items</strong>, the strictest criteria are applied. To minimize the likelihood of defects and reduce their impact if they do occur, the most rigorous coverage criterion — <strong>MC/DC (Modified Condition/Decision Coverage) at 100% completion</strong> — is set as the exit criterion. The goal is to test as wide a range of code as possible, leaving no room for hidden defects.</p>
<p>For <strong>Level 2 items</strong>, while not as strict as MC/DC, <strong>branch and condition coverage at 100% completion</strong> is set as the exit criterion. Sufficient testing is performed given the high impact, but with somewhat more flexibility than Level 1.</p>
<p>For <strong>Level 3 low-risk items</strong>, since the impact is relatively limited, <strong>statement coverage at 100% completion</strong> is set as the exit criterion, allowing for efficient allocation of test resources.</p>
<h3>Significance of Risk-Based Test Planning</h3>
<p>By incorporating risk analysis results into the test plan, strict criteria are applied to critical functions while appropriate criteria are applied to less critical ones, enabling the <strong>most effective use of limited resources</strong>. For instance, if the login function has a defect, all core system functions are paralyzed, customers stop using the service, and the company faces significant losses. In contrast, defects in Level 3 functions such as member withdrawal or logout have relatively limited impact. Ultimately, a risk-based test plan is a rational approach that addresses the greatest risks first and most thoroughly, <strong>effectively enhancing software product quality</strong>.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/297cb98a-78e6-496e-ab6b-678595745c98.png" alt="" style="display:block;margin:0 auto" />

<h3>Risk Factor-Based Risk Analysis Example</h3>
<p>Going one step further from the risk analysis method discussed earlier, let me examine an approach that <strong>breaks down risk factors by requirement</strong> for more granular analysis.</p>
<p>The basic structure remains the same; probability and impact are evaluated per requirement ; but here, risk is broken down into the following sub-categories for more systematic analysis:</p>
<ul>
<li><p><strong>LOP</strong> (Loss of Power)</p>
</li>
<li><p><strong>CFD</strong> (Corrupted File Data)</p>
</li>
<li><p><strong>UUA</strong> (Unauthorised User Access)</p>
</li>
<li><p><strong>DNS</strong> (Database Not Synchronized)</p>
</li>
<li><p><strong>UUD</strong> (Unclear User Documentation)</p>
</li>
<li><p><strong>ST</strong> (Slow Throughput)</p>
</li>
</ul>
<p>Each requirement is assessed against these risk factors, scores are assigned, and all scores are summed to derive the final risk magnitude for that requirement. For example, comparing Requirement 1 and Requirement 2, Requirement 2 scores <strong>67 points</strong>, indicating a higher overall risk. This leads to the conclusion that more test resources should be concentrated on functions related to Requirement 2.</p>
<p>This approach goes beyond simply evaluating probability and impact, by itemizing specific risk factors for systematic review, it enables a more <strong>rigorous and precise risk analysis</strong>.</p>
<hr />
<p>This concludes our examination of what risk is, how the risk management process is structured, and why this analysis is indispensable for developing a test strategy. Ultimately, a <strong>risk-based test strategy</strong> is an approach that incorporates all of these processes into the test plan, focusing on the most critical risks within limited resources to effectively improve software quality. The risk management concepts learned here will continue to serve as a critical foundation in the upcoming phases of test design and execution.</p>
<h3>[예시]위험 분석 기반의 단위 테스트 계획 수립 예시</h3>
<p>위험 분석 결과를 실제 테스트 계획에 어떻게 반영하는지 백신 접종 예약 시스템을 통해 살펴보자.</p>
<h3>위험 등급 분류</h3>
<p>위험 분석을 통해 산출된 점수를 기반으로 요구사항들을 네 가지 등급으로 분류한다. <strong>1등급</strong>은 발생 확률과 영향도가 모두 높은 최고위험 영역이고, <strong>2등급</strong>은 발생 확률은 낮지만 영향도가 높은 영역이다. <strong>3등급</strong>은 발생 확률은 높지만 영향도가 낮은 영역이며, <strong>4등급</strong>은 발생 확률과 영향도가 모두 낮은 저위험 영역이다.</p>
<p>백신 접종 예약 시스템에 적용하면, <strong>로그인, 접종 예약, 회원 가입, 예약 조회</strong>는 발생 확률과 영향도가 모두 높은 <strong>1등급 고위험</strong> 항목에 해당한다. <strong>예약 취소</strong>는 <strong>2등급</strong>에 해당하며, <strong>회원 탈퇴와 로그아웃</strong>은 위험의 크기가 상대적으로 작아 <strong>3등급</strong>으로 분류된다.</p>
<h3>등급별 테스트 완료 기준</h3>
<p>이렇게 분류된 위험 등급은 테스트 계획을 수립할 때 <strong>완료 기준과 테스트 방법을 차별화</strong>하는 데 직접적으로 활용된다.</p>
<p><strong>1등급 고위험 항목</strong>에 대해서는 가장 엄격한 기준을 적용한다. 결함의 발생 가능성을 최대한 줄이고 결함이 발생하더라도 그 영향도를 낮추어야 하기 때문에, 가장 높은 수준의 커버리지 기준인 <strong>MCDC 커버리지 100% 완료</strong>를 완료 기준으로 삼는다. 최대한 넓은 범위의 코드를 빠짐없이 테스트하여 결함이 숨어있을 가능성을 철저히 차단하는 것이다.</p>
<p><strong>2등급 항목</strong>에 대해서는 MCDC만큼 엄격하지는 않더라도, <strong>분기 및 조건 커버리지 100% 완료</strong>를 완료 기준으로 설정한다. 영향도가 높은 만큼 충분한 수준의 테스트를 수행하되, 1등급보다는 다소 유연한 기준을 적용하는 것이다.</p>
<p><strong>3등급 저위험 항목</strong>은 상대적으로 영향도가 작기 때문에 <strong>문장 커버리지 100% 완료</strong> 정도를 완료 기준으로 삼아 테스트 자원을 효율적으로 배분한다.</p>
<h3>위험 기반 테스트 계획의 의의</h3>
<p>이처럼 위험 분석 결과를 테스트 계획에 반영하면, 중요한 기능에는 타이트한 기준을 적용하고 그렇지 않은 기능에는 적절한 수준의 기준을 적용하여 <strong>한정된 자원을 가장 효과적으로 활용</strong>할 수 있다. 예를 들어 로그인 기능에 결함이 발생한다면 예약, 조회, 취소 등 시스템의 모든 핵심 기능이 마비되어 고객은 서비스 이용을 멈추게 되고, 회사 입장에서는 막대한 손실로 이어질 수 있다. 반면 회원 탈퇴나 로그아웃과 같은 3등급 기능에 결함이 발생하더라도 그 영향은 상대적으로 제한적이다. 결국 위험 기반 테스트 계획은 가장 큰 위험을 가장 먼저, 가장 철저하게 다루어 <strong>소프트웨어 제품의 품질을 효과적으로 높이는</strong> 합리적인 접근 방식이라고 할 수 있다.</p>
<hr />
<h3>위험 요인 기반의 위험 분석 예시</h3>
<p>앞서 살펴본 위험 분석 방법에서 한 단계 더 나아가, <strong>요구사항별 위험 요인(Risk Factor)을 세분화하여 분석하는 방법</strong>을 살펴보자.</p>
<p>기본적인 구조는 동일하다. 요구사항 항목별로 발생 확률과 영향도를 평가하되, 여기서는 위험을 보다 체계적으로 분석하기 위해 위험 요인을 아래와 같이 소분류로 세분화한다.</p>
<ul>
<li><p><strong>LOP</strong> (Loss of Power) — 전력 손실</p>
</li>
<li><p><strong>CFD</strong> (Corrupted File Data) — 파일 데이터 손상</p>
</li>
<li><p><strong>UUA</strong> (Unauthorised User Access) — 비인가 사용자 접근</p>
</li>
<li><p><strong>DNS</strong> (Database Not Synchronized) — 데이터베이스 미동기화</p>
</li>
<li><p><strong>UUD</strong> (Unclear User Documentation) — 불명확한 사용자 문서</p>
</li>
<li><p><strong>ST</strong> (Slow Throughput) — 느린 처리 속도</p>
</li>
</ul>
<p>각 요구사항에 대해 이 위험 요인들을 하나씩 점검하고 점수를 부여한 뒤, 모든 점수를 합산하여 해당 요구사항의 최종 위험 크기를 산출한다. 예를 들어 요구사항 1번과 2번을 비교했을 때, 요구사항 2번이 <strong>67점</strong>으로 더 높은 위험 크기를 가지고 있어 더 큰 위험을 내포하고 있음을 알 수 있다. 이를 통해 요구사항 2번과 관련된 기능에 더 많은 테스트 자원을 집중해야 한다는 판단을 내릴 수 있다.</p>
<p>이 방식은 단순히 발생 확률과 영향도만을 평가하는 것에서 벗어나, <strong>구체적인 위험 요인을 항목화하여 빠짐없이 점검</strong>할 수 있다는 점에서 보다 체계적이고 정밀한 위험 분석이 가능하다.</p>
<hr />
<p>여기까지 위험이 무엇인지, 위험 관리 프로세스가 어떻게 구성되는지, 그리고 왜 이 분석이 테스트 전략 수립에 반드시 필요한지를 살펴보았다. 결국 위험 기반 테스트 전략이란 이 모든 과정을 테스트 계획에 녹여내어, 한정된 자원 안에서 가장 중요한 위험에 집중함으로써 소프트웨어의 품질을 효과적으로 높이는 접근 방식이다. 앞으로 배울 테스트 설계와 수행 단계에서도 오늘 학습한 위험 관리의 개념이 핵심적인 기반으로 계속 활용될 것이다.</p>
]]></content:encoded></item><item><title><![CDATA[Processes in Operating Systems]]></title><description><![CDATA[1️⃣ Concept and Creation of Processors2️⃣ Process State and Management3️⃣ Process Execution and Control

1️⃣ Concept and Creation of Processors


How Does an Operating System Manage Running Tasks?
Whe]]></description><link>https://heesu.tech/processes-in-operating-systems</link><guid isPermaLink="true">https://heesu.tech/processes-in-operating-systems</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Fri, 27 Mar 2026 06:57:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9e2e3fa6-64a4-4dfa-b1ba-665a5d37c3ba.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>1️⃣ Concept and Creation of Processors<br />2️⃣ Process State and Management<br />3️⃣ Process Execution and Control</p>
<hr />
<h1>1️⃣ Concept and Creation of Processors</h1>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/befe296a-c710-46b2-9db7-46bd2443e882.png" alt="" style="display:block;margin:0 auto" />

<h3>How Does an Operating System Manage Running Tasks?</h3>
<p>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.</p>
<p>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.</p>
<h3>운영체제는 실행 중인 작업을 어떤 단위로 관리할까?</h3>
<p>컴퓨터를 사용하다 보면 웹브라우저, 메신저, 음악 재생 프로그램을 동시에 켜두는 일이 자연스럽다. 사용자 입장에서는 여러 프로그램이 그냥 한꺼번에 돌아가는 것처럼 보이지만, 운영체제 입장에서는 이 상황을 훨씬 세밀하게 들여다보고 있다.</p>
<p>그렇다면 운영체제는 이 실행들을 도대체 어떤 단위로 구분하고 관리하는 걸까? 이 질문에 바로 답을 내놓기보다는, 답이 자연스럽게 나올 수 있도록 개념을 하나씩 쌓아가 보자. 이 큰 흐름을 먼저 잡아두면, 나중에 배우게 될 스케줄링이나 동기화 같은 주제들도 훨씬 수월하게 이해할 수 있을 것이다.</p>
<hr />
<h2>1. The Basic Concept of a Process</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4af34c27-d557-45a2-8b73-60cfe55d8a3a.png" alt="" style="display:block;margin:0 auto" />

<h3>What's the Difference Between a Program and a Process?</h3>
<p>In everyday conversation, "program" and "process" are often used interchangeably. In operating systems, however, the two concepts are clearly distinct.</p>
<p>A <strong>program</strong> 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.</p>
<p>A <strong>process</strong>, 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.</p>
<p>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.</p>
<h3>1. 프로세스 기본 개념</h3>
<h3>프로그램과 프로세스, 무엇이 다를까?</h3>
<p>일상적인 대화에서는 '프로그램'과 '프로세스'를 크게 구분하지 않고 쓰는 경우가 많다. 하지만 운영체제에서는 이 두 개념이 명확하게 갈린다.</p>
<p><strong>프로그램</strong>은 저장장치에 저장된 파일, 즉 아직 실행되지 않은 정적인 상태를 말한다. 쉽게 말해 실행될 준비는 됐지만 아직 아무것도 하고 있지 않은 코드 덩어리다.</p>
<p>반면 <strong>프로세스</strong>는 그 프로그램이 메모리에 올라가 실제로 실행되고 있는 상태를 말한다. 프로세스가 되는 순간부터 운영체제는 CPU와 메모리 같은 시스템 자원을 해당 실행에 할당하고, 이를 하나의 독립적인 관리 대상으로 다루기 시작한다.</p>
<p>정리하면 이렇다. 실행 여부를 기준으로 보면, 프로그램은 실행되지 않은 상태이고 프로세스는 CPU를 할당받아 실제로 실행 중인 상태다. 자원 사용 면에서도 차이가 있다. 프로그램은 자원을 전혀 사용하지 않지만, 프로세스는 실행되면서 CPU와 메모리 같은 시스템 자원을 실제로 소비한다.</p>
<hr />
<h3>One Program, Multiple Processes</h3>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>하나의 프로그램, 여러 개의 프로세스</h3>
<p>여기서 한 가지 중요한 포인트가 있다. 하나의 프로그램이라도 여러 번 실행되면, 그 실행마다 서로 다른 프로세스가 만들어진다는 것이다.</p>
<p>예를 들어 같은 웹브라우저를 두 번 실행했다고 하자. 디스크에 저장된 프로그램 파일은 하나지만, 운영체제는 각각의 실행을 별개의 프로세스로 인식하고 따로따로 관리한다. 파일은 하나여도 실행 중인 인스턴스는 둘인 셈이다.</p>
<p>이 개념이 중요한 이유는, 앞으로 운영체제가 관리하는 기본 단위가 '프로그램'이 아니라 바로 '프로세스'라는 점을 기억해야 하기 때문이다. 스케줄링도, 메모리 할당도, 동기화도 모두 프로세스를 중심으로 돌아간다.</p>
<hr />
<h3>Seeing Processes in Action Through Task Manager</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e9e9174f-91cd-4ec3-b1f2-8709b1765180.png" alt="" style="display:block;margin:0 auto" />

<p>There's a way to make the concept of the operating system managing execution in process units feel more concrete. That's <strong>Windows Task Manager.</strong></p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>작업 관리자로 확인하는 프로세스의 실체</h3>
<p>운영체제가 프로세스 단위로 실행을 관리한다는 개념을 조금 더 실감 나게 확인할 수 있는 방법이 있다. 바로 윈도우의 작업 관리자다.</p>
<p>작업 관리자를 열면 현재 컴퓨터에서 실행 중인 프로세스들이 목록 형태로 쭉 나열된다. 이 목록의 항목 하나하나가 바로 운영체제가 관리하는 실행 단위, 즉 프로세스다. 내가 직접 실행한 프로그램도 있고, 사용자 눈에는 보이지 않지만 운영체제가 내부적으로 돌리고 있는 작업들도 함께 포함되어 있다.</p>
<p>여기서 흥미로운 점이 있다. 화면에 띄워 놓은 창이 몇 개 없더라도, 작업 관리자를 열어보면 실제로는 수십 개의 프로세스가 동시에 동작하고 있다는 걸 확인할 수 있다. 사용자가 인식하는 실행의 범위와 운영체제가 실제로 관리하는 실행의 범위가 전혀 다르다는 뜻이다.</p>
<p>작업 관리자는 이 사실을 눈으로 직접 확인시켜 주는 창구다. 운영체제의 실행은 단순히 하나의 흐름이 아니라, 여러 개의 실행 단위로 나뉘어 동시에 관리되고 있다는 것을 이 화면을 통해 직관적으로 이해할 수 있다.</p>
<hr />
<h3>The Moment a Program Becomes a Process</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9b7c7d5f-703b-43d3-8b61-86843469cf12.png" alt="" style="display:block;margin:0 auto" />

<p>To understand the relationship between a program and a process, it helps to trace the flow of execution step by step.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>프로그램이 프로세스가 되는 순간</h3>
<p>프로그램과 프로세스의 관계를 이해하려면 실행이 일어나는 흐름을 단계별로 따라가 보는 것이 좋다.</p>
<p>시작은 디스크다. 디스크에는 프로그램이 파일의 형태로 저장되어 있다. 이 프로그램은 코드와 정적인 데이터로 이루어진 실행 파일로, 이 상태에서는 아직 아무것도 실행되고 있지 않다. CPU를 사용하는 것도 아니고, 메모리를 점유하는 것도 아닌, 그냥 디스크 위에 가만히 놓인 파일일 뿐이다.</p>
<p>여기서 사용자가 프로그램을 실행하면 상황이 바뀐다. 디스크에 있던 프로그램이 메모리로 올라오게 되고, 메모리에 적재되어 실행 가능한 상태가 된 것을 바로 프로세스라고 부른다. 그리고 이 프로세스가 CPU를 할당받으면서 비로소 실제 연산이 시작된다.</p>
<p>다시 한번 정리하면 이렇다. 프로그램은 디스크에 저장된 실행 파일이고, 프로세스는 그 프로그램이 메모리에 적재되어 실행 중인 상태다. 같은 프로그램이라도 여러 번 실행하면 메모리에는 그만큼 서로 다른 프로세스가 생성될 수 있다.</p>
<p>이 흐름에서 핵심은 딱 하나다. 프로그램이 메모리에 적재되는 바로 그 순간, 운영체제의 관리 대상인 프로세스로 전환된다는 것이다. 그 전까지는 그저 저장된 파일에 불과하지만, 메모리에 올라오는 순간부터 운영체제는 이를 독립적인 실행 단위로 인식하고 CPU와 메모리 같은 자원을 할당하며 관리하기 시작한다.</p>
<hr />
<h2>2. Process Structure and Creation</h2>
<h3>The Memory Structure of a Process</h3>
<p>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.</p>
<h3>2.프로세스의 구성과 생성</h3>
<h3>프로세스의 메모리 구조</h3>
<p>프로세스가 메모리에 올라간다고 했을 때, 단순히 프로그램 코드 하나가 통째로 올라가는 것이 아니다. 실행에 필요한 여러 역할들이 구분되어 각자의 영역을 차지하는 구조로 배치된다. 이 구조를 이해하면 운영체제가 실행 중인 프로세스를 어떻게 체계적으로 관리하는지 훨씬 명확하게 보인다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/29982ff9-401a-4c8e-bf86-a9473d85d954.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Code Region</strong></p>
<p>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.</p>
<p><strong>Data Region</strong></p>
<p>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.</p>
<p><strong>Heap Region</strong></p>
<p>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.</p>
<p><strong>Stack Region</strong></p>
<p>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.</p>
<h3>Each Process Has an Independent Memory Space</h3>
<p>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.</p>
<p>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.</p>
<h3>코드 영역</h3>
<p>가장 먼저 코드 영역이다. 이름 그대로 실행할 프로그램의 명령어들이 저장되는 공간이다. CPU는 이 영역에 저장된 명령어를 하나씩 읽어가며 실행한다. 프로그램의 동작 자체를 정의하는 영역이라고 볼 수 있다.</p>
<h3>데이터 영역</h3>
<p>다음은 데이터 영역이다. 전역 변수나 정적 변수처럼 프로그램 전체에 걸쳐 사용되는 데이터들이 이곳에 저장된다. 특정 함수 안에서만 쓰이는 것이 아니라, 프로그램이 실행되는 동안 전반적으로 참조되는 데이터들의 자리다.</p>
<h3>힙 영역</h3>
<p>그다음은 힙 영역이다. 힙은 프로그램이 실행되는 도중에 동적으로 할당되는 메모리 공간이다. 예를 들어 실행 중에 새로운 객체를 생성하거나, 필요에 따라 메모리를 추가로 요청하는 경우가 여기에 해당한다. 미리 크기를 정해두는 것이 아니라 실행 흐름에 따라 유동적으로 사용된다는 점이 특징이다.</p>
<h3>스택 영역</h3>
<p>마지막으로 스택 영역이다. 함수 호출과 관련된 정보, 그리고 지역 변수와 매개변수가 저장되는 공간이다. 함수가 호출될 때마다 필요한 정보가 쌓이고, 함수가 종료되면 다시 걷혀나가는 방식으로 동작하기 때문에 사용량이 실행 흐름에 따라 계속해서 변한다.</p>
<hr />
<h3>각 프로세스는 독립된 메모리 공간을 가진다</h3>
<p>코드, 데이터, 힙, 스택으로 나뉜 이 구조를 통해 운영체제는 프로세스가 실행되는 데 필요한 메모리를 역할별로 체계적으로 관리한다.</p>
<p>여기서 반드시 짚고 넘어가야 할 점이 하나 있다. 각 프로세스는 이 메모리 구조를 서로 독립적으로 가진다는 것이다. 프로세스 A의 메모리 공간과 프로세스 B의 메모리 공간은 서로 직접 접근하거나 섞이지 않는다. 덕분에 한 프로세스에서 문제가 생기더라도 다른 프로세스에 영향을 주지 않도록 보호된다. 이 독립성이 운영체제가 여러 프로세스를 안정적으로 동시에 관리할 수 있는 중요한 기반이 된다.</p>
<hr />
<h3>The Memory Layout of a Process</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c680dcf7-9df9-4619-8b2d-e903c46b10e1.png" alt="" style="display:block;margin:0 auto" />

<p>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.</p>
<p>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.</p>
<p>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 <code>char *cp = malloc(10000)</code> requests memory mid-run, this region is used. The stack region stores local variables and parameters declared inside functions - things like <code>float f</code> or <code>int i</code> belong here.</p>
<p>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.</p>
<p>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.</p>
<h3>프로세스의 메모리 배치 구조</h3>
<p>프로세스가 메모리에 올라갈 때, 모든 데이터가 한데 뒤섞여 저장되는 것이 아니다. 실행에 필요한 데이터들은 그 성격에 따라 네 개의 영역으로 나뉘어 체계적으로 배치된다.</p>
<p>구조를 아래에서부터 살펴보면, 가장 아래에 코드 영역이 위치하고 그 위로 데이터 영역, 힙 영역, 그리고 가장 위에 스택 영역이 자리한다.</p>
<p>코드 영역에는 CPU가 하나씩 읽어 실행할 프로그램 명령어들이 저장된다. 데이터 영역에는 전역변수나 정적 변수처럼 프로그램 전반에 걸쳐 사용되는 데이터들이 올라간다. 힙 영역은 실행 중에 동적으로 할당되는 메모리 공간으로, <code>char *cp = malloc(10000)</code>처럼 실행 도중 메모리를 요청하는 경우 이 영역이 사용된다. 스택 영역에는 함수 안에서 선언된 지역변수와 매개변수가 저장되는데, <code>float f</code>나 <code>int i</code> 같은 지역변수가 여기에 해당한다.</p>
<p>이 구조에서 한 가지 눈여겨볼 점은 힙과 스택이 서로를 향해 확장된다는 것이다. 힙은 동적 할당이 늘어날수록 위쪽으로 커지고, 스택은 함수 호출이 쌓일수록 아래쪽으로 확장된다. 두 영역 사이의 빈 공간이 이 확장을 수용하는 여유분 역할을 한다.</p>
<p>이렇게 메모리를 영역별로 구분해서 사용하는 이유는 하나다. 프로세스가 실행되는 동안 성격이 서로 다른 데이터들이 뒤섞이지 않도록 명확히 분리해서 관리하기 위해서다. 운영체제는 바로 이 구조를 기준으로 각 프로세스에 메모리를 할당하고 관리한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/fd1cfcf6-542a-421c-805e-52f13f31f04d.png" alt="" style="display:block;margin:0 auto" />

<h3>How Is a Process Created?</h3>
<p>A process doesn't spontaneously appear out of nowhere. The entity that creates a new process is always an already-running existing process.</p>
<p>The process that creates a new one is called the <strong>parent process</strong>; the newly created one is called the <strong>child process</strong>. A child process, once running, can itself spawn further children. As this relationship repeats, processes form a tree-shaped hierarchical structure.</p>
<p>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.</p>
<p>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.</p>
<h3>프로세스는 어떻게 만들어질까?</h3>
<p>프로세스는 갑자기 혼자 생겨나지 않는다. 새로운 프로세스를 만들어내는 주체는 항상 이미 실행 중인 기존의 프로세스다.</p>
<p>이때 새로운 프로세스를 만들어낸 쪽을 부모 프로세스, 그로 인해 새롭게 생성된 쪽을 자식 프로세스라고 부른다. 자식 프로세스 역시 실행되고 나면 또 다른 자식을 낳을 수 있다. 이런 관계가 반복되면서 프로세스들은 나무 모양의 계층 구조를 이루게 된다.</p>
<p>운영체제는 이 과정에서 두 가지 역할을 함께 수행한다. 새로운 자식 프로세스를 위한 독립된 메모리 공간을 할당하고, 곧바로 실행에 들어갈 수 있도록 준비 작업을 마친다. 자식 프로세스는 생성되는 순간부터 자신만의 메모리 공간을 가지는 독립적인 실행 단위가 된다.</p>
<p>지금 단계에서 시스템 호출이나 구체적인 구현 방식까지 깊이 파고들 필요는 없다. 핵심은 두 가지다. 프로세스는 기존 프로세스에 의해 생성된다는 것, 그리고 그 관계는 부모와 자식이라는 계층 구조로 이어진다는 것이다. 이 개념을 머릿속에 잡아두면, 이후에 배울 스케줄링이나 프로세스 간 통신 같은 주제들을 이해하는 데 든든한 발판이 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4946a41b-9151-4d71-b7a4-9189213df9c7.png" alt="" style="display:block;margin:0 auto" />

<h3>The Process Tree and the Parent-Child Relationship</h3>
<p>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.</p>
<p>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.</p>
<p>For reference, in a Linux environment, a process called <code>init</code> serves as the starting point for all processes. No matter which running process you trace upward through its parents, you'll eventually reach <code>init</code>.</p>
<p>Understanding this parent-child relationship makes it easier to naturally connect to topics you'll encounter later, such as process termination and resource cleanup.</p>
<h3>프로세스 트리 구조와 부모-자식 관계</h3>
<p>부모와 자식 관계가 만들어진다고 했는데, 이 관계를 기준으로 보면 프로세스들은 서로 독립적으로 흩어져 있는 것이 아니라 계층적인 트리 구조를 이룬다. 모든 프로세스는 이 트리 안에서 반드시 어떤 부모 프로세스를 가지고 있으며, 최종적으로는 하나의 최상위 프로세스로부터 파생된다.</p>
<p>여기서 한 가지 중요한 점이 있다. 부모 프로세스의 상태 변화가 자식 프로세스에도 영향을 줄 수 있다는 것이다. 특히 부모 프로세스가 종료되는 경우, 자식 프로세스를 어떻게 처리할지는 운영체제의 정책에 따라 결정된다.</p>
<p>참고로 리눅스 환경에서는 <code>init</code>이라는 프로세스가 모든 프로세스의 시작점 역할을 한다. 실행 중인 어떤 프로세스든 부모를 따라 트리를 거슬러 올라가다 보면 결국 이 <code>init</code> 프로세스와 연결된다.</p>
<p>이렇게 부모와 자식 간의 관계를 이해해두면, 이후에 다루게 될 프로세스 종료나 자원 정리 같은 내용들도 자연스럽게 연결지어 이해할 수 있게 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/03a30ef2-6a8c-4c4d-98b3-42aa8b585138.png" alt="" style="display:block;margin:0 auto" />

<h2>The Process Tree: A Hierarchy Starting from init</h2>
<p>At the top of the process tree in a Linux environment sits <code>init</code> - the process that holds PID 1. This is the very first process to run on the system, and all processes ultimately derive from it.</p>
<p>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.</p>
<p>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.</p>
<p>In this way, processes are managed within a tree-structured hierarchy. No matter which process you trace upward, it eventually connects to <code>init</code>. Understanding this entire flow as a single picture makes later topics - process termination, resource cleanup - much easier to connect and comprehend.</p>
<h3>프로세스 트리, init에서 시작되는 계층 구조</h3>
<p>리눅스 환경에서 프로세스 트리의 최상위에는 <code>init</code>이라는 프로세스가 있다. PID 1번을 가지는 이 프로세스가 시스템에서 가장 먼저 실행되는 출발점으로, 모든 프로세스는 결국 이 <code>init</code>으로부터 파생된다.</p>
<p>그 아래 계층에는 쉘(Shell) 프로세스가 위치한다. 사용자가 시스템에 로그인하면 해당 사용자를 위한 쉘 프로세스가 생성된다. 쉘은 사용자가 입력한 명령어를 받아 운영체제에 전달해주는 역할을 하는데, 사용자 입장에서는 프로그램을 실행할 때 그 실행을 시작해주는 출발점이라고 보면 된다.</p>
<p>웹브라우저, 채팅, 미디어플레이어, 편집기 같은 프로그램들은 모두 이 쉘을 부모로 두는 자식 프로세스로 생성된다. 사용자가 실행하는 프로그램들이 각각 독립적으로 뚝 생겨나는 것이 아니라, 쉘이라는 프로세스를 거쳐 부모-자식 관계 속에서 계층적으로 만들어진다는 뜻이다.</p>
<p>이처럼 프로세스는 트리 구조의 계층 관계 안에서 관리된다. 어떤 프로세스든 부모를 따라 위로 거슬러 올라가다 보면 결국 <code>init</code>과 연결된다. 이 전체 흐름을 하나의 그림으로 이해해두면, 이후에 배울 프로세스 종료나 자원 정리 같은 내용들도 자연스럽게 연결지어 이해할 수 있게 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/44faba31-c5b7-4792-b02f-738890a921fc.png" alt="" style="display:block;margin:0 auto" />

<h3>From Execution Request to Actual Execution</h3>
<p>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.</p>
<p>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 <code>fork()</code>. Through this call, a new child process is born from an existing process.</p>
<p>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.</p>
<p>At this stage, there's no need to memorize functions like <code>fork()</code> 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.</p>
<h3>프로그램 실행 요청부터 실제 실행까지</h3>
<p>사용자가 프로그램을 실행하면 운영체제 내부에서는 여러 단계가 순서대로 진행된다. 아이콘을 클릭하거나 명령어를 입력하는 순간부터 실제로 프로그램이 동작하기까지의 흐름을 따라가 보자.</p>
<p>먼저 사용자의 실행 요청이 들어오면 운영체제는 시스템 호출을 통해 새로운 프로세스를 생성한다. 이때 사용되는 대표적인 시스템 호출 함수가 <code>fork()</code>다. 이 호출을 통해 기존 프로세스로부터 새로운 자식 프로세스가 만들어진다.</p>
<p>프로세스가 생성되고 나면 운영체제는 그 프로세스가 실행되는 데 필요한 메모리 공간을 할당한다. 앞서 살펴본 코드, 데이터, 힙, 스택 영역이 이 시점에 각각 확보된다. 메모리 할당이 끝나면 CPU 상태와 실행 위치를 초기화하는 실행 준비 작업이 이어진다. 이 준비가 완료되어야 비로소 프로세스가 CPU를 할당받아 실제 실행 단계로 넘어갈 수 있다.</p>
<p>이 단계에서 <code>fork()</code> 같은 함수나 세부 구현 방식을 외우려 할 필요는 없다. 중요한 것은 프로그램 실행 요청 → 프로세스 생성 → 메모리 할당 → 실행 준비라는 전체 흐름을 하나의 그림으로 머릿속에 담아두는 것이다. 이 흐름을 이해하고 있으면 이후에 배울 스케줄링이나 프로세스 관리 같은 내용들도 훨씬 자연스럽게 연결된다.</p>
<hr />
<h3>3.Condition of Process</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e96f7282-1b4c-40be-ab90-f3fd2fe00cf6.png" alt="" style="display:block;margin:0 auto" />

<h3>The State Changes of a Process</h3>
<p>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.</p>
<p>The very first is the <strong>New</strong> state. This is the phase where the process is just being created and is not yet ready to execute.</p>
<p>Once creation is complete, it moves to the <strong>Ready</strong> 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.</p>
<p>When a Ready process is allocated CPU time, it becomes <strong>Running</strong>. 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.</p>
<p>If a situation arises during Running where the process must wait for an external event - like an I/O operation - it transitions to the <strong>Waiting</strong> 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.</p>
<p>Finally, when execution is entirely complete, the process reaches the <strong>Terminated</strong> state. This is where the process's lifecycle ends.</p>
<p>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.</p>
<h3>프로세스의 상태 변화</h3>
<p>프로세스는 한번 생성되면 처음부터 끝까지 같은 상태로 머무는 것이 아니다. 실행되는 동안 여러 상태를 오가며 변화한다. 이 흐름을 순서대로 따라가 보자.</p>
<p>가장 처음은 <strong>New</strong> 상태다. 프로세스가 막 생성되고 있는 단계로, 아직 실행 준비가 완료되지 않은 상태를 의미한다.</p>
<p>생성이 완료되면 <strong>Ready</strong> 상태로 넘어간다. 실행에 필요한 준비는 모두 끝났지만 아직 CPU를 할당받지 못해 차례를 기다리는 상태다. 실행 가능한 상태이지만 CPU가 없어서 대기 중인 것이다.</p>
<p>Ready 상태의 프로세스가 CPU를 할당받으면 <strong>Running</strong> 상태가 된다. 실제로 명령어가 수행되는 상태로, 일반적으로 한 시점에 CPU 하나에서 실행될 수 있는 프로세스는 단 하나뿐이다. 실행 중에 CPU를 빼앗기면 다시 Ready 상태로 돌아가기도 한다.</p>
<p>Running 도중 입출력 작업처럼 외부 사건을 기다려야 하는 상황이 생기면 <strong>Waiting</strong> 상태로 전환된다. 이 상태에서는 CPU를 사용하지 않으며, 기다리던 사건이 완료되면 다시 Ready 상태로 돌아가 CPU 할당을 기다린다.</p>
<p>마지막으로 실행이 모두 끝나면 <strong>Terminated</strong> 상태가 된다. 프로세스의 생명 주기가 마무리되는 단계다.</p>
<p>결국 프로세스는 생성 → 준비 → 실행 → 대기 → 종료라는 일련의 흐름 속에서 상태를 끊임없이 바꾸며 동작한다. 이 상태 변화의 흐름을 이해해두면, 이후에 배울 스케줄링이 어떤 시점에 어떤 프로세스를 선택하는지도 훨씬 자연스럽게 이해할 수 있게 된다.</p>
<hr />
<h3>The Flow of Process State Transitions</h3>
<p>Let's trace which states a process passes through during execution and what triggers each state change.</p>
<h3>프로세스 상태 변화의 흐름</h3>
<p>프로세스가 실행되는 동안 어떤 상태를 거치고, 어떤 계기로 상태가 바뀌는지 흐름을 따라가 보자.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/7f507ae2-800c-4e49-a739-71c7d8343fc6.png" alt="" style="display:block;margin:0 auto" />

<h3><strong>Basic Flow</strong></h3>
<p>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.</p>
<h3><strong>기본 흐름</strong></h3>
<p>가장 단순하게 보면, 프로세스는 생성된 후 비실행 상태에 머물다가 CPU를 할당받는 순간 디스패치되어 실행 상태로 넘어간다. 실행이 끝나면 종료 상태로 이동한다. 단, 실행 중에 인터럽트가 발생하면 다시 비실행 상태로 돌아갈 수 있다. 이것이 프로세스 상태 변화의 가장 기본적인 골격이다.</p>
<hr />
<h3><strong>Detailed State Changes</strong></h3>
<p>In practice, when the operating system manages processes, this flow is more granular.</p>
<p>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 <strong>dispatch</strong>. The dispatch is precisely the transition action that occurs when moving from ready to running.</p>
<p>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.</p>
<p>After repeating this cycle, once execution is fully complete, the process moves to the terminated state.</p>
<p>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.</p>
<h3>구체적인 상태 변화</h3>
<p>실제로 운영체제가 프로세스를 관리할 때는 이 흐름이 좀 더 세분화된다.</p>
<p>프로세스가 생성되면 곧바로 CPU를 받는 것이 아니라 먼저 준비 상태로 들어간다. 준비 상태는 실행에 필요한 모든 준비는 끝났지만 CPU를 아직 할당받지 못해 차례를 기다리는 상태다. 여기서 운영체제가 실행할 프로세스를 하나 골라 CPU를 넘겨주는 과정을 디스패치라고 한다. 준비에서 실행으로 넘어가는 바로 그 전환 동작이 디스패치다.</p>
<p>실행 상태에 들어간 프로세스는 상황에 따라 다시 준비 상태로 돌아올 수 있다. 할당된 시간이 끝났거나, 더 우선순위가 높은 프로세스에게 CPU를 넘겨줘야 할 경우가 그 예다. 한편 실행 중에 입출력 요청처럼 외부 사건을 기다려야 하는 상황이 생기면 프로세스는 대기 상태로 이동한다. 대기 상태에서는 CPU를 사용하지 않으며, 기다리던 사건이 완료되면 다시 준비 상태로 돌아와 CPU 할당을 기다린다.</p>
<p>이 과정을 반복하다가 실행이 모두 끝나면 프로세스는 최종적으로 종료 상태로 이동한다.</p>
<p>여기서 핵심은 두 가지다. 프로세스는 한 방향으로만 쭉 진행되는 것이 아니라 상황에 따라 준비, 실행, 대기를 반복해서 오간다는 것, 그리고 그 흐름의 중심에 준비에서 실행으로 전환되는 디스패치라는 과정이 있다는 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e2e98345-1bd4-421a-b0e7-d24f3f2cc013.png" alt="" style="display:block;margin:0 auto" />

<h1>2️⃣ Process State and Management</h1>
<h2>1. Process State Changes</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0f3ee9a5-a65e-474a-9a9e-7961446c2e30.png" alt="" style="display:block;margin:0 auto" />

<h3>Why Process State Changes Are Necessary</h3>
<p>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.</p>
<p>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 <strong>process state change</strong>.</p>
<p>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.</p>
<h3>프로세스 상태 변화의 필요성</h3>
<p>컴퓨터에서 여러 프로그램이 동시에 실행되는 것처럼 보이지만, CPU는 한 순간에 단 하나의 프로세스만 실행할 수 있다. 이 제약이 있기 때문에 운영체제는 끊임없이 중요한 판단을 내려야 한다. 지금 어떤 프로세스를 실행할 것인지, 언제 실행 중인 프로세스를 멈추고 다른 프로세스로 전환할 것인지를 계속해서 결정해야 하는 것이다.</p>
<p>이 과정에서 프로세스는 실행 상태에만 머무는 것이 아니라 준비와 대기 상태를 오가며 관리된다. 운영체제가 프로세스를 전환하고 관리하는 과정에서 나타나는 이러한 흐름을 프로세스의 상태 변화라고 한다.</p>
<p>이 개념은 단순히 프로세스가 어떤 상태에 있는지를 설명하는 데서 그치지 않는다. 앞으로 살펴볼 스케줄링이나 동기화 같은 프로세스 관리 기법들을 이해하기 위한 기본 전제가 되는 개념이기 때문에, 지금 이 흐름을 확실히 잡아두는 것이 중요하다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2242107e-71bb-44c1-b625-fdba3238bd9a.png" alt="" style="display:block;margin:0 auto" />

<h3>What Would Happen Without Process State Changes?</h3>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h2>프로세스 상태 변화가 없다면 어떻게 될까?</h2>
<p>만약 프로세스의 상태 변화가 없다면 운영체제는 여러 프로세스를 제대로 구분할 수 없게 된다. 어떤 프로세스가 지금 실행되어야 하는지, 어떤 프로세스가 대기 중인지에 대한 정보가 없으면 CPU를 누구에게 할당해야 할지 결정할 수 없기 때문이다.</p>
<p>결국 프로세스의 상태 변화는 단순히 프로세스의 현재 상황을 표시하는 것에 그치지 않는다. 여러 프로세스가 CPU를 나눠 쓸 수 있도록 하고, 시스템 자원을 효율적으로 사용하기 위한 관리 기준이 된다.</p>
<p>이 관점에서 보면 프로세스의 상태 변화는 운영체제가 CPU를 회수하고 다시 할당하기 위해 사용하는 가장 기본적인 관리 방식이라고 이해할 수 있다. 운영체제가 여러 프로세스를 동시에 다루는 것처럼 보이는 것도, 결국 이 상태 변화를 기반으로 CPU를 빠르게 전환하며 관리하기 때문에 가능한 일이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d4d656a3-7363-4762-adfb-3f3e389e70f5.png" alt="" style="display:block;margin:0 auto" />

<h3>The Operating System Decides State Changes</h3>
<p>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.</p>
<p>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 <strong>kernel mode</strong>. User programs never directly change their own state.</p>
<p>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.</p>
<h3>상태 변화는 운영체제가 결정한다</h3>
<p>프로세스의 상태 변화는 아무 때나 임의로 발생하는 것이 아니다. 상태가 바뀐다는 것은 곧 운영체제가 실행 흐름에 개입하겠다는 의미이며, 이 개입은 항상 특정 시점에서만 일어난다.</p>
<p>다시 말해 프로세스가 스스로 자신의 상태를 바꾸는 것이 아니다. 운영체제가 개입이 필요하다고 판단한 순간에 비로소 상태 변화가 일어난다. 여기서 한 가지 중요한 점이 있다. 이 상태 변화는 반드시 커널 모드에서만 이루어진다는 것이다. 사용자 프로그램이 직접 자신의 상태를 바꾸는 일은 없다.</p>
<p>이 사실은 운영체제가 프로세스 관리의 주도권을 완전히 쥐고 있다는 것을 의미한다. 프로세스 입장에서는 언제 실행되고 언제 멈출지를 스스로 결정할 수 없으며, 그 모든 판단은 운영체제가 커널 모드에서 내린다. 이 구조 덕분에 여러 프로세스가 질서 있게 CPU를 나눠 쓸 수 있고, 한 프로세스가 시스템 전체를 독점하거나 불안정하게 만드는 상황을 막을 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/25a7f1fd-f505-47f6-ac69-46860116585f.png" alt="" style="display:block;margin:0 auto" />

<h3>When Does the Operating System Intervene?</h3>
<p>There are specific triggers that cause the operating system to change a process's state. The most representative cases are as follows.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>운영체제는 어떤 순간에 개입할까?</h3>
<p>운영체제가 프로세스의 상태를 변경하는 데는 특정한 계기가 있다. 대표적인 경우를 살펴보면 다음과 같다.</p>
<p>첫 번째는 프로세스가 CPU를 할당받는 순간이다. 준비 상태에서 기다리던 프로세스가 CPU를 받게 되면 운영체제는 해당 프로세스를 실행 상태로 전환한다.</p>
<p>두 번째는 프로세스가 할당된 CPU 사용 시간을 모두 소진했을 때다. 운영체제는 각 프로세스가 CPU를 무한정 점유하지 못하도록 사용 시간을 제한하는데, 그 시간이 끝나면 실행 중인 프로세스를 멈추고 다른 프로세스로 전환한다.</p>
<p>세 번째는 프로세스가 입출력 작업을 요청하거나, 요청했던 입출력 작업이 완료되었을 때다. 입출력 요청이 들어오면 해당 프로세스는 대기 상태로 전환되고, 작업이 완료되면 다시 준비 상태로 돌아온다. 이 두 시점 모두 운영체제가 직접 개입해 상태를 변경한다.</p>
<p>결국 이 세 가지 시점을 기준으로 프로세스는 실행, 준비, 대기 상태를 반복해서 오가게 된다. 운영체제는 이 흐름을 통해 여러 프로세스가 CPU를 질서 있게 나눠 쓸 수 있도록 조율하는 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/88b07255-85a6-44c3-8b5f-3ba76a9e9092.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/01a44084-0a8b-43cf-bd2d-de75496c341b.png" alt="" style="display:block;margin:0 auto" />

<h3>Terms for State Transitions</h3>
<p>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. <strong>Dispatch and timeout</strong> are expressions that refer to the concrete moments when the operating system intervenes and a process's state changes.</p>
<p><strong>Dispatch</strong> 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.</p>
<p><strong>Timeout</strong> 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.</p>
<p>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.</p>
<h3>상태 전환을 표현하는 용어</h3>
<p>프로세스의 상태가 바뀌는 흐름은 앞서 살펴봤다. 여기서는 그 상태가 바뀌는 각각의 순간을 부르는 용어를 정리해보자. 디스패치와 타임아웃은 운영체제가 개입해서 프로세스의 상태가 전환되는 구체적인 순간을 가리키는 표현이다.</p>
<p><strong>디스패치</strong>는 운영체제가 준비 상태에 있는 프로세스 중 하나를 골라 실행 상태로 전환하는 순간을 가리킨다. 쉽게 말해 운영체제가 "이 프로세스를 이제 실행하겠다"고 결정하는 바로 그 순간이다. 준비 상태에서 실행 상태로 넘어가는 전환점이 디스패치다.</p>
<p><strong>타임아웃</strong>은 그 반대 방향의 전환이다. 실행 중인 프로세스가 할당된 CPU 사용 시간을 모두 소진하면, 운영체제는 해당 프로세스를 다시 준비 상태로 돌려보낸다. 이때 발생하는 전환을 타임아웃, 즉 시간 종료라고 한다.</p>
<p>두 용어를 함께 놓고 보면 흐름이 명확해진다. 디스패치는 준비에서 실행으로, 타임아웃은 실행에서 준비로 돌아오는 전환이다. 운영체제는 이 두 가지 전환을 반복하면서 여러 프로세스가 CPU를 번갈아 사용할 수 있도록 조율한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ea699bd6-9508-4290-b010-f99f127ee058.png" alt="" style="display:block;margin:0 auto" />

<h3>Block and Wakeup</h3>
<p>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.</p>
<p><strong>Block</strong> 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.</p>
<p><strong>Wakeup</strong> 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.</p>
<p>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.</p>
<h2>Block과 Wakeup</h2>
<p>앞서 디스패치와 타임아웃이 준비와 실행 사이의 전환을 표현하는 용어였다면, 이번에는 대기 상태와 관련된 전환 용어를 살펴보자.</p>
<p><strong>Block</strong>은 실행 중인 프로세스가 입출력 작업을 요청하거나 특정 사건을 기다려야 하는 경우, 실행 상태에서 대기 상태로 전환되는 순간을 가리킨다. 프로세스가 스스로 더 이상 진행할 수 없는 상황이 되어 운영체제에 의해 대기 상태로 보내지는 것이다. 이 대기 상태에 머무는 동안에는 CPU를 사용하지 않는다.</p>
<p><strong>Wakeup</strong>은 그 반대다. 기다리던 입출력 작업이 완료되거나 특정 사건이 해결되면, 운영체제는 대기 중이던 프로세스를 다시 깨워 준비 상태로 이동시킨다. 이 전환을 Wakeup이라고 한다. 주의할 점은 Wakeup이 된다고 해서 곧바로 실행 상태로 가는 것이 아니라는 점이다. 다시 준비 상태로 돌아와 CPU 할당을 기다리는 순서를 밟게 된다.</p>
<p>이제 네 가지 전환 용어를 함께 놓고 보면 프로세스 상태 변화의 전체 흐름이 한눈에 정리된다. 디스패치로 실행이 시작되고, 타임아웃으로 준비 상태로 돌아오며, Block으로 대기에 들어가고, Wakeup으로 다시 준비 상태로 복귀한다. 운영체제는 이 네 가지 전환을 통해 여러 프로세스의 실행을 끊임없이 조율한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9c033aff-ca1b-400e-a730-771c075cb0aa.png" alt="" style="display:block;margin:0 auto" />

<h3>2. Process Control Block (PCB)</h3>
<p>We've seen which states a process passes through and what triggers each state change. Thinking carefully about this process, a natural question arises.</p>
<p>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.</p>
<p>So where exactly is all this information stored? Let's find the answer in what follows.</p>
<h3>2. 프로세스 제어 블록(PCB)</h3>
<p>지금까지 프로세스가 어떤 상태를 거치고, 어떤 계기로 상태가 바뀌는지 살펴봤다. 그런데 이 과정을 곰곰이 생각해보면 자연스럽게 한 가지 의문이 생긴다.</p>
<p>운영체제가 프로세스를 관리하려면 각 프로세스에 대한 여러 정보가 필요하다. 지금 이 프로세스가 어떤 상태에 있는지, 즉 Ready인지 Running인지 Waiting인지를 알아야 한다. 실행이 중단되었다가 다시 재개될 때 어디서부터 이어서 실행해야 하는지도 알아야 한다. 그 프로세스가 CPU를 어떻게 사용하고 있었는지와 같은 정보도 필요하다.</p>
<p>그렇다면 이런 정보들은 과연 어디에 저장되는 걸까? 다음 내용에서 이 질문의 답을 찾아보자.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/7238a28b-f634-4d00-b6e9-4e07af1e7043.png" alt="" style="display:block;margin:0 auto" />

<h3>What Is a PCB?</h3>
<p>The answer to the question posed earlier; where is information about processes stored?; is the <strong>Process Control Block (PCB)</strong>.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>PCB란 무엇인가?</h3>
<p>앞서 던진 질문, 프로세스에 관한 정보는 어디에 저장되는가에 대한 답이 바로 <strong>프로세스 제어 블록(PCB, Process Control Block)</strong> 이다.</p>
<p>운영체제는 프로세스의 상태와 실행 정보를 프로세스마다 따로 관리하기 위해 PCB라는 자료구조를 사용한다. 프로세스가 하나 생성될 때마다 그에 대응하는 PCB가 하나씩 만들어지며, 이 PCB는 커널 영역에 저장된다.</p>
<p>여기서 한 가지 중요한 점을 짚고 넘어가야 한다. PCB는 프로세스의 실행을 관리하기 위한 정보만 담고 있는 관리용 자료구조라는 것이다. 프로세스의 코드, 데이터, 스택처럼 실행 내용 자체는 PCB에 포함되지 않는다. 그 내용들은 앞서 살펴본 것처럼 프로세스의 메모리 공간인 코드 영역, 데이터 영역, 힙, 스택에 따로 존재한다.</p>
<p>쉽게 비유하자면 PCB는 프로세스에 대한 정보를 기록해둔 관리 카드 같은 것이다. 실제 실행 내용이 담긴 것이 아니라, 운영체제가 그 프로세스를 관리하고 제어하기 위해 필요한 정보들만 모아둔 구조체다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b639d914-c8d0-4c50-b01a-0407dac34d2f.png" alt="" style="display:block;margin:0 auto" />

<h3>The Relationship Between a Process and Its PCB</h3>
<p>Each time a process is created, a corresponding PCB is created in the <strong>kernel area</strong>. 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.</p>
<p>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.</p>
<p>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.</p>
<h3>프로세스와 PCB의 관계</h3>
<p>프로세스가 생성될 때마다 그에 대응하는 PCB가 커널 영역에 하나씩 만들어진다. 프로세스 A가 생성되면 PCB A가, 프로세스 B가 생성되면 PCB B가 만들어지는 식이다. 실행 중인 프로세스의 수만큼 PCB가 커널 영역에 존재하게 된다.</p>
<p>여기서 중요한 점은 운영체제가 프로세스를 직접 다루는 것이 아니라 PCB를 통해 프로세스의 상태와 실행 정보를 관리한다는 것이다. 운영체제 입장에서는 PCB가 곧 프로세스를 대표하는 존재다. 어떤 프로세스를 다음에 실행할지, 지금 어떤 상태에 있는지, CPU를 어떻게 사용하고 있었는지와 같은 판단을 모두 PCB에 담긴 정보를 기준으로 내린다.</p>
<p>쉽게 말해 프로세스는 메모리에서 실제로 실행되는 실체이고, PCB는 운영체제가 그 프로세스를 관리하기 위해 참조하는 정보 창구라고 볼 수 있다. 운영체제는 프로세스 자체보다 PCB를 먼저 들여다보며 프로세스를 파악하고 제어한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/322742b0-01a0-4bc9-9e6f-bd9356c12605.png" alt="" style="display:block;margin:0 auto" />

<h3>Information Stored in a PCB</h3>
<p>A PCB contains various pieces of information the operating system needs to manage a process. Let's look at the main items.</p>
<p>First is the <strong>process identifier</strong>. 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.</p>
<p>Next is the <strong>process state</strong>. 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.</p>
<p>One of the most <strong>important items</strong> is the <strong>Program Counter</strong>. 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.</p>
<h3>PCB에 저장되는 정보</h3>
<p>PCB에는 운영체제가 프로세스를 관리하는 데 필요한 여러 정보들이 담겨 있다. 대표적인 항목들을 살펴보자.</p>
<p>먼저 <strong>프로세스 식별자</strong>다. 각 프로세스를 구분하기 위한 고유한 숫자, 즉 PID(Process ID)가 저장된다. 운영체제는 이 식별자를 기준으로 여러 프로세스를 서로 구별하고 관리한다.</p>
<p>다음은 <strong>프로세스 상태</strong>다. 현재 이 프로세스가 생성, 준비, 실행, 대기, 종료 중 어떤 상태에 있는지가 PCB에 기록된다. 운영체제는 이 정보를 보고 어떤 프로세스를 실행할지, 어떤 프로세스가 대기 중인지를 판단한다.</p>
<p>그리고 가장 중요한 항목 중 하나가 <strong>프로그램 카운터(Program Counter)</strong> 다. 프로세스가 실행되다가 중단되면, 다음에 다시 실행될 때 어디서부터 이어서 실행해야 하는지를 알아야 한다. 프로그램 카운터는 바로 그 다음에 실행해야 할 명령어의 주소를 저장해두는 값이다. 이 값이 있기 때문에 프로세스가 중단되었다가 재개될 때 처음부터 다시 시작하는 것이 아니라 멈췄던 지점에서 정확히 이어서 실행할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/11e66390-463c-4216-97e6-8c33c7cac0bc.png" alt="" style="display:block;margin:0 auto" />

<h3>Information Stored in a PCB (Continued)</h3>
<p>Beyond the identifier, state, and program counter, a few more important pieces of information are stored in the PCB.</p>
<p><strong>Register values.</strong> 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.</p>
<p><strong>Scheduling-related information</strong> 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.</p>
<p>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.</p>
<h3>PCB에 저장되는 정보 (계속)</h3>
<p>앞서 살펴본 식별자, 상태, 프로그램 카운터 외에도 PCB에는 몇 가지 중요한 정보가 더 저장된다.</p>
<p><strong>레지스터 값</strong>이다. 프로세스가 실행되는 동안 CPU의 레지스터에는 현재 연산에 필요한 여러 값들이 담겨 있다. 그런데 인터럽트가 발생하거나 타임아웃으로 프로세스가 중단되면, 그 순간 레지스터에 있던 값들을 PCB에 저장해둔다. 이후 해당 프로세스가 다시 실행될 때 PCB에 저장해둔 레지스터 값을 그대로 복원함으로써, 중단되기 직전의 상태로 정확히 되돌아갈 수 있다. 프로그램 카운터와 레지스터 값이 함께 저장되기 때문에 프로세스는 마치 중단된 적이 없었던 것처럼 이어서 실행될 수 있는 것이다.</p>
<p><strong>스케줄링 관련 정보</strong>도 PCB에 포함된다. 이 프로세스의 우선순위가 얼마인지, 스케줄링 큐에서 어느 위치에 있는지, 그 밖에 스케줄링 결정에 필요한 매개변수들이 여기에 저장된다. 운영체제는 이 정보를 바탕으로 다음에 어떤 프로세스를 실행할지 판단한다.</p>
<p>결국 PCB는 프로세스가 중단되었다가 재개될 때 아무것도 잃지 않고 이전 상태로 완전히 복구될 수 있도록 필요한 모든 정보를 보관하는 역할을 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1a09c7cb-8efa-4d7d-aa3f-64c998842f8a.png" alt="" style="display:block;margin:0 auto" />

<h3>Information Stored in a PCB (Wrap-up)</h3>
<p>Beyond the information already covered, a few more items are included in the PCB.</p>
<p><strong>Accounting information.</strong> 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.</p>
<p><strong>I/O status information</strong> 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.</p>
<p>Finally, <strong>memory management information.</strong> Which areas of memory the process is using, and information related to memory allocation, are included in the PCB.</p>
<p>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.</p>
<h3>PCB에 저장되는 정보 (마무리)</h3>
<p>PCB에는 앞서 살펴본 정보들 외에도 몇 가지가 더 포함된다.</p>
<p><strong>계정 정보</strong>다. 해당 프로세스가 CPU를 얼마나 사용했는지, 실제 사용 시간은 얼마인지, 사용 가능한 상한 시간은 얼마인지와 같은 자원 사용 내역이 기록된다. 계정 번호나 작업 번호 같은 식별 정보도 함께 포함된다.</p>
<p><strong>입출력 상태 정보</strong>도 저장된다. 현재 이 프로세스가 어떤 입출력 작업을 요청했는지, 현재 열려 있는 파일은 무엇인지와 같은 정보가 PCB에 기록된다.</p>
<p>마지막으로 <strong>메모리 관리 정보</strong>다. 이 프로세스가 메모리의 어느 영역을 사용하고 있는지, 메모리 할당과 관련된 정보들이 PCB에 포함된다.</p>
<p>이렇게 PCB에 담긴 정보들을 종합해서 보면, PCB가 단순히 프로세스의 실행 상태만 관리하는 것이 아님을 알 수 있다. 운영체제는 PCB를 통해 프로세스의 실행 흐름은 물론, 자원 사용 현황까지 함께 파악하고 관리한다. 결국 PCB 하나에 해당 프로세스에 관한 모든 관리 정보가 집약되어 있는 셈이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/78bee389-7d9d-4e6f-a9d6-2c60312ca4f3.png" alt="" style="display:block;margin:0 auto" />

<h3>The Key Role of the PCB in Context Switching</h3>
<p>Among the various pieces of information stored in the PCB, the ones most critically used when a context switch occurs are <strong>register values</strong> and the <strong>program counter</strong>.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>문맥 교환과 PCB의 핵심 역할</h3>
<p>PCB에 저장되는 여러 정보 중에서 문맥 교환이 발생할 때 가장 핵심적으로 활용되는 것은 <strong>레지스터 값</strong>과 <strong>프로그램 카운터</strong>다.</p>
<p>프로세스가 실행되다가 중단되는 순간, 운영체제는 그 시점의 레지스터 값과 프로그램 카운터를 해당 프로세스의 PCB에 저장해둔다. 이후 다른 프로세스가 실행되다가 다시 이 프로세스로 전환되면, 운영체제는 PCB에 저장해뒀던 값들을 그대로 복원한다. 덕분에 프로세스는 마치 아무 일도 없었던 것처럼 중단된 지점부터 정확히 이어서 실행될 수 있다.</p>
<p>이 두 가지 정보가 PCB에 안전하게 보관되어 있기 때문에, 운영체제는 수십 개의 프로세스를 빠르게 전환하면서도 각 프로세스의 실행 흐름을 온전히 유지할 수 있는 것이다.</p>
<p>그렇다면 실제로 프로세스가 전환될 때 PCB에 저장된 정보들이 구체적으로 어떤 순서로 저장되고 복원되는지, 다음 내용에서 그 과정을 단계별로 살펴보자.</p>
<hr />
<h2>3. Context Switching</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a1997685-71f7-418a-88fe-b94389c25a01.png" alt="" style="display:block;margin:0 auto" />

<h3>What Is a Context Switch?</h3>
<p>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.</p>
<p>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.</p>
<p>The operation that handles both of these at once is the <strong>context switch</strong>. 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.</p>
<h3>문맥 교환이란?</h3>
<p>하나의 CPU에서 여러 프로세스가 실행되는 환경에서는 현재 실행 중인 프로세스를 멈추고 다른 프로세스에게 CPU를 넘겨야 하는 순간이 반드시 생긴다. 타임아웃으로 사용 시간이 끝났거나, 입출력 요청으로 대기 상태로 전환되어야 하는 경우가 대표적인 예다.</p>
<p>이 순간 운영체제는 두 가지를 동시에 처리해야 한다. 현재 실행 중인 프로세스의 실행 흐름을 안전하게 중단시키는 것, 그리고 다음에 실행할 프로세스가 이전에 멈췄던 지점부터 정확히 이어서 실행될 수 있도록 준비하는 것이다.</p>
<p>이 두 가지를 한꺼번에 처리하는 동작이 바로 <strong>문맥 교환(Context Switch)</strong> 이다. 문맥 교환은 단순히 CPU를 다른 프로세스에게 넘기는 것이 아니라, 현재 프로세스의 실행 상태를 PCB에 저장하고 다음 프로세스의 PCB에서 이전 상태를 복원하는 일련의 과정 전체를 가리킨다. 이 과정이 있기 때문에 여러 프로세스가 하나의 CPU를 번갈아 사용하면서도 각자의 실행 흐름을 유지할 수 있는 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b7070707-7536-45ef-a30f-c7f74b57ad48.png" alt="" style="display:block;margin:0 auto" />

<h3>When Does a Context Switch Occur?</h3>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>문맥 교환은 언제 발생할까?</h3>
<p>문맥 교환은 임의로 아무 때나 발생하는 것이 아니다. 운영체제가 CPU의 실행 대상을 바꿔야 한다고 판단한 순간에만 일어난다.</p>
<p>대표적인 계기는 세 가지다. 실행 중인 프로세스에 인터럽트가 발생했을 때, 할당된 타임 슬라이스가 종료되었을 때, 그리고 입출력 요청으로 인해 현재 프로세스가 더 이상 실행을 이어갈 수 없는 상황이 됐을 때다. 이런 시점에 운영체제는 CPU 실행 대상을 전환할지 여부를 결정한다.</p>
<p>여기서 한 가지 중요한 점이 있다. 인터럽트가 발생했다고 해서 반드시 문맥 교환이 일어나는 것은 아니라는 것이다. 인터럽트가 발생하더라도 운영체제가 현재 프로세스를 계속 실행해도 된다고 판단하면 문맥 교환은 일어나지 않는다.</p>
<p>즉 문맥 교환은 인터럽트 그 자체가 아니라, 인터럽트 이후 운영체제가 내린 판단의 결과다. 인터럽트는 운영체제에게 개입할 기회를 주는 신호일 뿐이고, 실제로 문맥 교환을 할지 말지는 운영체제가 상황을 보고 결정하는 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4ba7bc5f-150d-4fe4-93af-d1d26960406d.png" alt="" style="display:block;margin:0 auto" />

<h3>The Actual Sequence of a Context Switch</h3>
<p>When a context switch occurs, the operating system proceeds in the following order.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>문맥 교환이 발생하면 운영체제는 다음과 같은 순서로 동작한다.</h3>
<p>먼저 현재 실행 중이던 프로세스의 실행 위치와 CPU 상태를 저장한다. 앞서 살펴본 프로그램 카운터와 레지스터 값들이 해당 프로세스의 PCB에 기록되는 것이다. 이 저장이 완료되어야 나중에 이 프로세스가 다시 실행될 때 중단된 지점부터 정확히 이어갈 수 있다.</p>
<p>저장이 끝나면 다음에 실행할 프로세스의 PCB에 저장되어 있던 실행 위치와 CPU 상태를 꺼내어 복원한다. 이 복원 과정을 통해 CPU는 새로운 프로세스가 이전에 멈췄던 지점부터 실행을 이어받을 수 있는 상태가 된다.</p>
<p>이 두 단계, 즉 이전 프로세스의 상태를 PCB에 저장하고 다음 프로세스의 상태를 PCB에서 복원하여 CPU의 실행 대상을 전환하는 전체 과정을 문맥 교환이라고 한다.</p>
<p>결국 문맥 교환은 PCB가 있기 때문에 가능한 동작이다. 각 프로세스의 실행 상태가 PCB에 안전하게 보관되어 있기 때문에, 운영체제는 수십 개의 프로세스를 빠르게 전환하면서도 각 프로세스의 실행 흐름을 온전히 유지할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/141c0ec1-73e1-49f9-aa1f-6a56bdf7f80e.png" alt="" style="display:block;margin:0 auto" />

<h3>The Step-by-Step Sequence of a Context Switch</h3>
<p>Let's trace the specific sequence of a context switch through the transition between P1 and P2.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>문맥 교환의 실제 진행 순서</h3>
<p>문맥 교환이 구체적으로 어떤 순서로 진행되는지 P1과 P2의 전환 과정을 통해 살펴보자.</p>
<p>P1이 실행 중이다가 타임 슬라이스가 종료되면 운영체제가 개입한다. 운영체제는 가장 먼저 현재 실행 중이던 P1의 상태, 즉 프로그램 카운터와 레지스터 값을 PCB1에 저장한다. P1의 실행 흐름이 안전하게 보관되는 순간이다.</p>
<p>저장이 완료되면 운영체제는 다음에 실행할 P2의 PCB2를 가져와 이전에 저장해뒀던 실행 상태를 복원한다. 이 복원 과정을 통해 CPU는 P2가 이전에 멈췄던 지점부터 이어서 실행할 수 있는 상태가 된다. 이렇게 CPU의 제어권이 P1에서 P2로 넘어가고 P2가 실행을 시작한다.</p>
<p>이후에도 프로세스가 바뀔 때마다 동일한 방식이 반복된다. 현재 프로세스의 상태는 PCB에 저장되고, 다음 프로세스의 상태는 PCB에서 복원된다.</p>
<p>여기서 핵심은 문맥 교환이 프로세스 자체를 바꾸는 것이 아니라는 점이다. PCB를 기준으로 저장과 복원을 반복하는 과정이 문맥 교환의 본질이다. 프로세스들은 메모리에 그대로 있고, 운영체제가 PCB를 통해 실행 상태를 주고받으며 CPU의 실행 대상을 전환하는 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/495e0bdc-a026-426e-b044-478109b1b189.png" alt="" style="display:block;margin:0 auto" />

<h3>Summarizing the Relationship Between PCBs and Context Switching</h3>
<p>Let's pull together everything we've covered.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h3>PCB와 문맥 교환의 관계 정리</h3>
<p>지금까지 살펴본 내용을 한 번 정리해보자.</p>
<p>프로세스의 상태 정보는 모두 PCB에 저장된다. 문맥 교환이 발생하면 현재 프로세스의 상태를 PCB에 저장하고, 다음 프로세스의 PCB에서 이전 상태를 복원하는 방식으로 프로세스 전환이 이루어진다.</p>
<p>이 관점에서 보면 PCB의 역할이 명확해진다. PCB는 단순히 프로세스 정보를 기록해두는 공간이 아니라, 중단된 프로세스의 실행을 다시 이어서 수행할 수 있도록 필요한 정보를 유지해주는 관리 기준이다. PCB가 없다면 프로세스가 어디서 멈췄는지, 어떤 상태였는지를 알 수 없기 때문에 문맥 교환 자체가 불가능하다.</p>
<p>결론적으로 문맥 교환은 PCB를 기준으로 실행 대상을 바꾸는 과정이라고 정리할 수 있다. 프로세스가 전환될 때마다 PCB에 저장과 복원이 반복되고, 운영체제는 이 과정을 통해 여러 프로세스가 하나의 CPU를 끊김 없이 나눠 쓸 수 있도록 관리한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ce9f5048-bb75-4606-99a7-8a1d2c2c8c4c.png" alt="" style="display:block;margin:0 auto" />

<h3>The Overhead of Context Switching</h3>
<p>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.</p>
<p>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 <strong>overhead</strong>.</p>
<p>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.</p>
<p>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.</p>
<h3>문맥 교환의 오버헤드</h3>
<p>문맥 교환은 여러 프로세스가 CPU를 나눠 쓰기 위해 반드시 필요한 과정이다. 하지만 여기에는 한 가지 비용이 따른다. 문맥 교환이 일어나는 동안 CPU는 실제 작업을 수행하지 못하고 멈추게 된다는 것이다.</p>
<p>이 시간 동안 CPU가 하는 일은 이전 프로세스의 상태를 PCB에 저장하고, 다음 프로세스의 상태를 PCB에서 불러오는 것뿐이다. 실제 연산은 전혀 이루어지지 않는다. 이처럼 실제 작업은 하지 않으면서 CPU 시간이 소모되는 부분을 오버헤드라고 한다.</p>
<p>문맥 교환이 자주 발생할수록 이 오버헤드는 누적되고, 결과적으로 전체 시스템 성능에 영향을 미치게 된다. 프로세스 전환이 잦을수록 CPU가 실제 작업에 쓸 수 있는 시간이 그만큼 줄어드는 셈이다.</p>
<p>따라서 운영체제는 문맥 교환을 무조건 발생시키는 것이 아니라, 필요한 경우에만 최소한으로 일어날 수 있도록 관리해야 한다. 문맥 교환 횟수는 시스템 성능과 직접적으로 연결되어 있기 때문이다. 이와 관련된 구체적인 내용, 즉 운영체제가 어떤 기준으로 CPU 실행 대상을 선택하고 전환 횟수를 조율하는지는 CPU 스케줄링 챕터에서 본격적으로 다루게 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ff5e73e4-8cbb-4f9d-b20c-3db04db66f20.png" alt="" style="display:block;margin:0 auto" />

<h3>The Relationship Between Time Slice Size and Context Switching</h3>
<p>Let's examine how frequently context switches occur depending on time slice size, and how that affects system performance, through three cases.</p>
<p><strong>Case A</strong> 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.</p>
<p><strong>Case B</strong> 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.</p>
<p><strong>Case C</strong> 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.</p>
<p>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.</p>
<h3>타임 슬라이스 크기와 문맥 교환의 관계</h3>
<p>타임 슬라이스의 크기에 따라 문맥 교환이 얼마나 자주 발생하는지, 그리고 그것이 시스템 성능에 어떤 영향을 주는지 세 가지 경우를 통해 살펴보자.</p>
<p><strong>A</strong>는 타임 슬라이스가 큰 경우다. 하나의 프로세스가 오랜 시간 CPU를 점유하고, 문맥 교환은 상대적으로 드물게 발생한다. 실제 작업에 쓰이는 시간이 길기 때문에 오버헤드는 낮다. 다만 한 프로세스가 CPU를 너무 오래 독점하면 다른 프로세스가 오래 기다려야 하는 문제가 생길 수 있다.</p>
<p><strong>B</strong>는 타임 슬라이스가 적당한 경우다. 프로세스 실행 시간과 문맥 교환이 균형을 이루고 있다. 여러 프로세스가 고르게 CPU를 나눠 쓰면서도 오버헤드가 과도하지 않은 이상적인 상태다.</p>
<p><strong>C</strong>는 타임 슬라이스가 지나치게 작은 경우다. 프로세스가 실행을 시작하자마자 문맥 교환이 반복적으로 발생한다. CPU가 실제 작업을 수행하는 시간보다 상태를 저장하고 복원하는 데 쓰는 시간이 더 많아지는 상황이다. 오버헤드가 크게 누적되어 시스템 전체 성능이 떨어진다.</p>
<p>결국 타임 슬라이스가 너무 작으면 문맥 교환이 과도하게 발생해 오버헤드가 커지고, 너무 크면 특정 프로세스가 CPU를 독점하는 문제가 생긴다. 문맥 교환 횟수는 시스템 성능과 직접적으로 연결되어 있으며, 이 균형을 어떻게 맞출 것인가가 CPU 스케줄링의 핵심 과제가 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c7fcbd7a-1fe9-4aee-9239-4f3f887e81c6.png" alt="" style="display:block;margin:0 auto" />

<h3>The Next Challenges in Process Management</h3>
<p>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.</p>
<p>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?</p>
<p>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.</p>
<h3>프로세스 관리의 다음 과제</h3>
<p>문맥 교환을 통해 운영체제가 프로세스 간의 전환을 수행할 수 있다는 것을 확인했다. 그런데 여러 프로세스가 동시에 존재하는 환경에서는 단순히 전환이 가능하다는 것만으로는 충분하지 않다.</p>
<p>프로세스가 여럿 존재한다면 자연스럽게 새로운 문제들이 생겨난다. 대기 중인 프로세스가 여러 개일 때 그 중 어떤 프로세스를 먼저 실행할 것인가, 여러 프로세스가 같은 자원을 동시에 사용하려 할 때 이를 어떻게 조율할 것인가와 같은 문제들이다.</p>
<p>결국 프로세스를 전환하는 것 자체는 시작에 불과하다. 시스템 전체를 안정적이고 효율적으로 유지하기 위해서는 전환 이후의 관리, 즉 어떤 순서로 실행할지를 결정하는 스케줄링과 자원 사용을 조율하는 동기화 같은 추가적인 관리 기법이 필요하다. 앞으로 살펴볼 내용들이 바로 이 문제들을 어떻게 해결하는지에 대한 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f822394b-6839-49e7-9052-b2543ddd53bf.png" alt="" style="display:block;margin:0 auto" />

<h3>The Core Challenges of Process Management Ahead</h3>
<p>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.</p>
<p>The first is <strong>CPU scheduling</strong>. Among multiple waiting processes, which one gets the CPU? In what order and by what criteria is execution order determined?</p>
<p>The second is <strong>memory management</strong>. When multiple processes must share limited memory, how is memory allocated to each process and managed efficiently?</p>
<p>The third is <strong>inter-process communication</strong>. When processes with independent memory spaces need to exchange information, how is that handled?</p>
<p>The fourth is <strong>synchronization</strong>. 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.</p>
<p>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.</p>
<h3>앞으로 다룰 프로세스 관리의 핵심 과제들</h3>
<p>여러 프로세스가 함께 실행되는 환경에서 운영체제가 추가로 해결해야 할 문제들이 있다. 앞으로 하나씩 살펴볼 핵심 과제들을 먼저 정리해두자.</p>
<p>첫 번째는 <strong>CPU 스케줄링</strong>이다. 대기 중인 여러 프로세스 중에서 어떤 프로세스에게 CPU를 줄 것인지, 어떤 순서와 기준으로 실행 순서를 결정할 것인지의 문제다.</p>
<p>두 번째는 <strong>메모리 관리</strong>다. 한정된 메모리를 여러 프로세스가 나눠 써야 하는 상황에서 각 프로세스에게 메모리를 어떻게 할당하고 효율적으로 관리할 것인지의 문제다.</p>
<p>세 번째는 <strong>프로세스 간 통신</strong>이다. 서로 독립된 메모리 공간을 가진 프로세스들이 필요에 따라 정보를 주고받아야 할 때, 이를 어떤 방식으로 처리할 것인지의 문제다.</p>
<p>네 번째는 <strong>동기화</strong>다. 여러 프로세스가 동일한 데이터에 동시에 접근하려 할 때 충돌이 발생할 수 있다. 이런 상황에서 데이터의 일관성을 유지하고 충돌을 막기 위한 관리 방법이 필요하다.</p>
<p>이 네 가지가 앞으로 다루게 될 프로세스 관리의 핵심 이슈다. 지금은 이런 문제들이 존재한다는 것을 큰 그림으로 파악해두고, 각 항목에 대한 구체적인 내용은 이후에 하나씩 살펴보게 될 것이다.</p>
<hr />
<h1>3️⃣ Process Execution and Control</h1>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/09e12345-465b-4055-bfe2-9f4c22d0cdf8.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>Process Execution System Calls: fork()</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/7cc12e6b-1937-4fe0-b544-f5364a2228e9.png" alt="" style="display:block;margin:0 auto" />

<p>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 <code>fork()</code>.</p>
<p><code>fork()</code> 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.</p>
<p>When <code>fork()</code> 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.</p>
<p>There's an important point here. Code written after the <code>fork()</code> call is executed in both the parent and the child process. In other words, the execution flow splits into two branches after <code>fork()</code>, with each process independently continuing its own flow. Understanding this characteristic is essential for accurately grasping how code behaves after <code>fork()</code>.</p>
<h3>프로세스 실행 관련 시스템 호출; fork()</h3>
<p>우분투 환경에서 프로세스의 실행과 제어를 직접 확인하기에 앞서, 먼저 관련 용어를 정리해두자. 운영체제가 프로세스의 생성부터 종료까지 관리하는 데 사용하는 대표적인 시스템 호출들이 있다. 그 중 첫 번째로 살펴볼 것이 <code>fork()</code>다.</p>
<p><code>fork()</code>는 현재 실행 중인 프로세스를 기준으로 새로운 프로세스를 하나 더 생성하는 시스템 호출이다. 이때 새롭게 만들어진 프로세스를 자식 프로세스, 기존의 프로세스를 부모 프로세스라고 부른다.</p>
<p><code>fork()</code>가 호출되면 부모 프로세스의 메모리 공간이 논리적으로 복제되어 새로운 프로세스가 만들어진다. 이렇게 생성된 두 프로세스는 서로 다른 PID를 가지게 되며, 생성 이후에는 완전히 독립적으로 실행된다.</p>
<p>여기서 중요한 점이 있다. <code>fork()</code> 호출 이후에 작성된 코드는 부모 프로세스와 자식 프로세스 모두에서 실행된다는 것이다. 즉 <code>fork()</code> 이후의 실행 흐름이 두 갈래로 나뉘어, 각 프로세스가 자신의 흐름을 독립적으로 이어서 실행하게 된다. 이 특성을 이해하고 있어야 <code>fork()</code> 이후의 코드 동작을 정확히 파악할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3de3fea1-2a5b-474f-9048-11ef78e0f666.png" alt="" style="display:block;margin:0 auto" />

<h3>Distinguishing Parent from Child Using fork()'s Return Value</h3>
<p>When <code>fork()</code> 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 <code>fork()</code>'s return value.</p>
<p>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.</p>
<p>Using this difference in return values, you can clearly determine within the code after <code>fork()</code> 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.</p>
<h3>fork()의 반환값으로 부모와 자식 구분하기</h3>
<p><code>fork()</code>를 호출하면 부모와 자식 프로세스가 동시에 같은 코드를 실행하게 된다. 그렇다면 현재 실행 중인 코드가 부모 프로세스에서 돌아가고 있는지, 자식 프로세스에서 돌아가고 있는지를 어떻게 구분할 수 있을까? 바로 <code>fork()</code>의 반환값을 통해 구분할 수 있다.</p>
<p>반환값은 세 가지 경우로 나뉜다. 부모 프로세스에서는 새로 생성된 자식 프로세스의 PID가 반환된다. 자식 프로세스에서는 0이 반환된다. 그리고 프로세스 생성에 실패한 경우에는 -1이 반환된다.</p>
<p>이 반환값의 차이를 이용하면 <code>fork()</code> 이후의 코드 안에서 지금 실행 중인 주체가 부모인지 자식인지를 명확하게 구분할 수 있다. 반환값이 0이면 자식 프로세스에서 실행 중인 것이고, 0보다 큰 값이면 부모 프로세스에서 실행 중인 것이다. 프로그램 안에서 이 반환값을 조건으로 분기를 나누면 부모와 자식이 서로 다른 동작을 수행하도록 제어할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e9096e8c-9e20-41c5-969f-905c216ac7ef.png" alt="" style="display:block;margin:0 auto" />

<h3>The Execution Flow After fork()</h3>
<p>Let's trace the process by which a single process splits into two execution flows via <code>fork()</code>.</p>
<p>When the program starts, the main function executes, and the moment <code>fork()</code> 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 <code>fork()</code>.</p>
<p>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.</p>
<p>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.</p>
<p>Ultimately, after <code>fork()</code>, 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.</p>
<h3>fork() 이후의 실행 흐름</h3>
<p><code>fork()</code>를 통해 하나의 프로세스가 두 개의 실행 흐름으로 나뉘는 과정을 살펴보자.</p>
<p>프로그램이 시작되면 메인 함수가 실행되고, <code>fork()</code>가 호출되는 순간 새로운 자식 프로세스가 생성된다. 이 시점부터 부모 프로세스와 자식 프로세스가 동시에 존재하게 되며, 둘 다 <code>fork()</code> 다음 코드부터 실행을 이어간다.</p>
<p>이후 두 프로세스는 반환값을 기준으로 서로 다른 코드 블록을 실행한다. 자식 프로세스는 반환값이 0이기 때문에 자식 프로세스용 코드 블록을 실행하고, 부모 프로세스는 자식의 PID를 반환받아 부모 프로세스용 코드 블록을 실행한다. 각자의 코드 블록을 실행한 후 두 프로세스는 서로 독립적으로 실행을 마치고 종료된다.</p>
<p>여기서 반드시 기억해야 할 점이 있다. 부모와 자식 중 어느 프로세스가 먼저 실행될지는 정해져 있지 않다는 것이다. 부모가 먼저 실행될 수도 있고, 자식이 먼저 실행될 수도 있다. 이는 운영체제의 스케줄링 결정에 따라 달라지기 때문에 프로그램의 출력 결과는 실행할 때마다 순서가 바뀔 수 있다.</p>
<p>결국 <code>fork()</code> 이후에는 하나의 코드가 부모와 자식 프로세스에서 각각 실행되며, 그 실행 순서는 운영체제가 결정한다는 점을 이해해두어야 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/388429d4-220f-4fdf-8c99-e5018691544a.png" alt="" style="display:block;margin:0 auto" />

<h3>exec() — The System Call That Changes What a Process Runs</h3>
<p>The system call used when an already-created process needs to switch to running a different program is <code>exec()</code>. It replaces the currently running process entirely with a new program.</p>
<p>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.</p>
<p>The outcome of an <code>exec()</code> call is also clearly defined. If the call succeeds, the original program's execution flow is completely terminated, and any code written after <code>exec()</code> 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 <code>exec()</code> executes.</p>
<p><code>exec()</code> is commonly used together with <code>fork()</code>. The typical pattern is to create a child process with <code>fork()</code> and then call <code>exec()</code> within the child to run an entirely different program.</p>
<h3>exec() — 실행 내용을 바꾸는 시스템 호출</h3>
<p>이미 생성된 프로세스가 실행할 프로그램을 바꿔야 할 때 사용하는 시스템 호출이 <code>exec()</code>다. 현재 실행 중인 프로세스를 완전히 새로운 프로그램으로 교체하는 역할을 한다.</p>
<p>여기서 핵심은 새로운 프로세스가 만들어지는 것이 아니라는 점이다. 프로세스 자체, 즉 PID는 그대로 유지된다. 하지만 그 프로세스가 실행하던 코드 영역, 데이터 영역, 스택 영역이 모두 새로운 프로그램의 내용으로 완전히 교체된다. 프로세스라는 껍데기는 그대로 두고 안에서 실행되는 내용만 통째로 바꾸는 것이라고 이해하면 된다.</p>
<p><code>exec()</code>의 동작 결과도 명확하게 구분된다. 호출이 성공하면 기존 프로그램의 실행 흐름은 완전히 종료되고, <code>exec()</code> 이후에 작성된 코드는 실행되지 않는다. 이미 새로운 프로그램으로 교체되었기 때문에 돌아올 흐름 자체가 없어지는 것이다. 반대로 호출이 실패한 경우에는 교체가 이루어지지 않았기 때문에 기존 프로그램의 흐름이 그대로 유지되어 <code>exec()</code> 다음 줄의 코드가 실행된다.</p>
<p><code>exec()</code>는 보통 <code>fork()</code>와 함께 사용되는 경우가 많다. <code>fork()</code>로 자식 프로세스를 생성한 뒤, 자식 프로세스에서 <code>exec()</code>를 호출해 전혀 다른 프로그램을 실행시키는 패턴이 대표적인 활용 방식이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/683e7e66-00c3-4911-ad4d-6e26c7d6ca35.png" alt="" style="display:block;margin:0 auto" />

<h3>Analyzing the exec() Execution Flow Example</h3>
<p>This example is code that lets you directly observe how a process's execution flow changes before and after <code>exec()</code> is called. Let's follow the flow step by step.</p>
<p>First, a message is printed via <code>printf</code> before <code>exec()</code> is called. Up to this point, the original program's flow is executing normally.</p>
<p>Next, <code>execl("/bin/ls", "ls", NULL)</code> is called. The moment this single line executes, the current process is completely replaced by <code>/bin/ls</code> — the <code>ls</code> program that outputs a directory's file listing. The meaning of each argument is as follows.</p>
<p>The first argument, <code>/bin/ls</code>, 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, <code>ls</code>, specifies the name the executed program will recognize itself by. Even if you change the second argument to <code>hello</code>, the operating system will still execute <code>/bin/ls</code>, but internally the program will recognize its own name as <code>hello</code>. To avoid confusion, it is conventional to pass the same name as the executable file. The final <code>NULL</code> is a marker telling the operating system that the argument list ends here. Because <code>exec</code>-family functions can accept multiple arguments, the end must be explicitly indicated.</p>
<p>Once <code>execl()</code> succeeds, the original program's flow is completely gone. That's why the <code>printf("exec 호출 후\n")</code> written below it does not execute — there is no flow to return to.</p>
<p>The core takeaway from this example is one thing: <code>exec()</code> does not create a new process — it completely replaces the current process's execution content.</p>
<h3>exec() 실행 흐름 예제 분석</h3>
<p>이 예제는 <code>exec()</code> 호출 전후로 프로세스의 실행 흐름이 어떻게 바뀌는지를 직접 확인할 수 있는 코드다. 흐름을 단계별로 따라가보자.</p>
<p>먼저 <code>printf</code>를 통해 <code>exec()</code> 호출 전 메시지가 출력된다. 여기까지는 기존 프로그램의 흐름이 그대로 실행되는 구간이다.</p>
<p>그다음 <code>execl("/bin/ls", "ls", NULL)</code>이 호출된다. 이 한 줄이 실행되는 순간 현재 프로세스는 <code>/bin/ls</code>, 즉 디렉토리 파일 목록을 출력하는 <code>ls</code> 프로그램으로 완전히 교체된다. 각 인자의 의미를 살펴보면 다음과 같다.</p>
<p>첫 번째 인자 <code>/bin/ls</code>는 운영체제가 실제로 실행할 파일의 전체 경로다. 운영체제는 이 경로를 보고 어떤 파일을 실행할지 결정한다. 두 번째 인자 <code>ls</code>는 실행된 프로그램이 자기 자신을 어떤 이름으로 인식할지를 지정하는 값이다. 만약 두 번째 인자를 <code>hello</code>로 바꿔도 운영체제는 여전히 <code>/bin/ls</code>를 실행하지만, 실행된 프로그램 내부에서는 자신을 <code>hello</code>라는 이름으로 인식하게 된다. 혼란을 피하기 위해 실행 파일의 이름과 프로그램이 인식하는 이름을 동일하게 전달하는 것이 관례다. 마지막 <code>NULL</code>은 인자 목록이 여기서 끝났음을 운영체제에게 알려주는 표식이다. <code>exec</code> 계열 함수는 여러 인자를 받을 수 있기 때문에 끝을 명시적으로 표시해줘야 한다.</p>
<p><code>execl()</code> 호출이 성공하면 기존 프로그램의 흐름은 완전히 사라진다. 그래서 그 아래에 작성된 <code>printf("exec 호출 후\n")</code>는 실행되지 않는다. 돌아올 흐름 자체가 없어지기 때문이다.</p>
<p>이 예제에서 확인할 수 있는 핵심은 하나다. <code>exec()</code>는 새로운 프로세스를 만드는 것이 아니라, 현재 프로세스의 실행 내용을 완전히 교체한다는 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a057792d-d320-4bca-a81b-a4012c9ab2b5.png" alt="" style="display:block;margin:0 auto" />

<h3>The Combination of fork() and exec()</h3>
<p>In practice, <code>exec()</code> is rarely used alone — it is almost always used together with <code>fork()</code>. Let's look at how this combination works.</p>
<p>First, <code>fork()</code> is called, creating parent and child processes. The child process initially executes the same code as the parent, but the moment <code>execl("/bin/ls", "ls", NULL)</code> is called on the child's side, the child's execution content is completely replaced by the <code>ls</code> program. Afterward, the parent continues executing its own code while the child, now replaced by <code>ls</code>, outputs the directory file listing. As a result, the parent's output and the <code>ls</code> output from the child both appear together.</p>
<p>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 <code>fork()</code>, then calls <code>exec()</code> 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.</p>
<h2>fork()와 exec()의 조합</h2>
<p>실제 운영체제에서 <code>exec()</code>는 단독으로 사용되기보다 대부분 <code>fork()</code>와 함께 사용된다. 이 조합이 어떻게 동작하는지 살펴보자.</p>
<p>먼저 <code>fork()</code>가 호출되면 부모 프로세스와 자식 프로세스가 생성된다. 자식 프로세스는 처음에는 부모와 동일한 코드를 실행하다가, 자식 프로세스 쪽에서 <code>execl("/bin/ls", "ls", NULL)</code>이 호출되는 순간 자식 프로세스의 실행 내용이 <code>ls</code> 프로그램으로 완전히 교체된다. 이후 부모 프로세스는 자신의 코드를 그대로 이어서 실행하고, 자식 프로세스는 <code>ls</code> 프로그램으로 교체되어 디렉토리 파일 목록을 출력한다. 결과적으로 부모 프로세스의 출력과 자식 프로세스에서 실행된 <code>ls</code>의 결과가 함께 나타나게 된다.</p>
<p>이 패턴이 중요한 이유는 바로 쉘에서 명령어를 실행할 때 사용되는 기본 방식이기 때문이다. 사용자가 터미널에 명령어를 입력하면 쉘은 <code>fork()</code>로 자식 프로세스를 생성하고, 그 자식 프로세스에서 <code>exec()</code>를 호출해 입력한 명령어에 해당하는 프로그램으로 교체한다. 부모인 쉘 프로세스는 그대로 유지되면서 다음 명령어를 받을 준비를 하고, 자식 프로세스가 명령어를 실행하는 것이다. 앞서 프로세스 트리에서 쉘이 사용자 프로그램들의 부모 프로세스가 된다고 했던 것이 바로 이 구조 때문이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5fef5fb6-20fd-4e39-86a7-886dbf6699ba.png" alt="" style="display:block;margin:0 auto" />

<h3>exit() — The System Call for Terminating a Process</h3>
<p><code>exit()</code> 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 <code>exec()</code> examined earlier, it is a function from which execution does not continue after it's called.</p>
<p>When a process calls <code>exit()</code>, 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.</p>
<p>There's one more thing to note here. The termination information passed through <code>exit()</code> 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 <code>wait()</code> system call we'll look at next.</p>
<h3>exit() — 프로세스 종료 시스템 호출</h3>
<p><code>exit()</code>는 실행 중인 프로세스를 종료할 때 사용하는 시스템 호출이다. 호출되는 순간 현재 프로세스는 즉시 종료되며, 이후 어떤 코드도 실행되지 않는다. 앞서 살펴본 <code>exec()</code>와 마찬가지로 호출 이후 실행 흐름이 이어지지 않는 함수다.</p>
<p>프로세스가 <code>exit()</code>를 호출하면 운영체제에게 이 프로세스가 종료됨을 알린다. 이 과정에서 운영체제는 해당 프로세스가 사용하던 메모리, 열려 있던 파일 같은 자원들을 정리해서 다시 시스템으로 돌려보낸다. 프로세스가 점유하고 있던 자원이 이 시점에 회수되는 것이다.</p>
<p>여기서 한 가지 더 짚고 넘어갈 점이 있다. <code>exit()</code>를 통해 전달되는 종료 정보가 운영체제에 함께 전달된다는 것이다. 이 정보는 부모 프로세스가 나중에 확인할 수 있도록 보관된다. 자식 프로세스가 어떻게 종료되었는지를 부모 프로세스가 알 수 있는 구조가 마련되어 있는 셈이다. 이 부분이 실제로 어떻게 처리되는지는 다음에 살펴볼 <code>wait()</code> 시스템 호출을 통해 구체적으로 확인하게 될 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e15c6624-158e-4d9b-84de-35039ee5485d.png" alt="" style="display:block;margin:0 auto" />

<h3>Analyzing the exit() Example</h3>
<p>The moment <code>exit(0)</code> is called, the process terminates immediately. As a result, the <code>printf("이 문장은 실행되지 않는다.")</code> written below it does not execute. Even though the code exists, the flow after <code>exit()</code> is completely blocked.</p>
<p>Let's also note the meaning of the number passed to <code>exit()</code> — the exit status value. <code>0</code> means the process terminated normally without any problems. Values other than <code>0</code>, such as <code>1</code> or <code>2</code>, 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.</p>
<p>The core takeaway from this example is one thing: once <code>exit()</code> is called, no subsequent code ever executes, and the program's execution ends immediately on the spot.</p>
<h3>exit() 예제 분석</h3>
<p><code>exit(0)</code>이 호출되는 순간 프로세스는 즉시 종료된다. 그렇기 때문에 그 아래에 작성된 <code>printf("이 문장은 실행되지 않는다.")</code>는 실행되지 않는다. 코드가 존재하더라도 <code>exit()</code> 이후의 흐름은 완전히 차단되는 것이다.</p>
<p>여기서 <code>exit()</code>에 전달되는 숫자, 즉 종료 상태값의 의미도 짚어두자. <code>0</code>은 프로세스가 문제 없이 정상적으로 종료되었음을 뜻한다. 반면 <code>1</code>이나 <code>2</code> 같은 0 이외의 값들은 어떤 종류의 오류가 발생했는지를 구분하는 데 사용된다. 이 종료 상태값은 앞서 언급했듯이 부모 프로세스가 나중에 확인할 수 있도록 운영체제에 전달된다.</p>
<p>이 예제를 통해 확인할 수 있는 핵심은 하나다. <code>exit()</code>가 호출되면 그 이후의 코드는 절대 실행되지 않으며, 프로그램의 실행이 그 자리에서 즉시 끝난다는 것이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6a1c880d-b267-4c39-a16d-e011867056d4.png" alt="" style="display:block;margin:0 auto" />

<h3>wait() — The System Call for Waiting on a Child Process to Terminate</h3>
<p><code>wait()</code> is a system call that causes a parent process to wait until its child process terminates.</p>
<p>When a parent process calls <code>wait()</code>, 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 <code>exit()</code> is delivered to the parent process at this moment.</p>
<p>One of <code>wait()</code>'s important roles is controlling execution order. Using <code>wait()</code> guarantees that the parent process won't terminate before the child. We said that after <code>fork()</code>, which runs first — parent or child — depends on the operating system's scheduling; by using <code>wait()</code>, you can clearly establish that the parent only moves on to the next step after the child has fully terminated. For this reason, <code>wait()</code> is also a system call used for synchronization between parent and child processes.</p>
<h3>wait() — 자식 프로세스의 종료를 기다리는 시스템 호출</h3>
<p><code>wait()</code>는 부모 프로세스가 자식 프로세스가 종료될 때까지 기다리도록 만드는 시스템 호출이다.</p>
<p>부모 프로세스가 <code>wait()</code>를 호출하면 자식 프로세스가 아직 실행 중인 경우 부모는 대기 상태로 들어간다. 자식 프로세스가 종료되면 그제서야 부모 프로세스가 다시 실행을 이어간다. 이 과정에서 부모 프로세스는 자식 프로세스의 종료 상태값도 함께 회수할 수 있다. 앞서 <code>exit()</code>에서 살펴봤던 종료 상태값이 바로 이 시점에 부모 프로세스에게 전달되는 것이다.</p>
<p><code>wait()</code>의 중요한 역할 중 하나는 실행 순서의 제어다. <code>wait()</code>를 사용하면 부모 프로세스가 자식보다 먼저 종료되지 않는다는 것이 보장된다. <code>fork()</code> 이후에는 부모와 자식 중 누가 먼저 실행될지 운영체제의 스케줄링에 따라 달라진다고 했는데, <code>wait()</code>를 사용하면 자식이 완전히 종료된 이후에 부모가 다음 단계로 넘어간다는 순서를 명확히 정할 수 있다. 이런 이유로 <code>wait()</code>는 부모와 자식 프로세스 간의 동기화에 사용되는 시스템 호출이기도 하다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/44d56035-9b61-47bf-9cc4-ef1213c3b6f0.png" alt="" style="display:block;margin:0 auto" />

<h3>Analyzing the wait() Example</h3>
<p>This example shows how <code>fork()</code>, <code>exit()</code>, and <code>wait()</code> are used together to control the execution order of parent and child processes.</p>
<p>First, a child process is created via <code>pid_t pid = fork()</code>. The child process then prints a "child process running" message, waits briefly with <code>sleep(2)</code>, and terminates with <code>exit(0)</code>.</p>
<p>Meanwhile, after <code>fork()</code>, the parent process doesn't immediately execute the next code — it calls <code>wait(NULL)</code> and stays in the waiting state until the child process fully terminates. Only after the child terminates with <code>exit(0)</code> does the parent wake up from the waiting state, print the "parent running after child terminated" message, and continue executing the subsequent code.</p>
<p>This example makes <code>wait()</code>'s role clear. Without <code>wait()</code>, there's no guarantee which of the parent and child runs first; but by using <code>wait()</code>, it is solidly guaranteed that the parent runs after the child terminates. Ultimately, <code>wait()</code> should be understood as the means by which a parent process waits for its child's termination and handles the result.</p>
<h3>wait() 예제 분석</h3>
<p>이 예제는 <code>fork()</code>, <code>exit()</code>, <code>wait()</code>가 함께 사용되어 부모와 자식 프로세스의 실행 순서가 어떻게 제어되는지를 보여준다.</p>
<p>먼저 <code>pid_t pid = fork()</code>를 통해 자식 프로세스가 생성된다. 이후 자식 프로세스는 "자식 프로세스 실행" 메시지를 출력하고, <code>sleep(2)</code>로 잠시 대기한 뒤 <code>exit(0)</code>으로 종료된다.</p>
<p>한편 부모 프로세스는 <code>fork()</code> 이후 곧바로 다음 코드를 실행하지 않고 <code>wait(NULL)</code>을 호출하여 자식 프로세스가 완전히 종료될 때까지 대기 상태로 머문다. 자식 프로세스가 <code>exit(0)</code>으로 종료되고 나서야 부모 프로세스가 대기 상태에서 깨어나 "자식 종료 후 부모 실행" 메시지를 출력하며 이후 코드를 이어서 실행한다.</p>
<p>이 예제를 통해 <code>wait()</code>의 역할이 명확하게 드러난다. <code>wait()</code>가 없다면 부모와 자식 중 누가 먼저 실행될지 보장할 수 없지만, <code>wait()</code>를 사용함으로써 자식이 종료된 이후에 부모가 실행된다는 순서가 확실하게 보장된다. 결국 <code>wait()</code>는 부모 프로세스가 자식 프로세스의 종료를 기다리고 그 결과를 처리하는 방법이라고 이해하면 된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/02a9af4c-155d-4c0b-9e4f-e1dfb3d5513d.png" alt="" style="display:block;margin:0 auto" />

<h3>Zombie Processes</h3>
<p><code>exit()</code> and <code>wait()</code> must be used as a matched pair. When the two don't properly interlock, a state called a <strong>zombie process</strong> occurs.</p>
<p>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 <code>exit()</code> 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 <code>wait()</code>, 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.</p>
<p>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 <code>wait()</code>.</p>
<h3>좀비 프로세스</h3>
<p><code>exit()</code>와 <code>wait()</code>는 짝을 이루어 사용되어야 한다. 이 둘이 제대로 맞물리지 않으면 좀비 프로세스라는 상태가 발생한다.</p>
<p>좀비 프로세스란 자식 프로세스의 실행은 이미 끝났지만 완전히 정리되지 않고 시스템에 흔적으로 남아있는 상태의 프로세스를 말한다. 자식 프로세스가 <code>exit()</code>를 호출해 종료되면 운영체제는 그 종료 상태값을 부모 프로세스가 확인할 수 있도록 잠시 보관해둔다. 그런데 부모 프로세스가 <code>wait()</code>를 호출하지 않으면 이 정보를 아무도 회수하지 않는 상황이 된다. 자식 프로세스는 실행이 끝났음에도 불구하고 종료 정보가 회수되지 않아 완전히 정리되지 못한 채 시스템에 남게 되는 것이다.</p>
<p>좀비 프로세스 자체는 CPU를 사용하지 않기 때문에 당장 큰 문제처럼 보이지 않을 수 있다. 하지만 좀비 프로세스가 누적되면 PID와 같은 시스템 자원을 계속 점유하게 되어 장기적으로 시스템 자원 낭비로 이어질 수 있다. 따라서 자식 프로세스를 생성하는 프로그램을 작성할 때는 반드시 부모 프로세스에서 <code>wait()</code>를 통해 자식의 종료를 올바르게 처리해주어야 한다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1792d70e-1eb8-40cb-81d0-8be534d7759b.png" alt="" style="display:block;margin:0 auto" />

<h3>How Zombie Processes Are Created</h3>
<p>Let's look specifically at the path through which a zombie process comes into being.</p>
<p>When a child process calls <code>exit()</code>, 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 <code>wait()</code>, 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.</p>
<p>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.</p>
<p>To summarize: a zombie process occurs when <code>exit()</code> and <code>wait()</code> fail to pair up. The child has terminated via <code>exit()</code>, but the parent hasn't retrieved the termination information via <code>wait()</code>. 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.</p>
<h3>좀비 프로세스가 만들어지는 과정</h3>
<p>좀비 프로세스가 어떤 경로로 생겨나는지 그 과정을 구체적으로 살펴보자.</p>
<p>자식 프로세스가 <code>exit()</code>를 호출하면 실행은 끝난다. 이때 운영체제는 자식 프로세스의 종료 정보를 부모 프로세스가 확인할 수 있도록 보관해두어야 한다. 그런데 부모 프로세스가 <code>wait()</code>를 호출하지 않으면 이 종료 정보를 전달할 방법이 없다. 그렇다고 운영체제가 이 정보를 그냥 버릴 수도 없다. 결국 운영체제는 자식 프로세스의 PCB를 커널 영역에 그대로 남겨두게 된다.</p>
<p>바로 이 상태가 좀비 프로세스다. 실행은 이미 끝났지만 PCB가 회수되지 않아 시스템에 흔적으로 남아있는 프로세스인 것이다.</p>
<p>정리하면 좀비 프로세스는 <code>exit()</code>와 <code>wait()</code>가 짝을 이루지 못할 때 발생한다. 자식은 <code>exit()</code>로 종료했지만 부모가 <code>wait()</code>로 종료 정보를 회수하지 않은 상황이다. 이 두 시스템 호출이 제대로 맞물려야만 자식 프로세스의 PCB가 커널에서 완전히 정리되고, 프로세스의 생명 주기가 온전히 마무리된다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3ffd3083-0128-4d65-93d0-cfae8331609e.png" alt="" style="display:block;margin:0 auto" />

<h3>The Impact of Zombie Processes on the System</h3>
<p>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.</p>
<p>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.</p>
<p>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 <code>wait()</code>.</p>
<h3>좀비 프로세스가 시스템에 미치는 영향</h3>
<p>좀비 프로세스는 실행이 끝난 상태이기 때문에 CPU를 사용하거나 어떤 연산을 수행하지는 않는다. 그래서 당장 눈에 띄는 문제가 없는 것처럼 보일 수 있다.</p>
<p>하지만 좀비 프로세스는 PCB가 커널 영역에 그대로 남아있기 때문에 PID와 같은 시스템 자원을 계속 점유하고 있다. PID는 시스템에서 사용할 수 있는 개수가 한정되어 있는 자원이다. 좀비 프로세스가 하나둘 쌓이기 시작하면 사용 가능한 PID가 그만큼 줄어들고, 극단적인 경우에는 새로운 프로세스를 생성할 수 없는 상황까지 이어질 수 있다.</p>
<p>결국 좀비 프로세스가 누적될수록 PID 자원이 낭비되고 운영체제의 관리 효율도 떨어지게 된다. 실행도 하지 않으면서 자원만 붙들고 있는 상태가 계속되는 것이다. 이것이 부모 프로세스에서 반드시 <code>wait()</code>를 통해 자식 프로세스의 종료를 제대로 처리해주어야 하는 이유다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9363bb85-890f-44a2-a0ef-9839b9b824b9.png" alt="" style="display:block;margin:0 auto" />

<h3>Analyzing the Zombie Process Example</h3>
<p>This example is code that lets you directly observe how a zombie process is created when <code>wait()</code> is absent.</p>
<p>The child process prints a "Child process terminated" message and immediately terminates. Meanwhile, the parent process, without calling <code>wait()</code>, prints a "Parent process running" message and enters the waiting state via <code>sleep(2)</code>.</p>
<p>The output order can change every time the program runs, because after <code>fork()</code>, 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 <code>wait()</code>.</p>
<p>Even after the child terminates, if the parent never calls <code>wait()</code>, 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.</p>
<p>Ultimately, this example illustrates what goes wrong when <code>exit()</code> and <code>wait()</code> fail to pair up. Any program that creates child processes must use <code>wait()</code> to properly handle the child's termination.</p>
<h3>좀비 프로세스 발생 예제 분석</h3>
<p>이 예제는 <code>wait()</code>가 없을 때 좀비 프로세스가 어떻게 만들어지는지를 직접 확인할 수 있는 코드다.</p>
<p>자식 프로세스는 "Child process 종료" 메시지를 출력한 뒤 바로 종료된다. 반면 부모 프로세스는 <code>wait()</code>를 호출하지 않은 채 "Parent process 실행중" 메시지를 출력하고 <code>sleep(2)</code>로 대기 상태에 들어간다.</p>
<p>출력 순서는 실행할 때마다 달라질 수 있다. <code>fork()</code> 이후에는 부모와 자식 중 누가 먼저 실행될지 운영체제의 스케줄링에 따라 결정되기 때문이다. 하지만 이 예제에서 중요한 것은 출력 순서가 아니다. 핵심은 부모 프로세스가 <code>wait()</code>를 호출하지 않은 상태에서 자식 프로세스가 먼저 종료될 수 있는 상황이 만들어졌다는 점이다.</p>
<p>자식이 종료된 이후에도 부모가 <code>wait()</code>를 호출하지 않으면 운영체제는 자식 프로세스의 종료 정보를 바로 정리하지 못한다. 그래서 커널에 자식 프로세스의 PCB 일부를 잠시 남겨두게 된다. 실행은 끝났지만 종료 정보가 회수되지 않은 이 상태가 바로 좀비 프로세스가 발생하는 조건이다.</p>
<p>결국 이 예제는 <code>exit()</code>와 <code>wait()</code>가 짝을 이루지 못했을 때 어떤 문제가 생기는지를 보여주는 사례다. 자식 프로세스를 생성하는 프로그램에서는 반드시 <code>wait()</code>를 통해 자식의 종료를 올바르게 처리해주어야 한다.</p>
<hr />
<h2>Ubuntu Practice</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d7a35723-47de-45d9-8e01-25ddb2397b8b.png" alt="" style="display:block;margin:0 auto" />

<h3>fork() Lab</h3>
<p>The core takeaway from this lab is that after <code>fork()</code>, the parent and child processes have completely independent execution flows.</p>
<p>The moment <code>fork()</code> 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.</p>
<p>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 <code>fork()</code> through the order in which code is written. If a guaranteed order is required, synchronization mechanisms like <code>wait()</code> 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.</p>
<p>이번 실습을 통해 확인할 수 있는 핵심은 <code>fork()</code> 이후 부모와 자식 프로세스가 완전히 독립적인 실행 흐름을 가진다는 점이다.</p>
<p><code>fork()</code>가 호출되는 순간 두 프로세스는 각자의 길을 간다. 이후 어느 쪽이 먼저 실행될지는 프로그램 코드가 결정하는 것이 아니라 운영체제의 스케줄링에 의해 결정된다. 그렇기 때문에 같은 코드를 실행하더라도 실행할 때마다 출력 순서가 달라질 수 있다.</p>
<p>이 점은 앞으로 프로세스를 다루는 프로그램을 작성할 때 반드시 염두에 두어야 할 부분이다. <code>fork()</code> 이후의 실행 순서를 코드 작성 순서로 제어하려 해서는 안 된다. 순서를 보장해야 하는 상황이라면 <code>wait()</code>와 같은 동기화 수단을 명시적으로 사용해야 한다. 결국 이번 실습은 프로세스의 독립성과 스케줄링의 비결정성, 그리고 그것을 제어하기 위한 시스템 호출의 필요성을 한꺼번에 보여주는 예제라고 할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f3461044-fd1b-498f-86b3-36fbbfaa7907.png" alt="" style="display:block;margin:0 auto" />

<h3>exec() Lab</h3>
<p>This example is code that lets you directly observe how a process's execution flow changes when <code>exec()</code> is called.</p>
<p>Looking at the output, the content written before the <code>exec()</code> call appears normally, but the code written after the <code>exec()</code> call does not produce output. The moment <code>exec()</code> 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.</p>
<p>The core takeaway from this example is one thing: <code>exec()</code> 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 <code>fork()</code> ; a call that creates a new process ; and <code>exec()</code>; a call that changes the content of an existing one.</p>
<p>이 예제는 <code>exec()</code>가 호출되었을 때 기존 프로그램의 실행 흐름이 어떻게 바뀌는지를 직접 확인할 수 있는 코드다.</p>
<p>실행 결과를 보면 <code>exec()</code> 호출 전에 작성된 출력은 정상적으로 나타나지만, <code>exec()</code> 호출 후에 작성된 코드는 출력되지 않는다. <code>exec()</code>가 성공하는 순간 현재 프로세스의 코드와 실행 내용이 완전히 새로운 프로그램으로 교체되기 때문이다. 교체가 이루어진 이후에는 기존 프로그램으로 돌아올 흐름 자체가 사라지기 때문에 그 아래에 작성된 코드는 절대 실행되지 않는다.</p>
<p>이 예제를 통해 확인할 수 있는 핵심은 하나다. <code>exec()</code>는 새로운 프로세스를 만드는 것이 아니라 현재 프로세스의 실행 내용을 통째로 바꾸는 것이라는 점이다. 프로세스 자체는 그대로 유지되고 그 안에서 실행되는 프로그램만 교체된다. <code>fork()</code>가 프로세스를 새로 만드는 호출이라면, <code>exec()</code>는 기존 프로세스의 내용을 바꾸는 호출이라는 차이를 이 예제가 명확하게 보여준다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/016e020e-6ca4-4757-a9f8-db19b2432217.png" alt="" style="display:block;margin:0 auto" />

<h3>fork() + exec() Combined Lab</h3>
<p>This example demonstrates the typical usage flow of <code>fork()</code> and <code>exec()</code> — the most commonly used combination in actual operating systems.</p>
<p>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 <code>exec()</code> with the <code>ls</code> 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.</p>
<p>The core takeaway from this example is the basic process creation pattern in which <code>fork()</code> and <code>exec()</code> are used together. Creating a new process with <code>fork()</code> and then calling <code>exec()</code> 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 <code>fork()</code> and <code>exec()</code>, and the non-determinism of execution order.</p>
<p>이 예제는 실제 운영체제에서 가장 많이 사용되는 <code>fork()</code>와 <code>exec()</code>의 전형적인 사용 흐름을 보여준다.</p>
<p>실행할 때마다 출력 순서가 매번 달라진다는 점이 눈에 띈다. 부모 프로세스는 자신의 메시지를 출력하고, 자식 프로세스는 <code>exec()</code>를 통해 <code>ls</code> 프로그램으로 교체되어 디렉토리 목록을 출력한다. 둘 중 누가 먼저 실행될지는 운영체제의 스케줄링이 결정하기 때문에 실행할 때마다 결과의 순서가 바뀌는 것이다.</p>
<p>이 예제를 통해 확인할 수 있는 핵심은 <code>fork()</code>와 <code>exec()</code>가 함께 사용되는 기본적인 프로세스 생성 패턴이다. <code>fork()</code>로 새로운 프로세스를 생성하고, 자식 프로세스에서 <code>exec()</code>를 호출해 전혀 다른 프로그램으로 교체하는 이 구조가 운영체제에서 프로그램을 실행하는 가장 기본적인 방식이다. 앞서 살펴봤던 쉘이 사용자의 명령어를 실행하는 방식도 바로 이 패턴을 따른다. <code>fork()</code>와 <code>exec()</code>의 역할 분담, 그리고 실행 순서의 비결정성이라는 두 가지 개념을 이 예제 하나로 함께 확인할 수 있다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0ec56c9e-f34e-4219-9db3-a9b4043764d9.png" alt="" style="display:block;margin:0 auto" />

<h3>exit() Lab</h3>
<p>This example is code that lets you directly observe that a process's execution terminates immediately when <code>exit()</code> is called.</p>
<p>Looking at the output, only the content written before <code>exit(0)</code> appears; the <code>printf("이 문장은 실행되지 않습니다\n")</code> written below it does not. The moment <code>exit()</code> is called, the process terminates on the spot instantly, and the subsequent code never even gets the chance to execute.</p>
<p>The core takeaway from this example is that <code>exit()</code> 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 <code>exit()</code> is called, is when the process ends. Just like <code>exec()</code> examined earlier, this example clearly demonstrates that <code>exit()</code> is also a function from which execution flow is completely blocked after it is called.</p>
<p>이 예제는 <code>exit()</code>가 호출되었을 때 프로세스의 실행이 즉시 종료됨을 직접 확인할 수 있는 코드다.</p>
<p>실행 결과를 보면 <code>exit(0)</code> 이전에 작성된 내용만 출력되고, 그 아래에 작성된 <code>printf("이 문장은 실행되지 않습니다\n")</code>는 출력되지 않는다. <code>exit()</code>가 호출되는 순간 프로세스의 실행이 그 자리에서 즉시 종료되고, 이후의 코드는 실행될 기회조차 얻지 못하기 때문이다.</p>
<p>이 예제를 통해 확인할 수 있는 핵심은 <code>exit()</code>가 프로세스의 명확한 종료 지점이라는 것이다. 함수의 끝이나 프로그램의 마지막 줄까지 도달하지 않더라도, <code>exit()</code>가 호출된 바로 그 순간이 프로세스가 끝나는 시점이 된다. 앞서 살펴본 <code>exec()</code>와 마찬가지로 <code>exit()</code> 역시 호출 이후의 흐름이 완전히 차단되는 함수라는 점을 이 예제가 명확하게 보여준다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a0feeeb3-e314-4946-9ba0-83fc9a5b3a20.png" alt="" style="display:block;margin:0 auto" />

<h3>wait() Lab</h3>
<p>This example is code that lets you directly observe the behavior of a parent process waiting for a child process to terminate when <code>wait(NULL)</code> is called.</p>
<p>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.</p>
<p>This is possible because the parent process called <code>wait(NULL)</code> and entered the waiting state until the child terminated. We said that after <code>fork()</code>, there's no guarantee which of the parent or child runs first — but by using <code>wait()</code>, the execution order is clearly guaranteed: the parent only moves to the next step after the child has fully terminated.</p>
<p>The core takeaway from this example is that <code>wait()</code> 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.</p>
<p>이 예제는 부모 프로세스가 <code>wait(NULL)</code>을 호출했을 때 자식 프로세스의 종료를 기다리는 동작을 직접 확인할 수 있는 코드다.</p>
<p>실행 결과를 보면 "자식 프로세스 실행" 메시지가 먼저 출력되고, 잠시 텀이 생긴 뒤에 "자식 종료 후 부모 실행" 메시지가 출력된다. 자식 프로세스가 먼저 실행되고 완전히 종료된 이후에야 부모 프로세스의 출력이 나타나는 것이다.</p>
<p>이것이 가능한 이유는 부모 프로세스가 <code>wait(NULL)</code>을 호출하며 자식 프로세스가 종료될 때까지 대기 상태에 들어갔기 때문이다. <code>fork()</code> 이후에는 부모와 자식 중 누가 먼저 실행될지 보장할 수 없다고 했는데, <code>wait()</code>를 사용함으로써 자식이 완전히 종료된 이후에 부모가 다음 단계로 넘어간다는 실행 순서가 명확하게 보장된다.</p>
<p>이 예제를 통해 확인할 수 있는 핵심은 <code>wait()</code>가 단순히 자식의 종료를 기다리는 것을 넘어, 부모와 자식 프로세스 사이의 실행 순서를 동기화하는 역할을 한다는 점이다.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d14b52b0-62e0-416e-b27b-4a037c6c3110.png" alt="" style="display:block;margin:0 auto" />

<p>이 예제는 부모 프로세스가 <code>wait()</code>를 호출하지 않았을 때 좀비 프로세스가 발생하는 상황을 직접 확인할 수 있는 코드다.</p>
<p>파일을 실행하면 자식 프로세스는 메시지를 출력하고 종료된다. 부모 프로세스는 <code>sleep()</code>으로 대기 상태에 들어간다. 겉으로 보기에는 자식 프로세스가 정상적으로 종료된 것처럼 보인다. 출력도 나왔고 실행도 끝났으니 아무 문제가 없어 보이는 것이다.</p>
<p>하지만 부모 프로세스 코드에 <code>wait()</code>가 없다. 자식 프로세스가 <code>exit()</code>로 종료되었지만 부모가 <code>wait()</code>를 호출하지 않았기 때문에 운영체제는 자식의 종료 정보를 부모에게 전달하지 못한 채 커널에 그대로 남겨두게 된다. 실행은 끝났지만 PCB가 완전히 정리되지 않은 상태, 즉 좀비 프로세스가 만들어진 것이다.</p>
<p>이 예제가 보여주는 핵심은 좀비 프로세스는 겉으로 드러나지 않는다는 점이다. 출력 결과만 봐서는 정상적으로 종료된 것과 구별이 되지 않는다. 하지만 내부적으로는 커널에 자식 프로세스의 정보가 남아 자원을 점유하고 있다. 이것이 <code>wait()</code>를 반드시 짝을 맞춰 사용해야 하는 이유다.</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[Software Testing - Part 1: Core Concepts / Part 2: Test Process (1) / Part 3: Test Process (2)]]></title><description><![CDATA[1️⃣ Core Concept of the Test2️⃣ Test Process Details - 13️⃣ Test Process Details - 2

Core Concept of the Test
Impact of Software Defects


What is the purpose of testing? The software we develop is u]]></description><link>https://heesu.tech/software-testing-part-1-core-concepts-part-2-test-process-1-part-3-test-process-2</link><guid isPermaLink="true">https://heesu.tech/software-testing-part-1-core-concepts-part-2-test-process-1-part-3-test-process-2</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Tue, 24 Mar 2026 12:56:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/59cafd16-2837-4244-9428-2635de1bd69c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>1️⃣ Core Concept of the Test<br />2️⃣ Test Process Details - 1<br />3️⃣ Test Process Details - 2</p>
<hr />
<h1>Core Concept of the Test</h1>
<h3><strong>Impact of Software Defects</strong></h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/21c38741-20c1-48c3-9d92-9c31eef4ca47.png" alt="" style="display:block;margin:0 auto" />

<p>What is the purpose of testing? The software we develop is ultimately embedded in final products such as automobiles, aircraft, and mobile devices. A small mistake made by a developer becomes a fault, which then leads to a failure. These failures propagate to the higher-level subsystems that contain the software, then from the subsystem to the system, and finally to the end product, ultimately becoming a major hazard that leads to an accident. This results in harm; including casualties, economic losses, and environmental disasters. A small error originating in software can propagate through fault propagation and escalate into a critical problem. When a fault occurs due to a small human mistake, the primary goal of testing is to detect that fault. By discovering faults through testing, they can be contained rather than propagated to higher-level systems, ultimately reducing the likelihood of accidents. For this reason, testing must be conducted systematically by a third party based on predefined test cases. This is the purpose and role of testing.</p>
<hr />
<h3><strong>[Reference] Difference Between Testing and Debugging</strong></h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9cbb3c61-1b15-4fb8-b574-3ec50f7d72be.png" alt="" style="display:block;margin:0 auto" />

<p>Testing and debugging are easily confused, but they are clearly distinct concepts with different purposes and roles.</p>
<ul>
<li><p><strong>The primary purpose of testing</strong> is to discover unknown faults. <strong>Debugging,</strong> on the other hand, aims to accurately correct already known faults identified through testing. There is also a difference in terms of who is responsible. Testing can be performed by internal team members, but a third party such as an external test team can discover more unknown faults by approaching the system from a different perspective. Debugging, however, is best handled by internal developers who are familiar with the system, as it requires locating and correcting known faults.</p>
</li>
<li><p>The key activities also differ. The core activity of testing is fault detection, and test cases must be prepared in advance to carry this out systematically. In debugging, the first step is fault localization; identifying the exact location of the fault, such as which bit in memory is affected or between which modules in a connected system the fault occurred. This is followed by fault identification, which involves determining the type of fault, such as whether it is a compilation error or a logical error. Finally, fault correction, the act of properly fixing the identified fault, is the concluding activity of debugging.</p>
</li>
</ul>
<p>In conclusion, testing is the activity of finding faults without prior knowledge of what went wrong, while debugging is the activity of precisely fixing faults with full knowledge of what the problem is.</p>
<hr />
<h3>Error, Defect, Failure 용어</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9c3a34b2-652f-4676-917b-26f3e6edaaca.png" alt="" style="display:block;margin:0 auto" />

<p>In the context of software quality, it is important to clearly distinguish between three terms: <strong>Error, Defect, and Failure.</strong></p>
<ul>
<li><p>An error is the concept that causes a defect and refers to a mistake made by a person, typically a developer or analyst. In other words, it refers to the incorrect human action itself. An error leads to a defect, also referred to as a fault or bug.</p>
</li>
<li><p>A defect is a flaw embedded in a product as a result of an error, and it becomes the root cause of failures or problems. That is, if an error is the human act, then a defect is the flaw left behind in the code or artifact as a consequence of that act.</p>
</li>
<li><p>A failure is the state of malfunction that manifests when the system is actually executed due to an underlying defect. In other words, a failure is the phenomenon in which a latent defect surfaces in the runtime environment.</p>
</li>
</ul>
<p>In summary, a human mistake known as an error produces a defect embedded in the product, and that defect manifests as a failure during system execution. The three terms are linked in a cause-and-effect chain, and understanding each one clearly is fundamental to software quality management.</p>
<hr />
<h3><strong>Example of Error, Defect, and Failure</strong></h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d68196c3-5fa8-420a-ab5c-63073294d75d.png" alt="" style="display:block;margin:0 auto" />

<p>The concepts of Error, Defect, and Failure in software quality engineering can be illustrated through a concrete example. Consider the following simple pseudocode: Speed = Distance / Time. In this code, Time is positioned in the denominator of the division operation. If the value of Time becomes 0, a divide-by-zero exception will occur.</p>
<p>Mapping the three terms to this scenario makes each concept clear. First, the error is the developer's failure to consider the case where Time could be 0 — a mistake that occurred in the developer's thinking. Next, the defect is the resulting absence of exception-handling code in the program to address the case where Time equals 0 — a flaw reflected in the code itself. Finally, the failure is the occurrence of a Divide By Zero Exception when the program is actually executed with a Time value of 0 — the actual malfunction of the system.</p>
<p>The reason software quality engineering distinguishes between these three concepts is to propose appropriate countermeasures at each stage. At the error stage, training and process improvements are needed to reduce human mistakes. At the defect stage, code reviews and static analysis can eliminate defects before they lead to failures. By clearly distinguishing each stage, it becomes possible to accurately identify the root cause of a problem and systematically establish preventive and corrective measures.</p>
<hr />
<h3><strong>Common Misconceptions About Testing</strong></h3>
<p>There are three common misconceptions about software testing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3828cb3e-f7cf-4cbd-aebf-a60ed0b648f9.png" alt="" style="display:block;margin:0 auto" />

<p>The first misconception is that testing proves the absence of defects. However, the fundamental purpose of testing is not to prove that there are no defects, but to discover as many unknown defects as possible. Testing is, in essence, an activity that demonstrates the existence of defects. To achieve this, testing should be performed by a third party rather than the developer who built the product, and it should be conducted across diverse environments. Testing across various operating systems such as Windows, iOS, and Android can uncover defects that the developer had not anticipated.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/19945913-62c6-4f43-8fa0-fe0e232267e5.png" alt="" style="display:block;margin:0 auto" />

<p>The second misconception is that testing is easy and that all defects can be found. If testing is viewed simply as checking outputs against inputs, it may appear straightforward. However, proper testing requires thorough planning, design, and analysis, as well as a deep understanding of the product under development. Therefore, testing is by no means an easy task, and testers must also possess sufficient knowledge and competence in development. Furthermore, finding all defects is practically impossible. In the era of artificial intelligence, programs can have hundreds of millions of parameters, making it infeasible to test every possible combination within a reasonable timeframe. For this reason, it is important to incorporate the concept of defect prevention from the outset. Through activities such as reviews conducted during the requirements analysis and design phases, defects should be prevented from propagating to subsequent stages before testing even begins.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dd8b8616-fbc4-47af-a3d0-fabf844d705b.png" alt="" style="display:block;margin:0 auto" />

<p>The third misconception is that testing only needs to take place after the implementation, or coding, phase. From the perspective of the V-Model, however, testing should not begin after coding is complete. Rather, it must be initiated from the earliest stages of development, including requirements analysis and design. Test planning and preparation should begin before implementation starts, enabling quality to be managed systematically throughout the entire development lifecycle.</p>
<hr />
<h1>Test Process Details - 1</h1>
<p><strong>What is Systematic Testing?</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d72963c2-35c7-44fe-adb7-c9bf14080949.png" alt="" style="display:block;margin:0 auto" />

<p>To understand systematic testing, one must first think of the concept of PDCA. PDCA stands for Plan, Do, Check, and Act, and serves as the foundational framework for any organization to carry out its projects in a structured manner. Systematic testing refers to a state in which a process built upon this PDCA framework is established and followed throughout all testing activities.</p>
<hr />
<p><strong>1. Test Process from a PDCA Perspective</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/50e1f700-12dd-4926-a52d-ba440d97158a.png" alt="" style="display:block;margin:0 auto" />

<p>In the <strong>Plan</strong> phase, the overall test purpose, scope, schedule, and methods are established, and the features to be tested are selected. For example, in a shopping mall system, features such as member management and order history would be defined as test targets during this phase. Once the plan is established, test design follows, during which test cases are developed and test procedures are defined. From the perspective of the V-Model, test cases are derived based on requirements, architecture, and detailed design, meaning that test case development should begin immediately upon completion of the Plan phase. The test environment must also be prepared at this stage. This includes not only the environment in which the software operates independently, but also the environment in which it interfaces with related hardware devices, and even the real-world operating environment — for instance, if the software is embedded in a vehicle, testing must be conducted during actual driving conditions. Testing across diverse environments is essential to uncovering unknown defects. In particular, once coding is complete in the V-Model, there is likely insufficient time to plan and design tests. This is because the focus must shift to executing the actual tests on the running program. Therefore, it is essential to remember that test planning and design must be carried out in parallel with development in order to improve overall quality.</p>
<p>In the <strong>Do</strong> phase, the test cases developed during the Plan phase are actually executed and the test results are evaluated. This work can be automated using testing tools, which automatically assess results once test cases are provided as input.</p>
<p>In the <strong>Check and Act phases</strong>, the test results are analyzed and their adequacy is assessed. Based on the analysis, countermeasures are established and corrective actions are taken. If the identified issues are determined to stem not from defects themselves but from problems in the process or development environment, corrective action recommendations are issued accordingly.</p>
<p>In conclusion, it is essential to remember that systematic testing is not merely about executing tests, but rather an activity in which test planning, design, execution, evaluation, and improvement are carried out organically across the entire PDCA cycle.</p>
<hr />
<p><strong>[Reference] ISO/IEC/IEEE 29119 Testing Standard</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5c5be31a-8b3e-47e5-9a9b-33c6714f751e.png" alt="" style="display:block;margin:0 auto" />

<p>ISO/IEC/IEEE 29119 is the most representative international standard in the field of software testing, jointly established by three international organizations: ISO, IEC, and IEEE. This standard defines a multi-layer test process to ensure that testing is conducted systematically and correctly.</p>
<hr />
<p>The key emphasis of this standard is that the test process should not be addressed solely at the project level, but that a test process and its foundations must first be established at the organizational level. In other words, the standard requires a top-down structure in which the process and infrastructure for effective testing are first defined at the organizational level, cascaded down to the project and task management level, and ultimately executed by test engineers in accordance with those established criteria.</p>
<p>Specifically, this standard defines a multi-layer test process consisting of four layers, each following a top-down structure in which direction and criteria are passed from higher to lower levels.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/79cbb6ad-461f-43cd-bea3-2f2c2d3f78d2.png" alt="" style="display:block;margin:0 auto" />

<p>The first layer is the <strong>organizational test process.</strong> At this level, test policies and test strategies that apply across the entire organization are established. This serves as the highest-level foundation that sets the direction and criteria for all subsequent testing activities.</p>
<p>The second layer is the <strong>project-level test management process</strong>. Based on the policies and strategies established at the organizational level, a test management process is constructed at the project level. This process includes three key activities: test planning, monitoring and control, and test completion.</p>
<p>The third layer is the <strong>test management process</strong>. Building upon the project-level test management process, this layer involves establishing a more granular test management process broken down by task, phase, and test type. The same three activities; test planning, monitoring and control, and test completion; are applied at this level as well. Although the composition of activities is identical between the second and third layers, they are distinct stages that differ in their scope and level of application.</p>
<p>The fourth layer is the <strong>dynamic test execution process.</strong> This is the stage in which the software is actually executed and tested based on the criteria and processes passed down from the upper layers. To carry out testing effectively at this stage, test design and implementation must first be completed, followed by the setup and maintenance of the test environment, after which actual test execution takes place.</p>
<p>The key point is that testing should not be viewed merely as an execution activity. Rather, direction and criteria must first be defined and communicated at the organizational and project levels before testing begins. Subsequent chapters will examine in detail what each of the organizational, test management, and dynamic test layers specifically covers.</p>
<hr />
<p><strong>2. Organizational Test Process</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/00dfe485-4f16-43a0-bc4e-2602abf9898b.png" alt="" style="display:block;margin:0 auto" />

<p><strong>The organizational test process</strong> sits at a higher level than the testing carried out at the project level, and represents the stage at which the direction and criteria for testing across the entire organization are defined. This process consists of three key activities.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d04a5ef4-42da-4f70-a1e2-b2a494147660.png" alt="" style="display:block;margin:0 auto" />

<p><strong>The first activity</strong> is organizational test specification development. This involves developing an organizational test policy specification and an organizational test strategy specification based on the organization's test objectives. For example, if quality achievement targets are set in stages, this activity would involve concretely developing the policies and strategies needed to achieve 50% coverage at stage one, 30% at stage two, and 100% at stage three, broken down to the level of unit testing, integration testing, and so forth.</p>
<p><strong>The second activity</strong> is monitoring and control of organizational test specification utilization. This involves monitoring whether the organizational test specifications developed in the first activity are being effectively applied across projects and tasks within the organization, and exercising control when they are not being properly followed. Policies and strategies established at the organizational level must be applied to all subordinate projects, and appropriate control measures must be taken when this is not the case.</p>
<p><strong>The third activity</strong> is organizational test specification update. No matter how well-crafted the initial policies and strategies may be, issues are likely to surface when they are applied to real projects. For instance, if shortcomings in an existing policy are revealed during unit test execution, that policy must be revised and improved. The core of this activity lies in continuously incorporating feedback and results from the application of the specifications at the project and task level, and iteratively improving the organizational test policy and strategy specifications.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/bf2dd504-03a2-4e38-a910-9dac3532069f.png" alt="" style="display:block;margin:0 auto" />

<p>In conclusion, the organizational test process is not a one-time activity of establishing policies and strategies, but rather a cyclical process of monitoring whether the developed specifications are being correctly applied in practice, and continuously improving them based on the outcomes.</p>
<hr />
<p><strong>Example of Organizational Test Policy and Strategy Specifications</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/49d141e5-f80d-40ae-ae49-2079e7c5931f.png" alt="" style="display:block;margin:0 auto" />

<p>The organizational test policy and strategy specifications are structured hierarchically, beginning with the highest-level policy specification and cascading down to increasingly detailed strategy specifications.</p>
<p>At the highest level sits the <strong>organizational test policy specification</strong>. This document contains the broadest set of criteria applicable to all testing activities across the organization, and includes the test purpose, test process, test organization and roles, referenced test standards, test asset management and reuse methods, and policies for test process evaluation and improvement.</p>
<p>Below this sits the <strong>organizational test strategy specification</strong>. This layer defines more granular test strategies based on the policy specification, and includes risk management related to testing, test selection and prioritization, test documentation, configuration management, defect management, the use of automation tools, and individual test strategies related to performance and security testing.</p>
<p>The next layer defines strategies by specific test type. This is where decisions are made regarding which testing methods will be applied in practice, including unit testing strategy, integration testing strategy, and system testing strategy.</p>
<p>At the lowest layer sit the most granular strategies, including project-level test strategies and individual test-level test strategies. At this layer, specific strategies are defined that apply directly to particular projects or individual test units.</p>
<p>In conclusion, the organizational test policy and strategy specifications represent a system designed to ensure that consistent test criteria are communicated from the organizational policy specification, which captures the overall direction of the organization - all the way down to the project and individual test level through a progressively detailed hierarchical structure.</p>
<hr />
<h1>Test Process Details - 2</h1>
<p><strong>1. Test Management Process</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3ee2c0ed-c5b3-4cc1-a5c7-edc3fb6d5507.png" alt="" style="display:block;margin:0 auto" />

<p>Having previously examined the organizational test process, we established that test policies and strategies are formulated in the form of specifications. The test management process is the process by which these established strategies are reflected in the actual projects and individual tasks within the organization, and by which the testing activities carried out within them are systematically managed.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f12f71c3-b39c-42f5-b9be-69303b4ea08e.png" alt="" style="display:block;margin:0 auto" />

<p>The concept of "management" here is directly linked to the PDCA framework discussed earlier. When PDCA is applied to the test management process, it is structured as follows. First, a test plan is established, corresponding to the Plan phase. Actual test execution is then carried out in the dynamic test process as the Do phase. Whether the results are proceeding appropriately is verified and acted upon in the test monitoring and control phase, corresponding to Check and Act. Finally, upon completion of testing, a test completion report is produced.</p>
<p>Furthermore, if changes arise during the monitoring and control phase, the test plan must also be continuously updated. A plan is not a fixed document once established; rather, it is a living document that must be consistently revised to reflect test execution results and changes in circumstances.</p>
<p>In conclusion, the test management process consists of test planning, test monitoring and control, and test completion, and is a PDCA-based management activity designed to ensure that the organization's test policies and strategies are systematically executed and managed at the project and task level.</p>
<hr />
<p><strong>Test Management Process; Detailed Composition</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/034d6fcf-662a-4574-b27a-aabe793a0413.png" alt="" style="display:block;margin:0 auto" />

<p>The test management process consists of three key activities: <strong>test planning, test monitoring and control, and test completion.</strong></p>
<p>In the first activity, <strong>test planning</strong>, the scope and targets of testing are identified at the project and task level, and the test strategy defined within the organizational test process is established by referencing it as an input.</p>
<p>In the second activity, <strong>test monitoring and control</strong>, the execution of the dynamic test process is monitored based on the test plan, and the current state of testing is continuously tracked. If issues arise during execution, the testing activities must be appropriately controlled and necessary corrective actions must be taken.</p>
<p>In the third activity, <strong>test completion</strong>, the artifacts generated after testing is concluded are systematically managed. Since these artifacts may be reused in the future, they must be properly stored, and the test environment must also be organized with reusability in mind. Once these activities are completed, a final test closure report is produced.</p>
<hr />
<p><strong>Detailed Activities of Test Planning</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f56fdddc-f286-4461-a982-8bcd13634a30.png" alt="" style="display:block;margin:0 auto" />

<p>Test planning is not merely a matter of setting a schedule; rather, it consists of the following highly systematic detailed activities.</p>
<p>The first activity is understanding the <strong>context</strong>. Before planning the tests, it is essential to first understand the overall situation, including the project's objectives, requirements, relevant stakeholders, and overall schedule. Through this understanding, the scope of testing is clarified and the direction for structuring the test plan is formulated. This process yields a preliminary test plan and development schedule.</p>
<p>The second activity is <strong>risk identification and analysis</strong>. Risk management is a critical element of project management. During the course of testing, a wide range of risk factors may arise, such as changes in requirements, shifts in priorities, or the replacement of personnel. These risks must be identified and analyzed in advance, and methods for mitigating them must be derived. The analyzed risks and their mitigation strategies are then incorporated into the test strategy design.</p>
<p>The third activity is <strong>test strategy design and resource determination</strong>. Based on the risk analysis, the test strategy is designed, and the human resources and detailed schedule to be allocated are determined accordingly.</p>
<p>The fourth activity is <strong>drafting the test plan</strong>. Based on the preceding activities, an initial draft of the test plan is produced. However, the plan at this stage is not yet a finalized document and must go through a review and consensus process involving the relevant stakeholders.</p>
<p>Finally, through the <strong>review and consensus process</strong>, an agreed-upon test plan is produced and shared with all relevant stakeholders.</p>
<p>In conclusion, it is essential to remember that test planning is not a simple preparatory step, but rather a highly systematic process that spans from understanding the context through risk analysis, strategy design, resource determination, plan drafting, and review and consensus.</p>
<hr />
<p><strong>Detailed Activities of Test Monitoring and Control</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/bd412d3c-55c2-4054-9ffd-32b8671e2494.png" alt="" style="display:block;margin:0 auto" />

<p>Test monitoring and control is the process of verifying that testing is proceeding correctly in accordance with the established test plan, and taking appropriate corrective action when necessary.</p>
<p>The input to this process is the <strong>test plan</strong>. Once the test plan is received, the <strong>setup</strong> required for monitoring and control is configured accordingly. The content of tests executed in the dynamic test execution phase is then compared against the plan, and test measurement is performed. Based on these measurement results, <strong>monitoring</strong> is carried out. During monitoring, the progress of testing is continuously tracked, and if any deviation from the plan is identified, control activities are initiated. For example, if unit testing has not been performed or integration testing has been omitted, appropriate corrective measures are taken for those activities that have deviated from the plan.</p>
<p>Reporting must also be carried out on these activities. A representative indicator for test status reporting is test case effectiveness, which is expressed as a percentage representing the ratio of the number of defects found to the number of test cases executed. Furthermore, if there are 100 requirements, the degree to which the test cases satisfy those requirements can be measured as a percentage, providing insight into how well the product meets its requirements.</p>
<p>In conclusion, test monitoring and control is not merely about confirming whether testing has been completed, but rather a systematic process of quantitatively measuring and reporting test progress and quality satisfaction levels based on data.</p>
<hr />
<p><strong>Detailed Activities of Test Completion</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2c475df7-8f8b-48a7-af1e-2672d3a2feda.png" alt="" style="display:block;margin:0 auto" />

<p>Even after testing has been successfully conducted, the test completion phase involves a set of systematic preparatory activities for future testing.</p>
<p>The first activity is verification of the test asset repository. The assets generated during the testing process are reviewed and organized, with decisions made regarding where and how they will be stored. This is done to ensure that these assets can be reused in future testing efforts.</p>
<p>The second activity is test environment restoration. The environment that was configured for testing is restored to its original state so that it can be utilized again in future tests.</p>
<p>The third activity is retrospective review and lessons learned. The strengths and shortcomings of the current round of testing are reflected upon and documented, so that they can be used to improve the quality of future testing activities.</p>
<p>Finally, the test completion report is produced. The report includes a test summary, a comparison of planned versus actual results, test effectiveness metrics, and requirements satisfaction levels. Furthermore, since defects that were not discovered during testing may still remain in the software, the report must also address residual risk identification, post-test countermeasures, test artifact management, a list of reusable assets, and lessons learned, all compiled and managed in report form.</p>
<p>In conclusion, the test completion phase is not simply about wrapping up testing, but consists of a highly systematic set of activities encompassing asset storage, environment restoration, retrospective review, and completion reporting. It is important to remember that the entire process covered so far; test planning, test monitoring and control, and test completion, constitutes a PDCA-based process for systematically managing testing.</p>
<hr />
<p><strong>2. Dynamic Test Process</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4cc62a00-29b0-4e0c-8692-abbe8eb1a365.png" alt="" style="display:block;margin:0 auto" />

<p>Having examined test planning at the task level, the dynamic test process consists of a series of activities carried out to actually execute testing based on that plan.</p>
<p>The first activity is test design and implementation. Test cases are developed based on requirements documents, and the test cases and procedures required for actual test execution are concretely developed at this stage. The second activity is test environment setup and maintenance. The environment in which testing will actually take place is configured and maintained in accordance with the test environment requirements. The appropriate environment must be set up in advance — whether testing will be conducted in a PC environment, an actual vehicle environment, or otherwise — to suit the characteristics of the test target. The third activity is test execution. Tests are executed based on the test specification. The manner in which test results are handled varies depending on whether the issue identified was previously known. If it is a known issue, it must be resolved, and the process of determining how to address the defect continues through test result reporting.</p>
<p>One important point concerns when the test design and environment configuration activities that precede the actual Do phase of test execution take place. It is essential to remember that when the requirements analysis and architecture design processes on the left side of the V-Model are being carried out, test design and environment configuration are also conducted in parallel. The core principle of the dynamic test process is that test preparation must begin from the earliest stages of development, not just immediately before test execution.</p>
<hr />
<p><strong>Dynamic Test Process; Detailed Composition</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/43cc207d-51c6-4e34-aa56-114bed646468.png" alt="" style="display:block;margin:0 auto" />

<p>The dynamic test process consists of four activities: test design and implementation, test environment setup and maintenance, test execution, and test result reporting.</p>
<p>In the first activity, test design and implementation, test cases and test procedures are developed in accordance with the test scope and test strategy identified in the test plan. Specifically, this involves analyzing the test basis. The development artifacts used to conduct testing, and deriving test requirements, test conditions, test coverage criteria, and test cases.</p>
<p>In the second activity, test environment setup and maintenance, the environment and data required for actual test execution must be prepared. The test environment encompasses not only a standalone software environment such as a PC, but also embedded environments that include hardware, and even vehicle environments, depending on the characteristics of the test target.</p>
<p>In the third activity, test execution, actual tests are run using the previously developed test procedures, and the results of each test execution are recorded in the form of Pass or Fail.</p>
<p>In the fourth activity, test result reporting, defects are identified and recorded based on an analysis of the test execution results. This ensures that discovered defects are systematically managed and that the necessary follow-up actions can be initiated.</p>
<hr />
<p><strong>[Reference] Test Basis: Concept and Examples</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d469f50d-1efb-4569-8719-ff6f23059fe4.png" alt="" style="display:block;margin:0 auto" />

<p>The test basis refers to the development artifacts required for conducting testing; specifically, the documents and materials that serve as the foundation for deriving test cases and test procedures.</p>
<p>Viewed through the lens of the V-Model, the development artifacts on the left side correspond to the test activities on the right side. The test basis utilized at each test level is as follows.</p>
<p>For unit testing, which corresponds to the detailed design phase, the detailed design document serves as the test basis. Since detailed design is concrete to a degree comparable to actual source code, the source code itself is also a representative test basis artifact for unit testing. Integration testing verifies whether the interfaces between individual modules are appropriate; accordingly, the architecture design document that forms the basis for this verification serves as the test basis. System testing uses the requirements specification, the output of requirements analysis; as its basis. Finally, since acceptance testing is the stage at which the highest-level requirements of the customer and end users are verified, the requirements definition document and use case definition document serve as the test basis.</p>
<p>In conclusion, the test basis comprises the artifacts that must be referenced at each test level in order to derive test cases and test procedures, and it is important to understand that the development artifacts on the left side of the V-Model directly provide the foundation for the test activities on the right.</p>
]]></content:encoded></item><item><title><![CDATA[Linux 101: My Notes on Users, Permissions, and Getting Things Done]]></title><description><![CDATA[1️⃣ Using Linux, Installing Programs2️⃣ Using Commands

Using Linux, Installing Programs
1. Getting Started


When Linux starts, a login screen appears. Entering the login details used during initial ]]></description><link>https://heesu.tech/linux-101-my-notes-on-users-permissions-and-getting-things-done</link><guid isPermaLink="true">https://heesu.tech/linux-101-my-notes-on-users-permissions-and-getting-things-done</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Sat, 21 Mar 2026 16:21:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a3a87f44-938d-4456-abea-be479499db56.svg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>1️⃣ Using Linux, Installing Programs<br />2️⃣ Using Commands</strong></p>
<hr />
<h1><strong>Using Linux, Installing Programs</strong></h1>
<h2>1. Getting Started</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0cc4711c-33c4-4e81-ac33-1896db7cbe39.png" alt="" style="display:block;margin:0 auto" />

<p>When Linux starts, a login screen appears. Entering the login details used during initial setup and pressing Enter will prompt for a password. The password is not displayed on screen. In the case of Ubuntu, information such as the kernel version and current system status is shown. This information cannot be changed by the user.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/7d71ea2b-e24f-4865-ad0c-450fadc51477.png" alt="" style="display:block;margin:0 auto" />

<p>In Linux/Unix, uppercase and lowercase letters are treated differently. Additionally, the number 0, the letter O, the number 1, the pipe symbol |, lowercase l, and uppercase I all look similar but are different characters. The same applies to the backtick ` and the single quote '. Furthermore, computers convert human-readable characters into numbers for processing. The language humans recognize is called a high-level language, while the language machines recognize is called a low-level language. Humans interpret symbols based on their everyday meaning, whereas computers convert them into numbers. For example, the letter A (a) that humans recognize is converted by computers to 65 and 97 respectively in ASCII code.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/89a88756-3254-42a6-81d1-97b857f2bcc6.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Linux/Unix Accounts:</strong> Groups and Users Linux is an environment where multiple people can use a single computer simultaneously. Those users may be team members or administrators, making it crucial to manage who is allowed to do what. The units used to manage this are <strong>users and groups.</strong> A user is simply a single login account. An account is required to access the system, and permissions are granted accordingly. Individual permissions can be assigned directly to a user, and these take priority over group permissions. For example, even if a group is blocked from accessing file A, if user kim is granted access directly, kim can still access it. A group is a unit for managing permissions by bundling users together. A single user can belong to multiple groups simultaneously. A group itself cannot execute anything or log in; there must be a user within it for it to have any meaning. As an analogy, a group is like a "type of access pass" and a user is "the person holding that pass." The pass itself does not open the door. When a new account is created, a group with the same name is automatically created alongside it. Create account 'kim' → User 'kim' is created → Group 'kim' is also automatically created The permission priority is as follows: individual (user) permissions take precedence over group permissions. Permissions can be assigned to groups in bulk, and individual permissions can override them when exceptions are needed.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1a6857b1-ddfe-4187-a7d5-e5607fb3e368.png" alt="" style="display:block;margin:0 auto" />

<p><strong>root</strong> root is the superuser account with full administrative privileges over the entire system. Every file can be read, modified, and deleted, every setting can be changed, and other user accounts can be created or removed. Because the name "root" is publicly known and identical across every Linux system in the world, it is the primary target for hacking. Distributions like Ubuntu block direct root login by default and use the sudo command instead. Root login can be enabled for convenience, but doing so exposes the system to hacking. The root user's home directory is located at /root. It is important to be aware of the dual meaning of the term "root directory". It can refer to the root account's home directory, but it can also refer to the top-level directory of the entire filesystem, /.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/df232050-96d6-48c7-aae5-1e8c2bc0dc2e.png" alt="" style="display:block;margin:0 auto" />

<p><strong>sudo</strong> sudo is not available to everyone; only users who have been granted the permission can use it. The account created during the initial installation of Linux is automatically granted sudo privileges, but accounts created afterward do not have sudo access by default and must be explicitly granted it by an administrator. The users who can use sudo and the scope of their access are managed in a file called /etc/sudoers. This file allows fine-grained control over which users can run sudo and which specific commands they are permitted to execute. An important point here is that having sudo privileges does not mean inheriting all of root's permissions. Logging in directly as root grants full access to all system privileges, whereas sudo allows only the permissions explicitly defined in /etc/sudoers. In other words, sudo is not a system that copies the entire set of root's keys; it lends only the specific keys needed for a given situation.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d990e23f-d2da-4aba-921d-e091d82707a5.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Prompt</strong>: The prompt is the text displayed at the beginning of the line in the terminal where commands are entered. Its basic format is <strong>username@computername:directory\(</strong>, and this single line contains everything about who the current user is, where they are, and what permissions they hold. The username is the name of the account currently logged in. Since Linux allows switching to a different account during use; including switching to root if the user has the necessary permissions and knows the password, the current username is always displayed in the prompt so it is immediately clear who is performing actions. The computer name is the name of the machine currently connected to, displayed to distinguish which computer is being used when remotely connected to another machine. The directory indicates the current working folder, where <strong>~ is a special symbol representing the logged-in user's home directory.</strong> For example, the home directory of the account test is /home/test, which is abbreviated as <del>. The symbol at the very end of the prompt indicates the current permission level. # is displayed when the user has root privileges, and \) is displayed for regular users. The specific symbol may vary depending on the shell being used, but Ubuntu uses \( by default. For example, if the terminal displays test@test:</del>\), the currently logged-in user is test, the connected computer name is also test, the current location is test's home directory, and the $ symbol confirms regular user privileges. If the user switches to root, the prompt changes to root@test:~#, with both the username and the trailing symbol changing simultaneously, making the privilege change immediately visible.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1a368c84-412d-47bc-b7e4-a061af316c55.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Logging Out (logout, exit)</strong> Linux/Unix systems are rarely powered off and are often running 24 hours a day. Because multiple users can use the system simultaneously, shutting down after one user finishes would inconvenience others still using it. However, when an administrator needs to stop the operating system, commands such as shutdown and poweroff are used. These commands can only be executed with root privileges. logout and exit terminate only the current user's session visible on screen, whereas shutdown and poweroff bring the entire system down, making it completely inaccessible.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9f959627-7133-46aa-9964-cb90ab40a459.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Adding and Deleting Accounts:</strong> Accounts are added using <strong>useradd</strong> and deleted using <strong>userdel</strong>. However, these commands only perform basic operations; meaning they do not automatically create a home directory or password for the account. A home directory does not refer to the /home folder itself, but rather to the personal space represented by ~ for each account. While a personal computer is typically owned and used entirely by one person, Linux/Unix is designed for multiple users to use a single system simultaneously, so storage space is divided and shared among users. The personal space allocated to each account is what is referred to as a home directory. Therefore, an account's home directory means the personal space where that account can store and use files, which is a different concept from the /home directory where all users' home directories are collectively stored. This distinction is important to keep in mind. There are also adduser and deluser commands, which must be separately installed as they are not built into the operating system. Unlike useradd and userdel, these include additional functionality such as prompting for a password during account creation.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/22284425-dd93-45a7-8df2-fb615ee8ecd5.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Built-in Manual</strong> Linux/Unix provides a built-in manual that can be accessed by typing commands directly in the terminal. It is not included by default due to storage constraints and must be installed separately. The manual is divided into sections numbered 1 through 9, each covering a different type of content such as commands, system calls, and configuration files. The section numbers do not need to be memorized as they can be looked up easily when needed.</p>
<hr />
<h3>2. Copying and Installation</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/138a3697-04a1-4869-8570-5e1fbe51f30b.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Copying vs. Installing</strong> Copying is simply placing identical content in a different location, much like photocopying a document. The content is the same, but the copy may not be usable depending on the computer environment. Installing goes beyond copying by performing additional procedures; it makes the software usable in the new environment by supplementing what is missing and incorporating information specific to the installation location.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/35317dbf-adf1-49b3-8397-dff754f72f16.png" alt="" style="display:block;margin:0 auto" />

<p>The limitations of copying can be understood through an analogy. If a wireless internet device used at home is physically moved to an office, it may or may not work, because the device was configured for the home internet connection and only the location has changed. The same applies to software: copying source code to a new location does not mean it can be run immediately.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c3aa4ea8-ef5f-47dc-838f-24bdd0b18a49.png" alt="" style="display:block;margin:0 auto" />

<p>The process from copying to execution is as follows: the source code is copied, a compiler and libraries are installed separately, paths and linked files are configured for the environment, and only after all of these steps can the executable (binary file) be run.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e9dc339d-fda7-4325-bae3-29112fc59647.png" alt="" style="display:block;margin:0 auto" />

<p>Installation automatically handles all the steps required to make software usable anywhere. Returning to the earlier analogy, it is like pulling a dedicated line within the office building and performing a proper installation.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/63175756-85aa-461f-b531-4f4f5141daad.png" alt="" style="display:block;margin:0 auto" />

<p>The installation process works as follows: the executable (binary file) is automatically copied to the appropriate location, and a script for environment configuration; including path assignment and linked file specification - is executed automatically, leaving the software ready to use immediately.</p>
<hr />
<p>In summary, copying is the act of moving files, while installing is the process of automatically performing all the necessary configurations and procedures to make the software actually function in that environment. This is why simply copying a program is not sufficient, and depending on the purpose, the process is divided into full installation and simply copying the executable file.</p>
<hr />
<h3>3. Program</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/bc0a222e-e91a-4a05-8692-1dbf5bf37fe6.png" alt="" style="display:block;margin:0 auto" />

<p>How to Use apt When installing a program in Ubuntu, the following command is used. <strong>linux@linux:~$ sudo apt install openssh-server</strong> Running this command first prompts for the sudo password, which is the password set when the account was first created. Once the password is entered, the installation proceeds. At this point, apt does not simply install openssh-server alone - it automatically installs all the libraries and additional programs required for that program to function. At the end of the installation process, a [Y/N] prompt appears asking for confirmation of these additional installations.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/06aab4ef-42c9-4cb4-99d6-622d384600aa.png" alt="" style="display:block;margin:0 auto" />

<p>In other words, apt is a tool that installs all the files and libraries needed to use a program in a single operation. Taking openssh-server as an example, there are multiple dependency files required to run this program, and rather than the user having to find and install each one individually, apt identifies and installs them all at once.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/45dd275a-df7c-4e50-b7a7-23877713dc38.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Package List Management:</strong> apt retrieves packages by referencing package repository information. On Ubuntu, the files <strong>/etc/apt/sources.list</strong> and <strong>/etc/apt/sources.list.d/ubuntu.sources</strong> store repository information, and apt uses this to fetch the required packages from those repositories. The version information in the repository list can be updated with the <strong>sudo apt update</strong> command. It is important to distinguish between types of updates: the update prompted by the system at first login is related to critical security patches from the operating system's perspective, whereas the update performed manually with sudo apt update refreshes the version information of the package list. Since not everything needs to be kept up to date at all times, updates can be performed selectively as needed. apt is a program that manages the installation and removal of software on Debian-based Linux distributions.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5f08aa18-4c70-4072-8f7a-d2152fb227b1.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Installing a Package</strong>; <strong>sudo apt install package-name</strong></p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/964e20ed-a618-40da-b44f-724daea3fa5a.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Removing a Package:</strong> sudo apt remove package-name.. If it is necessary to also delete files created during the installation of a package,</p>
<p>purge is used instead of remove. <strong>sudo apt purge package-name</strong> The difference between remove and purge is that remove deletes only the package itself, while purge deletes both the package and all associated files created during installation. Upgrading a Package To upgrade installed packages to the latest version, the following two commands are executed in order.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/585598ad-d1fb-4ad5-ab92-a17de1389f51.png" alt="" style="display:block;margin:0 auto" />

<p><strong>sudo apt update / sudo apt upgrade:</strong> apt update first refreshes the version information in the package list, after which apt upgrade upgrades the actual packages to their latest versions.</p>
<hr />
<h3>5. How to use dpkg</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/027dca39-5926-4002-ba47-7c5d61a3ee92.png" alt="" style="display:block;margin:0 auto" />

<p>While apt automatically manages everything required for installation, dpkg is a tool that installs only a single package bundle. Unlike apt, it does not automatically install dependent libraries or additional programs - it processes only the one specified package. It is important not to confuse dpkg with compression. Compression bundles multiple files together for the purpose of transfer or storage. dpkg, on the other hand, manages files necessary for running a program as a single package unit. The fundamental difference is that dpkg manages units of executable programs, not simple file bundles.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/da85f8d0-7c05-4f02-9ec3-43fd0c51782a.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2fa8deae-c33d-493e-86a4-38245afdbecf.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6a5f4a34-06e8-45c6-9db9-d43f08ba3947.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b365ef8a-1291-46cf-934b-e97d58728516.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h1>Using Commands</h1>
<h3>passwd</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cd1c0fae-0ad0-4b02-9ca5-fbf9c41f4c5c.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>passwd</code> command is used to change the password of an account. When the prompt displays <code>linux@linux:~$</code>, the account name to be changed corresponds to the first <code>linux</code> at the very beginning of the prompt.</p>
<hr />
<h3>whoami</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/249ffc85-f567-41c1-bfc1-0776c8e39b26.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/eb314457-963d-4eea-b181-83ac00435bdc.png" alt="" style="display:block;margin:0 auto" />

<p>There are situations where it is necessary to confirm which account is currently being used when executing commands. One such example is when an administrator connects as a regular user in order to provide technical support. Running the <code>whoami</code> command immediately displays the name of the account currently in use.</p>
<h3>id</h3>
<p>Running the <code>id</code> command outputs more detailed information compared to <code>whoami</code>. It displays comprehensive information about the currently active account, including user and group data. The numbers shown in the output are important, as files are represented by numbers during the process of copying and moving them.</p>
<hr />
<h3>who</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/edb1e82f-d3f6-42af-af27-5d4248ded3f3.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>who</code> command displays a list of accounts currently logged into the system. On a personal Ubuntu Linux system used by a single person, only one account will be shown. However, if multiple accounts are logged in simultaneously or multiple terminals are open, all logged-in accounts will be listed.</p>
<p>The output of the <code>who</code> command also includes an item indicating the method of login, which is divided into two types. <code>tty (teletypewriter)</code> refers to a direct physical connection, while <code>pts (pseudo-terminal slave)</code> refers to a virtual connection. In other words, if the user is sitting directly in front of the computer, the connection is shown as <code>tty</code>, and if connected remotely, it is shown as <code>pts</code>.</p>
<hr />
<h3>w</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3e43b20c-d083-48b5-bc4d-0ed485a3b86c.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>w</code> command shows what is currently happening on the system. The output is displayed in the following format.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8bf077b8-7896-4b94-be0e-9b66a3759767.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">13:12:33 up 21min,  2 users,  load average: 0.00, 0.00, 0.00
USER     TTY FROM             LOGIN@  IDLE  JCPU  PCPU  WHAT
</code></pre>
<p>The current time, system uptime, number of logged-in users, and system load average are all displayed on a single line. Below that, detailed information for each logged-in account is listed. <code>jcpu</code> refers to the total CPU time used by all processes running under that account, while <code>pcpu</code> refers to the CPU time used by the process currently shown in the <code>what</code> column.</p>
<p>These commands do not need to be memorized in detail. Simply knowing that they exist is enough, as they can easily be looked up through a search when needed.</p>
<hr />
<h3>date</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/afae5e21-95fd-4af5-9599-6a5763064d67.png" alt="" style="display:block;margin:0 auto" />

<p>Running the <code>date</code> command outputs the time configured on the system. An important point to note is that this time does not necessarily match the actual real-world time. Since the output reflects the time set on the system, it may differ from the actual time if a user has manually changed it. This works the same way as the time settings on a mobile phone; it can be set to automatically sync with the current time, or manually configured to a desired time by the user.</p>
<hr />
<h3>touch</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/75e2160b-9372-484b-99c4-35192d589061.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>touch</code> command updates the access time and modification time of a file to the current time. Options that begin with <code>-</code> are called flags. The main flags available for <code>touch</code> are as follows.</p>
<p><code>-t</code> is used to change the time to a user-specified time rather than the current time. <code>-m</code> changes only the modification time of the file. <code>-a</code> changes only the access time of the file.</p>
<p>Detailed descriptions of each flag can be found in the built-in manual using <code>man touch</code>, or through external websites. There is no need to memorize the specific options - they can be looked up and applied as needed.</p>
<hr />
<h3>printenv, env</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5336d63d-bd87-44c7-99d1-9df7ba25906a.png" alt="" style="display:block;margin:0 auto" />

<p><code>printenv</code> and <code>env</code> are commands used to check what environment the computer is currently operating in. Running either command outputs the current environment information, and it is also possible to hide an ID or change a group for security purposes. The <code>env</code> command is used in the following format.</p>
<pre><code class="language-plaintext">linux@linux:~$ env [NAME=VALUE] ... [COMMAND [ARG] ...]
</code></pre>
<p>This sets the environment variable specified by <code>NAME</code> to the value of <code>VALUE</code> and then executes <code>COMMAND</code>. In other words, rather than permanently changing the entire system environment, it applies the specified environment value only for the duration of that particular command execution.</p>
<hr />
<h3>ls</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/41dd67f5-deb3-4667-b9e0-a4d3cf37556b.png" alt="" style="display:block;margin:0 auto" />

<p><code>ls</code> is the most commonly used command for files and directories, displaying the contents of the current directory. A variety of flags can be used alongside it.</p>
<p>Running <code>ls -F</code> appends an indicator to each file name to show the type of file. Running <code>ls</code> without <code>-F</code> displays only the file names without any indicators.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b152fee4-99c6-48bb-a6de-ac274874ab75.png" alt="" style="display:block;margin:0 auto" />

<p>The reason indicators were introduced is that when <code>ls</code> first appeared, color display was not supported and output was shown in a single color, making it difficult to distinguish file types. Color support is now standard, so indicators are no longer strictly necessary, but the feature is maintained because some users still rely on it and there is no reason to remove it. Preserving existing functionality for the sake of compatibility is a general principle in computing.</p>
<hr />
<h3>. and ..</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ef007d03-d738-40ca-aab7-d90610895d47.png" alt="" style="display:block;margin:0 auto" />

<p><code>.</code> refers to the current directory, while <code>..</code> is a relative notation referring to the directory one level above the current directory.</p>
<hr />
<h3>Permissions and File Types</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/743f38c3-da41-4a16-8003-508979074c7f.png" alt="" style="display:block;margin:0 auto" />

<p>Running <code>ls -lh /etc</code> outputs detailed information about files and directories. The character displayed at the very beginning of each line indicates the type of file. <code>d</code> indicates a directory, <code>l</code> (lowercase L) indicates a symbolic link, and <code>-</code> indicates a regular file. For a simple view, <code>ls -F</code> can be used, and for a detailed view, <code>ls -lh</code> is the appropriate option.</p>
<p>Permissions are divided among four targets: <code>u</code> for the owner (user), <code>g</code> for the group, <code>o</code> for others, and <code>a</code> for all.</p>
<p>When a file's permissions are displayed as <code>drwxr-xr-x</code>, the leading <code>d</code> indicates the file type, and the remaining characters are read in groups of three.</p>
<pre><code class="language-plaintext">rwx  /  r-x  /  r-x
 u       g       o
</code></pre>
<p>The meaning of each character is as follows: <code>r</code> means read permission (4), <code>w</code> means write permission (2), and <code>x</code> means execute permission (1).</p>
<p>In binary notation, the values corresponding to <code>r</code>, <code>w</code>, and <code>x</code> are 4, 2, and 1 respectively. Permissions can be set by specifying the sum of these values.</p>
<pre><code class="language-plaintext">Full permission  : 4+2+1 = 7  →  rwx
Read/Write only  : 4+2   = 6  →  rw-
Read only        : 4     = 4  →  r--
</code></pre>
<p>For example, setting permissions to <code>644</code> results in <code>rw-r--r--</code>, and setting them to <code>777</code> results in <code>rwxrwxrwx</code>. More complex administrator-level permission features also exist using <code>s</code> and <code>t</code>, but at this stage it is sufficient to simply be aware that such features exist.</p>
<hr />
<h3>pwd</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b4920142-8cdd-4fdd-a576-bf941238465a.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>pwd</code> command displays the current working directory. When permissions are frequently changed or tasks accumulate, it can become easy to lose track of the current location. In practice, <code>pwd</code> is used frequently.</p>
<hr />
<h3>cd</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cb7f2514-3025-4e90-924e-9b8839dd1f9f.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>cd</code> command is used to navigate between directories. <code>cd .</code> moves to the current directory, meaning the location does not change. <code>cd ..</code> moves one level up to the parent directory. <code>cd ~</code> moves to the home directory of the currently logged-in user.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/5705aad6-9a86-4b7d-8359-ce8921df2c0a.png" alt="" style="display:block;margin:0 auto" />

<p>The reason <code>~</code> is used is that it allows relative path expression. For example, to navigate to the <code>bin</code> directory under a specific user's home directory, <code>cd ~/bin</code> can be used. Without <code>~</code>, the full path such as <code>cd /home/linux/bin</code> would need to be typed out each time. When there are many users across various environments, each user's home directory path is different, making absolute paths impractical. Using <code>~/bin</code> universally implies each user's own <code>bin</code> directory under their respective home directory, which is why <code>~</code> is an essential notation.</p>
<hr />
<h3>du</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/04f0829a-8fee-458f-b8d2-1c8531c8d659.png" alt="" style="display:block;margin:0 auto" />

<p>While Windows and Mac allow users to easily check storage usage through a file explorer, Linux Ubuntu uses the <code>du</code> command for this purpose. <code>du</code> stands for disk usage and shows how much disk space the current directory or file occupies.</p>
<hr />
<h3>df</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/05b59006-4934-4789-8b7f-156e38be7ae1.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>df</code> command is used to check how much of the filesystem is being used. For example, if a 1TB disk is installed and 200GB is in use, the remaining 800GB will not be shown unless it has been mounted. A volume must be mounted before it can be recognized and used by the system.</p>
<p>The main flags are as follows: <code>-a</code> displays all filesystems including all types, and <code>-h</code> displays file sizes in a human-readable format. For example, using the <code>-h</code> option outputs sizes in units such as KB, MB, and GB instead of bytes.</p>
<hr />
<h3>mkdir, rmdir</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/751624a0-cdf9-4aba-a83a-0ad31e971425.png" alt="" style="display:block;margin:0 auto" />

<p><code>mkdir</code> is used to create a directory.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4175f199-00e2-4a21-82f4-5329dce5a477.png" alt="" style="display:block;margin:0 auto" />

<p><code>rmdir</code> is used to delete a directory. Adding the <code>-p</code> flag allows deletion of both parent and child directories simultaneously.</p>
<hr />
<h3>cp</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b80d09d9-3193-4232-ab9a-efba32ae2756.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>cp</code> command is used to copy files, creating an exact duplicate of the original. When copying, there are important points to note regarding file names. If the files are in different directories, the original and the copy may share the same name. However, within the same directory, two files cannot have the same name, so the copy must be given a different name. If a file with the same name already exists in the destination directory - for example, when copying a <code>src</code> file to a <code>dst</code> file and a file named <code>dst</code> already exists, the system will ask whether to overwrite it. This is an area that requires particular care when using Linux. Windows and Mac display overwrite warnings by default, whereas Linux may overwrite without warning depending on the settings. There is a difference in terms of efficiency, but it is important to judge which approach is more appropriate depending on the situation.</p>
<hr />
<h3>mv</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2e7d8228-b440-40c4-9e59-f086b669a690.png" alt="" style="display:block;margin:0 auto" />

<p>The <code>mv</code> command is used to move a file or directory from its current location to another. If the destination is a different directory, the file is fully moved to that location. If used within the same directory, it can also serve as a way to rename a file without actually moving it.</p>
<hr />
<h3>rm</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ad2c53dc-871e-46f0-a077-8ea146ad2612.png" alt="" style="display:block;margin:0 auto" />

<p>Directories can be deleted using <code>rmdir</code>. The <code>rm</code> command is also used for deletion.</p>
<hr />
<h3>chown, chmod</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/aaa846eb-278c-40f7-aa6c-7a683ca0a76c.png" alt="" style="display:block;margin:0 auto" />

<p><code>chown</code> is used to change the owner of a file or directory. For example, it can be used to change the owner from root to <code>test</code>, or from <code>test</code> to root. Because this involves changing ownership, the user must have the appropriate permissions to execute it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a4a273a2-64df-48ff-8370-f55abe3988e9.png" alt="" style="display:block;margin:0 auto" />

<p><code>chmod</code> is used to change the execution permissions of a file or directory. Permissions can be set using either the numeric method or the <code>rwx</code> character method. The symbols used in the character method have the following meanings: <code>+</code> adds a permission, <code>-</code> removes a permission, and <code>=</code> assigns only the specified permissions while removing any that are not explicitly stated. For example, <code>u+rw</code> means adding read (<code>r</code>) and write (<code>w</code>) permissions to the owner (<code>u</code>).</p>
<hr />
<h2>Summary</h2>
<p>Linux is an environment where multiple users can work simultaneously, making permission management critically important. Because root holds full control over the entire system, a successful attack could expose everything, resulting in a serious security breach. Anyone in a position to manage root privileges must always be aware of the responsibility that comes with it.</p>
<p>When copying or moving files, it is essential to develop the habit of verifying whether overwriting an existing file is intentional. Accidental overwrites are often difficult or impossible to undo.</p>
<p>There are multiple ways to install programs. While it is possible to compile source code directly and copy it, this approach is difficult to manage and time-consuming. For this reason, Ubuntu primarily uses package management tools such as <code>apt</code> and <code>dpkg</code>. Among these, <code>apt</code> is the most widely used. At this stage, it is sufficient to know the names of packages and how to perform updates.</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[Inside the Computer: Hardware Composition and Program Data Processing]]></title><description><![CDATA[1️⃣ Composition of computer hardware2️⃣ Processing of program data

1️⃣ Composition of computer hardware

Four Major Components of a computer: Processor (CPU), Memory, System Bus, and I/O Devices. All]]></description><link>https://heesu.tech/inside-the-computer-hardware-composition-and-program-data-processing</link><guid isPermaLink="true">https://heesu.tech/inside-the-computer-hardware-composition-and-program-data-processing</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Thu, 19 Mar 2026 12:50:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a191a9b2-eab3-4c24-bc07-cdf3243f99b1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>1️⃣ Composition of computer hardware<br />2️⃣ Processing of program data</p>
<hr />
<h2>1️⃣ Composition of computer hardware</h2>
<ol>
<li><strong>Four Major Components of a computer</strong><br />: <strong>Processor (CPU), Memory, System Bus, and I/O Devices</strong>. All components are connected through the system bus and operate together as a single system.</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/eb039fcc-3472-4fd6-bd47-6db8ae1bfa12.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><strong>CPU</strong>: The central unit that performs calculations and processes instructions <strong>Memory</strong>; A space where programs and data needed during CPU operations are temporarily stored. The CPU can only directly process data that is loaded into memory <strong>System Bus</strong>; A common pathway through which the CPU, memory, and I/O devices exchange data <strong>I/O Devices;</strong> All devices used to interact with the outside world, including keyboards, mice, disks, monitors, and network devices.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f1bf5ea0-0fbc-4f6b-b574-c0da098b3a98.png" alt="" style="display:block;margin:0 auto" />

<p>Hardware Connection Structure Here is a structural overview of how each component is actually connected.</p>
<ul>
<li><p><strong>Inside the CPU:</strong> Composed of the ALU, registers, and control unit, it not only performs calculations but also controls the order and timing of instructions</p>
</li>
<li><p><strong>Memory (RAM)</strong>: Holds currently running programs and data. The CPU exchanges data with memory through the <strong>system bus</strong></p>
</li>
<li><p><strong>I/O Devices</strong>: Not directly connected to the CPU, but indirectly connected through an I/O controller</p>
</li>
</ul>
<p>The most critical point is that all components are connected through the <strong>system bus,</strong> and all instruction delivery and addressing takes place through this pathway. The operating system uses this structure to utilize the CPU, allocate memory, and control I/O devices.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cfcd3870-3460-43b8-90a5-9cb008864c8f.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Von Neumann Architecture</strong> This is the most representative computer architecture followed by the majority of computers today. Two Most Important Characteristics First, the CPU, memory, I/O devices, and storage devices are all connected through a single bus.</p>
</li>
<li><p>Second, programs are stored in memory just like data. Because the CPU interprets the contents of memory as instructions and executes them, a program must be loaded into memory before it can run.</p>
</li>
<li><p>Why Is This Important? This is because the role of the operating system, which will be covered later, is centered around this memory. The operating system regulates execution order, allocates resources, and manages the system based on the programs loaded into memory.</p>
</li>
<li><p>Key Perspective Rather than focusing on the historical background of Von Neumann, the goal is simply to understand the concept that <strong>"a computer executes programs centered around memory."</strong></p>
</li>
</ul>
<hr />
<p><strong>2. Processor(CPU) and Registers</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dd4c8e95-9fa3-4f19-8233-ca1eb882ce27.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><strong>Processor (CPU):</strong> The CPU is the entity that actually executes programs stored in memory. It is the central unit in a computer that executes program instructions, interpreting commands stored in memory, performing the necessary operations, and controlling the overall flow of execution. The CPU also directly retrieves programs and data stored in memory, processes them, and stores the results back in memory. In other words, if memory is the space that stores programs, the CPU is the entity that actually executes them.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/13da66cd-e962-432a-95a9-7a32a355e4d5.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Components of the Processor:</strong> The CPU is composed of three elements. <strong>Registers, ALU (Arithmetic Logic Unit), and the control unit</strong>; each playing a different role in processing a single instruction.</p>
</li>
<li><p><strong>Registers</strong> are extremely fast temporary storage spaces inside the CPU that temporarily hold data and instructions retrieved from memory, as well as intermediate results of operations. Registers are the reason the CPU can work so quickly.</p>
</li>
<li><p><strong>The ALU (Arithmetic Logic Unit)</strong> is the part responsible for actual calculations, performing arithmetic operations such as addition and subtraction, as well as logical operations such as comparisons.</p>
</li>
<li><p><strong>The Control Unit</strong> decides which instruction to execute and directs the registers and ALU on when and how to operate through control signals. It manages the overall flow through the internal bus.</p>
</li>
<li><p>In summary, registers handle storage, the ALU handles computation, and the control unit handles control; these three elements work together to execute a single instruction.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2cd0eb4b-44d5-4070-a238-2be137232e03.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Registers</strong> are the storage space the CPU uses at the moment it executes an instruction. Values needed for execution, data required for operations, and intermediate results produced during calculations are all temporarily stored here. Because it would take too long for the CPU to travel back and forth to memory every time it works, information that is immediately needed is loaded into registers for processing.</p>
</li>
<li><p>Registers are therefore most directly connected to the flow of program execution and are the first storage space the CPU accesses. However, since they exist inside the CPU, their number and capacity are very limited; but in return, they are extremely fast.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e3f38583-3101-41a3-be19-fc82488ac115.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Components of Registers</strong> There are multiple registers inside the CPU, and each register plays a different role during the instruction execution process.</p>
</li>
<li><p><strong>PC (Program Counter)</strong> Stores the address of the next instruction to be executed. The CPU uses this value to determine where to fetch the next instruction</p>
</li>
<li><p><strong>IR (Instruction Register)</strong> Stores the currently executing instruction fetched from memory</p>
</li>
<li><p><strong>MAR (Memory Address Register)</strong> Stores the memory address to be accessed</p>
</li>
<li><p><strong>MBR (Memory Buffer Register)</strong> Temporarily stores the instruction or data value read from memory</p>
</li>
<li><p><strong>ACC / DR</strong> Stores data used in operations and the results of those operations.</p>
</li>
<li><p>Actual arithmetic and logical operations are performed by the ALU These registers and memory are connected through the system bus, and the CPU executes programs by exchanging instructions and data between registers and memory. Ultimately, this structure represents the core of the entire execution flow in which the CPU fetches → interprets → computes → and stores the results of instructions.</p>
</li>
</ul>
<h3><strong>3.Cache and Memory</strong></h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b2a00352-54ec-43e5-9913-fe2e5cfb8eeb.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Cache</strong> is a high-speed memory located between the CPU and main memory. While the CPU has a very fast processing speed, main memory is relatively slow to access. This speed difference often causes the CPU to stall while waiting for memory to respond, and cache memory is used to alleviate this problem.</p>
</li>
<li><p><strong>Cache</strong> is a space that pre-stores instructions and data frequently used by the CPU, allowing it to retrieve needed data directly from the nearby cache rather than going all the way to main memory. In summary, cache is a type of memory, but it should be understood as a performance-oriented structural element designed to help the CPU operate faster.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a565e817-c1d9-4ab3-8c0c-cd2263b3e03b.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Memory</strong> is a device that stores currently running programs and data. When a program is executed, the program and the data it needs must be loaded into memory, and the CPU reads from this memory to carry out instructions. In other words, memory is the workspace where the CPU performs its operations while the computer is running. When designing a computer system, memory and storage devices are typically selected based on three criteria.</p>
</li>
<li><p>The first is <strong>speed</strong>; how quickly data can be read and written.</p>
</li>
<li><p>The second is <strong>cost;</strong> how much it costs per unit of capacity.</p>
</li>
<li><p>The third is <strong>volatility;</strong> whether data is retained when the power is turned off. Since no single storage device can satisfy all three criteria simultaneously, computer systems combine these characteristics in a hierarchical structure.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/20115595-72a2-4773-8ab5-f132e3801e1f.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Main memory</strong> is the representative storage device that the CPU can directly access, and along with registers, it is the memory most closely connected to the CPU. When executing a program, the program's instructions and data must be located in main memory. Anything not in memory cannot be accessed by the CPU and therefore cannot be executed.</p>
</li>
<li><p>Main memory also serves to store intermediate results and temporary data generated during program execution. Main memory is generally implemented using DRAM technology, making it a volatile memory that can only retain data while power is supplied. Therefore, when the power is turned off, all contents of main memory are lost. As a result, main memory is a core storage space for program execution, but it is not a storage device suited for long-term data retention.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3913fd91-4b4e-4cb2-ba0d-d0251b5f2c64.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Memory Hierarchy Computer</strong> memory is not a single layer but a structure divided into multiple layers. The higher up in the hierarchy, the closer to the CPU; meaning faster speed but smaller capacity. The lower down, the slower the speed, but the larger the capacity and the lower the cost.</p>
</li>
<li><p>The CPU can directly access data in registers, cache, and main memory, but cannot directly access secondary storage. Therefore, programs stored in secondary storage must first be transferred to main memory before they can be executed. The reason for using this hierarchical structure is simple — because it is not possible to make all data storage both fast and cheap, the hierarchy strikes a balance between speed and cost in order to maximize CPU performance.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8cd7bd12-1f2c-4652-9aa0-60b8818a4176.png" alt="" style="display:block;margin:0 auto" />
</li>
<li><p><strong>System Bus</strong> The hardware components inside a computer do not operate independently; They work together by exchanging data with one another. The system bus is the pathway that connects the CPU, main memory, and I/O devices into a single system. Rather than connecting each device directly to one another, the system bus is structured so that all devices exchange data and control signals through a common pathway. Whether the CPU is fetching instructions from memory, receiving input data from an I/O device, or storing processed results, all data movement takes place through the system bus. In short, the system bus is the connection structure that ties all hardware inside the computer together into one unified system.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cf086ff3-600d-4bf1-8ccb-9846ce2fc345.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>System Bus Signal Types</strong> Although the system bus appears to be a single pathway, it is actually divided based on the type of information being transmitted. Depending on the nature of the signals being carried, it is divided into three types: <strong>address bus, data bus, and control bus. The address bus</strong> is the pathway that tells the CPU where to access. It specifies which location in memory or among devices is to be used. <strong>The data bus</strong> is the pathway through which actual instructions and data travel. The content being processed moves between the CPU, memory, and I/O devices through this bus. <strong>The control bus</strong> carries signals that control the manner and sequence of operations, such as read operations, write operations, I/O requests, and whether a task has started or completed.</p>
</li>
<li><p>In summary, the address bus handles where, the data bus handles what, and the control bus handles how — these three buses work together to ensure proper communication between the CPU, memory, and I/O devices.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1a1cac87-b072-4b11-a08b-0da7fb4f677b.png" alt="" style="display:block;margin:0 auto" />
</li>
<li><p><strong>I/O Devices</strong> The CPU cannot communicate with users through main memory alone; this is where I/O devices come in. I/O devices are hardware that connects the computer with the user or the external environment. They serve roles such as receiving input via a keyboard, displaying results on a monitor, and storing or transmitting data through disks or networks. These I/O devices do not connect directly to the CPU or main memory, but instead exchange data through the system bus.</p>
</li>
<li><p>They are broadly divided into three categories based on their role.</p>
</li>
<li><p>Input Devices: Keyboard, Mouse, Camera</p>
</li>
<li><p>Output Devices: Monitor, Printer, Speaker</p>
</li>
<li><p>Storage / Communication: Disk (SSD/HDD), Network Card</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/31725ac9-9cd5-411e-89c2-aa12cf25502f.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li>In summary, I/O devices are the components that allow information processed inside the computer to connect with the outside world. I/O Devices and the Operating System I/O devices are extremely diverse and complex in terms of hardware. Their operating methods, speeds, and control mechanisms are all different. If application programs had to directly control every device individually, they would become enormously complex. This is why the operating system takes on this role. Because the operating system manages I/O devices with different characteristics in a consistent manner, application programs do not need to worry about the specific operating methods of each device; they simply work using common operations such as read, write, and output. The actual device control is carried out by the operating system on their behalf. Ultimately, although I/O devices are diverse and complex at the hardware level, the operating system hides that complexity, allowing application programs to operate in the same way regardless of the type of device.</li>
</ul>
<hr />
<h1>2️⃣ Processing of program and data</h1>
<h3>1) Command and Data</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a8efe11d-d806-4d11-afe2-56919b101677.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li>Information handled by a computer is broadly divided into data and command. Data refers to values that enter through input devices or are processed during program execution, while instructions are directives telling the CPU what tasks to actually perform; such as "add these values," "store this," or "move to the next step." An important point here is that a program is not one large task, but rather a collection of many individual instructions. These commands are stored at specific memory addresses, and the CPU fetches and executes them one by one in order. In other words, the CPU reads instructions stored in memory one at a time, and uses the necessary data alongside them to carry out the entire program.</li>
</ul>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/79369482-8dab-44a9-9dd8-fd9399f7c8a2.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><strong>CPU command - Level Operation;</strong> The CPU processes one instruction at a time, and each instruction goes through a defined set of steps. First, if the required data is in <strong>main memory</strong>, it is brought into a <strong>register through the bus;</strong> this is the preparation phase for performing an operation. Next, the value stored in the register is passed to the <strong>ALU</strong>, where the actual operation such as <strong>ADD</strong> is performed. Once the operation is complete, the result is either stored back in a register or, if necessary, moved to memory. The <strong>control unit</strong> manages the sequence of this entire flow, and the CPU repeats this process for every single command.</li>
</ul>
<hr />
<p>In summary, the CPU executes a program by continuously repeating the basic cycle of fetching data, computing, and moving the result. Instruction Execution Cycle The following is the process the CPU goes through when executing a single instruction. Regardless of which command is being executed, the CPU always repeats the same defined sequence.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/467b9bc6-2a48-4214-9591-3c80afdd2e3d.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>1. Command Fetch</strong>; Based on the address pointed to by the Program Counter (PC), the next command to be executed is retrieved from memory and stored in the Instruction Register (IR)</p>
</li>
<li><p><strong>2. Instruction Decode / PC Update;</strong> The CPU interprets what the fetched instruction means, and updates the PC to point to the next instruction. This is the step that determines what to do and where to go next -</p>
</li>
<li><p>3. <strong>Operand Fetch</strong>. If the instruction requires data, the relevant values are retrieved from memory or registers Instruction Execute.</p>
</li>
<li><p>4. <strong>The actual operation; such as arithmetic, logical, or comparison operations</strong> - is carried out by hardware including the ALU Result Store.</p>
</li>
<li><p>5. Once the operation is complete, the result is stored in a register or written to memory if necessary Move to Next Instruction - The CPU returns to step 1 for the next instruction, and this cycle repeats continuously.</p>
</li>
</ul>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/301da7ed-2edd-4f3a-b06d-c490248ab230.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>CPU-Memory Speed Gap;</strong> CPU and Memory Speed Difference The most significant problem in computer performance is the speed gap between the <strong>CPU and memory.</strong> The CPU is extremely fast at interpreting and executing instructions, while main memory is relatively slow to access.</p>
</li>
<li><p>This means the CPU is often ready to execute the next instruction, but the required data has not yet been loaded from memory; leaving the CPU with nothing to do but wait for a memory response.</p>
</li>
<li><p>If this waiting time repeats, overall system performance will suffer no matter how powerful the CPU is. Over time, CPU performance has risen steeply while memory performance has grown only gradually, causing the gap to widen.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/acb5117a-63d1-4aa6-8dc1-fdd1a679ba38.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li>This gap is known as the <strong>processor-memory performance gap</strong>, and it means that even if CPU speed continues to increase, the overall system performance will be bottlenecked at the point where memory access cannot keep up.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ea48727b-a48e-420c-b5cc-06e4c8b54919.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Cache Memory;</strong> Cache memory is used to alleviate the speed gap between the CPU and main memory. It is a fast storage space located between the CPU and main memory that temporarily stores instructions and data frequently used by the CPU.</p>
</li>
<li><p>As a result, the CPU can retrieve needed data directly from the cache without accessing main memory every time. Cache is not a replacement for main memory; it is a supplementary storage space designed to assist main memory access and reduce CPU waiting time.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/16e22b5d-0fd0-433f-952c-ab1f21fadb36.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>How Cache Memory Works</strong> When the CPU needs an instruction or data, it checks the cache first rather than going directly to main memory. If the required data is already in the cache, this is called a <strong>cache hit;</strong> the CPU can retrieve the data immediately without accessing main memory, enabling very fast processing.</p>
</li>
<li><p>On the other hand, if the data is not in the cache, this is called a <strong>cache miss;</strong> in this case, the CPU must go all the way to main memory, which takes more time. The retrieved data is then stored in the cache to prepare for future accesses.</p>
</li>
<li><p>In summary, a cache hit is fast and reduces memory access, while a cache miss is slow and requires memory access. The effectiveness of cache depends on how frequently cache hits occur.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/452a9ea7-0660-4ea8-ba66-32443786c344.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Why Cache Works Effectively; Locality</strong> The reason cache is effective is due to a property called <strong>locality</strong>. The tendency for memory access during program execution to be concentrated and repeated within a specific range, rather than occurring randomly. <strong>Locality</strong> is divided into two types.</p>
</li>
<li><p><strong>Temporal locality</strong> refers to the characteristic that data or instructions used recently are likely to be used again in the near future. Variables inside loops or repeatedly executed instructions are typical examples.</p>
</li>
<li><p><strong>Spatial locality</strong> refers to the characteristic that if a particular memory location is accessed, nearby instructions or data are also likely to be used soon. Processing an array in order or executing a sequence of consecutive instructions are representative examples; in such cases, loading not just the needed data but also nearby data into the cache at once increases the probability of cache hits on future accesses.</p>
</li>
<li><p>Cache is therefore not simply a fast memory; it is a structure designed to exploit these locality characteristics to reduce memory access and improve performance.</p>
</li>
</ul>
<hr />
<h3>2. Bottleneck</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/f662b723-cdb2-411d-b150-0c3a559f6acf.png" alt="" style="display:block;margin:0 auto" />

<p>When a program runs, the CPU does not just perform calculations; it uses various resources such as memory access, cache usage, and I/O processing in sequence. If even one of these resources is slow, the entire flow will be delayed at that point. The slowest processing stage that limits overall performance in this way is called a <strong>bottleneck</strong>. For example, even if the CPU itself is very fast, if memory access is slow, the CPU must wait for the response; and as a result, the overall program speed is determined by memory performance.</p>
<p>To address this, systems use structures such as alleviating memory access delays with cache and improving the processing methods themselves to reduce I/O-related bottlenecks.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/05574557-c6f2-4827-b6ab-a3b7a83864ee.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Common Flow of I/O Device Processing</strong> I/O devices are physically much slower than the CPU. Therefore, if the CPU simply waits whenever an I/O operation occurs, efficiency drops significantly. For this reason, how much the CPU should be involved in I/O processing becomes an important design consideration for the operating system.</p>
</li>
<li><p>In some cases the CPU directly checks the device status, while in others the device sends a completion signal after finishing its task. Depending on the I/O processing method, the CPU can either be tied up waiting or freed to perform other work. The operating system uses various I/O processing methods to maximize system performance.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cf783040-d300-46b4-9d62-983f7865847d.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><strong>Polling;</strong> is a method in which the CPU periodically checks the status of an I/O device directly after making a request. The CPU continuously asks the device whether it is ready. The advantage of this method is that it is very simple to implement; it only requires checking device status without complex control logic. However, because the CPU repeatedly checks status without doing any other work even when the device is not yet ready, CPU resources are wasted and overall system efficiency drops.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/462d66f6-12c5-4d90-8daf-35e27d69d1d3.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><strong>Interrupt;</strong> With the interrupt method, the CPU does not check device status directly. Instead, after making an I/O request, it hands the task off to the device and continues performing other work. When the device finishes its task, it sends a signal to the CPU ; this signal is called an <strong>interrupt</strong>. The CPU temporarily pauses its current work, handles the device's completion request, and then returns to its original task. In other words, rather than the CPU continuously checking device status, the device calls the CPU only when needed. Because the CPU can perform other work during I/O wait time, this method uses CPU resources far more efficiently than polling. The flow of the interrupt method is as follows. First, the CPU sends an I/O request to the device and continues other work without waiting. The device processes the requested data, and upon completion sends an interrupt to the CPU. Multiple devices can generate interrupts simultaneously, but the CPU can only handle one interrupt at a time. When an interrupt occurs, the CPU stops its current work and passes control to the kernel. The operating system processes multiple simultaneous interrupts one by one in order of priority. Once all handling is complete, the CPU returns to its original task. The key point is that the CPU does not continuously check the device. There is no resource waste, and the device calls the CPU only when necessary.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/aa5bddaf-26b3-49f6-8422-3f4b52846c5b.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>DMA (Direct Memory Access);</strong> With DMA, the CPU only sets the conditions for the transfer; such as where data should come from, where it should go, and how much should be transferred. The actual data transfer is then handed off to the DMA controller. Once this setup is complete, the CPU no longer participates directly in the data transfer process and moves on to other work. The actual data movement occurs directly between the I/O device and memory without passing through the CPU. When the transfer is complete, the DMA controller generates an <strong>interrupt</strong> to notify the CPU that the transfer has finished.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/7f1edfdc-36cc-433f-9464-6436ad551da6.png" alt="" style="display:block;margin:0 auto" />
</li>
<li><p>In this way, the CPU only configures the transfer once at the beginning, and all subsequent repetitive data movement is handled entirely by the DMA controller. DMA is therefore the most efficient I/O method, consuming almost no CPU resources even for large-volume data transfers.</p>
</li>
</ul>
<hr />
<p><strong>Summary of I/O Methods</strong> I/O processing methods have evolved based on how much CPU involvement is required.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1eb10772-4054-4d6c-8740-e4c3704efe63.png" alt="" style="display:block;margin:0 auto" />

<hr />
]]></content:encoded></item><item><title><![CDATA[The Foundations of Software Testing]]></title><description><![CDATA[1️⃣ Quality and the V-Model Life Cycle Process2️⃣ Process Overview3️⃣ Test Overview

1️⃣ Quality and the V-Model Life Cycle Process
1. Definition of Quality
"Quality" is a term we use all the time; we]]></description><link>https://heesu.tech/the-foundations-of-software-testing</link><guid isPermaLink="true">https://heesu.tech/the-foundations-of-software-testing</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Mon, 16 Mar 2026 14:43:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d0527086-b0b4-4533-a017-18cfeffacbe5.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>1️⃣ Quality and the V-Model Life Cycle Process<br />2️⃣ Process Overview<br />3️⃣ Test Overview</p>
<hr />
<h2>1️⃣ Quality and the V-Model Life Cycle Process</h2>
<h2>1. Definition of Quality</h2>
<p>"Quality" is a term we use all the time; we say a product or service is "high quality" or "low quality." But how exactly is quality defined?</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b701d51d-2a32-4158-871e-324bc736d3b4.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Definitions by Leading Scholars</strong></p>
<ul>
<li><p>W. E. Deming (Father of Quality): "Quality is meeting the needs of customers."</p>
</li>
<li><p>J. M. Juran: "Quality is fitness for use."</p>
</li>
<li><p>P. B. Crosby: "Quality is conformance to requirements."</p>
</li>
</ul>
<p>Synthesizing these definitions, quality is determined by how well a product or service satisfies customer or user needs - in other words, how closely it conforms and how fit it is for its intended purpose.</p>
<blockquote>
<p><strong>Definition of Quality:</strong> The totality of characteristics of a product or service that bear on its ability to satisfy stated and implied requirements.</p>
</blockquote>
<p><strong>Stated Requirements vs. Implied Requirements</strong></p>
<ul>
<li><p>Stated requirements: Requirements that are documented or formally and explicitly defined.</p>
</li>
<li><p>Implied requirements: Requirements that are not documented but are implicitly expected by the user. Customers often cannot fully articulate everything they need, and these taken-for-granted, hidden expectations are what we call implied requirements.</p>
</li>
</ul>
<p>Therefore, a development organization must uncover and analyze not only stated requirements but especially implied requirements, and reflect them in the product or service - only then can the team deliver what customers truly want.</p>
<hr />
<h2>2. Two Perspectives on Quality</h2>
<p>What is ultimately delivered to the customer is the final product, but evaluating quality does not mean looking at the end deliverable alone. Quality must be viewed from two perspectives.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/3d6cf5f8-7116-48de-9dc7-506d55ec5688.png" alt="" style="display:block;margin:0 auto" />

<p><strong>1) Process Quality</strong></p>
<p>This refers to the quality of the entire workflow, including development, maintenance, and the work products produced at each stage. The higher the process quality, the more positive its impact on product quality. Process quality is based on the PDCA cycle.</p>
<ul>
<li><p>Plan: Establish a plan before starting work.</p>
</li>
<li><p>Do: Execute the work according to the plan.</p>
</li>
<li><p>Check: Regularly monitor whether the work is on track.</p>
</li>
<li><p>Act: Continuously improve the process.</p>
</li>
</ul>
<p>Organizations that follow the PDCA cycle demonstrate strong process quality, ensuring that all work products are managed systematically.</p>
<p><strong>2) Product Quality</strong></p>
<p>Work products generated through the process must ensure traceability and consistency. Only when the final software or system satisfies the customer's requirements can we say the product quality is good.</p>
<blockquote>
<p><strong>Key Takeaway:</strong> It is not just the final deliverable that matters. Process quality must be raised first — this improves the quality of intermediate work products at each stage, which in turn elevates the quality of the final deliverable.</p>
</blockquote>
<hr />
<p><strong>[Reference] Quality Management System Based on PDCA</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cc72eeb7-a28e-40b4-a61a-9f808be1d3a9.png" alt="" style="display:block;margin:0 auto" />

<p>A quality management system takes inputs such as customer requirements and the needs and expectations of relevant stakeholders, and operates across departments — including planning, marketing, sales, R&amp;D, development, testing, and production — all working in accordance with the PDCA cycle. Even a single coding task follows the Plan → Do → Check → Act cycle. Through this, organizations continuously improve customer satisfaction, QMS performance, and the quality of products and services.</p>
<hr />
<p><strong>[Reference] Relationship Between Process Quality and Product Quality: The BMW Case Study</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/879ce619-ad72-4645-9ed5-9f239fe674d2.png" alt="" style="display:block;margin:0 auto" />

<p>BMW divided its component suppliers into two groups — those with high process quality and those with low process quality — and analyzed the relationship between process quality and product quality. Product quality was measured by the number of defects discovered prior to SOP (Start of Production).</p>
<p>High process quality organization:</p>
<ul>
<li><p>16 months before SOP: 50% of total defects identified</p>
</li>
<li><p>11 months before SOP: 90% of total defects identified → 11 months of buffer time remaining</p>
</li>
</ul>
<p>Low process quality organization:</p>
<ul>
<li><p>8 months before SOP: 50% of total defects identified</p>
</li>
<li><p>2 months before SOP: 90% of total defects identified → 10% of defects still unresolved just before production begins</p>
</li>
</ul>
<p>The gap between the two groups in reaching the 90% defect detection milestone is a striking 9 months. This case study clearly demonstrates that process quality has an enormous impact on product quality — and this applies not only to the automotive industry but across all industries.</p>
<hr />
<h2>3. V-Model Life Cycle Process</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a0b54d22-e30e-4aea-9974-c2512ea75b51.png" alt="" style="display:block;margin:0 auto" />

<p>The V-Model is a software development life cycle (SDLC) model in which the development activities on the left side correspond directly to the testing activities on the right side. It is a model that places strong emphasis on Verification and Validation (V&amp;V), and is also named after the initials of these two concepts.</p>
<hr />
<p><strong>[Reference] Concepts of Verification and Validation</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ebb55dd9-64de-403a-888a-27fd2220f1a3.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>Verification: Confirming that the product conforms to its specifications, requirements, and design specs. "Are we building the product right?"</p>
</li>
<li><p>Validation: Confirming that the product meets the user's intended use and purpose. "Are we building the right product?"</p>
</li>
</ul>
<p>Example: Testing an electric vehicle against its technical specifications is Verification; evaluating it from the actual end user's perspective is Validation.</p>
<hr />
<p><strong>[Reference] V&amp;V Techniques</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ed2f834c-f030-41f1-907e-c8854ae3bcd3.png" alt="" style="display:block;margin:0 auto" />

<p>V&amp;V techniques are broadly divided into Static methods and Dynamic methods.</p>
<ul>
<li><p>Static methods: Techniques for finding defects without executing the program.</p>
<ul>
<li><p>Review: Document and code reviews (e.g., peer review, walkthrough, inspection)</p>
</li>
<li><p>Analysis: Static code analysis, formal methods, etc.</p>
</li>
</ul>
</li>
<li><p>Dynamic methods: Techniques for finding defects by actually executing the program.</p>
<ul>
<li>Testing: Black-box testing, white-box testing, etc.</li>
</ul>
</li>
</ul>
<hr />
<p><strong>Development–Test Correspondence in the V-Model</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8c7a188a-16cc-43cb-ad2d-d3899537b51a.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p>Customer/User Requirements ↔ Acceptance Testing (AT)</p>
</li>
<li><p>Requirements Analysis ↔ System Testing (ST)</p>
</li>
<li><p>Architectural Design ↔ Integration Testing (IT)</p>
</li>
<li><p>Detailed Design ↔ Unit Testing (UT)</p>
</li>
<li><p>Implementation (coding) → Execute tests sequentially, starting from Unit Testing</p>
</li>
</ul>
<p>It is important to note that this correspondence does not mean "run the tests as soon as each development phase is complete." Rather, as each development phase progresses, the corresponding test planning activities — including test procedures, test approaches, environment setup, and test case development — are carried out in parallel. The actual test execution begins after the code is implemented and proceeds in the order: Unit Testing → Integration Testing → System Testing → Acceptance Testing.</p>
<p><strong>Advantages of the V-Model</strong></p>
<ul>
<li><p>Improved quality of work products: By applying static methods such as reviews and analysis, defects in requirements documents, architecture documents, and code can be identified before execution, improving the overall quality of all work products.</p>
</li>
<li><p>Shorter schedule through parallel development and test planning: Running development and test planning in parallel fosters close communication between teams. As the quality of upstream work products improves, the workload in downstream phases is reduced — leading to an overall shorter project schedule.</p>
</li>
</ul>
<hr />
<h1>2️⃣ Process Overview</h1>
<h2>1. Definition and Role of a Process</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/99de9eff-416e-4e3d-9784-3259af13d8e8.png" alt="" style="display:block;margin:0 auto" />

<p>In general terms, a process defines the steps, sequences, and procedures for carrying out a piece of work. That is the dictionary definition. But when we look at the role a process actually plays, we can see something more specific.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/77932340-478f-4bdf-b757-2ef0f79d12e5.png" alt="" style="display:block;margin:0 auto" />

<p>A process exists so that we can systematically create products; such as vehicles, systems, components, or software - that satisfy customer requirements, including functional requirements, non-functional requirements, and constraints. What does it take to do work well? Work proceeds systematically when procedures and methods, tools and equipment, and personnel are properly integrated. A process can therefore be defined as "the means of integrating procedures/methods, tools/equipment, and personnel in order to build a product that satisfies customer requirements."</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/cab3d860-ce9a-446f-b3c3-9d25368cfd57.png" alt="" style="display:block;margin:0 auto" />

<p>Consider a waste sorting facility as an example. When a garbage truck arrives and workers need to sort plastics, aluminum, and paper efficiently, they need defined procedures, equipment set up for efficient sorting, and clearly assigned personnel. When all of these elements work together naturally, the sorting operation runs smoothly. In the same way, a process acts as the glue that integrates procedures, methods, tools, equipment, and people so that work can be done well. The role of a process is significant.</p>
<hr />
<h2>2. Defining a Plan Means Defining a Process</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/964ca247-4a58-45d9-b673-a7573aa3b90a.png" alt="" style="display:block;margin:0 auto" />

<p>Defining a process and creating a plan are essentially the same thing. Planning is not simply about writing a to-do list — it includes defining the process itself. In other words, a plan must define more than just a schedule. A process binds together procedures, methods, tools, equipment, systems, and personnel. The same applies when planning. To perform testing effectively, you need applicable methods and procedures, systems and equipment for efficiency, and personnel assigned to the right roles. Only when all of these elements are properly in place does the work proceed smoothly.</p>
<p>In short, planning is not merely a to-do list — it is the act of defining a process, and a process-driven plan is what truly matters.</p>
<hr />
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0e941755-7893-4ed3-864e-b5b2c5a4611b.png" alt="" style="display:block;margin:0 auto" />

<p><strong>[Reference] How to Define a Process: ETVX</strong></p>
<p>ETVX is a framework for systematically defining the activities to be performed at each stage of a process. The acronym stands for:</p>
<ul>
<li><p>Entry Criteria: The conditions that must be met before a task can begin — the entry gate for starting the work.</p>
</li>
<li><p>Task: The detailed activities to be performed and the steps in which they are carried out.</p>
</li>
<li><p>Verification: The criteria for verifying that the work in progress is being done correctly — a mid-process quality check.</p>
</li>
<li><p>eXit Criteria: The conditions that must be satisfied for the work to be considered complete — the final completion gate.</p>
</li>
</ul>
<p>ETVX can be applied to each phase of software development, including requirements development, design/implementation, and testing. Here is an example of how ETVX is applied to the software requirements development process in practice.</p>
<ul>
<li><p>Process: Software Requirements Development</p>
</li>
<li><p>Purpose: Analyze customer and system requirements to develop software requirements.</p>
</li>
</ul>
<p>Applying ETVX:</p>
<ul>
<li><p>Entry Criteria: Customer requirements and system requirements analysis results are delivered as inputs.</p>
</li>
<li><p>Task: Analyze, specify, and review the software requirements.</p>
</li>
<li><p>Verification: Check that customer and system requirements are properly reflected in the software requirements. Confirm that an objective review of the software requirements has been conducted.</p>
</li>
<li><p>eXit Criteria: System requirements must be fully converted into software requirements, and the software requirements review must be completed with no outstanding issues.</p>
</li>
</ul>
<p>In addition to the ETVX steps, a process definition also includes Tools, Methods, and Roles — because a process encompasses not just procedures but also tools, equipment, and personnel.</p>
<ul>
<li><p>Tools: Requirements modeling tools, requirements management systems.</p>
</li>
<li><p>Methods: Stakeholder interviews (using checklists), inspection-based reviews.</p>
</li>
<li><p>Roles: Requirements analysis → performed by the requirements engineer; requirements specification → performed by the requirements engineer; requirements review → performed by reviewers such as the architect and tester.</p>
</li>
</ul>
<p>By defining procedures, methods, tools, equipment, and personnel within the ETVX framework, teams can clearly understand each process and apply the right approach at every stage. This framework is widely used in industry and is well worth knowing.</p>
<hr />
<h1>3️⃣ Test Overview</h1>
<h2>1. Definition of Testing</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a3febea9-2b5c-4cf5-87cd-d034f515b4c5.png" alt="" style="display:block;margin:0 auto" />

<p>Let's look at some of the most widely referenced definitions of software testing.</p>
<p>Myers defines testing as "the process of executing a program with the intent of finding defects." This definition emphasizes that the fundamental purpose of testing is defect detection.</p>
<p>Craig and Jaskiel define testing as "a lifecycle process that engineers, uses, and maintains testware in order to measure and improve the quality of the software being tested." Here, testware refers to the various work products and tools produced during the planning and design of testing activities. In other words, this definition frames testing as the systematic and engineering-based application of testware to measure and improve how well a software product satisfies both stated and implied customer requirements.</p>
<p>IEEE Std 829 defines testing as "the process of analyzing a software item to detect the differences between existing and required conditions and to evaluate the features of the software item." This definition focuses on identifying the gap between the expected behavior of a system and its actual behavior under defect, error, or bug conditions.</p>
<p>While these definitions vary in focus, they share a common goal: to find as many defects and bugs as possible — before the software is released. Once code is complete, it undergoes a series of test levels including Unit Testing, Integration Testing, System Testing, and Acceptance Testing, with various techniques applied at each level. Since defects discovered after deployment or production cause far greater quality issues, the priority is early defect detection across multiple perspectives before release.</p>
<hr />
<h2>2. The Test Process from a PDCA Perspective</h2>
<p>As discussed in the context of the V-Model, actual test execution only becomes possible once the code has been implemented. However, the preparation that takes place before execution is equally critical. The test process as a whole follows the PDCA cycle.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/44f3e9be-d878-427a-a994-d096a000af22.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Plan — Test Planning</strong></p>
<p>The overall test plan is established and the features to be tested are identified. This phase covers planning activities such as defining the test schedule, test scope, test items, test strategy, and test environment. In the V-Model, test planning runs in parallel with the development phases — customer/user requirements, requirements analysis, architectural design, and detailed design — with corresponding test plans being prepared alongside each. Close communication and collaboration between the development team (left side of the V) and the test team (right side of the V) is the core concept of the V-Model.</p>
<p><strong>Plan — Test Design</strong></p>
<p>Based on the test plan, test cases are developed and test procedures are defined. The test environment is also prepared to replicate real-world usage conditions. For example, in automotive software development, this would mean setting up an environment that reflects actual road conditions.</p>
<p><strong>Do — Test Execution</strong></p>
<p>The prepared test cases are executed and the actual results are compared against expected results to produce a pass/fail verdict.</p>
<p><strong>Check / Act — Test Evaluation and Improvement</strong></p>
<p>Test results are analyzed and evaluated. Progress is monitored and controlled against the test plan, corrective actions are taken when issues arise, and the test process itself is continuously improved.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/99d16818-5f35-4c6b-be91-88912f14de4d.png" alt="" style="display:block;margin:0 auto" />

<p>In summary, testing is not simply the act of execution (Do). The full test process encompasses the entire PDCA cycle — planning, design, execution, and evaluation/improvement — and this is an important point to keep in mind.</p>
<hr />
<p><strong>[Reference] ISO/IEC/IEEE 29119 Software Testing Standard</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ede96e2a-ddd4-4466-9fc8-04608bf6b811.png" alt="" style="display:block;margin:0 auto" />

<p>An internationally recognized standard for software testing exists in the form of ISO/IEC/IEEE 29119. The standard is structured as follows:</p>
<ul>
<li><p>Part 1 — Concepts and Vocabulary: Defines common concepts and terminology used in software testing.</p>
</li>
<li><p>Part 2 — Test Processes: Defines the framework for software test processes.</p>
</li>
<li><p>Part 3 — Test Documentation: Defines requirements for test work products and documentation.</p>
</li>
<li><p>Part 4 — Test Techniques: Defines test design techniques applicable to software testing.</p>
</li>
<li><p>Part 5 — Keyword-Driven Testing: Covers test automation through the use of keyword-driven test scripts.</p>
</li>
</ul>
<p>The standard defines the test process using a multi-layer architecture consisting of three levels:</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8795d41c-f2c5-4686-8c60-4947ae58e5c1.png" alt="" style="display:block;margin:0 auto" />

<p><strong>1) Organizational Test Process</strong></p>
<p>At the organizational level, the Test Policy and Test Strategy are established. These are then passed down to the project level, where they serve as the governing framework for all test activities.</p>
<p><strong>2) Test Management Process</strong></p>
<p>This process operates at the project level and manages testing across different test levels and types — including Unit, Integration, Performance, and Security Testing. It defines a series of activities: test planning, test monitoring and control, and test completion.</p>
<p><strong>3) Dynamic Test Process</strong></p>
<p>This is the process by which testing is actually carried out. It consists of a series of activities including test design and implementation, test environment setup and maintenance, test execution, and test results reporting.</p>
<p>This standard provides an internationally agreed-upon framework for establishing and operating a systematic test process, and is widely referenced in industry practice.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9a8fe131-a8ff-4f28-a54a-5831fe5eb7e4.png" alt="" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[Introduction to Operating Systems: Concepts, Development, and Practice]]></title><description><![CDATA[1️⃣ Concept and role of operating system
2️⃣ Development and characteristics of operating systems3️⃣ Establishing a practical environment

1️⃣ Concept and role of operating system
Definition of an Ope]]></description><link>https://heesu.tech/introduction-to-operating-systems-concepts-development-and-practice</link><guid isPermaLink="true">https://heesu.tech/introduction-to-operating-systems-concepts-development-and-practice</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Fri, 06 Mar 2026 16:15:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b3436248-8922-4247-97ad-758fa0d1d5f9.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>1️⃣ Concept and role of operating system</h3>
<p>2️⃣ Development and characteristics of operating systems<br />3️⃣ Establishing a practical environment</p>
<hr />
<h2>1️⃣ Concept and role of operating system</h2>
<h3>Definition of an Operating System</h3>
<blockquote>
<p><strong>The core software that coordinates all operations between the user and hardware</strong></p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0b5be4d9-647a-4bd4-8042-e9faaa78a301.png" alt="" style="display:block;margin:0 auto" />

<p><strong>5 Key Roles</strong></p>
<ul>
<li><p>Intermediary between the user and hardware</p>
</li>
<li><p>Controls and manages the execution of applications</p>
</li>
<li><p>Allocates and manages computer resources</p>
</li>
<li><p>Provides input/output control and data management services</p>
</li>
<li><p>Provides an abstracted execution environment that hides the details of hardware</p>
</li>
</ul>
<hr />
<h3>2. Computer System Structure</h3>
<pre><code class="language-plaintext">User (Human, device, other computer)
  ↕
Software (Operating System) ← Core
  ↕
Hardware (CPU, memory, storage, I/O device)
</code></pre>
<p>These layers can never be skipped.</p>
<hr />
<h3>3. Main Roles of the OS (3 Roles)</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/fd10f5d2-eb66-4d34-a71f-096a42288cd7.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/df2ff18c-758a-49ee-a3ca-788f24e03fb9.png" alt="" style="display:block;margin:0 auto" />

<table>
<thead>
<tr>
<th>Role</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Coordinator</strong></td>
<td>Manages execution order of multiple programs and provides a concurrent execution environment</td>
</tr>
<tr>
<td><strong>Resource Allocator</strong></td>
<td>Distributes CPU, memory, storage, etc. efficiently, fairly, and safely</td>
</tr>
<tr>
<td><strong>Control Program</strong></td>
<td>Blocks improper resource usage and prevents errors from spreading throughout the system</td>
</tr>
</tbody></table>
<hr />
<h3>4. Goals of OS Development (3 Goals)</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ba11881c-bb1b-4be2-bda7-31d837ba0d0e.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Convenience</strong>; Hides complex hardware and provides intuitive interfaces (mouse, touch) → Accessible to everyone</p>
</li>
<li><p><strong>Efficiency</strong>; Manages resources in a balanced, waste-free manner → Higher throughput, faster response, stable operation</p>
</li>
<li><p><strong>Improved Control Services</strong>; Stably controls I/O devices and system state in multi-user and multi-program environments</p>
</li>
</ul>
<hr />
<h3>Main Functions of the OS (7 Functions)</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a3d7e2a7-bee2-4e70-b7ae-ee84fd5a7dbd.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/832310dd-ce6f-4506-981d-e6a3dfbd1b88.png" alt="" style="display:block;margin:0 auto" />

<h3>① Memory Management</h3>
<ul>
<li><p>Manages RAM allocation and reclamation when programs are executed</p>
</li>
<li><p>Memory is <strong>volatile</strong> → All data is lost when power is turned off</p>
</li>
</ul>
<h3>② Secondary Storage Management</h3>
<ul>
<li><p>Stores data on SSDs and HDDs (non-volatile, large capacity but slow)</p>
</li>
<li><p>Loads only what is needed into main memory, and moves the rest back to storage</p>
</li>
<li><p>Always works <strong>in conjunction with memory management</strong></p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6ae6795c-bc82-46a9-9ddb-e93f5d50fe1c.png" alt="" style="display:block;margin:0 auto" />

<h3>③ Process Management</h3>
<ul>
<li><p>Manages running programs as individual <strong>process</strong> units</p>
</li>
<li><p>Divides CPU time into small slices and distributes them to each process</p>
</li>
<li><p>Prevents resource monopolization and conflicts</p>
</li>
</ul>
<h3>④ Input/Output Device Management</h3>
<ul>
<li><p>Abstracts the differences between diverse devices such as keyboards, mice, and printers</p>
</li>
<li><p>Standardizes I/O so that programs can interact with all devices in a <strong>uniform way</strong></p>
</li>
<li><p>Prevents conflicts during simultaneous requests and handles them efficiently</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/669e4780-4233-4a1f-9c1f-d253e2d41a30.png" alt="" style="display:block;margin:0 auto" />

<h3>⑤ File (Data) Management</h3>
<ul>
<li><p>Systematically stores and manages documents, photos, executables, and other files</p>
</li>
<li><p>Prevents <strong>file corruption</strong> even when multiple users or programs access the same file</p>
</li>
</ul>
<h3>⑥ System Protection (User Access Control)</h3>
<ul>
<li><p>Defines and manages <strong>permissions</strong> for who can do what</p>
</li>
<li><p>Protects the system from malicious access and faulty programs</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/ea7c7856-7499-404b-bc0d-09379ad205a2.png" alt="" style="display:block;margin:0 auto" />

<h3>⑦ Networking and Command Interpreter</h3>
<ul>
<li><p>Manages <strong>external communication</strong> such as internet access and file transfers</p>
</li>
<li><p>Translates command-line inputs and button clicks into <strong>actual system operations</strong></p>
</li>
</ul>
<hr />
<blockquote>
<p><strong>One-line Summary:</strong> An operating system is the core software that makes computers convenient for users, efficient for the system, and stable for the overall environment.</p>
</blockquote>
<hr />
<h2>2️⃣ Development and characteristics of operating systems</h2>
<h2>💡 Development Process of Operation System</h2>
<h3>Why Did Operating Systems Become Necessary?</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/2fd6189b-3355-4792-b7a7-325cd5a2c208.png" alt="" style="display:block;margin:0 auto" />

<p>In the early days of computing, humans managed everything directly. There were two core problems: setting up tasks and handling input/output took far too long, and since the CPU was fast while I/O was slow, the CPU was constantly sitting idle. In trying to solve this inefficiency, an automated management system — the <strong>operating system</strong> — was born.</p>
<hr />
<h3>The Evolution of Operating Systems</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9864d5b2-60bd-4d83-b44c-9ad80dd3059d.png" alt="" style="display:block;margin:0 auto" />

<table>
<thead>
<tr>
<th>Era</th>
<th>Key Development</th>
</tr>
</thead>
<tbody><tr>
<td>1940s</td>
<td>No OS; humans managed everything directly</td>
</tr>
<tr>
<td>1950s</td>
<td><strong>Batch processing</strong> introduced; the beginning of automation</td>
</tr>
<tr>
<td>1960s</td>
<td><strong>Multiprogramming &amp; time-sharing</strong>; improved CPU efficiency, multi-user support</td>
</tr>
<tr>
<td>1970s~</td>
<td><strong>Distributed processing</strong>; multiple computers connected via network → leading to today's cloud computing</td>
</tr>
</tbody></table>
<hr />
<h2>💡Three Major Types of Operating Systems</h2>
<ul>
<li><strong>Batch Processing OS</strong>: Collects jobs and processes them in order. Automation achieved, but CPU efficiency remained low</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c5025f05-1aa0-415b-bebd-cb47d608000d.png" alt="" style="display:block;margin:0 auto" />

<hr />
<ul>
<li><strong>Time-Sharing OS</strong>: Divides CPU time into tiny slices and alternates between tasks → fast response times, multi-user support</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/74fac413-e6c9-4fc6-b091-1c795ed4978b.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1238988b-784a-4b77-a0b3-40595eec1890.png" alt="" style="display:block;margin:0 auto" />

<hr />
<ul>
<li><strong>Distributed OS</strong>: Connects multiple computers via a network to operate as a single system → improved performance and reliability, the foundation of cloud computing</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c703b561-7a0f-4ae6-bc5b-41729b73c887.png" alt="" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/d2d4de78-b2f3-4899-b6ed-b2b927d190e6.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>💡What Is OS Structure?</h2>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dc339f8c-5806-4ed8-bafe-5b64ac752bd3.png" alt="" style="display:block;margin:0 auto" />

<p>OS structure refers to <strong>how the functions of an operating system are internally organized and designed</strong>. It is not about describing what the OS does on the surface, but rather how it is architected on the inside. The central concept is the <strong>kernel</strong>, which is responsible for managing core resources such as the CPU and memory, with various services arranged around it. In short, the difference in OS structure comes down to <strong>how functions are arranged and separated</strong> within the system.</p>
<h3>🤔Why Is OS Structure Necessary?</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a4c74bc1-9d48-4e7e-a7a6-40a54f475e50.png" alt="" style="display:block;margin:0 auto" />

<p>There are two main reasons why a defined operating system structure is needed.</p>
<ul>
<li><p><strong>Functional Complexity</strong>: Early operating systems had relatively little to do, but today's OS is responsible for processor management, file protection, network handling, and much more. Implementing all of these functions without any structured design would make the system nearly impossible to maintain.</p>
</li>
<li><p><strong>Structure Determines Outcomes</strong>: Even with the same set of functions, the choice of structure directly affects the system's performance, stability, extensibility, and maintainability. A poorly designed structure means that modifying a single function could impact the entire system, whereas a well-designed structure allows only the necessary parts to be updated without broader side effects.</p>
</li>
</ul>
<hr />
<h3>Four Types of OS Structure</h3>
<p>There are four main types of OS structure, each designed differently depending on its purpose and use environment.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6aa079f8-67ec-4746-be22-392b80759a91.png" alt="" style="display:block;margin:0 auto" />

<table>
<thead>
<tr>
<th>Structure</th>
<th>Core Idea</th>
<th>Advantages</th>
<th>Disadvantages</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Monolithic</strong></td>
<td>All functions packed into a single kernel</td>
<td>Fast processing speed</td>
<td>Errors affect the entire system; hard to maintain</td>
</tr>
<tr>
<td><strong>Modular</strong></td>
<td>Functions separated as plug-and-play components</td>
<td>Flexible and extensible</td>
<td>Requires careful management of module dependencies</td>
</tr>
<tr>
<td><strong>Layered</strong></td>
<td>Access only permitted from upper to lower layers in order</td>
<td>Easy to debug; high stability</td>
<td>Performance overhead; lacks flexibility</td>
</tr>
<tr>
<td><strong>Microkernel</strong></td>
<td>Only core functions in kernel; rest moved to user space</td>
<td>High safety and security</td>
<td>Frequent message passing causes performance</td>
</tr>
</tbody></table>
<hr />
<h3>1) Monolithic Structure</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e8ca49d9-ca0e-4831-9e53-8a8e589bc8c9.png" alt="" style="display:block;margin:0 auto" />

<p>The earliest and simplest form of OS structure. All operating system functions run within a <strong>single kernel space</strong>, meaning nothing is separated — everything is packed together in one place. Functions within the kernel call each other directly with no intermediate steps, which makes processing very fast with minimal overhead. However, the dependency between functions is extremely high, meaning a problem in one area can spread to the entire system, making it difficult to maintain and debug. In short: <strong>fast, but risky</strong>. Early UNIX and MS-DOS were built on this structure.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/6a9c8679-1e38-43a0-8f19-144de180e1e5.png" alt="" style="display:block;margin:0 auto" />

<p>Looking at the structure from the top down, <strong>users</strong> sit at the very top, followed by <strong>user programs</strong> such as shells, compilers, and system libraries. When these user programs make a request, they enter the kernel through a <strong>system call</strong>. Inside the kernel, all functions; including the file system, processor scheduling, memory management, and I/O management; are <strong>packed together within a single kernel space.</strong> In this structure, the kernel knows everything and manages everything directly.</p>
<p>However, the dependency between functions is extremely high, meaning a problem in one area can spread to the entire system, making maintenance difficult. The larger the structure grows, the harder it becomes to manage. Ultimately, the monolithic structure is one that <strong>gains fast performance by placing all functions into a single kernel, but at the cost of safety and maintainability</strong></p>
<hr />
<h3>2) Modular Structure</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/339363c0-e9ed-4519-9653-a7d92da1a79e.png" alt="" style="display:block;margin:0 auto" />

<p>Born from the question: <em>"What if we kept the monolithic base but managed functions as separate, interchangeable parts?"</em> The modular structure maintains the monolithic foundation but separates functions into <strong>independent modules</strong> that can be dynamically loaded or removed as needed; much like plugging in or unplugging components. Its greatest strength is <strong>balance</strong>: it preserves the performance of a monolithic structure while significantly improving flexibility and maintainability. The downside is that dependencies between modules must be carefully managed, and loading a faulty module can still affect kernel stability. Modern UNIX-based systems such as <strong>Linux and Solaris</strong> use this structure.</p>
<hr />
<h3>3) Layered Structure</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/872b9072-ab4f-4298-8439-8ba884ff2361.png" alt="" style="display:block;margin:0 auto" />

<p>A structure with a <strong>strict top-to-bottom hierarchy</strong>. The OS is divided into multiple layers, and each layer can only interact with the layer directly below it — skipping layers is not allowed. This makes it easy to pinpoint exactly which layer a problem occurred in, improving overall stability and debuggability. However, because every request must pass through each layer one by one, the number of calls increases and <strong>performance suffers</strong>. The rigid rules between layers also make it inflexible when adding new features or modifying existing ones. As a result, while layered structure is easy to understand and well-organized in theory, it is rarely used in commercial operating systems due to its performance limitations. It has been used in educational OS environments such as <strong>THE operating system</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1e43c904-2321-43ca-84c0-41d11ad68b16.png" alt="" style="display:block;margin:0 auto" />

<p>Looking at the structure from top to bottom, the layers are stacked as follows. The most important point here is that <strong>each layer can only interact with the layer directly below it.</strong> Skipping any intermediate layer is strictly impossible. While this structure is very easy to understand, applying it directly to a real operating system places a significant burden in terms of both performance and flexibility. In short, <strong>the layered structure is well-suited for understanding and design, but has clear limitations when it comes to real-world performance.</strong></p>
<hr />
<h3>4) Microkernel Structure</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dab1e277-8ea5-4546-9472-b4abccfba15f.png" alt="" style="display:block;margin:0 auto" />

<p>Starts from the idea of <strong>"leave only the essentials in the kernel."</strong> Only the most fundamental functions - process management, memory management, and inter-process communication ; remain in the kernel. Everything else, such as file systems and device management, is moved out to <strong>user space</strong>. This results in a smaller kernel with significantly improved safety and security. The tradeoff is that the kernel and user space must frequently exchange messages, which introduces some <strong>performance overhead</strong>. Real-world examples include <strong>Mach, MINIX, and QNX</strong> — with QNX being widely used in safety-critical fields such as automotive and medical devices. macOS and Windows, on the other hand, use a <strong>hybrid structure</strong> that incorporates only some microkernel concepts rather than adopting it fully.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/32ee4fe3-b673-4a0c-949f-8bbcf9c30739.png" alt="" style="display:block;margin:0 auto" />

<p>The structure can be mapped out as follows. Only the core functions remain in the kernel, while everything else; such as the file system and device management is moved out to <strong>user space.</strong> Services in user space can only communicate with the kernel <strong>through system calls.</strong> As a result, the structure is cleanly separated, and even when a problem occurs, its impact does not spread widely across the system. In short, this is a structure that <strong>minimizes the kernel in order to maximize stability and extensibility.</strong></p>
<hr />
<h3><strong>🤔Why Do Modern Operating Systems Use a Hybrid Structure?</strong></h3>
<p>네, 해당 부분도 한국어 요약과 영어 번역 함께 정리해드립니다!</p>
<hr />
<h3>한국어</h3>
<h3>왜 현대 운영체제는 혼합 구조인가?</h3>
<p>이유는 크게 두 가지입니다.</p>
<p><strong>첫 번째, 구조마다 장단점이 너무 뚜렷하기 때문입니다.</strong> 단일 구조는 성능은 좋지만 관리가 어렵고, 마이크로 커널은 안정성은 좋지만 성능이 아쉬우며, 계층 구조는 이해하기는 쉽지만 유연성이 부족합니다. 하나의 구조만으로는 모든 요구를 동시에 만족시키기 어렵습니다.</p>
<p><strong>두 번째, 운영체제에 대한 요구가 너무 많아졌기 때문입니다.</strong> 현대 운영체제는 단순히 작동만 하면 되는 수준이 아닙니다. 높은 성능, 안정성과 보안, 다양한 하드웨어 지원, 지속적인 기능 확장까지 모두 요구받습니다. 현실적으로 단 하나의 구조가 이 모든 것을 충족시키기는 불가능에 가깝습니다. 그래서 운영체제는 상황에 맞게 구조를 섞는 방법을 선택하게 되었습니다.</p>
<p>결론적으로 현대 운영체제는 하나의 구조를 고집하지 않고, 여러 구조의 장점을 취하고 단점은 최대한 줄이기 위해 혼합 구조를 사용합니다.</p>
<ul>
<li><p><strong>리눅스</strong>: 모놀리식 구조 기반 + 모듈 구조 결합</p>
</li>
<li><p><strong>Windows·macOS</strong>: 모놀리식 + 마이크로 커널 개념 혼합</p>
</li>
</ul>
<blockquote>
<p><strong>핵심 결론</strong>: 현대 운영체제는 완벽한 구조를 찾는 것이 아닌, <strong>현실적인 타협</strong>을 선택한 결과물이다.</p>
</blockquote>
<hr />
<h3>🤔Why Do Modern Operating Systems Use a Hybrid Structure?</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/156447fc-37b2-4f15-bcee-f63826d4efdc.png" alt="" style="display:block;margin:0 auto" />

<p>There are two main reasons.</p>
<p><strong>First, every structure has clear and distinct trade-offs.</strong> The monolithic structure offers great performance but is difficult to manage. The microkernel structure is highly stable but falls short in performance. The layered structure is easy to understand but lacks flexibility. No single structure can realistically satisfy all requirements at the same time.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/36e13d37-1f9c-42ca-9926-e17b89850eac.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Second, the demands placed on operating systems have grown enormously.</strong> Modern operating systems are no longer expected to simply run; they must deliver high performance, strong stability and security, support for a wide variety of hardware, and continuous feature expansion all at once. It is practically impossible for any single structure to meet all of these demands. As a result, operating systems have chosen to mix structures depending on the situation.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/dacbb40b-2fef-4418-8895-3092c03e5518.png" alt="" style="display:block;margin:0 auto" />

<p>In conclusion, modern operating systems do not commit to a single structure. Instead, they adopt the strengths of multiple structures while minimizing their respective weaknesses through a hybrid approach.</p>
<ul>
<li><p><strong>Linux</strong>: Monolithic base + Modular structure combined</p>
</li>
<li><p><strong>Windows &amp; macOS</strong>: Monolithic + Microkernel concepts blended together</p>
</li>
</ul>
<blockquote>
<p><strong>Key Takeaway</strong>: Modern operating systems are not the result of finding a perfect structure — they are the product of <strong>practical compromise.</strong></p>
</blockquote>
<hr />
<h2>3️⃣ Establishing a practical environment</h2>
<h3>💡Virtual Machines</h3>
<h3>1. What Is a Virtual Machine?</h3>
<p>A virtual machine (VM) is a technology that divides the resources of a single physical computer into multiple independent execution environments. Using virtualization software, you can create several virtual computers on one laptop, each running a different operating system just like a real machine. Since each virtual machine operates independently, any problem that occurs inside a VM does not affect the actual host laptop.</p>
<hr />
<h3>2. Why Use Virtual Machines?</h3>
<p>Directly installing an operating system on a personal PC can lead to the following problems.</p>
<ul>
<li><p>Incorrect settings can prevent the system from booting or cause system damage</p>
</li>
<li><p>Once a problem occurs, restoring the original state is difficult</p>
</li>
<li><p>Switching between multiple operating systems for practice is a significant burden on a personal PC</p>
</li>
</ul>
<p>Virtual machines solve all of these issues. Multiple operating systems can be practiced <strong>safely</strong> on a single PC, and recovery is easy even if something goes wrong.</p>
<hr />
<h3>3. Host-Based Virtualization</h3>
<p>This course uses a <strong>host-based virtualization approach</strong>, where virtualization software is installed on top of the existing operating system and virtual machines are run within it. This method allows direct installation on a personal PC and easy recovery, making it the most suitable approach for hands-on practice.</p>
<p>The practice workflow is as follows.</p>
<blockquote>
<p><strong>Install Virtualization Software → Create Virtual Machine → Install Ubuntu OS</strong></p>
</blockquote>
<hr />
<h3>4. Virtualization Software: VirtualBox</h3>
<p>This course uses <strong>VirtualBox</strong> for hands-on practice.</p>
<ul>
<li><p><strong>Advantages</strong>: Free to use, relatively simple installation and setup, beginner-friendly</p>
</li>
<li><p><strong>Note</strong>: VirtualBox does not work properly — or at all — on MacBooks with Apple Silicon chips (M1·M2·M3). Students with these devices must use alternative virtualization software.</p>
</li>
</ul>
<hr />
<h3>5. Linux Distribution for Practice: Ubuntu</h3>
<p>The operating system to be installed inside the virtual machine is <strong>Ubuntu Linux</strong>. It was chosen for three reasons.</p>
<ul>
<li><p>Provides a user-friendly interface accessible even to beginners</p>
</li>
<li><p>Well-supported by learning resources and an active community</p>
</li>
<li><p>Relatively straightforward to install and use in a virtual machine environment</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b90e6e8c-1e8f-4552-b21f-67ac866f7e92.png" alt="" style="display:block;margin:0 auto" />

<p>At the end of this chaper - I was able to install Ubuntu in the VM using VirtualBox! yay.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Software: Definitions, Quality Challenges, and Development Processes]]></title><description><![CDATA[1️⃣Definition and characteristics of software2️⃣ Software quality issues3️⃣ Software Development Process

1️⃣Definition and characteristics of software
1) Changes Brought About by Software

What Kind ]]></description><link>https://heesu.tech/understanding-software-definitions-quality-challenges-and-development-processes</link><guid isPermaLink="true">https://heesu.tech/understanding-software-definitions-quality-challenges-and-development-processes</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Tue, 03 Mar 2026 12:58:44 GMT</pubDate><content:encoded><![CDATA[<p>1️⃣Definition and characteristics of software<br />2️⃣ Software quality issues<br />3️⃣ Software Development Process</p>
<hr />
<h2>1️⃣Definition and characteristics of software</h2>
<h3>1) Changes Brought About by Software</h3>
<hr />
<h3>What Kind of World Do We Live In?</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a962a9c8-50b8-4194-91ac-f80f94cfc540.png" alt="" style="display:block;margin:0 auto" />

<p>Looking back at the era we live in, we have passed through the 1st, 2nd, and 3rd Industrial Revolutions, and we are now entering the age of the <strong>4th Industrial Revolution</strong> - an era of <strong>intelligent information technology</strong> built on AI, IoT, Big Data, and autonomous driving.</p>
<p>So what makes all of this possible? It is the <strong>software that controls and drives these systems</strong>. We are living in a time where software plays a central role across society and the economy.</p>
<hr />
<h3>The Role of Software - The Case of Autonomous Driving</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/db210dbc-6518-4744-a4be-f975aac27c7b.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Tesla</strong>, a leader in the autonomous driving industry, uses numerous cameras and sensors to recognize objects, people, and lane markings, and to control the vehicle accordingly. What actually performs this judgment and control is <strong>software - invisible, yet operating deep within the system</strong>. The role of software will only continue to grow.</p>
<hr />
<h3>The Scale of Software - Understanding Size Through LOC</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/8bb768ce-6fb9-4a44-949f-5f40564846e3.png" alt="" style="display:block;margin:0 auto" />

<p>The size of software is measured in <strong>LOC (Lines of Code)</strong>.</p>
<table>
<thead>
<tr>
<th>Software</th>
<th>LOC</th>
</tr>
</thead>
<tbody><tr>
<td>Practice code</td>
<td>Tens to hundreds of lines</td>
</tr>
<tr>
<td>Average mobile app</td>
<td><strong>~30,000 lines</strong></td>
</tr>
<tr>
<td>Android OS</td>
<td><strong>~12 million lines</strong></td>
</tr>
<tr>
<td>Windows OS</td>
<td><strong>~40 million lines</strong></td>
</tr>
<tr>
<td>Modern high-end car</td>
<td><strong>100 million+ lines</strong></td>
</tr>
</tbody></table>
<p>If 30,000 lines were printed on A4 paper (at roughly 40–50 lines per page), it would produce approximately <strong>600 to 700 pages</strong>. Imagine someone handing you a 600-page book - just reading and understanding it would be an enormous challenge. The sheer scale of software is already staggering.</p>
<hr />
<h3>Software Defects and Quality Concerns</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9a98e8d9-cde5-4aef-a861-5895e4e5d314.png" alt="" style="display:block;margin:0 auto" />

<p>As the size of software grows, so does the <strong>number of potential defects</strong>.</p>
<blockquote>
<p>Even the best software development companies in the United States are known to produce <strong>"8 to 12 defects per 1,000 lines of code."</strong></p>
</blockquote>
<p>Applying this to real-world scale:</p>
<ul>
<li><p><strong>A 30,000-line app</strong> → approximately <strong>300 potential defects</strong></p>
</li>
<li><p><strong>A 100-million-line car</strong> → approximately <strong>1,000,000 potential defects</strong></p>
</li>
</ul>
<p>Why do so many defects occur? The most fundamental reason is that <strong>software is still developed by humans</strong>. As long as humans are doing the development, <strong>human error is inevitable</strong>.</p>
<hr />
<h3>2) What Is Software?</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/69f3914e-0aea-435d-9983-a2f34e7a7c9a.png" alt="" style="display:block;margin:0 auto" />

<p>We have already discussed software-centric society. So how do we define software itself? The most common definition is: <strong>a collection of instructions - source code programs - that control hardware</strong>. But if we take a broader view, the definition expands significantly.</p>
<p>The software development process is not focused solely on coding. It begins with gathering customer requirements, then moves through analysis, and then a design phase where those requirements are examined in concrete detail from a development perspective. From there, implementation (coding) takes place, followed by testing to verify that the software satisfies the original requirements, and finally the software is deployed.</p>
<p>Because software goes through this entire journey — from requirements to deployment — it produces files such as requirements specifications, architecture design documents, source code, and test result reports. <strong>Software, therefore, can be defined as all artifacts and data produced through the development process, including but not limited to the source code itself.</strong> This definition already implies that software development and maintenance are part of what software fundamentally is.</p>
<hr />
<p>Why Must the Process Be Reflected in the Definition? This is directly tied to the <strong>four key characteristics of software</strong>.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c69868f7-5898-4323-8626-18c2bb5168a4.png" alt="" style="display:block;margin:0 auto" />

<h3>1. Invisibility (비가시성)</h3>
<p>The internal structure of software is not visible to the eye. When using an app or a website, you see the UI on screen, but the actual underlying structure is not clearly visible. This is the most defining characteristic of software.</p>
<p>Should we simply accept this invisibility? If we do, projects will become increasingly difficult to manage. Instead, <strong>we must make every effort to visualize the structure as much as possible.</strong> The most representative approach is <strong>architecture design</strong> - before any coding begins, identifying what components make up the software, how they interact, and what their interfaces look like. Visualization is a deliberate and necessary effort.</p>
<hr />
<h3>2. Non-Linearity (비선형성)</h3>
<p>Software has a <strong>complex, non-linear structure</strong>. In a linear system, the flow of components is predictable and easy to follow. In a non-linear system, components are intricately entangled with one another.</p>
<p>The goal is to reduce this complexity, and this is addressed during the <strong>analysis and design phases</strong>. For example, if a system has a complexity level of 6 and we want to bring it down to 4 or 5, one approach is to introduce a <strong>mediating module</strong> between the existing modules. Instead of having them interface directly with each other, they communicate indirectly through the intermediary — and this reduces overall complexity. These structural decisions must be considered <strong>before programming begins</strong>.</p>
<hr />
<h3>3. Does Not Wear Out, But Continuously Changes (마모되지 않고 변경됨)</h3>
<p>Hardware wears down with use — it degrades and eventually breaks. Software, on the other hand, <strong>does not wear out</strong>. The same software used today, tomorrow, a month from now, or a year from now on the same platform will always perform the same way. It does not deteriorate.</p>
<p>However, software is <strong>constantly changing</strong>. For how long? Until the product it is embedded in is no longer in use. Changes occur for many reasons — a user discovers a problem, an internal request is made, or new requirements emerge.</p>
<p>Consider a car, which is typically used for about 10 years, or a subway train, which can remain in service for around 30 years. The software embedded in these vehicles does not stop evolving once it is first released — it continues to be updated and modified throughout the entire lifespan of the product.</p>
<hr />
<h3>4. Human Intensive (사람 중심의 작업)</h3>
<p>Hardware components are manufactured in factories and production lines, largely automated by robots. Software, however, is still built by people — and because of that, <strong>potential human error is inherent in the process</strong>.</p>
<p>The goal is to minimize human error and to detect faults before they lead to failures. The most representative tool for achieving this is <strong>testing</strong>. Through rigorous testing, we must actively work to find and eliminate the many defects that exist within software.</p>
<hr />
<h2>The Consequences: A 30% Success Rate</h2>
<p>Due to these four characteristics, the success rate of IT projects involving software development is approximately <strong>30%</strong>. One might assume that most projects succeed, but in reality, success requires hitting all three marks of <strong>QCD — Quality, Cost, and Delivery</strong>. Achieving all three simultaneously is extremely difficult, which is why the success rate remains so low.</p>
<p>Nevertheless, <strong>we must continue striving to raise that success rate and improve software quality</strong>, however incrementally.</p>
<h3>Conclusion</h3>
<p>Software is growing in scale at a rapid pace, and the number of potential defects grows proportionally. For this reason, <strong>systematic and thorough efforts to ensure software quality</strong> are absolutely essential.</p>
<hr />
<h2>2️⃣ Software quality issues</h2>
<h3>What Happens When Software Defects Occur?</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/fc623ff4-32d2-40d2-9925-20fd3a949ed3.png" alt="" style="display:block;margin:0 auto" />

<p>When the final product is an aircraft or an automobile, software may appear to be just a small component. However, a <strong>small error</strong> made by a developer becomes a fault, which then leads to a failure, propagating through subsystems and eventually up to the entire system -ultimately influencing the final product and causing <strong>accidents</strong>. This is known as <strong>software fault propagation</strong>. In severe cases, it can result in the loss of human life or catastrophic damage to property.</p>
<hr />
<h3>Real-World Accident Cases Caused by Software</h3>
<h3>1. Medical Field; Therac-25 Radiation Therapy Machine</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/210906ea-624a-43f4-bcf7-ced7323b57b2.png" alt="" style="display:block;margin:0 auto" />

<p>The <strong>Therac-25</strong>, a radiation therapy machine developed in 1985 by ACEL in Canada, is one of the most well-known examples. Because direct radiation exposure is harmful to the human body, the machine was designed with two modes: a low-power <strong>Electron mode</strong> and a high-power <strong>X-ray mode</strong>. In X-ray mode, a <strong>turntable</strong> was designed to intervene and prevent radiation from being directly applied to the patient.</p>
<p>However, due to a software malfunction, <strong>the turntable failed to activate even when the machine was in X-ray mode</strong>. As a result, between 1985 and 1987, <strong>6 radiation overdose incidents</strong> occurred, leaving <strong>3 people dead</strong> and <strong>3 others with permanent radiation-related disabilities</strong>. This is one of the most cited examples of a small developer error leading to fatal consequences.</p>
<hr />
<h3>2. Space Industry; Ariane 5</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/79edf188-31d0-491a-a7ef-8823a98dbb66.png" alt="" style="display:block;margin:0 auto" />

<p>In 1996, the <strong>Ariane 5</strong> rocket, launched by the European Space Agency, <strong>exploded just 37 seconds after liftoff</strong>; at a height still visible to the naked eye from the ground.</p>
<p>The cause was traced to the reuse of software from the previous model, <strong>Ariane 4 (a 16-bit system)</strong>, in the <strong>Ariane 5 (a 64-bit system)</strong> without accounting for the difference in bit architecture. During data conversion, an <strong>overflow error occurred in the variable representing altitude</strong>. Although the rocket was at a normal altitude, the software incorrectly determined that it had gone off course. Because the system was designed to self-destruct upon detecting a trajectory deviation, the rocket was automatically destroyed. Once again, a small developer oversight led to a catastrophic outcome.</p>
<hr />
<h3>3. Aviation; Boeing 737 MAX 8</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/be356021-b30d-45a5-8cb9-77a743f7f409.png" alt="" style="display:block;margin:0 auto" />

<p>In <strong>October 2018</strong>, a Boeing 737 MAX 8 crashed, killing <strong>all passengers on board</strong>. The aircraft was equipped with the <strong>MCAS (Maneuvering Characteristics Augmentation System)</strong>, which controls the pitch of the aircraft and relies on data from the <strong>AOA (Angle of Attack) sensor</strong>.</p>
<p>The critical flaw was a <strong>software error that allowed the MCAS to activate even when the AOA sensor was malfunctioning</strong>. The system continuously commanded the horizontal tail to push the nose downward, and when the pilots were unable to override it, the aircraft crashed.</p>
<hr />
<h3>4. Automotive; Toyota Lexus ES350</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/0614783f-79e4-49af-8e7d-f128875a5b6f.png" alt="" style="display:block;margin:0 auto" />

<p>In <strong>August 2009</strong>, a sudden unintended acceleration incident involving a <strong>Lexus ES350</strong> in California, USA, resulted in the <strong>deaths of an entire family of four</strong>. The vehicle was equipped with an <strong>ETCS (Electronic Throttle Control System)</strong>, and following the accident, a simulation experiment successfully <strong>reproduced the sudden acceleration by manipulating specific bit values in memory</strong>; providing experimental proof that the software was at fault.</p>
<h3>Conclusion</h3>
<p>These cases make it abundantly clear <strong>why ensuring software quality is of the utmost importance</strong>. Achieving that quality requires <strong>systematic efforts across process, engineering, and technical dimensions</strong>, and at the heart of those efforts lies <strong>testing</strong>.</p>
<hr />
<h2>3️⃣ Software Development Process</h2>
<h3>The Importance of Process for Quality Assurance</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/63a1d1ba-c9b3-4004-89aa-214e34b75882.png" alt="" style="display:block;margin:0 auto" />

<p>How can we ensure software quality? <strong>Watts Humphrey</strong>, a renowned software engineer, once said:</p>
<blockquote>
<p><em>"The quality of a product is determined by the quality of the process used to develop and maintain it."</em></p>
</blockquote>
<p>In other words, a better process leads to a better product. While delivering software to the customer is important, what truly matters is <strong>establishing and adhering to a well-defined process throughout the entire development lifecycle</strong> - from requirements analysis to architecture design.</p>
<hr />
<h2>Stages of Software Development</h2>
<p>Software development progresses through the following stages:</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/9160510f-25aa-42dc-ba42-dafbddebfa44.png" alt="" style="display:block;margin:0 auto" />

<ol>
<li><p><strong>Customer Requirements</strong> — Abstract and loosely defined requirements are received from the customer.</p>
</li>
<li><p><strong>Requirements Analysis</strong> — The customer's requirements are broken down and analyzed from a development perspective.</p>
</li>
<li><p><strong>Design</strong> — The <em>HOW</em> is defined: what structure the software will have and how it will behave. This is an indispensable stage in the development process.</p>
</li>
<li><p><strong>Implementation (Coding)</strong> — Developers write the actual code based on the design.</p>
</li>
<li><p><strong>Testing</strong> — The implemented software is verified to ensure it meets the design specifications and satisfies the original requirements.</p>
</li>
</ol>
<p>Only after testing is complete is the software considered finished and ready for deployment to the customer. <strong>Thoroughly following each of these stages is the most fundamental way to improve the quality of delivered software.</strong></p>
<hr />
<h2>Software Development Life Cycle (SDLC) Models</h2>
<h3>1. Waterfall Model</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/eeebb9d5-2306-453f-a794-4aa896ab4b55.png" alt="" style="display:block;margin:0 auto" />

<p>The most classic model, in which the development process flows <strong>sequentially from top to bottom. Like a waterfall</strong>.</p>
<p><strong>Requirements Analysis → Design → Implementation → Testing → Maintenance</strong></p>
<p>Each phase must be fully completed before moving on to the next. This model is best suited for projects with clear and well-defined requirements.</p>
<hr />
<h3>2. Prototyping Model</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/b342a6a0-f4ca-4717-97a4-0bf4a0554df0.png" alt="" style="display:block;margin:0 auto" />

<p>Rather than developing everything at once, this model involves <strong>first building a prototype of the most critical components</strong> from the customer's perspective, then gathering feedback before continuing development. The product is gradually refined based on customer evaluation.</p>
<p>This approach is far more flexible than the Waterfall model and can respond effectively even when customer requirements change during development.</p>
<hr />
<h3>3. Spiral Model</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/fee1454d-81f1-4b1b-af71-4c3720145a3c.png" alt="" style="display:block;margin:0 auto" />

<p>Like the Prototyping model, this approach develops software <strong>incrementally, prioritizing the most important requirements first</strong>. The key distinction is that <strong>risk analysis is explicitly integrated into every cycle of the process</strong>. At each spiral, risks are identified and analyzed, and the findings are incorporated into the next cycle; making it a more systematic and risk-aware development methodology.</p>
<hr />
<h3>4. V-Model</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/80d350f0-c632-462f-b6b1-01a5f44073b7.png" alt="" style="display:block;margin:0 auto" />

<p>One of the most widely used models in practice today, the V-Model is particularly applied in <strong>projects where quality is paramount</strong>. It is derived from the Waterfall model and takes the shape of the letter <strong>"V"</strong>.</p>
<p>The model places strong emphasis on <strong>Verification and Validation</strong>, mapping each development phase on the left side of the "V" to a corresponding testing phase on the right.</p>
<table>
<thead>
<tr>
<th>Development Phase</th>
<th>Testing Phase</th>
</tr>
</thead>
<tbody><tr>
<td>Customer Requirements</td>
<td>Acceptance Testing</td>
</tr>
<tr>
<td>Requirements Analysis</td>
<td>System Testing</td>
</tr>
<tr>
<td>Architecture Design</td>
<td>Integration Testing</td>
</tr>
<tr>
<td>Detailed Design</td>
<td>Unit Testing</td>
</tr>
</tbody></table>
<hr />
<h2>Summary of Today's Key Takeaways</h2>
<p>Today's content can be summarized into three main points:</p>
<p><strong>① Definition and Characteristics of Software</strong> - Software is not merely source code; it encompasses all artifacts and data produced throughout the entire development process. Its key characteristics include invisibility, non-linearity, human-intensive work, and continuous change.</p>
<p><strong>② The Importance of Software Quality Assurance</strong> - These characteristics make quality assurance inherently difficult, but as the accident case studies demonstrate, ensuring quality is absolutely non-negotiable.</p>
<p><strong>③ Software Development Processes</strong> - To achieve quality, structured processes must be applied. The four representative models are the Waterfall Model, Prototyping Model, Spiral Model, and V-Model.</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[Understanding Linux/Unix]]></title><description><![CDATA[1️⃣ Hardware Basics2️⃣ Software Basics3️⃣ Unix - A Closer Look4️⃣ Linux - A Quick Look5️⃣ Study Summary

1️⃣ Hardware Basics


Hardware is anything physical you can touch and feel on a computer. It is]]></description><link>https://heesu.tech/understanding-linux-unix</link><guid isPermaLink="true">https://heesu.tech/understanding-linux-unix</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Fri, 27 Feb 2026 16:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/a1b82455-2e49-4d18-a6ba-11fe29eba54a.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>1️⃣ Hardware Basics<br />2️⃣ Software Basics<br />3️⃣ Unix - A Closer Look<br /><strong>4️⃣</strong> Linux - A Quick Look<br />5️⃣ Study Summary</p>
<hr />
<h3>1️⃣ <strong>Hardware Basics</strong></h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/c6694079-09e3-4d69-a98c-a3b2f0e07195.png" alt="" style="display:block;margin:0 auto" />

<p>Hardware is anything physical you can touch and feel on a computer. It is divided into three main components: CPU, Memory, and Storage.</p>
<p>The <strong>CPU (Central Processing Unit)</strong> is like the brain of a computer. It processes all the instructions and is produced by companies such as AMD and Intel.</p>
<p><strong>Memory</strong> temporarily stores data that the CPU is currently working with, allowing for fast processing. A common example is RAM. Traditionally, RAM was known as volatile storage — meaning everything is lost when the power is turned off — while storage was non-volatile and could be recovered. However, in recent years, memory that retains data even without power has emerged. For now, the key distinction is whether data remains accessible after the power is cycled on and off.</p>
<p><strong>Storage</strong> preserves data even when the power is off, saving files in a permanent format. It is divided into HDD (Hard Disk Drive) and SSD (Solid State Drive).</p>
<p>Other hardware includes <strong>input/output devices</strong> such as keyboards, monitors, printers, and speakers. There are also <strong>network devices</strong> like the NIC (Network Interface Card). The NIC gets its name from the first letters of "Network Interface Card." While modern motherboards often have it built in, it was originally a separate card-shaped device, similar in size to a credit card.</p>
<h3>2️⃣ <strong>Software Basics</strong></h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/db6699ba-48d8-45e1-9974-1026c69e5680.png" alt="" style="display:block;margin:0 auto" />

<p>Unlike hardware, software has no physical form you can touch. It is divided into four categories: System Software, Operating Systems, Device Drivers, and Application Software — though the boundaries between them are not always clear-cut.</p>
<p><strong>System Software</strong> manages the hardware and serves as the foundation for all other software running on the computer.</p>
<p><strong>Operating Systems</strong> manage the computer's resources. Common examples include Linux, Windows, Unix, and Android.</p>
<p><strong>Device Drivers</strong> are software that operates and manages specific hardware devices. A new driver is added each time a new device is introduced.</p>
<p><strong>Application Software</strong> is software that performs specific tasks for the user, such as installing and updating apps.</p>
<h3><strong>A Closer Look at Operating Systems</strong></h3>
<p>Can a program run without an operating system? The answer is yes! Early computers actually worked this way. Even today, it is still possible through microcontrollers.</p>
<p>So why do we have operating systems? They were developed to help us use and manage computer resources more efficiently. Operating systems didn't always exist — they came about as computers grew more complex and the need for better resource management increased.</p>
<p>Here's the English version continuing the same blog style:</p>
<hr />
<h3>3️⃣Unix - A Closer Look</h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/e8e7213c-d4a6-4746-9c51-ac294aa575b0.png" alt="" style="display:block;margin:0 auto" />

<p>If you worked with computers in the 20th century, Unix was something you almost certainly had to know. It was the dominant operating system of the time, widely used from the latter half of the 20th century onwards. Back then, Unix made computers available for free or at a very low cost, which helped drive the adoption of operating systems overall.</p>
<p>Unix was developed in 1969 at Bell Labs, a research division of AT&amp;T, the telecommunications company.</p>
<p><strong>How did we get here?</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/61ac1649-79ad-40be-a7d5-f891740d161f.png" alt="" style="display:block;margin:0 auto" />

<p>Before Unix, there was a software system called Multics. In the early days of computing, programs were written on a 1:1 basis for each specific machine — completely different from today where the same software can run across many different devices. Every time a new machine came out, developers had to repeat the same work all over again. This made the need for a unified, portable software system very clear.</p>
<h3><strong>Key Features of Unix</strong></h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/4d474ec9-26c0-4a19-aef8-4b578965565e.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Time Sharing System</strong> Unix introduced the concept of sharing resources and time among multiple users. Think of it like a food delivery app: an Uber driver doesn't just deliver for one customer. They handle multiple orders. Similarly, Unix supports multiple users (multi-user) and multiple processes (multi-processing) running at the same time.</p>
<p><strong>Command Line Interface (CLI)</strong> Unlike today's large graphical displays, early technology only allowed users to view and input one line at a time on screen.</p>
<p><strong>Everything is a File</strong> In Unix, everything is treated and processed as a file.</p>
<p><strong>Batch Processing</strong> Before time sharing, computers used batch processing, where tasks were collected and processed one at a time. When one person's job finished, the next person could use the computer. This was how computers were shared back then.</p>
<p>The introduction of the <strong>Time Sharing System</strong> changed everything. It allowed user 1, 2, and 3 to all work at the same time, rather than waiting in line. This became the foundation of what we know as the Unix system.</p>
<h3><strong>4️⃣Linux - A Quick Look</strong></h3>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/1344fc1d-1279-4a4c-b735-beb17df734b2.png" alt="" style="display:block;margin:0 auto" />

<p>Linux was first released in 1991 by <strong>Linus Benedict Torvalds</strong>, starting out based on an operating system called MINIX.</p>
<img src="https://cdn.hashnode.com/uploads/covers/649932cadd0a52758cdbdeb5/53cb9b4e-32e5-4f54-b88a-f64a26390e70.png" alt="" style="display:block;margin:0 auto" />

<p><strong>MINIX</strong> was originally developed as an educational alternative to the expensive UNIX, making it more accessible for learning purposes. Linux began from MINIX and later expanded by incorporating many of the functional features of UNIX.</p>
<p>During the era when Unix dominated the market, Unix did not provide its source code. Instead, it would install Unix for you, and once you became comfortable using it, the environment practically encouraged you to go work for the company. For students who wanted to learn, it was a very restrictive environment — and for teachers, it was equally difficult to teach something so closed off.</p>
<p>This is the background that gave birth to Linux.</p>
<p>What I find really inspiring is that Linus started Linux as a <strong>hobby while he was still a student</strong>, through a MINIX class he was taking at the time. As a student myself, I find that really motivating. It's a reminder that great things can start from simply learning and being curious! 🌱</p>
<hr />
<h2><strong>Study Summary</strong></h2>
<p>Unix was an operating system developed by inheriting the <strong>time sharing</strong> concept from Multics. It was a highly scalable system, but eventually lost its market dominance to Linux. Why?</p>
<p>The risks associated with using Unix for free were significant. There was a time when Unix was a mandatory subject at universities, and because of that it was already widely spread with strong technical support. However, when Linux launched in the 1990s, it was completely free to use and had incorporated many of Unix's best features.</p>
<p>Unix kept its source code private, meaning users could only use it as provided without being able to modify it. Linux was the complete opposite — open and accessible. While official technical support for Linux does require payment, most distributions are free to install and use.</p>
]]></content:encoded></item><item><title><![CDATA[String Matching Algorithms: Everything About Efficient Pattern Search]]></title><description><![CDATA[Contents
1️⃣문자열 매칭 (String Matching)2️⃣원시적 매칭 (Naive Matching Algorithm)3️⃣오토마타 이용 매칭 (Automata Theory)4️⃣라빈-카프 알고리즘(Rabin-Karp Algorithms)5️⃣KMP 알고리즘과 실패 함수 (KMP Algorithm and Failure Function)6️⃣보이어-무어 알고리즘(Boyer-Moore Algorithm)

1️⃣문자열 매칭 (String...]]></description><link>https://heesu.tech/string-matching-algorithms-everything-about-efficient-pattern-search</link><guid isPermaLink="true">https://heesu.tech/string-matching-algorithms-everything-about-efficient-pattern-search</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Sun, 08 Dec 2024 06:12:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733638338395/76456c4e-6c07-4345-8374-b3b600cb2dd7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Contents</strong></p>
<p><strong>1️⃣</strong>문자열 매칭 (String Matching)<br /><strong>2️⃣</strong>원시적 매칭 (Naive Matching Algorithm)<br /><strong>3️⃣</strong>오토마타 이용 매칭 (Automata Theory)<br /><strong>4️⃣</strong>라빈-카프 알고리즘(Rabin-Karp Algorithms)<br />5️⃣KMP 알고리즘과 실패 함수 (KMP Algorithm and Failure Function)<br />6️⃣보이어-무어 알고리즘(Boyer-Moore Algorithm)</p>
<hr />
<h2 id="heading-1-string-matching"><strong>1️⃣</strong>문자열 매칭 (<strong>String Matching)</strong></h2>
<p><strong>문자열 매칭 (String Matching)이</strong>란 텍스트 문자열에 주어진 **패턴 문자열 (Pattern)**이 나타나는 위치를 찾아내는 과정을 말한다. 일반적으로 패턴 문자열은 텍스트보다 매우 짧은 경우가 많다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733585091673/0c32d641-858f-4eb4-8bf4-43bc8d16cfac.png" alt class="image--center mx-auto" /></p>
<p>문자열 매칭 알고리즘은 다양한 분야에서 활용된다.</p>
<ul>
<li><p><strong>문서 작업</strong>: 특정 문자열을 검색하는 알고리즘</p>
</li>
<li><p><strong>인터넷 검색</strong>: 키워드 검색</p>
</li>
<li><p><strong>데이터베이스</strong>: 항목 값 비교 또는 검색</p>
</li>
<li><p><strong>백신 프로그램</strong>: 악성코드 패턴 탐지</p>
</li>
</ul>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733585021885/41bb3bbe-3ef0-4223-93e3-853d820da996.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-2-naive-matching-algorithm"><strong>2️⃣</strong>원시적 매칭 (Naive Matching Algorithm)</h3>
<p>원시적 매칭은 텍스트에서 패턴 문자열을 찾기 위해 **순차적으로 하나씩 비교 (Sequential Comparison)**하는 방식이다. 이는 실무에서 거의 사용되지 않지만, 다른 문자열 매칭 알고리즘을 이해하기 위해 기본적으로 알아야 하는 개념이다. <strong>시간 복잡도 (Time Complexity)는</strong> O(mn) 패턴 길이 m, 텍스트 길이 n로 모든 위치를 하나씩 검사하기 때문에 효율이 떨어질 수 있다.</p>
<p><strong>✅ 작동 과정 (How it Works)</strong></p>
<ol>
<li><p><strong>첫 번째 위치부터 비교 (Compare from the first position)</strong><br /> 텍스트의 첫 번째 문자부터 패턴의 첫 번째 문자까지 하나씩 비교한다.</p>
<ul>
<li><p>매칭에 실패하면 패턴을 한 칸 뒤로 이동한다.</p>
</li>
<li><p>이 과정을 텍스트 끝까지 반복한다.</p>
</li>
</ul>
</li>
<li><p><strong>패턴이 매칭되었을 경우 (When the pattern matches)</strong><br /> 패턴의 모든 문자가 텍스트의 해당 위치에서 일치하면 해당 위치를 출력하게 된다.</p>
</li>
</ol>
<p><strong>✅원시적 매칭의 작동 원리(Principal Work of Native Matching Algorithms)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733585308344/576ac127-1217-4e2c-b7c9-ac03b313945d.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-bobycatsoaropt-soar">텍스트: <code>bobycatsoaropt</code>, 패턴: <code>soar</code></h4>
<ol>
<li><p>텍스트의 처음부터 soar를 비교:</p>
<ul>
<li><p>b와 s 비교 → 실패</p>
</li>
<li><p>o와 s 비교 → 실패</p>
</li>
<li><p>...</p>
</li>
</ul>
</li>
<li><p>패턴을 한 칸씩 이동하며 비교:</p>
<ul>
<li><p>s와 s → 성공</p>
</li>
<li><p>o와 o → 성공</p>
</li>
<li><p>a와 a → 성공</p>
</li>
<li><p>r와 r → 성공 (완전 매칭)</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong><mark>💡중요한 포인트 (Key Points)</mark></strong></p>
<ul>
<li><p><strong>순차적 비교 (Sequential Comparison)</strong>: 문자 하나씩 비교</p>
</li>
<li><p><strong>패턴 이동 (Pattern Shift)</strong>: 매칭 실패 시 한 칸 이동</p>
</li>
<li><p><strong>응용 분야 (Applications)</strong>: 인터넷 검색, 데이터베이스 검색, 백신 프로그램 등</p>
</li>
<li><p><strong>단점 (Disadvantages)</strong>: 시간 복잡도가 높아 대규모 데이터에는 비효율적</p>
</li>
</ul>
<hr />
<h3 id="heading-pseudocode-for-naive-string-matching-algorithm">원시적 매칭 의사코드 (Pseudocode for Naive String Matching Algorithm)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733585675471/b65dd69b-03fa-46c0-882d-fa10efd25436.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>텍스트 탐색 범위 설정 (Set search range in text)</strong>:</p>
<ul>
<li><p>반복문은 <code>i = 1</code>부터 <code>i = n−m + 1</code>까지 진행된다.</p>
</li>
<li><p>이는 패턴의 길이 m만큼 텍스트에서 잘라 비교할 수 있도록 범위를 제한하는 것이다.</p>
</li>
</ul>
</li>
<li><p><strong>비교 조건 (Comparison condition)</strong>:</p>
<ul>
<li><p>현재 텍스트의 i번째부터 <code>i + m − 1</code> 번째까지의 문자열을 패턴과 비교한다.</p>
</li>
<li><p>모든 문자가 동일할 경우 매칭이 발견된다.</p>
</li>
</ul>
</li>
<li><p><strong>시간 복잡도 (Time Complexity)</strong>:</p>
<ul>
<li>n은 텍스트의 길이, m은 패턴의 길이로, 최악의 경우 O(mn)만큼의 연산이 필요하다.</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-key-points"><strong><mark>💡중요한 포인트 (Key Points)</mark></strong></h4>
<ul>
<li><p><strong>탐색 범위 조정 (Adjust search range)</strong>: 패턴이 텍스트 끝을 초과하지 않도록 <code>n−m+1</code> 까지만 탐색</p>
</li>
<li><p><strong>조건 검사 (Condition check)</strong>: 텍스트의 특정 부분이 패턴과 동일한지 비교</p>
</li>
<li><p><strong>시간 복잡도 (Complexity)</strong>: 비효율적이지만 개념 이해에 중요</p>
</li>
</ul>
<hr />
<h3 id="heading-inefficiency-of-naive-string-matching">원시적 매칭의 비효율적인 사례 (Inefficiency of Naive String Matching)</h3>
<p><strong><mark>💡요약: </mark></strong> 원시적 문자열 매칭은 순차적으로 모든 위치에서 비교하기 때문에, 특히 패턴의 대부분이 매칭되다가 마지막에 불일치가 일어나는 경우, 이미 비교한 부분까지 모두 반복해야 하므로 비효율적이다. 이러한 문제를 개선하기 위해 패턴 문자열의 앞부분과 뒷부분의 일치 여부를 활용하는 고급 알고리즘이 필요하다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733586200225/0bad8df4-071e-4dc1-9c2c-7727ed8c31b0.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>1번째 시도: <code>abcdabcd</code>는 매칭되지만 <code>d</code>와 <code>w</code>에서 불일치 발생.</p>
</li>
<li><p>2번째 시도: 패턴을 한 칸 이동, 처음부터 다시 비교 → 실패.</p>
</li>
<li><p>3~4번째 시도: 반복적으로 비교 → 실패.</p>
</li>
<li><p>5번째 시도: 최종적으로 완전한 매칭 성공</p>
</li>
<li><p>이 과정에서 이미 일치했던 abc를 계속 비교하며 연산이 낭비된다 (파란색 박스)</p>
</li>
</ul>
<p><strong>✅ 비효율적인 사례 설명 (Example of Inefficiency)</strong>:</p>
<ul>
<li><p>패턴 문자열과 텍스트의 일부가 매칭되더라도, 마지막 문자에서 불일치가 발생하면 앞서 비교했던 모든 연산이 무의미해진다.</p>
</li>
<li><p>텍스트에서 패턴을 한 칸씩 이동하며 처음부터 다시 비교를 시작해야 한다.</p>
</li>
</ul>
<p>✅ <strong>첨부 예제와 연결된 설명 (Example Analysis)</strong>:</p>
<ul>
<li><strong>텍스트 (Text)</strong>: <code>...abcdabcdabcwz</code>, <strong>패턴 (Pattern)</strong>: <code>abcdabcwz</code></li>
</ul>
<p>✅<strong>문제점 (Problem)</strong>: 매칭 실패 시 매번 처음부터 다시 비교하고 불필요한 연산이 많아 시간 효율성이 떨어진다.</p>
<p><strong><mark>💡 중요한 포인트 (Key Points)</mark></strong></p>
<ul>
<li><p><strong>마지막 문자 불일치 (Mismatch at the last character)</strong>: 불필요한 반복 연산 발생.</p>
</li>
<li><p><strong>패턴의 앞/뒤 일치 정보 활용 (Use pattern prefix/suffix)</strong>: 효율적인 매칭 가능.</p>
</li>
<li><p><strong>비효율성의 해결 (Resolving inefficiency)</strong>: 오토마타(automata)이용 매칭</p>
</li>
</ul>
<hr />
<h2 id="heading-3-automata-theory"><strong>3️⃣</strong>오토마타 이론 (Automata Theory)</h2>
<p>오토마타는 수학적이고 추상적인 기계로, 현재 상태를 기반으로 입력에 따라 상태를 변화시키며 출력을 생성한다. 이는 컴퓨터 과학에서 알고리즘과 문제를 해결하는 데 매우 유용하며, 문자열 매칭에서도 활용될 수 있다.</p>
<h4 id="heading-what-is-automata-theory"><strong>✅ 오토마타 이론이란? (What is Automata Theory)</strong></h4>
<ul>
<li><p><strong>어원 (Etymology)</strong>: <strong>오토 (Auto)</strong>: 자동, <strong>마토 (Mata)</strong>: 기계</p>
</li>
<li><p><strong>정의 (Definition)</strong>: 오토마타는 문제를 해결하기 위해 유한한 상태를 정의하고, 특정 규칙에 따라 상태를 전환하며 출력을 생성하는 **추상적 기계 (Abstract Machine)**이다. 이는 실제 컴퓨터가 아닌 수학적으로 모델링된 개념이다.</p>
</li>
</ul>
<p><strong>✅오토마타의 예(Example of Automata Theory)</strong></p>
<p>현재 컴퓨터의 상태와 입력에 따라 생성되는 결과가 정의되어 있고 특정규칙에 따라 출력 결과가 달라지는 오토마타 이론의 좋은 예제이다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733588459555/58281266-eb16-4985-a6ab-ad912e58608a.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>상태 (States)</strong>:</p>
<ol>
<li><p><code>off 상태</code></p>
</li>
<li><p><code>절전 상태</code></p>
</li>
<li><p><code>on 상태</code></p>
</li>
</ol>
</li>
<li><p><strong>전이 (Transitions)</strong>:</p>
<ul>
<li><p>전원 스위치를 켜면 <code>off → 절전 상태</code></p>
</li>
<li><p>입력(키보드/마우스)이 있으면 <code>절전 상태 → on 상태</code></p>
</li>
<li><p>일정 시간 입력이 없으면 <code>on 상태 → 절전 상태</code></p>
</li>
<li><p>전원 스위치를 끄면 모든 상태에서 <code>off 상태</code>로 복귀</p>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-comparison-of-naive-matching-and-automata-based-matching">✅ 원시적 매칭과 오토마타 이용 매칭 비교(Comparison of Naive Matching and Automata-Based Matching)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733588997439/c43f57f0-0494-4592-99e9-eb6f9013cca7.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>원시적 매칭 vs. 오토마타 이용 매칭 (Naive Matching vs. Automata-Based Matching)</strong>:</p>
<ul>
<li><p>원시적 매칭은 패턴의 문맥을 고려하지 않아 불필요한 연산이 많다.</p>
</li>
<li><p>오토마타는 상태와 문맥을 활용하여 효율적으로 비교 연산을 줄이게 된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-1-components-of-automata">오토마타 이론 #1: 구성요소 (Components of Automata)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733589088620/5b3c8daa-6792-41d2-b30c-5e0303b91e77.png" alt class="image--center mx-auto" /></p>
<p>오토마타는 아래 5가지 구성 요소로 정의된다:</p>
<ol>
<li><p><strong>Q (상태 집합, Set of states)</strong>: 시스템이 가질 수 있는 모든 상태의 집합.</p>
</li>
<li><p><strong>q₀ (시작 상태, Initial state)</strong>: 입력 처리를 시작하는 초기 상태.</p>
</li>
<li><p><strong>A (목표 상태의 집합, Set of accept states)</strong>: 입력을 받아들이는 종료 상태(들).</p>
</li>
<li><p><strong>Σ (입력 알파벳, Input alphabet)</strong>: 시스템이 처리할 수 있는 입력의 집합.</p>
</li>
<li><p><strong>δ (상태 전이 함수, Transition function)</strong>: 특정 상태에서 특정 입력을 받을 때 이동할 다음 상태를 정의하는 함수.</p>
</li>
</ol>
<hr />
<h3 id="heading-2-example-of-transition-function-diagram">오토마타 이론 #2: <strong>상태 전이 함수 다이어그램 표현(Example of</strong> Transition Function Diagram)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733618858980/48e17b32-5e43-4741-9953-6789d78edfa3.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>시작 상태 (State 0)</strong>:</p>
<ul>
<li><p>아무 문자도 처리하지 않은 초기 상태이다.</p>
</li>
<li><p>첫 번째 입력이 <code>a</code>라면 상태 1로 이동한다.</p>
</li>
</ul>
</li>
<li><p><strong>상태 1</strong>:</p>
<ul>
<li><p>입력이 <code>b</code>라면 상태 2로 이동한다.</p>
</li>
<li><p>만약 다른 문자가 오면 상태 0으로 돌아간다.</p>
</li>
<li><p><strong>이유</strong>: 패턴이 <code>ababaca</code>로 시작하므로 <code>b</code>가 와야 다음으로 진행된다.</p>
</li>
</ul>
</li>
<li><p><strong>상태 2 → 상태 6</strong>:</p>
<ul>
<li>올바른 입력이 계속되면 상태가 차례로 증가하게 된다.<br />  예: 상태 2에서 <code>a</code> → 상태 3, 상태 3에서 <code>b</code> → 상태 4...</li>
</ul>
</li>
<li><p><strong>상태 7</strong>:</p>
<ul>
<li><p>입력된 문자가 <code>ababaca</code>와 완전히 매칭되면 최종 상태 7에 도달한다.</p>
</li>
<li><p>이는 패턴을 성공적으로 찾았다는 뜻이다.</p>
</li>
</ul>
</li>
<li><p><strong><mark>잘못된 입력 처리</mark></strong><mark>:</mark></p>
<ul>
<li><p>예: 상태 5에서 <code>b</code>가 오면 전 단계 상태 4로 되돌아간다.</p>
</li>
<li><p>이는 이미 매칭된 일부 정보를 재사용하여 불필요한 비교를 줄이기 위함이다.</p>
</li>
<li><p>원시적 매칭이었다면 처음부터 돌아가지만 오토마타는 중간 단계로 이동하기 때문에 비교연산을 줄일 수 있는 것이다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p>상태 전이 함수는 위에서 언급된 예제처럼 다이어그램도 표시할 수 있고 이번 예제와 같이 테이블로도 구성할 수 있다.</p>
<h3 id="heading-2-automata-transition-function-table-representation"><strong>오토마타 #2: 상태 전이 함수 테이블 구성</strong> (Automata Transition Function Table Representation)</h3>
<p><strong>✅ 상태 전이 함수란? (What is Transition Function):</strong> 상태 전이 함수는 현재 상태에서 입력 문자를 기반으로 다음 상태를 결정한다.</p>
<ul>
<li><p>예: 상태 0에서 입력이 <code>a</code>이면 상태 1로 이동.</p>
</li>
<li><p>이는 문자열 매칭 과정을 구조적으로 표현하는 방법이다.</p>
</li>
</ul>
<p>✅ <strong>테이블 구성 (Table Representation):</strong> 상태 전이 다이어그램을 테이블 형태로 변환한다.</p>
<ul>
<li><p><strong>행 (Row)</strong>: 현재 상태.</p>
</li>
<li><p><strong>열 (Column)</strong>: 입력 문자.</p>
</li>
<li><p><strong>값 (Value)</strong>: 다음 상태.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733618873737/bae1da9b-5c99-4428-8550-baa758354631.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p>왼쪽 기본 테이블 (Full Table): 모든 알파벳에 대해 상태를 정의한다.</p>
</li>
<li><p>압축된 테이블 (Compressed Table): 패턴에 등장하는 문자(a, b, c)만 고려하고, 나머지 문자(기타)는 한 열로 압축하여 테이블을 단순화하고 시간을 절약하였다.</p>
</li>
</ol>
<p><strong><mark>💡중요포인트:</mark></strong></p>
<ul>
<li><p><strong>기존 방식</strong>: 모든 알파벳에 대해 상태를 정의 → 테이블이 복잡</p>
</li>
<li><p><strong>개선 방식</strong>: 패턴에 등장하는 문자만 고려 → 테이블이 단순화되고 연산 효율 증가</p>
</li>
</ul>
<hr />
<h3 id="heading-3-pseudocode-in-automata"><strong>오토마타 #3: 의사코드 표현 (Pseudocode in Automata)</strong></h3>
<p>오토마타를 활용해 문자열 매칭을 수행하는 과정을 의사코드로 확인해보자.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733619249715/5ea9b3bd-2929-4f5c-906e-a57f79713be6.png" alt class="image--center mx-auto" /></p>
<p><code>δ(delta)</code> <strong>오토마타에서 상태 전이 함수 (Transition Function)를 의미한다.</strong> 오토마타가 현재 상태에서 특정 입력을 받았을 때 어디로 이동해야 하는지를 결정하는 역할을 한다.</p>
<ul>
<li><code>δ(delta)</code> 그리스 문자의 이름으로, 수학과 컴퓨터 과학에서 함수나 변화(transition)를 나타낼 때 자주 사용한다.</li>
</ul>
<pre><code class="lang-python">FA-Matcher (A[], δ[][], f):
▷ f : 목표 상태 (Final state), 
▷ n : 텍스트 A의 길이, 
▷ m : 패턴 길이

q ← <span class="hljs-number">0</span>  ▷ q는 현재 상태를 나타냄

<span class="hljs-keyword">for</span> i ← <span class="hljs-number">1</span> to n:  ▷ 텍스트의 각 문자를 처리
    q ← δ(q, A[i])  ▷ 현재 상태와 입력 문자에 따라 상태 전이
    <span class="hljs-keyword">if</span> (q = f):  ▷ 현재 상태가 최종 상태라면
        A[i - m + <span class="hljs-number">1</span>]에서 매칭이 발생했음을 알린다.
</code></pre>
<ol>
<li><p><strong>변수 정의 (Variables)</strong>:</p>
<ul>
<li><p>q: **현재 상태 (Current state)**를 나타낸다. 시작 상태는 0이다.</p>
</li>
<li><p>δ: **상태 전이 함수 (Transition function)**이다. 현재 상태와 입력 문자에 따라 다음 상태를 반환한다.</p>
</li>
<li><p>f: **최종 상태 (Final state)**로, 패턴 매칭이 완료되었음을 나타낸다.</p>
</li>
</ul>
</li>
<li><p><strong>작동 과정 (How It Works)</strong>:</p>
<ul>
<li><p>q ← 0: 시작 상태에서 시작한다.</p>
</li>
<li><p><code>for</code> 루프는 텍스트의 각 문자를 차례로 처리한다.</p>
</li>
<li><p><code>q ← δ (q, A[i])</code>: 현재 상태와 입력 문자 A[i]를 상태 전이 함수에 넣어 다음 상태로 이동한다.</p>
</li>
<li><p><code>if (q = f)</code>: 현재 상태가 최종 상태에 도달하면 매칭이 발생했음을 출력한다.</p>
<ul>
<li>매칭이 발생한 위치는 <code>i − m+1</code> 이다.</li>
</ul>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-4-time-complexity-of-string-matching-using-automata"><strong>오토마타 #4:</strong> 수행 시간 분석 (Time Complexity of String Matching Using Automata)</h3>
<p>오토마타를 이용한 문자열 매칭의 수행 시간은 크게 두 부분으로 나눌 수 있다:</p>
<ol>
<li><p><strong>매칭 수행 시간</strong>: 텍스트의 길이에 비례.</p>
</li>
<li><p><strong>오토마타 상태 전이 함수 준비 시간</strong>: 패턴과 알파벳 크기에 비례.</p>
</li>
</ol>
<hr />
<p>✅ <strong>매칭 수행 시간 (Matching Time)</strong></p>
<ul>
<li><p><strong>for 루프</strong>: 텍스트의 각 문자에 대해 한 번씩 상태 전이를 수행 → O(n)</p>
</li>
<li><p><strong>상태 전이 함수 실행</strong>: 한 번의 전이가 O(1)이므로 효율적.</p>
</li>
<li><p>매칭 수행 시간은 텍스트의 길이 n에 선형적으로 비례합니다.</p>
</li>
</ul>
<p>✅<strong>상태 전이 함수 준비 시간 (Setup Time for Transition Function)</strong></p>
<ul>
<li><p><strong>입력 알파벳의 개수</strong>: ∣Σ∣ 사용 가능한 문자(예: 영어 소문자 26개).</p>
</li>
<li><p><strong>패턴 길이</strong>: m</p>
</li>
<li><p><strong>준비 시간</strong>: <code>O(∣Σ∣m)</code> 모든 문자와 패턴의 길이에 대해 전이 상태를 정의.</p>
</li>
</ul>
<p><strong>✅총 수행 시간:</strong> <code>Θ(n+∣Σ∣m)</code></p>
<ul>
<li>n: 텍스트의 길이</li>
</ul>
<h4 id="heading-example-calculation"><strong>✅예제 수행 시간 계산 (Example Calculation)</strong></h4>
<ul>
<li><p><strong>입력 데이터</strong>:</p>
<ul>
<li><p>텍스트 길이 n=1000</p>
</li>
<li><p>패턴 길이 m=10</p>
</li>
<li><p>알파벳 크기 ∣Σ∣=26</p>
</li>
</ul>
</li>
<li><p><strong>계산</strong>:</p>
<ul>
<li><p>매칭 수행 시간: O(n) = O(1000)</p>
</li>
<li><p>상태 전이 함수 준비 시간: O(∣Σ∣m) = O(26⋅10) = O(260).</p>
</li>
<li><p>총 수행 시간: O(1000+260) = O(1260)</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong><mark>💡중요포인트:</mark></strong></p>
<ul>
<li><p>오토마타 기반 매칭은 텍스트 길이에 선형적으로 비례하여 효율적이다.</p>
</li>
<li><p>상태 전이 함수 준비 시간이 추가로 필요하지만, 이는 매칭 단계의 효율성을 보장하는 투자이다.</p>
</li>
<li><p>입력 데이터의 특성(텍스트 길이, 패턴 길이, 알파벳 크기)에 따라 성능이 결정된다.</p>
</li>
</ul>
<hr />
<h2 id="heading-4-rabin-karp-algorithms"><strong>4️⃣</strong>라빈 - 카프 알고리즘(Rabin-Karp Algorithms)</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733620228952/28e05a69-6b54-4c6e-aaa0-a448da4260c9.png" alt class="image--center mx-auto" /></p>
<p>문자열 매칭 알고리즘으로 라빈 카프 알고리즘에 대해서 배워본다. 문자열 패턴을 숫자로 변환하여 비교하는 효율적인 알고리즘이다.</p>
<ul>
<li><p><strong>라빈 카프 알고리즘(Rabin-Karp Algorithms):</strong> 가능한 문자들에 대해 숫자를 대응하고 패턴을 하나의 진수로 표현해서 만약 121 이라는 숫자가 있을 때, 문자로 하면 3개의 문자가 되듯이, 어떤 문자의 패턴이 있을때 그것을 각각의 문자로 할당해서 진수 표현하는 것이다. 이것을 “수치화 과정”이라고 한다.</p>
</li>
<li><p><strong><mark>수치화 (Numerical Transformation)</mark></strong><mark>: 문자열의 각 문자를 숫자로 매핑하고, 해당 숫자를 특정 진수(base)로 표현하여 문자열을 숫자로 변환하는 과정</mark></p>
</li>
<li><p>이 과정을 통해 빠르게 문자열의 패턴을 비교할 수 있게 된다.</p>
</li>
</ul>
<hr />
<p><strong>✅수치화의 예 (Example of Numerical Transformation)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733622659147/285b5d38-ad16-499a-b48e-454b4e2992eb.png" alt class="image--center mx-auto" /></p>
<p><strong>주어진 데이터</strong>: 알파벳 집합 <code>Σ={a,b,c,d,e}</code>, 크기 <code>∣Σ∣=5</code></p>
<ul>
<li>매핑: <code>a=0</code>, <code>b=1</code>, <code>c=2</code>, <code>d=3</code>, <code>e=4</code></li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733622666523/8a0f5937-4992-4fef-a297-d54a749a60ef.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>진수로 변환</strong>:</p>
<ul>
<li><p>c = <code>2⋅5^2</code> : <code>2×25</code> = 50</p>
</li>
<li><p>a = <code>0 ⋅ 5^1</code> : <code>0×5</code> = 0</p>
</li>
<li><p>d = <code>3 ⋅ 5^0</code> : <code>3×1</code> = 3</p>
</li>
</ul>
</li>
<li><p><strong>합산</strong>:</p>
<ul>
<li>50 + 0 + 3 = 53</li>
</ul>
</li>
</ul>
<p><strong><mark>💡라빈-카프 알고리즘의 중요포인트</mark></strong></p>
<ul>
<li><p>문자열을 숫자로 변환하여, 숫자 비교로 문자열 패턴 매칭을 수행한다.</p>
</li>
<li><p><strong>효율성</strong>: 숫자 비교는 문자열 비교보다 빠르며, 해시 값을 이용하여 중복 계산을 줄일 수 있게된다.</p>
</li>
</ul>
<hr />
<p><strong>✅수치화 계산 방법 기본 개념 (Basic Numerical Calculation)</strong></p>
<p><strong>문자를 숫자로 변환 (Mapping Characters to Numbers)</strong>: 문자열의 각 문자를 고유한 숫자로 매핑한다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733624272353/ab979e65-d15c-458d-bb46-c127100616fa.png" alt class="image--center mx-auto" /></p>
<p><strong>진수로 표현 (Representing in Base ddd)</strong>: 문자열의 각 문자를 진수 기반으로 표현하여 수치화한다.</p>
<ul>
<li><p>예) 10진수(기본 진수 d=10)를 사용하여 P[m] 다음과 같이 표현:</p>
</li>
<li><p><code>p=P[m] + 10⋅P[m−1] + 102⋅P[m−2] + ⋯ + 10m−1⋅P[1]</code></p>
<ul>
<li><p>P[1]: 가장 왼쪽 문자 → 가장 큰 자리수.</p>
</li>
<li><p>P[m]: 가장 오른쪽 문자 → 가장 작은 자리수.</p>
</li>
</ul>
</li>
</ul>
<p><strong>일반적인 계산 방식 (Basic Calculation):</strong> 텍스트에서 부분 문자열 <code>A[i⋯i+m−1]</code>를 수치화하는 계산:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733624548877/aaa6fb26-7141-4a8a-94eb-d560bffad7f7.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>시간 복잡도 O(m): 각 문자마다 10을 곱하면서 반복 계산.</p>
</li>
<li><p>전체 텍스트에 대해 비교: n개의 부분 문자열에 대해 계산 → 총 O(mn)</p>
<ul>
<li><strong><mark>문제점: 원시적 매칭과 성능 차이가 없음.</mark></strong></li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅수치화 계산의 개선 방법 (Optimization in Calculation)</strong></p>
<ul>
<li><strong>점화식 사용 (Using Recursive Formula)</strong>:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733624626702/e50522c8-4088-4c2c-af4f-8f2fce857925.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>이전 계산값 <code>ai−1</code>을 재활용하여 다음 값 <code>ai</code>를 계산에 활용한다.</p>
</li>
<li><p><strong>첫 번째 문자 영향 제거</strong>: <code>−10m−1 ⋅ A[i−1]</code></p>
</li>
<li><p><strong>새로운 문자 추가</strong>: <code>+A[i + m−1]</code></p>
</li>
</ul>
<h4 id="heading-kirwn5kh8jkloy1noygge2zlcdtmqjqs7zqsiag7j6i7j2e6rmmpydqt7jroifri6qukio"><strong><mark>💡🤔최적화 효과가 있을까? 그렇다.</mark></strong></h4>
<ul>
<li><p>각 부분 문자열 계산이 O(1)로 줄어듦.</p>
</li>
<li><p>전체 텍스트 비교 시간: O(n)</p>
</li>
<li><p>미리 계산된 <code>10m−1</code>는 반복 사용 가능.</p>
</li>
</ul>
<p><strong><mark>💡중요 포인트:</mark></strong></p>
<ol>
<li><p><strong>수치화 개념 (Numerical Transformation)</strong>: 문자열을 숫자로 변환해 효율적으로 비교한다.</p>
</li>
<li><p><strong>기본 계산 방식의 한계 (Limitations of Basic Calculation)</strong>: 시간 복잡도 O(mn)으로 원시적 매칭과 동일한 점</p>
</li>
<li><p><strong>점화식을 통한 개선 (Optimization Using Recursive Formula)</strong>: 이전 계산값을 활용하여 불필요한 연산 제거한다. O(n)시간 복잡도로 최적화하였다.</p>
</li>
<li><p><strong>효율성 (Efficiency)</strong>: 덧셈과 곱셈 각각 2회로 계산 가능 → 이뜻은 실행 속도가 대폭 증가되었다는 뜻이다.</p>
</li>
</ol>
<hr />
<p><strong>✅라빈-카프 알고리즘 수치화를 이용한 매칭 예제 (Example of Matching Using Numerical Transformation in the Rabin-Karp Algorithm)</strong></p>
<h4 id="heading-summary"><strong><mark>💡 요약 (Summary)</mark></strong></h4>
<ol>
<li><p><strong>수치화 계산 방법 (Numerical Calculation)</strong>:</p>
<ul>
<li><p>문자열의 각 문자를 진수로 변환하여 수치값을 계산한다.</p>
</li>
<li><p>P[]: 패턴 문자열 → 고유 수치값 계산.</p>
</li>
<li><p>A[]: 텍스트의 부분 문자열 → 고유 수치값 계산 후 패턴과 비교.</p>
</li>
</ul>
</li>
<li><p><strong>점화식을 사용한 계산 (Using Recursive Formula)</strong>:</p>
<ul>
<li><p>이전 계산값을 활용해 현재 값을 효율적으로 계산한다.</p>
</li>
<li><p>수치값이 같으면 문자열 매칭 성공하였다는 뜻이다</p>
</li>
</ul>
</li>
<li><p><strong>효율성</strong>: 점화식을 통해 한 칸씩 이동하며 효율적으로 비교할 수 있다.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733625315017/23ba27dc-1b01-4aa7-9021-9f2157fc5ad7.png" alt class="image--center mx-auto" /></p>
<p><strong>❤️ P[] 패턴 문자열의 수치화 (Numerical Transformation of Pattern):</strong> <code>eeaab</code></p>
<ul>
<li><p>각 문자에 고유한 값을 부여한다. <code>e=4</code>, <code>a=0</code>, <code>b=1</code></p>
</li>
<li><p><code>e=4, 5^4=625</code>, <code>a = 0</code>, <code>b = 1</code> 결과: <code>p=300</code></p>
</li>
</ul>
<p><strong>❤️A[] 텍스트 문자열의 수치화 (Numerical Transformation of Text):</strong> <code>acebb</code></p>
<ul>
<li><p>처음 5개의 문자를 가져와 수치 계산.</p>
</li>
<li><p>결과: <code>a1 = 356</code></p>
</li>
<li><p><strong>매칭 실패 (Does not match p = 3001)</strong></p>
</li>
</ul>
<p><strong>❤️문자열의 수치화 대신 점화식을 사용한 효율적 계산 (Efficient Calculation Using Recursive Formula)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733625866559/a0602dfc-eaa0-47fc-aac1-73a012f02d04.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><code>ai−1</code>​: 이전 계산값.</p>
</li>
<li><p><code>5m−1⋅A[i−1]</code>: 빠지는 첫 번째 문자.</p>
</li>
<li><p><code>A[i+m−1]</code>: 추가되는 마지막 문자.</p>
</li>
</ul>
<h4 id="heading-calculation-process"><strong>❤️ 계산 과정 (Calculation Process)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733625917168/5ba0e375-068d-41b4-bd9b-653d9f153667.png" alt class="image--center mx-auto" /></p>
<p><strong>a2​:</strong> <code>cebbc</code></p>
<ul>
<li><p>결과: a2 = 1782</p>
</li>
<li><p><strong>매칭 실패 (Does not match p=3001)</strong>.</p>
</li>
</ul>
<p><strong>a3​:</strong> <code>ebbce</code></p>
<ul>
<li><p>결과: a3 = 2664</p>
</li>
<li><p><strong>매칭 실패 (Does not match p=3001)</strong>.</p>
</li>
</ul>
<p><strong>a7:</strong> <code>eeaab</code></p>
<ul>
<li><p>결과: a7 = 3001</p>
</li>
<li><p><strong>매칭 성공 (Matches p=3001)</strong>.</p>
</li>
</ul>
<hr />
<h4 id="heading-key-points-1"><strong><mark>💡중요한 포인트 (Key Points)</mark></strong></h4>
<ol>
<li><p><strong>수치화의 효율성 (Efficiency of Numerical Transformation)</strong>:</p>
<ul>
<li><p>점화식을 통해 이전 계산값을 재활용한다.</p>
</li>
<li><p>계산 복잡도를 O(mn)에서 O(n)으로 줄였다.</p>
</li>
</ul>
</li>
<li><p><strong>정확성 (Accuracy)</strong>:</p>
<ul>
<li><p>동일한 문자열은 동일한 수치값을 가진다.</p>
</li>
<li><p>수치값이 일치하면 문자열 매칭 성공하게 된다.</p>
</li>
</ul>
</li>
<li><p><strong>효율적 매칭 (Efficient Matching)</strong>:</p>
<ul>
<li>한 칸씩 이동하며 계산 → 빠르고 정확한 비교가 가능하다.</li>
</ul>
</li>
</ol>
<hr />
<p><strong><mark>💡왜 수치화를 하는 걸까? (Why Use Numerical Transformation?)</mark></strong></p>
<p>문자열 비교는 한 문자씩 순서대로 확인해야 하므로 시간이 오래 걸리게 된다.<br />라빈-카프는 문자열을 숫자로 변환하고 <strong>숫자끼리 비교</strong>하므로 더 빠르게 매칭 여부를 확인할 수 있다.</p>
<hr />
<p><strong>✅라빈-카프 알고리즘 수치화를 이용한 의사코드 표현 (Pseudocode for Rabin-Karp Algorithm Using Numerical Transformation)</strong></p>
<p><strong><mark>💡요약:</mark></strong> 아래 예제 코드는 **패턴 문자열 P[]**를 텍스트 문자열 A[]에서 찾아내는 과정을 보여준다. 문자열을 숫자로 변환(수치화)하고, 이 숫자값을 비교하여 매칭 여부를 확인한다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733626052791/416f5132-78e3-438c-935d-0ff8acd19c46.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-initialization"><strong>1. 초기화 (Initialization)</strong></h4>
<pre><code class="lang-python">p ← <span class="hljs-number">0</span>; a₁ ← <span class="hljs-number">0</span>
</code></pre>
<ul>
<li><p><strong>p</strong>: 패턴 문자열 P[]의 수치값을 저장하는 변수이다.</p>
</li>
<li><p><strong>a₁</strong>: 텍스트의 첫 번째 부분 문자열의 수치값을 저장하는 변수이다.</p>
</li>
</ul>
<hr />
<h4 id="heading-2"><strong>2. 패턴과 첫 번째 부분 문자열의 수치값 계산</strong></h4>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> i ← <span class="hljs-number">1</span> to m:
    p ← d * p + P[i]
    a₁ ← d * a₁ + A[i]
</code></pre>
<ul>
<li><p>i: 현재 패턴과 텍스트의 문자를 계산하는 위치.</p>
</li>
<li><p><strong>패턴 수치값 계산</strong>:</p>
<ul>
<li><p><code>p = d⋅p+P[i]</code> : 기존 값에 d를 곱하고 현재 문자의 값을 더한다.</p>
</li>
<li><p>예: <code>eeaab</code> → p = 4⋅5^4 + 4⋅5^3 + 0⋅5^2 + 0⋅5^1 + 1⋅5^0 = 3001</p>
</li>
</ul>
</li>
<li><p><strong>첫 번째 텍스트 부분 문자열 수치값 계산</strong>: <code>a1 = d⋅a1+A[i]</code></p>
<ul>
<li>예: <code>acebb</code> → a1 = 0⋅5^4 + 2⋅5^3 + 4⋅5^2 + 1⋅5^1 + 1⋅5^0 = 356</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-3"><strong>3. 나머지 부분 문자열의 수치값 계산 (점화식 사용)</strong></h4>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> i ← <span class="hljs-number">1</span> to n - m + <span class="hljs-number">1</span>:
    <span class="hljs-keyword">if</span> (i ≠ <span class="hljs-number">1</span>):
        aᵢ ← d * (aᵢ₋₁ - dᵐ⁻¹ * A[i<span class="hljs-number">-1</span>]) + A[i+m<span class="hljs-number">-1</span>]
</code></pre>
<ul>
<li><p><strong>i≠1</strong>: 두 번째 부분 문자열부터 계산한다.</p>
</li>
<li><p><strong>점화식 설명</strong>:</p>
<ul>
<li><p><code>aᵢ</code>: 새로운 부분 문자열의 수치값.</p>
</li>
<li><p><code>aᵢ₋₁</code>​: 이전 부분 문자열의 수치값.</p>
</li>
<li><p><code>dm−1</code>: 패턴 길이에 따라 계산된 상수.</p>
</li>
<li><p><code>A[i−1]</code>: 빠지는 문자.</p>
</li>
<li><p><code>A[i+m−1]</code>: 추가되는 문자.</p>
</li>
</ul>
</li>
</ul>
<p><strong>예제 계산</strong>:</p>
<ul>
<li><p>a1 = 356</p>
</li>
<li><p>a2 = 5⋅(a1−0⋅625)+2 = 1782</p>
</li>
<li><p>a3 = 5⋅(a2−2⋅625)+4 = 2664</p>
</li>
</ul>
<hr />
<h4 id="heading-4"><strong>4. 매칭 여부 확인</strong></h4>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> (p = aᵢ): A[i] 자리에서 매칭이 되었음을 알린다.
</code></pre>
<ul>
<li><p>패턴의 수치값 p와 현재 부분 문자열의 수치값 aᵢ​를 비교.</p>
</li>
<li><p>값이 같다면 패턴이 텍스트의 해당 위치에서 매칭됨을 의미.</p>
</li>
</ul>
<p><strong>예제</strong>:</p>
<ul>
<li><p>패턴 p=3001</p>
</li>
<li><p>a₇ = 3001 → 매칭 성공</p>
</li>
</ul>
<hr />
<p><strong><mark>💡중요포인트</mark></strong></p>
<ol>
<li><p><strong>수치화</strong>: 문자열을 숫자로 변환하여 비교한다.</p>
</li>
<li><p><strong>점화식</strong>: 이전 계산값을 재활용해 효율적 계산을 한다.</p>
</li>
<li><p><strong>매칭 성공 조건</strong>: 패턴 수치값 p와 부분 문자열 수치값 aᵢ​가 같으면 매칭이 성공된다.</p>
</li>
<li><p><strong>효율성</strong>: 반복 계산을 줄이고 O(n) 시간 안에 패턴 매칭을 완료한다.</p>
</li>
</ol>
<hr />
<p><strong>✅라빈-카프 알고리즘 수치화를 이용한 매칭의 문제점</strong></p>
<p><strong><mark>💡요약: </mark></strong> 문자열 패턴과 텍스트의 수치화 과정에서 계산 결과가 너무 커져 **오버플로우 (Overflow)**가 발생할 수 있다. 이를 해결하기 위해 **모듈러 연산 (Modulo operation)**을 사용하여 값을 제한한다.</p>
<ul>
<li><p><strong>라빈-카프 알고리즘 (Rabin-Karp Algorithm)</strong>:</p>
<ul>
<li><p>문자열 패턴과 텍스트를 **해시값 (Hash value)**으로 변환한 뒤, 빠르게 비교한다.</p>
</li>
<li><p>모듈러 연산을 적용하여 효율적으로 수치화된 값 비교 수행할 수 있게 된다.</p>
</li>
</ul>
</li>
<li><p><strong>장점</strong>: 빠른 매칭 가능하고 모듈러 연산을 통해 값의 크기를 제한하여 오버플로우 방지한다.</p>
</li>
</ul>
<h4 id="heading-challenges-and-solutions"><strong>❤️ 문제점과 해결책 (Challenges and Solutions)</strong></h4>
<ol>
<li><p><strong>문제점</strong>:</p>
<ul>
<li><p><strong>문자 집합 Ω와 패턴 길이 m에 따라 ai가 매우 커질 수 있음.</strong><br />  예: <code>a_i = d(a_{i-1} - d^{m-1}A[i-1]) + A[i+m-1]</code></p>
</li>
<li><p>너무 큰 숫자는 레지스터의 크기를 초과해 **오버플로우 (Overflow)**가 발생한다.</p>
</li>
</ul>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733627496892/290b4a2e-9e5c-40a0-b180-d9ea36fba0dd.png" alt class="image--center mx-auto" /></p>
<ol start="2">
<li><p><strong>해결책</strong>: **모듈러 연산 (Modulo operation)**을 적용하여 값을 제한한다.</p>
<ul>
<li><p>식 변경: <code>bi ​= (d(bi−1​−(dm−1 modq) ⋅ A[i−1]) + A[i+m−1])</code></p>
</li>
<li><p><strong>q 값</strong>: 충분히 큰 소수를 선택해 충돌을 최소화한다.</p>
</li>
<li><p>이 방법은 <strong>해시테이블의 해시값 계산과 유사</strong>하다.</p>
</li>
</ul>
</li>
</ol>
<h4 id="heading-how-rabin-karp-works"><strong>❤️ 라빈-카프 알고리즘의 작동 원리 (How Rabin-Karp Works)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733627732263/9d2ef639-8c05-48bd-8fcd-5336c834e799.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>패턴 해시값 계산 (Hash Calculation for Pattern)</strong>:</p>
<ul>
<li><p>P[]=e,e,a,a,b</p>
</li>
<li><p>계산: <code>p = (4⋅5^4+4⋅5^3+0⋅5^2+0⋅5^1+1) mod 113 = 63</code></p>
</li>
</ul>
</li>
<li><p><strong>원문 해시값 계산 (Hash Calculation for Text)</strong>:</p>
<ul>
<li><p>원문 A[]: a, c, e, b, b, c, e, e, a, b, c, e, e, d, b…</p>
</li>
<li><p>처음 5개의 해시값 <code>a1 = 17</code></p>
</li>
<li><p>슬라이딩 윈도우 방식으로 다음 해시값 계산: <code>a2 = 87</code></p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733627738915/1fd51cba-649b-4bc8-84f5-ff6011f1f8b9.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>매칭 과정 (Matching Process)</strong>: 패턴 해시값 p=63 과 원문 해시값 ai를 비교하여 해시값이 같으면 실제 문자열을 확인하여 매칭 여부 판단한다.</p>
</li>
<li><p><strong>결과</strong>: <code>a7 = 63</code>에서 매칭 성공하였다.</p>
</li>
</ul>
<p><strong><mark>💡중요포인트: </mark> 라빈-카프 알고리즘은</strong> 빠르고 효율적인 문자열 매칭 알고리즘이다.</p>
<ul>
<li><p><strong>문제:</strong> 문자열 수치화 값이 커져 <strong>오버플로우</strong> 발생 가능.</p>
</li>
<li><p><strong>해결</strong>: <strong>모듈러 연산</strong>으로 값 크기를 제한하여 문제 해결.</p>
</li>
<li><p><strong>라빈-카프 알고리즘 작동 원리</strong>:</p>
<ul>
<li><p>패턴과 텍스트를 <strong>해시값</strong>으로 변환해 비교.</p>
</li>
<li><p>해시값이 같으면 매칭 성공(문자열도 추가 확인).</p>
</li>
</ul>
</li>
<li><p><strong>효율성</strong>: 슬라이딩 윈도우 방식으로 이전 계산 재활용 → 빠른 연산.</p>
</li>
<li><p><strong>결과</strong>: 패턴 해시값 p와 텍스트 해시값 ai가 동일한 경우 매칭 확인하게 된다.</p>
</li>
</ul>
<hr />
<p><strong>❤️ 라빈-카프 알고리즘 의사코드 요약 (Rabin-Karp Algorithm Pseudocode Summary)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733628065277/c91aaee4-2d14-41d3-a040-6fcf2d035ecf.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>모듈러 연산 사용 (Use of Modulo Operation)</strong>:</p>
<ul>
<li><p>해시값 계산 중 값이 커지는 것을 방지하기 위해 모든 계산에 <strong>모듈러 연산</strong>이 적용되었다.</p>
</li>
<li><p>p, bi​, h 계산 시 <strong>mod q를</strong> 사용한다.</p>
</li>
</ul>
</li>
<li><p><strong>시간복잡도 (Time Complexity)</strong>:</p>
<ul>
<li>전체 비교에 O(n + km), 평균적으로 O(n)의 복잡도를 갖는다.</li>
</ul>
</li>
<li><p><strong>작동 원리 (How it Works)</strong>:</p>
<ul>
<li><p>패턴 P의 해시값 p와 텍스트 A의 해시값 bi​를 계산한다.</p>
</li>
<li><p>해시값이 일치하면, 실제 문자열도 비교하여 매칭 여부 확인하게 된다.</p>
</li>
</ul>
</li>
</ul>
<p>**<mark>💡중요포인트: </mark> 모듈러 연산 (Modulo Operation)**을 통해 수치화 과정에서 값이 너무 커지지 않도록 계산마다 사용하여 오버플로우를 방지한다. 패턴 해시값 p와 텍스트 해시값 bi를 비교하여 매칭 여부를 빠르게 판단하고 슬라이딩 윈도우를 사용하여 효율적으로 계산한다. 문자열 매칭을 빠르게 수행이 가능하고 큰 데이터를 처리할때 효율적이다. 또한 해시값 충돌 가능성 때문에 해시값이 같을 경우 문자열을 추가로 비교하여 정확성을 보장한다.</p>
<hr />
<h2 id="heading-5kmp-kmp-algorithm-and-failure-function">5️⃣KMP 알고리즘과 실패 함수 (KMP Algorithm and Failure Function)</h2>
<p>💡 <strong>요약 (Summary)</strong></p>
<ol>
<li><p><strong>KMP 알고리즘의 핵심 (Core Idea of KMP)</strong>:</p>
<ul>
<li>매칭 실패 시 처음으로 돌아가지 않고, <strong>중간 지점</strong>으로 복귀하여 이전 비교 결과를 활용한다. 이를 통해 <strong>비교 연산 횟수가 감소하게 된다.</strong></li>
</ul>
</li>
<li><p><strong>실패 함수 (Failure Function)</strong>:</p>
<ul>
<li><p>매칭 실패 시 돌아갈 중간 지점을 미리 계산하고 저장하는 함수이다.</p>
</li>
<li><p>패턴의 중복 정보를 활용하여 불필요한 연산을 줄일 수 있다.</p>
</li>
</ul>
</li>
<li><p><strong>KMP vs. 오토마타</strong>: 오토마타와 유사한 방식으로 동작하지만, 준비 작업이 더 단순하고 빠르다는 장점이 있다. (오토마타는 상태함수 준비시 좀 복잡하다)</p>
</li>
</ol>
<hr />
<p><strong>✅ KMP 알고리즘 vs. 오토마타 매칭 비교 (KMP Algorithm vs. Automata Matching)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733628922663/0e5c7fc4-978f-4b28-ba49-66c2f2a54680.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>원시적 매칭과의 차이 (Compared to Naive Matching)</strong>:</p>
<ul>
<li><p><strong>원시적 매칭</strong>: 매칭 실패 시 처음으로 돌아가 다시 비교.</p>
</li>
<li><p><strong>오토마타와 KMP</strong>: 실패 시 <strong>중간 상태</strong>로 이동해 이전 결과를 재활용.</p>
</li>
</ul>
</li>
<li><p><strong>KMP와 오토마타의 공통점 (Similarities)</strong>:</p>
<ul>
<li>둘 다 <strong>문맥 활용</strong>: 매칭 실패 시 중간 상태로 이동해 불필요한 연산 제거.</li>
</ul>
</li>
<li><p><strong>KMP와 오토마타의 차이점 (Differences)</strong>:</p>
<ul>
<li><p><strong>오토마타</strong>: 상태 전이 함수를 상태 다이어그램으로 정의. 준비 과정이 복잡하지만 체계적이다.</p>
</li>
<li><p><strong>KMP</strong>: <strong>실패 함수</strong>를 이용해 중간 지점을 계산한다. 준비 과정이 단순하고 빠르다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅KMP 알고리즘 작동 원리 (How KMP Works)</strong></p>
<ol>
<li><p><strong>패턴 매칭</strong>:</p>
<ul>
<li><p>텍스트와 패턴을 비교하면서 <strong>실패 시 중간 지점으로 이동</strong>한다.</p>
</li>
<li><p>실패 함수 π[i]를 사용하여 복귀 지점을 결정한다.</p>
</li>
</ul>
</li>
<li><p><strong>실패 함수의 역할 (Role of Failure Function)</strong>:</p>
<ul>
<li><p>매칭 실패 시 처음으로 돌아가지 않고, 이미 매칭된 부분을 활용해 비교를 이어간다.</p>
</li>
<li><p>예: π[8] = 4 → 8번째 자리에서 실패하면 4번째 자리로 복귀하게 된다.</p>
</li>
</ul>
</li>
<li><p><strong>실패 함수 계산 (Failure Function Calculation)</strong>:</p>
<ul>
<li><p>패턴 내에서 부분 문자열의 중복 정보를 활용하여 재활용한다.</p>
</li>
<li><p>패턴의 각 위치에서 실패 시 복귀할 위치를 저장한다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅예제 분석 (Example Analysis)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733629098282/2e18fde4-76ae-4e56-afb5-a4e03c7f9f1e.png" alt class="image--center mx-auto" /></p>
<p><strong>매칭 실패 예시</strong>: 텍스트: <code>A[]</code>, 패턴: <code>P[] = abcdabcwz</code></p>
<ul>
<li><p>텍스트에서 <code>abcdabcd</code> 까지 매칭 후 <code>d≠w</code>에서 실패가 발생하였다.</p>
</li>
<li><p>실패 함수로 인해 중복된 abc를 살리고, d부터 비교를 재개한다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733629107862/03214872-3641-40c2-bdde-7eeaf6f0954c.png" alt class="image--center mx-auto" /></p>
<p><strong>실패 함수 예시</strong>: <code>P [1:9] = abcdabcwz</code></p>
<ul>
<li><p>π[8]=4: 8번째 자리에서 실패 시 4번째 자리 d로 이동한다.</p>
</li>
<li><p>결과적으로 4칸 점프하여 불필요한 연산 제거하게 된다.</p>
</li>
</ul>
<hr />
<h4 id="heading-kmp-advantages"><strong>✅KMP의 장점 (Advantages)</strong></h4>
<ol>
<li><p><strong>효율적 비교</strong>: 실패 함수로 중복 비교를 줄여 평균적으로 O(n+m)시간 복잡도를 가진다.</p>
</li>
<li><p><strong>중복 활용</strong>: 패턴 내부 중복을 활용해 이전 결과를 재활용한다.</p>
</li>
<li><p><strong>실제 응용</strong>: 텍스트 검색, 네트워크 패턴 매칭, 데이터 분석 등에 사용된다.</p>
</li>
</ol>
<hr />
<h4 id="heading-kmp-kmp-algorithm-and-failure-function-pseudocode"><strong>✅</strong> KMP 알고리즘과 실패 함수 준비 의사코드 (KMP Algorithm and Failure Function Pseudocode)</h4>
<h4 id="heading-summary-1"><strong><mark>💡 요약 (Summary)</mark></strong></h4>
<ol>
<li><p><strong>실패 함수 준비 (Failure Function Preparation)</strong>:</p>
<ul>
<li><p><strong>목적</strong>: 패턴 내 중복 정보를 활용해 매칭 실패 시 돌아갈 위치를 미리 계산.</p>
</li>
<li><p><strong>시간복잡도</strong>: Θ(m), 패턴의 길이 m에 비례.</p>
</li>
</ul>
</li>
<li><p><strong>KMP 알고리즘 (KMP Algorithm)</strong>:</p>
<ul>
<li><p>매칭 실패 시 실패 함수 π[j]를 이용해 중간 지점으로 점프.</p>
</li>
<li><p>원문의 각 문자에 대해 비교 진행 → Θ(n) 시간 소요.</p>
</li>
<li><p><strong>전체 시간복잡도</strong>: Θ(n+m)</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733629584392/7d4c56c7-c755-43d6-a76f-14483ee0a626.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>k: 패턴의 접두사 길이를 나타냄.</p>
</li>
<li><p>π[j]: 매칭 실패 시 복귀 위치를 저장.</p>
</li>
<li><p><code>if (k = 0 or A[j] = P[k]) j++, k++;</code> k = 0이거나 원문의 값이 <code>A[j] = P[k]</code>패턴의 값과 일치하면 계속 증가하다가 <code>π[j] &lt;- k</code> 실패함수 값을 업데이트해준다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733629588552/e43818fe-924b-423e-ab1b-31a9c4bbfad5.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><code>else j &lt;-π[j]</code> 매칭 실패 시 j를 π[j]로 업데이트하여 중간 지점으로 복귀한다.</p>
</li>
<li><p>매칭 성공 시, 매칭된 위치를 출력하고 다시 π[j]를 이용해 다음 비교 시작.</p>
</li>
<li><p>먼저 패턴에 대한 실패 함수를 준비한다. <code>preprocessing(p)</code> 그다음엔 while문을 돌며 실패가 일어났을 경우에 해당 지점으로 점프를 해나가게 된다. <code>else j &lt;-π[j]</code> 그러다가 전체적으로 매칭이 일어나면 매칭이 되고 처음으로 다시 돌아가게된다. <code>j &lt;-π[j]</code></p>
</li>
</ul>
<hr />
<h4 id="heading-time-complexity-analysis"><strong>✅시간복잡도 분석 (Time Complexity Analysis)</strong></h4>
<ul>
<li><p><strong>실패 함수 준비</strong>: Θ(m), 패턴의 길이에 비례</p>
</li>
<li><p><strong>KMP 알고리즘</strong>: Θ(n), 원문의 길이에 비례.</p>
</li>
<li><p><strong>전체 시간복잡도</strong>: Θ(n + m), 효율적 문자열 매칭 가능.</p>
</li>
<li><p>실패 함수를 준비하는데 패턴의 길이만큼의 시간이 필요해서 <code>preprocessing() = Θ(m)</code>이고 while 루프는 원문의 문자를 하나씩 보기때문에 <code>Θ(n)</code>이다.</p>
</li>
</ul>
<hr />
<h2 id="heading-6-boyer-moore-algorithm">6️⃣보이어-무어 알고리즘(Boyer-Moore Algorithm)</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733634672809/2df2ef90-95a2-427f-a18e-7f713db04a05.png" alt class="image--center mx-auto" /></p>
<p>기존의 라빈 카프 알고리즘이나 kmp알고리즘은 텍스트 문자열을 처음부터 적어도 1번씩은 검색하기 때문에 세타n의 수행시간이 필요한다. 텍스트를 왼쪽에서 오른쪽으로 비교하기때문에 최선의 경우에도 n만큼의 시간이 필요하다. 보이어 무어는 생각의 전환을 하였다. 텍스트 문자열을 전부 보지 않고 점프를 하며 일부만 보는것이다 또한 패턴을 왼쪽이 아닌 오른쪽부터 비교하게 된다. 긴문자열의 패턴이 5개라면 5번째부터 본다. 매칭이 되지 않는다면 앞에있는 4개는 보지 않고 뛰어넘게된다. 이렇게 반복하는 방식이다. 실무적으로 높은 성능을 보이는 알고리즘으로 많이 사용되고 있다.</p>
<hr />
<h4 id="heading-how-it-works"><strong>✅ 보이어-무어 알고리즘 작동 원리 (How it Works)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733634840761/f966feec-480f-4b08-8dc8-1352c5016f4f.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>비교 방향</strong>: 전체 텍스르로 보면 앞으로 진행하지만 패턴으로 보면 반대방향으로 진행한다.</p>
<ul>
<li><p><strong>패턴</strong>: 오른쪽에서 왼쪽으로 비교.</p>
</li>
<li><p><strong>텍스트</strong>: 여전히 왼쪽에서 오른쪽으로 진행.</p>
</li>
</ul>
</li>
<li><p><strong>불일치 처리</strong>:</p>
<ul>
<li><p>KMP알고리즘이 실패함수를 사용하는 것처럼 보이어무어 함수도 불일치 시 “<strong>점프 테이블”</strong>을 참고해 특정 칸 수를 건너뛴다.</p>
</li>
<li><p>검색하는 텍스트보다 패턴이 훨씬 짧고 여러번 찾을 때 유리하다.</p>
</li>
<li><p>실무적으로 문자열 매칭 알고리즘을 사용할때 보이어 무어 알고리즘이 벤치 마크 표준으로 사용될 정도로 많이 사용되고 있다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p>✅ <strong>예제 분석 (Example Analysis)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733634960987/49c41ed2-350e-4de8-a84c-d9caf462f37b.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>텍스트: A[] = <code>...btiger..</code>, 패턴: P[] = <code>tiger</code></p>
</li>
<li><p><strong>비교 과정</strong>:</p>
<ul>
<li><p>마지막 문자 r부터 비교 → 불일치 (b ≠ r).</p>
</li>
<li><p><strong>점프 테이블</strong>에 따라 5칸 점프 → 다음 비교 시작.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733635053996/a56bf997-a005-4fdc-9ef1-0a2b901b0310.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>텍스트: A[]=<code>...tigertiger...</code> 패턴: P[]=<code>"tiger"</code></p>
</li>
<li><p><strong>비교 과정</strong>:</p>
<ul>
<li><p>i ≠ r에서 불일치 발생.</p>
</li>
<li><p>패턴에서 i가 세 번째 위치에 있으므로 <strong>3칸 점프</strong>.</p>
</li>
<li><p>앞의 2개 문자는 재활용하여 비교 연산 감소.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅점프 테이블 1 (Jump Table in Boyer-Moore Algorithm)</strong></p>
<p><mark>💡</mark><strong><mark>요약 (Summary)</mark></strong></p>
<ol>
<li><p><strong>점프 테이블이란? (What is a Jump Table?)</strong></p>
<ul>
<li><p>패턴의 각 문자에 대해 <strong>매칭 실패 시 몇 칸 점프할지</strong>를 정의한 테이블이다.</p>
</li>
<li><p>패턴의 <strong>오른쪽에서 왼쪽</strong>으로 비교하기 때문에 <strong>점프 테이블도 반대 방향</strong>으로 정의한다.</p>
</li>
</ul>
</li>
<li><p><strong>점프 테이블의 목적 (Purpose of Jump Table)</strong>:</p>
<ul>
<li><p>매칭 실패 시 효율적으로 <strong>불필요한 비교를 건너뛰기</strong> 위해 사용한다.</p>
</li>
<li><p>특정 문자가 패턴에 없는 경우 <strong>패턴 길이만큼 점프</strong>한다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733635153079/6401e2a5-5c70-4a47-acdc-9d937731f1ff.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>"heyhibyez"에서 "bye"를 매칭.</p>
<ol>
<li><p>y ≠ e: 점프 테이블에 따라 1칸 점프.</p>
</li>
<li><p>h ≠ e: 기타 문자 → 3칸 점프.</p>
</li>
<li><p>y ≠ e: 다시 1칸 점프.</p>
</li>
<li><p>마지막 문자 e일치 → 매칭 성공.</p>
</li>
</ol>
</li>
<li><p><strong>문자별 이동 거리 계산</strong>:</p>
<ul>
<li><p>패턴의 각 문자에 대해 <strong>끝에서의 거리</strong>를 기준으로 이동 거리를 설정.</p>
</li>
<li><p>패턴에 없는 문자 → 패턴의 길이만큼 점프.</p>
</li>
<li><p>동일한 문자가 여러 번 나타나면 <strong>가장 작은 이동 거리</strong> 선택.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733635160050/fc8e088d-55a0-41d3-a70e-6d549e3305bb.png" alt class="image--center mx-auto" /></p>
<p>마지막으로 b, y, e 순서로 비교 → 모두 매칭 → 매칭 성공.</p>
<hr />
<p><strong>✅점프 테이블 2 (Jump Table in Boyer-Moore Algorithm)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733636273242/205361c7-7ad3-4604-9347-dac9fbca48fd.png" alt class="image--center mx-auto" /></p>
<p>❤️ 패턴: <code>tiger</code><strong>의 점프 테이블</strong></p>
<ul>
<li><p><strong>오른쪽 끝 문자 기준으로 이동 거리 계산</strong>:</p>
<ul>
<li><p><code>t</code>: 마지막에서 4번째 → 이동 거리 = 4</p>
</li>
<li><p><code>i</code>: 마지막에서 3번째 → 이동 거리 = 3</p>
</li>
<li><p><code>g</code>: 마지막에서 2번째 → 이동 거리 = 2</p>
</li>
<li><p><code>e</code>: 마지막에서 1번째 → 이동 거리 = 1</p>
</li>
<li><p><code>r</code>: 마지막에서 0번째 → 이동 거리 = 0</p>
</li>
<li><p><strong>기타 문자</strong>: 패턴 길이(5)만큼 점프</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-rational">❤️패턴: <code>rational</code><strong>의 점프 테이블</strong></h4>
<ul>
<li><p><strong>같은 문자가 여러 번 나타나는 경우</strong>:</p>
<ul>
<li><p>가장 오른쪽의 위치를 기준으로 이동 거리를 계산하지만, <strong>가장 작은 이동 거리</strong>를 선택.</p>
</li>
<li><p>예: <code>a</code>는 1번째와 6번째에 나타남 → 이동 거리 = 1 (6번째 기준).</p>
</li>
</ul>
</li>
<li><p><strong>오른쪽 끝 문자 기준으로 이동 거리 계산</strong>:</p>
<ul>
<li><p><code>r</code>: 마지막에서 7번째 → 이동 거리 = 7</p>
</li>
<li><p><code>a</code>: 마지막에서 6번째 → 이동 거리 = 1 (작은 값 선택)</p>
</li>
<li><p><code>t</code>: 마지막에서 5번째 → 이동 거리 = 5</p>
</li>
<li><p><code>i</code>: 마지막에서 4번째 → 이동 거리 = 4</p>
</li>
<li><p><code>o</code>: 마지막에서 3번째 → 이동 거리 = 3</p>
</li>
<li><p><code>n</code>: 마지막에서 2번째 → 이동 거리 = 2</p>
</li>
<li><p><code>l</code>: 마지막에서 1번째 → 이동 거리 = 0</p>
</li>
<li><p><strong>기타 문자</strong>: 패턴 길이(8)만큼 점프.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅원시적 매칭 vs. 개선된 매칭 의사코드 비교 (Naive Matching vs. Improved Matching Pseudocode)</strong></p>
<p>원시적 매칭 의사코드와 비교하면 크게 다른점은 없다. 하나 다른 점은 <code>computeJump(P, jump)</code> 점프하는 테이블을 준비하는 것과 실제 점프시 점프테이블을 이용해 점프한다는 점이다. <code>i&lt;- i + jump[A[i+m-1]]</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733636620021/4516e406-3ebc-4cca-9363-cfa04e05905c.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>공통점 (Similarities)</strong>:</p>
<ul>
<li><p>텍스트와 패턴을 비교하며 매칭 여부 확인.</p>
</li>
<li><p>while 루프를 통해 텍스트를 순차적으로 탐색.</p>
</li>
</ul>
</li>
<li><p><strong>차이점 (Differences)</strong>:</p>
<ul>
<li><p><strong>원시적 매칭 (Naive Matching)</strong>:</p>
<ul>
<li>매칭 실패 시 무조건 한 칸씩 이동.</li>
</ul>
</li>
<li><p><strong>개선된 매칭 (Improved Matching)</strong>:</p>
<ul>
<li><p><strong>점프 테이블</strong>을 사용하여 불일치 시 여러 칸 점프.</p>
</li>
<li><p>점프 테이블은 <code>computeJump(P,jump)</code>를 통해 미리 생성.</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅보이어-무어-호스풀 알고리즘 (Boyer-Moore-Horspool Algorithm)</strong></p>
<p>개선된 매칭 알고리즘의 확장된 형태를 보이어-무어-호스풀 알고리즘이라고 한다. 개선된 매칭은 단순한 점프 방식을 따르고, 보이어-무어-호스풀은 점프 전략이 더 정교하다. 따라서 두 알고리즘은 유사하지만, <strong>비교 방식과 점프 전략에서 차이가 있다.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733637573399/430df527-d32a-4256-b82a-0c053f00a0b2.png" alt class="image--center mx-auto" /></p>
<ul>
<li>computejum(P.jump) 점프 테이블을 만들고 그 자리에서 매칭이 발견되지 않았을때는 점프 테이블에 저장된 위치만큼 점프하며 문자열매칭을 수행하게 된다.</li>
</ul>
<hr />
<h4 id="heading-kiriniug7iiy7zaj7iuc6rceiou2hoyensoq"><strong>✅ 수행시간 분석</strong></h4>
<ol>
<li><p><strong>최악의 경우</strong>: Θ(mn)</p>
<ul>
<li><p>텍스트 전체가 동일한 문자로 구성된 경우, 한 칸씩만 점프 → 원시적 매칭과 동일.</p>
</li>
<li><p>이 경우가 실제적인 상황은 <strong>거의 없고</strong> 실무적으로 보았을땐 매우 빠르게 동작한다.</p>
</li>
</ul>
</li>
<li><p><strong>일반적인 경우</strong>:</p>
<ul>
<li><strong>실제 텍스트와 패턴이 다양한 경우</strong>에는 불필요한 비교를 줄이므로, <strong>평균적으로 Θ(n)보다 빠름</strong>.</li>
</ul>
</li>
<li><p><strong>최선의 경우</strong>: Θ(n/m)</p>
<ul>
<li><p>긴 텍스트에서 패턴이 나타나지 않는 경우, 패턴 길이만큼 점프.</p>
</li>
<li><p>예: 패턴 길이가 20인 경우, 한 번의 비교 후 20칸 점프 가능.</p>
</li>
</ul>
</li>
</ol>
<hr />
]]></content:encoded></item><item><title><![CDATA[Advanced Study of Greedy Algorithms and Matroids]]></title><description><![CDATA[Contents
1️⃣그리디 알고리즘(탐욕법)의 특징 (Overview of Greedy Algorithms)2️⃣그리디 알고리즘의 예 (Example of Greedy Algorithms)3️⃣그리디 알고리즘의 최적해 조건 (Conditions for Optimal Solution with Greedy Algorithm)4️⃣ 그리디 알고리즘 문제 (Example of Greedy Algorithms)5️⃣매트로이드 (Matroid)6️⃣매트...]]></description><link>https://heesu.tech/advanced-study-of-greedy-algorithms-and-matroids</link><guid isPermaLink="true">https://heesu.tech/advanced-study-of-greedy-algorithms-and-matroids</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Sat, 07 Dec 2024 14:45:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733582706524/9778b998-0018-46a9-ac89-e93705d3293c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Contents</strong></p>
<p><strong>1️⃣</strong>그리디 알고리즘(탐욕법)의 특징 (Overview of Greedy Algorithms)<br /><strong>2️⃣</strong>그리디 알고리즘의 예 (Example of Greedy Algorithms)<br /><strong>3️⃣</strong>그리디 알고리즘의 최적해 조건 (Conditions for Optimal Solution with Greedy Algorithm)<br /><strong>4️⃣</strong> 그리디 알고리즘 문제 (Example of Greedy Algorithms)<br />5️⃣매트로이드 (Matroid)<br />6️⃣매트로이드의 확장(Matroid Expansion)<br />7️⃣ 문제 공간 탐색 (Problem Space Exploration)</p>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733489722182/e0c588b4-4333-431c-bcd8-000765ad315b.png" alt class="image--center mx-auto" /></p>
<p>오늘은 그리디 알고리즘에 대해서 배워볼 예정이다. 그리디 알고리즘의 특징에 대해서 먼저 알아본다. 그리디는 “탐욕”이라는 뜻으로 빠른시간에 높은 성능을 보이도록 하기 위해 이것저것 따지지 않고 그대로 현재 시점에서 가장 좋은 옵션을 찾아 나가는 방식이다. "현재 시점" 만 보기떄문에 시야가 좁은 알고리즘이다. 대부분 "최적해"와는 거리가 있다. 하지만 드물게 최적해가 보장되는 경우도 있다. 그 예로는 프림, 크루스칼알고리즘이 있다. 다익스트라 알고리즘도 마찬가지로 최소의 비용을 찾아가는 과정에서 최적해를 보장한다.</p>
<hr />
<h3 id="heading-1-overview-of-greedy-algorithms"><strong>1️⃣</strong>그리디 알고리즘(탐욕법)의 특징(Overview of Greedy Algorithms)</h3>
<p><strong>✅ 그리디 알고리즘의 정의와 특징</strong></p>
<ul>
<li><p><strong>그리디 (Greedy)</strong>는 탐욕이라는 뜻으로, 매 순간 가장 좋아 보이는 옵션을 선택하는 방식이다.</p>
</li>
<li><p>이 알고리즘은 <strong>현재 시점 (current state)</strong>만 고려하며, <strong>시야가 좁다 (limited view)</strong>는 특징을 가진다.</p>
</li>
<li><p>따라서 대부분의 경우 <strong>전체 최적해 (global optimum)</strong>를 놓칠 가능성이 높다.</p>
</li>
<li><p>하지만 특정 상황에서는 최적해를 보장할 수 있는데, 그리디 알고리즘의 <strong>빠른 성능 (high performance)</strong>이 강점으로 작용한다.</p>
</li>
<li><p>어떤 특정 상황인지는 아래 장단점에서 언급하였다.</p>
</li>
</ul>
<hr />
<p><strong>✅작동 방식:</strong> 그리디 알고리즘은 다음과 같이 작동한다.</p>
<ul>
<li><pre><code class="lang-python">  do {
      우선 가장 좋아보이는 선택을 한다.
  } until (해 구성 완료)
</code></pre>
</li>
<li><p><strong>현재 시점 (current state)</strong>에서 반복적으로 가장 좋은 선택을 수행한다.</p>
</li>
<li><p>모든 선택이 완료되면 알고리즘이 종료된다.</p>
</li>
</ul>
<hr />
<p><strong>✅장점과 단점</strong></p>
<ul>
<li><p><strong>장점 (Advantages)</strong>:</p>
<ul>
<li><p>빠른 결과를 도출할 수 있다.</p>
</li>
<li><p>특정 문제(예: 프림 알고리즘 (Prim Algorithm), 크루스칼 알고리즘 (Kruskal Algorithm), 다익스트라 알고리즘 (Dijkstra Algorithm))에서 최적해를 보장 (guarantee optimal solution)할 수 있다. 성능도 빠르고 최적해를 찾을 수 있기 때문에 위 알고리즘들이 좋은 알고리즘으로 평가되는 이유이다.</p>
</li>
</ul>
</li>
<li><p><strong>단점 (Disadvantages)</strong>:</p>
<ul>
<li><p><strong>그래프로 표현시, 전체적인 시야 (global perspective)</strong>를 보지 못해서 중간의 작은 <strong>봉우리 (local peak)</strong>나 <strong>골 (valley)</strong>에서 이를 최적해로 인식하기 때문에 전체 최적해를 놓치게 된다.</p>
</li>
<li><p>예를 들어, 그래프에서 전체 최적해 대신 국소적인 최적해를 선택할 위험이 있다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅ 실제 예시</strong></p>
<ul>
<li><p><strong>프림 알고리즘 (Prim Algorithm)</strong>과 <strong>크루스칼 알고리즘 (Kruskal Algorithm)</strong>: 최소 비용 신장 트리(MST, Minimum Spanning Tree) 를 찾는 과정에서 그리디 방식을 사용하며 최적해를 보장한다.</p>
</li>
<li><p><strong>다익스트라 알고리즘 (Dijkstra Algorithm)</strong>: 그래프에서 최소 비용 경로(minimum cost path)를 찾는 문제에서 사용되며, 최적해를 제공한다.</p>
</li>
</ul>
<hr />
<p><strong>✅그리디 알고리즘의 국소적 최적화 (Greedy Algorithm - Local Optimization)</strong></p>
<p><strong><mark>💡정리: </mark> 그리디 알고리즘</strong>은 매 단계에서 가장 좋아 보이는 선택을 하지만, 국소적 최소값을 선택하여 전체 최적 해(전체 최소값)를 보장하지 못할 수 있다. 단, 문제가 전역 최적 해와 국소 최적 해가 동일한 구조(예: 포물선 형태)라면 효과적다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733490118962/5d3db0a2-3ed9-497e-b66d-c811881b63eb.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>그래프는 여러 봉우리(peak)와 골짜기(valley)를 가진 곡선을 보여주며, 이는 가능한 해(solution)를 나타낸다.</p>
<ul>
<li><p><strong>전체 최대값 (Global Maximum)</strong>: 그래프에서 가장 높은 지점.</p>
</li>
<li><p><strong>국소 최대값 (Local Maximum)</strong>: 주변 지점들보다 높지만 전체에서 가장 높지는 않은 봉우리.</p>
</li>
<li><p><strong>전체 최소값 (Global Minimum)</strong>: 그래프에서 가장 낮은 지점.</p>
</li>
<li><p><strong>국소 최소값 (Local Minimum)</strong>: 주변 지점들보다 낮지만 전체에서 가장 낮지는 않은 골짜기. <strong>그리디 알고리즘</strong>은 이 단계에서 최적이라고 판단되는 선택을 하기 때문에 전체 최적해를 놓치는 경우가 많다.</p>
</li>
</ul>
</li>
<li><p>그래프의 맨 아래에 있는 <strong>전체 최소값 (global minimum)</strong>가 전체 최적해이다.</p>
</li>
<li><p><code>전체 최적해(Global Optimal Solution)</code>란, 문제의 모든 가능한 해 중에서 가장 <strong>최적</strong>(최대 또는 최소)의 값을 가지는 해를 의미한다.</p>
</li>
<li><p><strong>최적해 (optimal solution)</strong>를 보장하려면 전체 해 공간이 단순해야 하며, <strong>포물선 형태 (parabolic space)</strong>인 경우 그리디 알고리즘이 올바른 최적화를 보장하 게 된다.</p>
</li>
<li><p>혹은 <strong>골 (valley)</strong>이 하나뿐이거나, 해 공간이 <strong>단일 봉우리 (single peak)</strong>로 이루어진 문제는 그리디 알고리즘으로 해결 가능하다.</p>
</li>
</ul>
<hr />
<h4 id="heading-kirwn5khioykkeyalo2vncdtj6zsnbjtirgqkg"><strong><mark>💡 중요한 포인트</mark></strong></h4>
<ol>
<li><p><strong>빠른 성능 (High performance)</strong>: 이것저것 따지지 않고 빠르게 해를 구할 수 있음.</p>
</li>
<li><p><strong>시야 제한 (Limited view)</strong>: 전체 최적해를 놓칠 가능성이 큼.</p>
</li>
<li><p><strong>최적해 보장 조건 (Conditions for optimality):</strong> 해 공간이 단순한 구조일 때 가능.</p>
</li>
</ol>
<hr />
<h3 id="heading-1-steps-in-greedy-algorithm"><strong>그리디 알고리즘 #1: 과정 (Steps in Greedy Algorithm)</strong></h3>
<p>💡핵심은 <strong>현재 순간의 최적 해를 선택</strong>하고, 이를 점진적으로 전체 해로 확장하는 방식이다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733490783013/3b497e59-4172-4419-b007-a73a9611b182.png" alt class="image--center mx-auto" /></p>
<p><strong>1. 해 선택 (Select Solution)</strong></p>
<ul>
<li><p>현재 시점에서 가장 최선이라고 판단되는 해를 선택한다.</p>
</li>
<li><p>이는 위에서 언급된 <strong>국소 최적화(Local Optimization)</strong> 접근 방식으로, 문제 해결의 첫 번째 단계이다.</p>
</li>
<li><p>예를 들어, 다익스트라 알고리즘에서는 출발점에서 각 지점까지의 최소 비용을 계산하며 가장 작은 비용을 가진 노드를 선택하게 되는데 현재 시점에서 가장 최선이라고 판단하여 선택하게 되었다.</p>
</li>
</ul>
<hr />
<p><strong>2. 적절성 검사 (Feasibility Check)</strong></p>
<ul>
<li><p>선택한 해가 <strong>전체 문제의 제약 조건(constrains)에 맞는지</strong>를 검사하게 된다.</p>
</li>
<li><p>제약 조건을 통과하면 다음 단계로 진행하는데, 만약 제약 조건에 맞지 않으면 선택한 해를 제외하거나 다시 수정해야 한다.</p>
</li>
</ul>
<hr />
<p><strong>3. 해 검사 (Solution Validation)</strong></p>
<ul>
<li><p>현재까지 선택한 해가 전체 해의 일부가 되는지 확인한다.</p>
</li>
<li><p>조건에 맞으면 해를 계속 추가하여 다음 단계로 진행한다.</p>
</li>
<li><p>만약 맞지 않다면 (조건 불충족) 다시 이전 단계로 돌아가 반복하게 된다.</p>
</li>
<li><p>예:</p>
<ul>
<li><p><strong>다익스트라 알고리즘 (Dijkstra Algorithm):</strong> 출발점에서 특정 지점까지의 비용을 계산하며, 새로운 노드를 선택하고 비용을 업데이트하는 경우</p>
</li>
<li><p><strong>크루스칼 알고리즘 (Kruskal Algorithm):</strong> 최소 비용 간선을 선택하며, 사이클을 생성하지 않는지 확인 후 간선을 추가한다.</p>
</li>
<li><p><strong>프림 알고리즘 (Prim Algorithm):</strong> 현재까지 선택된 정점 집합에서 최소 비용으로 연결되는 간선을 추가한다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>4. 종료 (Termination)</strong></p>
<ul>
<li><p>모든 정점이나 노드를 포함하면 알고리즘이 종료된다. 그 전까지는 새로운 해를 선택하고 적절성을 검사하며 최종 해를 찾는다.</p>
</li>
<li><p><strong>최종 해 (final solution)</strong>를 출력한다.</p>
</li>
<li><p>예:</p>
<ul>
<li><p><strong>다익스트라 알고리즘:</strong> 모든 노드에 대한 최소 비용이 계산되면 종료된다.</p>
</li>
<li><p><strong>크루스칼/프림 알고리즘:</strong> 모든 정점이 연결된 <strong>최소 신장 트리 (minimum spanning tree)</strong>가 완성되면 종료된다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-key-points"><strong>핵심 포인트 (Key Points)</strong></h4>
<ol>
<li><p><strong>해 선택 (Solution Selection):</strong> 현재 시점에서 가장 좋은 옵션을 선택한다.</p>
</li>
<li><p><strong>적절성 검사 (Feasibility):</strong> 제약 조건에 맞는지 확인한다.</p>
</li>
<li><p><strong>해 추가 (Solution Expansion):</strong> 조건을 만족하면 해를 계속 확장한다.</p>
</li>
<li><p><strong>종료 (Termination):</strong> 모든 요소가 포함되면 전체 해를 출력하고 종료하게 된다.</p>
</li>
</ol>
<hr />
<h3 id="heading-typical-structure-of-greedy-algorithm">✅ 그리디 알고리즘의 전형적인 구조 의사코드 (Typical Structure of Greedy Algorithm)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733491084339/d9d7107f-1810-4e72-9253-933abe0342bb.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>초기화 (Initialization):</strong></p>
<ul>
<li><p><code>S ← ∅</code> 해집합 S를 공집합으로 설정한다. C는 선택 가능한 원소들의 집합이.</p>
</li>
<li><p>S는 선택된 원소들의 집합으로, 초기에는 비어 있게된다(공집합 상태)</p>
</li>
</ul>
</li>
<li><p><strong>반복 조건(while statement condition)</strong>: 알고리즘은 다음 두 조건을 만족할 때까지 반복하게 된다.</p>
<ul>
<li><p><code>C ≠ ∅</code> (원소를 선택할 수 있는 집합이 비어 있지 않음).</p>
</li>
<li><p><code>S</code>가 아직 온전한 해(완전한 솔루션)가 아님.</p>
</li>
</ul>
</li>
<li><p><strong>현재 최선의 원소 선택 (Element Selection):</strong></p>
<ul>
<li><p><code>x ← C에서 원소 하나 선택</code>: C에서 <strong>현재 시점에서 가장 좋은 해 (best solution)</strong>로 판단되는 x를 선택한다.</p>
</li>
<li><p>이 선택은 <strong>현재 시점에서 가장 유리한 선택</strong>을 의미한다.</p>
</li>
<li><p>예: 최소 비용 노드, 최대 이익 간선 등.</p>
</li>
</ul>
</li>
<li><p><strong>원소 제거 (Remove element):</strong> <code>C ← C - {x}</code></p>
<ul>
<li><strong>현재 시점에서 가장 좋은 해 (best solution)</strong>로 판단되는 x를 집합 C에서 제거한다.</li>
</ul>
</li>
<li><p><strong>조건 검사 및 해 집합 추가:</strong> <code>S ← S ∪ {x}</code></p>
<ul>
<li>만약 <code>S</code>에 <code>x</code>를 더해도 문제가 없으면, <code>x</code>를 해 집합 <code>S</code>에 추가한다.</li>
</ul>
</li>
<li><p><strong>검사와 종료 (Validation and Termination):</strong></p>
<ul>
<li><p>S가 온전한 해(전체 문제의 해결)를 구성하면 S를 반환한다.</p>
</li>
<li><p>C에 더 이상 선택할 원소가 없거나 반복문이 끝났음에도 온전한 해를 찾지 못하면 <code>"해 없음!"</code>을 반환하게 된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-key-highlights"><mark>💡중요한 부분 (Key Highlights)</mark></h4>
<ol>
<li><p><strong>현재 시점에서 최선의 선택 (Best Choice at Current State) a.k.a 탐욕적 선택 (Greedy Choice)</strong></p>
<ul>
<li>핵심은 반복문 안에서 x를 선택할 때, <strong>현재 시점에서 가장 최선의 옵션</strong>을 고르는 것</li>
</ul>
</li>
<li><p><strong>집합 이동의 의미 (Transition Between Sets):</strong></p>
<ul>
<li><p>C는 아직 방문하지 않은 원소 집합, S는 이미 선택된 원소 집합으로 간주된다.</p>
</li>
<li><p>선택한 원소는 C에서 S로 옮겨진다.</p>
</li>
</ul>
</li>
<li><p><strong>반복 (Iteration):</strong></p>
<ul>
<li>이 과정을 계속 반복하며 S가 전체 해가 될 때까지 진행한다.</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-2"><strong>2️⃣</strong>그리디 알고리즘의 예</h2>
<p>이제 그리디 알고리즘의 최적해를 보장할때 사용하는 프림 알고리즘의 작동원리에 대해 알아본다.</p>
<h3 id="heading-1-example-of-optimal-solution-in-greedy-algorithm-prim-algorithm">그리디 알고리즘의 예 #1 : 최적해 보장하는 프림 알고리즘 (Example of Optimal Solution in Greedy Algorithm - Prim Algorithm)</h3>
<h4 id="heading-characteristics-of-prim-algorithm"><strong>✅프림 알고리즘의 특징 (Characteristics of Prim Algorithm)</strong></h4>
<ol>
<li><p><strong>탐색 범위 제한 (Limited Search Scope):</strong></p>
<ul>
<li><p>현재 연결된 노드와 인접한 간선만 탐색한다.</p>
</li>
<li><p>그래프가 크거나 복잡해도 효율적으로 작동한다.</p>
</li>
<li><p>이렇게 함으로써 <strong>최적해를 구성하는 선택이 각 단계에서 국소적으로도 최적임</strong>을 보장한다.</p>
</li>
<li><p>즉, 부분적으로 최적이면 점진적으로 전체 최적해를 구성할 수 있다는 뜻이다.</p>
</li>
</ul>
</li>
<li><p><strong>가중치 기준 선택 (Weight-Based Selection):</strong></p>
<ul>
<li>각 단계에서 가장 낮은 가중치를 가진 간선을 선택하여 진행한다.</li>
</ul>
</li>
<li><p><strong>최적해 보장 (Guarantee of Optimal Solution):</strong></p>
<ul>
<li>모든 노드를 연결할 때, 최소 가중치로 연결된 신장 트리를 생성하게 된다.</li>
</ul>
</li>
</ol>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733491566913/422d9fa6-65a9-43e3-8930-00d11873af1a.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>시작 노드:</strong> 0번 노드</p>
</li>
<li><p><strong>연결된 노드와 가중치:</strong> 8번(8), 9번(9), 11번(11)</p>
</li>
<li><p><strong>선택된 노드:</strong> 가장 낮은 가중치를 가진 8번 노드</p>
</li>
<li><p><strong>다음 단계:</strong> 8번 노드가 현재 시점이 되며, 인접 노드의 가중치를 업데이트.</p>
</li>
</ul>
<hr />
<p><strong>1. 초기화 (Initialization):</strong></p>
<ul>
<li><p>시작 노드(예: <strong>0번 노드</strong>)에서 출발한다.</p>
</li>
<li><p>모든 노드의 가중치를 무한대(∞)로 초기화하였다.</p>
</li>
<li><p>시작 노드와 연결된 노드들의 가중치를 확인한다.</p>
</li>
</ul>
<hr />
<p><strong>2. 해 선택 (Selection of Solution):</strong></p>
<ul>
<li><p>시작 노드(0번)와 연결된 <strong>8, 9, 11번 노드</strong>의 가중치를 탐색한다.</p>
</li>
<li><p>가중치: <strong>8번 노드:</strong> 8, <strong>9번 노드:</strong> 9, <strong>11번 노드:</strong> 11</p>
</li>
<li><p>이 중에서 가장 낮은 가중치를 가진 <strong>8번 노드</strong>를 선택한다.</p>
</li>
</ul>
<hr />
<p><strong>3. 반복 (Iteration):</strong></p>
<ul>
<li><p>선택된 <strong>8번 노드</strong>가 <strong>현재 시점 (current state)</strong>이 된다.</p>
</li>
<li><p>8번 노드에서 연결된 다른 노드를 탐색한다.</p>
</li>
<li><p>기존에 <strong>무한대(∞)</strong>로 초기화된 가중치 값을 업데이트하게 된다.</p>
<ul>
<li>8번 노드에서 연결된 노드의 가중치가 <strong>10</strong>으로 변경된다.</li>
</ul>
</li>
</ul>
<hr />
<p><strong>4. 종료 조건 (Termination):</strong></p>
<ul>
<li><p>모든 노드가 선택되어 연결되면 알고리즘이 종료된다.</p>
</li>
<li><p>결과는 <strong>최소 신장 트리 (Minimum Spanning Tree)</strong>가 된다.</p>
</li>
</ul>
<hr />
<h3 id="heading-2-example-of-optimal-solution-in-greedy-algorithm-prim-algorithm">그리디 알고리즘의 예 #2 : 프림 알고리즘 의사코드 (Example of Optimal Solution in Greedy Algorithm - Prim Algorithm)</h3>
<p><strong><mark>💡요약:</mark></strong> 프림 알고리즘은 탐욕적 접근 방식을 기반으로, 그래프에서 최소 신장 트리(MST, Minimum Spanning Tree)를 점진적으로 구성한다. 이 의사코드는 <strong>정점 중심(</strong><br />vertex center) 으로 작동하며, 각 단계에서 비용이 가장 작은 정점을 선택하고, 이를 통해 최적의 MST를 보한다. <strong>ExtractMin</strong> 연산과 비용 갱신이 알고리즘의 핵심이다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733491866046/d87687ae-56cb-408f-84e7-80f7c8da1bd1.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>입력값 정의(Input definition):</strong></p>
<ul>
<li><p><code>G = (V, E)</code>: 정점 집합 V와 간선 집합 E로 이루어진 그래프.</p>
</li>
<li><p><code>r</code>: 시작 정점(알고리즘이 시작되는 지점).</p>
</li>
<li><p><strong>정점(node)</strong>: 위치라는 개념. (node 라고도 부름)</p>
</li>
<li><p><strong>간선(edge)</strong>: 위치 간의 관계. 즉, 노드를 연결하는 선 (link, branch 라고도 부름)</p>
</li>
</ul>
</li>
<li><p><strong>초기화 (Initialization):</strong></p>
<ul>
<li><p><code>S ← ∅</code>: MST에 포함된 정점 집합을 초기화(비어 있음).</p>
</li>
<li><p>각 정점 v의 비용(<code>u.cost</code>)을 무한대로 설정: <code>u.cost←∞</code></p>
</li>
<li><p>시작 정점 r의 비용을 0으로 설정: <code>r.cost←0</code></p>
</li>
</ul>
</li>
<li><p><strong>반복 (Iteration):</strong></p>
<ul>
<li><p>조건: <code>S≠V</code> (모든 정점이 MST에 포함될 때까지 반복).</p>
</li>
<li><p><code>ExtractMin</code><mark>: S에 포함되지 않은 정점 중에서 비용(</mark><code>u.cost</code><mark>)이 가장 작은 정점 u를 선택한다.</mark></p>
</li>
<li><p>선택된 정점 u를 <strong>정점 집합 S</strong>에 추가한다. <code>S←S∪{u}</code></p>
</li>
</ul>
</li>
<li><p><strong>인접 정점 업데이트(Adjacent Vertex Update)</strong></p>
<ul>
<li><p>선택한 정점 u와 연결된 모든 인접 정점 v에 대해:</p>
<ul>
<li><p>v가 아직 S에 포함되지 않았고, 간선 (u,v)의 가중치 <code>wuv​</code>가 <code>v.cost</code>보다 작으면:</p>
<ul>
<li><p><code>v.cost←wuv</code>​: 비용 갱신.</p>
</li>
<li><p><code>v.tree←u</code>: v의 트리에 연결된 정점을 u로 설정.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li>선택된 정점과 인접한 노드의 비용을 업데이트한다.</li>
</ul>
<ul>
<li><p><strong>종료 (Termination):</strong></p>
<ul>
<li><p>모든 정점이 S에 포함되면 종료된다.</p>
</li>
<li><p><strong>최소 신장 트리 (Minimum Spanning Tree)</strong>가 완성된다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong><mark>💡중요포인트</mark></strong></p>
<ol>
<li><p><strong>탐욕적 접근(Greedy Choice Property)</strong>:</p>
<ul>
<li><p>각 단계에서 S에 포함되지 않은 정점 중에서 비용이 가장 작은 정점을 선택(<code>ExtractMin</code>)한다.</p>
</li>
<li><p>이러한 선택은 항상 최적해(MST)의 일부가 되도록 보장되는 역할을 한다.</p>
</li>
</ul>
</li>
<li><p><strong>ExtractMin</strong>:</p>
<ul>
<li><p>우선순위 큐(Heap)를 사용하여, 비용(<code>u.cost</code>)이 가장 작은 정점을 효율적으로 선택한다.</p>
</li>
<li><p>이는 알고리즘의 핵심 연산이며, 프림 알고리즘의 시간 복잡도를 결정짓는 요소이다. O (ElogV)</p>
</li>
</ul>
</li>
<li><p><strong>MST(Minimum spanning tree)의 점진적 확장</strong>:</p>
<ul>
<li>선택한 정점을 기반으로 연결된 간선을 탐색하고, 비용이 더 작은 간선을 선택하여 MST를 점진적으로 확장한다.</li>
</ul>
</li>
</ol>
<hr />
<p>👀✅ 다음으로 이진트리의 최적합 경로에 대해서 알아본다. 그리디 알고리즘으로 최적해를 구할수없는 대표적인 예이다. 이로써 최적해가 보장되는 예 vs 보장되지 않는 예를 비교하며 어떤 경우에 알맞는 알고리즘을 적용할 것인가를 배우는 것이 이번시간의 목표이다.</p>
<hr />
<h3 id="heading-3-optimal-path-in-binary-tree">그리디 알고리즘의 예 #3: <strong>이진트리 - 최적합 경로 (Optimal Path in Binary Tree)</strong></h3>
<h4 id="heading-topic-overview"><strong>💡 주제 개요 (Topic Overview)</strong></h4>
<p>이진트리의 최적합 경로란 루트에서 리프노드까지 방문할때 각 노드의 가중치를 최대화하는 경로를 찾는 것이다. 그리디 알고리즘이 최적해가 되려면 이전에 선택한 결정이 이후에 영향을 주면 안된다. 현재 시점만 생각하기 때문에 그 다음 시점은 고려하지 않기 때문이다. 그런데 만약 현재 시점이 그 다음 결정에 영향을 준다고 하면 현재 시점에서 더 먼 범위를 바라봐야하므로 이 경우엔 그리디 알고리즘 적용이 불가능하게 된다.</p>
<hr />
<h4 id="heading-why-greedy-fails-for-binary-tree-paths"><strong>✅ 그리디 알고리즘이 실패하는 이유 (Why Greedy Fails for Binary Tree Paths)</strong></h4>
<ol>
<li><p><strong>의존성 문제 (Dependency Issue):</strong></p>
<ul>
<li><p>그리디 알고리즘은 <strong>현재 시점에서만 최선의 선택</strong>을 한다.</p>
</li>
<li><p>그러나 이진트리의 최적합 경로에서는 <strong>현재 선택이 이후 선택에 영향을 미치기 때문에</strong>, 전체 경로를 고려해야 최적해를 구할 수 있다.</p>
</li>
</ul>
</li>
<li><p><strong>국소적 최적화와 전체 최적화의 차이 (Local vs Global Optimization):</strong></p>
<ul>
<li><p>그리디 알고리즘은 <strong>국소적 최적화 (local optimization)</strong>를 목표로 한다.</p>
</li>
<li><p>이진트리 문제에서는 <strong>전체 최적화 (global optimization)</strong>가 필요하므로, 그리디 알고리즘은 부적합하다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-vs"><strong>✅ 최적해가 보장되는 예 vs 보장되지 않는 예</strong></h4>
<p><strong>(Guaranteed vs Non-Guaranteed Examples)</strong></p>
<p><strong>💡 최적해 보장되는 경우 (Guaranteed Examples):</strong></p>
<ul>
<li><p><strong>프림 알고리즘 (Prim Algorithm):</strong> 최소 신장 트리를 찾을 때, 간선 가중치만 고려하므로 그리디 방식이 적합하다.</p>
</li>
<li><p><strong>다익스트라 알고리즘 (Dijkstra Algorithm):</strong> 특정 노드에서 모든 다른 노드까지의 최소 비용 경로를 찾는 경우이다.</p>
</li>
<li><p><strong>크루스칼 알고리즘 (Kruskal Algorithm):</strong> 사이클 없이 최소 비용으로 모든 노드를 연결하는 경우이다.</p>
</li>
</ul>
<p><strong>💡 최적해 보장되지 않는 경우 (Non-Guaranteed Examples):</strong></p>
<ul>
<li><p><strong>이진트리의 최적합 경로 (Optimal Path in Binary Tree):</strong> 현재 시점에서 최선의 선택이 이후에 더 나은 선택을 방해할 수 있다.</p>
</li>
<li><p><strong>배낭 문제 (Knapsack Problem, 일반형):</strong> 물건의 이익/무게 비율만 고려하면 전체 최적해를 놓칠 수 있다.</p>
</li>
</ul>
<p>문제를 해결하기 전에, 문제의 구조를 파악해 <strong>독립적인 결정인지, 의존적인 결정인지</strong> 분석하는 것이 중요하다. 분석 후에는 적합한 알고리즘 (그래디 vs 동적 프로그래밍)을 선택한다.</p>
<hr />
<h3 id="heading-4-optimal-path-in-binary-tree-example-of-greedy-failure">그리디 알고리즘의 예 #4 : 그리디 알고리즘이 실패하는 예 (Optimal Path in Binary Tree - Example of Greedy Failure)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733493751285/2957d4da-dfae-44d1-b312-a9966aaa32e7.png" alt class="image--center mx-auto" /></p>
<p><strong>1. 문제 정의 (Problem Definition):</strong></p>
<ul>
<li>트리의 루트 노드에서 리프 노드까지 이동하며 각 경로의 가중치 합이 최대가 되는 경로를 찾는 문제이다.</li>
</ul>
<p><strong>2. 그리디 알고리즘의 작동 방식 (How Greedy Works):</strong></p>
<ul>
<li><p><strong>루트(10)</strong>에서 출발하여, 인접 노드 중 가장 큰 값을 가진 <strong>60</strong>을 선택한다.</p>
</li>
<li><p><strong>60</strong>에서 리프 노드 <strong>2</strong>로 이동하며 경로가 종료된다.</p>
</li>
<li><p>이 과정에서 전체 경로의 가중치 합은 10 + 60 + 2 = 72 이다.</p>
</li>
</ul>
<p><strong>3. 전체 최적경로 (Global Optimal Path):</strong></p>
<ul>
<li>루트(10)에서 <strong>15</strong>를 선택한 뒤, <strong>30 → 45 → 67 → 38 → 33</strong> 경로를 선택하는 경우, 경로의 가중치 합은 10+15+30+45+67+38+33=238로 그리디 경로보다 훨씬 큰 결과가 된다.</li>
</ul>
<hr />
<h4 id="heading-limitations-of-greedy-algorithm"><strong>✅ 그리디 알고리즘의 한계 (Limitations of Greedy Algorithm)</strong></h4>
<ol>
<li><p><strong>현재 시점만 고려 (Focus on Current State Only):</strong></p>
<ul>
<li><p>그리디 알고리즘은 항상 현재 시점에서 가장 큰 값(최선의 선택)을 따른다.</p>
</li>
<li><p>이로 인해 다음 단계에서 발생할 선택의 기회를 놓치게 된다.</p>
</li>
</ul>
</li>
<li><p><strong>전역 시야 부족 (Lack of Global View):</strong></p>
<ul>
<li>전체 트리를 보지 않고 <strong>인접한 두 노드만 비교</strong>하기 때문에 전체 경로의 최적해를 구할 수 없게 된다.</li>
</ul>
</li>
<li><p><strong>의존성 문제 (Decision Dependency):</strong></p>
<ul>
<li><p>루트에서 특정 노드를 선택한 결과가 이후 경로 선택에 제한을 가하게 된다.</p>
</li>
<li><p>예: 루트에서 60을 선택하면, 이후에는 리프 노드 2로 제한되며 다른 경로를 탐색할 수 없다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p>✅<strong>이진 트리 최적합 경로의 적합한 알고리즘</strong></p>
<ul>
<li><p><strong>동적 프로그래밍 (Dynamic Programming)</strong>이 적합하다.</p>
</li>
<li><p>전체 경로를 탐색하며, 각 단계에서의 최적 경로를 저장하고 재활용하기 때문이다.</p>
</li>
</ul>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733493879864/56ed1969-c191-4ce5-b3bf-40ec8e97b0d8.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-5-coin-change-problem-limitations-of-greedy-algorithm-and-optimal-solution">그리디 알고리즘의 예 #5: 동전 바꾸기 문제 (Coin Change Problem - Limitations of Greedy Algorithm and Optimal Solution)</h3>
<p>동전바꾸기 문제는 쉽게 생각하면 거스름돈을 자판기에서 계산해서 출력할때 거스름돈이 600원인데 이 잔돈을 10원짜리로 60개가 나온다면 이용자는 당황할 것 이다. 거스름돈은 최소한의 동전이 되도록 계산해서 배출이 되게 된다. 이러한 상황을 알고리즘으로 구현하면 최적해가 보장되는 상황이 된다.</p>
<hr />
<h4 id="heading-summary"><strong><mark>💡요약 (Summary)</mark></strong></h4>
<ol>
<li><p><strong>동전 바꾸기 문제 정의 (Coin Change Problem):</strong></p>
<ul>
<li>주어진 금액을 가장 적은 수의 동전으로 교환하는 문제이다.</li>
</ul>
</li>
<li><p><strong>그리디 알고리즘의 성공 조건 (When Greedy Works):</strong></p>
<ul>
<li><p>동전 액면이 모두 <strong>아래 액면의 배수</strong>여야 최적해를 보장하게 된다.</p>
</li>
<li><p>예) 500원 = <code>100원 × 5</code>, 100원 = <code>50원 × 2</code></p>
</li>
</ul>
</li>
<li><p><strong>그리디 알고리즘의 한계 (Limitations of Greedy):</strong></p>
<ul>
<li>액면 간 배수 관계가 없을 경우, 선택이 이후 값 계산에 영향을 미쳐 최적해를 보장할 수 없음.</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-kirinixqt7jrpqzrljqg7jwm6rog66as7kay7j20ioyeseqzte2vmouklcdqsr3smraqkg"><strong>✅그리디 알고리즘이 성공하는 경우</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733493984947/914a3a66-10c6-478f-9b07-6dc50c9cd0df.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>예제: 3,256원을 만들기 (Example: Making 3,256 KRW):</strong></p>
<ul>
<li><p>동전 액면: 500원, 100원, 50원, 10원, 5원, 1원.</p>
</li>
<li><p><strong>그리디 알고리즘 동작:</strong></p>
<ul>
<li><p>500원 × 6개 = 3,000원</p>
</li>
<li><p>100원 × 2개 = 200원</p>
</li>
<li><p>50원 × 1개 = 50원</p>
</li>
<li><p>5원 × 1개 = 5원</p>
</li>
<li><p><strong>총 동전 수:</strong> 11개.</p>
</li>
</ul>
</li>
<li><p>액면이 배수 관계를 유지하므로, 그리디 알고리즘이 <strong>최적해를 보장</strong>하게 된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-kirinixqt7jrpqzrljqg7jwm6rog66as7kay7j20ioylpo2mqo2vmouklcdqsr3smraqkg"><strong>✅그리디 알고리즘이 실패하는 경우</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733494131914/e01fddc0-a26b-4e40-8bcb-4cfd8631140e.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>예제 : 1,300원을 만들기 (Example: Making 1,300 KRW):</strong></p>
<ul>
<li><p>동전 액면: 500원, 400원, 100원, 75원, 50원.</p>
</li>
<li><p><strong>그리디 알고리즘 동작:</strong></p>
<ul>
<li><p>500원 × 2개 = 1,000원</p>
</li>
<li><p>100원 × 3개 = 300원</p>
</li>
<li><p><strong>총 동전 수:</strong> 5개.</p>
</li>
<li><p>자판기 거스름돈 계산과 같은 빠른 계산, 단순 구현 상황에서 적합하다.</p>
</li>
</ul>
</li>
<li><p><strong>최적해 동작:</strong></p>
<ul>
<li><p>500원 × 1개 = 500원</p>
</li>
<li><p>400원 × 2개 = 800원</p>
</li>
<li><p><strong>총 동전 수:</strong> 3개.</p>
</li>
<li><p>DP 배열을 이용하여 각 금액에 대해 최소 동전 수를 계산할 때 적합하다.</p>
</li>
</ul>
</li>
<li><p>위의 첨부된 예제에서도 설명하듯이 동전이 아래 액면의 배수가 아닌 경우 최적해를 보장하지 않게 된다. 나머지 값이 어떤 동전을 선택했느냐에 따라 나눌수있는 값이 달라지기 때문에 최적해가 보장되지 않게 되는 것이다. 위의 문제의 경우 그리디알고리즘이라면 500원 2개 + 100원 3개 = 1300원이되어 총 5개의 동전으로 만들게 된다. 최적해의 경우엔 500원 1개 + 400원 2개 = 1300원 이렇게 3개의 동전만으로 값을 완성 시키게 되는데 이는 가장 큰 값인 500원을 제일 많이 사용하지 않았으므로 그리디 알고리즘에서 나올 수 없는 결과이다. 그렇기 때문에 최적해가 보장되지 않게 된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733494325112/b03419b0-1e40-4893-9ea9-149beb2aea13.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-6-conditions-for-guaranteeing-optimal-solution-with-greedy-algorithm">그리디 알고리즘의 예 #6: 최적해가 보장되는 조건 (Conditions for Guaranteeing Optimal Solution with Greedy Algorithm)</h3>
<p>그리디 알고리즘이 <strong>전체 최적해 (global optimal solution)</strong>를 보장하려면 다음 두 가지 조건이 반드시 만족되어야 한다.</p>
<hr />
<p><strong>✅ 탐욕 선택 조건 (Greedy Choice Property)</strong></p>
<ul>
<li><p>현재 시점에서 가장 최선의 선택을 했을 때, 이 선택이 이후의 선택에 영향을 미치지 않아야 한다. 즉 현재 시점에서 내린 선택이 이후의 선택에 독립적이어야 하며, 이는 현재 선택만으로 최적해를 만들 수 있음을 보장한다.</p>
</li>
<li><p><strong>프림 알고리즘 (Prim Algorithm):</strong> 최소 가중치를 가진 간선을 선택해도 이후의 간선 선택 과정에 영향을 미치지 않음.</p>
</li>
<li><p><strong>다익스트라 알고리즘 (Dijkstra Algorithm):</strong> 현재 최소 비용 노드를 선택해도 이후 노드 탐색에 독립적.</p>
</li>
<li><p>반대의 경우로는 <strong>이진트리 최적합 경로:</strong> 현재 선택한 노드가 이후 선택 가능한 경로를 제한하므로, 탐욕 선택 조건이 성립하지 않게 된다.</p>
</li>
</ul>
<hr />
<p><strong>✅ 최적 부분 구조 (Optimal Substructure)</strong></p>
<ul>
<li><p>문제를 부분 문제 (subproblem)로 나누었을 때, 부분 문제의 최적해가 전체 문제의 최적해에 포함되어야 한다.</p>
</li>
<li><p>이는 동적 프로그래밍에서도 중요한 성질로, 문제를 재귀적으로 나누고 각 부분의 최적해를 조합해 전체 최적해를 보장할 수 있게 된다.</p>
</li>
<li><p><strong>동전 거스름돈 문제 (Coin Change Problem):</strong> 3,256원을 만드는 문제를 500원, 100원, 50원 등으로 나누어 각각의 최적해를 구하고 조합한다.</p>
</li>
<li><p><strong>크루스칼 알고리즘 (Kruskal Algorithm):</strong> 최소 신장 트리를 구성할 때, 부분적으로 선택된 간선들의 최적해가 전체 트리의 최적해에 포함한다.</p>
</li>
<li><p>반대의 경우, <strong>1300원 동전 문제:</strong> 부분적으로 500원을 최대한 많이 사용하는 선택이 전체 최적해를 구성하지 않음.</p>
</li>
</ul>
<hr />
<h2 id="heading-4-example-of-greedy-algorithms"><strong>4️⃣</strong> 그리디 알고리즘 문제 (Example of Greedy Algorithms)</h2>
<p><strong>❤️동전 개수 최솟값 구하기 문제 (Coin Change Problem)<br />❤️카드 정렬하기 (Card Sorting Problem)<br />❤️회의실 배정하기 (Meeting Room Allocation Problem)<br />❤️최솟값을 만드는 괄호 배치 찾기(Finding Minimum Value with Proper Parentheses)</strong></p>
<hr />
<h3 id="heading-coin-change-problem-minimizing-coin-count">❤️ 동전 개수 최솟값 구하기 문제 (Coin Change Problem - Minimizing Coin Count)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733496745847/6fcd07a0-08e7-402a-bf94-1a5232508744.png" alt class="image--center mx-auto" /></p>
<p>✅ <strong>문제 정의:</strong></p>
<ul>
<li><p>일반적으로 자판기에서 상품의 거스름 돈을 받을때 동전 개수가 최소가 되도록 하는 알고리즘이다.</p>
</li>
<li><p>동전은 총 N 종류가 있고 각 동전의 개수는 충분히 많다고 가정한다.</p>
</li>
<li><p>동전의 종류는 미리 정해져있다.</p>
</li>
<li><p>주어진 금액 K 를 동전개수가 최소가 되도록 채워야한다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733496922632/c6921e60-2daa-479f-b9a0-5d78189107b2.png" alt class="image--center mx-auto" /></p>
<hr />
<p>✅ <strong>입출력의 예제</strong></p>
<ul>
<li>오름차순으로 동전의 액면이 입력으로 주어졌다 1원 , 5원 , 10원이렇게해서 50000원까지 포함하였다.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733496993969/231d3073-2648-40c8-9551-bb70251a2344.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>예제 1: 목표 금액 4200원을 만들기 위한 최소 동전 개수 = <strong>6개</strong></p>
</li>
<li><p>예제 2: 목표 금액 4790원을 만들기 위한 최소 동전 개수 = <strong>12개</strong></p>
</li>
</ul>
<hr />
<p><strong>✅손으로 풀어보기 (Step-by-Step Example)</strong></p>
<p><strong>목표 금액 4200원(K = 4200)</strong></p>
<ul>
<li>동전 리스트: [1,5,10,50,100,500,1000,5000,10000,50000]</li>
</ul>
<ol>
<li>첫 번째 선택</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733497105325/d8289e95-1417-4a7c-bf32-60753171d17f.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>동전리스트 A를 보면 5000, 10000, 50000은 목표 금액보다 크기 때문에 선택 할수없다. 결국 1000원짜리 부터 선택이 된다.</p>
</li>
<li><p>K=4200, 가장 큰 동전은 1000원</p>
</li>
<li><p>4200 ÷ 1000=4 (몫 = 4개, 나머지 = 200).</p>
</li>
<li><p>동전 개수 += 4.</p>
</li>
</ul>
<hr />
<ol start="2">
<li>두 번째 선택</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733497109982/c2be8a4a-dfeb-414b-aea2-d8ced84e730b.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>200원을 가지고 다시 진행하게 된다. 동전리스트에서 500원은 200원보다 크므로 선택할수없다. 결국 100원짜리를 선택하게 된다.</p>
</li>
<li><p>K(목표금액) = 200, 가장 큰 동전은 100원</p>
</li>
<li><p>200 ÷ 100=2 (몫 = 2개, 나머지 = 0).</p>
</li>
<li><p>동전 개수 += 2.</p>
</li>
</ul>
<hr />
<ol start="3">
<li>세 번째 선택</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733497116060/4cb5b3d3-b71f-420c-b0c7-d288dcafcf73.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>k = 0이 되며 알고리즘이 종료하게 된다. 4+2 의 계산결과 6이 최소한의 동전개수가 되는 것이다.</p>
</li>
<li><p>K = 0, 최소 동전 개수 4 + 2= 6</p>
</li>
</ul>
<hr />
<h3 id="heading-2-pseudocode-coin-change-problem">동전 개수 최솟값 구하기 문제 #2: 의사코드 (Pseudocode - Coin Change Problem)</h3>
<p>주어진 금액 K를 가장 적은 수의 동전으로 만들기 위해 <strong>큰 동전부터 사용</strong>한다.<br />동전 액면값은 <strong>내림차순(Descending Order)으로 정렬되어</strong> 있다고 가정한다.</p>
<h4 id="heading-descending-order"><code>내림차순 (Descending Order)</code> 값이 <strong>큰 것부터 작은 것</strong>으로 정렬되는 순서</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733496013837/65f27166-0cc3-42a4-ad73-57218f8d7c9d.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-2-1"><strong>2. 동전 액면값 저장</strong></h4>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> N만큼 반복:
    A 리스트 저장
</code></pre>
<ul>
<li><p><strong>목적:</strong> N개의 동전 액면값을 입력받아 A리스트에 저장한다.</p>
</li>
<li><p>A는 동전의 액면값(예: 500, 100, 50, 10, 5, 1)을 포함하는 배열이다.</p>
</li>
</ul>
<h4 id="heading-3"><strong>3. 큰 동전부터 사용</strong></h4>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> N만큼 반복 (N - <span class="hljs-number">1</span> → <span class="hljs-number">0</span>으로 역순으로 반복): <span class="hljs-comment"># N: 사용할 동전의 종류(개수).</span>
    <span class="hljs-keyword">if</span> 현재 K보다 동전 가치가 작거나 같으면:
        동전 수 += 목표 금액 // 현재 동전 가치
        목표 금액 = 목표 금액 % 현재 동전 가치
</code></pre>
<ul>
<li><p><strong>탐색 순서:</strong> A(동전 데이터 리스트)를 큰 값부터 작은 값으로 탐색한다.</p>
</li>
<li><p><strong>조건 확인:</strong> 현재 동전 A[i]의 가치가 K(목표 금액)보다 작거나 같으면, 해당 동전을 사용할 수 있게 된다.</p>
</li>
<li><p><strong>동작:</strong></p>
<ul>
<li><p><strong>동전 사용 개수 계산:</strong> K ÷ A[i] (목표 금액에서 해당 동전을 최대한 사용 가능한 개수).</p>
</li>
<li><p><strong>남은 금액 계산:</strong> K % A[i] (현재 동전을 사용하고 남은 금액).</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-4"><strong>4. 결과 출력</strong></h4>
<ul>
<li>반복이 종료되면, 사용한 <strong>동전의 총 개수</strong>를 출력한다.</li>
</ul>
<hr />
<h3 id="heading-3-coin-change-problem-python-code">동전 개수 최솟값 구하기 문제 #3: 파이썬 코드 (Coin Change Problem - Python Code)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733496021953/05cf6c98-7761-47d0-b998-41ceb92e3cfa.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1"><strong>1. 입력 처리</strong></h4>
<pre><code class="lang-python">N, K = map(int, input().split())  <span class="hljs-comment"># 동전 개수 N, 목표 금액 K 입력</span>
A = [<span class="hljs-number">0</span>] * N                      <span class="hljs-comment"># 동전 리스트 초기화 (N개의 0으로 시작)</span>
</code></pre>
<ul>
<li><p><strong>입력값:</strong> N: 사용할 동전의 종류 수, K: 만들고자 하는 목표 금액.</p>
</li>
<li><p><strong>A:</strong> 동전의 액면값을 저장할 리스트로, 초기값은 0으로 설정한다.</p>
</li>
</ul>
<hr />
<h4 id="heading-2-2"><strong>2. 동전 액면값 저장</strong></h4>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(N): <span class="hljs-comment"># N개의 동전 액면값을 입력받아 리스트 A에 저장</span>
    A[i] = int(input())
</code></pre>
<ul>
<li><p>N개의 동전 액면값을 입력받아 리스트 A에 저장한다.</p>
</li>
<li><p>입력된 액면값은 <strong>오름차순 정렬된 상태</strong>로 저장된다.</p>
</li>
<li><p><code>오름차순 (Ascending Order)</code> 값이 <strong>작은 것부터 큰 것</strong>으로 정렬되는 순서를 말함</p>
</li>
</ul>
<hr />
<h4 id="heading-3-1"><strong>3. 동전 개수 계산 (큰 동전부터 사용)</strong></h4>
<pre><code class="lang-python">count = <span class="hljs-number">0</span>                    <span class="hljs-comment"># 사용된 동전 개수를 기록하는 변수 초기화</span>

<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(N - <span class="hljs-number">1</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">-1</span>):<span class="hljs-comment"># 큰 동전부터 반복 (리스트를 역순 탐색)</span>
    <span class="hljs-keyword">if</span> A[i] &lt;= K:             <span class="hljs-comment"># 현재 동전 가치가 목표 금액보다 작거나 같으면:</span>
        count += K // A[i]    <span class="hljs-comment"># 현재 동전의 최대 개수를 추가</span>
        K = K % A[i]          <span class="hljs-comment"># 남은 금액 계산</span>
</code></pre>
<ul>
<li><p><strong>탐색 순서:</strong> 리스트 A를 <strong>역순</strong>으로 탐색하여 큰 동전부터 처리한다.</p>
</li>
<li><p><strong>조건:</strong> 현재 동전의 액면값 A[i]이 목표 금액 K보다 작거나 같을 경우, 해당 동전을 사용할 수 있다.</p>
</li>
<li><p><strong>동전 개수 계산:</strong></p>
<ul>
<li><p><code>K//A[i]</code>: 현재 목표 금액에서 사용할 수 있는 최대 동전 개수.</p>
</li>
<li><p><code>count</code>: 사용한 동전의 총 개수를 누적한다.</p>
</li>
</ul>
</li>
<li><p><strong>남은 금액 계산:</strong></p>
<ul>
<li>K = K % A[i] : 현재 동전을 사용하고 남은 금액을 업데이트한다.</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-4-1"><strong>4. 결과 출력</strong></h4>
<pre><code class="lang-python">print(count)  <span class="hljs-comment"># 사용된 동전의 총 개수 출력</span>
</code></pre>
<ul>
<li>모든 반복이 끝난 후, 사용된 동전의 <strong>최소 개수</strong>를 출력한다.</li>
</ul>
<hr />
<p><strong><mark>💡 동전 개수의 최솟값 문제와 우선순위 큐 학습의 연계</mark></strong></p>
<p><strong>✅ 우선순위 큐는 그리디 알고리즘에서 자주 사용되는 도구</strong></p>
<p>그리디 알고리즘의 핵심은 <strong>현재 시점에서 가장 최적의 선택</strong>을 반복적으로 수행하는 것이다. 이를 구현하기 위해 가장 적합한 자료구조 중 하나가 <strong>우선순위 큐</strong>이다.</p>
<ul>
<li><p>동전 문제에서는 단순히 "가장 큰 동전부터 탐색"하는 방식으로 해결되지만,</p>
</li>
<li><p><strong>복잡한 문제</strong>에서는 "가장 비용이 낮은 옵션"을 빠르게 찾기 위해 우선순위 큐가 필요하다.</p>
<ul>
<li>예: <strong>Prim 알고리즘</strong>, <strong>Dijkstra 알고리즘</strong> 등.</li>
</ul>
</li>
</ul>
<p>따라서, 동전 문제를 통해 그리디 알고리즘의 개념을 배우고, 우선순위 큐를 활용할 수 있게 학습하는 것이다.</p>
<hr />
<p><strong>✅동전 문제는 간단한 예제, 우선순위 큐는 복잡한 문제로 확장 가능</strong></p>
<ul>
<li><p>동전 문제는 비교적 단순한 그리디 문제로, <strong>우선순위 큐 없이도 효율적으로 해결</strong>된다.</p>
</li>
<li><p>하지만 복잡한 문제(예: 그래프 탐색, 작업 스케줄링 등)로 확장하면, <strong>최적의 선택을 효율적으로 관리</strong>하기 위해 우선순위 큐가 필요하게 된다.</p>
</li>
<li><p>만약 동전의 리스트가 동적으로 변경되거나, 조건이 추가된다면?</p>
</li>
<li><p>모든 후보 중 최적의 선택을 효율적으로 관리해야 한다면?</p>
</li>
</ul>
<hr />
<p><strong>✅우선순위 큐의 학습이 그리디 알고리즘 확장에 도움된다.</strong></p>
<p>우선순위 큐는 <strong>그리디 알고리즘을 구현하거나 확장</strong>하는 데 유용한 도구가 된다. 동전 문제에서 단순히 "최적의 선택"을 반복적으로 수행하는 과정을 확장하여, 다음과 같은 문제로 적용할 수 있기 때문이다.</p>
<ol>
<li><p><strong>Prim 알고리즘 (최소 신장 트리):</strong></p>
<ul>
<li>우선순위 큐를 사용해, 현재 가장 낮은 비용의 간선을 빠르게 선택.</li>
</ul>
</li>
<li><p><strong>Dijkstra 알고리즘 (최단 경로):</strong></p>
<ul>
<li>우선순위 큐를 사용해, 현재 가장 짧은 거리의 노드를 효율적으로 탐색.</li>
</ul>
</li>
<li><p><strong>Huffman 코딩 (압축 알고리즘):</strong></p>
<ul>
<li>우선순위 큐를 사용해, 최소 비용으로 이진 트리를 구성.</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅정리:</strong> 동전 개수의 최솟값 문제는 그리디 알고리즘의 단순하고 직관적인 예제이다. 우선순위 큐는 이 문제를 직접적으로 최적화하지 않더라도:</p>
<ol>
<li><p><strong>현재 시점에서 최적 선택을 관리</strong>하는 도구로서 그리디 알고리즘의 본질과 연결된다.</p>
</li>
<li><p><strong>복잡한 문제로 확장</strong>할 때, 우선순위 큐의 효용성을 이해하도록 돕는다.</p>
</li>
<li><p>학습의 흐름으로, 간단한 문제에서 출발해 알고리즘의 <strong>기본 개념과 도구 활용법</strong>을 배우게 된다.</p>
</li>
</ol>
<hr />
<h3 id="heading-4-priority-queue-coin-change-problem">동전 개수 최솟값 구하기 문제 #4: 우선순위 큐(Priority Queue - Coin Change Problem)</h3>
<p><strong><mark>💡 우선순위 큐란?</mark></strong></p>
<p>우선순위 큐는 <strong>"가장 중요한 데이터"</strong>를 먼저 꺼내도록 설계된 구조를 뜻한다.</p>
<ul>
<li><p><strong>예시</strong>: 만약 은행에서 VIP 고객이 일반 고객보다 먼저 처리되길 원한다면, 우선순위를 VIP &gt; 일반 고객으로 설정할 수 있다.</p>
</li>
<li><p>큐에 넣는 순서와 상관없이, <strong>우선순위가 높은 것부터 꺼내는 것이다.</strong></p>
</li>
</ul>
<hr />
<h3 id="heading-python"><strong>Python에서 우선순위 큐를 만드는 두 가지 방법</strong></h3>
<h4 id="heading-priorityqueue"><strong>✅</strong> <code>PriorityQueue</code> (스레드-안전 큐)</h4>
<p>Python에서 <code>PriorityQueue</code>는 멀티스레드 환경에서도 안전하게 작동하는 우선순위 큐이다.</p>
<ul>
<li><p><strong>사용법 간단 요약</strong>:</p>
<ol>
<li><p><code>PriorityQueue</code>를 우선순위 큐로 생성한다.</p>
</li>
<li><p><code>put(data)</code>: 데이터를 큐에 넣는다.</p>
</li>
<li><p><code>get()</code>: 가장 우선순위가 높은 데이터를 꺼낸다.</p>
</li>
<li><p><code>qsize()</code>: 큐사이즈를 가져온다.</p>
</li>
<li><p><code>empty()</code>: 큐가 비어 있는지 확인한다.</p>
</li>
</ol>
</li>
</ul>
<pre><code class="lang-python">    <span class="hljs-keyword">from</span> queue <span class="hljs-keyword">import</span> PriorityQueue

    <span class="hljs-comment"># 우선순위 큐 생성</span>
    myque = PriorityQueue()

    <span class="hljs-comment"># 데이터 넣기</span>
    myque.put(<span class="hljs-number">5</span>)  <span class="hljs-comment"># 숫자 5 추가</span>
    myque.put(<span class="hljs-number">1</span>)  <span class="hljs-comment"># 숫자 1 추가</span>
    myque.put(<span class="hljs-number">3</span>)  <span class="hljs-comment"># 숫자 3 추가</span>

    <span class="hljs-comment"># 우선순위 높은 데이터 꺼내기</span>
    print(myque.get())  <span class="hljs-comment"># 출력: 1 (가장 작은 숫자가 우선)</span>
    print(myque.get())  <span class="hljs-comment"># 출력: 3</span>
    print(myque.get())  <span class="hljs-comment"># 출력: 5</span>
</code></pre>
<hr />
<h4 id="heading-heapq"><strong>✅</strong><code>heapq</code> (간단한 힙 구조 큐)</h4>
<p><code>heapq</code>는 Python에서 우선순위 큐를 구현하는 또 다른 방법이다. <strong>리스트</strong>를 <strong>최소 힙(Min Heap)</strong> 구조로 바꿔서 데이터를 관리한다.</p>
<ul>
<li><p><strong>사용법 간단 요약</strong>:</p>
<ol>
<li><p>리스트를 생성한다.</p>
</li>
<li><p><code>heappush()</code>: 데이터를 힙구조로 <strong>삽입</strong>하며, 자동으로 정렬한다.</p>
</li>
<li><p><code>heappop()</code>: <strong>가장 작은 값</strong>을 꺼내고 힙을 유지한다.</p>
</li>
<li><p><code>heapify()</code>: 기존 리스트를 힙으로 변환한다.</p>
</li>
</ol>
</li>
</ul>
<pre><code class="lang-python">    <span class="hljs-keyword">import</span> heapq

    <span class="hljs-comment"># 빈 리스트 생성</span>
    mylist = []

    <span class="hljs-comment"># 데이터 넣기</span>
    heapq.heappush(mylist, <span class="hljs-number">1</span>)  <span class="hljs-comment"># 숫자 1 추가</span>

    <span class="hljs-comment"># 우선순위 높은 데이터 꺼내기</span>
    heapq.heappush(mylist, data)  <span class="hljs-comment"># 힙에 데이터 추가</span>
    heapq.heappop(mylist)         <span class="hljs-comment"># 힙에서 가장 작은 데이터 꺼내기</span>
    heapq.heapify(mylist)         <span class="hljs-comment"># 일반 리스트를 힙 구조로 변환</span>
</code></pre>
<p>    보통 빠른 성능을 원할 때는 heapq를 많이 사용한다.</p>
<hr />
<h2 id="heading-card-sorting-problem"><strong>❤️카드 정렬하기(</strong>Card Sorting Problem)</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733500189402/7246efb5-d4cd-4b45-864b-58fc27fa9b2a.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-problem-definition"><strong>✅ 문제 정의 (Problem Definition)</strong></h4>
<ul>
<li><p>정렬된 두 묶음 A와 B를 하나로 합치려면 A+B만큼의 비교가 필요하다.</p>
</li>
<li><p>정렬된 여러 묶음의 숫자 카드가 있을 때, 이들을 두 묶음씩 골라 서로 합쳐 나가는 과정을 반복한다.</p>
<ul>
<li>이 과정에서 합치는 순서에 따라 <strong>비교 횟수</strong>가 달라지게 된다.</li>
</ul>
</li>
<li><p><strong>목표 (Goal)</strong>: 묶음의 <strong>순서를 잘 조정</strong>하여, <strong>최소한의 비교 횟수</strong>를 구하는 것이다.</p>
</li>
</ul>
<hr />
<p><strong>✅입출력의 예</strong></p>
<p>세 가지의 묶음을 두 개씩 합쳐서 전체적으로 하나의 묶음으로 만들때 최소한의 비교횟수를 출력하는 것이 이 문제이다. 이 예제에서는 100이 출력되었다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733500495325/c9b02119-a96e-42cc-b5cc-1a02fe15e1b9.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-input-format"><strong>입력 형식 (Input Format)</strong></h4>
<ol>
<li><p><strong>첫째 줄</strong>: 숫자 카드 묶음의 개수 N (1 ≤ N ≤ 100,000)</p>
</li>
<li><p><strong>다음 N줄</strong>: 각 줄에 카드 묶음의 크기 K가 주어짐 (1 ≤ K ≤ 1,000,000)</p>
</li>
</ol>
<hr />
<p><strong>✅입출력의 예제 문제 분석하기</strong></p>
<h5 id="heading-case-1-10-20"><strong>Case 1 : 10장과 20장을 먼저 합치는 경우</strong></h5>
<ol>
<li><p><strong>첫 번째 합침:</strong> 10 + 20 =30</p>
</li>
<li><p><strong>두 번째 합침:</strong> 합쳐진 묶음 30과 40을 합침. 30 + 40 = 70</p>
</li>
<li><p><strong>총 비교 횟수:</strong> 30 + 70 = 100</p>
</li>
</ol>
<h5 id="heading-case-2-10-40"><strong>Case 2: 10장과 40장을 먼저 합치는 경우</strong></h5>
<ol>
<li><p><strong>첫 번째 합침:</strong> 10 + 40 = 50</p>
</li>
<li><p><strong>두번째 합침:</strong> 합쳐진 묶음 50과 20을 합친다. 50 + 20 = 70</p>
</li>
<li><p><strong>총 비교 횟수:</strong> 50 + 70 = 120</p>
</li>
</ol>
<hr />
<h4 id="heading-kiriniug67ae7isdioqysoqzvcoq"><strong>✅ 분석 결과</strong></h4>
<ol>
<li><p><strong>비교 횟수는 선택 순서에 따라 달라진다.</strong></p>
<ul>
<li><p>Case 1 (10 + 20 먼저 합침): 총 비교 횟수 = 100.</p>
</li>
<li><p>Case 2 (10 + 40 먼저 합침): 총 비교 횟수 = 120.</p>
</li>
</ul>
</li>
<li><p><strong>초기 선택이 중요한 이유:</strong></p>
<ul>
<li><p>처음 선택된 묶음은 이후에 계속 포함되어 추가 비교가 발생한다.</p>
</li>
<li><p>따라서, 처음 선택하는 묶음은 카드 개수가 작은 것이 유리하다.</p>
</li>
<li><p>한 번 합쳐진 묶음은 다음 합칠 때 더 많은 비교에 포함되기 때문이다.</p>
</li>
<li><p>매번 가장 작은 두 묶음을 선택하는 방식이 최적의 선택인데 이는 그리디 알고리즘이다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅손으로 풀어보기 (Step-by-Step)</strong></p>
<ul>
<li><p>현재 카드 묶음 중 <strong>가장 작은 두 묶음</strong>을 선택해 합친다.</p>
</li>
<li><p>합쳐진 묶음을 <strong>다시 카드 묶음 집합에 추가</strong>한다.</p>
</li>
<li><p>위 두 과정을 <strong>카드 묶음이 하나만 남을 때까지 반복</strong>한다.</p>
</li>
<li><p>합쳐진 묶음의 비교 횟수를 모두 더하여 <strong>최소 비교 횟수</strong>를 구한다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733501607728/91f02f56-7f7f-43df-be0b-8a62d8621b10.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>우선순위 큐 작동 방식:</strong></p>
<ul>
<li><p>카드 묶음을 <strong>우선순위 큐</strong>에 삽입한다.</p>
</li>
<li><p>우선순위 큐는 항상 <strong>가장 작은 값</strong>이 먼저 나오도록 정렬 상태를 유지한다.</p>
</li>
</ul>
</li>
<li><p><strong>구체적인 동작 과정:</strong></p>
<ul>
<li><p><strong>초기 상태:</strong> [40,20,10]</p>
<ul>
<li><p>큐에서 가장 작은 두 묶음 10과 20을 꺼냄.</p>
</li>
<li><p><strong>합침:</strong> 10 + 20 = 30</p>
</li>
<li><p>합친 결과 30을 다시 큐에 삽입: [40,30]</p>
</li>
<li><p>순서가 중요한 것은 아니지만 현재 가장 작은 두 묶음을 선택해 합치는 방식이 전체적으로 최적의 결과를 보장하는 그리디 알고리즘의 개념이 구현된 부분이다.</p>
</li>
</ul>
</li>
<li><p><strong>두 번째 반복:</strong> [40,30]</p>
<ul>
<li><p>큐에서 가장 작은 두 묶음 30과 40을 꺼냄.</p>
</li>
<li><p><strong>합침:</strong> 30 + 40 = 70</p>
</li>
<li><p>합친 결과 70을 다시 큐에 삽입: [70]</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>최종 상태:</strong></p>
<ul>
<li><p>큐에 카드 묶음이 하나만 남으면 종료.</p>
</li>
<li><p>사용된 모든 비교 횟수를 합산: 30 + 70 = 100</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-1-pseudocode-in-card-sorting-problem"><strong>카드 정렬하기 #1: 의사코드 (Pseudocode in</strong> Card Sorting Problem)</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733502010621/111053d0-9987-4eae-991b-40a0489b1e88.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>초기화</strong>: <code>N</code>: 카드 묶음의 개수, <code>pq</code>: 우선순위 큐(작은 값이 우선적으로 처리됨).</p>
</li>
<li><p><strong>데이터 입력</strong>: <code>N</code>만큼 반복하며, 각 카드 묶음의 크기를 우선순위 큐에 저장한다.</p>
</li>
<li><p><strong>알고리즘 동작</strong>:</p>
<ul>
<li><p><strong>반복 조건</strong>: 우선순위 큐의 크기가 1이 될 때까지 진행.</p>
</li>
<li><p><strong>동작 과정</strong>:</p>
<ol>
<li><p>우선순위 큐에서 가장 작은 두 개의 카드 묶음을 꺼냄.</p>
</li>
<li><p>두 카드 묶음을 합치는 데 필요한 비교 횟수를 결과 값에 더함.</p>
</li>
<li><p>합친 카드 묶음의 크기를 다시 우선순위 큐에 넣음.</p>
</li>
</ol>
</li>
</ul>
</li>
<li><p><strong>최종 결과</strong>: 큐의 크기가 1이 되면 최소 비교 횟수가 계산 완료.</p>
</li>
</ol>
<p><strong>✅주요 특징</strong></p>
<ul>
<li><p><strong>우선순위 큐</strong>: 각 단계에서 가장 작은 두 값을 쉽게 꺼낼 수 있어 효율적이다.</p>
</li>
<li><p><strong>그리디 전략</strong>: 매번 가장 작은 두 카드 묶음을 합치는 국소 최적 선택을 통해 전체 최적 결과를 도출한다.</p>
</li>
</ul>
<p>이 알고리즘은 <strong>허프만 코딩(Huffman Coding)</strong>과 유사한 방식으로, 주어진 문제에서 최소 비용으로 작업을 수행하기 위해 설계되었다.</p>
<hr />
<h2 id="heading-2-python-in-card-sorting-problem"><strong>카드 정렬하기 #2: 파이썬코드 (Python in</strong> Card Sorting Problem)</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733502089333/aa9e70aa-507b-4cd8-b721-eb7adc16888f.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>우선순위 큐 초기화</strong>:</p>
<ul>
<li><p><code>from queue import PriorityQueue</code>를 사용하여 Python의 우선순위 큐 모듈을 가져온다.</p>
</li>
<li><p><code>pq = PriorityQueue()</code>를 통해 우선순위 큐 객체를 생성한다.</p>
</li>
</ul>
</li>
<li><p><strong>입력 처리</strong>:</p>
<ul>
<li><p><code>N = int(input())</code>: 카드 묶음의 개수를 입력받는다.</p>
</li>
<li><p>반복문 <code>for _ in range(N):</code>:</p>
<ul>
<li>각 카드 묶음의 크기를 입력받아(<code>date = int(input())</code>) 우선순위 큐에 저장한다.(<code>pq.put(date)</code>).</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>초기 변수 설정</strong>:</p>
<ul>
<li><p><code>data1</code>, <code>data2</code>, <code>sum</code> 변수를 0으로 초기화한다.</p>
</li>
<li><p><code>sum</code>은 최종 최소 비교 횟수를 저장하는 변수이다.</p>
</li>
</ul>
</li>
<li><p><strong>그리디 알고리즘 실행</strong>:</p>
<ul>
<li><p><code>while pq.qsize() &gt; 1</code>: 우선순위 큐의 크기가 1보다 클 때까지 반복.</p>
<ul>
<li><p><strong>두 카드 묶음 꺼내기</strong>: <code>data1 = pq.get()</code>, <code>data2 = pq.get()</code>.</p>
</li>
<li><p><strong>두 카드 묶음 합치기</strong>:</p>
<ul>
<li><p><code>temp = data1 + data2</code>: 두 카드 묶음의 크기를 더한다.</p>
</li>
<li><p><code>sum += temp</code>: 비교 횟수를 합산.</p>
</li>
</ul>
</li>
<li><p><strong>합친 묶음을 다시 큐에 삽입</strong>: <code>pq.put(temp)</code>.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>결과 저장</strong>:</p>
<ul>
<li><code>sum</code>에 모든 비교 횟수의 합이 저장된다.</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅ 작동 방식 요약</strong></p>
<ul>
<li><p>작은 두 카드 묶음을 반복적으로 합치며 비교 횟수를 최소화하는 방식이다.</p>
</li>
<li><p>우선순위 큐를 사용해 매번 가장 작은 두 값을 빠르게 선택한다.</p>
</li>
<li><p>이는 <strong>허프만 코딩</strong> 문제의 원리와 동일하며, 최소 비용으로 작업을 수행할 수 있는 대표적인 그리디 알고리즘이다.</p>
</li>
</ul>
<hr />
<h2 id="heading-meeting-room-allocation-problem"><strong>❤️회의실 배정하기 (Meeting Room Allocation Problem)</strong></h2>
<p><strong><mark>💡요약:</mark></strong> 이 알고리즘은 종료 시간이 빠른 순서대로 회의를 선택하여 최대한 많은 회의를 배치하는 <strong>그리디 알고리즘</strong>의 대표적인 사례이다. 이 알고리즘을 이용하면 강의실, 세미나실 대여와 같은 실제 상황에서 효과적으로 활용할 수 있게 된다.</p>
<h4 id="heading-problem-definition-1"><strong>✅ 문제 정의 (Problem Definition)</strong></h4>
<ol>
<li><p><strong>목표 (Goal):</strong> 하나의 회의실에서 겹치지 않게 최대한 많은 회의를 배정하는 스케줄을 작성한다.</p>
</li>
<li><p><strong>입력 조건 (Input Conditions)</strong></p>
<ul>
<li><p>첫 번째 줄에 회의의 개수 n이 주어진다.</p>
</li>
<li><p>두번째 줄부터는 N+1줄까지 각 회의의 시간과 끝 시간이 주어진다.</p>
</li>
<li><p>이후 각 회의의 시작 시간과 종료 시간이 주어진다.</p>
</li>
</ul>
</li>
<li><p><strong>출력 조건 (Output Conditions)</strong></p>
<ul>
<li>겹치지 않고 진행 가능한 최대 회의 수를 출력하게 된다.</li>
</ul>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733534833439/3e5b0af1-037e-45ff-a1a1-8193ed23e351.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-problem-analysis-and-solution-approach"><strong>✅ 문제 분석 및 해결 방법 (Problem Analysis and Solution Approach)</strong></h4>
<ol>
<li><p><strong>종료 시간 기준 정렬 (Sort by End Time)</strong></p>
<ul>
<li><p>회의를 가장 많이 개최하려면 종료 시간이 빠른 회의를 먼저 선택해야 한다.</p>
</li>
<li><p>종료 시간이 같을 경우, 시작 시간을 기준으로 다시 정렬한다.</p>
</li>
</ul>
</li>
<li><p><strong>그리디 알고리즘 전략 (Greedy Algorithm Strategy)</strong></p>
<ul>
<li>종료 시간이 가장 빠른 회의를 먼저 선택하고, 이후로 겹치지 않는 회의만 추가한다.</li>
</ul>
</li>
<li><p><strong>구현 순서 (Implementation Steps)</strong></p>
<ol>
<li><p>종료 시간을 기준으로 회의를 정렬한다.</p>
</li>
<li><p>첫 번째 회의를 선택한다.</p>
</li>
<li><p>이후로 순차적으로 겹치지 않는 회의를 추가한다.</p>
</li>
</ol>
</li>
</ol>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733535086037/4382b1bf-68bd-42b6-8af9-2913d73a0acd.png" alt class="image--center mx-auto" /></p>
<p>입력으로 회의 개수가 먼저 주어진다. 그 다음 줄엔 회의의 시작 시간과 종료 시간이 주어지고, 결과적으로 4개의 회의가 가능함을 예제 출력을 통해 보여주고 있다.</p>
<hr />
<p><strong>✅ 손으로 풀어보기 (Step-By-Step)</strong></p>
<p>위에서 주어진 입력 값으로 0 시에서 13시까지 배정하는 예제이다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733535535869/84fd0c14-d8cd-43af-9d89-71babfb74293.png" alt class="image--center mx-auto" /></p>
<p>각 회의의 시작 시간과 끝시간이 컬러 블록으로 표시되어 정렬되어 있다. 정렬의 기준은 "끝시간"이된다. 그 다음, 순서대로 회의를 하나씩 스케줄화 하여 등록하게 된다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733535227091/a7432b83-d2e0-434e-b428-5a8c1e2056fa.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>가장 처음에 배치되어있던 배열이 우선순위가 먼저이므로 첫 번째 회의 (1, 4) 선택한다</p>
</li>
<li><p>두번째 세번째는 배정 순위가 겹치기 때문에 배정할 수 없다. (3,5) &amp; (0,6)</p>
</li>
<li><p>겹치는 다음 배열은 모두 무시하고 겹치지 않으면서 종료시간이 빠른 회의를 선택한다. (5, 7), (8, 11), (12, 14)</p>
</li>
<li><p>최종적으로 총 4개의 회의가 배치되었다.<br />  (<strong>Select meetings in order: (1, 4), (5, 7), (8, 11), (12, 14).</strong>)</p>
</li>
</ul>
<hr />
<h3 id="heading-pseudocode-in-meeting-room-allocation-problem"><strong>회의실 배정하기: 의사코드 (Pseudocode in Meeting Room Allocation Problem)</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733535883729/289c89ec-5c98-4266-9983-95e0ea39b808.png" alt class="image--center mx-auto" /></p>
<p>✅ <strong>입력 데이터</strong></p>
<ul>
<li><p>N: 회의의 개수 (총 몇 개의 회의가 있는지).</p>
</li>
<li><p>A: 각 회의의 시작 시간과 종료 시간 정보가 저장된 리스트.</p>
</li>
</ul>
<p>✅<strong>정렬 과정</strong> <code>A리스트 정렬 수행</code></p>
<ul>
<li><p>회의의 <strong>종료 시간 기준</strong>으로 회의를 정렬한다.</p>
</li>
<li><p>만약 종료 시간이 같다면 <strong>시작 시간 기준</strong>으로 정렬한다.</p>
<ul>
<li>이유: 종료 시간이 빠른 회의를 우선적으로 배정해야 최대한 많은 회의를 배치할 수 있기 때문이다.</li>
</ul>
</li>
</ul>
<p><strong>✅ 회의 배정</strong></p>
<ul>
<li><p>이전에 선택한 회의의 종료 시간을 기준으로 잡고, 그 이후에 시작할 수 있는 회의를 선택한다.</p>
</li>
<li><p>선택된 회의를 진행 가능하다고 판단하고, 종료 시간을 업데이트한다.</p>
</li>
</ul>
<p><strong>✅결과 계산:</strong> 반복문이 끝날 때까지 배정된 회의의 개수를 출력한다.</p>
<hr />
<h3 id="heading-python-code-in-meeting-room-allocation-problem"><strong>회의실 배정하기: 파이썬 코드 (Python Code in Meeting Room Allocation Problem)</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733535891399/282ff22e-2875-4e32-82b2-febbf9246b0a.png" alt class="image--center mx-auto" /></p>
<p><strong>✅입력 데이터 초기화</strong></p>
<pre><code class="lang-python">N = int(input())  <span class="hljs-comment"># 회의의 개수 (총 N개의 회의)</span>
A = [[<span class="hljs-number">0</span>] * <span class="hljs-number">2</span> <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(N)]  <span class="hljs-comment"># 각 회의의 [종료 시간, 시작 시간]을 저장할 리스트</span>
</code></pre>
<ul>
<li><p><code>N</code>: 총 몇 개의 회의가 있는지 사용자로부터 입력받습니다.</p>
</li>
<li><p><code>A</code>: 각 회의의 정보를 저장할 2차원 리스트이다.</p>
<ul>
<li><code>[종료 시간, 시작 시간]</code> 형식으로 저장한다.</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅ 회의 데이터 입력받기</strong></p>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(N):
    S, E = map(int, input().split())  <span class="hljs-comment"># 각 회의의 시작 시간(S)과 종료 시간(E)을 입력받음</span>
    A[i][<span class="hljs-number">0</span>] = E  <span class="hljs-comment"># 종료 시간을 첫 번째 값으로 저장</span>
    A[i][<span class="hljs-number">1</span>] = S  <span class="hljs-comment"># 시작 시간을 두 번째 값으로 저장</span>
</code></pre>
<ul>
<li><p><strong>입력 형식</strong>:</p>
<ul>
<li><p>각 회의의 시작 시간과 종료 시간이 공백으로 구분되어 주어진다.</p>
</li>
<li><p>예를 들어, <code>1 4</code>는 시작 시간이 <code>1</code>, 종료 시간이 <code>4</code>인 회의를 의미한다.</p>
</li>
</ul>
</li>
<li><p><code>A</code> 리스트:</p>
<ul>
<li><p>종료 시간을 첫 번째, 시작 시간을 두 번째 값으로 저장한다.</p>
</li>
<li><p>종료 시간이 우선 정렬의 기준이 되기 때문이다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅ 회의 정렬</strong></p>
<pre><code class="lang-python">A.sort()
</code></pre>
<ul>
<li><p><strong>정렬 기준</strong>:</p>
<ul>
<li><p>기본적으로 Python의 리스트 정렬은 첫 번째 값을 기준으로 정렬한다.</p>
</li>
<li><p>따라서, 종료 시간을 기준으로 오름차순 정렬된다. (오름차순: <strong>작은 것부터 큰 것의 순)</strong></p>
</li>
<li><p>만약 종료 시간이 같다면, 시작 시간을 기준으로 정렬한다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅ 회의 배정 과정</strong></p>
<pre><code class="lang-python">count = <span class="hljs-number">0</span>  <span class="hljs-comment"># 배정된 회의 개수</span>
end = <span class="hljs-number">-1</span>   <span class="hljs-comment"># 현재 진행 중인 회의의 종료 시간 (-1로 초기화)</span>
</code></pre>
<ul>
<li><p><code>count</code>: 최종적으로 배정된 회의의 개수를 저장하는 변수.</p>
<ul>
<li>초기에 <code>0</code>으로 설정.</li>
</ul>
</li>
<li><p><code>end</code>: 현재 선택된 회의의 종료 시간을 저장한ㄷ,.</p>
<ul>
<li>초기값은 <code>-1</code>로 설정하여 어떤 회의도 배정되지 않았음을 나타낸다.</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅ 반복문을 통해 회의 배정</strong></p>
<pre><code class="lang-python"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(N):
    <span class="hljs-keyword">if</span> A[i][<span class="hljs-number">1</span>] &gt;= end:  <span class="hljs-comment"># 현재 회의의 시작 시간이 이전 회의의 종료 시간 이후라면</span>
        end = A[i][<span class="hljs-number">0</span>]   <span class="hljs-comment"># 현재 회의의 종료 시간으로 업데이트</span>
        count += <span class="hljs-number">1</span>      <span class="hljs-comment"># 배정된 회의 개수 증가</span>
</code></pre>
<ul>
<li><p><strong>조건</strong>: 현재 회의의 시작 시간이 이전 회의의 종료 시간 이후라면, 현재 회의를 배정할 수 있다.</p>
</li>
<li><p><strong>동작</strong>: 현재 회의를 배정한 뒤, 종료 시간을 업데이트한다.</p>
<ul>
<li><code>count</code>를 1 증가시켜 배정된 회의의 개수를 기록한다.</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅ 결과 출력</strong></p>
<pre><code class="lang-python">print(count)
</code></pre>
<ul>
<li>최종적으로 배정된 <strong>최대 회의 개수</strong>를 출력한다.</li>
</ul>
<hr />
<h2 id="heading-finding-minimum-value-with-proper-parentheses">❤️ 최솟값을 만드는 괄호 배치 문제 (Finding Minimum Value with Proper Parentheses)</h2>
<h4 id="heading-problem-definition-2"><strong>✅ 문제 정의 (Problem Definition)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733549575587/3ef36cc9-69c3-4ce7-9d38-1e98631576d4.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>입력 형식:</strong> 0~9 사이의 숫자와 <code>+</code>, <code>-</code> 연산자로 이루어진 수식이 주어지게 된다.</p>
<ul>
<li>예제 입력: <code>100-40+50+74-30+29-45+43+11</code></li>
</ul>
</li>
<li><p><strong>출력 형식:</strong> 수식의 값을 최소로 만드는 결과를 출력해야 한다.</p>
<ul>
<li>예제 출력: <code>-222</code></li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-step-by-step-solution"><strong>✅ 풀이 과정 (Step-by-Step Solution)</strong></h4>
<h5 id="heading-1-1"><strong>1. 문제 분석</strong></h5>
<ul>
<li><p>최솟값을 구하려면:</p>
<ul>
<li><p>덧셈(+) 부분을 먼저 계산해서 최대한 큰 값을 만든다.</p>
</li>
<li><p>이 값을 <code>-</code> 연산과 함께 한꺼번에 빼준다.</p>
</li>
</ul>
</li>
<li><p>예를 들어)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733549632760/ed258d24-795c-46ec-bfca-ee561f49c3f4.png" alt class="image--center mx-auto" /></p>
<ul>
<li>100 − (40+50+74) − (30+29) − (45+43+11)</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733549651572/9ef2ca2a-3ac1-46cd-99d2-e122f466378f.png" alt class="image--center mx-auto" /></p>
<ul>
<li>계산하면 100 − 164 − 59 − 99 = −222</li>
</ul>
<hr />
<h4 id="heading-kirwn5kh7ksr7jqu7ys7j247yq4oioq"><strong><mark>💡중요포인트:</mark></strong></h4>
<ul>
<li><p><strong>핵심 아이디어:</strong> <code>-</code> 뒤의 값을 모두 더한 후 한꺼번에 빼주는 방식이 가장 작은 값을 만든다.</p>
</li>
<li><p><strong>풀이 전략:</strong> 수식을 <code>-</code>로 분리하고, 각 부분의 덧셈을 계산한 뒤 첫 번째 값에서 차감한다.</p>
</li>
<li><p><strong>시간 복잡도:</strong> 효율적으로 O(N)의 시간에 계산 가능하다.</p>
</li>
</ul>
<hr />
<h3 id="heading-pseudocode-in-finding-minimum-value-with-proper-parentheses">최솟값을 만드는 괄호 배치 문제 - 의사코드 (Pseudocode in Finding Minimum Value with Proper Parentheses)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733549809602/6d40161e-6107-476e-83d9-7776e1555a7b.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><code>answer</code> 변수: 정답을 저장하는 변수이고 초기값은 <code>0</code>으로 설정한다.</p>
</li>
<li><p><code>A 리스트:</code> 입력된 수식을 <code>-</code> 기호를 기준으로 나눈다.</p>
<ul>
<li>예: <code>100 - 40+50+74 - 30+29</code> → <code>['100', '40+50+74', '30+29']</code>.</li>
</ul>
</li>
<li><p><code>mySum(string)</code> 함수: 입력받은 문자열에서 <code>+</code> 기호를 기준으로 나누고 split 수행, 각각의 숫자를 더한 값을 반환한다.</p>
<ul>
<li>예: <code>mySum('40+50+74'</code>) → 40 + 50 + 74 = 164</li>
</ul>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733549815297/dabc43b9-c95f-4115-b60e-7466bddf5e1a.png" alt class="image--center mx-auto" /></p>
<ol start="3">
<li><p><code>for</code> 반복문:</p>
<ul>
<li><p><code>A</code> 리스트의 각 요소를 순회하며, 값을 더하거나 뺀다.</p>
</li>
<li><p>첫 번째 요소는 항상 <strong>더하기</strong> 연산.</p>
</li>
<li><p>두 번째 요소부터는 모두 <strong>빼기</strong> 연산.</p>
</li>
</ul>
</li>
<li><p><strong>최종 출력:</strong></p>
<ul>
<li>반복문이 종료되면 <code>answer</code>에는 최솟값이 저장되며, 이를 출력하게 된다.</li>
</ul>
</li>
</ol>
<h4 id="heading-kirwn5kh7j20ioydmoycroy9loutnoyxkoyencdspjhsmpttj6zsnbjtirg6kio"><strong><mark>💡이 의사코드에서 중요포인트:</mark></strong></h4>
<ol>
<li><p><strong>그리디 접근:</strong> 첫 번째 값은 더하고, 나머지 값은 모두 빼주는 방식으로 최소값을 구하는 전형적인 그리디 알고리즘의 형태이다.</p>
</li>
<li><p><strong>시간 복잡도:</strong></p>
<ul>
<li><p>입력된 수식의 길이를 N이라 할 때, <code>split</code>과 <code>mySum</code> 함수 모두 O(N)에 처리된다.</p>
</li>
<li><p>따라서, 전체 알고리즘의 시간 복잡도는 O(N)가 된다.</p>
</li>
</ul>
</li>
<li><p><strong>장점:</strong> 코드가 간결하며, 효율적이다. 수식이 길어져도 빠르게 처리 가능하다.</p>
</li>
</ol>
<hr />
<h3 id="heading-pseudocode-in-finding-minimum-value-with-proper-parentheses-1">최솟값을 만드는 괄호 배치 문제 - 파이썬 코드 (Pseudocode in Finding Minimum Value with Proper Parentheses)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733549825749/5c37e522-d46a-409a-a1eb-b5f2e43eb309.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><code>A</code> 리스트 생성: 입력된 수식을 <code>-</code>로 나눈다.</p>
</li>
<li><p><code>mySum</code> 함수: 이 함수는 <code>+</code> 연산을 처리한다. -로 나뉜 그룹들(문자열) 안의 값을 모두 더해 반환하는 함수이다.</p>
</li>
<li><p><strong>최종 계산:</strong> 첫 번째 그룹은 항상 <strong>더하기 연산</strong>을 수행한다. 나머지 그룹은 모두 <strong>빼기 연산</strong>을 수행한다.</p>
</li>
</ol>
<hr />
<p><strong><mark>💡중요포인트</mark></strong></p>
<ol>
<li><p><strong>코드 흐름:</strong></p>
<ul>
<li><p><code>mySum</code> 함수는 그룹 내부의 덧셈을 처리한다.</p>
</li>
<li><p><code>for</code> 반복문은 그룹 간 연산(더하기/빼기)을 처리한다.</p>
</li>
</ul>
</li>
<li><p><strong>동작 원리:</strong> 첫 번째 그룹은 무조건 더하고, 나머지 그룹은 모두 빼준다.</p>
</li>
<li><p><strong>최소값을 만드는 이유:</strong> 가장 큰 값을 빼기 위해 <code>-</code> 뒤에 있는 모든 값을 더한 후 한꺼번에 빼준다.</p>
</li>
</ol>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733553067360/51149849-8b37-4bb0-917d-dbe9e225bc24.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-5matroid">5️⃣매트로이드(Matroid)</h2>
<p><strong>✅ 매트로이드란?</strong> 그리디 알고리즘으로 최적해(optimal solution)가 보장되는 공간 구조(spatial structure)를 의미한다.</p>
<h4 id="heading-kiriniug66ek7yq466gc7j2065oc7jmaioq3uoumrouulcdslyzqs6drpqzsppjsnzgg6rsa6roekio"><strong>✅ 매트로이드와 그리디 알고리즘의 관계</strong></h4>
<ol>
<li><p><strong>매트로이드의 중요성:</strong> 매트로이드로 정의된 문제는 <strong>항상 그리디 알고리즘으로 최적해를 구할 수 있다.</strong></p>
</li>
<li><p><strong>반례:</strong></p>
<ul>
<li><p>그리디 알고리즘으로 최적해를 구할 수 있다고 해서 <strong>모든 문제</strong>가 매트로이드가 되는 것은 아니다.</p>
</li>
<li><p>예: 다익스트라 알고리즘은 매트로이드 이론과 직접적으로 관련되지 않지만 최적해를 보장한다.</p>
</li>
</ul>
</li>
<li><p><strong>포인트:</strong> 매트로이드 구조가 성립하면 <strong>그리디 알고리즘</strong>의 적용이 쉽고, 최적화 문제를 효율적으로 풀 수 있게 된다.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733553242472/a7c5b210-86b0-44a5-8190-10cd6d1dbd25.png" alt class="image--center mx-auto" /></p>
<p>위의 그림을 예로 들면, 매트로이드는 <strong>그리디 알고리즘으로 최적해가 보장되는 부분집합</strong>이다. 즉, 전체 집합의 일부로, 최적화 문제를 해결하기 위한 성질을 만족한다.  </p>
<p><strong>✅ 실무에서의 활용:</strong> 매트로이드 이론을 이해하면, 그리디 알고리즘을 적용할 수 있는 문제를 더 잘 구분하고 최적의 결과를 도출할 수 있게 된다.</p>
<hr />
<h3 id="heading-matroid-1">매트로이드(Matroid) #1: 상속성과 증강성 = 독립성</h3>
<p><strong>✅ 독립성 (Independence)</strong></p>
<p>매트로이드의 두 가지 성질인 <strong>상속성 (Hereditary Property)</strong>과 <strong>증강성 (Exchange Property)</strong>을 합쳐 <strong>독립성</strong>이라고 부른다. 즉 <strong>부분해들이 독립적으로 최적 조건을 만족</strong>하여, 이들이 합쳐져도 전체 최적해를 이루어야 한다는 조건을 뜻한다.</p>
<p><strong><mark>💡 독립성의 핵심</mark></strong></p>
<ul>
<li>매트로이드 공간에서 모든 부분해는 독립적으로 최적 조건을 만족한. 이를 통해, 매트로이드 공간에서는 그리디 알고리즘을 안전하게 사용할 수 있게된다.</li>
</ul>
<hr />
<h4 id="heading-hereditary-property"><strong>✅ 상속성 (Hereditary Property)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733559015926/1d1bf7cc-3a5a-4846-b510-306c28841c8f.png" alt class="image--center mx-auto" /></p>
<ol>
<li><strong>정의:</strong> 해집합의 모든 부분집합은 여전히 해집합에 속해야 한다.</li>
</ol>
<ul>
<li><p>만약 𝐴 ∈ 𝐼 이고 𝐵 ⊆ 𝐴 라면 𝐵 ∈ 𝐼야 한다.</p>
</li>
<li><p>즉, 해집합에 속한 집합 A의 모든 부분집합 B도 해집합에 속해야 한다는 뜻이다.</p>
<ul>
<li>❓🤔당연한 것 같은데 왜 이 조건이 언급된 것 일까? 앞에서 그리디 알고리즘의 특성에 대해 생각해보자. 전체적인 골이 하나가 있을때 그리디 알고리즘을 적용하기 좋은 상태가 된다. 이에 따라 집합도 정의가 되는데 실제 그래프의 경우는 항상 포물선의 형태가 아닌 여러개의 골짜기가 생기는 경우도있다. 이럴 경우엔 B가 A에 속하지만 해집합이 아니게 되게 된다. 그래서 “상속성”이 당연히 모든 상황에 일어날 것 같지만 항상 당연한 경우는 아니기 때문에 매트로이드가 수행되려면 상속성이 보장되어야한다.</li>
</ul>
</li>
<li><p><code>∈ (원소 포함, "belongs to")</code> <strong>:</strong> 어떤 요소가 특정 집합의 원소임을 나타낸다.</p>
</li>
<li><p><code>⊆ (부분 집합, "subset of")</code> 한 집합의 모든 원소가 다른 집합에 포함될 때, 부분 집합 관계를 나타낸다.</p>
</li>
<li><p><code>해집합 (Solution Set)</code> 문제나 방정식을 만족하는 값들의 집합</p>
</li>
</ul>
<ol start="2">
<li><strong>그리디 알고리즘과의 연관성:</strong></li>
</ol>
<ul>
<li><p>큰 해인 A가 작은 해인 B를 품고 있는 형태는 그리디 알고리즘에서 언급된 "최적 부분 구조(Optimal Substructure)"와 밀접한 연관이 있다.</p>
<ul>
<li>"최적 부분 구조"란, 전체 해의 최적해가 포함하는 모든 부분도 최적해라는 성질을 의미한다.</li>
</ul>
</li>
<li><p><strong><mark>Key Point: </mark> 매트로이드의 성질인 상속성에서 그리디 알고리즘의 최적 부분 구조</strong>를 이해하는 것이 핵심이다.</p>
</li>
</ul>
<hr />
<h4 id="heading-exchange-property"><strong>✅ 증강성 (Exchange Property)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733560041794/c874a7df-ff9e-40a6-8873-9cd2de92bcfa.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>정의:</strong> 작은 집합을 확장해도 여전히 해집합에 속해야 한다.</p>
<ul>
<li><p>두 집합 A,B ∈ I가 주어지고, ∣A∣ &lt; ∣B∣일 때:</p>
<ul>
<li>B∖A에 속하는 원소 x를 A에 추가해도 A ∪ {x} ∈ I 가 되어야 한다.</li>
</ul>
</li>
<li><p>즉, 작은 집합 A를 확장해도 여전히 전체 해집합에 속할 수 있어야 한다.</p>
</li>
<li><p><code>∈ (원소 포함, "belongs to")</code> <strong>:</strong> 어떤 요소가 특정 집합의 원소임을 나타낸다.</p>
</li>
<li><p><code>∣A∣</code>: 집합 A의 원소 개수</p>
</li>
<li><p><code>B ∖ A</code> 집합 B에서 A에 속하지 않는 원소들만 포함하는 집합</p>
</li>
<li><p><code>A ∪ {x}</code> A에 x를 추가한 새로운 집합</p>
</li>
<li><p><code>∪ {x}</code>: 합집합. 집합에 원소 x를 추가.</p>
</li>
</ul>
</li>
<li><p><strong>그리디 알고리즘과의 연관성:</strong></p>
<ul>
<li><p>증강성은 <strong>"탐욕 선택 조건(greedy choice property)"</strong>과 밀접하게 연관된다.</p>
<ul>
<li>"탐욕 선택 조건"이란, 이전에 선택된 해가 이후에 추가된 해에 영향을 주지 않아야 함을 의미한다.</li>
</ul>
</li>
<li><p><strong><mark>Key Point:</mark></strong> 매트로이드의 성질인 증강성(Exchange Property)에서 그리디 알고리즘의 탐욕 선택 조건(Greedy Choice Property)를 이해하는 것이 핵심이다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-matroid-2-graphic-matroid">매트로이드(Matroid) #2: 그래픽 매트로이드 (Graphic Matroid)</h3>
<h4 id="heading-ia"> </h4>
<p><strong>✅ 그래픽 매트로이드란?</strong></p>
<ul>
<li><p>그래픽 매트로이드는 간단하게 <strong>"숲(Forest)"</strong>으로 정의된다.</p>
</li>
<li><p><strong>숲:</strong> 사이클이 없는 간선 집합 또는 여러 개의 트리로 이루어진 집합이다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733570407393/cd10a9be-83ea-4f5d-b1b3-0c2ad5ee05c6.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><code>숲 집합 𝐹</code>: 모든 가능한 사이클이 없는 부분 집합의 집합을 뜻한다.</p>
</li>
<li><p><code>2^E</code> : 그래프E에서 나올 수 있는 모든 부분 집합을 뜻한다.</p>
</li>
<li><p><code>𝐹 ⊆ 2𝐸</code> : 그래프의 모든 간선 중 사이클이 없는 부분 그래프들만 선택된 집합이라는 뜻</p>
</li>
<li><p>F가 매트로이드라는 것은, 숲 집합이 독립성 조건 (independence condition)을 만족한다는 것을 뜻한다.</p>
</li>
</ul>
<h4 id="heading-graphic-matroid"><strong>✅ 숲(Graphic Matroid)의 예제</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733570438765/2461e326-2d9e-4f1f-9673-d4f492637a1e.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>그래프와 숲:</strong></p>
<ul>
<li><p>주어진 그래프 G는 정점과 간선으로 이루어져 있다.</p>
</li>
<li><p>이 그래프에서 간선의 부분집합을 선택하여 숲을 구성하게 된다.</p>
</li>
<li><p>숲은 사이클이 없는 간선 집합으로 이루어진 <strong>부분 그래프</strong>이다.</p>
</li>
<li><p>여러 개의 트리로 구성되므로 이를 "숲(Forest)"이라 부른다.</p>
</li>
</ul>
</li>
<li><p><strong>숲의 집합:</strong></p>
<ul>
<li><p>그래프에서 간선들을 선택하여 사이클이 없는 트리들로 이루어진 집합이 된다.</p>
</li>
<li><p>이 숲의 집합은 매트로이드의 조건을 만족한다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-kiriniug7iiy7j20iounpo2kuouhnoydtoutnoyehoydhcdspp3rqoxtlzjqulaqkg"><strong>✅ 숲이 매트로이드임을 증명하기</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733570838003/c4e0ae6e-9561-4326-bc89-27566a16b119.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>상속성 (Hereditary Property):</strong> 숲의 모든 부분집합도 숲이어야 한다.</p>
<ul>
<li><p>트리는 사이클이 없는 간선 집합이다.</p>
</li>
<li><p>트리의 간선을 일부 선택한 부분집합도 사이클이 없으므로 숲이다.</p>
</li>
<li><p><strong>결론:</strong> 숲의 상속성이 성립하기 때문에 매트로이드이다.</p>
</li>
</ul>
</li>
<li><p><strong>증강성 (Exchange Property):</strong></p>
<ul>
<li><p><strong>정의:</strong> 두 숲 A, B에서 ∣A∣ &lt; ∣B∣ 일 때, B∖A 속하는 간선 e를 A에 추가해도 A∪{e}는 숲이어야 한다.</p>
</li>
<li><p><strong>설명:</strong> 숲 A와 B가 있다고 가정하자. 작은 숲에 간선을 추가해도 여전히 숲이다.</p>
</li>
<li><p><strong>결론:</strong> 숲의 증강성이 성립하기 때문에 매트로이드이다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-6-matroid-expansion">6️⃣매트로이드의 확장(Matroid Expansion)</h2>
<p><strong>매트로이드의 확장과 포화 (Extension and Maximal Set of Matroid)</strong></p>
<p><strong><mark>💡요약:</mark></strong> 확장 (Extension)은 집합 A에 원소 x를 추가해도 독립적이라면, x는 A를 확장한다.포화 (Maximal Set)란 A가 확장되지 않는 상태라면, A는 포화 상태임을 뜻한다.</p>
<hr />
<h4 id="heading-extension"><strong>✅ 매트로이드의 확장 (Extension)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733571560759/6fd4a7c7-e7b5-478c-b338-cc36de6765a8.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>매트로이드 I는 "독립적인 집합"의 모음이다.</p>
</li>
<li><p>집합 A가 이미 독립적이고, x라는 새 원소를 추가해도 여전히 독립적이라면,<br />  이 원소 x는 A를 확장할 수 있다는 뜻이다.</p>
</li>
<li><p><strong>의미:</strong></p>
<ul>
<li><p><strong>그리디 알고리즘</strong>에서 해를 하나씩 확장하여 최적해를 구성하는 과정과 동일합니다.</p>
</li>
<li><p>원소를 추가해가며 전체 해집합으로 확장하는 과정을 나타냅니다.</p>
</li>
</ul>
</li>
<li><p><strong>예제:</strong></p>
<ul>
<li><p>최소 신장 트리 (Minimum Spanning Tree) 문제에서 간선 e를 현재 트리에 추가할 때 사이클이 없으면, 이는 트리를 확장하는 과정에 해당한다.</p>
</li>
<li><p>S={1,2,3}, I={∅,{1},{2},{1,2}}라고 가정하고 A = {1}일 때, 원소 x = 2를 추가하면 A∪{x} = {1,2}이고, {1,2} ∈ I 이므로 x는 A를 확장할 수 있다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-maximal-set"><strong>✅ 매트로이드의 포화 (Maximal Set)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733571742536/91e9970b-f5f4-4d11-be23-090a82c63fc1.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>집합 A에 새로운 원소 x를 추가함으로써 독립성을 잃는다면, A는 더 이상 확장될 수 없다. 즉, 모든 가능한 원소를 추가해 더 이상 확장할 수 없는 상태이다.</p>
</li>
<li><p>이 상태의 A를 <strong>포화 집합</strong>이라고 한다.</p>
</li>
<li><p><strong>의미:</strong></p>
<ul>
<li><p><strong>그리디 알고리즘의 종료 조건</strong>과 유사하다.</p>
</li>
<li><p>최적해를 찾고 모든 해를 포함하면 알고리즘이 종료된다.</p>
</li>
</ul>
</li>
<li><p><strong>예제:</strong></p>
<ul>
<li><p>최소 신장 트리에서 모든 정점을 연결하고 더 이상 추가할 간선이 없으면, 이는 포화 상태를 나타낸다.</p>
</li>
<li><p>위 예제에서S = {1,2,3}, I = {∅,{1},{2},{1,2}} 에서 A = {1,2}일 때, 새로운 원소 x = 3를 추가하면 A∪{x} = {1,2,3}이 되고, 결과적으로 {1,2,3} ∉ I 이므로 AAA는 더 이상 확장되지 않는다. 따라서 A={1,2}는 포화 집합이다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-kiriniug66ek7yq466gc7j2065oc7j2yio2zleyepsdsojxrpqwqkg"><strong>✅ 매트로이드의 확장 정리</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733571944535/0bf0cf8d-3e5b-4d3a-bacb-641a332adfb7.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>매트로이드의 포화 집합 (Maximal Set of a Matroid)</strong></p>
<ul>
<li><p>매트로이드 <code>I⊆2S</code> 의 모든 포화 집합은 항상 <strong>같은 크기</strong>를 가진다는 것을 의미한다.</p>
</li>
<li><p>포화 집합이란, 더 이상 확장될 수 없는 독립 집합이다.</p>
</li>
</ul>
</li>
<li><p><strong>숲 집합의 경우 (Forest Set Example)</strong></p>
<ul>
<li><p>그래프 이론에서 숲 집합은 <strong>사이클이 없는 간선 집합(즉, 트리 또는 트리들의 집합)</strong>이다.</p>
</li>
<li><p>숲 집합 <code>F ⊆ 2E</code> 의 포화 집합은 <strong>트리(Tree)</strong>가 되며, 이는 항상 <code>∣V∣−1</code> 개의 간선을 포함한다. 정점이 5개라면 <code>∣V∣−1</code>로 인해 트리는 4개의 간선을 가지게 된다.</p>
</li>
</ul>
</li>
<li><p><strong>설명:</strong> 최소 신장 트리 (MST) 문제를 예로 들면:</p>
<ul>
<li>서로 다른 방법으로 트리를 구성하더라도, 선택된 간선의 수는 항상 ∣V∣−1로 동일하다. 포화된 집합의 크기는 항상 일정하다.</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-weighted-matroid-amp-greedy-algorithms"><strong>✅</strong> 가중치 매트로이드와 그리디 알고리즘 (Weighted Matroid &amp; Greedy Algorithms)</h4>
<p><strong><mark>💡요약: </mark></strong> 그리디 알고리즘의 최적해를 보장하는 매트로이드 구조는 있을까? 있다. 가중치 매트로이드(Weighted Matroid) 이다.</p>
<hr />
<ol>
<li><p><strong>가중치 매트로이드란?</strong></p>
<ul>
<li><p>매트로이드에서 원집합 S의 원소들이 <strong>가중치(weight)</strong>를 가지는 매트로이드이다.</p>
</li>
<li><p>매트로이드 I ⊆ 2S에서 각 원소에 가중치(양의 값)가 부여된 구조를 말한다.</p>
</li>
<li><p>즉, 원소마다 중요도나 값어치를 나타내는 숫자가 매겨져 있다.</p>
</li>
<li><p>예를들어 그래픽 매트로이드에서 간선의 가중치의 합이 최대가 되는 숲을 찾는 경우 → <strong>최대 신장 트리 (Maximum Spanning Tree)</strong> 문제가 된다.</p>
</li>
</ul>
</li>
<li><p><strong>목표:</strong></p>
<ul>
<li>가중치 매트로이드에서, <strong>가중치의 합</strong>을 최대화하거나 최소화하는 해를 찾는것이 목표이다.</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-kiriniug7lwc64yaioqwgoykkey5mcdtlakg6rws7zwy6riwicjqt7jrpqzrljqg7jwm6rog66as7kayksoq"><strong>✅ 최대 가중치 합 구하기 (그리디 알고리즘)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733572317963/6a110376-d211-4981-83bb-dee7cac6ea7e.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>알고리즘:</strong></p>
<ul>
<li><p>초기 해 집합 A=∅</p>
</li>
<li><p>원소들을 가중치 w기준으로 내림차순 정렬한다.</p>
</li>
<li><p>각 원소 x ∈ S에 대해:</p>
<ul>
<li>A ∪ {x} 이면, A ← A∪{x}</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>설명:</strong></p>
<ul>
<li><p>가중치가 가장 큰 원소부터 하나씩 선택한다.</p>
</li>
<li><p>선택된 원소들이 매트로이드의 독립 조건을 만족해야 한다.</p>
</li>
<li><p>결과적으로 최대 가중치를 가지는 해를 구성하게 된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-7-problem-space-exploration">7️⃣ 문제 공간 탐색 (Problem Space Exploration)</h3>
<p><strong><mark>💡요약:</mark></strong> 매트로이드에서 문제 공간 탐색은 주어진 문제의 가능한 모든 독립 집합(independent sets)을 효율적으로 탐색하여 최적의 해를 찾는 과정이다. 매트로이드의 독립성 조건과 그리디 알고리즘의 구조적 특성은 이러한 탐색을 효율적으로 찾게 한다. 문제 공간 탐색 문제를 해결하는 방법은 크게 두 가지 알고리즘 유형으로 나뉜다.</p>
<h4 id="heading-constructive-algorithm"><strong>✅ 구축형 알고리즘 (Constructive Algorithm)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733578624998/a573993b-0e93-478f-9d81-fc1ed20fe9e4.png" alt class="image--center mx-auto" /></p>
<ul>
<li><h4 id="heading-6ro17ker7zwp7jeq7iscioylnoyeke2vmoyxrcdtlbtrpbwg7zwy64ky7jspioy2loqwgo2vmoupscdstzzsoihtlbtrpbwg6rws7lav7zw064ky6rca64quiouwqeylneydtoulpc4">공집합에서 시작하여 해를 하나씩 추가하며 최적해를 구축해나가는 방식이다.</h4>
</li>
<li><p>기존에 배웠던 알고리즘과 비슷하다. 첫번째 해 선택, 두번째 해 선택, 세번째 해 선택하며 전체적으로 종료 조건을 만족할 때 해집합이 완료되고 종료된다.</p>
</li>
<li><p>"건설자적인 개념"으로 접근하는 방식이다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733578224977/52f7be11-f8bd-48b8-9d27-33e1623819d7.png" alt class="image--center mx-auto" /></p>
<p><strong>✅ 구축형 알고리즘 과정(Process of Constructive Algorithms)</strong></p>
<ul>
<li><p><strong>공집합 → 최적해 원소 추가 → 온전한 해 완성</strong></p>
</li>
<li><p>매 단계마다 탐색 공간이 줄어들고, 최적해를 향해 나아간다. 각 단계에서 최적의 원소를 선택하게 된다.</p>
</li>
</ul>
<p>✅ <strong>구축형 알고리즘의 활용 예제</strong></p>
<ul>
<li><p><strong>최소 신장 트리 (MST):</strong> 프림 알고리즘, 크루스칼 알고리즘</p>
</li>
<li><p><strong>Shortest Path:</strong> 다익스트라 알고리즘</p>
<ul>
<li>트리의 간선을 하나씩 추가하며 최적해를 구축해나간다.</li>
</ul>
</li>
</ul>
<p><strong><mark>💡중요포인트</mark></strong>: 최적해를 구할 때, <strong>그리디 알고리즘</strong>을 활용하여 효율적으로 해를 구성한다.</p>
<hr />
<h4 id="heading-iterative-improvement-algorithm"><strong>✅ 개선형 알고리즘 (Iterative Improvement Algorithm)</strong></h4>
<ul>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733578633730/2b8c08be-9f26-4df1-a8dc-dbc179024861.png" alt class="image--center mx-auto" /></p>
<p>  처음부터 어떤 해에서 시작하는데, 이 해는 조건은 만족하지만 최적해는 아닌 해이다. 이 해를 조금씩 바꿔가며 최적해를 찾아간다.</p>
</li>
<li><p>여행자의 관점으로 해를 조금씩 바꿔가며 최적해로 이동하는 알고리즘이다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733578518882/6f6365b6-a6ba-40a7-8b25-8a51973f12b9.png" alt class="image--center mx-auto" /></p>
<p><strong>✅ 개선형 알고리즘 과정(Process of Improvement Algorithms)</strong></p>
<ul>
<li><p><strong>초기해 → 해를 수정 → 최적해 도달.</strong></p>
</li>
<li><p>초기해는 임의의 값일 수 있으며, 점진적으로 개선하여 최적해에 도달하게 된다.</p>
</li>
</ul>
<p><strong>✅ 개선형 알고리즘 활용 예제</strong></p>
<ul>
<li><p><strong>인공지능 (AI)</strong>에서 개선형 알고리즘이 많이 사용되고 있다.</p>
</li>
<li><p>AI 알고리즘은 최적해 탐색 과정에서 매트로이드 구조를 활용하여 효율성을 높인다.</p>
</li>
</ul>
<p><strong><mark>💡중요포인트: </mark></strong> 개선형 알고리즘은 탐색 과정에서 다양한 해를 경험하며 최적해를 탐색합니다.</p>
<hr />
<h4 id="heading-kiriniug66ek7yq466gc7j2065ocioq1royhsoyzgcdrkzag7jyg7ziv7j2yioyvjoqzooumroymmoydmcdsl7dqtidshleqkg"><strong>✅ 매트로이드 구조와 두 유형의 알고리즘의 연관성</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733578717045/974718d9-e40f-485a-9d89-9d8f1243d9e0.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>구축형 알고리즘:</strong> 매트로이드 구조는 구축형 알고리즘에서 최적해를 보장한다.</p>
<ul>
<li>예: 프림 알고리즘, 크루스칼 알고리즘.</li>
</ul>
</li>
<li><p><strong>개선형 알고리즘:</strong> 매트로이드 구조는 개선형 알고리즘에서도 최적해를 보장한다.</p>
<ul>
<li><p>이는 <strong>탐욕적 접근법</strong>과 독립적 조건을 이용해 효율적으로 최적해를 탐색할 수 있음을 의미한다.</p>
</li>
<li><p>개선형 알고리즘의 유형은 아래에 설명할 예정이다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-problem-space-exploration-1">문제 공간 탐색 (Problem Space Exploration) #1:</h3>
<p><strong>매</strong>트로이드 구조의 개선형 알고리즘 의사코드(Pseudocode of Improvement Algorithms in Matroid Structure)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733578845078/8905f35a-e8f3-4f98-80d7-dd6674ce6f97.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>초기 상태</strong></p>
<ul>
<li><p>I: 매트로이드의 독립 집합.</p>
</li>
<li><p>A: 초기 해 집합 (온전한 해지만 최적해는 아님).</p>
</li>
<li><p>w[]: 각 원소의 가중치 배열.</p>
</li>
</ul>
</li>
<li><p><strong>조건 검사:</strong></p>
<ul>
<li><p><code>w(a) &lt; w(x)</code> 기존 원소 a의 가중치가 새 원소 x의 가중치보다 작을 때.</p>
</li>
<li><p><code>A ∪ {x} - {a} ∈ I</code> 원소 a를 제거하고 x를 추가한 새로운 집합이 매트로이드 조건을 만족할 때</p>
</li>
</ul>
</li>
<li><p><strong>동작:</strong></p>
<ul>
<li><p><code>A ← A U {x} - {a}</code> 조건을 만족하면 a를 제거하고 x를 추가한 새로운 해를 구성한다.</p>
</li>
<li><p>조건을 더 이상 만족하지 않으면 반복 종료한다.</p>
</li>
</ul>
</li>
<li><p><strong>반환:</strong> 최적화된 해 A를 반환한다.  </p>
</li>
</ul>
<p><strong><mark>💡중요 포인트 </mark> 기존 원소 a</strong>를 제거하고, <strong>새로운 원소 x</strong>를 추가한다. 변경된 해가 여전히 매트로이드 조건을 만족하면 이 변경을 유지한다.</p>
<hr />
<h3 id="heading-problem-space-exploration-of-matroid-2-concept-you-should-know-in-improvement-algorithms">문제 공간 탐색 (Problem Space Exploration of Matroid) #2:<strong>개선형 알고리즘에 필요한 개념(Concept you should know in Improvement Algorithms)</strong></h3>
<p><strong><mark>💡중요 포인트:</mark></strong> 인접성과 지역 최적해는 <strong>개선형 알고리즘의 작동 방식과 한계를 이해하는 핵심 개념</strong>이다. 개선형 알고리즘이 "인접성"을 통해 탐색을 진행하지만, "지역 최적해"에 머물 수 있는 한계를 가진다는 점이다. 이를 탈출하거나 전체 최적해로 나아가기 위한 추가 전략이 필요하다.</p>
<hr />
<h4 id="heading-adjacency"><strong>✅ 인접성 (Adjacency)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733580458298/9d53bded-7b82-4e6e-a2fc-853b8cc50090.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>한 원소를 추가하거나 제거하여 다른 해로 이동 가능할 경우, 두 해가 인접 관계에 있다고 한다.</p>
</li>
<li><p>인접성은 개선형 알고리즘에서 <strong>현재 해를 다른 해로 이동하는 기준</strong>이 된다.</p>
</li>
</ul>
<hr />
<h4 id="heading-local-optimum"><strong>✅ 지역 최적해 (Local Optimum, 끌개)</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733580855730/85381459-acd5-4c7a-a620-2a18be544a67.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>개선형 알고리즘에서, 현재 해를 계속 개선해 나가다 보면 더 이상 나아갈 수 없는 지점에 도달하게 되는데, 이 지점이 <strong>지역 최적해(Local Optimum)</strong>이다.</p>
</li>
<li><p>앞서 배운 그리디 알고리즘의 포물선 예제에서 A에서 출발해 인접한 해로 이동시에 더 나은 품질(더 작은 비용)을 가진 해로 계속 이동한다. 더 이상 나아갈 수 없을 때, 이는 지역 최적해가 된다.</p>
</li>
<li><p><strong>지역의 의미:</strong> 다양한 골짜기(최소값)가 존재할 경우,</p>
<ul>
<li><p>현재 위치한 골의 최저점이 지역 최적해가 된다.</p>
</li>
<li><p>지역 최적해는 전체 최적해(global optimum)에 도달하지 못할 수 있다.</p>
</li>
<li><p>전체 최적해와 달리 지역 최적해는 다양한 골(최소값)이 존재할 경우, 하나의 골에 머물게 된다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-problem-space-exploration-of-matroid-3-improvement-algorithms-amp-matroid-space-overview">문제 공간 탐색 (Problem Space Exploration of Matroid) #3: 개선형 알고리즘과 매트로이드 공간 정리 (Improvement Algorithms &amp; Matroid Space Overview)</h3>
<h4 id="heading-kirwn5khio2vteylrcdqsjzrhzaqkg"><strong><mark>💡 핵심 개념</mark></strong></h4>
<ul>
<li><p><strong>매트로이드 공간 (Matroid Space)</strong> 에서 개선형 알고리즘은 <strong>국소 탐색 (Local Search)</strong> 만으로도 <strong>전역 최적해 (Global Optimum)</strong> 를 찾을 수 있다.</p>
</li>
<li><p>이는 매트로이드 공간이 하나의 <strong>봉우리 (Peak)</strong> 또는 <strong>골 (Valley)</strong> 만 가지는 구조이기 때문에 가능한 것이다.</p>
</li>
</ul>
<hr />
<p><strong>✅ 정리 1: 품질 좋은 해의 존재 (Existence of High-Quality Solution)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733582358361/4e2a40ba-bd2a-4050-926f-f7183d575d09.png" alt class="image--center mx-auto" /></p>
<p>매트로이드 공간에서, 어떤 <strong>품질 좋은 해 (High-Quality Solution)</strong> a가 존재한다면,<br />a와 <strong>인접한 해 (Adjacent Solution)</strong> 중에서도 품질 좋은 해가 반드시 존재한다.</p>
<ul>
<li><strong>의미 (Meaning):</strong> 개선형 알고리즘이 해를 변경하면서도 품질을 유지할 수 있는 근거가 된다.</li>
</ul>
<hr />
<p><strong>✅ 정리 2: 국소 최적해와 전역 최적해의 관계 (Relationship Between Local and Global Optima)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733582387494/02b73489-e485-4c3e-bddd-bf674d1a3564.png" alt class="image--center mx-auto" /></p>
<p>특정 해 a 주변에 더 나은 품질의 해가 없다면, <strong>a는 전역 최적해 (Global Optimum)</strong>이다.</p>
<ul>
<li><strong>의미 (Meaning):</strong> 매트로이드 공간에서는 <strong>지역 최적해 (Local Optimum)</strong> 가 곧 전역 최적해가 된다. 이는 매트로이드 공간이 <strong>봉우리 (Peak)</strong> 나 <strong>골 (Valley)</strong> 이 하나만 있는 구조이기 때문이다.</li>
</ul>
<hr />
<p>✅ <strong>정리 3: 전역 최적해의 가중치 합 동일성 (Weight Consistency in Global Optima)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733582465875/5d43d9cb-8779-48dd-8cb4-d68c1e3adccc.png" alt class="image--center mx-auto" /></p>
<p>매트로이드 공간에서, 만약 두 개의 <strong>전역 최적해 (Global Optima)</strong> 가 존재한다면,<br /><strong>두 해의 가중치 합 (Sum of Weights)</strong> 은 항상 동일하다.</p>
<ul>
<li><p><strong>예시 (Example):</strong> 프림 알고리즘과 크루스칼 알고리즘은 서로 다른 방식으로 최소 신장 트리를 찾는다. 두 알고리즘이 구한 트리는 다를 수 있지만, <strong>가중치 합 (Sum of Weights)</strong> 은 동일하다.</p>
</li>
<li><p><strong>의미 (Meaning):</strong> 개선형 알고리즘에서도 최적해의 형태는 다를 수 있지만,<br />  결과적으로 "최적 비용 (Optimal Cost)" 은 동일하게 보장된다는 뜻이다.</p>
</li>
</ul>
<hr />
<p>✅ <strong>정리 4: 동일한 가중치를 가진 해의 연결성 (Connectivity Between Equal-Weight Solutions)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733582528519/7d846fcc-62c0-4420-96a1-b1964788dba8.png" alt class="image--center mx-auto" /></p>
<p>매트로이드 공간에서, 동일한 <strong>가중치 (Weight)</strong> 를 가진 두 해는 <strong>인접 관계 (Adjacency Relationship)</strong> 를 따라 서로 연결될 수 있다.</p>
<ul>
<li><strong>의미 (Meaning):</strong> 개선형 알고리즘은 인접 관계를 통해 해를 이동하며, 동일한 품질의 해 사이에서도 <strong>연결성 (Connectivity)</strong> 을 보장한다.</li>
</ul>
<hr />
<h4 id="heading-kiriniug66ek7yq466gc7j2065oc7j2yio2kueynlsoq"><strong>✅ 매트로이드의 특징</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733582570627/2bb5d15d-8cfb-4c72-a1f7-a847b00ea263.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>봉우리 (Peak)</strong> 또는 <strong>골 (Valley)</strong> 이 하나인 구조:</p>
<ul>
<li><p>매트로이드 공간은 하나의 봉우리 또는 골만 가지는 단순한 구조를 가진다.</p>
</li>
<li><p>이로 인해, 개선형 알고리즘이 지역 최적해에서 멈출 필요 없이 바로 전역 최적해를 찾을 수 있게된다.</p>
</li>
</ul>
</li>
<li><p><strong>그리디 알고리즘의 적용 가능성 (Applicability of Greedy Algorithms):</strong></p>
<ul>
<li>매트로이드 공간은 <strong>탐욕적 선택 조건 (Greedy Choice Property)</strong> 을 만족하므로,<br />  개선형 알고리즘으로 최적해를 보장한다.</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733582565717/0475e046-e8bd-483e-b9fe-efe52ee27628.png" alt class="image--center mx-auto" /></p>
<hr />
]]></content:encoded></item><item><title><![CDATA[From Dijkstra to A: A Deep Dive into Graph Algorithms 2]]></title><description><![CDATA[Contents
1️⃣위상 정렬 (Topological Sorting)2️⃣최단 경로 알고리즘 (Shortest Path Algorithm)3️⃣다익스트라 알고리즘 (Dijkstra's Algorithm)4️⃣벨만-포드 알고리즘 (Bellman-Ford Algorithm)5️⃣모든 쌍 최단 경로 (All-Pairs Shortest Path)6️⃣강연결 요소 구하기 (Finding Strongly Connected Components, SCC)7...]]></description><link>https://heesu.tech/from-dijkstra-to-a-a-deep-dive-into-graph-algorithms-2</link><guid isPermaLink="true">https://heesu.tech/from-dijkstra-to-a-a-deep-dive-into-graph-algorithms-2</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Fri, 06 Dec 2024 07:52:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733471509449/47018639-f4ed-451c-ae98-a981806cb6b9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Contents</strong></p>
<p><strong>1️⃣</strong>위상 정렬 (Topological Sorting)<br /><strong>2️⃣</strong>최단 경로 알고리즘 (Shortest Path Algorithm)<br /><strong>3️⃣</strong>다익스트라 알고리즘 (Dijkstra's Algorithm)<br /><strong>4️⃣</strong>벨만-포드 알고리즘 (Bellman-Ford Algorithm)<br />5️⃣모든 쌍 최단 경로 (All-Pairs Shortest Path)<br />6️⃣강연결 요소 구하기 (Finding Strongly Connected Components, SCC)<br />7️⃣ A* 알고리즘 (A* Search Algorithm)</p>
<hr />
<p><strong>Summary: Graph Algorithms Review (그래프 알고리즘 리뷰)</strong></p>
<h4 id="heading-1-topological-sorting">1. <strong>Topological Sorting (위상 정렬)</strong></h4>
<ul>
<li><p><strong>Description (설명)</strong>: <strong>Orders vertices in a Directed Acyclic Graph (DAG) such that no vertex points back to its predecessors (DAG에서 모든 간선의 방향과 위배되지 않도록 정점을 정렬)</strong>.</p>
</li>
<li><p><strong>Key Points (핵심 포인트)</strong>:</p>
<ul>
<li><p><strong>Achievable using Depth-First Search (DFS) (DFS를 이용하여 구현 가능)</strong>.</p>
</li>
<li><p>간선이 순방향으로만 흐르도록 보장하며 순환 구조가 없음.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-2-shortest-path-algorithms">2. <strong>Shortest Path Algorithms (최단 경로 알고리즘)</strong></h4>
<ul>
<li><p><strong>Description (설명)</strong>: <strong>Calculates the path with the minimum cost between two vertices (두 정점 사이의 경로들 중 간선의 가중치 합이 최소가 되는 경로를 구함)</strong>.</p>
</li>
<li><p><strong>Key Points (핵심 포인트)</strong>:</p>
<ul>
<li><p>DAG에서는 가중치가 양수여야 함.</p>
</li>
<li><p>음의 가중치 합계가 포함된 사이클이 있으면 계산 불가.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-3-cycle-free-shortest-path">3. <strong>Cycle-Free Shortest Path (사이클이 없는 최단 경로)</strong></h4>
<ul>
<li><p><strong>Description (설명)</strong>: <strong>Uses topological sorting to ensure paths are acyclic (DAG에서의 최단 경로는 위상 정렬을 이용하여 수행 가능)</strong>.</p>
</li>
<li><p><strong>Key Points (핵심 포인트)</strong> 각 정점들마다 거리값을 업데이트하여 최소값을 구함.</p>
</li>
</ul>
<hr />
<h4 id="heading-4-dijkstras-algorithm">4. <strong>Dijkstra's Algorithm (다익스트라 알고리즘)</strong></h4>
<ul>
<li><p><strong>Description (설명)</strong>: <strong>Computes shortest paths from a single source vertex, only for non-negative weights (단일 시작점의 최단 경로 알고리즘으로 음의 가중치를 허용하지 않음)</strong>.</p>
</li>
<li><p><strong>Key Points (핵심 포인트)</strong>: 출발 정점과 현재 정점 간의 최단 거리를 저장.</p>
</li>
</ul>
<hr />
<h4 id="heading-5-bellman-ford-algorithm">5. <strong>Bellman-Ford Algorithm (벨만-포드 알고리즘)</strong></h4>
<ul>
<li><p><strong>Description (설명)</strong>: <strong>Handles graphs with negative weights, unlike Dijkstra’s (다익스트라 알고리즘과 달리 음의 가중치를 허용하는 최단 경로 알고리즘)</strong>.</p>
</li>
<li><p><strong>Key Points (핵심 포인트)</strong>: 이중 for문으로 다익스트라 알고리즘에 비해 시간 복잡도가 높음.</p>
</li>
</ul>
<hr />
<h4 id="heading-6-all-pairs-shortest-path">6. <strong>All-Pairs Shortest Path (모든 쌍 최단 경로)</strong></h4>
<ul>
<li><p><strong>Description (설명)</strong>: <strong>Finds shortest paths between all pairs of vertices (그래프의 모든 정점들 간의 상호 최단거리를 구하는 문제)</strong>.</p>
</li>
<li><p><strong>Key Points (핵심 포인트)</strong>: 네비게이션 또는 네트워크 경로 탐색에서 유용.</p>
</li>
</ul>
<hr />
<h4 id="heading-7-floyd-warshall-algorithm">7. <strong>Floyd-Warshall Algorithm (플로이드-워샬 알고리즘)</strong></h4>
<ul>
<li><p><strong>Description (설명)</strong>: <strong>Uses dynamic programming to solve all-pairs shortest path problems (정점 집합을 하나씩 증가시키면서 동적 프로그래밍으로 최단 거리 구함)</strong>.</p>
</li>
<li><p><strong>Key Points (핵심 포인트)</strong>: 음수 가중치도 허용되며, 밀집 그래프에서 효율적임.</p>
</li>
</ul>
<hr />
<h4 id="heading-8-strongly-connected-components">8. <strong>Strongly Connected Components (강연결 요소 구하기 알고리즘)</strong></h4>
<ul>
<li><p><strong>Description (설명)</strong>: <strong>Identifies maximal subgraphs where every pair of vertices is mutually reachable (강하게 연결된 부분 그래프를 DFS를 응용하여 구하는 알고리즘)</strong>.</p>
</li>
<li><p><strong>Key Points (핵심 포인트)</strong>: 그래프와 역방향 그래프를 순차적으로 탐색하여 요소를 분리.</p>
</li>
</ul>
<hr />
<h4 id="heading-9-a-algorithm">9. <em>A Algorithm</em>*</h4>
<ul>
<li><p><strong>Description (설명)</strong>: <strong>Combines path cost and heuristic estimates to find the shortest path efficiently (출발점에서 도착점까지 가는 최단 거리를 추정값을 활용하는 알고리즘)</strong>.</p>
</li>
<li><p><strong>Key Points (핵심 포인트)</strong>:</p>
<ul>
<li><p>평가 함수: <strong>f(n) = g(n) + h(n)</strong></p>
<ul>
<li><p><strong>g(n)</strong>: 출발점에서 현재 노드까지의 실제 비용.</p>
</li>
<li><p><strong>h(n)</strong>: 목표 노드까지의 추정 비용.</p>
</li>
</ul>
</li>
<li><p>탐색과 최적화를 균형 있게 수행.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-1"><strong>1️⃣</strong>위상 정렬</h2>
<p>그래프 고급 알고리즘을 배우기에 앞서 우선 <strong>위상 정렬 (Topological Sorting)</strong>의 개념과 이를 설명하기 위한 <strong>DAG (Directed Acyclic Graph)</strong>에 대해 알아본다. DAG는 <strong>사이클이 없는 유향 그래프 (Directed Acyclic Graph)</strong>로, 방향이 있는 그래프이며 특정 정점 간 순서 관계를 나타낸다. “위상 정렬”은 이 DAG의 모든 정점을 일렬로 배치하는 알고리즘으로, 모든 간선의 방향이 위배되지 않도록 정렬하는 것을 목표로 한다.</p>
<p><code>topological</code>: the study of shapes that can be stretched and moved while points on the shape continue to stay close to each other.</p>
<h4 id="heading-dag-directed-acyclic-graph">✅ <strong>DAG (Directed Acyclic Graph)</strong>란?</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733375758660/0f709878-8a74-4f2d-8a3b-d4ce668b2c86.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>DAG는 방향이 있는 그래프 (Directed Graph)</strong>로, <strong>사이클이 없는 (Acyclic)</strong> 구조를 가진다.</p>
</li>
<li><p>즉, 정점에서 출발하여 간선을 따라가다 보면 다시 원래 정점으로 돌아올 수 없는 그래프이다.</p>
</li>
<li><p>DAG의 정점들은 특정 간선의 방향에 따라 정렬된다.</p>
<ul>
<li>예) 정점 A → 정점 B라면, A는 반드시 B보다 먼저 나오게 된.</li>
</ul>
</li>
</ul>
<h4 id="heading-topological-sorting">✅ <strong>위상 정렬 (Topological Sorting)</strong>이란?</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733375773349/b6ddfff7-2bdc-4284-b77f-0a0240c72203.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>위상 정렬은 DAG에서 모든 정점을 순서대로 정렬하는 알고리즘 (Algorithm to order all nodes in sequence in a DAG)</strong>이다. 즉 DAG의 간선 방향에 따라 정렬된 순서를 만들게 된다.</p>
</li>
<li><p>정렬된 순서에서 간선의 방향이 모두 올바르게 유지되도록 보장한다.</p>
</li>
</ul>
<hr />
<h3 id="heading-1-key-concepts-for-topological-sorting">위상정렬 #1: 필요한 개념 (Key Concepts for Topological Sorting)</h3>
<p><strong>위상 정렬 (Topological Sorting)</strong>을 이해하기 위해 필수적인 두 가지 개념을 설명한다. 위상정렬을 실행 하려면 간선들이 들어오고 나가는지 파악해야 하는데 이는 u를 기준으로 한다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733375943419/0a72df6a-93ad-4151-b045-157cb288d90f.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-incoming-edges">✅ 진입 간선 (Incoming Edges)</h4>
<ul>
<li><p>진입 간선은 특정 정점으로 <strong>들어오는 방향의 간선 (Edges pointing toward a node)</strong>이다.</p>
<ul>
<li>예: 정점 u에 도달하는 화살표가 “진입 간선(Incoming Edges)”이다.</li>
</ul>
</li>
</ul>
<h4 id="heading-outgoing-edges">✅ 진출 간선 (Outgoing Edges)</h4>
<ul>
<li><p>진출 간선은 특정 정점에서 <strong>나가는 방향의 간선 (Edges pointing away from a node)</strong>이다.</p>
<ul>
<li>예: 정점 u에서 다른 정점으로 나가는 화살표가 진출 간선(Outgoing Edges)이다.</li>
</ul>
</li>
</ul>
<h4 id="heading-in-degree">✅진입 차수 (In-degree)</h4>
<ul>
<li><p>진입 차수는 <strong>정점으로 들어오는 간선의 개수 (The number of incoming edges)</strong>이다.</p>
<ul>
<li>예: 정점 u에 들어오는 화살표가 3개라면, u의 진입 차수는 3이 된다.</li>
</ul>
</li>
<li><p><strong>진입 차수가 0</strong>이면:</p>
<ul>
<li>이 정점은 다른 정점에 의존하지 않고 가장 먼저 처리될 수 있다.</li>
</ul>
</li>
<li><p><strong>진입 차수가 1 이상</strong>이면:</p>
<ul>
<li>이전에 처리해야 할 정점이 존재하며, 제약 조건이 발생하게 된다.</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-2-dependency-graph-for-cooking-instant-ramen">위상정렬 #2: 라면 끓이기 작업의 선후 관계 (Dependency Graph for Cooking Instant Ramen)</h3>
<p><strong>라면 끓이기 작업</strong>을 선후 관계에 따라 그래프로 표현한 것이다. 각 작업은 <strong>노드 (Nodes)</strong>로, 작업 간 관계는 <strong>간선 (Edges)</strong>으로 나타낸다. 위상 정렬이 어떻게 실생활 작업에 적용될 수 있는지를 보여준다.</p>
<p><code>선후 관계(Precedence Relationship)</code>: 어떤 사상 A가 일어난 뒤에 사상 B가 관측되는 관계를 뜻한다. the specific order in which certain components or joints of a product must be disassembled, based on their relationship to each other.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733376343171/33873c47-c72a-4095-bf67-5d9dc28af519.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-4pyfioq3uouemo2uhcdshktrqou6">✅ 그래프 설명:</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733378337851/ade82eb3-9472-4efd-aa2c-08c6605278d3.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>초기 상태 (Step a): 진입 차수가 0인 노드 선택 (Choose Nodes with In-degree 0):</strong></p>
<ul>
<li><p>"냄비에 물 붓기"와 "라면 봉지 뜯기"는 진입 차수가 0이다.</p>
</li>
<li><p>예제에서는 "냄비에 물 붓기"를 먼저 선택하였다.</p>
</li>
</ul>
</li>
<li><p><strong>진출 간선 제거 (Step b):</strong> "냄비에 물 붓기"와 연결된 <strong>진출 간선 (Outgoing Edges)</strong>을 제거하였다. 이 작업으로 "점화"가 진입 차수 0이 되어 선택된다.</p>
</li>
<li><p><strong>점화 이후 (Step c):</strong> "점화"와 연결된 노드 3개가 활성화된다: "라면 넣기", "수프 넣기", "계란 풀어넣기". 하지만 이들은 여전히 다른 간선과 연결되어 있어 바로 선택할 수 없다.</p>
</li>
<li><p><strong>다음 작업 선택 (Step d):</strong> "라면 봉지 뜯기"는 더 이상 연결된 간선이 없으므로 선택되었다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733378435559/dc0f3555-1f2d-4f11-8d2c-e1c7390ccc68.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>라면과 수프 (Step e):</strong> "라면 넣기"와 "수프 넣기" 중에서 임의로 "수프 넣기"를 선택하여 제거한다. 순서에 상관없이 추가할 수 있다.</p>
</li>
<li><p><strong>마지막 단계 (Step f):</strong> 이제 남은 노드는 "계란 풀어넣기"로, 이를 제거하면 정렬이 완료된다.</p>
</li>
</ul>
<h4 id="heading-4pyfioychoydgsdsojxrokzsnzgg7zmc7jqpog">✅ 위상 정렬의 활용:</h4>
<ul>
<li><p>위상 정렬을 사용하면 선후 관계를 유지하면서 작업을 순서대로 수행할 수 있게 된다.</p>
<ul>
<li>예: 불 켜기 → 라면 봉지 뜯기 → 수프 또는 라면 추가.</li>
</ul>
</li>
<li><p><strong>실생활 예시 (Real-world Analogy)</strong>:</p>
<ul>
<li>서류를 작성해야 프린트를 할 수 있는 것처럼, 작업 간 의존성을 정리할 때 사용된다.</li>
</ul>
</li>
</ul>
<hr />
<p><strong><mark>💡 중요 포인트 (Key Points)</mark></strong></p>
<ol>
<li><p>작업 간 <strong>선후 관계 (Dependency Relationship)</strong>를 이해하는 것이 중요하다.</p>
</li>
<li><p><strong>점화 (Start the Stove)</strong>는 모든 작업의 시작점이다.</p>
</li>
<li><p>특정 작업을 수행하려면 <strong>필요 조건 (Prerequisites)</strong>을 먼저 완료해야 한다.</p>
</li>
<li><p>위상 정렬은 이러한 순서를 결정해주는 <strong>효율적 알고리즘 (Efficient Algorithm)</strong>이다.</p>
</li>
</ol>
<hr />
<h3 id="heading-3-pseudocode-for-topological-sorting">위상정렬 #3: <strong>의사코드 (Pseudocode for Topological Sorting)</strong></h3>
<p><strong><mark>💡요약:</mark></strong> 위상 정렬은 <strong>진입 차수가 0인 정점</strong>을 반복적으로 선택하여 결과 배열에 추가하고, 해당 정점과 연결된 간선을 제거하는 방식으로 진행된다. DAG 구조에서는 진입 차수가 0인 정점이 시작점이 되며, 정렬이 끝날 때까지 반복한다. 복수의 정렬이 가능한 이유는 진입 차수가 0인 정점이 동시에 여러 개 있을 수 있기 때문이다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733378569960/45513976-7d1d-44bc-9257-d451dd326b30.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>G: 그래프, v: 정점</strong></p>
</li>
<li><p><strong>진입 차수 0인 정점 선택 (Choose a Node with In-degree 0)</strong>: 그래프에서 진입 차수가 0인 정점 u를 선택한다. 그 이유는 DAG에서 a, g는 들어오는 간선이 없기 때문에 진입 차수가 0이 된다.</p>
</li>
<li><p><strong>결과 배열에 추가 (Add to Result Array)</strong>: <code>A[i] ← u</code> 정점 u를 결과 배열 A[i]에 추가한다.</p>
</li>
<li><p><strong>간선 제거 (Remove Outgoing Edges)</strong>: 정점 u에 진출한 간선을 모두 제거한다.연결된 다른 정점들의 진입 차수를 업데이트한다.</p>
</li>
<li><p><strong>반복 (Repeat)</strong>: 정점의 개수만큼 위 과정을 반복하여 모든 정점을 정렬하게 된다.</p>
</li>
<li><p><strong>복수의 정렬 가능성:</strong> 만약 <strong>진입 차수 0인 정점이 여러 개 (Multiple Nodes with In-degree 0)</strong>라면, 임의로 하나를 선택해도 된다. 선택 순서에 따라 <strong>여러 위상 정렬 결과</strong>가 나올 수 있다.</p>
</li>
</ul>
<hr />
<p><strong>✅ 중요 포인트 (Key Points)</strong></p>
<ol>
<li><p><strong>진입 차수 0인 정점 (Nodes with In-degree 0)</strong>: 그래프에서 시작점으로 사용할 수 있는 정점.</p>
</li>
<li><p><strong>진출 간선 제거 (Remove Outgoing Edges)</strong>: 정렬된 정점의 간선을 제거하며, 연결된 다른 정점들의 진입 차수를 업데이트</p>
</li>
<li><p><strong>복수의 정렬 가능 (Multiple Sorting Orders)</strong>: 선택 순서에 따라 결과가 달라질 수 있다.</p>
</li>
<li><p><strong>시간 복잡도 (Time Complexity)</strong>: O(E + V), 여기서 E는 간선 수, V는 정점 수를 뜻한다.</p>
</li>
</ol>
<hr />
<h3 id="heading-4-topological-sorting-with-data-structures">위상정렬 #4 : 자료구조 (Topological Sorting with Data Structures)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733380355486/a8ce6f0c-de5c-4984-9150-dce3cc517cda.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-calculate-in-degree">✅ <strong>진입 차수 계산하기 (Calculate In-degree):</strong> 노드의 진입 차수를 계산하여 배열에 저장</h4>
<ul>
<li><p>노드 1에서 2, 3으로 연결된 상태이다. <strong>D[2]와 D[3]의 진입 차수</strong>를 각각 1씩 증가시킨다.</p>
</li>
<li><p>결과: 진입 차수 리스트 D[N]는 다음과 같이 설정된다</p>
<ul>
<li><p><strong>1번 노드: 0</strong> (들어오는 간선 없음).</p>
</li>
<li><p><strong>2번, 3번 노드: 1</strong> (노드 1에서 들어오는 간선).</p>
</li>
<li><p><strong>4번, 5번 노드: 2</strong> (다른 두 노드에서 들어오는 간선).</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733380575090/64d3ceae-84a2-45e3-a6dd-4d6b2225e741.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-0-select-nodes-with-in-degree-0">✅<strong>진입 차수 0인 노드 선택 (Select Nodes with In-degree 0)</strong></h4>
<ul>
<li><p>진입 차수(<strong>In-degree)</strong> 0인 노드를 선택하여 정렬 리스트에 저장한다.</p>
<ul>
<li><p>처음에는 노드 1이 진입 차수 0이므로 선택되었다.</p>
</li>
<li><p>노드 1과 연결된 간선을 제거하면서, 2번과 3번의 진입 차수를 각각 1 감소시킨다.</p>
</li>
<li><p>결과: 2번, 3번 노드의 진입 차수가 0이 된다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733382136291/3bd015cf-bf6b-461f-acb3-a084276cf1dd.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-repeat-until-all-nodes-are-processed">✅ <strong>과정 반복 (Repeat Until All Nodes are Processed)</strong></h4>
<ul>
<li><p>진입 차수 0이 된 2번 노드를 선택하고, 정렬 리스트에 추가한다.</p>
<ul>
<li>노드 2와 연결된 노드 4와 5의 진입 차수를 각각 1 감소시킨다.</li>
</ul>
</li>
<li><p>그다음 3번 노드를 선택하고, 진입 차수를 업데이트한다.</p>
</li>
<li><p>4번 노드와 5번 노드가 순차적으로 선택되며, 정렬이 종료된다.</p>
</li>
<li><p>2와 3의 순서는 임의로 선택할 수 있으므로 복수의 정렬 결과가 가능하다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733383494819/85717ce2-a15b-4322-86f4-8947b2cac3c9.png" alt class="image--center mx-auto" /></p>
<hr />
<p><strong><mark>💡 중요포인트</mark></strong></p>
<ul>
<li><p><strong>진입 차수 계산 (Calculate In-degrees):</strong> 각 노드의 진입 차수를 미리 계산하여 정렬 과정을 준비한다.</p>
</li>
<li><p><strong>진입 차수 0인 노드 선택 (Select Nodes with In-degree 0):</strong> 진입 차수 0인 노드를 우선 선택하여 처리한다. 연결된 간선을 제거하며 다른 노드들의 진입 차수를 업데이트한다.</p>
</li>
<li><p><strong>복수 정렬 가능성 (Multiple Valid Orders):</strong> 동일한 진입 차수를 가진 노드 순서는 임의로 선택할 수 있으므로, 여러 정렬 결과가 가능하다.</p>
</li>
</ul>
<hr />
<h3 id="heading-5-dfs-topological-sorting-with-dfs">위상정렬 #5 : 의사코드 - DFS 버전 (Topological Sorting with DFS)</h3>
<p>위상 정렬의 이 버전은 <strong>깊이 우선 탐색 (Depth-First Search, DFS)</strong> 알고리즘을 사용한다.</p>
<ul>
<li><p>DFS는 끝까지 탐색한 후 뒤로 돌아오는 <strong>재귀적인 구조</strong>를 가집니다.</p>
</li>
<li><p><strong>DFS 기반 위상 정렬</strong>은 깊이 우선 탐색을 사용하여, 노드의 방문 여부를 확인하며 모든 노드를 탐색하개 된다.</p>
</li>
<li><p>따라서, 위상 정렬에서는 <strong>가장 마지막에 도달한 노드부터 정렬</strong>이 시작된다.</p>
</li>
<li><p><strong>거꾸로 정렬 (Reverse Order):</strong> DFS는 결과를 거꾸로 쌓으므로, 위상 정렬 리스트는 DFS의 반환 순서대로 앞에서부터 정렬되게 된다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733383528735/37a7b616-3b54-4a9e-abbd-a0f1063d277d.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>초기화 (Initialization)</strong></p>
<ul>
<li><p>모든 노드를 방문 하지 않았다는 <code>v.visited←NO</code> 로 초기화한다.</p>
</li>
<li><p>정점의 순서는 중요하지 않으므로, <strong>임의의 정점 (Arbitrary Node)</strong>부터 탐색을 시작한다.</p>
</li>
</ul>
</li>
<li><p><strong>DFS 호출 (DFS-TS):</strong> 아직 방문하지 않은 노드라면, DFS를 호출한다.</p>
</li>
<li><p><strong>깊이 우선 탐색 (Perform DFS):</strong></p>
<ul>
<li><p>현재 노드를 방문했음을 표시한다: <code>v.visited←YES</code></p>
</li>
<li><p>현재 노드의 <strong>인접한 노드들 (Adjacent Nodes)</strong>을 탐색한다.</p>
<ul>
<li>방문하지 않은 인접 노드가 있다면, 해당 노드에서 다시 DFS를 호출한다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>결과 리스트 삽입 (Insert into Result):</strong></p>
<ul>
<li><p>모든 인접 노드의 탐색이 끝난 후, 현재 노드를 <strong>결과 리스트의 맨 앞에 삽입</strong>한다.</p>
</li>
<li><p>DFS의 특성상, <strong>가장 마지막에 방문한 노드부터 정렬이 시작</strong>된다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong><mark>💡 중요 포인트 (Key Points):</mark></strong></p>
<ol>
<li><p><strong>깊이 우선 탐색 (Depth-First Search):</strong> DFS를 사용해 모든 노드를 탐색하며, 재귀적으로 호출한다.</p>
</li>
<li><p><strong>정렬 순서 결정 (Order of Sorting):</strong> 가장 나중에 방문한 노드가 정렬 리스트의 앞에 위치한다.</p>
</li>
<li><p><strong>재귀 호출 (Recursive Calls):</strong> 인접한 노드를 모두 탐색할 때까지 DFS를 계속 호출한다.</p>
</li>
<li><p><strong>시간 복잡도 (Time Complexity):</strong></p>
<ul>
<li>O(V + E) 여기서 V는 정점 수, E는 간선 수이다.</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-6-dfs-topological-sorting-with-dfs">위상정렬 #6 : 작동방식 - DFS 버전 (Topological Sorting with DFS)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733383837893/f05022a2-9903-41c1-ab66-40eda917b2ab.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>(b) 수프 넣기 선택:</strong> DFS를 시작하며 "수프 넣기" 노드가 선택되었다.</p>
</li>
<li><p><strong>(c) 계란 풀어넣기 발견:</strong></p>
<ul>
<li><p>DFS를 수행하며 "계란 풀어넣기"까지 탐색한다.</p>
</li>
<li><p><strong>"계란 풀어넣기"는 정렬의 마지막 자리</strong>로 설정된다.</p>
</li>
</ul>
</li>
<li><p><strong>(d) 수프 넣기로 돌아가기:</strong></p>
<ul>
<li>계란 풀어 넣기는 갈 곳이 없으므로 DFS 경로를 되돌아가며 "수프 넣기"를 정렬 리스트의 두 번째 자리에 설정하게 된다.</li>
</ul>
</li>
<li><p><strong>(e) 임의 선택 (냄비에 물 붓기):</strong></p>
<ul>
<li>DFS를 수행할 새로운 시작 노드로 "냄비에 물 붓기"를 선택하였다.</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733383894892/0a5267f9-76bb-4967-af79-b1d6999442b7.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>(f) 점화로 이동:</strong></p>
<ul>
<li>"냄비에 물 붓기"에서 DFS를 통해 "점화"로 이동한다.</li>
</ul>
</li>
<li><p><strong>(g) 라면 넣기 탐색:</strong></p>
<ul>
<li><p>"점화"에서 연결된 "라면 넣기"로 이동하며, "라면 넣기"가 DFS 종료 지점으로 설정된다.</p>
</li>
<li><p><strong>"라면 넣기"는 정렬의 세 번째 자리</strong>를 차지한다.</p>
</li>
</ul>
</li>
<li><p><strong>(h) DFS 경로 거슬러 올라가기:</strong></p>
<ul>
<li>DFS를 되돌아가며 "점화"를 네 번째 자리로 설정한다.</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733383952980/7d89b38a-fca6-482b-b2fa-b78def8f1faa.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>(i) 냄비에 물 붓기 정렬:</strong></p>
<ul>
<li>"냄비에 물 붓기"가 정렬의 다섯 번째 자리에 설정된다.</li>
</ul>
</li>
<li><p><strong>(j) 라면 봉지 뜯기:</strong></p>
<ul>
<li>마지막 남은 노드인 "라면 봉지 뜯기"가 정렬의 첫 번째 노드로 설정된다.</li>
</ul>
</li>
</ul>
<p><strong>최종 순서 (Final Order):</strong></p>
<ul>
<li>"라면 봉지 뜯기"→"계란 풀어넣기"→"수프 넣기"→"라면 넣기"→"점화"→"냄비에 물 붓기"</li>
</ul>
<hr />
<p><strong><mark>💡 중요 포인트 (Key Points):</mark></strong></p>
<ol>
<li><p><strong>DFS 특성 활용:</strong> 깊이 우선 탐색의 특성을 통해 <strong>가장 깊은 노드부터 역순으로 정렬</strong>되었다.</p>
</li>
<li><p><strong>거꾸로 정렬 (Reverse Order):</strong> DFS 종료 시점부터 정렬 리스트에 추가하므로, 결과가 역순으로 쌓이게 된다.</p>
</li>
<li><p><strong>임의 선택 가능 (Arbitrary Selection):</strong> 시작할 노드가 여러 개일 경우, 임의로 선택할 수 있으며 결과적으로 <strong>복수의 정렬 순서</strong>가 가능하다.</p>
</li>
</ol>
<hr />
<h2 id="heading-2-shortest-path-algorithm"><strong>2️⃣</strong>최단 경로 알고리즘 (Shortest Path Algorithm)</h2>
<p><strong><mark>💡정리: </mark> 최단 경로 (Shortest Path)</strong>란 두 정점 사이의 경로들 중 <strong>간선의 가중치 합 (Sum of Edge Weights)</strong>이 가장 작은 경로를 말한다. 예를 들어, <strong>노드 A → 노드 B</strong>로 가는 여러 경로 중 가중치가 최소인 경로가 최단 경로가 된다.</p>
<p>최단 경로 알고리즘은 그래프 이론에서 중요한 개념으로, 실생활의 <strong>네트워크 경로 탐색, 물류 최적화, 길 찾기</strong> 등에서 널리 활용되고 있다.  </p>
<p><strong>✅ 최단 경로 알고리즘을 위한 조건</strong></p>
<ol>
<li><p><strong>방향성 (Direction):</strong></p>
<ul>
<li><p>그래프가 방향성을 가지면, <strong>A → B</strong>와 <strong>B → A</strong>의 경로가 달라진다.</p>
</li>
<li><p>따라서 방향성을 명확히 정의해야 한다.</p>
</li>
</ul>
</li>
<li><p><strong>가중치 (Weights):</strong></p>
<ul>
<li><p>각 간선은 특정 <strong>비용(가중치)</strong>을 가진다</p>
</li>
<li><p>가중치는 경로의 총 비용을 계산하는 데 사용된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅ 음수 가중치와 사이클</strong></p>
<ol>
<li><p><strong>음수 가중치 (Negative Weights):</strong></p>
<ul>
<li><p>간선의 가중치가 음수인 경우에도 최단 경로를 구할 수 있다.</p>
</li>
<li><p>단, 사이클이 없는 경우에만 적용 가능하다.</p>
</li>
</ul>
</li>
<li><p><strong>음의 사이클 (Negative Cycle):</strong></p>
<ul>
<li><p>사이클(순환)이 존재하며, 그 가중치 합이 음수일 경우 문제가 발생한다.</p>
<ul>
<li>예: 음수 가중치를 가진 간선을 따라 계속 순환하면 <strong>비용이 무한히 줄어드는 현상</strong>이 발생하게 된다.</li>
</ul>
</li>
<li><p>이럴 경우엔, 최단 경로를 정의할 수 없게 되며, 알고리즘이 <strong>무한 루프</strong>에 빠질 수 있다.</p>
</li>
<li><p>하지만 음수 가중치가 있어도문제가 되지 않는다. 알고리즘을 통해 최단경로를 구할 수 있다. 사이클이 생기는 경우만 안되는 것이다.</p>
</li>
</ul>
</li>
<li><p><strong>음의 사이클 탐지 (Detecting Negative Cycles):</strong></p>
<ul>
<li>이따 배울 <strong>벨만-포드 알고리즘 (Bellman-Ford Algorithm)</strong>은 음의 사이클 여부를 탐지할 수 있는 알고리즘이다. 최단 경로 문제를 해결하는 데 사용된다.</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅ 무방향 그래프와 방향 그래프</strong></p>
<ul>
<li>무방향 그래프는 <strong>양방향 간선</strong>을 포함하는 방향 그래프로 변환하여 최단 경로 알고리즘을 적용할 수 있다. 예) A — B → A → B로 변환</li>
</ul>
<hr />
<h3 id="heading-1-type-of-shortest-path-algorithm">최단 경로 알고리즘 #1 : 분류 (Type of Shortest Path Algorithm)</h3>
<p><strong><mark>💡정리: </mark> 최단 경로 알고리즘</strong>은 크게 두 가지로 나뉜다. 알고리즘은 문제 상황에 맞게 선택되며, 다음 강의에서는 각 알고리즘의 구현 및 세부 원리를 배울 예정이다.</p>
<ol>
<li><p><strong>단일 시작점 최단 경로</strong>: 하나의 출발점에서 모든 노드까지의 최단 경로를 계산.</p>
</li>
<li><p><strong>모든 쌍 최단 경로</strong>: 모든 노드 간의 최단 경로를 계산.</p>
</li>
</ol>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733384920264/8b27fb8a-3a1c-4637-864a-bd6014e8fb71.png" alt class="image--center mx-auto" /></p>
<p>✅<strong>단일 시작점 최단 경로 (Single-Source Shortest Path)</strong></p>
<ul>
<li><p><strong>개념:</strong> 그래프에서 <strong>하나의 시작점</strong>에서 출발하여 <strong>모든 노드</strong>까지의 최단 경로(최솟값)를 구하는 알고리즘이다.</p>
<ul>
<li>시작점에서 종료점을 정하지 않고, 시작점에서 그래프의 <strong>모든 정점</strong>으로 가는 경로를 계산한다.  </li>
</ul>
</li>
<li><p><strong>특징:</strong> 좀 복잡해 보이는 과정이다. 비교적 <strong>복잡도가 낮은 알고리즘</strong>을 사용할 수 있지만, 그래프의 크기에 따라 여전히 복잡도가 높은 편이다.  </p>
</li>
<li><p><strong>주요 알고리즘:</strong></p>
<ol>
<li><p><strong>다익스트라 알고리즘 (Dijkstra's Algorithm):</strong></p>
<ul>
<li><p>간선 가중치가 모두 <strong>양수</strong>일 때 사용한다.</p>
</li>
<li><p>시간 복잡도: O(V log V + E) (우선순위 큐 활용 시).</p>
</li>
</ul>
</li>
<li><p><strong>벨만-포드 알고리즘 (Bellman-Ford Algorithm):</strong></p>
<ul>
<li><p><strong>음수 가중치</strong>를 포함한 그래프에서도 사용 가능하다.</p>
</li>
<li><p>음의 사이클 탐지 가능하다.</p>
</li>
<li><p>시간 복잡도: O(V⋅E)</p>
</li>
</ul>
</li>
<li><p><strong>사이클이 없는 그래프 (Acyclic Graph):</strong></p>
<ul>
<li><p>그래프에 <strong>사이클이 없을 경우</strong>, 더 간단하고 효율적인 알고리즘 사용이 가능하다.</p>
</li>
<li><p>위상 정렬 기반으로 구현한다.</p>
</li>
<li><p><code>Acyclic</code> <strong>:</strong> Not displaying or forming part of a cycle.</p>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<hr />
<p><strong>✅ 모든 쌍 최단 경로 (All-Pairs Shortest Path)</strong></p>
<ul>
<li><p><strong>개념:</strong> 그래프 내 <strong>모든 정점 쌍</strong> 사이의 최단 경로를 계산하는 알고리즘이다.</p>
<ul>
<li>단일 시작점보다 복잡도가 당연하게도 <strong>훨씬 높다</strong>.  </li>
</ul>
</li>
<li><p><strong>특징:</strong> 모든 노드와 모든 노드 사이의 경로를 고려하므로, 그래프의 크기가 클수록 계산량이 기하급수적으로 증가하게 된다.  </p>
</li>
<li><p><strong>주요 알고리즘:</strong></p>
<ol>
<li><p><strong>플로이드-워샬 알고리즘 (Floyd-Warshall Algorithm):</strong></p>
<ul>
<li><p>모든 쌍 최단 경로를 계산하는 대표적인 알고리즘이다.</p>
</li>
<li><p>시간 복잡도: O(V³)</p>
</li>
</ul>
</li>
<li><p><strong>존슨 알고리즘 (Johnson's Algorithm):</strong></p>
<ul>
<li><p>음수 가중치를 포함한 그래프에서도 사용 가능한 알고리즘이다.</p>
</li>
<li><p>다익스트라 알고리즘과 벨만-포드 알고리즘을 조합하여 효율적으로 계산할 수 있다.</p>
</li>
<li><p>시간 복잡도: O(V² log V + V ⋅ E)</p>
</li>
</ul>
</li>
</ol>
</li>
</ul>
<hr />
<p><strong><mark>💡 알아야 할 핵심</mark></strong></p>
<ol>
<li><p><strong>최단 경로 알고리즘의 두 가지 분류:</strong></p>
<ul>
<li><p><strong>단일 시작점 최단 경로 (Single-Source Shortest Path):</strong><br />  시작점에서 모든 노드까지의 최단 경로.</p>
</li>
<li><p><strong>모든 쌍 최단 경로 (All-Pairs Shortest Path):</strong><br />  그래프의 모든 정점 쌍 사이의 최단 경로.</p>
</li>
</ul>
</li>
<li><p><strong>각 알고리즘의 목적에 따른 사용:</strong></p>
<ul>
<li><p>시작점과 종료점이 명확하면 <strong>단일 시작점 알고리즘</strong>을 사용.</p>
</li>
<li><p>전체 그래프의 모든 정점 쌍 경로가 필요하면 <strong>모든 쌍 최단 경로 알고리즘</strong>을 선택.</p>
</li>
</ul>
</li>
<li><p><strong>주요 알고리즘 목록:</strong></p>
<ul>
<li><p>단일 시작점: <strong>다익스트라, 벨만-포드, 위상 정렬 기반 알고리즘.</strong></p>
</li>
<li><p>모든 쌍: <strong>플로이드-워샬.</strong></p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-2-concept-you-should-know-in-single-source-shortest-path-algorithms">최단 경로 알고리즘 #2 : 알아야 할 개념, 완화 (Concept you should know in Single-Source Shortest Path Algorithms)</h3>
<p><strong><mark>“완화(relaxation)”란?</mark></strong></p>
<p>최단 경로 알고리즘에서 <strong>현재 계산된 거리 값을 더 작은 값으로 갱신</strong>하는 과정이다. 이 과정은 <strong>새로운 정점이나 경로를 추가적으로 고려</strong>했을 때 더 짧은 거리 경로를 발견할 가능성이 있을 때 수행된다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733393132910/5e2b16ed-f928-4376-8399-142077b63f34.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>현재 상태</strong>:</p>
<ul>
<li><p>시작점 r에서 특정 정점 v까지의 거리 A는 이미 계산되어 있다.</p>
</li>
<li><p>u는 현재 탐색 중인 정점이다.</p>
</li>
<li><p>w_u,v는 u에서 v로의 간선 가중치를 의미한다.</p>
</li>
</ul>
</li>
<li><p><strong>새로운 경로 탐색</strong>:</p>
<ul>
<li><p>r → u 까지의 거리 B가 이미 계산되어 있다고 가정한다.</p>
</li>
<li><p>u → v 를 거쳐 r → u 의 거리를 계산하면, 총 거리는 B+w{u,v}​가 된다.</p>
</li>
</ul>
</li>
<li><p><strong>완화 조건</strong>:</p>
<ul>
<li><p>기존 거리 A와 새로운 경로 거리w_{u,v}를 비교한다.</p>
</li>
<li><p>만약 새로운 경로의 거리가 기존 거리보다 작다면:</p>
<ul>
<li><p><strong>A &gt; B + w{u,v}</strong></p>
</li>
<li><p>v까지의 최단 거리 값을 A에서 B + w_{u,v}로 업데이트한다.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>업데이트</strong>:</p>
<ul>
<li><p>갱신된 거리 값은 A ← B + w{u,v} ​로 저장된다.</p>
</li>
<li><p>이 과정을 모든 가능한 간선에 대해 반복하여 최적의 경로를 찾아가게 된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅ 완화의 목적</strong></p>
<p>완화는 기존에 계산된 거리 값이 정확하지 않을 가능성을 염두에 두고, 새로운 경로를 통해 <strong>더 짧은 거리 값을 찾아내는 과정</strong>이다. 최종적으로 모든 간선을 완화하여 최단 경로를 도출할 수 있다.</p>
<hr />
<p><strong>💡 이미지의 주요 포인트</strong></p>
<ol>
<li><p><strong>기존 거리 A</strong>: 시작점 r에서 정점 v까지의 거리.</p>
</li>
<li><p><strong>새로운 거리 B + w_{u,v}:</strong> r에서 u를 거쳐 v까지 가는 거리.</p>
</li>
<li><p><strong>비교 조건</strong>: 새로운 경로가 더 짧으면 기존 값을 갱신.</p>
</li>
<li><p><strong>결과</strong>: 최단 거리 리스트에서 정점 v의 값을 업데이트.</p>
</li>
<li><p>다익스트라(Dijkstra) 및 벨만-포드(Bellman-Ford) 알고리즘에서 이 완화 과정을 활용하여, 인접 노드 간의 최단 거리를 반복적으로 갱신해 나간다.</p>
</li>
</ol>
<hr />
<h3 id="heading-4">최단 경로 알고리즘 #4 : 최단 경로 알고리즘의 동적 프로그래밍 적용</h3>
<p><strong>✅ DP 테이블</strong></p>
<ul>
<li><strong>정의</strong>: 시작점 r로부터 특정 정점 v까지의 거리를 저장한다. 이를 v.dist로 표시한다.</li>
</ul>
<hr />
<p><strong>✅ 점화식(Recurrence relation)</strong></p>
<ul>
<li><p>점화식의 목적은 <strong>현재까지 계산된 거리 값을 새로운 경로를 통해 최소화</strong>하는 것이.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733393928546/19246716-bb4f-4b95-944d-ac612b0def46.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>v.dist</strong> : 현재까지 계산된 시작점에서 정점 v까지의 거리.</p>
</li>
<li><p><strong>u.dist</strong> : 시작점에서 정점 u까지의 거리.</p>
</li>
<li><p><strong>w_{u,v}​</strong>: 정점 u에서 v로 이동하는 간선의 가중치.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733393789440/0c1edd70-6527-404f-9c17-31ccc70dc286.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>현재 상태</strong>:</p>
<ul>
<li><p>시작점 r에서 u까지의 거리 u.dist는 이미 계산된 값이다.</p>
</li>
<li><p>u에서 v까지의 간선 가중치 w_{u,v}​를 추가로 고려한다.</p>
</li>
</ul>
</li>
<li><p><strong>새로운 거리 계산</strong>:</p>
<ul>
<li>시작점 r에서 v까지의 기존 거리 v.dist와 새로운 경로를 통해 계산된 거리 u.dist + w_{u,v}​를 비교한다.</li>
</ul>
</li>
<li><p><strong>최소 거리 선택</strong>:</p>
<ul>
<li>기존 거리 v.dist와 새로운 거리 u.dist + w{u,v} 중 더 작은 값을 v.dist로 업데이트한다.</li>
</ul>
</li>
<li><p><strong>결과</strong>:</p>
<ul>
<li>u를 거친 새로운 경로가 더 짧다면 DP 테이블에서 v.dist 값을 갱신하게 된다.</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-5-dagdirected-acyclic-graph">최단 경로 알고리즘 #5 : 사이클이 없는<strong>DAG(Directed Acyclic Graph) 최단 경로를 구하는 알고리즘</strong></h3>
<p>사이클이 없는(DAG) 최단 경로 알고리즘은 DFS 기반 위상 정렬과 같이 위상 정렬 기반으로 동작하는 알고리즘이다. 양수 및 음수 가중치를 모두 허용하며, 음수 사이클이 없는 DAG에서 안전하게 동작한다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733394173538/10065d58-6a1f-41d9-884d-1e6cc7ceef53.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>초기화 (Initialization)</strong>:</p>
<ul>
<li><p>모든 정점 u ∈ V의 초기 거리 값을 무한대 (∞)로 설정한다. u.dist ← ∞ u</p>
</li>
<li><p>시작점 r의 거리 값은 0로 설정한다 : r.dist ← 0</p>
</li>
</ul>
</li>
<li><p><strong>위상 정렬 (Topological Sort)</strong>:</p>
<ul>
<li><p>그래프 G의 정점들을 <strong>위상 정렬 순서</strong>로 나열한다.</p>
</li>
<li><p>위상 정렬은 DAG에서 모든 정점의 선행 관계를 유지하며 정렬한다.</p>
</li>
</ul>
</li>
<li><p><strong>거리 갱신 (Relaxation)</strong>:</p>
<ul>
<li><p>위상 정렬된 순서대로 각 정점 u를 순회한다.</p>
</li>
<li><p>각 정점 u의 인접 리스트에 포함된 모든 정점 v ∈ u.adjlist에 대해:</p>
<ul>
<li><p>만약 u.dist + w{u,v} &lt; v.dist 이면, v.dist 값을 u.dist + w{u,v}로 업데이트 한다.</p>
</li>
<li><p>v.prevv를 u로 설정하여 최단 경로를 추적 가능하도록 유지한다.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>최종 결과</strong>:</p>
<ul>
<li><p>v.dist는 시작점 r에서 각 정점 v까지의 최단 거리를 저장한다.</p>
</li>
<li><p>v.prev 를 통해 각 정점에 도달하는 최단 경로를 역추적할 수 있게 된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-6-dagdirected-acyclic-graph">최단 경로 알고리즘 #6: 사이클이 없는<strong>DAG(Directed Acyclic Graph) 최단 경로를 구하는 알고리즘의 동작 예</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733395267588/7a0c94bb-e091-44b5-adbd-03b3b104024d.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-1"><strong>1단계: 그래프 입력 및 시작 정점 설정</strong></h4>
<ul>
<li><p>DAG(Directed Acyclic Graph)가 주어졌다.</p>
</li>
<li><p>시작 정점은 <code>r</code>로 설정되었다.</p>
</li>
<li><p>모든 간선은 가중치가 있으며, 정점 간의 방향이 지정되어 있다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733395274507/a21f9e9a-6760-4638-8a56-8589a78862ba.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-2"><strong>2단계: 위상 정렬 수행</strong></h4>
<ul>
<li><p>DAG에서 위상 정렬을 수행하여 정점들의 처리 순서를 정한다.</p>
</li>
<li><p>위상 정렬 순서는 <code>r → 원형 정점 → 하트 정점 → 마름모 정점 → 삼각형 정점</code>으로 결정된다.</p>
</li>
</ul>
<hr />
<h4 id="heading-3"><strong>3단계: 거리 초기화</strong></h4>
<ul>
<li><p>시작 정점(<code>r</code>)의 거리는 0으로 설정되고, 나머지 정점의 초기 거리는 무한대(<code>∞</code>)로 설정된다.</p>
</li>
<li><p>초기 상태: <code>r: 0</code> , 나머지 정점: <code>∞</code></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733395282735/4a1599ce-9140-454a-946b-d76dfc982dee.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-4-r"><strong>4단계: 정점 r의 거리 갱신</strong></h4>
<ul>
<li><p>정점 <code>r</code>(0)에서 인접 정점으로 거리 값을 갱신한다.</p>
<ul>
<li><p><code>r → 원형 정점</code>: 거리 3, <code>r → 하트 정점</code>: 거리 7</p>
</li>
<li><p>결과: <code>r: 0</code>, 원형: 3, 하트: 7, 나머지 정점: <code>∞</code></p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-5"><strong>5단계: 원형 정점의 거리 갱신</strong></h4>
<ul>
<li><p>정점 <code>원형</code>에서 인접 정점으로 거리 값을 갱신한다.</p>
<ul>
<li><p><code>원형 → 하트 정점</code>: 기존 거리 7과 새로운 거리 <code>3 + 4 = 7</code> 비교 → 갱신 필요 없음.</p>
</li>
<li><p>결과: <code>r: 0</code>, 원형: 3, 하트: 7, 나머지 정점: <code>∞</code></p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733395293985/ddd55fc7-1362-4d1a-8978-d29ac6724e24.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-6"><strong>6단계: 하트 정점의 거리 갱신</strong></h4>
<ul>
<li><p>정점 <code>하트</code>에서 인접 정점으로 거리 값을 갱신한다.</p>
<ul>
<li><p><code>하트 → 마름모 정점</code>: 거리 <code>7 + (-2) = 5</code></p>
</li>
<li><p>결과: <code>r: 0</code>, 원형: 3, 하트: 7, 마름모: 5, 삼각형: <code>∞</code></p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-7"><strong>7단계: 마름모 정점의 거리 갱신</strong></h4>
<ul>
<li><p>정점 <code>마름모</code>에서 인접 정점으로 거리 값을 갱신한다.</p>
<ul>
<li><p><code>마름모 → 삼각형 정점</code>: 거리 <code>5 + (-3) = 2</code></p>
</li>
<li><p>결과: <code>r: 0</code>, 원형: 3, 하트: 7, 마름모: 5, 삼각형: 2</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733395299072/6fd89007-85d4-4a93-9518-083ce95920f0.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-8"><strong>8단계: 최종 결과</strong></h4>
<ul>
<li><p>모든 정점의 최단 거리가 계산되었다. 최단 경로는 아래와 같다:</p>
<ul>
<li><p><code>r → 원형(3)</code></p>
</li>
<li><p><code>r → 하트(7)</code></p>
</li>
<li><p><code>r → 하트 → 마름모(5)</code></p>
</li>
<li><p><code>r → 하트 → 마름모 → 삼각형(2)</code></p>
</li>
</ul>
</li>
<li><p>굵은 간선으로 최단 경로를 표시하였다.</p>
</li>
</ul>
<hr />
<h3 id="heading-8jsoeylnoyekeygkoydtcdsnojripqg7lwc64uoioqyveuhncdslyzqs6drpqzsppjsnzgg7jyg7ziv">💡시작점이 있는 최단 경로 알고리즘의 유형</h3>
<h4 id="heading-single-source-shortest-path"><strong>단일 시작점 최단 경로(Single Source Shortest path)란?</strong></h4>
<ul>
<li><p>그래프에서 <strong>하나의 시작점 (Single Source)</strong>에서 출발하여, <strong>모든 정점 (All Nodes)</strong>까지의 최단 경로를 구하는 알고리즘이다.</p>
</li>
<li><p>이번 시간에는 두 가지 주요 알고리즘을 배우게 된다:</p>
<ol>
<li><p><strong>다익스트라 알고리즘 (Dijkstra's Algorithm)</strong></p>
</li>
<li><p><strong>벨만 포드 알고리즘 (Bellman-Ford Algorithm)</strong></p>
</li>
</ol>
</li>
</ul>
<hr />
<h4 id="heading-dijkstras-algorithm"><strong>✅ 다익스트라 알고리즘 (Dijkstra's Algorithm)</strong></h4>
<ul>
<li><p><strong>특징: 음의 가중치 (Negative Weights)</strong>를 <strong>허용하지 않음 (Not Allowed)</strong>.</p>
<ul>
<li><p>예: 간선의 가중치가 -2인 경우 사용 불가.</p>
</li>
<li><p>시작점에서 가장 가까운 정점부터 탐색하며 <strong>비용을 갱신 (Update Costs)</strong>하는 알고리즘이다..</p>
</li>
</ul>
</li>
<li><p><strong>수행 시간 (Time Complexity):</strong></p>
<ul>
<li><p>O(E log V) 여기서 E는 간선의 개수, V는 정점(vertex, <strong>node)</strong>의 개수이다.</p>
</li>
<li><p>매우 빠른 알고리즘이다.</p>
</li>
<li><p><code>정점(vertex)</code> <strong>node</strong>를 의미한다. <code>간선(edge)</code> 노드 간에 연결되어 있는 선을 의미</p>
</li>
</ul>
</li>
<li><p><strong>사용 조건:</strong></p>
<ul>
<li><p>모든 간선의 가중치가 <strong>양수일 때 (All Positive Weights)</strong> 사용한다.</p>
</li>
<li><p>대규모 그래프에서 효율적이다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-bellman-ford-algorithm"><strong>✅ 벨만 포드 알고리즘 (Bellman-Ford Algorithm):</strong></h4>
<ul>
<li><p><strong>특징: 음의 가중치 (Negative Weights)</strong>를 허용한다.</p>
<ul>
<li><p>예: 간선의 가중치가 -2인 경우에도 사용 가능하다.</p>
</li>
<li><p>모든 간선을 반복적으로 확인하며 <strong>비용을 갱신 (Update Costs)</strong>한다.</p>
</li>
</ul>
</li>
<li><p><strong>수행 시간 (Time Complexity):</strong></p>
<ul>
<li>O(E ⋅ V), 상대적으로 느린 알고리즘이다.</li>
</ul>
</li>
<li><p><strong>사용 조건:</strong></p>
<ul>
<li><p>그래프에 <strong>음의 가중치 (Negative Weights)</strong>가 포함되어 있을 경우 사용한다.</p>
</li>
<li><p>음의 가중치 사이클(무한히 비용을 감소시키는 경로)을 탐지할 수도 있다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-3-dijkstras-algorithm"><strong>3️⃣</strong>다익스트라 알고리즘(Dijkstra's Algorithm)</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733395660789/60558c1a-c4cf-4b7d-a472-88f98e56abb8.png" alt class="image--center mx-auto" /></p>
<p><strong>다익스트라 알고리즘의 과정 (Detailed Analysis of Dijkstra's Algorithm)</strong></p>
<h4 id="heading-1-initialization"><strong>1. 초기화 (Initialization):</strong></h4>
<ul>
<li><p>시작점에서 출발하기 위해, <strong>시작점을 제외한 모든 정점의 거리 (Distance)</strong>를 <strong>무한대 (Infinity)</strong>로 설정한다.</p>
<ul>
<li>이 단계는 알고리즘이 최단 경로를 찾기 시작하기 위한 준비 단계이다.</li>
</ul>
</li>
</ul>
<h4 id="heading-2-select-new-node"><strong>2. 신규 정점 선택 (Select New Node):</strong></h4>
<ul>
<li><p>방문하지 않은 정점 중에서 <strong>거리가 가장 짧은 정점 (Node with the Shortest Distance)</strong>을 선택한다.</p>
<ul>
<li><p>예: 시작점 A에서 출발하여 인접한 노드 B, C 중에서 가장 비용이 작은 노드를 선택한다.</p>
</li>
<li><p><strong>이 과정을 "신규 정점 탐색 (New Node Selection)"</strong>이라고 한다.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-3-distance-relaxation"><strong>3. 거리 완화 (Distance Relaxation):</strong></h4>
<ul>
<li><p>선택된 정점(노드)의 <strong>인접한 노드들 (Adjacent Nodes)</strong>을 탐색하며 <strong>거리를 업데이트 (Update Distance)</strong>합니다.</p>
<ul>
<li><p>만약 <strong>새로운 경로를 통해 도달하는 비용이 더 작다면</strong>, 그 값을 갱신한다.</p>
</li>
<li><p>예: A → B의 비용이 5이고, A → C → B를 통해 비용이 4라면, B의 거리 값을 4로 갱신한다.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-4-repeat-until-all-nodes-are-visited"><strong>4. 반복 (Repeat Until All Nodes Are Visited):</strong></h4>
<ul>
<li><p><strong>모든 정점이 방문될 때까지</strong> 위 과정을 반복한다</p>
<ul>
<li>새로운 정점을 선택 → 인접한 정점의 거리 업데이트</li>
</ul>
</li>
</ul>
<h4 id="heading-5-termination"><strong>5. 종료 (Termination):</strong></h4>
<ul>
<li><p>모든 정점의 최단 경로가 계산되었으면 알고리즘이 종료되게 된다.</p>
<ul>
<li>결과적으로, 시작점에서 모든 정점으로 가는 최단 경로가 계산된다.</li>
</ul>
</li>
</ul>
<hr />
<p><strong><mark>✅중요 포인트 (Key Points)</mark></strong></p>
<ul>
<li><p>다익스트라 알고리즘은 <strong>단계별로 정점과 거리를 갱신 (Update Step by Step)</strong>하며 최단 경로를 계산하는 방식이다.</p>
</li>
<li><p><strong>매 단계마다 최단 경로를 확정</strong>하기 때문에 효율적이고 빠르다. 단순한 구조 덕분에 널리 사용된다.</p>
</li>
<li><p>음의 가중치가 없을 때만 사용 가능하며, O(E log V)의 시간 복잡도를 가진다.</p>
</li>
</ul>
<hr />
<h3 id="heading-1-analysis-of-dijkstras-algorithm-pseudocode">다익스트라 알고리즘 #1: 의사코드 분석 (Analysis of Dijkstra's Algorithm Pseudocode)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733401245717/476f1b85-1ffd-4e26-8874-3090591afee0.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-initialization-1"><strong>1. 알고리즘 초기화 (Initialization):</strong></h4>
<ul>
<li><p><strong>방문한 정점 집합 S (Visited Nodes Set):</strong></p>
<ul>
<li><p>초기 상태에서는 아무 정점도 방문하지 않았으므로, S는 <strong>공집합 (Empty Set)</strong>으로 설정된다. <code>S←ϕ</code></p>
</li>
<li><p><code>ϕ</code>는 수학에서 <strong>공집합 (empty set)</strong>을 나타내는 기호이다. 이는 <strong>비어 있는 집합</strong>을 의미하며, 집합 안에 어떤 요소도 포함되지 않은 상태를 나타낸다. 알고리즘이 진행되면서 방문한 정점들은 <code>S</code>에 추가되어, 점점 채워지게 된다.</p>
</li>
</ul>
</li>
<li><p><strong>거리 초기화 (Distance Initialization):</strong></p>
<ul>
<li><p>시작점을 제외한 모든 정점의 거리를 <strong>무한대 (Infinity)</strong>로 초기화한다. <code>u.dist ← ∞</code></p>
</li>
<li><p>시작점의 거리를 0으로 설정한다. <code>r.dist ← 0</code></p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-2-main-loop"><strong>2. 반복문 (Main Loop):</strong></h4>
<ul>
<li><p>반복문은 모든 정점이 방문될 때까지 실행되게 된다.</p>
<ul>
<li>방문한 정점의 집합 S가 전체 정점 집합 V와 같아질 때 종료된다. <code>while (S ≠ V)</code></li>
</ul>
</li>
</ul>
<h4 id="heading-3-extractmin"><strong>3. 방문하지 않은 정점 중 최소 거리 정점 선택 (ExtractMin):</strong></h4>
<ul>
<li><p>방문하지 않은 정점 중에서 <strong>최단 거리 값을 가진 정점 u</strong>를 선택한다: <code>u ← extractMin(V−S)</code></p>
<ul>
<li>이 과정은 앞에서 배운 <code>그리디 알고리즘 (Greedy Algorithm)</code>의 특성을 따른다.</li>
</ul>
</li>
</ul>
<h4 id="heading-4-add-to-visited-set"><strong>4. 방문 집합에 추가 (Add to Visited Set):</strong></h4>
<ul>
<li><p>선택된 최단 거리 값을 가진 정점 u를 방문한 집합 S에 추가한다: S ← S∪{u}</p>
</li>
<li><p><strong>S</strong>: 이미 방문한 정점들의 집합</p>
</li>
<li><p><strong>{u}</strong>: 새롭게 방문한 정점 <strong>u</strong>를 포함하는 집합.</p>
</li>
<li><p><strong>S ∪ {u}</strong>: 현재 집합 <strong>S</strong>에 새 정점 <strong>u</strong>를 합쳐 새로운 방문 집합을 만든다는 뜻.</p>
</li>
<li><p>특정 정점 <strong>u</strong>를 방문한 것으로 기록하는 과정을 수학 기호로 표시하였다.</p>
</li>
</ul>
<h4 id="heading-5-distance-relaxation"><strong>5. 거리 완화 (Distance Relaxation):</strong></h4>
<ul>
<li><p>u와 <strong>인접한 노드들 (Adjacent Nodes)</strong>의 거리를 업데이트한다.</p>
<ul>
<li><p>새로운 경로를 통해 비용이 더 작아지는 경우, 거리 값을 갱신하게 된다:</p>
<ul>
<li><p><code>v.dist ← u.dist + wuv</code></p>
</li>
<li><p>v.prev ← u</p>
</li>
</ul>
</li>
<li><p>이 과정을 <strong>완화 (Relaxation)</strong>이라고 부른다.</p>
</li>
</ul>
</li>
<li><p>💡 <code>v.dist ← u.dist + wuv</code> 추가 설명</p>
<ul>
<li><p>현재 정점 <strong>u</strong>를 통해 정점 <strong>v</strong>로 가는 새로운 경로를 고려하여, <strong>v까지의 거리 값</strong>(v.dist)을 갱신한다는 뜻이다.</p>
</li>
<li><p><strong>u.dist</strong>: 시작점에서 정점 <strong>u</strong>까지의 최단 거리이다.</p>
</li>
<li><p><strong>w{uv}</strong>: 정점 <strong>u</strong>에서 <strong>v</strong>로 가는 간선의 가중치이다.</p>
</li>
<li><p><strong>u.dist + w{uv}</strong>: 시작점에서 <strong>u</strong>를 거쳐 <strong>v</strong>까지 가는 경로의 총 거리를 뜻한다.</p>
</li>
<li><p><strong>v.dist</strong>: 기존에 저장된 시작점에서 <strong>v</strong>까지의 최단 거리 값이다.</p>
</li>
</ul>
</li>
<li><p>💡<code>v.prev ← u</code> 추가 설명</p>
<ul>
<li><p>정점(노드) <strong>v</strong>까지의 최단 경로에서 바로 이전 정점(노드)이 <strong>u</strong>임을 기록하는 곳이다.</p>
</li>
<li><p><strong>v.prev</strong>는 최단 경로를 추적하기 위해 사용된다.</p>
</li>
<li><p>갱신된 경로로 <strong>v.dist</strong>가 더 짧아졌다면, <strong>u</strong>가 <strong>v</strong>의 직전 노드임을 저장하는 곳이다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong><mark>💡중요 포인트 (Key Points)</mark></strong></p>
<ol>
<li><p>다익스트라 알고리즘은 <strong>그리디 알고리즘 (Greedy Algorithm)</strong>으로 작동한다.</p>
<ul>
<li>현재 상태에서 최적의 선택(최소 거리 정점)을 반복적으로 수행하게 된다.</li>
</ul>
</li>
<li><p><strong>시간 복잡도 (Time Complexity):</strong></p>
<ul>
<li>O(E log ⁡V)(우선순위 큐를 사용할 경우).</li>
</ul>
</li>
<li><p><strong>음의 가중치 허용 불가 (Negative Weights Not Allowed):</strong></p>
<ul>
<li>음수 가중치가 있는 그래프에서는 사용 불가능하다.</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-2-dijkstras-algorithm-in-action">다익스트라 알고리즘 #2: 그래프 예제 (Dijkstra's Algorithm in Action)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733402300909/2914310b-ed32-413b-a514-a06b1d3e19d7.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-initialization-0"><strong>1. 초기화 (Initialization):</strong> 시작점 <strong>노드 0</strong>에서 알고리즘이 시작된다.</h4>
<ul>
<li><strong>노드 0의 거리 값은 0 (0.dist = 0)</strong>으로 설정되고, 나머지 노드들의 거리 값은 <strong>무한대 (Infinity)</strong>로 초기화된다.</li>
</ul>
<hr />
<h4 id="heading-2-step-1-0-8-9-11"><strong>2. 첫 번째 단계 (Step 1): 0에서 인접한 노드</strong>들(8, 9, 11)의 거리 값을 업데이트한다.</h4>
<ul>
<li><p><strong>업데이트 결과:</strong> 노드 8: 0 + 8 = 8, 노드 9: 0 + 9 = 9, 노드 11: 0 + 11 = 11</p>
</li>
<li><p>방문한 노드(0)는 고려하지 않는다. 가장 작은 거리 값(8)을 가진 노드 8이 선택된다.</p>
</li>
</ul>
<hr />
<h4 id="heading-3-step-2-8"><strong>3. 두 번째 단계 (Step 2): 노드 8에서 인접한 노드</strong>를 탐색하여 거리 값을 업데이트한다.</h4>
<ul>
<li><p>8 → 10 : 8 + 10 = 18</p>
<ul>
<li><p><strong>업데이트 결과:</strong> 노드 10의 거리 값이 무한대에서 18로 설정되었다.</p>
</li>
<li><p>다음으론 나머지 노드에서 가장 작은 거리 값(9)을 가진 노드 9가 선택된다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733402313714/1855c1a2-cede-4366-a806-a3c532321e8b.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-4-step-3-9"><strong>4. 세 번째 단계 (Step 3): 노드 9에서 인접한 노드</strong>를 탐색한다.</h4>
<ul>
<li><p>9 → 10: 기존 거리 값(18)보다 새로운 값(9 + 1 = 10)이 더 작으므로 업데이트한다.</p>
</li>
<li><p>기존의 값이 더 작은 경우에는 업데이트 되지 않으므로 11은 변하지 않고 그대로이다.</p>
<ul>
<li><p><strong>업데이트 결과:</strong> 노드 10: 18 → 9+1 = <strong>10</strong></p>
</li>
<li><p>다음으로 가장 작은 거리 값(10)을 가진 노드 10이 선택되게 된다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-5-step-4-10"><strong>5. 네 번째 단계 (Step 4): 노드 10에서 인접한 노드</strong>를 탐색한다.</h4>
<ul>
<li><p>10 → 12: 기존 거리 값(무한대)에서 새로운 값(10 + 2 = 12)로 업데이트된다.</p>
<ul>
<li><p><strong>업데이트 결과:</strong> 노드 12: 무한대 → 10 + 2 = <strong>12</strong></p>
</li>
<li><p>업데이트 결과를 확인 한 후, 다음으로 가장 작은 거리 값(11)을 가진 노드 11이 선택된다. (이미 방문한 10은 선택 옵션에서 제외되었다)</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-6-step-5-11"><strong>6. 다섯 번째 단계 (Step 5): 노드 11에서 인접한 노드</strong>를 탐색합니다.</h4>
<ul>
<li><p>11 → 19: 업데이트.</p>
<ul>
<li><p><strong>업데이트 결과:</strong> 11이 바라보는 노드 2개, 19 : 무한대 → 11 + 8 = <strong>19</strong></p>
</li>
<li><p>비용값이 최소인 값을 보니 다음으로 가장 작은 거리 값(12)을 가진 노드 12가 선택된다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733402319926/0b393e79-c0fa-490c-95f4-7242766c9ca2.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-7-step-6-12"><strong>7. 여섯 번째 단계 (Step 6): 노드 12에서 인접한 노드</strong>를 탐색한다.</h4>
<ul>
<li><p>12하고 연결된 노드는 16 하나밖에 없으므로 기존의 값보다 더 작은 16으로 설정이 된다.</p>
</li>
<li><p>12 → 16: 기존 거리 값보다 새로운 값(12 + 4 = 16)로 업데이트한다.</p>
<ul>
<li><p><strong>업데이트 결과:</strong> 노드 16: 19 → 12 + 4 = <strong>16</strong></p>
</li>
<li><p>다음으로 가장 작은 거리 값(16)을 가진 노드 16이 선택된다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-8-step-7-16"><strong>8. 마지막 단계 (Step 7): 노드 16에서 인접한 노드</strong>를 탐색한다.</h4>
<ul>
<li><p>16 → 19: 이미 방문한 노드로 더 이상 업데이트가 필요없다.</p>
</li>
<li><p>모든 노드를 방문했으므로 알고리즘이 종료된다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733402325801/66392e39-8f4e-4930-93e2-f8a3c204ed76.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>시작점 <strong>노드 0</strong>에서 모든 노드까지의 <strong>최단 경로</strong>가 계산되었다.</p>
</li>
<li><p><strong>최적 경로 그래프:</strong> 각 노드로 가는 최단 경로가 붉은색 화살표로 표시되었다.</p>
</li>
</ul>
<hr />
<p><strong><mark>💡 중요 포인트 (Key Points)</mark></strong></p>
<ol>
<li><p>다익스트라 알고리즘은 <strong>그리디 방식 (Greedy Approach)</strong>으로 최적의 해를 단계적으로 선택한다.</p>
</li>
<li><p><strong>거리 값이 작은 노드부터 업데이트</strong>를 진행한다.</p>
</li>
<li><p><strong>업데이트(완화) 과정 (Relaxation):</strong> 기존 거리 값보다 작은 값이 발견되면 갱신된다.</p>
</li>
</ol>
<hr />
<p><strong>💡👀다익스트라(Dijkstra)❓</strong></p>
<p>다익스트라는 이를 만든 수학자의 이름에서 유래되었다. <strong>에츠허르 비버 다익스트라(Edsger Wybe Dijkstra)</strong>라는 네덜란드의 유명한 컴퓨터 과학자이다. 다익스트라는 컴퓨터 과학 발전에 큰 기여를 한 인물로, 특히 그래프 알고리즘과 소프트웨어 엔지니어링 분야에서 잘 알려져 있다. 1956년에 고안되어 오늘날까지도 다양한 응용 분야에서 사용되고 있다.</p>
<hr />
<h3 id="heading-3-dijkstras-algorithm-data-structure-example">다익스트라 알고리즘 #3 : 자료구조 (Dijkstra's Algorithm Data Structure Example)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733403615226/b8fd57cd-57ec-4170-a35d-c4eb84d460e1.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-graph-representation-using-adjacency-list"><strong>1. 인접 리스트로 그래프 구현 (Graph Representation Using Adjacency List)</strong></h4>
<p><code>adjacency</code><strong>The fact of being very near, next to, or touching something</strong></p>
<ul>
<li><p>그래프를 <strong>인접 리스트 (Adjacency List)</strong>로 표현한다.</p>
<ul>
<li><p>각 노드는 자신과 연결된 <strong>인접 노드와 가중치 (Adjacent Node and Weight)</strong>를 저장한다.</p>
<ul>
<li><p>노드 1 → [노드 2(8), 노드 3(3)]</p>
</li>
<li><p>노드 2 → [노드 4(4), 노드 5(15)]</p>
</li>
<li><p>노드 3 → [노드 4(13)]</p>
</li>
<li><p>노드 4 → [노드 5(2)]</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733403621196/cc00ea8f-4b30-4075-9641-ffe85f59bbe6.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>파이썬에선 리스트이지만 일반적으론 배열이다.</p>
</li>
<li><h4 id="heading-2-initialize-distance-list-distance-list"><strong>2. 최단 거리 리스트 초기화 (Initialize Distance List): 최단 거리 리스트 (Distance List)</strong>를 초기화한다.</h4>
<ul>
<li><p>시작 노드(1번)의 거리는 <strong>0</strong>으로 설정한다. <code>D[1] = 0</code></p>
</li>
<li><p>나머지 노드의 거리는 <strong>무한대 (Infinity)</strong>로 초기화한다. ∞</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733403625541/4ed10dfd-fee3-4364-9e71-65de81936c3d.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-3-select-node-with-minimum-distance"><strong>3. 가장 작은 거리 노드 선택 (Select Node with Minimum Distance)</strong></h4>
<ul>
<li><p>초기화된 리스트에서 <strong>가장 작은 값</strong>을 가진 노드를 선택한다.</p>
<ul>
<li><p>첫 번째로 선택되는 노드는 항상 시작점(1번)이다.</p>
</li>
<li><p>선택된 노드(1번)와 연결된 인접 노드(2번, 3번)의 거리를 <strong>업데이트 (Update)</strong>한다.</p>
<ul>
<li><p>2번 노드: D [2] = min⁡ (∞, 0 + 8) =8</p>
</li>
<li><p>3번 노드: D [3] = min ⁡(∞, 0 + 3) = 3</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733403633148/468193d2-3cd8-4cbb-bc09-74b697cab9bd.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-4-relaxation"><strong>4. 거리 완화 (Relaxation):</strong> 선택된 노드와 연결된 노드들의 거리를 계속 업데이트한다.</h4>
<ul>
<li><p>인접 리스트를 탐색하며, 새로운 경로가 더 짧다면 거리 값을 갱신한다.</p>
</li>
<li><p><strong>완화 과정:</strong></p>
<ul>
<li><p>선택된 노드의 거리 값 + 간선의 가중치가 기존 거리 값보다 작다면 갱신.</p>
</li>
<li><p>방문한 노드는 다시 선택되지 않도록 방문 리스트를 관리합니다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733403639761/35fcade8-7a05-44ab-bb4d-bba1ad58383f.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-5-repeat-until-all-nodes-are-visited"><strong>5. 반복 (Repeat Until All Nodes Are Visited)</strong></h4>
<ul>
<li><p>위 과정을 모든 노드를 방문할 때까지 반복한다.</p>
<ul>
<li><p>1번 → 3번 → 2번 → 4번 → 5번 순서로 진행된다.</p>
</li>
<li><p>각 단계에서 선택된 노드와 인접한 노드의 거리 값을 업데이트한다.</p>
</li>
<li><p>모든 노드를 방문하면 알고리즘이 종료된다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong><mark>💡중요 포인트 (Key Points)</mark></strong></p>
<ol>
<li><p><strong>인접 리스트 사용:</strong> 그래프를 효율적으로 표현하고 탐색한다. (리스트 = 배열)</p>
</li>
<li><p><strong>거리 완화:</strong> 최단 거리 값을 갱신하며 최적의 경로를 찾는다.</p>
</li>
<li><p><strong>반복:</strong> 모든 노드를 방문할 때까지 최소 거리 노드를 반복적으로 선택하고 업데이트한다.</p>
</li>
</ol>
<hr />
<h3 id="heading-4-detailed-pseudocode-of-dijkstras-algorithm">다익스트라 알고리즘 #4 : 상세 의사 코드 <strong>(Detailed Pseudocode of Dijkstra's Algorithm)</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733405028960/ac54e5b3-2077-46ea-8f78-712d0fcdbe7b.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-key-data-structures"><strong>1. 주요 데이터 구조 (Key Data Structures):</strong></h4>
<ol>
<li><p><strong>노드와 엣지 (Nodes and Edges):</strong></p>
<ul>
<li><p>V: <strong>노드 개수 (Number of Nodes)</strong></p>
</li>
<li><p>E: <strong>엣지 개수 (Number of Edges)</strong></p>
</li>
<li><p>K: <strong>출발 노드 (Start Node)</strong></p>
</li>
</ul>
</li>
<li><p><strong>거리 리스트 (Distance List):</strong></p>
<ul>
<li><p>각 노드로부터의 <strong>최단 거리 (Shortest Distance)</strong>를 저장.</p>
</li>
<li><p>초기 값은 <strong>무한대 (Infinity)</strong>로 설정된다.</p>
</li>
</ul>
</li>
<li><p><strong>방문 리스트 (Visited List):</strong></p>
<ul>
<li>각 노드가 방문되었는지 확인하는 <strong>체크리스트 (Checklist)</strong>이다.</li>
</ul>
</li>
<li><p><strong>인접 리스트 (Adjacency List):</strong></p>
<ul>
<li><p>각 노드와 연결된 노드와 가중치를 저장한다.</p>
</li>
<li><p>예: 노드 1 → [(노드 2, 가중치 8), (노드 3, 가중치 3)]</p>
</li>
</ul>
</li>
<li><p><strong>우선순위 큐 (Priority Queue):</strong></p>
<ul>
<li><p>노드의 거리 값을 기준으로 <strong>가장 작은 값</strong>을 자동으로 선택해주는 큐.</p>
</li>
<li><p>예: 거리 값이 작은 순서로 데이터를 출력한다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733405035651/ee8d0805-c967-4044-b94d-b1806e4bd601.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-2-algorithm-steps"><strong>2. 알고리즘 실행 과정 (Algorithm Steps):</strong></h4>
<ol>
<li><p><strong>초기화 (Initialization):</strong></p>
<ul>
<li><p><strong>거리 리스트:</strong> 출발 노드의 거리를 0으로 설정, 나머지는 무한대로 초기화.</p>
</li>
<li><p><strong>우선순위 큐:</strong> 출발 노드를 큐에 넣는다.</p>
</li>
<li><p>위에서 설명했듯이 우선선위 큐는 노드의 거리 값을 기준으로 <strong>가장 작은 값</strong>을 자동으로 선택해주는 큐이다. 자동으로 거리가 최소인 노드를 선택하는 것이다. 출발노드의 거리는 0이므로 최소인 노드가 된다.</p>
</li>
</ul>
</li>
<li><p><strong>노드 선택 (Select Node):</strong></p>
<ul>
<li><p>우선순위 큐에서 <strong>가장 작은 거리 값을 가진 노드</strong>를 선택한다.</p>
</li>
<li><p>이미 방문한 노드인지 확인한다.</p>
</li>
</ul>
</li>
<li><p><strong>거리 업데이트 (Distance Update - <mark>Relaxation</mark>):</strong></p>
<ul>
<li><p>선택된 노드와 연결된 <strong>인접 노드들의 거리 값</strong>을 확인한다.</p>
</li>
<li><p>현재 노드의 거리 값 + 엣지 가중치와 기존 거리 값을 비교하여 <strong>더 작은 값</strong>으로 갱신한다.</p>
</li>
</ul>
</li>
<li><p><strong>우선순위 큐 업데이트 (Update Priority Queue):</strong></p>
<ul>
<li>거리 값이 갱신된 인접 노드를 우선순위 큐에 추가한다.</li>
</ul>
</li>
<li><p><strong>반복 (Repeat):</strong> 위 과정을 모든 노드가 방문될 때까지 반복한다.</p>
</li>
<li><p><strong>종료 (Termination):</strong> 모든 노드의 최단 거리 값이 계산되면 알고리즘이 종료된다.</p>
</li>
</ol>
<hr />
<p><strong><mark>💡중요 포인트 (Key Points)</mark></strong></p>
<ol>
<li><p><strong>우선순위 큐 (Priority Queue):</strong> 노드 선택 시 가장 작은 거리 값을 자동으로 제공하므로 효율적이다.</p>
</li>
<li><p><strong>거리 완화 (Relaxation):</strong> 현재 노드와 연결된 노드의 거리를 비교하여, 더 작은 값으로 갱신한다.</p>
</li>
<li><p><strong>시간 복잡도 (Time Complexity):</strong> O (E log ⁡V) (우선순위 큐 사용시)</p>
</li>
</ol>
<hr />
<h2 id="heading-4-bellman-ford-algorithm"><strong>4️⃣</strong>벨만-포드 알고리즘 (Bellman-Ford Algorithm)</h2>
<p>벨만 포드알고리즘 과정에 대해서 살펴본다. 벨만포드는 음의 가중치를 허용한다는 특징이있다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733407007458/524d374e-f11a-4cab-bc38-5672e5ec1661.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-initialization-distance-infinity"><strong>1. 초기화 (Initialization): 시작점 외 모든 정점의 거리 (Distance)</strong>를 <strong>무한대 (Infinity)</strong>로 설정한다.</h4>
<ul>
<li><h4 id="heading-0">시작점의 거리는 0으로 설정한다.</h4>
</li>
<li><p>이 과정은 <strong>다익스트라 알고리즘과 동일</strong>하다.</p>
</li>
</ul>
<hr />
<h4 id="heading-2-relax-all-edges"><strong>2. 모든 간선 완화 (Relax All Edges)</strong></h4>
<p>다익스트라에서는 for문을 활용하여 알고리즘을 최적화 하는 방향이있었다. 하지만 벨만 포드는 음의 가중치를 허용하다 보니 같은 방식으로 진행이 불가능하다. 이유는 최소 비용을 선택 하였는데 음의 가중치가 있다면 그것보다 더 작은 값이 나타나게 되므로 음의 가중치를 허용하지 않는 다익스트라의 특성이 적용되기 때문이다 . 이로인해 모든 간선들을 순서대로 거리를 업데이트 하게 된다. 다익스트라보다 더 많은 완화과정이 필요한 단점이 있다.</p>
<ul>
<li><p>벨만-포드 알고리즘은 <strong>모든 간선을 반복적으로 완화 (Relaxation)</strong>한다. 다익스트라처럼 <strong>가장 작은 값을 가진 노드를 선택하지 않고</strong>, 모든 간선을 하나씩 확인하며 거리 값을 갱신한다</p>
<ul>
<li><p><strong>이유❓</strong></p>
<ul>
<li><p>음의 가중치 (Negative Weight)가 포함된 그래프에서는, 특정 경로를 선택하더라도 더 작은 값이 나올 가능성이 있기 때문이다.</p>
</li>
<li><p>따라서 모든 간선을 탐색하며 거리 값을 업데이트하는 방식으로 작동한다.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>완화 과정 (Relaxation Process):</strong></p>
<ul>
<li><p>각 간선을 탐색하며, 다음을 수행한다.</p>
</li>
<li><p><code>새로운 거리 값 = 현재 거리 + 간선의 가중치</code></p>
</li>
<li><p>만약 새로운 거리 값이 기존 거리 값보다 작다면, 거리 값을 업데이트한다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-3-detect-negative-cycle"><strong>3. 음의 사이클 여부 확인 (Detect Negative Cycle):</strong></h4>
<ul>
<li><p>벨만-포드 알고리즘의 장점은 <strong>음의 사이클 (Negative Cycle)</strong>을 탐지할 수 있다는 점이다.</p>
<ul>
<li><p><strong>음의 사이클이란?</strong> 반복적으로 탐색할수록 비용이 계속 줄어드는 사이클.</p>
<ul>
<li>예: 한 사이클을 돌 때마다 비용이 -10씩 감소.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>탐지 방법:</strong></p>
<ul>
<li><p>모든 간선에 대해 반복적으로 완화 과정을 수행한 뒤,</p>
</li>
<li><p>또다시 거리 값을 업데이트했는데도 비용이 줄어든다면, <strong>음의 사이클이 존재</strong>한다는 뜻이다.</p>
</li>
</ul>
</li>
<li><p><strong>음의 사이클 여부에 따른 결과:</strong></p>
<ul>
<li><p><strong>사이클 있음 (Negative Cycle Exists):</strong> 경로가 무한히 줄어들기 때문에, 최단 경로를 정의할 수 없다.</p>
</li>
<li><p><strong>사이클 없음 (No Negative Cycle):</strong> 지금까지 계산된 최단 거리를 출력하며 알고리즘이 종료된다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p>✅<strong>벨만-포드 알고리즘과 다익스트라 알고리즘 비교 (Comparison)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733407311907/fe9acf0e-b9cc-42fe-8001-bde2f9d25387.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-1-bellman-ford-algorithm-pseudocode">벨만-포드 알고리즘 #1: 의사코드 (Bellman-Ford Algorithm Pseudocode)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733407349148/40b99782-9777-48b3-a5ef-c56dcee22b26.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-initialization-2"><strong>1. 초기화 (Initialization):</strong></h4>
<ul>
<li><p>시작점에서 출발하기 위해, 모든 정점의 거리를 <strong>무한대 (Infinity)</strong>로 설정한다.</p>
</li>
<li><p><strong>시작점의 거리 (Start Node Distance)</strong>는 0으로 초기화한다: <code>r.dist ← 0</code></p>
</li>
<li><p>이 과정은 <strong>다익스트라 알고리즘과 동일</strong>하다.</p>
</li>
</ul>
<hr />
<h4 id="heading-2-vertex-loop"><strong>2. 정점 반복 (Vertex Loop):</strong></h4>
<ul>
<li><p>정점의 수가 V라면, <strong>V−1 반복</strong>한다 <code>for i ← 1 to ∣V∣−1</code></p>
<ul>
<li><strong>이유:</strong> 그래프에 정점이 4개라면, 최단 경로를 완전히 계산하기 위해 최대 3번의 거리 업데이트가 필요하기 때문이다.</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-3-relaxation-of-edges"><strong>3. 간선 완화 (Relaxation of Edges):</strong></h4>
<ul>
<li><p>각 정점 반복 내부에서는 <strong>모든 간선 (Edges)</strong>을 따라가며 <strong>거리 값을 업데이트</strong>한다: <code>for each (u, v) in E</code></p>
</li>
<li><p><strong>완화 조건 (Relaxation Condition):</strong> <code>if (u.dist + wuv &lt; v.dist)</code></p>
<ul>
<li><ul>
<li><p>현재 노드 u를 거쳐 v로 가는 경로가 기존 경로보다 짧다면,</p>
<ul>
<li><p><code>v.dist ← u.dist + wuv</code></p>
</li>
<li><p><code>v.prev ← u</code> (최단 경로의 이전 노드 기록).</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>이 과정은 다익스트라와 비슷하지만, <strong>모든 간선을 탐색</strong>한다는 점에서 차이가 있다. (for each)</p>
</li>
</ul>
<hr />
<h4 id="heading-4-negative-cycle-detection"><strong>4. 음의 사이클 탐지 (Negative Cycle Detection):</strong></h4>
<ul>
<li><p>정점 반복이 끝난 후, <strong>추가로 한 번 더 모든 간선을 탐색</strong>한다.</p>
</li>
<li><p>만약 다음 조건이 참이라면, <strong>음의 사이클 (Negative Cycle)</strong>이 존재한다는 뜻이다:</p>
<ul>
<li><p><code>if (u.dist + wuv &lt; v.dist)</code> 이미 계산이 끝난 상태에서 더 작은 값이 발견되었다면, 이는 <strong>음의 사이클</strong>로 인해 발생한 것이다.</p>
<ul>
<li>출력: "음의 사이클 발견, 해 없음 (Negative Cycle Detected, No Solution)".</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-5-termination-1"><strong>5. 알고리즘 종료 (Termination):</strong></h4>
<ul>
<li>음의 사이클이 없는 경우, <strong>계산된 최단 거리 값을 출력</strong>하며 종료한다.</li>
</ul>
<hr />
<h3 id="heading-2-bellman-ford-algorithm-in-action">벨만-포드 알고리즘 #2: 작동 예제 (Bellman-Ford Algorithm in Action)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733407812304/681e7697-ebd9-4589-b9cb-9345083c4ccc.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-initial-state-0"><strong>1. 초기 상태 (Initial State): 노드 0</strong>이 시작점이다.</h4>
<ul>
<li>시작점의 거리는 <strong>0</strong>, 나머지 노드들의 거리는 <strong>무한대 (Infinity)</strong>로 초기화된다.</li>
</ul>
<hr />
<h4 id="heading-2-first-round-0"><strong>2. 첫 번째 라운드 (First Round): 0번 노드와 연결된 모든 노드</strong>를 업데이트한다.</h4>
<ul>
<li><p><code>0 → 8: 거리 값은 0 + 8 = 8</code> <code>0 → 9: 거리 값은 0 + 9 = 9</code> <code>0 → 11: 거리 값은 0 + 11 = 11</code></p>
</li>
<li><p>결과: D[8] = 8, D[9] = 9, D[11] = 11</p>
</li>
</ul>
<hr />
<h4 id="heading-3-second-round"><strong>3. 두 번째 라운드 (Second Round): 모든 간선을 따라 거리 값을 업데이트 한다.</strong></h4>
<ul>
<li><ul>
<li><p>9 → 10: 기존 값(∞)보다 새로운 값 9 + 1 = 10 으로 업데이트 되었다. 그 뒤에</p>
<ul>
<li><p>9 → -15: 기존 값(8)보다 작은 9−15 =−6 으로 업데이트 되었다.</p>
</li>
<li><p>9 → 11: 9 + 3 = 12가 되어 더 큰값이므로 작은값인 11이 계속 유지된다.</p>
</li>
<li><p>11 → 19: 기존 값(∞)보다 새로운 값 11 + 8 = 19 두개의 노드가 업데이트 되었다.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>결과: D[−6], D[10], D[19] 가 새롭게 계산된다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733407818142/82adc056-ef4f-4555-93af-3dd90fa028a8.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-4-iterations"><strong>4. 반복 과정 (Iterations):</strong></h4>
<ul>
<li><p><strong>다음 라운드들:</strong></p>
<ul>
<li><p><strong>모든 간선</strong>을 탐색하며 거리 값을 반복적으로 갱신한다.</p>
</li>
<li><p>벨만-포드는 정점의 수(V)가 5라면 V−1 = 4번 반복.</p>
</li>
<li><p>각 라운드에서 모든 간선의 거리 값을 계산하고 일률적으로 업데이트한다.</p>
</li>
<li><p>중요한 점은 다익스트라에서는 가장 작은 값을 선택해서 그것에 인접한 노드를 업데이트하는 반면 벨만포트는 간선들을 모두 일률적으로 업데이트한다는 점이다. 실질적으로 순서가 중요하진 않고 의사코드의 과정이 중요하다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733407823345/9ce82313-0ea6-4d03-9b28-9428c812ca79.png" alt class="image--center mx-auto" /></p>
<p><strong>5. 최종 결과 (Final Result):</strong></p>
<ul>
<li><p>각 노드로의 <strong>최단 거리</strong>가 계산된다.</p>
</li>
<li><p>벨만-포드는 <strong>모든 간선을 탐색</strong>하며 최단 경로를 확인하므로, 특정 순서에 의존하지 않는다.</p>
</li>
</ul>
<hr />
<p><strong>✅ 벨만-포드와 다익스트라의 차이 (Difference from Dijkstra):</strong></p>
<p><strong>방식 (Method):</strong></p>
<ul>
<li><p>다익스트라는 가장 작은 값을 가진 노드부터 업데이트한다.</p>
</li>
<li><p>벨만-포드는 모든 간선을 반복적으로 탐색하며 업데이트한다.</p>
</li>
</ul>
<p><strong>음의 가중치 (Negative Weights):</strong></p>
<ul>
<li><p>다익스트라는 음의 가중치를 허용하지 않는다.</p>
</li>
<li><p>벨만-포드는 음의 가중치를 허용하며 음의 사이클도 탐지할 수 있다.</p>
</li>
</ul>
<hr />
<h3 id="heading-3-data-structure-in-bellman-ford-algorithms">벨만-포드 알고리즘 #3: 자료구조( Data Structure in Bellman-Ford Algorithms)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733410038304/bd5da629-d063-4019-82a4-647c30e3e436.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-initialization-edge"><strong>1. 초기화 (Initialization): 벨만-포드 알고리즘</strong>은 간선(Edge)을 중심으로 작동한다.</h4>
<ul>
<li><p>간선의 정보를 담기 위해 <strong>엣지 리스트 (Edge List)</strong>를 사용한다.</p>
<ul>
<li>엣지 리스트는 다음과 같이 구성된다: <strong>출발 노드 (Start Node),종료 노드 (End Node), 가중치 (Weight)</strong></li>
</ul>
</li>
<li><p><strong>최단 거리 리스트 (Shortest Distance List)</strong>:</p>
<ul>
<li><p>시작점(0번 노드)의 거리는 <strong>0</strong>으로 설정.</p>
</li>
<li><p>나머지 모든 노드의 거리는 <strong>무한대 (Infinity)</strong>로 초기화.</p>
</li>
<li><p>초기화된 리스트는 알고리즘의 첫 번째 단계가 된다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733410044835/ebba20c3-cfd2-4df0-a6b0-845836b18211.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-2-iterative-updates"><strong>2. 반복적 업데이트 (Iterative Updates): 모든 간선을 확인</strong>하며, 각 간선을 따라 <strong>거리 값을 업데이트</strong>한다.</h4>
<ul>
<li><p><strong>업데이트 횟수 (Number of Updates):</strong></p>
<ul>
<li><p>정점의 수(V)가 5라면, V − 1 = 4번 반복.</p>
</li>
<li><p>이유: 정점 간 최단 경로는 최대 V−1개의 간선을 거칠 수 있기 때문이다.</p>
</li>
</ul>
</li>
<li><p><strong>업데이트 과정:</strong></p>
<ul>
<li><p>첫 번째 라운드: 시작점(0)과 연결된 2, 3번 노드의 비용이 갱신된다.</p>
</li>
<li><p>두 번째 라운드: 4,5 번 노드와 연결된 노드들의 비용이 갱신된다.</p>
</li>
<li><p>이렇게 <strong>순차적으로 모든 간선을 탐색</strong>하며, 최단 거리 리스트를 갱신한다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733410051979/54bad224-a128-43eb-8b71-c4a8e8673fa7.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-3-negative-cycle-detection"><strong>3. 음수 사이클 탐지 (Negative Cycle Detection):</strong></h4>
<ul>
<li><p><strong>음수 사이클 (Negative Cycle):</strong> 특정 경로를 반복해서 탐색할 때, 비용이 계속 줄어드는 경우</p>
</li>
<li><p><strong>탐지 방법:</strong></p>
<ul>
<li><p>V−1번 반복 후, 한 번 더 모든 간선을 탐색한다.</p>
</li>
<li><p>만약 추가 업데이트가 발생하면, <strong>음수 사이클이 존재</strong>한다는 뜻이다.</p>
</li>
<li><p>따라서 아래와 같이 결과를 출력한다.</p>
<ul>
<li>"음수 사이클 존재 (Negative Cycle Exists)"</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-4-bellman-ford-algorithm-python-code-example">벨만-포드 알고리즘 #4: 파이썬 코드 <strong>(Bellman-Ford Algorithm Python Code Example)</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733410871451/0ab87fa7-3b24-4a92-a908-d828524f973d.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1-initialize-data"><strong>1. 데이터 초기화 (Initialize Data):</strong></h4>
<ul>
<li><p><strong>노드(N)와 간선(M) 읽기</strong>: 그래프의 노드 개수 N과 간선 개수 M을 입력받는다.</p>
<pre><code class="lang-sql">  N, M = map(int, input().split())
</code></pre>
</li>
<li><p><strong>간선 리스트와 거리 리스트 선언 (Edge List and Distance Array):</strong></p>
<pre><code class="lang-sql">  edges = []  
  distance = [sys.maxsize] * (N + 1)
</code></pre>
<ul>
<li><p><code>edges</code>: 간선 정보를 저장하는 리스트.</p>
</li>
<li><p><code>distance</code>: 각 노드까지의 최단 거리를 저장하는 리스트.</p>
<ul>
<li><strong>sys.maxsize</strong>로 초기화하여 무한대 값을 나타낸다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>엣지 데이터 입력받기 (Store Edge Data):</strong></p>
<pre><code class="lang-sql">  for _ in range(M):  
      <span class="hljs-keyword">start</span>, <span class="hljs-keyword">end</span>, <span class="hljs-built_in">time</span> = <span class="hljs-keyword">map</span>(<span class="hljs-built_in">int</span>, <span class="hljs-keyword">input</span>().split())  
      edges.append((<span class="hljs-keyword">start</span>, <span class="hljs-keyword">end</span>, <span class="hljs-built_in">time</span>))
</code></pre>
<p>  각 간선의 출발 노드, 도착 노드, 가중치를 입력받아 <code>edges</code> 리스트에 저장한다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733410880159/ba675971-5e49-4285-a64f-19ce0afa06f7.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-3-negative-cycle-detection-1"><strong>3. 음수 사이클 여부 확인 (Negative Cycle Detection):</strong></h4>
<pre><code class="lang-python">pythonCopy codemCycle = <span class="hljs-literal">False</span>  
<span class="hljs-keyword">for</span> start, end, time <span class="hljs-keyword">in</span> edges:  
    <span class="hljs-keyword">if</span> distance[start] != sys.maxsize <span class="hljs-keyword">and</span> distance[end] &gt; distance[start] + time:  
        mCycle = <span class="hljs-literal">True</span>
</code></pre>
<p>반복 이후에도 <strong>값이 더 작아지면 음수 사이클(Negative Cycle)</strong>이 존재한다는 뜻이다.</p>
<h4 id="heading-4-output-results"><strong>4. 결과 출력 (Output Results):</strong></h4>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> mCycle:  
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">2</span>, N + <span class="hljs-number">1</span>):  
        <span class="hljs-keyword">if</span> distance[i] != sys.maxsize:  
            print(distance[i])  
        <span class="hljs-keyword">else</span>:  
            print(<span class="hljs-number">-1</span>)  
<span class="hljs-keyword">else</span>:  
    print(<span class="hljs-number">-1</span>)
</code></pre>
<ul>
<li><p>음수 사이클이 없으면, 각 노드까지의 최단 거리를 출력한다.</p>
</li>
<li><p>음수 사이클이 있으면, <code>-1</code>을 출력한다.</p>
</li>
</ul>
<hr />
<h2 id="heading-5-all-pairs-shortest-path">5️⃣모든 쌍 최단 경로(All-Pairs Shortest Path)</h2>
<h4 id="heading-all-pairs-shortest-path"><strong>✅ 모든 쌍 최단 경로 소개 (All-Pairs Shortest Path):</strong></h4>
<ul>
<li><p>이전에는 <strong>단일 시작점 최단 경로 (Single-Source Shortest Path)</strong>를 배웠다. 단일 시작점 최단 경로란 특정 <strong>시작점</strong>에서 다른 모든 노드까지의 최단 경로를 구하는 방법이다.</p>
</li>
<li><p>이제는 <strong>모든 쌍 (All-Pairs)</strong>을 대상으로 하는 경로이다.</p>
<ul>
<li><p>그래프에 있는 <strong>모든 노드 쌍 간</strong>의 최단 경로를 계산한다.</p>
</li>
<li><p>예: A → B, A → C, B → C 등 모든 경로</p>
</li>
</ul>
</li>
<li><p><strong>언제 사용하나요? (When to Use):</strong></p>
<ul>
<li><p><strong>네비게이션 (Navigation):</strong> 도시 간의 최단 경로를 구하는 경우.</p>
</li>
<li><p><strong>네트워크 통신 (Network Communication):</strong> 데이터가 여러 서버 간에 전송될 때 가장 빠른 경로를 계산하는 경우</p>
</li>
</ul>
</li>
<li><p><strong>특징 (Key Characteristics):</strong></p>
<ul>
<li><p>이 알고리즘은 모든 노드 쌍 간의 최단 경로를 구하므로 계산량이 많다.</p>
</li>
<li><p><strong>복잡도가 높다 (High Complexity)</strong>: 계산량이 커서 시간 소모가 크다.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-representative-algorithm"><strong>✅ 대표 알고리즘 (Representative Algorithm):</strong></h4>
<ul>
<li><p><strong>플로이드-워셜 알고리즘 (Floyd-Warshall Algorithm):</strong></p>
<ul>
<li><p>모든 쌍 간의 최단 경로를 구하는 대표적인 알고리즘이다.</p>
</li>
<li><p><strong>동적 프로그래밍 (Dynamic Programming)</strong> 방식을 사용한다.</p>
</li>
<li><p>그래프에 음수 가중치가 있어도 동작 가능하지만, 음수 사이클은 허용되지 않는다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-1-dynamic-programming-for-all-pairs-shortest-path">모든 쌍 최단 경로 #1: 동적 프로그래밍 적용(Dynamic Programming for All-Pairs Shortest Path)</h3>
<p><strong>✅ 동적 프로그래밍 (Dynamic Programming) 적용:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733414626947/cef781e6-39a0-4ee6-8831-120bed8d7236.png" alt class="image--center mx-auto" /></p>
<p>즉, 경로를 점진적으로 늘려가며 계산한다.</p>
<ul>
<li><p><strong>최소 간선</strong>부터 시작해서 처음에는 노드 간의 직접 연결된 거리만 확인한다. m=1</p>
</li>
<li><p>m &gt; 1일 때, 추가 경로를 통해 더 짧아질 수 있는지를 확인한다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733414693351/2a8163c0-5f83-4d70-844f-e394907487e6.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>m = 1: 노드 vi 와 vj 간의 직접 거리.</p>
</li>
<li><p>m &gt; 1: 중간 노드 k를 통해 vi → k 로 이동할 때 거리</p>
</li>
</ul>
<hr />
<h3 id="heading-2-simple-shortest-path-algorithm">모든 쌍 최단 경로 #2: 단순 최단 경로 알고리즘(Simple Shortest Path Algorithm)</h3>
<p><strong><mark>💡요약 (Summary)</mark></strong></p>
<ol>
<li><p><strong>목표:</strong> 모든 정점 쌍 간의 최단 경로 계산.</p>
</li>
<li><p><strong>방법:</strong> 모든 정점에서 경로를 확장하면서 거리 비용을 업데이트.</p>
</li>
<li><p><strong>문제:</strong> 시간 복잡도가 O(n4)로 비효율적.</p>
</li>
<li><p><strong>개선:</strong> 플로이드-워샬 알고리즘으로 최적화.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733415138910/a5619083-2b96-4617-b5ea-2d213f220568.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-kiriniug64uo7iicioy1noulqcdqsr3rozwg7jwm6rog66as7kayoioq"><strong>✅ 단순 최단 경로 알고리즘:</strong></h4>
<ul>
<li><p>그래프에서 모든 정점 쌍 간의 최단 경로를 계산한다.</p>
</li>
<li><p>경로를 점진적으로 확장하면서 최단 거리 비용을 업데이트한다.</p>
</li>
</ul>
<p>✅<strong>동작 과정 (How It Works):</strong></p>
<ol>
<li><p><strong>초기화 (Initialization):</strong></p>
<ul>
<li><p>모든 정점 쌍의 가중치를 초기화한다. (dij = wij)</p>
</li>
<li><p>직접 연결된 거리를 사용하거나 연결이 없는 경우 무한대 (∞)로 설정한다.</p>
</li>
</ul>
</li>
<li><p><strong>모든 정점 탐색 (All Nodes Exploration):</strong></p>
<ul>
<li><p>정점 k를 하나씩 추가하면서 i → k → j 로 이동하는 경로를 확인한다.</p>
</li>
<li><p>기존 거리 dij 와 dik + wkj​를 비교하여 더 짧은 값을 업데이트한다.</p>
</li>
</ul>
</li>
<li><p><strong>점화식 (Recurrence Formula):</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733415371672/f76ad4f9-ccfe-4497-a14b-f6f2ba19eb5b.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>dij: 정점 i에서 j로 가는 최단 거리.</p>
</li>
<li><p>k: 중간에 추가된 노드</p>
</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-4pyficoq66y47kcc7kcqiouwjydqsjzshkag67cp67kvoioq">✅ <strong>문제점 및 개선 방법:</strong></h4>
<ul>
<li><p><strong>단점 (Drawbacks): 성능이 느림 (Slow Performance)</strong></p>
<ul>
<li>이중 루프와 중첩된 계산으로 인해 시간 복잡도가 O(n4)로 매우 비효율적이다.</li>
</ul>
</li>
<li><p><strong>개선 방법 (Optimized Approach): 플로이드-워샬 알고리즘 (Floyd-Warshall Algorithm)</strong></p>
<ul>
<li>중간 정점 집합을 활용하여 계산을 최적화하고 수행 시간을 단축할 수있다.</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-2-floyd-warshall-algorithm-with-dynamic-programming">모든 쌍 최단 경로 #2: <strong>플로이드-워샬 알고리즘의 동적 프로그래밍 적용 (Floyd-Warshall Algorithm with Dynamic Programming)</strong></h3>
<p>플로이드-워샬 알고리즘은 그래프의 <strong><mark>모든 정점 쌍 간</mark></strong> 최단 경로를 효율적으로 계산하는 알고리즘이다.</p>
<p><strong>✅ 목적 (Goal):</strong> 모든 정점 vi에서 vj로 가는 최단 경로를 찾는 것.</p>
<p><strong>✅ 특징 (Feature):</strong> 이전 단계에서 계산한 최단 거리 값을 재활용하여 시간 복잡도를 줄인다.</p>
<p><strong>✅DP 테이블 정의 (DP Table Definition)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733415593533/e1a6039d-7367-49fe-b678-434a6d831ff5.png" alt class="image--center mx-auto" /></p>
<ul>
<li><ul>
<li><p>n개의 간선이 아닌 vertex set을 명확하게 지정한다. 정점 집합 {v1,v2,⋯ ,vk}만 거쳐 vi에서 vj로 가는 최단 거리이다.</p>
<ul>
<li>초기값으로 직접 연결된 거리 wij를 사용하거나 연결이 없으면 무한대 (∞)로 설정한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>✅점화식 (Recurrence Formula):</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733415701894/56d8e272-52f9-40fe-a345-c572c442bdaf.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>기본 경우 (Base Case):</strong> k = 1 일 때</p>
</li>
<li><p><strong>일반 경우 (General Case):</strong> k ≥ 1 일 때</p>
<ul>
<li><p><strong>해석 (Interpretation):</strong></p>
<ul>
<li><p><code>dijk−1​</code>: vk ​를 거치지 않고 vi ​에서 vj ​로 가는 최단 거리.</p>
</li>
<li><p><code>dikk−1 + dkjk−1</code> : vk를 거쳐가는 경로의 거리.</p>
</li>
<li><p>이 둘 중 더 짧은 값을 선택한다.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>정점 집합 활용 (Vertex Set Utilization):</strong></p>
<ul>
<li><p>정점 집합을 점진적으로 확장하며 최단 거리를 업데이트한다.</p>
</li>
<li><p>불필요한 계산을 줄여 효율성을 높일 수 있다.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-kirinixso7zsmpqg7yq57keviouwjydsnqxsoja6kio"><strong>✅주요 특징 및 장점:</strong></h4>
<ul>
<li><p><strong>효율성 향상 (Improved Efficiency):</strong> 이전의 단순 알고리즘보다 O(n4)에서 n³ 로 시간 복잡도로 최적화되었다.</p>
</li>
<li><p><strong>재활용 (Reusability):</strong> 이전 단계에서 계산된 거리 값을 활용하여 중복 계산을 줄인다.</p>
</li>
</ul>
<hr />
<h3 id="heading-3-understanding-the-recurrence-formula-in-floyd-warshall-algorithm">모든 쌍 최단 경로 #3: 플로이드-워샬 알고리즘의 점화식 이해 (Understanding the Recurrence Formula in Floyd-Warshall Algorithm)</h3>
<p><strong><mark>핵심 아이디어:</mark></strong> 플로이드-워샬 알고리즘은 모든 정점 쌍 간의 최단 경로를 찾기 위해 점화식을 사용할 수 있다. 특정 정점 k를 경유할 때와 경유하지 않을 때의 최단 거리를 비교하여 더 짧은 값을 선택한다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733414956730/c47b17d6-8a5b-41c3-b2f2-f2a0ce45d413.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-kiriniug7kcq7zmu7iudio2vtoyentoqkg"><strong>✅ 점화식 해석:</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733416064969/804123ce-2936-4d25-99ba-47403b655ec2.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>기존 경로 ( i</strong> ➡️ <strong>j</strong> <strong>)</strong></p>
<ul>
<li>i에서 j까지 k를 경유하지 않은 상태에서의 최단 거리이다.</li>
</ul>
</li>
<li><p><strong>경유 경로 ( i ➡️ k ➡️ j)</strong></p>
<ul>
<li><p>i에서 k를 거쳐 j로 가는 경로의 거리이다.</p>
</li>
<li><p>이 경로는 k−1 단계까지의 계산된 최단 거리를 사용한다.</p>
</li>
</ul>
</li>
<li><p><strong>최종 선택:</strong></p>
<ul>
<li><p>k를 경유하지 않는 기존 경로와 k를 경유하는 새로운 경로를 비교한다.</p>
</li>
<li><p>위 두 값 중 더 작은 값을 선택하여 최단 거리를 업데이트하게 된다.</p>
</li>
<li><p>O(n3)로 효율적이다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-all-pairs-shortest-path-3-understanding-the-pseudocode-in-floyd-warshall-algorithm">All-Pairs Shortest Path #3: 플로이드-워샬 알고리즘의 의사코드 (Understanding the Pseudocode in Floyd-Warshall Algorithm)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733459615544/6d7ab511-a73c-4690-9f65-4e15c3b3c798.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-kirinixslyzqs6drpqzsppgg6rws7kgwioyepouqhsoq"><strong>✅알고리즘 구조 설명</strong></h4>
<ol>
<li><p><strong>초기화 (Initialization):</strong></p>
<ul>
<li><p>그래프의 각 간선 가중치를 <strong>초기 거리 값</strong>으로 설정한다. <code>dij = wij</code></p>
</li>
<li><p>정점 i와 j가 직접 연결되어 있지 않은 경우, 초기값을 무한대로 설정한다. <code>∞</code></p>
</li>
<li><p><code>간선(Edge)</code> : 노드와 노드를 연결하는 선</p>
</li>
</ul>
</li>
<li><p><strong>중간 정점 추가 (Adding Intermediate Vertices):</strong></p>
<ul>
<li><code>정점(Vertex)</code> : <strong>각 노드</strong>를 뜻한다.</li>
</ul>
</li>
</ol>
<ul>
<li><p>바깥쪽 <strong>for문 (k)</strong>: 중간에 포함할 정점의 범위를 1에서 n−1지 확장한다. <code>for k ← 1 to n-1</code></p>
</li>
<li><p>중간 <strong>for문 (i)</strong>: 모든 시작 정점 i를 확인한다.</p>
</li>
<li><p>안쪽 <strong>for문 (j)</strong>: 시작 정점 i에서 도착 정점 j로 가는 최단 거리를 점화식으로 업데이트한다.</p>
</li>
</ul>
<ol start="3">
<li><p><strong>점화식 (Recurrence Formula):</strong></p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733459865223/d0d3e1d3-e243-400a-8f64-50334d33f650.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>k를 포함하지 않은 경로와 k를 포함한 경로의 거리를 비교한다.</p>
</li>
<li><p>작은 값을 선택하여 최단 경로를 갱신한다.</p>
</li>
</ul>
</li>
<li><p><strong>결과:</strong> 알고리즘이 종료되면, dij에는 i에서 j까지의 최단 경로가 저장된다.</p>
</li>
</ol>
<hr />
<h3 id="heading-all-pairs-shortest-path-4-floyd-warshall-data-structure-example">All-Pairs Shortest Path #4: 플로이드-워샬 알고리즘의 자료구조 순 (Floyd-Warshall Data Structure Example)</h3>
<p>1️⃣ <strong>초기화 (Initialization)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733459911631/18af7fa9-02dd-49ee-8111-3577e25ad046.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>우선 그래프의 각 노드 쌍에 대해 최단 거리 리스트를 초기화한다.</p>
</li>
<li><p>자기 자신으로 가는 거리는 0으로 설정하고, 나머지 경로는 무한대(∞)로 설정한다.</p>
</li>
</ul>
<hr />
<p>2️⃣<strong>그래프 데이터 저장 (Store Graph Data)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733459918068/e7130c0a-1bc8-4c29-a1d6-5f1c31f6830e.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>초기 리스트는 가중치로 초기화 된다. 그래프의 경로 정보를 리스트에 저장한다.</p>
</li>
<li><p>예를 들어, 1에서 2로 가는 경로의 가중치가 8이라면 D[1][2] = 8로 설정한다.</p>
</li>
<li><p>D[2][4] = -4에서 볼 수 있듯이 음수 가중치도 처리할 수 있는 장점이 있다.</p>
</li>
</ul>
<hr />
<p><strong>3️⃣점화식 업데이트 (Update with Recurrence Relation)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733459923663/82e58eca-f28c-4008-a4ec-6c52574eb917.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>모든 경로를 탐색하며, 중간 노드(K)를 거쳤을 때 더 짧은 경로가 있는지 확인한다.</p>
</li>
<li><p>점화식: <strong>D[S][E] = Math.min(D[S][E], D[S][K] + D[K][E])</strong></p>
</li>
<li><p>이 과정에서 <strong>D[S][E]와 D[S][K] + D[K][E] 중에서</strong> 더 작은 값을 리스트를 업데이트한다.</p>
</li>
</ul>
<hr />
<p>4️⃣<strong>결과 출력 (Output the Final List)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733459927630/e0e020f7-7620-45c2-b5ac-048a89aebe71.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>모든 경로를 반복적으로 탐색한 후, 최종적으로 완성된 최단 거리 리스트를 출력한다.</p>
</li>
<li><p>이 리스트는 각 노드 쌍 간의 최단 거리를 포함한다.</p>
</li>
</ul>
<hr />
<p><strong><mark>💡중요한 부분과 요약본</mark></strong></p>
<ul>
<li><p><strong>플로이드-워샬은 모든 정점 쌍의 최단 거리를 구한다. (Floyd-Warshall calculates the shortest path between all pairs of nodes.)</strong></p>
</li>
<li><p><strong>초기화, 그래프 저장, 점화식 업데이트, 최종 결과 출력 순으로 진행한다. (It proceeds in the order of initialization, storing graph data, updating with a recurrence relation, and printing the final result.)</strong></p>
</li>
<li><p><strong>점화식을 이용해 더 짧은 경로를 반복적으로 갱신한다. (Uses a recurrence relation to iteratively update to shorter paths.)</strong></p>
</li>
<li><p>음수 가중치를 허용하지만, 음수 사이클은 처리할 수 없다.</p>
</li>
</ul>
<hr />
<h3 id="heading-all-pairs-shortest-path-5-floyd-warshall-algorithm-in-python">All-Pairs Shortest Path #5: 플로이드-워샬 알고리즘의 파이썬 코드 (Floyd-Warshall Algorithm in Python)</h3>
<p><strong><mark>💡정리:</mark></strong> 첫 번째 이미지는 그래프의 최단 거리를 초기화하고, 플로이드-워샬 알고리즘의 핵심 반복문인 <code>for</code> 루프를 통해 완화 과정을 구현한다. 두 번째 이미지는 최종 결과를 출력하는 부분으로, 각 정점 쌍의 최단 거리를 행렬 형태로 보여주고 있다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733460581690/765f94df-65f4-468e-a686-697b7c93fd7f.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>초기화 (Initialization)</strong></p>
<ul>
<li><p>각 정점에서 자기 자신으로 가는 거리 (<code>distance[i][i]</code>)는 0으로 설정한다.</p>
</li>
<li><p>두 정점 사이에 간선이 있으면 그 가중치로 초기화한다. 만약 간선이 없으면 초기 값으로 <code>무한대 (infinity)</code>를 설정한다.</p>
</li>
<li><p><code>간선(Edge)</code> : 노드와 노드를 연결하는 선</p>
</li>
<li><p><code>정점(Vertex)</code> : 각 노드를 뜻한다.</p>
</li>
</ul>
</li>
<li><p><strong>반복문을 통한 완화 (Relaxation through Loops)</strong>:</p>
<ul>
<li><p>세 개의 <code>for</code> 루프를 사용한다:</p>
<ul>
<li><p>가장 바깥쪽 루프는 중간 경유지 (<code>k</code>)를 반복한다.</p>
</li>
<li><p>그 안쪽 두 개의 루프는 출발 정점 (<code>i</code>)과 도착 정점 (<code>j</code>)의 쌍을 반복한다.</p>
</li>
</ul>
</li>
<li><p>점화식 (<code>distance[i][j] = min(distance[i][j], distance[i][k] + distance[k][j])</code>)을 통해, 현재 계산된 최단 거리와 경유지를 거쳤을 때의 거리를 비교해 더 작은 값으로 업데이트한다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733460586892/b9dec9a8-acd0-4861-9e21-285b7a136e9a.png" alt class="image--center mx-auto" /></p>
<p><strong>결과 출력 (Output)</strong>:</p>
<ul>
<li><p>모든 정점 쌍에 대해 최단 거리 행렬을 출력한다.</p>
</li>
<li><p>만약 두 정점 간 경로가 존재하지 않으면 <code>0</code>으로 출력하게 된다.</p>
</li>
</ul>
<hr />
<h3 id="heading-6-finding-strongly-connected-components-scc">6️⃣강연결 요소 구하기 (Finding Strongly Connected Components, SCC)</h3>
<p>강연결 요소 구하기(Finding Strongly Connected Components)는 그래프 이론과 알고리즘에서 매우 중요한 개념이다. 방향 그래프에서 강하게 연결된 요소는 같은 부분 집합 내의 모든 정점이 해당 집합의 다른 모든 정점으로 방향 간선을 따라 도달할 수 있는 정점들의 부분 집합을 의미한다. 그래프의 SCC를 찾는 것은 그래프의 구조와 연결성을 이해하는 데 중요한 통찰력을 제공하며, 이는 소셜 네트워크 분석, 웹 크롤링, 네트워크 라우팅 등 다양한 분야에 응용될 수 있다. 또한 이 알고리즘은 <strong>Kosaraju's Algorithm</strong>의 핵심 아이디어를 따른다.</p>
<hr />
<p><strong>강연결 요소 (Strongly Connected Components, SCC)</strong>는 유향 그래프에서 특정 조건을 만족하는 노드들의 집합이다.</p>
<h4 id="heading-4pyfioyhsoqxtdogkirqsjxtlzjqsowg7jew6rkw65cciou2gou2hcdqt7jrnpjtliqqkuuegcwg6re4656y7zse7j2yiouqqoutocdsojxsojag7iyn7jeqioumgo2vtcdslphrskntlqxsnlzrozwg7j2064z7zwgioyimcdsnojripqg6rk966gc6rcaioyhtoyero2vmouklcdqsr3smrdrpbwg7j2y6647zwc64uklg">✅ 조건: <strong>강하게 연결된 부분 그래프</strong>란, 그래프의 모든 정점 쌍에 대해 양방향으로 이동할 수 있는 경로가 존재하는 경우를 의미한다.</h4>
<ul>
<li>예를 들어) A → B로 가는 경로가 있고, 동시에 B → A로 가는 경로도 존재한다면, A와 B는 강하게 연결되어 있다고 한다.</li>
</ul>
<p>✅ <strong>SCC 찾기의 목표</strong>: 그래프를 강연결 요소들로 나누어 각 요소의 경로 특성을 분석한다.</p>
<p>✅ <strong>시간 복잡도:</strong> 이 알고리즘은 DFS를 두 번 사용하며, 수행 시간 복잡도는 O(V+E)로 매우 효율적이다.</p>
<hr />
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733465007661/f47a7a64-cc08-434c-a823-2fdcefd931c6.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>첫 번째 그래프</strong>:</p>
<ul>
<li><p>노드 11, 12, 13이 서로 강하게 연결(Strongly connected)되어 있다.</p>
</li>
<li><p>예) 11에서 12로 가는 경로와 12에서 11로 가는 경로가 존재하기 때문에 양방향 연결이 가능하다다.</p>
</li>
</ul>
</li>
<li><p><strong>두 번째 그래프</strong>:</p>
<ul>
<li><p>전체 그래프는 여러 강연결 요소로 나뉜다.</p>
</li>
<li><p><strong>단일 노드의 SCC</strong>: 노드 9와 10은 다른 노드들과 강하게 연결되지 않아 독립적인 요소를 형성한다.</p>
</li>
<li><p><strong>강하게 연결된 서브그래프</strong>:</p>
</li>
<li><p>노드 6, 7, 8은 서로 강하게 연결되어 하나의 요소를 만든다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-1-pseudocode-in-finding-strongly-connected-components-scc">강연결 요소 구하기 #1: 의사코드 (Pseudocode in Finding Strongly Connected Components, SCC)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733465256940/4627fd32-9c0a-4868-abbe-b54a3f599ffb.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>그래프 탐색 (DFS 수행)</strong>:</p>
<ul>
<li><p>그래프 G에서 <strong>DFS(깊이 우선 탐색)</strong>를 수행한다.</p>
</li>
<li><p>각 정점 v의 <strong>완료시간 f[v]</strong>를 계산한다.</p>
<ul>
<li><p>f[v]: DFS 탐색 중 정점 v에서 더 이상 갈 곳이 없을 때 기록되는 시간</p>
</li>
<li><p>이 완료시간은 이후 역방향 그래프에서 탐색 순서를 결정하는 데 중요하다.</p>
</li>
<li><p><code>간선(Edge)</code> : 노드와 노드를 연결하는 선</p>
</li>
<li><p><code>정점(Vertex)</code> : 각 노드를 뜻한다.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>역방향 그래프 생성 (Reverse the Graph)</strong>:</p>
<ul>
<li><p>그래프 G의 모든 간선 방향을 뒤집어 새로운 그래프 Gr를 만든다.</p>
</li>
<li><p>Gr: G의 모든 간선이 반대로 연결된 그래프.</p>
</li>
</ul>
</li>
<li><p><strong>다시 탐색 시작 (DFS on Gr)</strong>:</p>
<ul>
<li><p>Gr에서 다시 DFS를 수행한다.</p>
</li>
<li><p>이번에는 <strong>완료시간 f[v]</strong>가 가장 큰 정점부터 탐색을 시작한다.</p>
<ul>
<li>완료시간이 크다는 것은 G에서 가장 나중에 종료된 정점임을 의미한다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>강연결 요소 반환 (Return SCCs)</strong>:</p>
<ul>
<li><p>Gr에서 탐색을 통해 분리된 트리(서브그래프)를 구한다.</p>
</li>
<li><p>각 트리는 하나의 <strong>강연결 요소 (SCC)</strong>가 된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong><mark>💡 중요한 점 요약 (Key Points Summary)</mark></strong></p>
<ol>
<li><p><strong>DFS 완료시간 (Finish Time)</strong>: DFS 수행 중 완료된 순서를 기준으로 역방향 탐색을 시작한다.</p>
</li>
<li><p><strong>역방향 그래프 (Reverse Graph)</strong>: 원래 그래프의 간선을 반대로 뒤집어 새로운 그래프를 생성한다.</p>
</li>
<li><p><strong>DFS 재탐색</strong>: 완료시간 f[v]이 큰 정점부터 탐색을 시작하여 SCC를 구한다.</p>
</li>
<li><p><strong>결과</strong>: 분리된 트리 형태로 강연결 요소들이 반환된다.</p>
</li>
</ol>
<hr />
<h3 id="heading-2-components-process-in-finding-strongly-connected-components-scc">강연결 요소 구하기 #2: 작동 과정 (Components Process in Finding Strongly Connected Components, SCC)</h3>
<p>1️⃣ <strong>DFS 수행</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733465269195/40099b35-f2b0-44d0-8590-2167c4a7b61c.png" alt class="image--center mx-auto" /></p>
<p>그래프 G에서 각 정점에 대해 <strong>깊이 우선 탐색(DFS, Depth First Search)</strong> 을 수행한다.</p>
<ul>
<li><p>DFS를 통해 각 정점의 <strong>완료 시간</strong> f[v]을 기록한다.</p>
</li>
<li><p>완료된 순서가 1 → 2 → 3 → ... 순으로 설정된다.</p>
</li>
</ul>
<hr />
<p><strong>2️⃣ 간선 방향 뒤집기</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733465274963/05f2029e-719b-4917-8464-a80f0ffc760f.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>그래프의 모든 간선 방향을 뒤집어 <strong>역 그래프(G^R)</strong> 를 만든다.</p>
</li>
<li><p>G^R는 G의 모든 연결 방향을 반대로 한 것이다.</p>
</li>
</ul>
<hr />
<p><strong>3️⃣강연결요소 구하기</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733465281638/4db6af08-f7c1-4739-9183-560739144868.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>GR에서 f[v] 값이 가장 큰 정점부터 시작해 DFS를 다시 수행한다.</p>
</li>
<li><p>한 번의 DFS 탐색으로 묶인 노드들이 하나의 <strong>강연결요소(SCC)</strong> 를 형성한다.</p>
</li>
<li><p>이 과정을 모든 정점이 방문될 때까지 반복한다.</p>
</li>
</ul>
<hr />
<p><strong>4️⃣ 결과 출력</strong></p>
<ul>
<li><p>G^R에서 DFS로 묶인 각 부분 집합이 <strong>강연결요소</strong>이다.</p>
</li>
<li><p>위 이미지에서 강연결요소는 서로 다른 색으로 구분된다.</p>
</li>
</ul>
<hr />
<h2 id="heading-7-a-a-search-algorithm">7️⃣ A* 알고리즘 (A* Search Algorithm)</h2>
<p><strong><mark>💡요약:</mark></strong> A* 탐색 알고리즘은 경로 탐색 및 그래프 순회에서 가장 우수하고 널리 사용되는 기술 중 하나이다. 왜 A 탐색 알고리즘을 사용하는가?라고 묻는다면 A* 탐색 알고리즘은 다른 순회 기법과는 달리 "뇌(brains)"를 가지고 있다. 이는 정말로 똑똑한 알고리즘이라는 의미이며, 이를 통해 다른 전통적인 알고리즘과 차별화된다. 또한 많은 게임과 웹 기반 지도에서 이 알고리즘을 사용하여 매우 효율적으로(근사값으로) 최단 경로를 찾게 된다.</p>
<hr />
<p>✅ <strong>최단경로 문제의 복잡성</strong></p>
<ul>
<li><p>최단경로 구하는 문제는 매우 복잡한 문제로 최적화를 짧은시간에 구하는것은 어렵다.</p>
</li>
<li><p>최단경로를 찾는 문제는 계산량이 매우 많고, 시간이 오래 걸리는 알고리즘이다.</p>
</li>
<li><p>예를 들어, 벨만-포드나 플로이드-워셜 알고리즘은 여러 <strong>중첩된 for문</strong>으로 구성되어 있으며, 수행 시간이 길어질 수 있다.</p>
</li>
</ul>
<p><strong>✅ A 알고리즘의 접근 방식</strong></p>
<ul>
<li><p>네비를 따라 운전하다 보면 좀 이상하다?싶을 때가 있을 것 이다. 특히 이상한 길을 안내할 때 더욱 그렇다. 이렇게 최단 경로를 구하는 것은 매우 어렵기때문에 A* 알고리즘은 <strong>정확한 최단경로를 찾는 대신, 효율성을 높이기 위해 근사값(Heuristic)</strong> 을 사용한다.</p>
</li>
<li><p>특정 정점에서 목표 정점까지의 비용을 예측하는 함수 h(x)를 활용한다.</p>
</li>
<li><p>이 값은 목표에 "얼마나 가까운가"를 추정하며, 정확하지는 않지만 경로 탐색을 더 효율적으로 만든다.</p>
</li>
</ul>
<p>✅ <strong>활용 예시</strong></p>
<ul>
<li><p>네비게이션 시스템에서 최적의 경로를 찾을 때 자주 사용된다.</p>
</li>
<li><p>예를 들어, 도로 상태, 거리, 예상 시간 등을 고려하여 실제 최적의 길을 안내할 때이다.</p>
</li>
<li><p>하지만 휴리스틱 함수 h(x)가 부정확하면, 예상치 못한 "이상한 경로"를 안내할 수도 있다.</p>
</li>
</ul>
<hr />
<p><strong><mark>💡 중요한 부분 (Key Points)</mark></strong></p>
<ul>
<li><p><strong>휴리스틱 함수 h(x)</strong>: 목표 정점까지의 비용을 <strong>추정</strong>하며, 탐색의 효율성을 높이는 핵심.</p>
</li>
<li><p><em>A</em> 알고리즘의 장점: 정확한 비용 계산 없이도 최적 경로에 가까운 결과를 빠르게 제공한다.</p>
</li>
<li><p><strong>단점</strong>: h(x)가 부정확하거나 잘못 정의되면 최적 경로를 보장하지 못한다.</p>
</li>
</ul>
<hr />
<h3 id="heading-a-1-detailed-explanation-of-the-a-search-algorithm">A* 알고리즘 #1: 자세한 설명 (Detailed Explanation of the A * Search Algorithm)</h3>
<p><strong><mark>💡요약: </mark></strong> 네비게이션 시스템은 A* 알고리즘을 활용해 최단경로를 탐색한다.</p>
<ul>
<li><p><strong>최단 거리</strong>를 기준으로 한다면: h(n)은 "직선 거리"를 기준으로 계산된다.</p>
</li>
<li><p><strong>최단 시간</strong>을 기준으로 한다면: h(n)은 "예상 시간"을 기준으로 계산된다. 이처럼 h(n)의 정의가 달라지면 결과도 달라질 수 있다.</p>
</li>
</ul>
<p>A* 알고리즘은 이 두 정보를 결합해, 최단 경로를 찾으면서도 효율적으로 계산하려는 시도이다.</p>
<hr />
<p>✅ <strong>평가 함수 f(n)</strong></p>
<ul>
<li><p>A* 알고리즘은 평가 함수 f(n) = g(n) + h(n)을 사용한다.</p>
</li>
<li><p><strong>g(n): 출발점에서 정점 n까지의 실제 경로 비용</strong></p>
<ul>
<li>예: 지금까지 이동한 거리나 시간.</li>
</ul>
</li>
<li><p><strong>h(n): 정점 n에서 도착점까지의 추정 경로 비용</strong></p>
<ul>
<li><p>정확한 값이 아닌, 도착점까지 얼마나 가까운지 "예상"하는 값입니다.</p>
</li>
<li><p>h(n)은 알고리즘의 성능을 좌우하는 중요한 요소이다.</p>
</li>
</ul>
</li>
</ul>
<p>✅ <strong>작동 과정</strong></p>
<ul>
<li><p>f(n) 값을 기준으로 가장 비용이 적은 정점을 선택해 탐색을 진행한다.</p>
</li>
<li><p>탐색은 다음 두 가지 정보를 기반으로 진행된다.</p>
<ul>
<li><p>지금까지 이동한 거리 g(n)</p>
</li>
<li><p>앞으로 남은 예상 거리 h(n)</p>
</li>
</ul>
</li>
<li><p>최적 경로는 f(n) 값을 최소화하는 경로를 찾는 것이다.</p>
</li>
</ul>
<p>✅ <strong>h(n)의 역할</strong></p>
<ul>
<li><p>h(n)이 정확 할수록 탐색이 효율적이고 빨라진다.</p>
</li>
<li><p>반대로, h(n)이 부정확하면 잘못된 경로로 탐색하거나 효율이 떨어질 수 있다.</p>
</li>
<li><p>예를 들어, 네비게이션 시스템의 A* 알고리즘은 도로 거리나 예상 시간을 h(n)으로 사용한다.</p>
</li>
</ul>
<p>✅ <strong>예시: 네비게이션의 차이점</strong></p>
<ul>
<li><p><strong>네비게이션 앱</strong> 간의 길 안내가 다른 이유는 h(n)을 정의하는 방식이 다르기 때문이다.</p>
<ul>
<li><p>어떤 앱은 <strong>최단 거리</strong>를 기준으로 하고,</p>
</li>
<li><p>다른 앱은 <strong>최단 시간</strong>을 기준으로 h(n)을 정의하기 때문이다.</p>
</li>
</ul>
</li>
<li><p>도로 상황, 교통량, 속도 제한 등을 반영하는 방식이 달라 알고리즘 결과가 조금씩 차이가 나게 된다.</p>
</li>
</ul>
<p>✅ <strong>장점과 단점</strong></p>
<ul>
<li><p><strong>장점</strong>: 실제 경로 비용과 예상 비용을 함께 고려해 더 효율적이고 현실적인 탐색이 가능하다.</p>
</li>
<li><p><strong>단점</strong>: h(n)을 설계하는 것이 까다롭고, 정확도에 따라 성능이 달라지게 된다. 경우에 따라 시간 복잡도가 높아질 수 있다.</p>
</li>
</ul>
<hr />
<h3 id="heading-a-2-pseudo-code-of-a-algorithm">A* 알고리즘 #2: 의사코드 (Pseudo-code of A Algorithm)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733470752264/56924ad4-e6e5-405b-93a1-0f4309b2618d.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>초기화 (Initialization)</strong></p>
<ul>
<li><p>시작 노드의 비용 <code>g(start_node)+h(start_node)</code>를 계산하여 <strong>우선순위 큐 (priority queue)</strong>에 삽입한다.</p>
<ul>
<li><strong>우선순위 큐</strong>는 비용이 가장 작은 노드부터 처리하도록 정렬된 데이터 구조이다. 시작점에서 출발하여 탐색을 시작한다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>탐색 반복 (While loop)</strong></p>
<ul>
<li><p>우선순위 큐가 비어 있지 않은 동안 반복한다.</p>
<ol>
<li><p>큐에서 가장 작은 비용의 노드를 꺼낸다. (<code>node=pq.dequeue</code>)</p>
</li>
<li><p>현재 노드가 목표 노드인지 확인한다.</p>
<ul>
<li><strong>목표 노드라면</strong> 탐색을 종료한다.</li>
</ul>
</li>
<li><p>목표 노드가 아니라면 현재 노드에서 이동 가능한 다음 노드들을 확인한다.</p>
</li>
</ol>
</li>
</ul>
</li>
<li><p><strong>다음 노드 탐색 (Exploring Next Nodes)</strong></p>
<ul>
<li><p><strong>for 루프</strong>를 사용해 현재 노드에서 이동 가능한 모든 이웃 노드를 확인한다.</p>
<ol>
<li><p>각 이웃 노드의 비용을 계산합니다:</p>
<ul>
<li><p><code>f(next_node)=g(node)+cost+h(next_node)</code></p>
</li>
<li><p><code>cost</code>: 현재 노드에서 이웃 노드로 이동하는 비용.</p>
</li>
<li><p><code>h(next_node)</code>: 이웃 노드에서 목표 노드까지의 예상 비용 (휴리스틱).</p>
</li>
</ul>
</li>
<li><p>계산된 비용을 우선순위 큐에 삽입한다 <code>pq.enqueue(...)</code></p>
</li>
</ol>
</li>
<li><p>이 과정을 반복하며 비용이 낮은 경로를 따라 탐색한다.</p>
</li>
</ul>
</li>
<li><p><strong>목표 도달 후 종료 (Termination)</strong></p>
<ul>
<li>목표 노드에 도달하면, 해당 경로의 총 비용을 출력하고 알고리즘을 종료하게 된다.</li>
</ul>
</li>
</ol>
<hr />
<p><strong><mark>💡 주요 코드 설명 (Key Steps)</mark></strong></p>
<ul>
<li><p><strong>우선순위 큐 (Priority Queue)</strong>: 비용이 가장 낮은 노드를 우선적으로 처리해 탐색 효율성을 높인다.</p>
</li>
<li><p><strong>평가 함수</strong>: <code>f(n)=g(n)+h(n)</code>을 결합해 최적의 노드를 선택한다.</p>
</li>
<li><p><strong>휴리스틱 h(n)</strong>: 도착점까지의 예상 비용으로, 정확도가 높을수록 알고리즘 성능이 개선된다.</p>
</li>
</ul>
<hr />
]]></content:encoded></item><item><title><![CDATA[Join Algorithms for Database Optimization]]></title><description><![CDATA[Contents
1️⃣조인 (Overview Of Join)2️⃣조인의 동작 방식(How Join Works)3️⃣Comparison of Join Methods (조인 방식 비교)

Database Joins and Performance Optimization (데이터베이스 조인과 성능 최적화)
지금까지 데이터 베이스의 물리적설계와 구현에 대해서 배웟다. DB의 물리적 설계 부분은 dbms와 굉장히 밀접한 연관이 있다. 저장 구조를 어떻게 설...]]></description><link>https://heesu.tech/join-algorithms-for-database-optimization</link><guid isPermaLink="true">https://heesu.tech/join-algorithms-for-database-optimization</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Thu, 05 Dec 2024 02:39:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733366193276/468bdf65-f05f-4d60-9c93-5a08f8a12818.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Contents</strong></p>
<p><strong>1️⃣</strong>조인 (Overview Of Join)<br /><strong>2️⃣</strong>조인의 동작 방식(How Join Works)<br />3️⃣Comparison of Join Methods (조인 방식 비교)</p>
<hr />
<p><strong>Database Joins and Performance Optimization (데이터베이스 조인과 성능 최적화)</strong></p>
<p>지금까지 데이터 베이스의 물리적설계와 구현에 대해서 배웟다. DB의 물리적 설계 부분은 dbms와 굉장히 밀접한 연관이 있다. 저장 구조를 어떻게 설정할 것인지,인덱스를 어떻게 만들 것인지, 파티션 여부 등 저장 구조와 관련된 부분, 동시성 제어, 락 관리, 트랜잭션은 구현, 구축 부분이다. 이러한 데이터베이스 설계 구축에서 가장 중요시 하는 첫번째 목표는 “반응속도”이다. 사용자가 쿼리를 주었는데 늦게 나온다면 문제가 된다. 잘 가져오는것도 중요하지만 속도가 중요한 부분을 차지한다. 그렇게 하기위해 dbms는 많은 노력을 하고있다. 관계형 데이터 베이스를 테이블의 형태로 저장하는데 이때 가장 안좋은 점은 "조인"을 할 때이다. 하나의 테이블만 보면 데이터가 아무리 많아도 인덱스로 평균적인 퍼포먼스 활용이 가능하다. 혹은 파티션 생성으로도 접근속도를 빠르게 할 수 있다. 그런데 두 개 이상의 테이블을 접근해서 조인으로 가져오는 경우는 사실 많은 노력이 필요하다. 속도 또한 갑자기 느려질 수 있다. 그래서 쿼리 설계시 특히 조인 쿼리 설계시 많은 주의가 요구된다. 조인 과정이 어떻게 동작하는지 머릿속에 그릴 수 있어야 성능 좋은 쿼리를 생성할 수 있게 된다. 이번 시간은 조인을 좀 더 정리하는 시간을 갖을 것이다.</p>
<h2 id="heading-1-overview-of-join"><strong>1️⃣</strong>조인 (Overview Of Join)</h2>
<p><strong><mark>💡요약: </mark></strong> 관계형 데이터베이스는 테이블의 형태로 데이터베이스를 분리해서 저장한다. 이 과정을 정규화(normalization) 라고 하는데 이를 통해 중복을 최소화하는 relation으로 분리하게 된다. 사용자가 원하는 것은 분리되서 저장하는 것도 그렇고 데이터를 모아서 보는 것이다. 이를 위해 데이터를 결합하는 조인 과정을 통해 데이터를 가져와야 하기 때문에 조인 과정이 필요하게 된다. 조인의 종류는 크로스 조인, 내부조인, 외부조인이 있는데 주로 크로스조인, 내부조인을 사용한다.</p>
<h4 id="heading-definition-of-joins"><strong>✅ 조인의 정의</strong> (Definition of Joins)</h4>
<p><strong>조인은 두 개 이상의 테이블을 묶어 하나의 결과 집합으로 만드는 것</strong>(Combining Two or More Tables into a Single Result Set)을 의미한다. 예를 들어)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733321156151/0f5729e1-7d16-42a8-9006-f7317ffa7163.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>departments</strong> 테이블에는 부서 정보가 있다.</p>
</li>
<li><p><strong>locations</strong> 테이블에는 부서의 위치 정보가 저장되어 있다. 보통 부서의 정보를 가져올땐 location id를 가져오진 않는다. 우리가 원하는 것은 주로 어느 시티에 있는지, 국가에 있는지를 찾아보게 된다. 이런 데이터를 따로 보고 싶을 때 조인이 필요하다.  </p>
</li>
</ul>
<p><strong>✅ 조인의 필요성(Need for Joins):</strong> 부서 정보와 함께 위치 정보(도시 및 국가)를 확인하려면 두 테이블의 데이터를 결합해야 한다.  </p>
<p>✅<strong>조인 성능 최적화</strong> (Join Performance Optimization):</p>
<p>조인의 성능을 높이기 위해서는,</p>
<ul>
<li><p><strong>인덱스</strong>(Indexes)를 활용한다.</p>
</li>
<li><p><strong>파티션</strong>(Partitions)을 설정하여 데이터 접근 속도를 높인다.</p>
</li>
<li><p>조인 쿼리를 설계할 때 실행 계획을 이해한 뒤 최적화해야 한다.</p>
</li>
</ul>
<p><strong><br />✅조인 종류 (Types of Joins):</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733321098420/ffff16a3-1f8a-472c-bb2a-513b65bf08c3.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>크로스 조인</strong>(Cross Join): 두 테이블의 모든 행을 조합하여 반환한다.</p>
</li>
<li><p><strong>내부 조인</strong>(Inner Join): 두 테이블에서 매칭되는 데이터만 반환한다.</p>
</li>
<li><p><strong>외부 조인</strong>(Outer Join): 한쪽 테이블에서 매칭되지 않은 데이터도 포함한다.</p>
</li>
</ol>
<hr />
<h3 id="heading-1-cross-join-and-cartesian-product"><strong>조인 #1: Cross Join and Cartesian Product</strong> (크로스 조인과 카티션 프로덕트)</h3>
<p><strong><mark>💡요약:</mark></strong> 두 테이블의 데이터의 모든 행을 곱하는 연산을 릴레이션에서는 카티션 프로덕트라고 한다. 이 카티션 프로덕트를 구현한 것이 크로스 조인이다.  </p>
<p><strong>✅카티션 프로덕트의 특징 (Characteristics of Cartesian Product):</strong></p>
<ol>
<li><p><strong>모든 조합 반환</strong>(Returns All Combinations): 테이블 간 모든 행 조합을 생성한다.</p>
</li>
<li><p><strong>조건이 없는 조인</strong>(Unconditional Join): <code>ON</code> 또는 <code>WHERE</code> 절이 없다.</p>
</li>
<li><p><strong>큰 결과 집합 생성</strong>(Large Result Set): 테이블의 크기가 클수록 행의 수가 기하급수적으로 증가한다.  </p>
</li>
</ol>
<h4 id="heading-definition-of-cross-join"><strong>✅크로스 조인의 정의</strong> (Definition of Cross Join)</h4>
<p><strong>크로스 조인</strong>(Cross Join)은 데이터베이스에서 두 개의 테이블을 곱하는 조인 방식이다. 이 연산은 테이블 간의 모든 가능한 조합을 반환하며, <strong>카티션 프로덕트</strong>(Cartesian Product)를 생성한다.</p>
<ul>
<li><p>크로스 조인은 두 테이블의 모든 행을 곱한다.</p>
</li>
<li><p>각 행은 두 테이블의 모든 가능한 조합을 나타낸다.  </p>
</li>
</ul>
<p><strong>✅예제 (Example):</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733322794073/9e92ac9f-6043-4a23-946e-b243da6992c7.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>departments의 각 행이 locations의 모든 행과 매칭되었다. departments 테이블의 첫 번째 행과 locations 테이블의 23개 행이 조합된다.</p>
</li>
<li><p>같은 방식으로 departments 테이블의 나머지 26개 행도 반복된다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733321288882/5832d5d1-8b39-4d5b-b766-5f32062e8e3d.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>departments</strong> 테이블: 27개의 행 (Rows)</p>
</li>
<li><p><strong>locations</strong> 테이블: 23개의 행 (Rows)</p>
</li>
<li><p><strong>크로스 조인 결과</strong>: 27 × 23 = 621개의 행이 생성되었다.</p>
</li>
</ul>
<p><strong>크로스 조인 쿼리</strong>:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> departments <span class="hljs-keyword">CROSS</span> <span class="hljs-keyword">JOIN</span> locations;
</code></pre>
<p>또는 <strong>크로스 조인 생략</strong>(Without Explicit Cross Join):</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> departments, locations;
</code></pre>
<hr />
<h4 id="heading-cautions"><strong>✅주의점</strong> (Cautions)</h4>
<ol>
<li><p><strong>큰 테이블 사용 시 성능 문제</strong>(Performance Issues with Large Tables): 크로스 조인은 결과 집합이 매우 커질 수 있으므로 메모리와 처리 속도에 영향을 미친다.</p>
</li>
<li><p><strong>사용 목적</strong>(Purpose of Use): 보통 특정 테스트 또는 모든 조합이 필요한 경우에만 사용한다.</p>
</li>
<li><p>성능 저하를 방지하려면 <strong>필터 조건</strong>을 추가하는 것이 중요하다. (Add Filter Conditions to Avoid Performance Degradation)</p>
</li>
</ol>
<hr />
<h3 id="heading-2-inner-join-and-efficient-execution">조인 #2: <strong>Inner Join and Efficient Execution</strong> (내부 조인과 효율적인 실행)</h3>
<p>inner join은 조건이 있다. 크로스 조인이 무조건 2개를 곱하는 것 이라면 내부 조인은 조건을 달아 연관된 데이터만 반환하는 방식이다. 이때, 데이터를 결합하는 과정은 크게 <strong>세 가지 단계</strong>로 나뉜다.</p>
<h4 id="heading-definition-of-inner-join"><strong>✅ 내부 조인의 정의</strong> (Definition of Inner Join):</h4>
<ul>
<li><p>두 테이블의 모든 행을 <strong>카티션 프로덕트</strong>로 결합한다.</p>
</li>
<li><p>이후 <strong>ON</strong> 조건이나 <strong>WHERE</strong> 절을 사용하여 특정 조건을 충족하는 데이터만 필터링한다.</p>
</li>
</ul>
<hr />
<h4 id="heading-query-examples-and-results"><strong>✅ 쿼리 예제와 결과</strong> (Query Examples and Results):</h4>
<ol>
<li><p><strong>기본 내부 조인 (Basic Inner Join):</strong></p>
<pre><code class="lang-sql"> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> departments <span class="hljs-keyword">INNER</span> <span class="hljs-keyword">JOIN</span> locations 
 <span class="hljs-keyword">ON</span> departments.location_id = locations.location_id;
</code></pre>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733323068437/b3c5fbde-1427-4111-ada6-5f8896380c27.png" alt class="image--center mx-auto" /></p>
<ul>
<li><strong>결과</strong>: 카티션 프로덕트한 전체 집합에서 조건에 맞는 레코드만 남게된다. <code>departments</code>와 <code>locations</code>의 <code>location_id</code>가 같은 27개의 레코드 반환.  </li>
</ul>
</li>
<li><p><strong>간단히 표현한 내부 조인 (Simplified Inner Join):</strong></p>
<pre><code class="lang-sql"> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> departments, locations 
 <span class="hljs-keyword">WHERE</span> departments.location_id = locations.location_id;
</code></pre>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733323206836/8fca59f6-3866-43c1-a150-9a8c2d818853.png" alt class="image--center mx-auto" /></p>
<ul>
<li>좀 더 간단하게 표현도 가능하다. INNER JOIN 대신 WHERE 만으로도 동일한 결과를 얻을 수 있다.  </li>
</ul>
</li>
<li><p><strong>조건 추가 (Adding Conditions):</strong></p>
<pre><code class="lang-sql"> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> departments, locations 
 <span class="hljs-keyword">WHERE</span> departments.location_id = locations.location_id 
 <span class="hljs-keyword">AND</span> department_id &lt;= <span class="hljs-number">100</span>;
</code></pre>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733323308068/1f7e638d-ccf0-443c-8881-c18daa6e0185.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>AND를 붙여</strong> 조인 조건에 조건을 줄 수 있다. where 로 가져 왔는데 department_id가 100보다 작거나 같은 것만 꺼내라는 조건을 AND뒤에 붙인것이다.</p>
</li>
<li><p><strong>결과</strong>: 부서 ID가 100 이하인 경우만 반환 (10개의 레코드).</p>
</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-kiriniuqkuuctou2goyhsoyduoydmcdsi6ttlokg7kci7lco"><strong>✅</strong>내부조인의 실행 절차</h4>
<ul>
<li><p><strong>카티션 프로덕트 생성</strong> (Cartesian Product):</p>
<ul>
<li><code>SELECT * FROM departments INNER JOIN locations</code></li>
</ul>
</li>
</ul>
<ul>
<li>두 테이블의 모든 행 조합을 생성 (621개의 행).</li>
</ul>
<ul>
<li><p><strong>조건 필터링</strong> (Filtering by Condition):</p>
<ul>
<li><p>그 중에서 department.location_id와 locations.location_id가 같은 것을 뽑아낸다.</p>
</li>
<li><p><code>ON departments.location_id = locations.location_id</code></p>
</li>
</ul>
</li>
</ul>
<ul>
<li><code>location_id</code>가 같은 데이터만 필터링되어 27개의 행이 출력된다.</li>
</ul>
<ul>
<li><p><strong>추가 조건 적용</strong> (Applying Additional Conditions):</p>
<ul>
<li><code>WHERE department_id &lt;= 100;</code></li>
</ul>
</li>
</ul>
<ul>
<li>department_id가 100보다 작은 것을 뽑아 낸다. 10개가 생성되었다.</li>
</ul>
<hr />
<p>💡DBMS의 입장에서 621-&gt; 27-&gt;10 를 추출하는 과정은 비효율적인 면이 있다. 이러한 이유로 거꾸로 하면 빠르지 않을까? 라는 해결법이 제시 되었다.</p>
<hr />
<h4 id="heading-dbms-dbms-optimization"><strong>✅ DBMS 최적화 방법</strong> (DBMS Optimization):</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733323766368/cba3ffb7-30ea-4d4f-b144-d37413eef860.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>필터 조건을 먼저 적용(Apply Filters Early):</strong></p>
<ul>
<li><p><code>department_id</code> 가 100보다 작은 결과를 원한다면, 100보다 큰 것은 처음부터 조인을 하지 않으면 된다. <code>department_id &lt;= 100</code> 조건을 먼저 처리하여 10개의 행만 선택한다.</p>
</li>
<li><p>department 조인에 참여하는 숫자를 줄인다. 불필요한 조인을 줄여 성능 향상하는 것이다.</p>
</li>
</ul>
</li>
<li><p><strong>인덱스 활용(Using Indexes):</strong></p>
<ul>
<li><p><code>dept_id_pk</code>(부서 ID)와 <code>loc_id_pk</code>(위치 ID) 인덱스를 사용하여 효율적으로 데이터를 조회.</p>
</li>
<li><p>인덱스 range scan을 통해 100보다 작은 10개를 뽑아내게 되었다. <strong>인덱스 덕분에 필요한 데이터만 검색이 가능해졌다.</strong></p>
</li>
</ul>
</li>
<li><p><strong>Sort Merge Join</strong>:</p>
<ul>
<li>그 뒤 필터링된 데이터(10개)를 정렬한 뒤 병합하여 결합한다.</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-execution-plan-analysis"><strong>✅ 실행 계획 분석</strong> (Execution Plan Analysis):</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733323772451/2dc29044-5d7f-49e8-947a-43e8c611469a.png" alt class="image--center mx-auto" /></p>
<p>첨부된 실행 계획 이미지에서 주요 과정:</p>
<ol>
<li><p><strong>Index Range Scan</strong>:</p>
<ul>
<li><code>department_id &lt;= 100</code> 조건에 따라 필터링.</li>
</ul>
</li>
<li><p><strong>Table Access</strong>:</p>
<ul>
<li>필터링된 10개의 데이터를 가져옵니다.</li>
</ul>
</li>
<li><p><strong>Sort and Merge</strong>:</p>
<ul>
<li><code>departments.location_id = locations.location_id</code> 조건에 따라 정렬 및 병합.</li>
</ul>
</li>
</ol>
<hr />
<p>💡이것이 내부조인을 dbms에서 효율적으로 구현하는 방법의 예가 된다. 쓸데없는 조인을 줄이게 된다. 조인을 설계할 때 이런식의 효과적인 접근방법이 나오게 설계하는 것이 굉장히 중요하다. 그러기 위해선 조인이 어떻게 동작 하는지를 이해하고 있어야한다.</p>
<hr />
<h2 id="heading-2-how-join-works"><strong>2️⃣</strong>조인의 동작 방식(How Join Works)</h2>
<p><strong><mark>💡요약: </mark></strong> 조인의 동작방식에 대해 더 자세히 알아본다. 조인이 수행될 때 테이블 간에 접근하는 방식이 중요하다고 배웠다. 조인 방식에 따라 쿼리의 비용과 성능이 달라지기 때문이다. DBMS의 쿼리 옵티마이저가 접근 방식을 결정한다. 오라클에서 주로 사용하는 조인 방식은 크게 3가지가있다. <strong>중첩 루프 조인</strong>(Nested Loop Join), <strong>소트 머지 조인</strong>(Sort Merge Join), <strong>해시 조인</strong>(Hash Join)은 오라클 DBMS에서 주로 사용되는 조인 방식이다. 각각의 조인 방식은 데이터 크기, 정렬 여부, 조건에 따라 성능이 달라진다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733323896058/74780799-769d-4f6d-a471-f98161b6ab00.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-1-how-join-works-nested-loop-join">조인의 동작 방식 #1: 중첩 루프 조인 (How Join Works - Nested Loop Join )</h2>
<p><strong>✅ 중첩 루프 조인 (Nested Loop Join):</strong></p>
<ul>
<li><p><strong>중첩 루프 조인은 간단하지만 성능은 데이터 크기와 인덱스 여부에 따라 달라집니다.</strong><br />  (Nested Loop Join is Simple but Performance Depends on Data Size and Index Presence)</p>
</li>
<li><p><strong>인덱스 설정은 중첩 루프 조인의 핵심 최적화 전략입니다.</strong> (Indexing is Key to Optimizing Nested Loop Join)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733323990724/890804b7-b4c8-46a6-9cb2-cd2c6e767b51.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>작동 방식</strong>:</p>
<ul>
<li><p>첫번째 테이블(outer relation)의 각각의 로우에 대해서 두번째 테이블(inner relation)의 모든 로우를 비교한다. 첫번째 테이블에 46은 두번째 테이블에 없으므로 넘어간다. 다음 로우 0은 두번째 테이블에 로우 3에 있다. 그 뒤엔 어떻게 될까? 중복 여부에 따라 달라진다. 중복이 될 수 없다면 이 0 결합 후 끝나게 된다. 중복이 허용 된다면 테이블 끝까지 full scan을 진행하게 된다. 이 경우 인덱스가 있었다면 중복여부와 상관없이 정렬이 되어있기 때문에 훨식 효율적이다.</p>
</li>
<li><p>결론적으로 inner relation에 인덱스 여부가 중요한 지표가 된다. 인덱스가 없다면 전부 돌아야하기 때문이다. 이 예제에 중복허용이 안된다면 0에서 끝나지만 중복 허용이 되었다면 full scan을 진행하므로 첫 번째 테이블의 10은 두번째 테이블의 10과 결합 된다. 모든 레코드에 대해 모든 로우를 비교하는 것을 중접 루프 조인의 동작 방식이라고 한다.</p>
</li>
<li><p>우리가 흔히 생각하는 조인이다.</p>
</li>
<li><p>첫번째 테이블(outer relation)의 각각의 로우에 대해서 두번째 테이블(inner relation)의 모든 로우를 비교하여 조건에 맞는 로우(데이터)를 결합한다.</p>
</li>
<li><p><strong>카티션 프로덕트</strong>를 생성한 뒤 조건을 적용하여 데이터를 필터링한다.  </p>
</li>
</ul>
</li>
<li><p><strong>특징</strong>:</p>
<ul>
<li><p>인덱스 구성이 되어있어야 한다. 인덱스가 없다면 엄청나게 느려질수있다.</p>
</li>
<li><p>조인을 한 레코드씩 순차적으로 진행한다. 먼저 액세스 되는 테이블의 처리 범위에 의해 전체 조인 성능이 결정된다. outer relation 에 있는 로우를 줄여야 한다. 그래서 이것으로 inner relation으로 스캔하게 된다. 이렇듯 조인에 참여하는 로우의 갯수를 줄이는 게 중요함을 확인 할 수있다.</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>작은 데이터셋에 적합하다.</p>
</li>
<li><p>조인 컬럼의 인덱스 여부와 인덱스 컬럼의 구성 방식에 따라 조인 효율이 크게 달라진다. index가 있는것이 가장좋고 적어도 secondary index라도 있어야 좋다.</p>
</li>
<li><p>두 테이블 간 정렬이 필요없다.</p>
</li>
<li><p>팁을 언급 하자면 조인 쿼리를 많이 작성해야 할 때, 많이 등장하는 컬럼에 인덱스를 설치하게 되면 접근성이 증가할 수 있게 된다.  </p>
</li>
</ul>
<ul>
<li><p><strong>단점</strong>:</p>
<ul>
<li>인덱스 구성이 되어 있어도 대량의 데이터를 조인할 때 매우 비효율적이다.</li>
</ul>
</li>
</ul>
<ul>
<li><p>큰 테이블에 사용하면 비효율적(연산량 증가).</p>
</li>
<li><p>테이블의 로우가 많으면 많을수록 m2이므로 선형적으로 증가하게 된다.</p>
</li>
</ul>
<hr />
<p><strong>✅ 중첩루프조인의 PL/SQL 구현예제 1 - 조인 연산 없이</strong></p>
<p>이 예제는 조인 연산 없이 PL/SQL을 구현한 예제이다.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SET</span> serveroutput <span class="hljs-keyword">ON</span>;

<span class="hljs-keyword">BEGIN</span>
    <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">outer</span> <span class="hljs-keyword">IN</span> (<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> departments) <span class="hljs-keyword">LOOP</span>
        <span class="hljs-keyword">FOR</span> <span class="hljs-keyword">inner</span> <span class="hljs-keyword">IN</span> (<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> locations <span class="hljs-keyword">WHERE</span> location_id = outer.location_id) <span class="hljs-keyword">LOOP</span>
            DBMS_OUTPUT.PUT_LINE(
                outer.department_id || <span class="hljs-string">' '</span> || 
                outer.department_name || <span class="hljs-string">' '</span> || 
                inner.location_id || <span class="hljs-string">' '</span> || 
                inner.city
            );
        <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>외부 루프(Outer relation)</strong></p>
<ul>
<li><code>FOR outer IN (SELECT * FROM departments) LOOP</code></li>
</ul>
</li>
</ul>
<ul>
<li><code>departments</code> 테이블의 모든 row(행)을 가져온다. (27개)</li>
</ul>
<ul>
<li><p><strong>내부 루프(Inner relation)</strong></p>
<ul>
<li>각각의 로우에 대해 Inner relation에 있는 값을 비교한다.</li>
</ul>
</li>
</ul>
<ul>
<li><code>FOR inner IN (SELECT * FROM locations WHERE location_id = outer.location_id) LOOP</code></li>
</ul>
<ul>
<li><p><code>locations</code> 테이블에 23개의 row가 있는데 <code>WHERE location_id = outer.location_id</code>서 첫번째 레코드 23번 비교 두번째 레코드 23번 비교 .. 이렇게 된다.</p>
</li>
<li><p>만약 인덱스가 있다면 인덱스 1번만 비교하게 될 것이다. 621번 했어야 하는 비교가 27로 바뀌게 된다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733326159517/9cc59776-0bb1-48eb-a0db-1f72a9e40850.png" alt class="image--center mx-auto" /></p>
<ul>
<li><strong>출력:</strong> <code>department_id</code>, <code>department_name</code>, <code>location_id</code>, <code>city</code>.</li>
</ul>
<hr />
<p><strong>✅ 중첩루프조인의 PL/SQL 구현예제 2- 조인 연산 사용 WHERE</strong></p>
<p><strong>조인 테이블 확인</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733326392845/3bb5bede-6b1f-4d92-822a-d4c13ba48dcc.png" alt class="image--center mx-auto" /></p>
<ul>
<li><code>SELECT * FROM locations;</code> 에선 <code>country_id</code>가 조인키가 된다. <code>SELECT * FROM countries;</code> 또한 <code>country_id</code>가 조인키가 된다.</li>
</ul>
<hr />
<p><strong>조인 완료</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733326542864/c0e055bd-bc7b-42ba-b239-c166eb21c5bb.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><code>SELECT l.location_id,</code> <a target="_blank" href="http://l.city"><code>l.city</code></a><code>, l.state_province,</code> <a target="_blank" href="http://c.country"><code>c.country</code></a><code>_id,</code>까지는 location테이블에 있다. <a target="_blank" href="http://c.country"><code>c.country</code></a><code>_name</code>은 countries에 있다.</p>
</li>
<li><p>예제의 결과처럼 조인된 결과를 보여주기 위해선 아래의 명령어를 사용한다.</p>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> l.location_id, l.city, l.state_province, c.country_id, c.country_name
<span class="hljs-keyword">FROM</span> locations l, countries c <span class="hljs-keyword">WHERE</span> l.country_id = c.country_id;
</code></pre>
<ul>
<li>WHERE은 조인 조건으로써 <code>l.country_id</code>와 <code>c.country_id</code>가 동일한 조건이다.</li>
</ul>
<hr />
<p><strong>조인 실행 계획</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733326798545/9393a0ff-7cb0-40b2-833f-21705ad461bb.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Nested Loops</strong>: 중첩 루프 조인을 사용.</p>
</li>
<li><p>거기에 locations, full 테이블 조인이 되었다. 23개의 레코드가 있었음을 볼 수 있다.</p>
</li>
<li><p><strong>Index Scan</strong>: <code>country_id</code>에 인덱스(<code>COUNTRY_C_ID_PK</code>)가 설정.</p>
<ul>
<li>내부 테이블을 풀 스캔하지 않고, 필요한 데이터만 검색.</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-importance-of-index"><strong>✅인덱스의 중요성</strong> (Importance of Index):</h4>
<ul>
<li><p><strong>인덱스가 없는 경우</strong>: 내부 테이블의 모든 행을 풀 스캔(Full Scan), 성능 저하를 일으킨다.</p>
</li>
<li><p><strong>인덱스가 있는 경우</strong>: 필요한 데이터만 검색, 예: <code>location_id</code>에 인덱스가 있으면 비교 횟수가 27번으로 줄어듦.</p>
</li>
<li><p><strong>조인 컬럼의 인덱스 설정</strong>: 조인 조건에 자주 사용되는 컬럼에 <strong>인덱스 추가</strong>할 수 있다. 예) <code>location_id</code>, <code>country_id</code>.</p>
</li>
</ul>
<hr />
<h2 id="heading-2-how-join-works-sort-merge-join">조인의 동작 방식 #2: 소트 머지 조인 (How Join Works - <strong>Sort Merge Join</strong>)</h2>
<p><strong><mark>💡요약: </mark></strong> 소트 머지 조인(Sort Merge Join)에 대해 알아보자. 중첩 루프 조인은 좀 무식한 방법이기 때문에 dbms도 이를 최후의 방법으로 여기고 소트머지조인(Sort Merge Join)을 우선으로 처리하는 편이다. 소트 머지 조인(Sort Merge Join)은 두 테이블의 데이터를 <strong>정렬(Sorting)</strong>한 뒤 병합(Merge)하여 조인을 수행하는 방식이다.  </p>
<p><strong>✅ 소트 머지 조인의 작동 방식</strong> (How Sort Merge Join Works):</p>
<ol>
<li><p><strong>정렬(Sort)</strong>:</p>
<ul>
<li>PGA영역의 Sort 영역에서 정렬한 뒤 Nested Loop 조인 방식으로 진행된다.</li>
</ul>
</li>
</ol>
<ul>
<li><p>쉽게 설명하면 두 테이블을 각각 정렬한 다음에 두 집합을 합치면서 조인을 수행하는 방식이다.</p>
</li>
<li><p>정렬 후에 합치기 때문에 inner relation은 이미 1, 1, 1 이런식으로 정렬된 상태가 된다.</p>
</li>
<li><p>즉 인덱스가 없는데도 이와 비슷한 역할을 하는 셈이다.</p>
</li>
<li><p>정렬 효율이 빨라지고 full table scan을 하지 않게 된다. 버퍼캐시를 사용하는 NL보다 빠르다.</p>
</li>
<li><p>예: <code>employees</code>와 <code>departments</code> 테이블의 <code>department_id</code> 기준으로 정렬.</p>
</li>
</ul>
<ol start="2">
<li><p><strong>병합(Merge)</strong>:</p>
<ul>
<li>정렬된 두 테이블을 순차적으로 비교하여 조인 조건에 맞는 데이터를 결합.</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅ 소트 머지 방식의 단점 (Disadvantage of Sort Merge Join)</strong></p>
<ol>
<li><p><strong>정렬 비용 추가</strong>: 정렬 작업이 필요하므로 추가 비용 발생.</p>
</li>
<li><p><strong>이미 정렬된 경우에는 불필요</strong>: 정렬된 데이터에선 성능 이점이 적음.</p>
</li>
</ol>
<p>단점은 정렬을 하는데 이 정렬 비용이 추가적으로 들게 된다는 점이다. 만약 정렬하는 비용이 더 들 것 같다고 DBMS가 판단 하면 중첩 루프 조인을 선택하게 될 것 이다. 일반적으로 소트 머지 조인은 버퍼 캐시를 사용하는 Nested Loop보다 빠르게 수행된다. 인덱스가 없는 경우 인덱스를 실시간으로 생성하는 효과를 볼 수 있다. 미리 정렬이 되어 있기 때문이다. 만약 인덱스가 있는 경우엔 조인 속도가 바로 증가하게 된다. 데이터가 정렬되어 있기 때문에 비교값이 없거나 다 찾은 후에는 종료하게 된다.</p>
<hr />
<p><strong>✅ 예제 쿼리와 결과 (Query and Results):</strong></p>
<p>이 예제를 통해 인덱스가 없어도 정렬을 통해 빠르게 접근이 가능한 것을 알수있다. Nested Loop Join과 달리, <code>=</code>, <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code>, <code>&gt;=</code> 조건에 모두 적용 가능하다. 또한 대용량 데이터 처리도 가능해서 대규모 테이블에 적합하다.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> e.employee_id, e.first_name, e.last_name, e.department_id, d.department_name
<span class="hljs-keyword">FROM</span> employees e, departments d
<span class="hljs-keyword">WHERE</span> e.department_id = d.department_id;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733328921355/b5dfddf4-2ab3-4907-8efc-3252813e80e8.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Employees 테이블 (Outer Relation)</strong>: 모든 데이터를 읽기 때문에 Full Table Scan (107개 레코드).</p>
</li>
<li><p><strong>Departments 테이블 (Inner Relation)</strong>: <code>department_id</code>에 <strong>인덱스가 존재</strong>하여 인덱스를 사용하였다.</p>
</li>
<li><p><strong>정렬 및 병합</strong>: 조건(WHERE): <code>e.department_id = d.department_id</code> , 두 테이블의 <code>department_id</code> 기준으로 정렬 후 병합되었다.  </p>
</li>
</ul>
<p><strong>✅소트 머지 조인의 실행 계획</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733329258372/79459c1c-e7ca-4c84-b323-2248d55249bf.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>employee테이블에서 outer relation은 모든 레코드를 비교해야하므로 full, 107개가 확인된다.</p>
</li>
<li><p>inner relation인 department테이블이 중요한데, 인덱스가 없다면 full table scan을 하게된다. 이 경우에는 인덱스가 있으므로 첫번째 인덱스만 찾아 가져왔다.</p>
</li>
<li><p>자, 여기까지보면 nested loop와 차이가 없는것처럼 보인다. 하지만 sort merge join에서는 department id에 대해서 sort를 한다. 그래서 sort merge join 이 일반적으론 빠른편에 속한다.</p>
</li>
</ul>
<hr />
<p><strong>✅소트 머지 조인의 실행 계획에서 알수있는 특징</strong></p>
<p>1) 첫번째 테이블에 소트 연산을 대체할 인덱스가 있을 때 유용하다.</p>
<ul>
<li>두 테이블을 정렬하기 때문이다. 부분 범위 처리가 가능하다는 뜻이다.</li>
</ul>
<p>2) 첫번째 테이블이 이미 정렬되어 있을 때 유용하다.</p>
<ul>
<li>group by, order by 등을 먼저 수행한 경우이다.</li>
</ul>
<p><strong><mark>3) 조인 조건식이 = 조건이 아닐 때에도 적용 가능하다.</mark></strong></p>
<ul>
<li>이 부분이 제일 <strong><mark>중요하다</mark></strong>. 참고로 해시조인은 조인 조건식이 =일 경우에만 사용할 수 있다.</li>
</ul>
<hr />
<h2 id="heading-3-how-join-works-hash-join">조인의 동작 방식 #3: 해시 조인 (How Join Works - <strong>Hash Join</strong>)</h2>
<p><strong><mark>💡 요약: </mark> 해시 조인</strong>은 기존의 중첩 루프 조인(Nested Loop Join)과 소트 머지 조인(Sort Merge Join)이 비효율적인 경우에 성능을 개선하기 위해 개발된 방식이다.</p>
<p><strong>✅ 해시 조인의 특징 (Characteristics of Hash Join):</strong></p>
<ol>
<li><p><strong>중첩루프조인과 소트머지조인이 효과적이지 않은 상황에 대한 대안으로 개발</strong></p>
<ul>
<li>"효과적이지 않은 상황”을 정의하긴 좀 어렵다. 보통은 인덱스가 없거나, 대규모의 데이터 처리에 적합하지 않을 때리 할 수 있겠다.</li>
</ul>
</li>
<li><p><strong>대규모의 데이터 처리에 적합하다.</strong></p>
<ul>
<li>원리는 무엇일까? 해시라는 것은 해시함수를 적용하여 범위를 줄여준다. 해시를 이용해서 주소가 1~1000까지 있을때 이 1000까지 주소 되어 있는 부분을 10개씩 묶을 수 있게 된다. 그럼 1000개를 비교하는대신 100개를 10번 비교하는 형태가 된다. 이런식으로 비교 범위를 확 줄여주는 것이 해시조인의 기본 원리라고 할 수 있다.</li>
</ul>
</li>
<li><p><strong>일반적인 경우 중첩루프조인이나 소트머지조인보다 나은 성능을 보임</strong></p>
<ul>
<li>또 "언제" 더 나은 성능을 보이느냐? 대부분은 outer relation에 비해 inner relation의 로우의 숫자 더 많을 때 이다.</li>
</ul>
</li>
<li><p><strong>두 테이블 중 작은 사이즈의 테이블을 읽어 해시 영역에 해시 테이블 생성한다.</strong></p>
<ul>
<li>Build Input(작은 테이블) 이라고 한다.</li>
</ul>
</li>
<li><p><strong>나머지 큰 테이블의 레코드를 하나씩 읽어 해시 테이블에 연결하는 방식이다.</strong></p>
<ul>
<li>Probe Input(큰 테이블)이라고 한다.</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅ 해시 조인의 동작 과정 (Hash Join Execution Process):</strong></p>
<p>해시 조인은 큰 두 개의 테이블에서 조인 조건에 맞는 데이터를 효율적으로 찾아내는 방법이다. 데이터를 메모리에 저장한 후, 해시 알고리즘을 통해 데이터를 비교하고 매칭한다. 이 과정은 특히 대규모 데이터에서 유용하다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733329884320/ef5fd08e-38cb-4a4a-9c48-d7281416e58b.png" alt class="image--center mx-auto" /></p>
<p>1️⃣ <strong>Table Scan (테이블 스캔) - Vehicles Table</strong></p>
<ul>
<li><p>첫 번째로, "Vehicles Table"이라는 테이블을 읽는다.</p>
</li>
<li><p>이 과정에서 데이터를 <strong>스캔</strong>하고, 조인에 필요한 <strong>컬럼 값</strong>을 뽑아낸다.</p>
</li>
<li><p>이 값들을 활용해 2️⃣ <strong>해시 테이블(Hash Table)</strong>이라는 것을 만든다. <strong>해시 테이블</strong>은 데이터를 빠르고 효율적으로 저장하고 검색하기 위해 사용하는 자료구조이다.</p>
</li>
<li><p>이럴때 vehicles table은 값이 작은 것을 확인할 수 있다. sales는 판매될수록 계속 늘어나게 될 것이다.</p>
</li>
<li><p><strong>(비유)</strong>: "차량"이라는 박스를 열어 안에 들어 있는 필요한 자료만 꺼내오는 과정이다.</p>
</li>
</ul>
<hr />
<p>2️⃣ <strong>Hash Table 생성</strong></p>
<ul>
<li><p>"Vehicles Table"에서 꺼낸 값들을 사용해 <strong>해시 테이블</strong>이라는 특별한 데이터 구조를 만든다.</p>
</li>
<li><p>이 해시 테이블은 <strong>메모리(PGA)</strong>에 저장되며, 검색을 빠르게 도와준다.</p>
</li>
<li><p><strong>(비유)</strong>: 필요한 자료들을 바구니(PGA)에 넣고 정리해둔 상태라고 볼 수 있다.</p>
</li>
</ul>
<hr />
<p>3️⃣ <strong>Table Scan (테이블 스캔) - Sales Table</strong></p>
<ul>
<li><p><strong>(설명)</strong>: 두 번째 테이블인 "Sales Table"을 읽는다.</p>
</li>
<li><p>이 과정에서 조인 조건에 맞는 데이터만 추려낸다. (해시 테이블 생성)</p>
</li>
<li><p>테이블 스캔이 한번 진행된다. 중요한 부분이다. 위에서 부터 아래로 한번만 한다. 이로써 해시 테이블을 구성한다.</p>
</li>
<li><p><strong>(비유)</strong>: "판매 기록"이라는 박스를 열어 차량 정보와 관련된 것만 가져오는 작업이다.</p>
</li>
</ul>
<hr />
<p>4️⃣ <strong>Row Sent to Hash Join (행을 해시 조인으로 전달)</strong></p>
<ul>
<li>"Sales Table"에서 가져온 데이터 중, 조인에 필요한 데이터만 선택한다.</li>
</ul>
<ul>
<li><p>이 데이터는 해시 조인 알고리즘에 따라 다시 <strong>해시 테이블</strong>로 보내진다.</p>
</li>
<li><p><strong>(비유)</strong>: 판매 데이터에서 차량과 관련된 부분만 선별해서 바구니에 추가한다.</p>
</li>
</ul>
<hr />
<p>5️⃣ <strong>Hash Join 수행</strong></p>
<ul>
<li><p>마지막으로 "Sales Table"에서 가져온 데이터와 "Vehicles Table"로 만든 해시 테이블을 비교하게 된다.</p>
</li>
<li><p>두 테이블의 데이터가 <strong>매칭</strong>되는지를 확인하여 결과를 출력한다.</p>
</li>
<li><p><strong>(비유)</strong>: 두 박스에서 꺼낸 데이터를 서로 비교하여 연결 가능한 것들만 짝짓는 작업이다.</p>
</li>
</ul>
<hr />
<p><strong>✅ Hash Join with Temporary Tables (임시 테이블을 이용한 해시 조인)</strong></p>
<p><strong>해시 조인</strong>은 기존의 중첩 루프 조인(Nested Loop Join)이나 소트 머지 조인(Sort Merge Join)보다 효율적이지 않은 경우, 특히 <strong>인덱스가 없거나 대규모 데이터</strong>를 처리할 때 성능을 개선하기 위해 사용되는 조인방법이다.</p>
<hr />
<p><strong>1️⃣ 임시 테이블 생성 (Creating Temporary Tables)</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> emp_temp;
<span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> dept_temp;
<span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> loc_temp;
<span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> coun_temp;

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> emp_temp <span class="hljs-keyword">AS</span> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> employees;
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> dept_temp <span class="hljs-keyword">AS</span> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> departments;
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> loc_temp <span class="hljs-keyword">AS</span> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> locations;
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> coun_temp <span class="hljs-keyword">AS</span> <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> countries;
</code></pre>
<ul>
<li><p>실습을 위해 <strong>인덱스나 제약 조건 없이 테이블 생성</strong>을 하였다.</p>
</li>
<li><p>인덱스가 없으므로 Full Table Scan이 필요하며, 해시 함수를 적용하여 데이터 비교한다.</p>
</li>
<li><p>데이터만 가지고와서 똑같은 테이블을 만드는 것이고 인덱스나 다른 제약조건이 없는 상태이다.</p>
</li>
</ul>
<hr />
<p><strong>2️⃣해시 조인 예제 1 (Hash Join Example 1)</strong></p>
<p><strong>Location ID 기준 조인 (Example 1: Join on Location ID)</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> dept_temp d, loc_temp l
<span class="hljs-keyword">WHERE</span> d.location_id = l.location_id;
</code></pre>
<ul>
<li><p><code>dept_temp</code>와 <code>loc_temp</code>를 Full Scan한다.</p>
</li>
<li><p><code>location_id</code> 기준으로 해시 테이블 생성한다.</p>
</li>
<li><p>해시 테이블을 사용해 조건에 맞는 데이터를 결합한다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733364915328/c9f77bf3-7da0-41f4-9816-7c4d988475e9.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Full Table Scan</strong>: <code>dept_temp</code> (27개), <code>loc_temp</code> (23개).</p>
</li>
<li><p><strong>Hash Join</strong>: 해시 테이블을 생성하고 매칭하였다.</p>
</li>
</ul>
<hr />
<p><strong>3️⃣해시 조인 예제 2 (Hash Join Example 2 with Filter)</strong></p>
<p><strong>Location ID와 Country ID 조합 (Example 2: Join on Location ID and Filter)</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> l.location_id, l.city, l.state_province, c.country_id, c.country_name
<span class="hljs-keyword">FROM</span> loc_temp l, coun_temp c
<span class="hljs-keyword">WHERE</span> l.country_id = c.country_id
<span class="hljs-keyword">AND</span> l.location_id &lt;= <span class="hljs-number">2000</span>;
</code></pre>
<ul>
<li><p><code>loc_temp</code>에서 <code>location_id &lt;= 2000</code> 조건으로 필터링한다.</p>
</li>
<li><p><code>coun_temp</code>와 <code>country_id</code>를 기준으로 해시 테이블 생성한다.</p>
</li>
<li><p>필터링된 <code>loc_temp</code>와 해시 테이블을 매칭한다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733365013211/e244d26b-f196-4635-b4e6-4d3c120af3d1.png" alt class="image--center mx-auto" /></p>
<ul>
<li>해시 조인을 통해 조건에 맞는 데이터만 결합하였다.</li>
</ul>
<hr />
<p><strong>4️⃣해시 조인 예제 3 (Hash Join Example 3)</strong></p>
<p><strong>Department ID 기준 조인 (Example 3: Join on Department ID)</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> e.last_name, d.department_id, d.department_name
<span class="hljs-keyword">FROM</span> dept_temp d, emp_temp e
<span class="hljs-keyword">WHERE</span> d.department_id = e.department_id;
</code></pre>
<ol>
<li><p><code>dept_temp</code>(작은 테이블)를 Build Input으로 사용해 해시 테이블 생성한다.</p>
</li>
<li><p><code>emp_temp</code>(큰 테이블)를 Probe Input으로 사용해 매칭한다.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733365124111/8ed33460-db8d-4611-9511-698d87ab5c87.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Full Table Scan</strong>: <code>dept_temp</code> (27개), <code>emp_temp</code> (110개).</p>
</li>
<li><p><strong>Hash Join</strong>: <code>department_id</code> 기준으로 해시 테이블 생성하였다.</p>
</li>
</ul>
<hr />
<p><strong>✅해시 조인이 유용한 상황 (Scenarios Where Hash Join is Useful)</strong>  </p>
<ul>
<li><p><strong>조인 컬럼에 적당한 인덱스가 없을 때</strong></p>
<ul>
<li><h4 id="heading-nested-loop-join-inner"><strong>인덱스가 없으면</strong> Nested Loop Join은 Inner 테이블의 모든 행을 반복적으로 스캔해야 하기 때문에 비효율 적이게 된다.</h4>
</li>
<li><p>하지만 해시 조인을 사용하면 <strong>해시 테이블을 생성</strong>하고, 비교 범위를 줄여 효율적으로 데이터 검색이 가능해지게 된다.  </p>
</li>
</ul>
</li>
<li><p><strong>인덱스가 있어도 Inner 테이블 액세스량이 많을 때</strong>:</p>
<ul>
<li><p>Inner 테이블에 <strong>대규모 데이터</strong>가 포함된 경우, 인덱스 접근만으로도 많은 비용이 발생하게 된다.</p>
</li>
<li><p>한번 스캔하는 것이 비용이 많이 드는 경우 해시 조인을 통해 <strong>한 번의 Full Table Scan</strong> 후 데이터를 그룹화(나누기)하여 해당하는 부분을 Nested Loop로 조인할 수 있다. 이로써 비용 절감이 가능해진다.  </p>
</li>
</ul>
</li>
<li><p><strong>대용량 테이블 조인 시</strong>:</p>
<ul>
<li>수행 빈도가 낮은 <strong>대용량 테이블</strong>에서 쿼리 시간이 오래 걸릴 때 해시 조인을 활용한다. 한 번의 테이블 스캔만으로 데이터를 해시 테이블에 저장하고 비교할 수 있게 된다.  </li>
</ul>
</li>
<li><p><strong>스캔 비용이 높은 경우</strong>:</p>
<ul>
<li><p>큰 테이블을 Full Scan해야 할 때, 데이터를 <strong>해시 함수로 그룹화</strong>하여 비교.</p>
</li>
<li><p>그룹화된 데이터는 Nested Loop Join을 통해 효율적으로 조합 가능.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅ 해시 조인 사용 조건 (Conditions for Using Hash Join):</strong></p>
<ul>
<li><p><strong>한쪽 테이블이 충분히 작아야 함</strong>:</p>
<ul>
<li><p><strong>Build Input</strong>(해시 테이블 생성에 사용되는 테이블)이 <strong>해시 영역(PGA)</strong>에 들어갈 정도로 작아야 함.</p>
</li>
<li><p>작은 테이블에서 해시 테이블을 생성하여 비교 작업을 단순화.  </p>
</li>
</ul>
</li>
<li><p><strong>Build Input의 해시 키에 중복 값이 적어야 함</strong>:</p>
<ul>
<li><p>해시 키 컬럼에 중복 값이 많으면 해시 테이블의 효율성이 떨어질 수 있음.</p>
</li>
<li><p>중복 값이 많을 경우 Nested Loop Join이나 Sort Merge Join이 더 적합할 수 있음.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-3comparison-of-join-methods">3️⃣Comparison of Join Methods (조인 방식 비교)</h3>
<p><strong><mark>💡요약: </mark></strong> 조인 방식은 데이터 크기, 인덱스 유무, 작업 범위에 따라 효율성이 달라진다. <strong>중첩 루프 조인</strong>, <strong>소트 머지 조인</strong>, <strong>해시 조인</strong>의 특징과 적합한 상황을 비교한다.</p>
<p><strong>✅ 중첩 루프 조인 (Nested Loop Join)</strong></p>
<h4 id="heading-kirtirnsp5uqkjo"><strong>특징</strong>:</h4>
<ol>
<li><p><strong>기본 조인 방법</strong>: 테이블의 모든 행을 다른 테이블의 모든 행과 비교한다. (카티션 프로덕트)</p>
</li>
<li><p><strong>소량 데이터에 적합</strong>: 데이터가 적을수록 빠른 성능을 가진다.</p>
</li>
<li><p><strong>인덱스 필수</strong>: 조인 컬럼에 인덱스가 필요하다.</p>
</li>
<li><p><strong>순차적 접근</strong>: 테이블 접근 순서에 따라 성능이 달라지므로 테이블의 접근 순서가 중요하다.</p>
</li>
</ol>
<h4 id="heading-kirsnqxsojaqkjog7iam65jiounsoydto2escdsspjrpqzsl5ag7zqo7jyo7kcb7j206rogiou2gou2hcdrsptsniqg7lky66asioqwgoukpe2vncdsoja"><strong>장점</strong>: 소량 데이터 처리에 효율적이고 부분 범위 처리 가능한 점</h4>
<h4 id="heading-kirri6jsojaqkjogkirrjidrn4kg642w7j207ysw7jeq7isciou5ho2aqoycqoyggsoqlidsnbjrjbhsiqtqsiag7jeg7jy866m0ioyeseukpeydtcdtgazqsowg7kca7zwy65cc64uklg"><strong>단점</strong>: <strong>대량 데이터에서 비효율적</strong>. 인덱스가 없으면 성능이 크게 저하된다.</h4>
<hr />
<p><strong>✅소트 머지 조인 (Sort Merge Join)</strong></p>
<h4 id="heading-kirtirnsp5uqkjo-1"><strong>특징</strong>:</h4>
<ol>
<li><p><strong>대량 데이터 처리에 적합</strong>: 데이터를 정렬(Sort)한 뒤 병합(Merge)하는 방식이다.</p>
</li>
<li><p><strong>인덱스 불필요</strong>: 정렬 작업이 임시 인덱스 역할을 한다.</p>
</li>
<li><p><strong>전체 범위 처리</strong>: 모든 데이터를 처리해야 하는 작업에 적합하다.</p>
</li>
</ol>
<h4 id="heading-kirsnqxsojaqkjogkirrjidrn4kg642w7j207yswioyymoumroqwgcdtmqjsnkjsoieqkuydtoulpc4gkirsojxrokwg7j6r7jefio2bhcdruadrpbgg67or7zwpkirsnbqg6rca64ql7zwy64uklg"><strong>장점</strong>: <strong>대량 데이터 처리가 효율적</strong>이다. <strong>정렬 작업 후 빠른 병합</strong>이 가능하다.</h4>
<h4 id="heading-kirri6jsojaqkjogkirshoztirgg67aa7zwy6rcakiog67cc7iod7zwc64uklidspokg7kcv66csioyekeyxheyxkcdrlldrpbgg7lau6rcaiou5hoyaqeydtcdrk6dri6tripqg65y77j2064uklidrjbdsnbtthldqsiag7j20664ioygleugrouqmoywtcdsnojsnlzrqbqg67ai7zwe7jqu7zwcioygjo2kucdsnphsl4ug67cc7iod7zwgioyimcdsnojri6qu"><strong>단점</strong>: <strong>소트 부하가</strong> 발생한다. 즉 정렬 작업에 따른 추가 비용이 든다는 뜻이다. 데이터가 이미 정렬되어 있으면 불필요한 소트 작업 발생할 수 있다.</h4>
<hr />
<p><strong>✅ 해시 조인 (Hash Join)</strong></p>
<h4 id="heading-kirtirnsp5uqkjo-2"><strong>특징</strong>:</h4>
<ol>
<li><p><strong>대량 데이터와 전체 범위 처리에 적합</strong>하다. 특히 <strong>작은 테이블과 큰 테이블</strong> 조인 시 유용하다.</p>
</li>
<li><p><strong>해시 테이블 생성</strong>: 작은 테이블을 기반으로 해시 테이블 생성 후 큰 테이블과 매칭하는 방식이다.</p>
</li>
<li><p><strong>메모리 사용</strong>: 해시 테이블 생성에 메모리 의존으로 메모리 사용량에 영향을 받는다.</p>
</li>
</ol>
<h4 id="heading-kirsnqxsojaqkjogkirrjidrn4kg642w7j207yswioyymoumrcdtmqjsnkjsoieqkuydtoulpc4g7j24642x7iqkioyxhuydtouphcdruadrpbgg7kgw7j24ioyimo2wieydtcdqsidriqxtlzjri6qu"><strong>장점</strong>: <strong>대량 데이터 처리 효율적</strong>이다. 인덱스 없이도 빠른 조인 수행이 가능하다.</h4>
<h4 id="heading-build-input-pga"><strong>단점</strong>: <strong>메모리 크기에 의존</strong>한다. 즉 작은 테이블(<strong>Build Input)</strong>이 PGA 메모리에 들어갈 정도로 작아야한다. 해시 함수에 의한 추가 작업 필요하다.</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733366099053/b0c10be6-cf94-4313-831d-b1a59c39b5af.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Core Elements of PL/SQL: Cursor, Stored Procedure, and Function]]></title><description><![CDATA[Contents
1️⃣커서(Cursor)2️⃣저장 프로시저 (Stored Procedure)3️⃣함수 (Function)

오늘은 함수와 프로시저에 대해 공부하는데 그전에 커서에 대해서 먼저 공부해본다. 커서는 데이터베이스에 존재하는 독특한 개념이다. 데이터베이스에서 결과 집합을 한 행씩 처리할 수 있도록 제공하는 특별한 도구라고 할 수 있다. 이를 통해 대량의 데이터를 제어하고 관리할 수 있게 된다.
그 후 함수와 프로시저를 학습할 때는 다음...]]></description><link>https://heesu.tech/core-elements-of-plsql-cursor-stored-procedure-and-function</link><guid isPermaLink="true">https://heesu.tech/core-elements-of-plsql-cursor-stored-procedure-and-function</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Wed, 04 Dec 2024 13:39:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733319534972/f830917f-ed22-47d4-9380-6b272e9d7450.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p><strong>Contents</strong></p>
<p><strong>1️⃣</strong>커서(Cursor)<br /><strong>2️⃣</strong>저장 프로시저 (Stored Procedure)<br /><strong>3️⃣</strong>함수 (Function)</p>
<hr />
<p>오늘은 함수와 프로시저에 대해 공부하는데 그전에 커서에 대해서 먼저 공부해본다. 커서는 데이터베이스에 존재하는 독특한 개념이다. 데이터베이스에서 결과 집합을 한 행씩 처리할 수 있도록 제공하는 특별한 도구라고 할 수 있다. 이를 통해 대량의 데이터를 제어하고 관리할 수 있게 된다.</p>
<p>그 후 함수와 프로시저를 학습할 때는 다음 포인트를 중점적으로 비교하며 접근하면 좋다:</p>
<ul>
<li><p><strong>함수</strong>는 반환값이 있고, 보통 특정 계산이나 값을 반환하기 위해 사용된다.</p>
</li>
<li><p><strong>프로시저</strong>는 반환값이 없거나, OUT 매개변수를 사용하여 값을 반환하며, 주로 비즈니스 로직을 수행하는 데 적합하다.</p>
</li>
</ul>
<hr />
<h2 id="heading-1cursor"><strong>1️⃣</strong>커서(Cursor)</h2>
<h3 id="heading-cursor">커서(Cursor) - 데이터베이스 결과집합 처리</h3>
<p><strong><mark>💡요약: </mark> Cursor</strong>는 데이터베이스의 SELECT 결과를 <strong>순차적으로 탐색</strong>하며 데이터를 처리할 수 있는 도구이다. SELECT문의 결과는 항상 <strong>Result Set</strong>(결과집합) 형태로 반환되며, 이를 다루기 위해 커서를 사용하게 된다. 커서를 통해 데이터를 한 행씩 처리하면서, 원하는 로직을 적용할 수 있게 된다.</p>
<h4 id="heading-result-set">✅ <strong>결과집합 (Result Set)</strong></h4>
<p><strong>Result Set</strong>: SELECT문을 실행하면 관계형 데이터 베이스를 다루고 있기 때문에 SELECT문 관계형 데이터 베이스인 테이블, 일부 부분 집합을 가져오게 된다. 혹은 두 개의 테이블을 합쳐 조인된 테이블을 가져오기도 한다. 그래서 SELECT문을 실행한 결과는 항상 집합의 형태로 나타나게 된다. employee테이블에서 조건을 가지고 데이터를 가져오게 되면 "결과집합"의 형태로 출력 된다.</p>
<p>즉 결과 집합은 SELECT문이 반환하는 데이터의 집합이다. 예를 들어, <code>employee</code> 테이블에서 조건을 이용해 데이터를 조회하면, 결과는 테이블 형태로 반환된다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733307104685/8d95ab56-0949-453b-91e4-46b88615fafe.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-cursor-1">✅ <strong>커서(Cursor)</strong></h4>
<p>우리가 지금까지 배운 내용은 결과 집합을 화면에 보여주는 것을 배웠다. 나타난 결과 집합을 출력만 하는 것이 아닌 각각의 데이터 결과를 다뤄 볼 수 있을까? 데이터를 다루려고 하면 그 결과를 어딘가에 store해야 한다. 프로그래밍 언어로 치면 변수 같은 곳에 저장을 해야 그 데이터를 다룰 수 있게 된다. select문의 조회 명령문을 통해 가져온 결과집합을 어딘가에 넣어두고 처리한다에서 출발한 개념이 "커서"이다. 즉 레코드 각각에 대한 개별적인 처리가 가능한 결과 집합의 확장이 된다.</p>
<ul>
<li>결과를 저장한 뒤, <strong>한 행(Row)씩 처리</strong>할 수 있다.</li>
</ul>
<ul>
<li>각 레코드에 개별적으로 접근하여 원하는 작업을 수행할 수 있도록 도와준다.<br />  예)결과집합에서 <strong>SCOTT(7788)</strong> 행을 읽은 뒤 다음 행으로 이동.</li>
</ul>
<h4 id="heading-role-of-cursor">✅ <strong>커서의 역할 (Role of Cursor)</strong></h4>
<ul>
<li><p>데이터를 <strong>순서대로 가져와</strong> 프로그래밍 언어에서 처리 가능하게 한다.</p>
</li>
<li><p>데이터를 화면에 단순히 출력하는 것이 아니라 <strong>조작, 계산, 조건 처리</strong>를 할 수 있다.</p>
</li>
<li><p><strong>레코드 단위</strong>로 데이터를 다룰 수 있어 효율적이다.</p>
</li>
</ul>
<hr />
<h3 id="heading-cursor-1-1"><strong>커서(Cursor) #1: 데이터베이스에서 명시적/묵시적 커서의 이해</strong></h3>
<p><strong>✅ 커서의 종류 (Types of Cursor)</strong></p>
<ol>
<li><p><strong>명시적 커서 (Explicit Cursor)</strong>: 우리가 일반적으로 커서라고 하면 명시적 커서를 일컫는다. "이러한 커서 A를 선언하였습니다. 그리고 여기에 SELECT문의 결과를 가지고 오세요" 이렇게 사용자가 변수를 정리하듯이 커서를 선언한다. 그리고 가져온 결과에 대해 각각의 레코드에 대해 하나씩 데이터를 가져와서 처리한다. 명시적으로 선언하고 사용하는 것이다.  </p>
<ul>
<li>사용자가 직접 선언하고 관리하는 커서로, SELECT문의 결과를 다루기 위해 선언부터 처리까지 모든 과정을 사용자가 제어한다.</li>
</ul>
</li>
</ol>
<ul>
<li><strong>예시</strong>: <code>CURSOR emp_csr IS SELECT * FROM employees;</code>  </li>
</ul>
<ol start="2">
<li><p><strong>묵시적 커서 (Implicit Cursor)</strong>: 반면에 묵지적 커서는 사용자에게는 보이진 않는다. 오라클이 알아서 최근에 select문한 쿼리 결과을 임시적으로 가지고있는 내부적인 커서이다. ‘SQL’ 이라는 이름으로 속성에 접근할 수 있다. 맨 마지막에 실행된 결과값, 즉 항상 최근에 실행된 SQL 문장에 대한 커서를 가지고 있다고 생각하면 되겠다.  </p>
<ul>
<li>오라클이 내부적으로 자동 생성하는 커서로, SELECT문 또는 DML(INSERT, UPDATE, DELETE) 실행 시 최근 실행된 결과를 임시적으로 저장한다.</li>
</ul>
</li>
</ol>
<ul>
<li><strong>속성 접근</strong>: <code>SQL%ROWCOUNT</code>, <code>SQL%NOTFOUND</code> 등을 사용해 상태를 확인할 수 있다.</li>
</ul>
<hr />
<h3 id="heading-cursor-2-explicit-cursor"><strong>커서(Cursor) #2:</strong> 명시적 커서 (Explicit Cursor)</h3>
<p><strong>✅명시적 커서 처리 순서 (Explicit Cursor Workflow)</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733307559749/38c82eda-d0d3-47a7-ad59-76dc2595c02f.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>커서 선언 (DECLARE)</strong>: 커서는 당연히 사용하기 전에 먼저 선언되어야 한다. 커서 선언은 변수 선언과 마찬가지로 선언된 커서는 한 개의 이름이 할당되고 SELECT 문과 연결된다.</p>
<ul>
<li><strong>예시</strong>: <code>CURSOR emp_csr IS SELECT employee_id FROM employees;</code></li>
</ul>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733309096241/e637c2b7-66e7-4d21-91f4-6b2df4a62257.png" alt class="image--center mx-auto" /></p>
<hr />
<ol start="2">
<li><strong>커서 열기 (OPEN)</strong>: 커서를 "연다"라는 뜻은 앞에서 커서로 정의된 쿼리문을 실행시키는 것을 뜻한다. <strong>테이블에 있는 데이터를 커서로 가져오는 명령어이다.</strong> 실행 시킨후 해당 커서로 결과 집합을 가져온다. 라고 이해하면 되겠다.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733309192265/add3c3bc-e950-4270-8334-fc094264b148.png" alt class="image--center mx-auto" /></p>
<ul>
<li><strong>예시</strong>: <code>OPEN emp_csr;</code></li>
</ul>
<hr />
<ol start="3">
<li><strong>패치 (FETCH)</strong>: 결과 집합이 커서라는 변수에 들어있는데 쿼리의 결과에 접근하여 그 데이터를 하나씩 가져오는 것이다. "하나씩"가져오는 것이 중요하다.<br /> 한번 패치 후 그 다음 패치 가져오고, 다시 패치 후 그 다음 패치 가져온다. 결과 집합의 5개의 레코드가 있 으면 5번 패치가 가능하다. 결과집합에서 한 행씩 데이터를 가져와 변수에 저장한다.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733309379542/4e6fc4c6-46c2-4c21-b343-6dc3b44b9626.png" alt class="image--center mx-auto" /></p>
<ul>
<li><strong>예시</strong>: <code>FETCH emp_csr INTO emp_id;</code></li>
</ul>
<hr />
<ol start="4">
<li><strong>커서 닫기 (CLOSE)</strong>: 패치 후 결과 집합이 empty 가 된다면 커서를 닫고 자원을 반환하게 된다. 결과 집합을 갖고서 첫번째 레코드부터 마지막 레코드까지 하나씩 패치하여 데이터를 하나씩 가져오게된다. 이것이 명시적 커서의 처리방법이다. 커서를 닫고 자원을 해제한다.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733309505147/0a73d300-fd86-4511-bd51-00d57b8bbee5.png" alt class="image--center mx-auto" /></p>
<ul>
<li><strong>예시</strong>: <code>CLOSE emp_csr;</code></li>
</ul>
<hr />
<p>✅ 다시 한번 정리 하자면 selelct문의 결과는 결과를 그냥 보여줄 뿐이다. 그런데 이 결과집합의 각각의 레코드에 대해 무언가 해보고싶다면 커서를 이용하게 된다.</p>
<hr />
<p><strong>✅ 명시적 커서 예제 (Explicit Cursor Example)</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span> <span class="hljs-comment">-- 커서 선언(SELECT문과 연결)</span>
   <span class="hljs-keyword">CURSOR</span> emp_csr <span class="hljs-keyword">IS</span>
      <span class="hljs-keyword">SELECT</span> employee_id <span class="hljs-keyword">FROM</span> employees
      <span class="hljs-keyword">WHERE</span> department_id = <span class="hljs-number">100</span>;

   emp_id employees.employee_id%TYPE;

<span class="hljs-keyword">BEGIN</span>
   <span class="hljs-keyword">OPEN</span> emp_csr; <span class="hljs-comment">-- 커서 열기 (커서로 정의된 쿼리 실행)</span>

   LOOP
      FETCH emp_csr INTO emp_id; <span class="hljs-comment">--패치, 현재 데이터 행을 한 행씩 OUTPUT변수에반환</span>
      EXIT WHEN emp_csr%NOTFOUND;
      DBMS_OUTPUT.PUT_LINE(emp_id);
   <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;

   CLOSE emp_csr; <span class="hljs-comment">-- 커서닫기, 커서 사용을 마치고 자원을 반납</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733309727718/e823d815-17d0-4c34-a2bf-f77b90f82c94.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>커서 선언</strong>: <code>CURSOR emp_csr IS SELECT employee_id FROM employees WHERE department_id = 100;</code></p>
<ul>
<li><code>department_id</code>가 100인 직원들의 <code>employee_id</code>를 가져오는 커서를 선언한다.</li>
</ul>
</li>
<li><p><strong>커서 열기</strong>: <code>OPEN emp_csr;</code></p>
<ul>
<li>커서가 정의되었으니 이제 오픈을 해야한다. 이 오픈 명령어는 커서의 질의문을 수행하고 그 결과집합을 커서에 저장하게 된다. 그 다음 LOOP를 작동한다.</li>
</ul>
</li>
<li><p><strong>데이터 처리</strong>: <code>FETCH emp_csr INTO emp_id;</code></p>
<ul>
<li><p>결과집합에서 한 행씩 데이터를 <code>emp_id</code> 변수에 저장한다. 또한 항상 맨 위에 있는 레코드 부터 접근하게 된다.</p>
</li>
<li><p>더이상 데이터가 없을때까지(%NOTFOUND) emp_id에 store하게 된다.</p>
</li>
</ul>
</li>
<li><p><strong>커서 닫기</strong>: <code>CLOSE emp_csr;</code></p>
<ul>
<li>사용이 끝난 커서를 닫고 자원 반환하게 된다.</li>
</ul>
</li>
<li><p><strong>실행 결과:</strong> 110, 109, 108... 등의 숫자가 출력되며, 이는 <code>department_id</code>가 100인 직원들의 <code>employee_id</code>이다.</p>
</li>
</ol>
<hr />
<p><strong>✅명시적 커서의 FOR .. LOOP문 사용 (FOR Loop with Cursor)</strong></p>
<p>FOR 루프를 사용하면 OPEN, FETCH, CLOSE 과정을 자동으로 처리하게 된다.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
   <span class="hljs-keyword">CURSOR</span> emp_csr <span class="hljs-keyword">IS</span>
      <span class="hljs-keyword">SELECT</span> employee_id <span class="hljs-keyword">FROM</span> employees
      <span class="hljs-keyword">WHERE</span> department_id = <span class="hljs-number">100</span>;

<span class="hljs-keyword">BEGIN</span>
   <span class="hljs-keyword">FOR</span> item <span class="hljs-keyword">IN</span> emp_csr <span class="hljs-keyword">LOOP</span>
      DBMS_OUTPUT.PUT_LINE(item.employee_id);
   <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p>FOR 루프는 명시적 커서를 간결하게 작성할 수 있는 방법이다.</p>
</li>
<li><p><code>item</code> 변수를 통해 레코드를 자동으로 처리하며, 별도의 <code>OPEN</code>, <code>FETCH</code>, <code>CLOSE</code>가 필요 없게된다.</p>
</li>
<li><p>커서는 for문/ loop문 하고 같이 사용하는 경우가 굉장히 많다. 어떤 조건을 줄 때에는 loop가 많이 사용되고 조건 없이 커서의 모든 레코드를 탐색 할때는 for문을 사용하는 것이 유리하다.</p>
</li>
</ul>
<hr />
<h3 id="heading-cursor-3-implicit-cursor"><strong>커서(Cursor) #3:</strong> 묵시적 커서 (Implicit Cursor) - 오라클 내부의 자동 커서 처리</h3>
<p><strong><mark>💡요약:</mark></strong> <strong>Implicit Cursor</strong> (묵시적 커서)는 오라클 데이터베이스에서 자동으로 생성되며, 최근 실행된 SQL 문장(특히 DML 문장: INSERT, UPDATE, DELETE)에 대한 정보를 저장한다. 이는 내부적으로 사용되며, 명시적으로 선언하지 않아도 자동으로 생성된다.  </p>
<p><strong>✅ 묵시적 커서의 특징 (Key Features)</strong></p>
<ul>
<li><p><strong>묵시적 커서와 사용자의 접점</strong></p>
<ul>
<li><p><strong>묵시적 커서</strong>는 오라클 내부에서 자동으로 처리되며, 사용자가 직접 접하는 경우는 많지 않다.</p>
</li>
<li><p>주로 간단한 상태 확인(예: SQL 실행 성공 여부)이나 영향을 받은 행의 수를 확인하는 데 사용된.</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>자동 생성 (Automatically Generated)</strong></p>
<ul>
<li><p>묵시적 커서는 사용자가 선언하지 않아도 <strong>SQL 실행 시 자동으로 생성</strong>됩니다.</p>
</li>
<li><p>명시적 커서와 달리 프로그래밍적으로 선언, 열기, 닫기 과정이 필요하지 않습니다.</p>
</li>
</ul>
</li>
<li><p><strong>DML 문과 주로 사용 (Used with DML Statements)</strong></p>
<ul>
<li><strong>INSERT, UPDATE, DELETE</strong> 문 실행 시 생성되며, 쿼리 결과를 임시 저장합니다.</li>
</ul>
</li>
<li><p><strong>최근 SQL 문 정보 저장 (Tracks Last SQL Statement)</strong></p>
<ul>
<li>항상 <strong>최근 실행된 SQL 문</strong>에 대한 정보를 속성(SQL%ROWCOUNT, SQL%FOUND, SQL%NOTFOUND 등)을 통해 접근할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>간단한 작업 처리에 유용 (Simple Operations)</strong></p>
<ul>
<li>묵시적 커서는 데이터를 조회하거나 변경한 결과를 빠르게 확인하기에 적합합니다.</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅ 묵시적 커서 속성 (Implicit Cursor Attributes)</strong></p>
<ul>
<li><p><code>SQL%ROWCOUNT</code>: <strong>영향받은 행의 수 (Number of Rows Affected)</strong></p>
<ul>
<li><p>최근 실행된 SQL 문(INSERT, UPDATE, DELETE 등)에 의해 영향을 받은 행의 개수를 나타냅니다.</p>
</li>
<li><p>다른 SQL 문을 실행하면 값이 업데이트됩니다.</p>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-sql">DBMS_OUTPUT.PUT_LINE('Rows affected: ' || SQL%ROWCOUNT);
</code></pre>
<hr />
<ul>
<li><p><code>SQL%FOUND</code>: <strong>SQL 실행 성공 여부 (SQL Execution Success)</strong></p>
<ul>
<li>최근 SQL 문에 영향을 받은 행이 <strong>1개 이상일 경우 TRUE</strong>를 반환한다.</li>
</ul>
</li>
</ul>
<pre><code class="lang-sql">IF SQL%FOUND THEN
   DBMS_OUTPUT.PUT_LINE('Rows affected.');
<span class="hljs-keyword">END</span> <span class="hljs-keyword">IF</span>;
</code></pre>
<hr />
<ul>
<li><p><code>SQL%NOTFOUND</code>: <strong>SQL 실행 실패 여부 (SQL Execution Failure)</strong></p>
<ul>
<li><code>SQL%FOUND</code>와 반대의 개념이다. 최근 SQL 문에 영향을 받은 행이 <strong>없을 경우 TRUE</strong>를 반환한다.</li>
</ul>
</li>
</ul>
<pre><code class="lang-sql">IF SQL%NOTFOUND THEN
   DBMS_OUTPUT.PUT_LINE('No rows affected.');
<span class="hljs-keyword">END</span> <span class="hljs-keyword">IF</span>;
</code></pre>
<hr />
<ul>
<li><p><code>SQL%ISOPEN</code>: <strong>커서 열림 상태 (Cursor Open Status)</strong></p>
<ul>
<li>묵시적 커서는 자동으로 닫히기 때문에 항상 FALSE를 반환하게 된다.</li>
</ul>
</li>
</ul>
<pre><code class="lang-sql">IF SQL%ISOPEN THEN
   DBMS_OUTPUT.PUT_LINE('Cursor is open.');
<span class="hljs-keyword">END</span> <span class="hljs-keyword">IF</span>;
</code></pre>
<hr />
<p><strong>✅ 묵시적 커서 예제 (Implicit Cursor Example):</strong> 묵시적 커서를 사용자가 접하게 되는 경우는 많이 없기 때문에 간단하게 살펴보고 넘어가도록 한다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733311425043/d98bf8fd-412b-4b69-a24c-dc829aea37b7.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-1">1. <strong>테이블 초기화 및 생성</strong></h4>
<pre><code class="lang-sql"><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> employees_temp;
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> employees_temp <span class="hljs-keyword">AS</span>
<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> employees;
</code></pre>
<ul>
<li><p><code>employees_temp</code>라는 임시 테이블이 이미 존재할 수 있으므로 삭제(<code>DROP TABLE</code>) 후 새로 정의하게 된다.</p>
</li>
<li><p><code>CREATE TABLE</code> 문을 사용해 <code>employees</code> 테이블의 데이터를 복사한다.</p>
</li>
</ul>
<h4 id="heading-2-delete-sqlrowcount">2. <strong>DELETE문과 SQL%ROWCOUNT 사용</strong></h4>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
   mgr_no <span class="hljs-built_in">NUMBER</span>(<span class="hljs-number">6</span>) := <span class="hljs-number">122</span>;
<span class="hljs-keyword">BEGIN</span>
   <span class="hljs-keyword">DELETE</span> <span class="hljs-keyword">FROM</span> employees_temp <span class="hljs-keyword">WHERE</span> manager_id = mgr_no;

   DBMS_OUTPUT.PUT_LINE(
      'Number of employees deleted: ' || TO_CHAR(SQL%ROWCOUNT)
   );
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p>DECLARE후 <code>mgr_no</code> 를 변수로 선언한뒤 이 변수에 <code>122</code>를 할당한다.</p>
</li>
<li><p><code>DELETE</code> 문 수행한다. 이를 통해 <code>employees_temp</code> 테이블에서 <code>manager_id</code>가 <code>122</code>인 레코드를 삭제하게 된다. 6명이면 6명, 9명이면 9명이 삭제가 될 것이다.</p>
</li>
<li><p>이럴때 방금 수행한 명령문인 DELECT에 <code>SQL%ROWCOUNT</code> 커서가 잠시 열려 값을 갖고 있는데 <code>SQL%ROWCOUNT</code>의 역할은 방금 수행한 질의문의 영향을 받은 로우의 수를 출력해주는 것이다. 이를 사용해 영향을 받은 행의 개수를 출력하게 된다.</p>
</li>
</ul>
<h4 id="heading-3">3. <strong>실행 결과:</strong> 첨부된 세 번째 이미지를 참고하면,</h4>
<ul>
<li><pre><code class="lang-sql">  Number of employees deleted: 8
  PL/SQL 프로시저가 성공적으로 완료되었습니다.
</code></pre>
</li>
<li><p><code>manager_id = 122</code>인 레코드 8개가 삭제되었다.</p>
</li>
<li><p>이때 이 (SQL%ROWCOUNT) sql 묵시적 커서를 지칭하는 역할을 한다.</p>
</li>
</ul>
<hr />
<p><strong><mark>💡PL/SQL 서브프로그램(Subprogram)</mark></strong></p>
<ul>
<li><strong>서브프로그램(Subprogram)</strong>은 메인 프로그램의 작업을 분리하고 반복 작업을 모듈화한 프로그램 블록이다.</li>
</ul>
<ul>
<li><p>PL/SQL에서는 <strong>저장 프로시저(Stored Procedure)</strong>, <strong>함수(Function)</strong>, <strong>패키지(Package)</strong>, <strong>트리거(Trigger)</strong>를 <strong>서브프로그램(Subprogram)</strong>이라고 부른다. 이들은 각각 특정 목적에 맞게 사용된다.</p>
</li>
<li><p>서브프로그램은 메인 프로그램에서 자주 사용되는 기능이나 반복적인 작업을 <strong>모듈화</strong>하여 정의한 후, 필요할 때 호출하여 사용할 수 있는 프로그램 블록을 뜻한다.</p>
</li>
<li><p>메인 프로그램의 작업을 분리하여 <strong>효율성을 높이고 유지보수를 용이하게</strong> 하는 장점이 있다.</p>
</li>
<li><p>데이터베이스 내부 구조를 숨기고, 서브프로그램만 노출하여 데이터 보호할 수도 있다.</p>
</li>
</ul>
<p><strong><mark>💡저장 프로시저와 함수의 사용 차이</mark></strong></p>
<ul>
<li><p><strong>프로그래밍 언어</strong>에서는 주로 계산이나 값 반환을 위한 <strong>함수(Function)</strong>를 많이 사용한다.</p>
</li>
<li><p><strong>데이터베이스 프로그래밍</strong>에서는 데이터 조작이나 상태 변경 작업이 많아 <strong>저장 프로시저(Stored Procedure)</strong>가 더 자주 사용된다.</p>
</li>
</ul>
<hr />
<h2 id="heading-2-stored-procedure"><strong>2️⃣</strong>저장 프로시저 (Stored Procedure)</h2>
<p><strong><mark>💡요약: </mark> Stored Procedure</strong>는 데이터베이스 내에서 반복적으로 수행되는 작업을 효율적으로 처리하기 위해 사용된다. <strong>재사용성</strong>과 <strong>성능 향상</strong>이 주요 장점으로, 잘 튜닝된 SQL을 미리 컴파일해 저장하고 반복적으로 호출할 수 있는 장점이 있다. DML 작업(INSERT, UPDATE, DELETE)뿐만 아니라, 조건부 로직과 파라미터를 활용한 복잡한 작업도 처리 가능하다.</p>
<hr />
<p><img src="https://i1.wp.com/www.techmixing.com/wp-content/uploads/2009/08/sql-stored-procedure-e1549017779977.jpg?fit=265%2C320&amp;ssl=1" alt="SQLrevisited: How to use Stored Procedure in SQL or Database? Pros and Cons  with Example" /></p>
<p><strong>✅ 저장 프로시저의 정의 (Stored Procedure)</strong></p>
<ul>
<li><p>데이터베이스 내에서 사전에 작성된 PL/SQL 문장의 집합으로, 특정 작업을 수행하기 위해 데이터베이스 서버에 저장된 프로그램 블록이다.</p>
</li>
<li><p>주로 데이터베이스의 <strong>상태 변경</strong> 작업(INSERT, UPDATE, DELETE)에 사용된다.</p>
</li>
<li><p>"일련의 PL/SQL 문장을 사전에 작성하고 컴파일한 다음 실행할 수 있는 상태로 만들어 데이터베이스 서버에 저장해 놓은 프로시저."</p>
</li>
</ul>
<hr />
<p><strong>✅저장 프로시저의 활용</strong></p>
<ol>
<li><p><strong>DML 문장 작업의 효율화</strong></p>
<ul>
<li><p>테이블의 레코드를 <strong>삽입(INSERT)</strong>, <strong>수정(UPDATE)</strong>, <strong>삭제(DELETE)</strong>할 때 사용.</p>
</li>
<li><p>예를 들어, 특정 조건에 따라 급여를 수정하거나 특정 데이터 그룹을 삭제하는 작업을 자동화할 수 있다.</p>
</li>
</ul>
</li>
<li><p><strong>파라미터를 이용한 동적 데이터 처리</strong></p>
<ul>
<li><p>저장 프로시저는 입력 파라미터를 통해 데이터를 동적으로 처리하고, 필요 시 결과값을 반환할 수 있다.</p>
</li>
<li><p>호출 시 파라미터만 전달하면, 프로시저 내부에서 해당 데이터를 사용해 작업을 수행할 수 있다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅ 저장 프로시저를 사용하는 이유와 장점</strong></p>
<h4 id="heading-1-1">1. <strong>재사용성</strong></h4>
<ul>
<li><p>SQL 문장을 매번 작성할 필요 없이, 자주 사용하는 DML 문장을 저장 프로시저로 정의하여 재사용 가능하기 때문이다.</p>
</li>
<li><p>잘 작성된 프로시저는 <strong>실수를 줄이고</strong> 다음에 또 만들지 않아도 되기 때문에 <strong>유지보수를 용이하게</strong> 한다.</p>
</li>
</ul>
<h4 id="heading-2">2. <strong>성능 최적화</strong></h4>
<ul>
<li><p><strong>미리 컴파일된 상태로 저장:</strong> 저장 프로시저는 작성 후 컴파일되어 데이터베이스에 저장되므로, 실행 시 <strong>실행 계획을 다시 계산하지 않아도 된다.</strong> 실행 계획이 변경되지 않으므로, 매번 새로 컴파일할 필요 없이 바로 실행 가능하다는 뜻이다.</p>
</li>
<li><p><strong>성능 향상</strong>: 질의문 실행 시 매번 새로 작성하는 것보다, 미리 튜닝된 저장 프로시저를 사용하면 성능이 높아지게 된다.</p>
</li>
</ul>
<h4 id="heading-3-1">3. <strong>유지보수성</strong></h4>
<ul>
<li><strong>중앙 관리 가능</strong>: 저장 프로시저를 데이터베이스에 저장해 놓으면, 로직이 한 곳에 통합되므로 유지보수가 용이해진다. 변경이 필요한 경우, 저장 프로시저를 수정하면 된다.</li>
</ul>
<h4 id="heading-4">4. <strong>보안 강화</strong></h4>
<ul>
<li><p>사용자에게 저장 프로시저만 노출하여 데이터베이스 구조와 로직을 숨길 수 있다.</p>
</li>
<li><p>개발자들이 테이블에 직접 접근하지 않고 저장 프로시저를 통해 작업을 수행하도록 제한하는 것이다.</p>
</li>
</ul>
<h4 id="heading-5">5. <strong>파라미터 활용</strong></h4>
<ul>
<li>저장 프로시저는 <strong>입력(IN)</strong>, <strong>출력(OUT)</strong>, <strong>입출력(IN OUT)</strong> 파라미터를 지원하여, 유연한 데이터 처리가 가능하다.</li>
</ul>
<hr />
<p><strong>✅ 저장 프로시저와 함수의 차이</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733315970771/289dbf44-f3eb-46cc-a1e0-d6d4305b04ec.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-stored-procedure-1">저장 프로시저(Stored Procedure) #1: 구조와 실행</h3>
<p><strong><mark>💡요약: </mark> 저장 프로시저</strong>는 데이터베이스 상태를 변경하거나 반복 작업을 효율적으로 처리하기 위해 사용되는 서브프로그램이다. 첨부된 이미지들을 참고하여 저장 프로시저의 구조와 실행에 대해 자세히 살펴보자</p>
<hr />
<p><strong>✅ Database Applications와 Stored Procedure의 관계</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733316053296/ec267add-79ec-4559-ac0a-fda8168e0c33.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>왼쪽의 Database Applications</strong>:</p>
<ul>
<li><p>데이터베이스 프로그램 코드들이 전시되어 있다. 이 중에서 stored procedure를 호출하게 된다.</p>
</li>
<li><p><code>hire_employees(...)</code>: 이 함수는 직원 고용 정보를 <code>employee</code> 테이블에 한명의 개인을 삽입하는 저장 프로시저로 보인다</p>
</li>
<li><p>예를 들어, 이것을 매번쓰는 것이 아닌 새로운 직원 정보를 추가할 때마다 이 저장 프로시저를 호출하여 동일한 코드를 반복 작성하지 않아도 된다. stored procedure begin 과 end사이에 들어가게 된다.</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>Stored Procedure의 주요 특징</strong>:</p>
<ul>
<li><p>BEGIN과 END 사이에 데이터베이스 상태를 변경하는 명령문들이 포함된다</p>
</li>
<li><p>프로그램 코드가 저장 프로시저를 호출하여 작업을 위임한다.</p>
</li>
<li><p>함수하고 비슷하긴 하지만 함수는 주로 데이터베이스의 상태를 변경하는 것에 사용하진 않는다. 함수는 주로 데이터 베이스에서 값을 가지고와서 총 합 계산, 평균 계산, 가장 큰 값 가져오기 등 데이터베이스에서 어떤 값을 가져와서 필요한 값을 return 할때 함수를 많이 쓰는 반면, 저장프로시져는 데이터베이스의 상태를 변경, 위임하는데 사용한다.</p>
</li>
<li><p>위에서 언급된 저장 프로시저의 장점 중 “보안성” 설명을 추가하자면 stored procedure의 권한은 dba에게 있다면 나머지 개발자들은 database applications만 불러서 사용하게 된다. 그렇게 되면 실제 데이터구조가 어떤지, 어떤 데이터를 가지고있는지 개발자들은 접근할 필요가 없게 된다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅저장 프로시저 정의 및 실행 (Defining and Executing a Stored Procedure)</strong></p>
<p>문법 같은 경우는 좋은 예제를 가지고 조금씩 수정하면서 사용하는 것이 제일 좋다. 오라클의 가장 좋은 예제는 오라클 사이트에서 찾아볼 수 있다. 그런 코드를 참고해서 필요한 코드를 작성하면 되겠다.</p>
<h4 id="heading-1-2">1. <strong>저장 프로시저 정의</strong></h4>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">PROCEDURE</span> emp_register
<span class="hljs-keyword">IS</span>
<span class="hljs-keyword">BEGIN</span>
   <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> employees (employee_id, first_name, last_name, email, hire_date, job_id)
   <span class="hljs-keyword">VALUES</span> (EMPLOYEES_SEQ.NEXTVAL, <span class="hljs-string">'HONGSEOK'</span>, <span class="hljs-string">'NA'</span>, <span class="hljs-string">'hsna99@cuk.edu'</span>, <span class="hljs-keyword">SYSDATE</span>, <span class="hljs-string">'IT_PROG'</span>);
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>CREATE PROCEDURE</strong>: <code>emp_register</code>라는 이름의 저장 프로시저를 정의한다.</p>
</li>
<li><p><strong>BEGIN ... END</strong>: 데이터베이스 상태를 변경하는 명령문을 포함한다.</p>
<ul>
<li><p><code>INSERT INTO employees</code>:</p>
<ul>
<li><p>새로운 직원 정보를 <code>employees</code> 테이블에 삽입한다.</p>
</li>
<li><p><strong>EMPLOYEES_SEQ.NEXTVAL</strong>: 시퀀스의 가장 최근값을 가져오란 뜻이다. <code>employee_id</code>의 다음 값을 생성.</p>
</li>
<li><p><code>first_name</code>, <code>last_name</code>, <code>email</code>, <code>hire_date</code>, <code>job_id</code>이 값들을 INSERT INTO 에 넣는다.</p>
</li>
</ul>
</li>
<li><p>이 저장 프로시저는 단순한 예제로, 실제로는 동적 데이터를 처리하도록 파라미터를 추가해 활용할 수 있다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-2-1">2. <strong>저장 프로시저 실행</strong></h4>
<ul>
<li><p>저장 프로시저를 호출하여 실행한다.</p>
<pre><code class="lang-sql">  EXEC emp_register;
</code></pre>
</li>
</ul>
<hr />
<h4 id="heading-3-2">3. <strong>결과 확인</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733316658497/ddc50dce-e6ce-4881-a85e-c71d7a2f3924.png" alt class="image--center mx-auto" /></p>
<ul>
<li>이미지를 보면, <code>emp_register</code>를 실행한 후 데이터가 <code>employees</code> 테이블에 삽입되었음을 확인할 수 있다.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733316759173/94cbda05-082e-4ddf-845f-a1cc9b637613.png" alt class="image--center mx-auto" /></p>
<p><strong><mark>💡저장 프로시저 정리:</mark></strong></p>
<ul>
<li>데이터베이스 상태를 변경하는 작업(INSERT, UPDATE, DELETE)을 효율적으로 처리하기 위해 사용된다.</li>
</ul>
<ul>
<li><p>프로그램 코드에서 저장 프로시저를 호출하여 반복적인 작업을 간소화할 수 있다.</p>
</li>
<li><p>보안 및 성능 이점이 있으며, 개발자는 저장 프로시저를 호출하여 데이터베이스 작업을 위임받아 수행한다.</p>
</li>
<li><p>예제에서 <code>emp_register</code>는 직원 정보를 <code>employees</code> 테이블에 삽입하는 간단한 저장 프로시저를 보여준다.</p>
</li>
</ul>
<hr />
<h3 id="heading-stored-procedure-2-in">저장 프로시저(Stored Procedure) #2: IN 파라미터 활용</h3>
<p><strong><mark>💡요약: </mark></strong> 저장 프로시저를 사용할 때 <strong>파라미터(IN, OUT, IN OUT)</strong>를 활용하여 데이터베이스 작업을 동적으로 수행할 수 있다. 이번 예제에서는 <strong>IN 파라미터</strong>를 통해 직원 정보를 삽입하는 방법을 살펴본다.</p>
<hr />
<h4 id="heading-1-3">1. <strong>저장 프로시저 정의</strong></h4>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">PROCEDURE</span> emp_register1
   (f_name <span class="hljs-built_in">VARCHAR2</span>, l_name <span class="hljs-built_in">VARCHAR2</span>, e_mail <span class="hljs-built_in">VARCHAR2</span>, j_id <span class="hljs-built_in">VARCHAR2</span>)
<span class="hljs-keyword">IS</span>
<span class="hljs-keyword">BEGIN</span>
   <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> employees (employee_id, first_name, last_name, email, hire_date, job_id)
   <span class="hljs-keyword">VALUES</span> (EMPLOYEES_SEQ.NEXTVAL, f_name, l_name, e_mail, <span class="hljs-keyword">SYSDATE</span>, j_id);
   <span class="hljs-keyword">COMMIT</span>;
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>파라미터 정의</strong>:</p>
<ul>
<li><p>저장 프로시저를 사용할 때는 주로 파라미터를 사용하여 데이터를 준다.</p>
</li>
<li><p>employee 한명을 삽입 하려고 할땐 employee 정보를 줘야 하는데 이것을 IN 파라미터로 주게된다. 한국어로는 매개변수이다.</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><code>f_name</code>, <code>l_name</code>, <code>e_mail</code>, <code>j_id</code>와 같은 입력값을 파라미터로 받아 테이블에 데이터를 삽입한다. 즉 emp_register1을 호출할 때 이 파라미터 값들을 주겠다는 뜻이다.</p>
</li>
<li><p>각 파라미터는 <strong>VARCHAR2 타입</strong>으로 정의되었다.</p>
</li>
</ul>
<ul>
<li><p><strong>INSERT INTO</strong>:</p>
<ul>
<li><p><code>employee_id</code>는 시퀀스 <code>EMPLOYEES_SEQ.NEXTVAL</code>을 사용해 자동으로 생성된다.</p>
</li>
<li><p>나머지 필드는 파라미터로 전달받은 값을 삽입한다.</p>
</li>
</ul>
</li>
<li><p><strong>COMMIT</strong>:</p>
<ul>
<li>데이터 삽입 후 변경 사항을 저장한다.</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-2-2">2. <strong>저장 프로시저 컴파일 및 실행</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733317154073/6f53cdec-9728-4b6e-aa9b-ce1d89283595.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><code>emp_register1</code> 저장 프로시저를 생성하고 컴파일하였다.</p>
</li>
<li><p>컴파일 성공 메시지가 표시되며, 예제에서 빨간색 점선 박스처럼 프로시저가 완성되었음을 확인할 수 있다. 데이터베이스에서 해당 프로시저를 사용할 준비가 완료되었다는 뜻이다.</p>
</li>
</ul>
<hr />
<h4 id="heading-3-3">3.<strong>프로시저 호출 및 실행</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733317336713/9e01a2fa-0f3a-4f22-9524-691417402c37.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><code>EXEC</code>를 사용해 저장 프로시저를 호출하였다.</p>
</li>
<li><p>파라미터 값으로 <code>first_name = 'gildong'</code>, <code>last_name = 'hong'</code>, <code>email = '</code><a target="_blank" href="mailto:hdg@gmail.com"><code>hdg@gmail.com</code></a><code>'</code>, <code>job_id = 'IT_PROG'</code>을 전달한다.</p>
</li>
<li><p>실행 결과: 입력한 데이터를 기반으로 새로운 레코드가 <code>employees</code> 테이블에 삽입 되었다. (빨간 점선 박스참조)</p>
</li>
</ul>
<hr />
<h3 id="heading-stored-procedure-3-out">저장 프로시저(Stored Procedure) #3: OUT 파라미터 활용</h3>
<p><strong><mark>💡요약: </mark></strong> OUT 파라미터도 존재하는데 주로 IN 파라미터를 많이 쓰게 된다. <strong>OUT 파라미터</strong>는 저장 프로시저가 수행된 후 호출한 프로그램이 결과 값을 받을 수 있도록 값을 반환하는 역할을 한다. 이는 함수의 반환값과 비슷하지만, 저장 프로시저는 리턴값이 없으므로 <strong>OUT 파라미터</strong>를 사용하여 결과를 전달하게 된다.</p>
<p><strong>✅ OUT 파라미터의 특징</strong></p>
<ol>
<li><p><strong>결과값 반환:</strong> OUT 파라미터를 통해 저장 프로시저가 완료된 후 호출한 프로그램이 결과값을 받을 수 있다.</p>
</li>
<li><p><strong>리턴값 대신 사용:</strong> 저장 프로시저 자체는 리턴값이 없으므로, OUT 파라미터를 사용하여 값을 반환할 수 있다.</p>
</li>
<li><p><strong>함수와의 차이:</strong> 함수는 단일 값을 반환하지만, 저장 프로시저는 여러 OUT 파라미터를 사용해 여러 값을 반환할 수 있다.</p>
</li>
</ol>
<hr />
<p><strong>✅OUT 파라미터를 사용하는 저장 프로시저의 구조</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733317475315/1216dccf-6895-421d-9f04-fed15b2bbfd1.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>OUT 파라미터</strong></p>
<ul>
<li><p><code>emp_id OUT NUMBER</code>: 호출한 프로그램에 <strong>employee_id</strong> 값을 반환한다.</p>
</li>
<li><p><code>OUT</code> 키워드는 프로시저가 값을 반환할 수 있도록 지정한다.</p>
</li>
</ul>
</li>
<li><p><strong>INSERT INTO</strong></p>
<ul>
<li><p>새로운 직원 정보를 <code>employees</code> 테이블에 삽입.</p>
</li>
<li><p><code>emp_id</code>는 <code>EMPLOYEES_SEQ.NEXTVAL</code>을 사용하여 자동 생성된 값이다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅실행화면</strong></p>
<ul>
<li><p>저장 프로시저를 컴파일하면 성공 메시지가 표시된다.</p>
</li>
<li><p>호출 시 OUT 파라미터로 <code>emp_id</code> 값을 확인할 수 있다.</p>
<ul>
<li>예: <code>209</code> 값이 반환되었음을 확인.</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733317759602/c27e1990-4da6-4a87-a8ee-867d15175ee8.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-stored-procedure-4-drop">저장 프로시저(Stored Procedure) #4: DROP</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733317832230/1f666d05-83f9-4db1-90a4-17d4dec8a23f.png" alt class="image--center mx-auto" /></p>
<ul>
<li>불필요해진 저장 프로시저는 <code>DROP PROCEDURE</code>를 사용해 삭제할 수 있다.</li>
</ul>
<hr />
<h2 id="heading-3-function"><strong>3️⃣</strong>함수 (Function)</h2>
<h3 id="heading-plsql-function">PL/SQL에서 함수(Function)의 특징과 저장 프로시저와의 차이점</h3>
<p>함수와 저장 프로시저는 서로 다른 목적에 맞게 사용되며, 이들의 차이점을 이해하면 더 적합한 데이터베이스 작업을 설계할 수 있다.</p>
<hr />
<p><strong>✅ 함수(Function)의 정의와 특징</strong></p>
<ul>
<li><strong>함수(Function)</strong>는 데이터베이스에서 계산 작업을 수행하고 <strong>결과를 반환(Return)</strong>하는 목적으로 사용된다.</li>
</ul>
<ul>
<li><p>PL/SQL뿐만 아니라 거의 모든 DBMS에서 함수를 지원한다.</p>
</li>
<li><p><strong>주요 목적</strong>:</p>
<ul>
<li><p>함수는 return값이 제일 중요하다. 항상 존재하기 때문이다. 어떤 계산을 하고 계산된 결과를 return하게 된다.</p>
</li>
<li><p>함수(function)는 계산을 수행하여 호출한 애플리케이션에 반환하거나 결과집합에 통합해 넣을 목적으로 사용한다.</p>
</li>
<li><p>DBMS는 문자열 함수, 수학 함수, 집계 함수 등 많은 편리한 함수를 제공하고 사용자가 직접 함수를 정의할 수 있다.</p>
</li>
<li><p>데이터베이스 상태를 변경하지 않고 계산 중심의 작업 수행 호출한 애플리케이션에 반환하거나 결과집합에 통합해 넣을 목적으로 사용한다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅저장 프로시저(Stored Procedure)와의 차이점</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733318207064/15eedc40-eb06-411e-8d97-28dbe81c9f16.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>함수</strong>는 항상 값을 반환한다. (RETURN).</p>
<ul>
<li><p>SELECT 문에 포함되어 호출된다.</p>
</li>
<li><p>계산 용도로 사용하며 데이터베이스 상태를 변경하지 못한다.</p>
</li>
<li><p>함수는 복잡한 계산이 필요한 경우나 급여의 합계 계산, 평균 또는 최대값과 같은 데이터를 조회하여 특정 결과를 도출해야할때 사용하기 적합하다.</p>
</li>
</ul>
</li>
<li><p><strong>저장 프로시저</strong>는 데이터베이스 상태를 변경하는 작업(INSERT, UPDATE, DELETE 등)을 수행다.</p>
<ul>
<li><p>EXEC 또는 CALL로 호출되며, RETURN 값이 없다.</p>
</li>
<li><p>저장 프로시저는 데이터베이스 상태를 변경해야 할때나 새로운 직원 정보 삽입 혹은 오래된 데이터 삭제와 같은 INSERT, UPDATE, DELECTE 와 같은 DML 작업을 수행할 때 사용하기 적합하다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h3 id="heading-function-1-plsql"><strong>함수(Function) #1: PL/SQL 함수의 구조와 실행</strong></h3>
<p><strong>✅ 함수의 구문 형식 (Function Syntax)</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> <span class="hljs-keyword">FUNCTION</span> 함수명(파라미터<span class="hljs-number">1</span> 데이터타입, ...)  
<span class="hljs-keyword">RETURN</span> 데이터타입  
<span class="hljs-keyword">IS</span> [<span class="hljs-keyword">AS</span>]  
   변수 선언부 ...;  
<span class="hljs-keyword">BEGIN</span>  
   프로시저 본문 ...;  
   RETURN 변수;  
EXCEPTION  
   예외처리 ...;  
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>CREATE OR REPLACE</strong>:</p>
<ul>
<li><p>CREATE만 써도 상관없지만</p>
</li>
<li><p>보통은 CREATE OR REPLACE로 혹시 함수가 이미 존재할 경우 새 함수로 대체(Replace)하게 된다.</p>
</li>
<li><p>기존 함수와 중복될 우려가 있는 경우 보통 이 구문을 사용한다.</p>
</li>
</ul>
</li>
<li><p><strong><mark>RETURN 데이터타입: 중요💡</mark></strong></p>
<ul>
<li><p>함수는 반드시 하나의 값을 반환해야 하며, 반환값의 데이터타입을 지정한다.</p>
</li>
<li><p>예: <code>RETURN NUMBER</code>는 숫자 값을 반환.</p>
</li>
</ul>
</li>
<li><p><strong>BEGIN ... END</strong>:</p>
<ul>
<li><p>함수는 BEGIN 과 END사이에서 정의 된다.</p>
</li>
<li><p><code>RETURN</code> 문을 통해 반환값을 명시적으로 지정해야 한다가 함수의 가장 큰 특징이다.</p>
</li>
</ul>
</li>
<li><p><strong>EXCEPTION</strong>:</p>
<ul>
<li>함수 실행 중 발생할 수 있는 예외를 처리하는 블록이다.</li>
</ul>
</li>
</ul>
<hr />
<p><strong>✅ 함수 예제: 직원 급여 반환</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">FUNCTION</span> emp_salaries (emp_id <span class="hljs-built_in">NUMBER</span>)
<span class="hljs-keyword">RETURN</span> <span class="hljs-built_in">NUMBER</span> <span class="hljs-keyword">IS</span>
   nSalaries <span class="hljs-built_in">NUMBER</span>(<span class="hljs-number">9</span>);
<span class="hljs-keyword">BEGIN</span>
   nSalaries := <span class="hljs-number">0</span>;
   <span class="hljs-keyword">SELECT</span> salary <span class="hljs-keyword">INTO</span> nSalaries <span class="hljs-keyword">FROM</span> employees
   <span class="hljs-keyword">WHERE</span> employee_id = emp_id;
   RETURN nSalaries;
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>파라미터</strong>:</p>
<ul>
<li><p><code>emp_id NUMBER</code>: 함수가 입력받는 매개변수.</p>
</li>
<li><p>직원 ID를 기준으로 급여(<code>salary</code>)를 조회.</p>
</li>
</ul>
</li>
<li><p><strong>반환값</strong>:</p>
<ul>
<li><code>RETURN NUMBER</code>: 숫자 데이터타입을 반환.</li>
</ul>
</li>
<li><p><strong>로직</strong>:</p>
<ol>
<li><p><code>nSalaries</code> 변수를 초기화(<code>nSalaries := 0;</code>).</p>
</li>
<li><p><code>SELECT salary INTO nSalaries</code>로 해당 직원의 급여를 조회하여 변수에 저장.</p>
</li>
<li><p><code>RETURN nSalaries</code>를 통해 조회된 급여를 반환.</p>
</li>
</ol>
</li>
</ul>
<h4 id="heading-kirrs7tslyjshleg6rcv7zmukio6"><strong>보안성 강화</strong>:</h4>
<ul>
<li><p>SQL문 으로도 할수 있는데 굳이 함수로 하는 이유이다.</p>
</li>
<li><p>테이블 구조를 노출하지 않고 함수만 노출함으로써 필요한 데이터만 반환하므로 보안성이 강화된다.</p>
</li>
<li><p>권한이 제한된 개발자는 함수만 호출할 수 있어 테이블에 직접 접근하지 못한다.</p>
</li>
</ul>
<hr />
<p><strong>함수 정의 및 호출</strong></p>
<h4 id="heading-kiriniug7zwo7iiyioygleydmcdtm4qg6rcd7lk0ioydneyessoq"><strong>✅ 함수 정의 후 객체 생성</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733318920229/72cd950d-339b-41cd-8ad0-221d0e989a85.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>함수 정의가 완료되면 데이터베이스에 함수 객체로 저장된다.</p>
</li>
<li><p>예: <code>emp_salaries</code> 함수가 객체로 생성되었음을 확인 가능하다.</p>
</li>
</ul>
<hr />
<h4 id="heading-kiriniug7zwo7iiyio2yuoy2ncoq"><strong>✅ 함수 호출</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733318840320/f50246b0-53c3-40c5-93a6-130ffe8f5c8f.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><code>SELECT</code> 문을 통해 호출:</p>
<ul>
<li><p>함수는 반환값이 있으므로 <code>SELECT</code> 문에 포함되어 호출된다.</p>
</li>
<li><p>예제에서는 직원 ID가 <code>100</code>인 직원의 급여를 조회.</p>
</li>
</ul>
</li>
<li><p><strong>결과</strong>: <code>EMP_SALARIES(100)</code>: <code>24000</code>(직원의 급여)이 반환되었다.</p>
</li>
</ul>
<p><strong><mark>💡정리:</mark></strong></p>
<ol>
<li><p><strong>PL/SQL 함수</strong>는 특정 계산 작업을 수행하고 하나의 값을 반환한다.</p>
</li>
<li><p>함수는 SELECT 문에서 호출되며, 반환값을 통해 다른 SQL 작업에 활용될 수 있다.</p>
</li>
<li><p>이번 예제의 <code>emp_salaries</code> 함수는 직원 ID를 기반으로 급여를 반환하며, 데이터베이스 보안성을 강화할 수 있다.</p>
</li>
</ol>
<hr />
<h3 id="heading-function-3"><strong>함수(Function) #3: 함수의 또다른 예제: 부서 이름 변환</strong></h3>
<p><strong>✅부서 이름을 반환하는 함수</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">FUNCTION</span> get_dep_name (dept_id <span class="hljs-built_in">NUMBER</span>)
<span class="hljs-keyword">RETURN</span> <span class="hljs-built_in">VARCHAR2</span> <span class="hljs-keyword">IS</span>
   sDeptname <span class="hljs-built_in">VARCHAR2</span>(<span class="hljs-number">30</span>);
<span class="hljs-keyword">BEGIN</span>
   <span class="hljs-keyword">SELECT</span> department_name <span class="hljs-keyword">INTO</span> sDeptname <span class="hljs-keyword">FROM</span> departments
   <span class="hljs-keyword">WHERE</span> department_id = dept_id;
   RETURN sDeptname;
<span class="hljs-keyword">END</span>;
</code></pre>
<ol>
<li><p><strong>입력 파라미터</strong>:</p>
<ul>
<li><p><code>dept_id NUMBER</code>: 부서 번호를 입력받는 매개변수.</p>
</li>
<li><p>이 값을 기준으로 부서 이름(<code>department_name</code>)을 조회한다.</p>
</li>
</ul>
</li>
<li><p><strong>반환 타입</strong>:</p>
<ul>
<li><code>RETURN VARCHAR2</code>: 함수는 <strong>문자열</strong> 타입을 반환.</li>
</ul>
</li>
<li><p><strong>로직</strong>:</p>
<ul>
<li><p><code>sDeptname VARCHAR2(30)</code> 변수 선언: 부서 이름을 저장할 문자열 변수.</p>
</li>
<li><p><code>SELECT department_name INTO sDeptname</code>:</p>
<ul>
<li><code>departments</code> 테이블에서 <code>department_id</code>와 일치하는 행의 <code>department_name</code>을 가져온다</li>
</ul>
</li>
<li><p><code>RETURN sDeptname</code>: 조회된 부서 이름을 반환한다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅함수 호출</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> get_dep_name(<span class="hljs-number">100</span>) <span class="hljs-keyword">FROM</span> dual;
</code></pre>
<ul>
<li><strong>호출 방법</strong>: SELECT 문을 사용해 <code>get_dep_name</code> 함수를 호출한다. 입력값 <code>100</code>을 전달하여 부서 이름을 조회한다.</li>
</ul>
<hr />
<h4 id="heading-kirinixsi6ttlokg6rkw6ro8kio"><strong>✅실행 결과</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733319238605/3f55e007-ddbd-45d9-a05d-e7028990202b.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>입력값</strong>: <code>dept_id = 100</code></p>
</li>
<li><p><strong>출력값</strong>: <code>Finance</code> (부서 이름)</p>
</li>
<li><p>함수는 부서 번호 100에 해당하는 부서 이름을 반환하였다.</p>
</li>
</ul>
<hr />
<h3 id="heading-function-4"><strong>함수(Function) #4:</strong> 함수와 프로시저를 사용하는 장점</h3>
<h4 id="heading-security"><strong>✅ 보안성 (Security)</strong></h4>
<ul>
<li><p><strong>데이터에 대한 직접 접근 제한</strong>:</p>
<ul>
<li><p>데이터베이스에 직접 접근하는 대신, 프로시저와 함수를 통해 접근하도록 제한할 수 있다. 이를 통해 데이터베이스의 내부 구조를 숨기고 보안을 강화한다.</p>
</li>
<li><p>예: 권한이 제한된 사용자/개발자는 테이블에 직접 접근하지 않고, 제공된 함수나 프로시저만 호출 가능하다.</p>
</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-performance-improvement"><strong>✅ 성능 개선 (Performance Improvement)</strong></h4>
<ul>
<li><p><strong>미리 컴파일된 상태로 저장</strong>:</p>
<ul>
<li><p>함수와 프로시저는 미리 컴파일되어 <strong>SGA(Shared Global Area)</strong>의 공유 풀에 저장된다.</p>
</li>
<li><p>이로 인해 <strong>반복적인 실행 시 바로 호출 가능</strong>하며, 실행 계획을 다시 계산하지 않아도 된다.</p>
</li>
</ul>
</li>
<li><p><strong>공유 자원의 활용</strong>:</p>
<ul>
<li>여러 사용자가 동일한 함수나 프로시저를 공유해 사용함으로써 성능을 최적화한다.</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-reusability"><strong>✅ 재사용성 (Reusability)</strong></h4>
<ul>
<li><p><strong>코드 모듈화</strong>:</p>
<ul>
<li><p>자주 사용되거나 오류가 발생해서는 안 되는 코드를 미리 함수나 프로시저로 정의하여 모듈화한다.</p>
</li>
<li><p>필요할 때 호출만 하면 되므로 <strong>코드의 재사용성이 높아지고 유지보수</strong>가 용이하다.</p>
</li>
</ul>
</li>
<li><p><strong>가독성 증가</strong>:</p>
<ul>
<li>복잡한 로직을 간단하게 함수나 프로시저로 캡슐화하여 프로그램의 가독성을 향상시킨다.</li>
</ul>
</li>
</ul>
<hr />
<h4 id="heading-integrity"><strong>✅ 데이터 무결성 보장 (Integrity)</strong></h4>
<ul>
<li><p><strong>오류 발생 가능성 감소</strong>:</p>
<ul>
<li><p>직접 SQL 문을 작성하여 사용하는 대신, 프로시저와 함수에 로직을 구현하면 <strong>통합된 로직</strong>으로 오류 발생 가능성을 줄일 수 있다.</p>
</li>
<li><p>개발자마다 SQL 문을 개별적으로 작성하다 보면 발생할 수 있는 불일치나 오류가 발생할 수 있다. 혹은 감지를 처음엔 못하지만 나중에 발생 할 수도 있다.</p>
</li>
</ul>
</li>
<li><p><strong>업무 로직에 따른 일관성 유지</strong>:</p>
<ul>
<li>프로시저와 함수는 업무 로직에 맞추어 프로그래밍되므로 데이터 무결성을 보장할 수 있다.</li>
</ul>
</li>
</ul>
<hr />
]]></content:encoded></item><item><title><![CDATA[Managing ORACLE database - PL/SQL]]></title><description><![CDATA[Contents
1️⃣PL/SQL2️⃣PL/SQL 구성요소 (PL/SQL Components)3️⃣PL/SQL 제어문(Control Structures)

PL/SQL Summary
PL/SQL (Procedural Language/Structured Query Language)은 SQL에 절차적 프로그래밍 언어의 기능을 확장한 언어로, SQL의 데이터 처리 능력과 일반 프로그래밍 언어의 제어 구조를 결합한 것이다.
✅ 주요 특징 (Key Fe...]]></description><link>https://heesu.tech/managing-oracle-database-plsql</link><guid isPermaLink="true">https://heesu.tech/managing-oracle-database-plsql</guid><dc:creator><![CDATA[Heesu Noh]]></dc:creator><pubDate>Sun, 01 Dec 2024 16:10:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733069393933/7b521634-e0e5-45d2-a059-ec92d4732a58.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Contents</strong></p>
<p><strong>1️⃣</strong>PL/SQL<br /><strong>2️⃣</strong>PL/SQL 구성요소 (PL/SQL Components)<br /><strong>3️⃣</strong>PL/SQL 제어문(Control Structures)</p>
<hr />
<h3 id="heading-plsql-summary"><strong>PL/SQL Summary</strong></h3>
<p>PL/SQL (Procedural Language/Structured Query Language)은 SQL에 절차적 프로그래밍 언어의 기능을 확장한 언어로, SQL의 데이터 처리 능력과 일반 프로그래밍 언어의 제어 구조를 결합한 것이다.</p>
<h4 id="heading-key-features"><strong>✅ 주요 특징 (Key Features)</strong></h4>
<ol>
<li><p><strong>SQL과 프로그래밍 언어의 결합</strong>:<br /> PL/SQL은 SQL의 데이터 처리 기능과 일반 프로그래밍 언어의 제어 흐름을 결합하여 데이터베이스 작업을 더 효율적으로 처리할 수 있도록 한다.</p>
</li>
<li><p><strong>변수 및 상수 선언</strong>:<br /> 변수와 상수를 선언하여 프로그램 내에서 값을 저장하고 변경할 수 있다.</p>
</li>
<li><p><strong>조건문 (Control Statements)</strong>:</p>
<ul>
<li><p><strong>IF문</strong>: 조건에 따라 다른 동작을 수행할 수 있다.</p>
</li>
<li><p><strong>CASE문</strong>: 여러 조건을 한 번에 처리할 수 있으며, <strong>WHEN</strong>을 사용하여 조건을 지정한다.</p>
</li>
</ul>
</li>
<li><p><strong>반복문 (Loops)</strong>:</p>
<ul>
<li><p><strong>FOR문</strong>: 반복 횟수가 정해져 있을 때 사용하며, 범위 내에서 반복된다.</p>
</li>
<li><p><strong>LOOP문</strong>: 반복 조건을 명시적으로 설정할 수 있으며, 조건을 만족할 때까지 반복된다.</p>
</li>
<li><p><strong>WHILE문</strong>: 주어진 조건이 <strong>true</strong>일 때만 반복하며, 조건이 <strong>false</strong>로 변하면 종료된다.</p>
</li>
</ul>
</li>
</ol>
<h4 id="heading-plsql-plsql-components"><strong>✅ PL/SQL 구성 요소 (PL/SQL Components)</strong></h4>
<ol>
<li><p><strong>변수와 상수 (Variables &amp; Constants)</strong>:<br /> PL/SQL에서는 <strong>변수</strong>와 <strong>상수</strong>를 선언하여 데이터를 저장하고 처리할 수 있다. 상수는 값이 변경되지 않는 값을 저장하는 데 사용된다.</p>
</li>
<li><p><strong>콜렉션 (Collections)</strong>:<br /> 여러 값을 한 번에 저장할 수 있는 <strong>배열, 리스트</strong> 형태의 자료 구조이다.</p>
</li>
<li><p><strong>레코드 (Records)</strong>:<br /> 여러 필드로 구성된 구조체와 같은 자료형으로, 관련된 데이터를 함께 저장할 수 있다. 레코드는 <strong>중첩</strong>되어 사용할 수 있게 된다.</p>
</li>
</ol>
<h4 id="heading-plsql-control-structures-in-plsql"><strong>✅ PL/SQL 제어문 (Control Structures in PL/SQL)</strong></h4>
<ul>
<li><p><strong>조건문 (Conditional Statements)</strong>:</p>
<ul>
<li><p><strong>IF문</strong>: 조건에 따라 다른 처리를 진행하는 구문이다.</p>
</li>
<li><p><strong>CASE문</strong>: 여러 조건을 처리할 수 있는 구문으로, 여러 가지 경우의 수를 쉽게 다룰 수 있게 된다.</p>
</li>
</ul>
</li>
<li><p><strong>반복문 (Loops)</strong>:</p>
<ul>
<li><p><strong>FOR문</strong>: 반복 횟수가 정해져 있을 때 사용하며, 특정 범위 내에서 순차적으로 실행된다.</p>
</li>
<li><p><strong>LOOP문</strong>: 종료 조건을 명시적으로 설정하여 조건이 맞을 때까지 반복을 실행한다.</p>
</li>
<li><p><strong>WHILE문</strong>: 주어진 조건을 만족할 때만 반복하며, 조건이 <strong>false</strong>가 되면 종료된다.</p>
</li>
</ul>
</li>
</ul>
<p><strong>정리 (Summary)</strong></p>
<p>PL/SQL은 SQL의 데이터 처리 능력과 프로그래밍 언어의 흐름 제어 기능을 결합하여 강력한 데이터베이스 처리 시스템을 구축할 수 있게 해준다. <strong>조건문</strong>과 <strong>반복문</strong>을 활용하여 복잡한 논리 처리를 가능하게 하고, <strong>변수</strong>, <strong>상수</strong>, <strong>콜렉션</strong>, <strong>레코드</strong>를 통해 데이터를 효율적으로 관리할 수 있게된다.</p>
<hr />
<h2 id="heading-1plsql"><strong>1️⃣</strong>PL/SQL</h2>
<p><strong><mark>💡요약: </mark> PL/SQL (Procedural Language/Structured Query Language)</strong>은 SQL의 한계를 보완하여, 데이터베이스 내에서 논리적 흐름과 절차적 제어를 가능하게 하는 확장 언어이다. PL/SQL은 Oracle 데이터베이스에서 주로 사용되며, 다음과 같은 특징과 기능을 제공한다.</p>
<p><strong>✅ PL/SQL의 특징</strong></p>
<ol>
<li><p><strong>절차적 언어</strong></p>
<ul>
<li><p>SQL은 내가 원하는 것만 요구하면 되는 "비절차적" 언어라면, PL/SQL은 "절차적" 언어로 조건문, 반복문, 예외 처리 등을 지원한다.</p>
</li>
<li><p>데이터베이스를 이용하다보면 "흐름"의 제어가 필요할 때가 있다. 조건에 따라서 A명령어 사용 후 B명령어 사용한다던지 일때다. PL/SQL은 논리적인 흐름 제어와 데이터 조작을 함께 처리할 수 있다.</p>
</li>
</ul>
</li>
<li><p><strong>SQL과의 통합</strong></p>
<ul>
<li>PL/SQL은 SQL과 완벽하게 통합되어 있다. 데이터를 처리하는 SQL 명령어와 절차적 흐름을 조합하여 강력한 데이터베이스 작업이 가능하다.</li>
</ul>
</li>
<li><p><strong>트랜잭션 제어</strong></p>
<ul>
<li>트랜잭션 단위로 작업이 수행되며, 명령어의 성공 또는 실패에 따라 데이터의 일관성을 보장한다.</li>
</ul>
</li>
<li><p><strong>보안 및 성능</strong></p>
<ul>
<li><p>데이터베이스 내에서 직접 실행되므로 네트워크 지연이 없고, 효율적이다.</p>
</li>
<li><p>데이터베이스에 저장된 PL/SQL 블록은 재사용이 가능하며, 보안성이 높다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-plsql-1-definition-and-features-of-plsql">PL/SQL #1: 정의 및 특징 (Definition and Features of PL/SQL)</h2>
<p><strong><mark>💡요약: </mark></strong> PL/SQL은 SQL에 <strong>프로그래밍 언어(Programming Language)</strong>의 기능을 결합하여, 데이터 조작뿐 아니라 조건문과 반복문 등을 통해 절차적 흐름을 제어할 수 있음을 강조한다.</p>
<p>PL/SQL은 <strong>SQL(Structured Query Language)</strong>의 기능을 확장하여 만든 프로그래밍 언어이다. 데이터 조작과 제어 흐름을 결합하여 더욱 강력한 데이터베이스 작업 가능하게 한다.</p>
<ol>
<li><p><strong>SQL만으로는 부족한 경우가 있다.</strong></p>
<ul>
<li>일반 SQL은 데이터를 저장하거나 수정하거나 조회하는 데는 강력하지만, 특정 조건에 따라 명령을 실행하거나 여러 단계를 거쳐야 하는 작업을 하기에는 한계가 있다.</li>
</ul>
</li>
<li><p><strong>PL/SQL의 목적</strong></p>
<ul>
<li><p>이러한 한계를 해결하기 위해 절차적 프로그래밍(Programming)을 SQL에 추가한 것이 PL/SQL이다.</p>
</li>
<li><p>예를 들어 "만약 어떤 조건이 만족되면, 데이터를 이렇게 처리하라"와 같은 명령을 내릴 수 있다.</p>
</li>
</ul>
</li>
<li><p><strong>PL/SQL의 특징</strong></p>
<ul>
<li><p><strong>변수와 상수 선언 가능 (Can declare variables and constants)</strong>: 데이터를 저장하거나 계산할 때 필요하다.</p>
</li>
<li><p><strong>조건문 사용 가능 (Can use conditional statements)</strong>: 상황에 따라 다른 명령 실행한다.</p>
</li>
<li><p><strong>반복문 사용 가능 (Can use loops)</strong>: 동일한 작업을 여러 번 반복한다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-plsql-2-basic-structure-of-plsql">PL/SQL #2: 기본 구조 (Basic Structure of PL/SQL)</h2>
<p><strong><mark>💡요약:</mark></strong>기본 구조를 세 부분으로 나누어 설명한다. 각 부분은 <strong>선언부(Declarative Part)</strong>, <strong>실행부(Executable Part)</strong>, <strong>예외처리부(Exception Handlers)</strong>로 구성되며, 특히 실행부가 PL/SQL에서 가장 중요한 핵심 역할을 담당한다고 강조하고 있다.</p>
<ol>
<li><p><strong>선언부 (Declarative Part)</strong>:</p>
<ul>
<li><p><strong>DECLARE로 시작 (Starts with DECLARE)</strong>.</p>
</li>
<li><p>이 부분은 <strong>변수(variables)</strong>와 <strong>상수(constants)</strong>를 선언한다.</p>
</li>
<li><p>선택 사항이며 필요하지 않을 경우 생략할 수 있다.</p>
</li>
</ul>
</li>
<li><p><strong>실행부 (Executable Part)</strong>:</p>
<ul>
<li><p><strong>BEGIN으로 시작 (Starts with BEGIN)</strong>.</p>
</li>
<li><p>실제 작업(로직)을 실행하는 부분다.</p>
</li>
<li><p>데이터 처리, 반복문, 조건문 등 모든 프로그램 흐름 제어가 여기서 이루어진다.</p>
</li>
<li><p><strong>필수(required)</strong>로 포함되어야 한다. 이 부분에서 모든 핵심 작업이 수행된다.</p>
</li>
</ul>
</li>
<li><p><strong>예외처리부 (Exception Handlers)</strong>:</p>
<ul>
<li><p><strong>EXCEPTION으로 시작 (Starts with EXCEPTION)</strong>.</p>
</li>
<li><p>실행 중에 발생할 수 있는 오류를 처리한다.</p>
</li>
<li><p>선택 사항이다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-plsql-3-basic-structure-of-plsql-with-example">PL/SQL #3: 기본 구조와 예제 이해 (Basic Structure of PL/SQL with Example)</h2>
<p><strong><mark>💡요약: </mark></strong> PL/SQL 블록의 기본 구조(선언부, 실행부, 예외처리부)를 이해하고, 각 부분에서 수행되는 역할을 강조한다. PL/SQL은 SQL뿐만 아니라 프로그래밍 언어의 제어 흐름(반복문, 조건문)을 추가로 사용할 수 있어 더 복잡한 작업을 처리할 수 있음을 설명하고 있다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733057418340/f259ad76-3f4b-4c06-8e60-62b7fcbafcf5.png" alt class="image--center mx-auto" /></p>
<p>PL/SQL 블록은 하나의 완전한 프로그램 단위를 형성하며, 아래와 같은 구조를 가진다.</p>
<ol>
<li><p><strong>DECLARE (선언부)</strong>:</p>
<ul>
<li><p><strong>변수(variables)</strong>나 <strong>상수(constants)</strong>를 선언하는 부분이다.</p>
</li>
<li><p>예제에서는 <code>v_lname VARCHAR(25);</code>로 문자열 변수를 선언했다.</p>
</li>
<li><p><strong>SQL문이 들어가지는 않으며</strong>, 주로 데이터 저장을 위해 변수만 선언한다.</p>
</li>
</ul>
</li>
<li><p><strong>BEGIN (실행부, Executable Part)</strong>:</p>
<ul>
<li><p><strong>코드 실행의 핵심 부분</strong>이다.</p>
</li>
<li><p>SQL문과 PL/SQL 로직이 결합되어 데이터 처리와 흐름 제어를 수행한다.</p>
</li>
<li><p>예제에서는:</p>
<ul>
<li><p><code>SELECT last_name INTO v_lname FROM employees WHERE employee_id = 101;</code>로 SQL을 실행하여 결과를 변수에 저장한다.</p>
</li>
<li><p><code>DBMS_OUTPUT.PUT_LINE</code>로 결과를 출력한다.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>EXCEPTION (예외처리부, Exception Handlers)</strong>:</p>
<ul>
<li><p><strong>오류가 발생했을 때</strong> 실행되는 코드이다.</p>
</li>
<li><p>예제에서는 <code>WHEN OTHERS THEN</code>을 사용하여 모든 종류의 오류를 포착하고 <code>DBMS_OUTPUT.PUT_LINE('ERRORS');</code>로 메시지를 출력한다.</p>
</li>
<li><p>선택 사항이며 필요하지 않다면 생략 가능하다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-plsql-4-executing-a-plsql-block-and-output-settings">PL/SQL #4: 블록의 실행과 출력 설정 (Executing a PL/SQL Block and Output Settings)</h3>
<p>위에서 설명한 코드를 출력한 결과이다. PL/SQL 블록 실행 시 결과를 화면에 출력하려면 <code>SET serveroutput ON;</code> 명령어를 먼저 실행해야 한다. 이 명령은 <strong>DBMS_OUTPUT.PUT_LINE</strong>로 출력된 결과를 SQL Developer에서 볼 수 있도록 해준다.</p>
<p>이 예제에서는 <code>employee_id = 101</code>에 해당하는 직원의 <code>last_name</code>이 조회되어 <strong>Kochhar</strong>라는 이름이 출력되었다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733057562936/c6dd2061-efdc-4075-930e-38f1ab793784.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-plsql-5-exception-handling-in-plsql">PL/SQL #5: 예외처리 (Exception Handling in PL/SQL)</h3>
<p><strong><mark>💡요약: </mark></strong> PL/SQL의 <strong>예외처리(EXCEPTION)</strong>는 프로그램 실행 중 발생하는 오류를 처리하기 위해 사용된다. 이것은 <strong>TRY...CATCH 문</strong>을 사용하는 Java와 비슷하다. 미리 정의된 예외와 사용자 정의 예외를 처리할 수 있으며, 마지막에 <strong>WHEN OTHERS</strong>를 사용하면 예상하지 못한 오류도 처리할 수 있게된다.</p>
<p><strong>✅ 기본 구조</strong></p>
<pre><code class="lang-python">EXCEPTION
    WHEN 예외<span class="hljs-number">1</span> THEN 예외처리<span class="hljs-number">1</span> -- 특정 오류 <span class="hljs-number">1</span>에 대한 처리
    WHEN 예외<span class="hljs-number">2</span> THEN 예외처리<span class="hljs-number">2</span> -- 특정 오류 <span class="hljs-number">2</span>에 대한 처리
    …
    WHEN OTHERS THEN 나머지 예외처리 -- 다른 모든 예외에 대한 처리
</code></pre>
<ul>
<li><p><strong>WHEN 예외1 THEN</strong>: 특정 예외에 대한 처리 코드를 작성한다.</p>
</li>
<li><p><strong>WHEN OTHERS THEN</strong>: 정의되지 않은 모든 예외를 처리한다. (옵션)</p>
</li>
</ul>
<hr />
<p><strong>✅ 예제 코드</strong></p>
<pre><code class="lang-python">BEGIN
    -- 실행부: 오류가 발생할 가능성이 있는 코드
    SELECT salary INTO v_salary
    FROM employees
    WHERE employee_id = <span class="hljs-number">999</span>; -- 없는 ID를 조회해 의도적으로 오류 발생
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'No data found for the given employee ID.'</span>); -- 데이터가 없을 경우 처리
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'An unexpected error occurred.'</span>); -- 기타 오류 처리
END;
</code></pre>
<h4 id="heading-7isk66qfog">설명:</h4>
<ol>
<li><p><strong>WHEN NO_DATA_FOUND THEN</strong>:</p>
<ul>
<li><p><code>employee_id = 999</code>인 데이터가 없을 경우 이 블록이 실행된다.</p>
</li>
<li><p>오류 메시지: "No data found for the given employee ID."</p>
</li>
</ul>
</li>
<li><p><strong>WHEN OTHERS THEN</strong>:</p>
<ul>
<li><p>다른 모든 예기치 못한 오류를 처리한다.</p>
</li>
<li><p>예를 들어, 데이터베이스 연결 문제나 문법 오류 등이 포함된다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<p><strong>✅ 실행 흐름</strong></p>
<ol>
<li><p><code>BEGIN</code>에서 지정된 SQL을 실행한다.</p>
</li>
<li><p>실행 중 오류가 발생하면 <strong>EXCEPTION</strong> 블록으로 이동한다.</p>
</li>
<li><p>오류 유형에 따라 <strong>WHEN ... THEN</strong> 조건문이 실행된다.</p>
</li>
<li><p><strong>WHEN OTHERS</strong>는 정의되지 않은 모든 오류를 처리한다.</p>
</li>
</ol>
<hr />
<h3 id="heading-plsql-6-predefined-exceptions-in-plsql">PL/SQL #6: 미리 정의된 예외와 처리 방법 (Predefined Exceptions in PL/SQL)</h3>
<p><strong><mark>💡요약: </mark></strong> PL/SQL은 실행 중 발생할 수 있는 <strong>미리 정의된 예외(predefined exceptions)</strong>를 제공한다. 특정 오류를 자동으로 인식하고 처리하는 도구이다. 너무 자세히 알 필요는 없고, "이런 오류가 있을 수 있구나" 정도만 이해하면 된다.<br />이 예외들은 특정 상황에서 프로그램 실행을 중단하지 않고 오류를 처리하도록 도와준다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733059926494/6c5a033d-fa56-4be2-96fe-a10139aa6e8d.png" alt class="image--center mx-auto" /></p>
<hr />
<p><strong>✅ 처리 방법 (예제 코드)</strong></p>
<pre><code class="lang-python">DECLARE
    v_number NUMBER;
BEGIN
    -- 오류를 의도적으로 발생시키는 코드
    v_number := <span class="hljs-number">10</span> / <span class="hljs-number">0</span>; -- <span class="hljs-number">0</span>으로 나눔
EXCEPTION
    WHEN ZERO_DIVIDE THEN
        DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'Cannot divide by zero!'</span>); -- <span class="hljs-number">0</span>으로 나눌 경우 처리
    WHEN INVALID_NUMBER THEN
        DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'Invalid number encountered.'</span>); -- 잘못된 숫자 처리
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'An unexpected error occurred.'</span>); -- 기타 오류 처리
END;
</code></pre>
<hr />
<p><strong>✅ 실행 흐름</strong></p>
<ol>
<li><p>오류가 발생하면 PL/SQL은 실행을 중단하지 않고 <strong>EXCEPTION</strong> 블록으로 이동한다.</p>
</li>
<li><p>발생한 오류가 <strong>WHEN ZERO_DIVIDE</strong> 또는 <strong>WHEN INVALID_NUMBER</strong>와 일치하면 해당 처리를 실행한다.</p>
</li>
<li><p><strong>WHEN OTHERS</strong>는 위에서 처리되지 않은 나머지 모든 오류를 처리한다.</p>
</li>
</ol>
<hr />
<p><strong>✅ 중요한 포인트</strong></p>
<ul>
<li><p><strong>미리 정의된 예외</strong>는 특정 오류를 자동으로 인식하고 처리하는 도구이다.</p>
</li>
<li><p>자주 사용되는 예외:</p>
<ul>
<li><p><strong>NO_DATA_FOUND</strong>: SELECT 결과 없음.</p>
</li>
<li><p><strong>ZERO_DIVIDE</strong>: 0으로 나눔.</p>
</li>
<li><p><strong>INVALID_NUMBER</strong>: 숫자 변환 오류.</p>
</li>
</ul>
</li>
<li><p><strong>EXCEPTION 블록</strong>을 사용하면 이러한 오류를 안전하게 처리할 수 있다.</p>
</li>
<li><p><strong>옵션이 없다면 실행이 중단</strong>되므로 예외 처리를 적절히 설정하는 것이 중요하다.</p>
</li>
</ul>
<hr />
<h3 id="heading-plsql-7-role-and-structure-of-the-plsql-engine">PL/SQL #7: 엔진의 역할과 구조 (Role and Structure of the PL/SQL Engine)</h3>
<p><strong><mark>💡요약: </mark></strong> PL/SQL 엔진(PL/SQL Engine)은 PL/SQL 코드를 컴파일하고 실행하며, SQL 문장을 처리하고 그 결과를 종합하는 것이다. 오라클 데이터베이스 내부에서 <strong>PL/SQL 블록</strong>을 실행하는 중요한 구성 요소이다.</p>
<p><strong><mark>💡 중요한 포인트</mark></strong></p>
<ul>
<li><ol>
<li><p><strong>PL/SQL 엔진</strong>은 PL/SQL 코드를 처리하고 SQL 문을 실행하는 역할을 한다.</p>
<ol start="2">
<li><p>실행 단계:</p>
<ul>
<li><strong>컴파일 → PL/SQL 엔진 실행 → SQL 문 처리 → 결과 종합</strong></li>
</ul>
</li>
<li><p><strong>Database Application</strong>은 외부 프로그램이며, 결과 종합 단계에서 PL/SQL 엔진을 통해 데이터베이스와 연결된다.</p>
</li>
<li><p>PL/SQL 엔진은 <strong>SQL문과 PL/SQL 로직을 효율적으로 처리하는 독립적인 구조</strong>를 가지고 있다.</p>
</li>
</ol>
</li>
</ol>
</li>
</ul>
<h4 id="heading-plsql">PL/SQL 엔진을 별도로 두는 이유는 효율적인 실행과 데이터베이스와의 통합을 위해서입니다. 이 정도만 이해하면 되겠다.</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733060116685/c910e523-ee74-426d-bbe0-bb585ca360aa.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>컴파일 단계 (Compilation)</strong>:</p>
<ul>
<li><p>PL/SQL 블록은 먼저 <strong>System Global Area(SGA)</strong>에서 컴파일된다.</p>
</li>
<li><p>컴파일후, 이 컴파일된 코드는 <strong>PL/SQL 엔진</strong>으로 전달된다.</p>
</li>
</ul>
</li>
<li><p><strong>실행 단계 (Execution)</strong>:</p>
<ul>
<li><p><strong>PL/SQL 엔진</strong>은 컴파일된 코드를 실행한다.</p>
</li>
<li><p>이 과정에서 <strong>SQL문(statement)</strong>은 데이터베이스에 전달되어 실행된다.</p>
</li>
<li><p><strong>SQL Statement Executor</strong>는 SQL 문장을 처리한 결과를 PL/SQL 엔진으로 반환한다.</p>
</li>
</ul>
</li>
<li><p><strong>종합 및 실행 (Processing Results)</strong>:</p>
<ul>
<li><p>PL/SQL 엔진은 SQL 실행 결과를 종합해 데이터베이스 응용 프로그램(database application)에 전달한다.</p>
</li>
<li><p>여기서 <strong>Database Application</strong>은 PL/SQL 엔진 외부에서 실행되는 프로그램을 의미한다.</p>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-2plsql-plsql-components"><strong>2️⃣</strong>PL/SQL 구성요소 (PL/SQL Components)</h2>
<p><strong><mark>💡요약: </mark> PL/SQL</strong>도 다른 프로그래밍 언어처럼 다양한 구성 요소를 제공한다. 대표적인 요소로는 <strong>변수(Variables)</strong>와 <strong>상수(Constants)</strong>가 있다. 이 두 가지는 데이터를 저장하고 처리하는 데 핵심적인 역할을 하며, 정보 시스템이나 대부분의 프로그래밍에서 없어서는 안 되는 중요한 개념이다.</p>
<hr />
<p><strong>✅ 변수(Variables)란? 변수(Variable)</strong>는 <strong>데이터를 저장하는 메모리 공간</strong>이다. 프로그램이 데이터를 읽고 처리한 결과를 저장하거나, 최종 결과를 출력하기 위해 사용된다.</p>
<h4 id="heading-67oa7iiy7j2yioyxre2voa">변수의 역할</h4>
<ul>
<li><p><strong>데이터 저장</strong>: 예를 들어, 숫자, 문자열, 이미지 등을 저장합니다.</p>
</li>
<li><p><strong>중간 결과 저장</strong>: 계산 도중 중간 값을 저장하여 후속 처리를 가능하게 합니다.</p>
</li>
<li><p><strong>최종 출력 준비</strong>: 결과 값을 변수에 저장한 후, 이를 출력하거나 활용합니다.</p>
</li>
</ul>
<h4 id="heading-67oa7iiyioyeooywucdsmijsojw">변수 선언 예제</h4>
<pre><code class="lang-sql">emp_num1 NUMBER(9); 
<span class="hljs-comment">-- 직원 번호를 저장하기 위한 숫자형 변수 선언</span>
</code></pre>
<hr />
<p><strong>✅ 상수(Constants)란? 상수(Constant)</strong>는 <strong>한 번 정의하면 값이 변하지 않는 데이터</strong>이다.<br />이는 프로그램에 <strong>제약</strong>을 추가하여 코드의 일관성을 유지하고, 의도치 않은 오류를 방지한다.</p>
<h4 id="heading-7iob7iiy66w8ioycroyaqe2vmouklcdsnbtsnka">상수를 사용하는 이유</h4>
<ul>
<li><p><strong>코드 안정성</strong>: 여러 개발자가 작업 중, 중요한 데이터가 실수로 수정되는 것을 방지한다.</p>
</li>
<li><p><strong>가독성 향상</strong>: 코드에서 특정 숫자나 값의 의미를 명확히 나타낼 수 있다.<br />  예: <code>nYear CONSTANT INTEGER := 30;</code>는 "30"이 연도를 의미함을 명확히 한다.</p>
</li>
</ul>
<h4 id="heading-7iob7iiyioyeooywucdsmijsojw">상수 선언 예제</h4>
<pre><code class="lang-sql">Year CONSTANT INTEGER := 30;  
<span class="hljs-comment">-- 30이라는 값은 "변경 불가"하며 연도와 관련된 고정된 값임을 나타낸다.</span>
</code></pre>
<hr />
<p><strong><mark>💡 중요한 개념 요약</mark></strong></p>
<ol>
<li><p><strong>변수</strong>는 데이터를 저장하고 가공하는 데 사용되는 기본적인 메모리 공간이다.</p>
<ul>
<li>예: <code>emp_num1 NUMBER(9);</code> (9자리 숫자형 데이터를 저장)</li>
</ul>
</li>
<li><p><strong>상수</strong>는 값을 변경할 수 없으며, 프로그램의 안정성과 가독성을 높이는 데 도움을 준다.</p>
<ul>
<li>예: <code>nYear CONSTANT INTEGER := 30;</code> (30은 변하지 않는 값)</li>
</ul>
</li>
<li><p><strong>상수의 목적</strong>은 <strong>제약</strong>을 통해 오류를 방지하고, 협업 중 코드의 일관성을 유지하는 데 있다.</p>
</li>
</ol>
<hr />
<h2 id="heading-plsql-1-plsql-variables-and-data-types">PL/SQL 구성요소 #1: 변수와 데이터 타입 (PL/SQL Variables and Data Types)</h2>
<p><strong><mark>💡요약: </mark></strong> PL/SQL에서 <strong>변수(Variables)</strong>는 데이터 값을 저장하는 메모리 공간이다. 하지만, 변수는 <strong>타입(Type)</strong>과 항상 함께 정의된다. <strong>타입</strong>이란 <strong>저장할 데이터의 종류</strong>를 나타낸다. 예를 들어, <strong>정수</strong>는 정수 타입, <strong>실수</strong>는 실수 타입, <strong>문자열</strong>은 문자열 타입이어야 한다. 이렇게 타입을 명시하는 이유는 <strong>데이터가 올바른 형태로 저장되도록 하기 위해</strong>서이다. 또한, <strong>데이터베이스 컬럼의 데이터 타입</strong>과 <strong>변수 타입</strong>은 일관성을 유지해야 한다.</p>
<p><strong>✅ %TYPE - 테이블 컬럼의 데이터 타입 사용</strong></p>
<p>PL/SQL에서 변수를 정의할 때, 기존 테이블의 컬럼에서 데이터 타입을 그대로 가져올 수 있다. 이때 사용하는 것이 <strong>%TYPE</strong>이다.</p>
<pre><code class="lang-sql">Salaries EMPLOYEES.SALARY%TYPE;
<span class="hljs-comment">-- EMPLOYEES 테이블의 SALARY 컬럼의 데이터 타입을 사용하여 변수를 정의</span>
</code></pre>
<ul>
<li><p><code>EMPLOYEES.SALARY%TYPE</code>은 <strong>EMPLOYEES 테이블</strong>의 <strong>SALARY 컬럼</strong>의 데이터 타입을 그대로 사용하는 방법이다.</p>
</li>
<li><p><strong>이점</strong>: 테이블 컬럼의 데이터 타입을 그대로 사용하므로, 데이터베이스 구조 변경 시 변수의 타입도 자동으로 일관되게 유지된다.</p>
</li>
</ul>
<hr />
<p><strong>✅%ROWTYPE - 테이블의 한 레코드를 변수로 선언</strong></p>
<p>%TYPE과 유사하나 <strong>%ROWTYPE</strong>은 <strong>하나 이상의 값</strong>을 묶어서 사용할 때 유용한 방법이다. 이 방식은 테이블의 <strong>전체 레코드</strong>를 한꺼번에 <strong>변수</strong>로 선언할 수 있게 해준다.</p>
<pre><code class="lang-sql">emp_record EMPLOYEES%ROWTYPE;
<span class="hljs-comment">-- EMPLOYEES 테이블의 한 행(Row)을 저장할 수 있는 변수 선언</span>
</code></pre>
<ul>
<li><p><code>EMPLOYEES%ROWTYPE</code>은 <strong>EMPLOYEES 테이블의 한 레코드</strong> 전체를 저장할 수 있는 변수를 정의하는 방법이다.</p>
</li>
<li><p>이 변수에는 <strong>테이블의 모든 컬럼</strong>의 데이터가 포함된다.</p>
</li>
</ul>
<hr />
<p><strong><mark>💡 중요한 개념 요약</mark></strong></p>
<ol>
<li><p><strong>%TYPE</strong>은 테이블의 <strong>컬럼 데이터 타입</strong>을 그대로 사용하는 방법이다.</p>
<ul>
<li>예: <code>EMPLOYEES.SALARY%TYPE</code> (테이블의 <code>SALARY</code> 컬럼 타입을 그대로 사용)</li>
</ul>
</li>
<li><p><strong>%ROWTYPE</strong>은 테이블의 <strong>전체 행(레코드)</strong>을 변수로 선언할 때 사용한다.</p>
<ul>
<li>예: <code>EMPLOYEES%ROWTYPE</code> (테이블의 한 행을 저장하는 변수)</li>
</ul>
</li>
<li><p><strong>타입 일관성</strong>은 데이터베이스의 컬럼과 변수가 같은 타입을 유지하도록 보장한다.</p>
</li>
</ol>
<hr />
<h2 id="heading-plsql-2-collection">PL/SQL 구성요소 #2: 콜렉션 (Collection)</h2>
<p><strong><mark>💡요약:</mark></strong> PL/SQL에서 <strong>변수</strong>는 기본적으로 하나의 값만을 저장할 수 있습니다. 하지만, <strong>배열</strong> 형태의 데이터값을 PL/SQL에서도 지원하는데 이를 <strong>콜렉션(Collection)</strong>이라고 한다. PL/SQL에서는 세 가지 종류의 콜렉션을 지원한다. <strong>VARRAY</strong>, <strong>Nested Table</strong>, <strong>Associative Array</strong>. 각각의 특성을 이해하는 것이 중요하다.</p>
<hr />
<p><strong>✅ VARRAY (변수 크기 배열)</strong></p>
<ul>
<li><ul>
<li><p><strong>고정된 크기</strong>: VARRAY는 선언할 때 배열의 크기를 <strong>정해</strong> 놓아야 한다. 크기는 고정되지만, <strong>중간에 배열의 크기를 변경</strong>할 수 있다.</p>
<ul>
<li><p><strong>숫자형 인덱스</strong>: VARRAY는 숫자형 인덱스를 사용하여 배열의 요소를 접근한다.</p>
</li>
<li><p><strong>순서와 밀집된 데이터</strong>: VARRAY는 <strong>순서가 중요한 데이터</strong>를 처리하는 데 유리하며, <strong>밀집된 데이터</strong> 집합을 처리할 때 사용된다. 예를 들어, <strong>학생들의 점수</strong>, <strong>주문 목록</strong> 등 순서대로 처리할 필요가 있는 데이터에 적합하다.</p>
</li>
<li><p><strong>일부 원소 삭제 불가</strong>: VARRAY에서 원소는 <strong>삭제할 수 없으며</strong>, <strong>전체 배열을 삭제</strong>해야 한다. 즉, 중간에 원소를 제거할 수는 없고, <strong>배열을 전체적으로 초기화</strong>해야 한다.</p>
</li>
<li><p><strong>배열의 크기 변경 가능</strong>: 배열의 크기를 선언할 때 크기를 지정하지만, 이후 <strong>배열 크기를 동적으로 변경</strong>할 수 있다.</p>
</li>
<li><p><strong>테이블 내 저장 가능</strong>: VARRAY는 데이터베이스 테이블의 <strong>컬럼 타입으로 사용할 수</strong> 있다. 예를 들어, 한 테이블의 컬럼에 여러 값을 배열로 저장할 수 있다.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
<span class="hljs-comment">-- VARRAY 타입 정의</span>
    <span class="hljs-keyword">TYPE</span> num_array <span class="hljs-keyword">IS</span> <span class="hljs-keyword">VARRAY</span>(<span class="hljs-number">5</span>) <span class="hljs-keyword">OF</span> <span class="hljs-built_in">NUMBER</span>;  <span class="hljs-comment">-- 5개의 숫자를 저장할 수 있는 배열 타입 선언</span>
    nums num_array := num_array(1, 2, 3, 4, 5);  <span class="hljs-comment">-- 배열 초기화</span>
<span class="hljs-keyword">BEGIN</span>
    <span class="hljs-comment">-- 배열 값 출력</span>
    DBMS_OUTPUT.PUT_LINE(nums(<span class="hljs-number">1</span>));  <span class="hljs-comment">-- 첫 번째 요소 출력</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<h4 id="heading-kiriniug7kcc7zwcioycro2vrsoq"><strong>✅ 제한 사항</strong></h4>
<ul>
<li><p><strong>배열의 크기 고정</strong>: VARRAY를 선언할 때 크기를 <strong>고정</strong>해야 하므로, 동적으로 크기를 변경하는 데는 제한이 있다.</p>
</li>
<li><p><strong>부분 삭제 불가</strong>: 배열 내 원소는 <strong>삭제할 수 없</strong>으며, <strong>전체 배열 삭제</strong>만 가능하다.</p>
</li>
<li><p><strong>단일 타입의 데이터만 사용 가능</strong>: VARRAY는 배열 내 <strong>동일한 데이터 타입</strong>을 갖는 원소들만 포함할 수 있다.</p>
</li>
</ul>
<hr />
<p><strong>✅VARRAY (변수 크기 배열) 사용 예시:</strong> 복잡해보이지만 전혀 어렵지 않다. 예제코드가 이미 잘 정리되어있기 때문에 필요시마다 찾아서 하면 된다. <strong>배열 선언</strong>과 <strong>배열 초기화</strong>, 그리고 <strong>배열의 값 출력</strong>을 다루고 있습니다. 이 코드를 이해하고 필요할 때 참조하면 복잡한 PL/SQL 배열 사용에 대한 이해가 쉬워질 것이다.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
    <span class="hljs-keyword">TYPE</span> Foursome <span class="hljs-keyword">IS</span> <span class="hljs-keyword">VARRAY</span>(<span class="hljs-number">4</span>) <span class="hljs-keyword">OF</span> <span class="hljs-built_in">VARCHAR2</span>(<span class="hljs-number">15</span>);  <span class="hljs-comment">-- VARRAY type 선언</span>
    <span class="hljs-comment">-- 'Foursome'은 크기 4의 문자열 배열을 정의</span>
    team Foursome := Foursome('John', 'Mary', 'Alberto', 'Juanita');  <span class="hljs-comment">-- 배열 초기화</span>
<span class="hljs-keyword">BEGIN</span>
    DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'---'</span>);
    FOR i IN 1..4 LOOP  <span class="hljs-comment">-- 배열의 각 요소에 접근</span>
        DBMS_OUTPUT.PUT_LINE(i || '.' || team(i));  <span class="hljs-comment">-- 배열의 원소 출력</span>
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
    DBMS_OUTPUT.PUT_LINE('<span class="hljs-comment">---');</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<ol>
<li><p><strong>VARRAY 타입 정의</strong></p>
<pre><code class="lang-sql"> TYPE Foursome IS VARRAY(4) OF VARCHAR2(15);
</code></pre>
<ul>
<li><code>Foursome</code>이라는 이름의 VARRAY 타입을 정의한다. 이 배열은 <strong>4개의 요소</strong>를 가질 수 있으며, 각 요소는 <strong>최대 15자의 문자열</strong>이다.</li>
</ul>
</li>
<li><p><strong>VARRAY 변수 초기화</strong></p>
<pre><code class="lang-sql"> team Foursome := Foursome('John', 'Mary', 'Alberto', 'Juanita');
</code></pre>
<ul>
<li><code>team</code>이라는 VARRAY 변수를 선언하고, <strong>'John', 'Mary', 'Alberto', 'Juanita'</strong>라는 문자열 값을 배열로 초기화한다.</li>
</ul>
</li>
<li><p><strong>배열 값 출력</strong></p>
<pre><code class="lang-sql"> FOR i IN 1..4 LOOP
     DBMS_OUTPUT.PUT_LINE(i || '.' || team(i));
 <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
</code></pre>
<ul>
<li><p><code>FOR</code> 루프를 사용하여 <strong>배열의 각 원소</strong>를 출력한다. 배열의 인덱스는 <strong>1부터 4까지</strong>이며, 각 인덱스에 해당하는 <code>team(i)</code>의 값을 출력한다.</p>
</li>
<li><p><code>DBMS_OUTPUT.PUT_LINE</code>은 출력문을 담당한다. <code>i || '.' || team(i)</code>는 배열의 인덱스와 해당 인덱스의 값을 문자열로 연결하여 출력하는 부분이다.</p>
</li>
</ul>
</li>
<li><p><strong>구분선 출력</strong></p>
<pre><code class="lang-sql">  DBMS_OUTPUT.PUT_LINE('<span class="hljs-comment">---');</span>
 <span class="hljs-keyword">END</span>;
</code></pre>
<p> 이 부분은 배열의 출력 전에 구분선을 출력하여, 출력되는 값들을 더 명확하게 구분하기 위한 구문이다. 구분선으로 <code>---</code>를 출력하여 배열 항목들을 보기 좋게 구분할 수 있다.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733063806147/35195b90-748f-468b-a60f-e51908a849cd.png" alt class="image--center mx-auto" /></p>
<hr />
<p><strong>✅ Nested Table (중첩 테이블)</strong></p>
<ul>
<li><p><strong>동적 크기</strong>: <code>NESTED TABLE</code>은 처음 선언할 때 크기를 명시할 필요가 없으며, 사용에 따라 크기가 동적으로 변경된다. 즉, 배열의 크기를 제한하지 않고 유연하게 데이터를 추가할 수 있다.</p>
</li>
<li><p><strong>숫자형 인덱스</strong>: 이 배열은 숫자형 인덱스를 사용하며, 데이터가 추가될 때 순차적으로 인덱스가 증가한다. 처음에 a,b,c가 있고 d,f가 추가가 된다면 순서대로 1,2,3,4,5로 구성된다.</p>
</li>
<li><p><strong>삭제가 가능</strong>: <code>NESTED TABLE</code>은 중간에 원소를 삭제할 수 있는 특성이 있다. 삭제된 위치는 <code>NULL</code>로 처리되고, 테이블이 흩어진 상태(sparse)로 듬성 듬성한 배열이 될수있다.</p>
</li>
<li><p><strong>밀집(dense)과 흩어진(sparse) 데이터</strong>: 처음에는 데이터를 밀집된 형태로 처리하지만, 일부 원소가 삭제되면 배열이 흩어지게 된다. 이로 인해 <code>NESTED TABLE</code>은 매우 유연하게 데이터를 처리할 수 있게 된다.</p>
</li>
<li><p><strong>테이블의 컬럼으로 사용 가능</strong>: <code>NESTED TABLE</code>은 테이블 내 컬럼 타입으로도 사용할 수 있지만, 성능상의 이유로 자주 사용되지 않는 것이 권장된다.</p>
</li>
<li><p><strong>문법:</strong> TYPE 타입명 IS TABLE OF 요소데이터 타입 [NOT NULL]</p>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
    <span class="hljs-keyword">TYPE</span> num_table <span class="hljs-keyword">IS</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">OF</span> <span class="hljs-built_in">NUMBER</span>;  <span class="hljs-comment">-- 숫자들을 저장하는 중첩 테이블 타입 선언</span>
    nums num_table := num_table(1, 2, 3, 4, 5);  <span class="hljs-comment">-- 테이블 초기화</span>
<span class="hljs-keyword">BEGIN</span>
    DBMS_OUTPUT.PUT_LINE(nums(<span class="hljs-number">1</span>));  <span class="hljs-comment">-- 첫 번째 요소 출력</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><strong>중첩 테이블</strong>은 <strong>배열</strong>처럼 여러 값을 저장하지만, <strong>동적 크기</strong>를 갖는다.</li>
</ul>
<hr />
<p><strong>✅ Nested Table (중첩 테이블) 사용 예시</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
    <span class="hljs-keyword">TYPE</span> Roster <span class="hljs-keyword">IS</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">OF</span> <span class="hljs-built_in">VARCHAR2</span>(<span class="hljs-number">15</span>); <span class="hljs-comment">-- NESTED TABLE 타입 정의</span>
    <span class="hljs-comment">-- NESTED TABLE 변수 초기화</span>
    names Roster := Roster('D Caruso', 'J Hamil', 'D Piro', 'R Singh');
<span class="hljs-keyword">BEGIN</span>
    DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'---'</span>);
    <span class="hljs-comment">-- FIRST와 LAST 메소드를 사용하여 배열의 처음부터 끝까지 순회</span>
    FOR i IN names.FIRST .. names.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(names(i)); <span class="hljs-comment">-- 각 이름 출력</span>
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
    DBMS_OUTPUT.PUT_LINE('<span class="hljs-comment">---');</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<ol>
<li><p><strong>NESTED TABLE 타입 선언</strong></p>
<pre><code class="lang-sql"> TYPE Roster IS TABLE OF VARCHAR2(15);
</code></pre>
<p> <code>Roster</code>라는 이름의 <code>NESTED TABLE</code> 타입을 정의하였다. 이 배열은 <code>VARCHAR2(15)</code> 타입의 요소들을 담을 수 있으며, 각 요소는 최대 15자까지 가능하다는 뜻이다.</p>
</li>
<li><p><strong>NESTED TABLE 변수 초기화</strong></p>
<pre><code class="lang-sql"> names Roster := Roster('D Caruso', 'J Hamil', 'D Piro', 'R Singh');
</code></pre>
<p> <code>names</code>라는 변수를 선언하고 <code>Roster</code> 타입을 이용해 초기값을 설정하였다. 여기서는 네 명의 이름을 담고 있는 배열을 생성한다.</p>
</li>
<li><p><strong>FIRST와 LAST 메소드 사용</strong>:</p>
<pre><code class="lang-sql"> FOR i IN names.FIRST .. names.LAST LOOP
     DBMS_OUTPUT.PUT_LINE(names(i));
 <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
</code></pre>
<ul>
<li><p><code>FIRST</code>: 배열에서 첫 번째 요소의 인덱스를 반환한다. 예를 들어, <code>names.FIRST</code>는 <code>1</code>을 반환한다.</p>
</li>
<li><p><code>LAST</code>: 배열에서 마지막 요소의 인덱스를 반환합니다. 예를 들어, <code>names.LAST</code>는 <code>4</code>를 반환한다.</p>
</li>
<li><p><code>names(i)</code>: 배열의 인덱스 <code>i</code>에 해당하는 값을 가져온다. 루프 내에서 인덱스를 1부터 4까지 순차적으로 증가시키며 값을 출력하게 된다.</p>
</li>
</ul>
</li>
</ol>
<p>    <code>FIRST</code>와 <code>LAST</code> 메소드는 배열의 시작과 끝을 나타내는 메소드로, 이를 사용하여 배열의 처음부터 끝까지 순차적으로 접근한다.  </p>
<ol start="4">
<li><strong>결과 출력</strong>: <code>FOR</code> 루프를 통해 <code>names</code> 배열에 있는 각 요소를 출력한다. 이 배열의 크기와 요소는 <code>FIRST</code>와 <code>LAST</code> 메소드를 통해 동적으로 결정되며, 이 범위 내에서 배열의 모든 값을 출력한다.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733064170013/9adc7b64-070a-43fd-8af0-cec83653d529.png" alt class="image--center mx-auto" /></p>
<hr />
<p><strong><mark>👀VARAY와 NESTED TABLE의 차이점 간단 소개</mark></strong></p>
<ul>
<li><p><strong>VARRAY</strong>: 고정 크기, 밀집(dense)된 데이터, 순차적 처리</p>
</li>
<li><p><strong>NESTED TABLE</strong>: 동적 크기, 희소(sparse) 데이터, 유연한 데이터 처리, 빈번한 삽입 및 삭제가 이루어지면 성능이 저하될 수 있다.</p>
</li>
</ul>
<hr />
<p><strong>✅ Associative Array (연관 배열 또는 맵)</strong>  </p>
<ul>
<li><p><strong>키와 값의 쌍</strong>: Associative Array는 <strong>키-값 쌍</strong>으로 데이터를 저장하는 맵(Map)과 유사한 데이터 구조이다. 예를 들어, <code>(A, 30)</code>, <code>(B, 50)</code>, <code>(C, 20)</code>와 같은 형태이다. 같은 데이터 타입을 가진 요소들로 구성된다.</p>
<ul>
<li><strong>A, B, C</strong>는 <strong>키(인덱스)</strong>이며, <strong>30, 50, 20</strong>은 <strong>값(Value)</strong>이 된다. 각각의 요소가 <strong>고유한 키</strong>에 의해 식별되며, 키를 통해 값을 <strong>빠르게 검색</strong>할 수 있다.</li>
</ul>
</li>
<li><p><strong>Index와 데이터 타입</strong>:</p>
<ul>
<li><p><strong>Index</strong>는 <strong>PLS_INTEGER</strong>, <strong>BINARY_INTEGER</strong>, <strong>VARCHAR2</strong> 등의 데이터 타입을 사용할 수 있다.</p>
</li>
<li><p><code>PLS_INTEGER</code>는 PL/SQL에서 제공하는 특수한 정수 타입으로, 계산 속도가 빠르기 때문에 자주 사용된다.</p>
</li>
<li><p>키를 Index라고 부르기 때문에 Index-by 테이블 이라고도 한다.</p>
</li>
</ul>
</li>
<li><p><strong>동적 크기</strong>: Associative Array는 크기가 <strong>동적</strong>이어서, 필요한 만큼 요소를 추가하거나 제거할 수 있다.</p>
</li>
<li><p><strong>배열 접근</strong>: 값을 참조할 때 인덱스를 사용하여 빠르게 접근할 수 있게 된다.</p>
</li>
</ul>
<p><strong>문법:</strong></p>
<pre><code class="lang-sql">TYPE 타입명 IS TABLE OF 요소 데이터타입 [NOT NULL]
INDEX BY [PLS_INTEGER | BINARY_INTEGER | VARCHAR2(크기)];
</code></pre>
<ul>
<li>예를 들어, 인덱스가 <code>VARCHAR2</code>인 경우는 문자열을 키로 사용하여 배열을 구성할 수 있다.</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
    <span class="hljs-keyword">TYPE</span> AssociativeArray <span class="hljs-keyword">IS</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">OF</span> <span class="hljs-built_in">NUMBER</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">BY</span> <span class="hljs-built_in">VARCHAR2</span>(<span class="hljs-number">20</span>);  <span class="hljs-comment">-- 키는 VARCHAR2, 값은 NUMBER</span>
    scores AssociativeArray;
<span class="hljs-keyword">BEGIN</span>
    scores(<span class="hljs-string">'A'</span>) := <span class="hljs-number">30</span>;  <span class="hljs-comment">-- 키 'A'에 30을 저장</span>
    scores('B') := 50;  <span class="hljs-comment">-- 키 'B'에 50을 저장</span>
    scores('C') := 20;  <span class="hljs-comment">-- 키 'C'에 20을 저장</span>

    DBMS_OUTPUT.PUT_LINE('A: ' || scores('A'));  <span class="hljs-comment">-- A의 값을 출력 -&gt; 30</span>
    DBMS_OUTPUT.PUT_LINE('B: ' || scores('B'));  <span class="hljs-comment">-- B의 값을 출력 -&gt; 50</span>
    DBMS_OUTPUT.PUT_LINE('C: ' || scores('C'));  <span class="hljs-comment">-- C의 값을 출력 -&gt; 20</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>인덱스 타입</strong>: <code>PLS_INTEGER</code>, <code>BINARY_INTEGER</code>, <code>VARCHAR2</code> 등을 키로 사용하며, 문자열을 인덱스로 사용하는 것도 가능하다.</p>
</li>
<li><p><strong>유사한 구조</strong>: 자바의 <code>Map</code>과 유사하며, 인덱스를 통해 값을 빠르게 찾을 수 있다.</p>
</li>
<li><p><strong>데이터베이스의 키-값 구조</strong>를 구현할 때 유용하며, 특정 키를 기준으로 빠르게 데이터를 처리해야 할 경우 적합하다.</p>
</li>
<li><p>예를 들어, <strong>학생 이름</strong>을 <strong>점수</strong>와 연결하는 구조나 <strong>상품명</strong>을 <strong>가격</strong>과 연결하는 구조 등에서 사용할 수 있다.</p>
</li>
</ul>
<hr />
<p>✅ <strong>Associative Array (연관 배열 또는 맵) 사용예제</strong></p>
<p>도시 이름을 인구 수와 연결하는 구조를 표현한 코드이다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733064646008/e18a8415-df1a-4fc7-a2cf-5e9f85e1154f.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>Associative Array 타입 정의</strong></p>
<pre><code class="lang-sql">  TYPE population IS TABLE OF NUMBER INDEX BY VARCHAR2(64);
</code></pre>
<ul>
<li><p><code>population</code>이라는 타입을 정의하고, <code>NUMBER</code> 타입 값을 저장하는 배열을 생성한다.</p>
</li>
<li><p>인덱스는 <code>VARCHAR2(64)</code> 타입으로 설정하여, 각 원소를 문자열(도시 이름)으로 식별한다.</p>
</li>
</ul>
</li>
<li><p><strong>변수 선언</strong>:</p>
<pre><code class="lang-sql">  city_population population;
</code></pre>
<ul>
<li><code>city_population</code>이라는 변수를 선언하여 <code>population</code> 타입의 associative array를 만든다.</li>
</ul>
</li>
<li><p><strong>값 추가</strong>:</p>
<pre><code class="lang-sql">  city_population('Smallville') := 2000;
  city_population('Midland') := 750000;
  city_population('Megalopolis') := 1000000;
</code></pre>
<ul>
<li><code>city_population</code> 배열에 도시 이름을 키로, 인구 수를 값으로 추가한다. 각 도시의 인구를 인덱스를 사용하여 저장한다.</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733064747085/71baca7f-5ad4-40a5-a43c-a63b8a5c7370.png" alt class="image--center mx-auto" /></p>
<hr />
<p><strong>✅ 콜렉션 메소드</strong></p>
<p>콜렉션을 사용하면 배열의 크기를 동적으로 관리하거나, 여러 값을 효율적으로 저장하고 관리할 수 있게 된다. PL/SQL은 이러한 콜렉션을 위한 메소드도 제공하여 데이터를 쉽게 다룰 수 있게 해준다. 예를 들어, 배열에서 첫 번째 요소를 가져오거나, 크기를 세거나, 다음 요소로 이동하는 작업을 <strong>메소드</strong>로 처리할 수 있다.</p>
<h4 id="heading-7ko87jquiouploygjoutncdsmijsi5w">주요 메소드 예시</h4>
<ul>
<li><p><strong>FIRST</strong>: 첫 번째 요소를 반환</p>
</li>
<li><p><strong>LAST</strong>: 마지막 요소를 반환</p>
</li>
<li><p><strong>NEXT</strong>: 다음 요소로 이동</p>
</li>
<li><p><strong>COUNT</strong>: 배열의 요소 수를 반환</p>
</li>
</ul>
<hr />
<h2 id="heading-plsql-1-record">PL/SQL 구성요소 #1: 레코드 (Record)</h2>
<p><strong><mark>💡요약: </mark> 레코드 (Record)</strong>는 PL/SQL에서 여러 다른 타입의 데이터를 하나의 구조체로 묶을 수 있는 데이터 타입이다. 이를 통해 테이블의 한 행(row)을 그대로 PL/SQL에서 다룰 수 있으며, 테이블의 각 열(column)을 개별적으로 처리할 수 있게 된다. <strong>구조체 (Structure)</strong>와 유사한 개념으로, 데이터베이스 테이블의 각 컬럼을 변수로 묶어 관리할 수 있다.  </p>
<p><strong>✅ 레코드의 주요 특징</strong></p>
<ol>
<li><strong>복합 데이터 구조</strong>:</li>
</ol>
<ul>
<li>레코드는 여러 필드(field)로 구성된다. 각 필드는 다른 데이터 타입을 가질 수 있다. 예를 들어, 이름은 문자열로, 나이는 정수로, 날짜는 날짜 형식으로 지정할 수 있다.</li>
</ul>
<ol start="2">
<li><strong>테이블 또는 커서 행을 참조</strong>:</li>
</ol>
<ul>
<li><p>테이블에서 한 행을 읽어와서 저장할 때, <code>ROWTYPE</code>을 사용하여 해당 테이블의 행 구조와 동일한 레코드를 정의할 수 있다.</p>
</li>
<li><p>커서에서 한 행을 가져올 때도 <code>ROWTYPE</code>을 사용하여 커서의 구조와 일치하는 레코드를 정의할 수 있다.</p>
</li>
</ul>
<p><strong>✅레코드 정의 방법</strong></p>
<ul>
<li><p><strong>사용자 정의 레코드</strong>: 이 방법은 특정 구조를 가진 레코드를 정의할 때 사용한다.</p>
<pre><code class="lang-sql">  TYPE 레코드이름 IS RECORD (
      필드1 데이터타입1,
      필드2 데이터타입2,
      ...
  );
</code></pre>
</li>
<li><p><strong>테이블의 행을 레코드로 정의</strong>:</p>
<pre><code class="lang-sql">  레코드이름 테이블명%ROWTYPE;
</code></pre>
<p>  테이블의 한 행과 동일한 구조를 가지는 레코드를 정의할 때 사용한다. 이 경우, 테이블의 컬럼명이 자동으로 레코드의 필드명이 된다.  </p>
</li>
<li><p><strong>커서의 행을 레코드로 정의</strong>: 커서에서 반환된 한 행의 구조를 레코드로 정의할 때 사용한다.</p>
<pre><code class="lang-sql">  레코드이름 커서명%ROWTYPE;
</code></pre>
<hr />
</li>
</ul>
<h2 id="heading-plsql-2-1-example-of-record">PL/SQL 구성요소 #2: 레코드 예제 1 (Example of Record)</h2>
<p><strong><mark>💡요약: </mark></strong> 이 예제에서는 <strong>PL/SQL 레코드 (PL/SQL Record)</strong> 의 사용법을 보여준다. 레코드는 여러 다른 데이터 타입을 하나로 묶어서 처리할 수 있게 해주는 <strong>구조체 (Structure)</strong> 이다. 주어진 예제에서는 <code>DeptRecType</code>이라는 이름의 레코드를 정의하고, 이를 <code>dept_rec</code>라는 변수에 할당하였다.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
  <span class="hljs-keyword">TYPE</span> DeptRecType <span class="hljs-keyword">IS</span> <span class="hljs-built_in">RECORD</span> (  <span class="hljs-comment">-- 레코드 타입을 정의</span>
    dept_id <span class="hljs-built_in">NUMBER</span>(<span class="hljs-number">4</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> := <span class="hljs-number">10</span>,  <span class="hljs-comment">-- 부서 ID (기본값: 10)</span>
    dept_name <span class="hljs-built_in">VARCHAR2</span>(<span class="hljs-number">30</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> := <span class="hljs-string">'Administration'</span>,  <span class="hljs-comment">-- 부서 이름 (기본값: 'Administration')</span>
    mgr_id <span class="hljs-built_in">NUMBER</span>(<span class="hljs-number">6</span>) := <span class="hljs-number">200</span>,  <span class="hljs-comment">-- 부서 관리자 ID (기본값: 200)</span>
    loc_id <span class="hljs-built_in">NUMBER</span>(<span class="hljs-number">4</span>)  <span class="hljs-comment">-- 위치 ID (기본값 없음)</span>
  );

  dept_rec DeptRecType;  <span class="hljs-comment">-- DeptRecType 타입의 변수 선언</span>

<span class="hljs-keyword">BEGIN</span>
DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'dept_rec:'</span>);
DBMS_OUTPUT.PUT_LINE('<span class="hljs-comment">---------');</span>
DBMS_OUTPUT.PUT_LINE('dept_id: ' || dept_rec.dept_id);
DBMS_OUTPUT.PUT_LINE('dept_name: ' || dept_rec.dept_name);
DBMS_OUTPUT.PUT_LINE('mgr_id: ' || dept_rec.mgr_id);
DBMS_OUTPUT.PUT_LINE('loc_id: ' || dept_rec.loc_id);
<span class="hljs-keyword">END</span>;
</code></pre>
<ol>
<li><p><strong>레코드 타입 정의</strong> (<code>TYPE DeptRecType IS RECORD</code>):</p>
<ul>
<li>이 부분에서 <code>DeptRecType</code>이라는 레코드 타입을 정의하고 있다. 레코드 타입은 하나의 변수로 여러 데이터를 묶을 수 있도록 해준다.</li>
</ul>
</li>
<li><p><strong>필드와 기본값</strong> (<code>dept_id</code>, <code>dept_name</code>, <code>mgr_id</code>, <code>loc_id</code>):</p>
<ul>
<li><p>각 레코드는 여러 필드(field)로 구성됩니다. 필드는 서로 다른 데이터 타입을 가질 수 있다. 예를 들어, <code>dept_id</code>는 숫자 타입이고, <code>dept_name</code>은 문자열 타입이다. 각 필드에는 <strong>기본값 (default value)</strong>이 설정되어 있다.</p>
<ul>
<li><p><code>dept_id</code>: 기본값 <code>10</code> (부서 ID)</p>
</li>
<li><p><code>dept_name</code>: 기본값 <code>'Administration'</code> (부서 이름)</p>
</li>
<li><p><code>mgr_id</code>: 기본값 <code>200</code> (부서 관리자 ID)</p>
</li>
<li><p><code>loc_id</code>: 기본값 없음 (위치 ID) **기본값이 지정되지 않았기 때문에, 해당 값은 <strong>NULL</strong>이 된다. 출력 결과는 <strong>빈 문자열</strong>처럼 보이게 된다. 즉, <strong>공백</strong>이 출력된다.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>변수 선언</strong> (<code>dept_rec DeptRecType</code>):</p>
<ul>
<li><code>dept_rec</code>라는 변수는 <code>DeptRecType</code> 타입의 레코드 변수를 선언하는 부분이다. 이 변수는 나중에 데이터를 저장하는데 사용된다.</li>
</ul>
</li>
<li><p><strong>DBMS_OUTPUT.PUT_LINE</strong>:</p>
<ul>
<li><code>DBMS_OUTPUT.PUT_LINE</code>은 PL/SQL에서 결과를 화면에 출력하는 명령이다. 이 명령을 사용해 변수의 값을 출력할 수 있.</li>
</ul>
</li>
<li><p><strong>점 연산자 (Dot operator)</strong>:</p>
<ul>
<li><p>레코드의 필드에 접근할 때 <code>dept_rec.dept_id</code>와 같이 <strong>점 연산자 (dot operator)</strong>를 사용한다. <code>dept_rec</code>는 레코드 변수이고, 그 뒤에 점(<code>.</code>)을 찍고 필드명을 입력하여 해당 필드의 값을 참조할 수 있다.</p>
<ul>
<li><p><code>dept_rec.dept_id</code>: 부서 ID</p>
</li>
<li><p><code>dept_rec.dept_name</code>: 부서 이름</p>
</li>
<li><p><code>dept_rec.mgr_id</code>: 부서 관리자 ID</p>
</li>
<li><p><code>dept_rec.loc_id</code>: 위치 ID</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733067173096/770c525d-bb0e-4b7d-a0fc-86b5c7cac820.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
<hr />
<h2 id="heading-plsql-3-2-example-of-record">PL/SQL 구성요소 #3: 레코드 예제 2 (Example of Record)</h2>
<p><strong><mark>💡요약: </mark></strong> 이 예제에서는 <strong>레코드 (Record)</strong>와 그 안에 또 다른 레코드가 포함된 구조를 설명하고 있다. <strong>레코드</strong>는 데이터베이스 테이블의 한 행(row)과 비슷한 개념으로, 여러 개의 필드를 가지는 데이터 타입이다. <strong>중첩된 레코드 (Nested Record)</strong> 는 하나의 레코드 안에 다른 레코드가 포함되는 구조를 의미한다.</p>
<pre><code class="lang-sql">TYPE name_rec IS RECORD (
    first employees.first_name%TYPE,  <span class="hljs-comment">-- 직원의 이름</span>
    last employees.last_name%TYPE    <span class="hljs-comment">-- 직원의 성</span>
);
TYPE contact IS RECORD (
    name name_rec, <span class="hljs-comment">-- 중첩된 레코드</span>
    phone employees.phone_number%TYPE  <span class="hljs-comment">-- 전화번호</span>
);
friend contact;

<span class="hljs-keyword">BEGIN</span>
friend.name.first := <span class="hljs-string">'John'</span>;
friend.name.last := 'Smith';
friend.phone := '1-650-555-1234';
DBMS_OUTPUT.PUT_LINE ( friend.name.first || ', ' ||
friend.name.last || ', ' || friend.phone );
<span class="hljs-keyword">END</span>;
</code></pre>
<pre><code class="lang-sql">TYPE name_rec IS RECORD (
    first employees.first_name%TYPE,  <span class="hljs-comment">-- 직원의 이름</span>
    last employees.last_name%TYPE    <span class="hljs-comment">-- 직원의 성</span>
);
</code></pre>
<ul>
<li><code>name_rec</code> 레코드 타입: <code>first</code>와 <code>last</code>라는 두 필드를 가지며, 각각 <code>employees</code> 테이블의 <code>first_name</code>과 <code>last_name</code> 컬럼의 데이터 타입을 따른다.</li>
</ul>
<pre><code class="lang-sql">TYPE contact IS RECORD (
    name name_rec, <span class="hljs-comment">-- 중첩된 레코드</span>
    phone employees.phone_number%TYPE  <span class="hljs-comment">-- 전화번호</span>
);
</code></pre>
<ul>
<li><p><code>contact</code> 레코드 타입:</p>
<ul>
<li><p><code>name</code>이라는 필드를 가진다. 이 필드는 <code>name_rec</code> 레코드 타입을 사용하므로, **<code>first</code>**와 **<code>last</code>**를 가지게 된다.</p>
</li>
<li><p>또 하나의 필드인 <code>phone</code>은 직원의 전화번호를 나타내는 <a target="_blank" href="http://employees.phone"><code>employees.phone</code></a><code>_number</code> 컬럼의 데이터 타입을 사용한다.</p>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-sql">friend contact;  <span class="hljs-comment">-- friend 변수 선언</span>
</code></pre>
<ul>
<li><code>friend</code> 변수: <code>friend</code>는 <code>contact</code> 타입의 레코드 변수로, 이 변수는 <code>name</code> 필드 안에 **<code>first</code>**와 **<code>last</code>**를 포함하고, <code>phone</code> 필드에는 전화번호를 저장할 수 있다.</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">BEGIN</span>
friend.name.first := <span class="hljs-string">'John'</span>;
friend.name.last := 'Smith';
friend.phone := '1-650-555-1234';
</code></pre>
<ul>
<li><p><strong>레코드 값 할당</strong>:</p>
<ul>
<li><p><a target="_blank" href="http://friend.name"><code>friend.name</code></a><code>.first := 'John';</code> <code>friend</code> 레코드의 <code>name</code> 중 <code>first</code> 필드에 <code>'John'</code>이라는 값을 할당한다.</p>
</li>
<li><p><a target="_blank" href="http://friend.name"><code>friend.name</code></a><code>.last := 'Smith';</code> <code>friend</code> 레코드의 <code>name</code> 중 <code>last</code> 필드에 <code>'Smith'</code>라는 값을 할당한다.</p>
</li>
<li><p><a target="_blank" href="http://friend.phone"><code>friend.phone</code></a> <code>:= '1-650-555-1234';</code> <code>friend</code> 레코드의 <code>phone</code> 필드에 전화번호 <code>'1-650-555-1234'</code>를 할당한다.</p>
</li>
</ul>
</li>
</ul>
<p>    이렇게 세 가지 필드에 값을 할당하여 친구의 이름과 전화번호를 설정한다.</p>
<pre><code class="lang-sql">DBMS_OUTPUT.PUT_LINE ( friend.name.first || ', ' ||
friend.name.last || ', ' || friend.phone );
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>값 출력</strong>: 이 부분은 **<code>DBMS_OUTPUT.PUT_LINE</code>**을 사용하여 레코드의 값을 출력한다.</p>
<ul>
<li><p><code>||</code> 기호는 문자열을 이어 붙이는 <strong>(concatenation)</strong> 연산자입니다.</p>
</li>
<li><p><a target="_blank" href="http://friend.name"><code>friend.name</code></a><code>.first</code>, <a target="_blank" href="http://friend.name"><code>friend.name</code></a><code>.last</code>, <a target="_blank" href="http://friend.phone"><code>friend.phone</code></a> 값들이 이어져서 하나의 긴 문자열을 만든다.</p>
</li>
<li><p>예를 들어, 위 코드에서는 <a target="_blank" href="http://friend.name"><code>friend.name</code></a><code>.first</code>가 <code>'John'</code>, <a target="_blank" href="http://friend.name"><code>friend.name</code></a><code>.last</code>가 <code>'Smith'</code>, <a target="_blank" href="http://friend.phone"><code>friend.phone</code></a>이 <code>'1-650-555-1234'</code>이므로 출력되는 결과는 <code>John, Smith, 1-650-555-1234</code>가 된다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733067746041/d4e3cd54-91c2-4e84-bb40-5530680227f7.png" alt class="image--center mx-auto" /></p>
<hr />
<h3 id="heading-3plsql-control-structures"><strong>3️⃣</strong>PL/SQL 제어문(Control Structures)</h3>
<p><strong><mark>💡요약: </mark></strong> PL/SQL에서 <strong>제어문</strong>은 프로그램 흐름을 제어하는 데 사용된다. 제어문은 <strong>조건문</strong>과 <strong>반복문</strong>으로 나뉜다. <strong>IF문</strong>은 조건에 맞춰 여러 가지 처리를 할 수 있는 제어문이고, <strong>CASE문</strong>은 여러 조건값을 비교하여 해당하는 처리문을 실행하는 제어문이다. 두 문법 모두 PL/SQL에서 자주 사용된다.</p>
<ul>
<li><p><strong>IF문</strong>은 주어진 조건이 참일 때 실행할 문을 결정하고, 추가적인 조건을 <code>ELSIF</code>로 확인할 수 있다. 마지막에는 <code>ELSE</code>로 모든 조건이 거짓일 때 실행될 문을 설정할 수 있다.</p>
</li>
<li><p><strong>CASE문</strong>은 여러 조건을 한 번에 확인하고, 조건값에 맞는 처리문을 실행하는 방식이다. <code>WHEN</code>으로 조건을 비교하고, <code>ELSE</code>는 모든 조건이 일치하지 않을 때 실행된다.</p>
</li>
</ul>
<hr />
<p>✅<strong>IF문 (IF Statement)</strong>  </p>
<p><code>IF</code>문은 주어진 조건이 참일 때 특정 코드를 실행하는 제어문이다. 주로 조건에 맞는 처리를 할 때 사용된다.</p>
<pre><code class="lang-sql">IF 조건 THEN
    처리문1;
ELSIF 조건2 THEN
    처리문2;
...
ELSE
    처리문N;
<span class="hljs-keyword">END</span> <span class="hljs-keyword">IF</span>;
</code></pre>
<ul>
<li><p><code>IF</code> 뒤에 조건을 적고, 그 조건이 <strong>참일 때</strong> 처리할 내용을 <code>THEN</code> 아래에 작성한다.</p>
</li>
<li><p><code>ELSIF</code>는 첫 번째 조건이 <strong>거짓</strong>일 때 추가적으로 또 다른 조건을 검사할 수 있게 해준다.</p>
</li>
<li><p><code>ELSE</code>는 모든 조건이 거짓일 때 실행되는 코드를 작성한다.</p>
</li>
</ul>
<hr />
<p><strong>예시</strong>: 이 예제는 <strong>판매 금액</strong>(sales)과 <strong>목표 금액</strong>(quota)을 비교하여 <strong>보너스</strong>(bonus)를 계산하는 로직을 보여준다. 사용된 제어문은 <code>IF</code>로, 주어진 조건에 따라 <strong>보너스 금액</strong>을 다르게 계산한다.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span> <span class="hljs-comment">--변수 선언 (Variable Declaration)</span>
    sales <span class="hljs-built_in">NUMBER</span> := <span class="hljs-number">10100</span>;  <span class="hljs-comment">-- 판매 금액 (Sales amount)</span>
    quota NUMBER := 10500;  <span class="hljs-comment">-- 목표 금액 (Quota)</span>
    bonus NUMBER := 0;      <span class="hljs-comment">-- 보너스 금액 (Bonus)</span>

<span class="hljs-keyword">BEGIN</span> <span class="hljs-comment">--  IF문 사용 (Using IF statement)</span>
    <span class="hljs-keyword">IF</span> sales &gt; (<span class="hljs-keyword">quota</span> + <span class="hljs-number">200</span>) <span class="hljs-keyword">THEN</span>  <span class="hljs-comment">-- 판매 금액이 목표 금액보다 200 이상 클 때</span>
        bonus := (sales - <span class="hljs-keyword">quota</span>) / <span class="hljs-number">4</span>;  <span class="hljs-comment">-- 보너스는 목표 금액을 초과한 판매 금액의 1/4</span>
    ELSE
        IF sales &gt; quota THEN  <span class="hljs-comment">-- 판매 금액이 목표 금액을 초과할 때</span>
            bonus := 50;  <span class="hljs-comment">-- 보너스는 50</span>
        ELSE  <span class="hljs-comment">-- 판매 금액이 목표 금액에 미치지 못할 때</span>
            bonus := 0;  <span class="hljs-comment">-- 보너스는 0</span>
        <span class="hljs-keyword">END</span> <span class="hljs-keyword">IF</span>;
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">IF</span>;
    DBMS_OUTPUT.PUT_LINE('bonus = ' || bonus);  <span class="hljs-comment">-- 계산된 보너스 출력</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>변수 선언 (Variable Declaration):</strong> <code>sales</code>: 실제 판매 금액 , <code>quota</code>: 목표 금액 , <code>bonus</code>: 보너스 금액으로, 기본값은 0으로 설정된다.</p>
</li>
<li><p><strong>IF문 사용 (Using IF statement)</strong></p>
<ul>
<li><p>첫 번째 <code>IF</code>문은 판매 금액이 목표 금액을 200 이상 초과했을 때 <strong>보너스를 1/4</strong>로 계산한다. <code>(sales - quota) / 4</code>로 계산된 보너스를 <code>bonus</code>에 할당한다.</p>
</li>
<li><p>만약 첫 번째 조건이 <strong>거짓</strong>이라면, <strong>두 번째</strong> <code>IF</code>문이 실행된다. 이 경우 판매 금액이 목표 금액을 초과하면 보너스를 <strong>50</strong>으로 설정한다.</p>
</li>
<li><p>만약 <strong>두 번째 조건도 거짓</strong>이라면, <code>ELSE</code> 부분에서 보너스를 <strong>0</strong>으로 설정한다.</p>
</li>
</ul>
</li>
<li><p><strong>DBMS_OUTPUT.PUT_LINE</strong>을 사용하여 최종 보너스를 출력한다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733068127705/5315f9a3-c738-47f7-a7ff-b2ae70efaec2.png" alt class="image--center mx-auto" /></p>
<hr />
<p>✅<strong>CASE문 (CASE Statement)</strong>  </p>
<p><code>CASE</code>문은 여러 조건을 동시에 처리하고, <strong>조건값에 맞는 처리문</strong>을 실행하는 제어문이다. 각 조건을 <code>WHEN</code>과 함께 설정하여 사용한다.</p>
<pre><code class="lang-sql">plsqlCopy codeCASE 조건
    WHEN 조건값1 THEN 처리문1;
    WHEN 조건값2 THEN 처리문2;
    ...
    ELSE 처리문N;
<span class="hljs-keyword">END</span> <span class="hljs-keyword">CASE</span>;
</code></pre>
<ul>
<li><p><code>WHEN</code>은 주어진 조건값을 검사하고, <strong>일치하는 조건값에 해당하는 코드</strong>를 실행합니다.</p>
</li>
<li><p><code>ELSE</code>는 모든 조건에 맞지 않을 때 실행할 코드를 작성합니다.</p>
</li>
</ul>
<p><strong>예시</strong>: 이 예제는 <strong>GRADE</strong> 값에 따라 적절한 평가 메시지를 출력하는 <strong>CASE문</strong>을 보여준다. <code>CASE</code>문은 <strong>조건에 맞는 값을 선택하여 처리</strong>할 수 있는 구조이다.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span> <span class="hljs-comment">-- 변수 선언 (Variable Declaration)</span>
grade <span class="hljs-built_in">CHAR</span>(<span class="hljs-number">1</span>);

<span class="hljs-keyword">BEGIN</span>
grade := <span class="hljs-string">'B'</span>;

CASE grade <span class="hljs-comment">-- CASE문 사용 (Using CASE statement)</span>
WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent');
WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good');
WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('Good');
WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair');
WHEN 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor');

ELSE DBMS_OUTPUT.PUT_LINE('No such grade');

<span class="hljs-keyword">END</span> <span class="hljs-keyword">CASE</span>;
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>변수 선언 (Variable Declaration):</strong> 먼저, <code>grade</code>라는 학생의 성적을 나타내 변수를 선언하고 초기값으로 <code>'B'</code>를 설정한다.</p>
</li>
<li><p><strong>CASE문 사용 (Using CASE statement):</strong> <code>CASE</code>문은 <code>grade</code> 값에 따라 <strong>각각 다른 출력</strong>을 한다. 성적이 <code>'A'</code>일 때는 <code>Excellent</code>, <code>'B'</code>일 때는 <code>Very Good</code> 등의 메시지를 출력한다.</p>
</li>
<li><p>만약 <code>grade</code> 값이 <code>'A'</code>, <code>'B'</code>, <code>'C'</code>, <code>'D'</code>, <code>'F'</code> 중 어느 것도 아니면 <code>ELSE</code>를 사용하여 <code>'No such grade'</code> 메시지를 출력하게 된다.</p>
</li>
</ul>
<ul>
<li><p><code>CASE</code>문은 <code>grade</code> 값을 체크하여 각 조건에 맞는 문장을 실행한다.</p>
</li>
<li><p><code>WHEN</code> 뒤에 조건값을 적고, 조건이 맞으면 그 뒤의 <code>THEN</code> 절을 실행하게 된다.</p>
</li>
<li><p>조건에 맞는 것이 없으면 <code>ELSE</code> 절을 실행한다.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733068403405/2a5ddb25-a77f-4951-9133-8171dd864079.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-plsql-4-loops">PL/SQL 구성요소 #4: 반복문 (Loops)</h2>
<p><strong><mark>💡요약: </mark></strong> PL/SQL에서 반복문을 사용하면 <strong>특정 조건을 만족할 때까지 동일한 작업을 반복</strong>할 수 있다. 가장 많이 사용되는 반복문은 <strong>FOR문</strong>이며, 그 외에 <strong>LOOP문</strong>과 <strong>WHILE문</strong>도 자주 사용된다.</p>
<hr />
<h2 id="heading-loop"><mark>💡LOOP</mark></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733068579443/205b1b19-ee7a-4bad-b914-48a8e618519b.png" alt class="image--center mx-auto" /></p>
<p><strong>✅ LOOP문 (LOOP Statement) :</strong> <code>LOOP</code>문은 <strong>무한 반복</strong>문이다. 조건 없이 반복이 시작되고, 반복을 멈추기 위해서는 <strong>EXIT 조건</strong>을 명시해야 한다.</p>
<p><strong>✅ LOOP문 예제 (LOOP Example)</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
    x <span class="hljs-built_in">NUMBER</span> := <span class="hljs-number">0</span>;  <span class="hljs-comment">-- 변수 x를 0으로 초기화</span>
<span class="hljs-keyword">BEGIN</span>
    <span class="hljs-keyword">LOOP</span>
        DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'Inside loop: x = '</span> || TO_CHAR(x));  <span class="hljs-comment">-- x 값을 출력</span>
        x := x + 1;  <span class="hljs-comment">-- x 값 1 증가</span>
        IF x &gt; 3 THEN  <span class="hljs-comment">-- x가 3보다 크면 반복문을 종료</span>
            EXIT;  <span class="hljs-comment">-- EXIT 조건이 맞으면 반복문을 종료</span>
        <span class="hljs-keyword">END</span> <span class="hljs-keyword">IF</span>;
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
    DBMS_OUTPUT.PUT_LINE('After loop: x = ' || TO_CHAR(x));  <span class="hljs-comment">-- 반복문 종료 후 x 값을 출력</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>LOOP</strong>: 반복문을 시작한다. 이 안의 모든 코드가 반복 실행된다.</p>
</li>
<li><p><strong>DBMS_OUTPUT.PUT_LINE</strong>: <code>x</code> 값을 출력하는 명령어이다.</p>
</li>
<li><p><strong>x := x + 1</strong>: <code>x</code> 값을 1 증가시키는 연산이다.</p>
</li>
<li><p><strong>IF x &gt; 3 THEN EXIT;</strong>: <code>x</code>가 3보다 커지면 <code>EXIT</code>가 실행되어 반복문을 종료한다.</p>
</li>
<li><p><strong>END LOOP;</strong>: <code>LOOP</code>문이 끝나는 부분이다.</p>
</li>
<li><p><strong>DBMS_OUTPUT.PUT_LINE</strong>: 반복문 종료 후 <code>x</code> 값을 출력한다.</p>
</li>
</ul>
<p><strong>✅중요한 부분 요약 (Summary of Key Points)</strong></p>
<ul>
<li><p><code>LOOP</code>문은 <strong>조건 없이 무한 반복</strong>되므로, 반드시 반복을 종료할 조건을 설정해야 한다.</p>
</li>
<li><p><code>EXIT</code> 명령어를 사용하여 반복문을 강제로 종료할 수 있다.</p>
</li>
<li><p><code>LOOP</code>문은 <strong>간단한 반복</strong>을 위해 사용된다.</p>
</li>
</ul>
<p><strong><mark>💡EXIT WHEN 문 사용 (Using EXIT WHEN) </mark></strong> <code>LOOP</code>문을 더 간단하게 표현할 수 있는 방법은 <code>EXIT WHEN</code> 조건을 사용하는 것이다. <code>EXIT WHEN</code>은 반복문을 <strong>특정 조건</strong>에서 자동으로 종료하도록 도와준다. 이를 통해 <code>IF</code>문을 간소화할 수 있게 되고 코드가 더 깔끔하고 읽기 쉽다.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
    x <span class="hljs-built_in">NUMBER</span> := <span class="hljs-number">0</span>;  <span class="hljs-comment">-- 변수 x를 0으로 초기화</span>
<span class="hljs-keyword">BEGIN</span>
    <span class="hljs-keyword">LOOP</span>
        DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'Inside loop: x = '</span> || TO_CHAR(x));  <span class="hljs-comment">-- x 값을 출력</span>
        x := x + 1;  <span class="hljs-comment">-- x 값 1 증가</span>
        EXIT WHEN x &gt; 3;  <span class="hljs-comment">-- x가 3보다 크면 반복문 종료</span>
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
    DBMS_OUTPUT.PUT_LINE('After loop: x = ' || TO_CHAR(x));  <span class="hljs-comment">-- 반복문 종료 후 x 값을 출력</span>
<span class="hljs-keyword">END</span>;
</code></pre>
<ul>
<li><p><strong>EXIT WHEN x &gt; 3;</strong>: 이 구문은 <code>x</code>가 3보다 크면 반복문을 종료한다. 이전 예제에서 <code>IF</code>문과 <code>EXIT</code>를 사용한 방식과 동일한 기능을 한다.</p>
</li>
<li><p>나머지 부분은 <strong>이전 예제와 동일</strong>하며, 반복문 안에서 <code>x</code> 값을 1씩 증가시키고, <code>x &gt; 3</code>이면 반복문을 종료하게 된다</p>
</li>
<li><p><code>EXIT WHEN</code>을 사용하면 <strong>조건을 간단하게 표현</strong>할 수 있으며, <code>IF</code>문 없이 바로 종료 조건을 지정할 수 있게 된다.</p>
</li>
<li><p>**<code>EXIT WHEN</code>**은 조건을 만족하면 즉시 반복문을 종료한다.</p>
</li>
</ul>
<hr />
<h2 id="heading-for-loop"><mark>💡FOR Loop</mark></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733068862331/d7e510e2-07e7-4cd6-a304-bc689671fdd3.png" alt class="image--center mx-auto" /></p>
<p><strong>✅FOR Loop:</strong> <code>FOR</code>문은 반복문 중 하나로, <strong>인덱스 변수를 사용</strong>하여 <strong>정해진 범위</strong> 내에서 값을 증가시키거나 감소시키면서 반복된다. <code>FOR</code>문은 범위 내에서 인덱스가 자동으로 증가하거나 감소하므로 코드가 간결해지고 이해하기 쉬워진다. 가장 많이 쓰이는 loop 이다.</p>
<ul>
<li><p><strong>카운터</strong>: 반복문을 실행할 때 값을 가지고 있는 인덱스 변수이다.</p>
</li>
<li><p><strong>최소값..최대값</strong>: 반복문이 시작될 범위와 종료될 범위를 나타낸다.</p>
</li>
<li><p><code>FOR</code>문은 <strong>시작값부터 끝값까지</strong> 반복을 수행하며, <strong>끝값을 포함</strong>하지 않으며, <strong>자동으로 증가</strong>한다.</p>
</li>
</ul>
<p><strong>✅FOR문 예제 (FOR Loop Example)</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">BEGIN</span>
    DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'lower_bound &lt; upper_bound'</span>);
    FOR i IN 1..3 LOOP  <span class="hljs-comment">-- i는 1부터 3까지 반복</span>
        DBMS_OUTPUT.PUT_LINE(i);  <span class="hljs-comment">-- i 값을 출력</span>
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;

    DBMS_OUTPUT.PUT_LINE('lower_bound = upper_bound');
    FOR i IN 2..2 LOOP  <span class="hljs-comment">-- i는 2부터 2까지 반복</span>
        DBMS_OUTPUT.PUT_LINE(i);  <span class="hljs-comment">-- i 값을 출력</span>
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;

    DBMS_OUTPUT.PUT_LINE('lower_bound &gt; upper_bound');
    FOR i IN 3..1 LOOP  <span class="hljs-comment">-- 실행되지 않음, 시작값이 끝값보다 큼</span>
        DBMS_OUTPUT.PUT_LINE(i);  <span class="hljs-comment">-- 실행되지 않음</span>
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
<span class="hljs-keyword">END</span>;
</code></pre>
<ol>
<li><p><strong>첫 번째 FOR문 (First FOR Loop)</strong>:</p>
<ul>
<li><p>범위가 <code>1..3</code>이므로 <code>i</code>는 1, 2, 3으로 반복된다.</p>
</li>
<li><p>출력 결과는 <code>1</code>, <code>2</code>, <code>3</code>이 차례로 출력된다.</p>
</li>
</ul>
</li>
<li><p><strong>두 번째 FOR문 (Second FOR Loop)</strong>:</p>
<ul>
<li><p>범위가 <code>2..2</code>로, <code>i</code>는 2로만 반복된다.</p>
</li>
<li><p>출력은 <code>2</code>만 출력된다.</p>
</li>
</ul>
</li>
<li><p><strong>세 번째 FOR문 (Third FOR Loop)</strong>:</p>
<ul>
<li><p>범위가 <code>3..1</code>로, <strong>시작값이 끝값보다 크기 때문에</strong> 반복문이 실행되지 않게 된다.</p>
</li>
<li><p>결과적으로 <strong>출력되지 않는다</strong>.</p>
</li>
</ul>
</li>
</ol>
<p><strong>✅ 중요한 부분 요약 (Summary of Key Points)</strong></p>
<ul>
<li><p><strong>FOR문</strong>은 범위 내에서 <strong>자동으로 값을 증가</strong>시키며 반복한다.</p>
</li>
<li><p>시작값이 <strong>끝값보다 크면</strong> 반복문이 <strong>실행되지 않게 된다</strong>.</p>
</li>
<li><p>범위 내에서 지정된 값들만 반복되며, <strong>끝값은 포함되지 않는다</strong>.</p>
</li>
</ul>
<hr />
<h2 id="heading-while"><mark>💡WHILE 문</mark></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733069062309/6197d93d-66c4-4ad0-9d6a-0c13ec786086.png" alt class="image--center mx-auto" /></p>
<p><strong>✅ WHILE 문:</strong> <code>WHILE</code>문은 <strong>조건이 만족될 때까지</strong> 반복을 실행하는 구조이다. 조건이 <strong>true</strong>일 때만 반복이 실행되며, 조건이 <strong>false</strong>이면 반복을 종료한다. 이 예제는 두 개의 <code>WHILE</code>문을 사용하여 조건을 다르게 설정하고 있다.</p>
<p><strong>✅ WHILE문 예제 (WHILE Loop Example)</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">DECLARE</span>
    done <span class="hljs-built_in">BOOLEAN</span> := <span class="hljs-literal">FALSE</span>;
<span class="hljs-keyword">BEGIN</span>
    <span class="hljs-keyword">WHILE</span> done <span class="hljs-keyword">LOOP</span>
        DBMS_OUTPUT.PUT_LINE(<span class="hljs-string">'This line does not print.'</span>);
        done := TRUE;  <span class="hljs-comment">-- 이 할당은 실행되지 않습니다.</span>
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;

    WHILE NOT done LOOP
        DBMS_OUTPUT.PUT_LINE('Hello, world!');
        done := TRUE;
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">LOOP</span>;
<span class="hljs-keyword">END</span>;
</code></pre>
<ol>
<li><p><strong>첫 번째 WHILE문 (First WHILE Loop)</strong>:</p>
<ul>
<li><p><code>done</code>은 <code>FALSE</code>로 초기화되었기 때문에, 첫 번째 <code>WHILE</code>문은 <strong>조건이 false</strong>로 시작하여 <strong>실행되지 않게된다.</strong></p>
</li>
<li><p><code>done := TRUE;</code>는 <strong>실행되지 않으며</strong> <code>WHILE</code>문이 실행되지 않기 때문에, 해당 출력문은 출력되지 않게된다.</p>
</li>
</ul>
</li>
<li><p><strong>두 번째 WHILE문 (Second WHILE Loop)</strong>:</p>
<ul>
<li><p><code>done</code>은 첫 번째 <code>WHILE</code>문에서 변경되지 않아서 여전히 <code>FALSE</code>이다.</p>
</li>
<li><p><code>NOT done</code>은 <code>TRUE</code>가 되어 두 번째 <code>WHILE</code>문이 실행된다.</p>
</li>
<li><p><code>DBMS_OUTPUT.PUT_LINE('Hello, world!');</code>가 출력되며, <code>done := TRUE;</code>로 <code>done</code>이 <code>TRUE</code>로 변경된다.</p>
</li>
<li><p>그 후 <strong>조건이 false</strong>가 되어 <strong>두 번째</strong> <code>WHILE</code>문은 종료된다.</p>
</li>
</ul>
</li>
</ol>
<p><strong>✅ 중요한 부분 요약 (Summary of Key Points)</strong></p>
<ul>
<li><p><code>WHILE</code>문은 조건이 <strong>true</strong>일 때만 실행된다.</p>
</li>
<li><p><code>done</code>이 <code>FALSE</code>이면 첫 번째 <code>WHILE</code>문은 실행되지 않고, <strong>두 번째</strong> <code>WHILE</code>문에서만 실행된다.</p>
</li>
<li><p><code>done := TRUE;</code>는 두 번째 <code>WHILE</code>문에서 실행되어 <strong>조건이 false로 변경되며 반복이 종료된다.</strong></p>
</li>
</ul>
<hr />
<h3 id="heading-loop-for-while-comparison-of-loop-for-while">LOOP, FOR, WHILE 비교 (Comparison of LOOP, FOR, WHILE)</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733069243080/edece968-5a93-464e-8b68-7dea11b2c037.png" alt class="image--center mx-auto" /></p>
<ol>
<li><p><strong>LOOP</strong>:</p>
<ul>
<li><p><strong>무한 루프</strong>로 시작하지만, 내부에서 <code>EXIT</code> 조건을 사용해 종료할 수 있다.</p>
</li>
<li><p>조건을 동적으로 설정할 수 있어 유연성이 높다.</p>
</li>
</ul>
</li>
<li><p><strong>FOR</strong>:</p>
<ul>
<li><p><strong>반복 횟수</strong>가 명확하게 주어질 때 사용된다.</p>
</li>
<li><p>코드가 간결하고 <strong>반복 횟수가 정해져 있어</strong> 코드 작성이 용이하다.</p>
</li>
</ul>
</li>
<li><p><strong>WHILE</strong>:</p>
<ul>
<li><p><strong>조건이 true일 때만 반복</strong>되며, 조건을 만족할 때까지 반복한다.</p>
</li>
<li><p>반복 조건이 <strong>false</strong>가 되면 반복이 종료되며, <strong>조건 만족 여부</strong>를 기준으로 반복이 진행된다.</p>
</li>
</ul>
</li>
</ol>
<p><strong><mark>💡 정리 (Summary)</mark></strong></p>
<ul>
<li><p><strong>LOOP</strong>: 반복을 명시적으로 제어하고 싶을 때 사용.</p>
</li>
<li><p><strong>FOR</strong>: 반복 횟수가 정해져 있을 때 사용.</p>
</li>
<li><p><strong>WHILE</strong>: 조건에 맞을 때만 반복하고 싶을 때 사용.</p>
</li>
</ul>
<hr />
]]></content:encoded></item></channel></rss>