<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hee-suh.log</title>
        <link>https://velog.io/</link>
        <description>원리를 파헤치는 것을 좋아하는 프론트엔드 개발자입니다 🏃🏻‍♀️</description>
        <lastBuildDate>Sun, 09 Feb 2025 08:03:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hee-suh.log</title>
            <url>https://velog.velcdn.com/images/hee-suh/profile/95617f6a-79b3-4e91-a551-5402826c14be/image.JPG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hee-suh.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hee-suh" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[CPU 스케줄링]]></title>
            <link>https://velog.io/@hee-suh/CPU-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81</link>
            <guid>https://velog.io/@hee-suh/CPU-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81</guid>
            <pubDate>Sun, 09 Feb 2025 08:03:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="🦖-operating-system-concepts-10th">🦖 <a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition">Operating System Concepts 10th</a></h4>
<p><strong>PART TWO PROCESS MANAGEMENT</strong>
Chapter 5 CPU Scheduling</p>
</blockquote>
<blockquote>
<p>이전 글인 <a href="https://velog.io/@hee-suh/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4">프로세스</a>에서 <a href="https://velog.io/@hee-suh/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4#%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81-process-scheduling">프로세스 스케줄러</a>의 세 가지 유형을 살펴보았다. </p>
</blockquote>
<ol>
<li>Long Term Scheduler</li>
<li>Medium Term Scheduler</li>
<li>Short Term Scheduler (<strong>CPU Scheduler</strong>)<blockquote>
</blockquote>
이 글에서는 단기 스케줄러에 해당하는 CPU 스케줄러를 알아보자.</li>
</ol>
<h1 id="basic-concepts">Basic Concepts</h1>
<p>CPU 스케줄링은 멀티 프로그램 운영체제의 기본이다. 운영체제는 CPU를 프로세스 간에 교환(switch)함으로써, 컴퓨터를 보다 생산적으로 만든다.</p>
<p>멀티 프로그래밍의 목적은 CPU 이용률을 최대화하기 위해 항상 실행 중인 프로세스를 가지게 하는 데 있다. 멀티 프로그래밍에서는 어느 한순간에 다수의 프로세스를 메모리 내에 유지하고, 어떤 프로세스가 대기(wait)해야 할 경우, 운영체제는 CPU를 그 프로세스로부터 회수해 다른 프로세스에 할당한다.</p>
<h2 id="cpu-io-burst-cycle">CPU-I/O Burst Cycle</h2>
<p>프로세스 실행은 CPU 실행과 I/O 대기(wait)의 <strong>사이클</strong>로 구성된다. 프로세스들은 두 상태 사이를 교대로 왔다 갔다 한다. 프로세스 실행은 <strong>CPU burst</strong>로 시작된다. 뒤이어 <strong>I/O burst</strong>가 발생하고, CPU와 I/O burst가 교차 발생한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/33bd6183-647a-439f-8dee-689f888a2c9b/image.png" alt="**Figure 5.1** Alternating sequence of CPU and I/O bursts."></th>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/8d1d8398-1672-4b5d-84e1-3402031d4a77/image.png" alt="**Figure 5.2** Histogram of CPU-burst durations."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.1</strong> Alternating sequence of CPU and I/O bursts.</td>
<td><strong>Figure 5.2</strong> Histogram of CPU-burst durations.</td>
</tr>
</tbody></table>
<p>CPU burst의 분포는 CPU 스케줄링 알고리즘을 구현할 때 매우 중요할 수 있다.</p>
<h2 id="cpu-scheduler">CPU Scheduler</h2>
<p>CPU가 유휴 상태(idle)가 될 때마다, 운영체제는 준비(ready) 큐에 있는 프로세스 중에서 하나를 선택해 실행해야 한다. 선택 절차는 <strong>CPU 스케줄러</strong>에 의해 수행된다. 스케줄러는 실행 준비(<strong>ready</strong>)가 되어 있는 메모리 내의 프로세스 중에서 선택하여, 이들 중 하나에게 CPU를 할당(<strong>allocate</strong>)한다.</p>
<p>준비(ready) 큐를 구현하는 방식은 다양하다. (e.g. FIFO 큐, 우선순위 큐, 트리, 연결 리스트 등)</p>
<p>큐에 있는 레코드들은 일반적으로 프로세스들의 프로세스 제어 블록(PCB)들이다.</p>
<h2 id="preemptive-and-nonpreepmtive-scheduling">Preemptive and Nonpreepmtive Scheduling</h2>
<h3 id="비선점nonpreemptive-스케줄링">비선점(<strong>nonpreemptive</strong>) 스케줄링</h3>
<p>CPU가 한 프로세스에 할당되면 프로세스가 종료하거나 대기(wait) 상태로 전환해 CPU를 방출(release)할 때까지 점유한다. 이러한 스케줄링 방법을 비선점(<strong>nonpreemptive</strong>) 또는 협력적(<strong>cooperative</strong>)이라고 한다.</p>
<h3 id="선점preemptive-스케줄링">선점(<strong>preemptive</strong>) 스케줄링</h3>
<p>프로세스가 스케줄러에 의해 선점(<strong>preemptive</strong>)될 수 있다. 시분할 시스템에서 time slice가 만료되었거나, 인터럽트나 시스템 호출 종료 시에 더 높은 우선 순위 프로세스가 있을 때, 실행되고 있는 프로세스로부터 강제로 CPU를 회수한다.</p>
<p>선점 스케줄링은 데이터가 다수의 프로세스에 의해 공유될 때 race condition을 초래할 수 있다. 선점은 또한 운영체제 커널 설계에 영향을 주는데, 선점형 커널에는 공유 커널 데이터 구조에 액세스 할 때 race condition 방지를 위해 mutex lock과 같은 기법이 필요하다.</p>
<p>Cf. Windows, macOS, Linux 및 UNIX를 포함한 거의 모든 최신 운영체제들은 선점 스케줄링 알고리즘을 사용한다.</p>
<h3 id="cpu-스케줄링-결정-decision-making-for-cpu-scheduling">CPU 스케줄링 결정 (Decision Making for CPU-Scheduling)</h3>
<ol>
<li>프로세스가 running 상태에서 waiting 상태로 전환될 때 (e.g. I/O 요청, 자식 프로세스 종료를 기다리기 위해 <code>wait()</code> 호출)</li>
<li>프로세스가 running 상태에서 ready 상태로 전환될 때 (e.g. 인터럽트 발생)</li>
<li>프로세스가 waiting 상태에서 ready 상태로 전환될 때 (e.g. I/O 종료)</li>
<li>프로세스가 종료(terminate)할 때</li>
</ol>
<p>상황 1과 4의 경우에는 비선점(nonpreemptive) 스케줄링만 발생한다.</p>
<p>상황 2와 3의 경우에는 비선점(nonpreemptive)과 선점(preemptive) 스케줄링 중 선택이 가능하다.</p>
<h2 id="dispatcher">Dispatcher</h2>
<p>디스패처(<strong>dispatcher</strong>)는 CPU 코어의 제어를 CPU 스케줄러가 선택한 프로세스에 주는 모듈이다.</p>
<p>디스패처의 기능은 다음과 같다.</p>
<ul>
<li>한 프로세스에서 다른 프로세스로 문맥 교환 (context-switching)</li>
<li>사용자 모드로 전환</li>
<li>프로그램을 다시 시작하기 위해 사용자 프로그램의 적절한 위치로 이동 (jump)</li>
</ul>
<p>디스패처는 모든 프로세스의 문맥 교환 시 호출되므로, 가능한 한 빨리 수행되어야 한다. 디스패치 지연(<strong>dispatch latency</strong>)이란 디스패처가 한 프로세스를 정지하고 다른 프로세스의 수행을 시작하는 데까지 소요되는 시간이다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/2ac3c556-5f60-4937-a519-6766ac338554/image.png" alt="**Figure 5.3** The role of the dispatcher."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.3</strong> The role of the dispatcher.</td>
</tr>
</tbody></table>
<h1 id="scheduling-criteria">Scheduling Criteria</h1>
<p>CPU 스케줄링 알고리즘을 비교하기 위한 여러 기준이 있다.</p>
<h3 id="cpu-이용률utilization">CPU 이용률(utilization)</h3>
<p>CPU를 가능한 한 최대한 바쁘게 유지하기를 원한다.</p>
<p>CPU 이용률은 개념적으로는 0에서 100% 범위를 갖지만, 실제 시스템에서는 40%(부하가 적은 시스템의 경우)에서 90%(부하가 큰 시스템의 경우)의 범위를 가져야 한다.</p>
<h3 id="처리량throughput">처리량(throughput)</h3>
<p>CPU가 프로세스를 실행하느라 바쁘다면, 작업이 진행되고 있는 것이다.</p>
<p>작업량 측정의 한 방법은 단위 시간당 완료된 프로세스의 개수로, 이것을 <strong>처리량</strong>(<strong>throughput</strong>)이라고 한다.</p>
<h3 id="총처리-시간turnaround-time">총처리 시간(turnaround time)</h3>
<p>프로세스의 제출(submission) 시간과 완료 시간의 간격을 총처리 시간이라고 한다.</p>
<p><strong>총처리 시간</strong> = 준비(ready) 큐에서 대기한 시간 + CPU에서 실행하는 시간 + I/O 시간을 합한 시간</p>
<h3 id="대기-시간waiting-time">대기 시간(waiting time)</h3>
<p>CPU 스케줄링 알고리즘은 프로세스가 준비(ready) 큐에서 대기하는 시간의 양에만 영향을 준다.</p>
<p><strong>대기 시간</strong>은 준비(ready) 큐에서 대기하면서 보낸 시간의 합이다.</p>
<h3 id="응답-시간response-time">응답 시간(response time)</h3>
<p>대화식 시스템(interactive system)에서는 프로세스가 어떤 출력을 매우 일찍 생성하고, 앞의 결과가 사용자에게 출력되는 사이에 새로운 결과를 얻으려고 연산을 계속하는 경우가 종종 있다. 따라서 하나의 요구를 제출한 후 첫 번째 응답이 나올 때까지의 시간이 중요한 기준이 되는 경우가 있다.</p>
<p>응답 시간은 응답이 시작되는 데까지 걸리는 시간이지, 그 응답을 출력하는 데 걸리는 시간은 아니다.</p>
<blockquote>
<p>CPU 이용률과 처리량을 최대화하고 총처리 시간, 대기 시간, 응답 시간을 최소화하는 것이 바람직하다.</p>
</blockquote>
<h1 id="scheduling-algorithms">Scheduling Algorithms</h1>
<p>CPU 스케줄링은 준비(ready) 큐에 있는 어느 프로세스에 CPU 코어를 할당할 것인지를 결정하는 문제를 다룬다. 한 번에 하나의 프로세스만 실행할 수 있는, 한 개의 처리 코어를 가진 CPU가 한 개인 시스템이라고 가정하고, CPU 스케줄링 알고리즘을 살펴보자.</p>
<h2 id="first-come-first-served-fcfs-scheduling">First-Come, First Served (FCFS) Scheduling</h2>
<ul>
<li><p><strong>FCFS</strong></p>
<ul>
<li>가장 간단한 CPU 스케줄링 알고리즘은 선입 선처리(<strong>First-Come, FIrst Served</strong>, <strong>FCFS</strong>) 스케줄링 알고리즘이다.</li>
<li>이 방법에서는 CPU를 먼저 요청하는 프로세스가 CPU를 먼저 할당받는다.</li>
<li>FCFS 정책의 구현은 선입선출(FIFO) 큐로 쉽게 관리할 수 있다.</li>
</ul>
</li>
<li><p><strong>Preemptive or <code>Nonpreemptive</code></strong></p>
<p>FCFS 스케줄링 알고리즘은 비선점형(nonpreemptive)이다.
일단 CPU가 한 프로세스에 할당되면, 그 프로세스가 종료하든지 I/O 처리를 요구하여 CPU를 방출할 때까지 CPU를 점유한다.</p>
</li>
<li><p><strong>Cons</strong></p>
<ul>
<li><p>FCFS 정책 하에서 평균 대기 시간은 일반적으로 <strong>최소가 아니며</strong>, 프로세스 <strong>CPU burst 시간</strong>이 크게 변할 경우에는 평균 대기 시간도 <strong>상당히 변할 수 있다</strong>.</p>
</li>
<li><p>추가로, 동적 상황에서 FCFS 스케줄링의 성능을 고려해 보자. 하나의 CPU 중심(<strong>CPU-bound</strong>) 프로세스와 많은 수의 I/O 중심(<strong>I/O-bound</strong>) 프로세스를 갖는다면 어떨까?</p>
<p>모든 다른 프로세들이 하나의 긴 프로세스가 CPU를 양도하기를 기다리는 호위 효과(<strong>convoy effect</strong>)가 발생하여, 짧은 프로세스들이 먼저 처리되도록 허용될 때보다 CPU와 장치 이용률이 저하되는 결과를 낳을 수 있다.</p>
</li>
</ul>
</li>
</ul>
<h2 id="shortest-job-first-sjf-scheduling">Shortest-Job-First (SJF) Scheduling</h2>
<ul>
<li><p><strong>SJF</strong></p>
<ul>
<li>최단 작업 우선(<strong>shortest-job-first</strong>, <strong>SJF</strong>): <strong>shortest-next-CPU-burst</strong></li>
<li>SJF 알고리즘은 각 프로세스에 다음 CPU burst 길이를 연관시킨다.</li>
<li>CPU가 이용 가능해지면, 가장 작은 다음 CPU burst를 가진 프로세스에 할당한다.</li>
<li>두 프로세스가 동일한 길이의 다음 CPU burst를 가지면, FCFS 스케줄링을 적용하여 순위를 정한다.</li>
</ul>
</li>
<li><p><strong><code>Preemptive</code></strong> or <strong><code>Nonpreemptive</code></strong></p>
<p>SJF 알고리즘은 선점형일 수도 있고 비선점형일 수도 있다.
  앞의 프로세스가 실행되는 동안, 현재 실행되고 있는 프로세스의 남은 시간보다도 더 짧은 CPU burst를 가진 새로운 프로세스가 준비 큐에 도착했다고 가정하자.</p>
<ul>
<li>선점형 SJF 알고리즘은 현재 실행하고 있는 프로세스를 선점할 것이다. <em>Cf. <a href="https://velog.io/@hee-suh/CPU-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81#shortest-remaining-time-first-srtf-scheduling">Shortest Remaining Time First (SRTF) Scheduling</a></em></li>
<li>비선점형 SJF 알고리즘은 현재 실행하고 있는 프로세스가 자신의 CPU burst를 끝내도록 허용한다.</li>
</ul>
</li>
<li><p><strong>Pros</strong></p>
<ul>
<li><p>SJF 알고리즘은 주어진 프로세스 집합에 대해 최소의 평균 대기 시간을 가진다는 점에서 <strong>최적임을 증명</strong>할 수 있다.</p>
<p>짧은 프로세스를 긴 프로세스의 앞으로 이동함으로써, 짧은 프로세스의 대기 시간을 긴 프로세스의 대기 시간이 증가하는 것보다 더 많이 줄일 수 있으며 결과적으로 평균대기 시간이 줄어든다.</p>
</li>
</ul>
</li>
<li><p><strong>Cons</strong></p>
<ul>
<li>SJF 알고리즘이 최적이긴 하지만, <strong>다음(next) CPU burst</strong>의 길이를 알 방법이 없기(<strong>no way to know</strong>) 때문에 CPU 스케줄링 수준에서는 구현할 수 없다.</li>
<li>SJF 스케줄링과 근사한 방법을 사용하여 다음 CPU burst를 예측(<strong>predict</strong>)할 수는 있다. 그러므로 다음 CPU burst 길이의 근삿값을 계산해, 가장 짧은 예상 CPU burst를 가진 프로세스를 선택한다.</li>
</ul>
<p>일반적으로 이전의 CPU burst들의 길이를 지수 평균(<strong>exponential average</strong>)한 것으로 예측한다.</p>
<blockquote>
<p>$τ_{n+1} =αt_n +(1−α)τ_n$ </p>
<ul>
<li>$τ_{n+1}$은 다음 CPU burst에 대한 예측값이다.</li>
<li>$t_n$은 n번째 CPU burst의 길이로 최근의 정보를 가지며, $τ_n$은 과거의 역사를 저장한다.</li>
<li>$α$는 매개변수로 예측에서 최근의 값과 이전 값의 상대적인 무게를 제어한다. <em>$(0$ ≤ $α$ ≤ $1)$</em></li>
</ul>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/8680132d-2d7e-4640-9cd5-f3605bd5f5ae/image.png" alt="**Figure 5.4** Prediction of the length of the next CPU burst."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.4</strong> Prediction of the length of the next CPU burst.</td>
</tr>
</tbody></table>
</li>
</ul>
<h3 id="shortest-remaining-time-first-srtf-scheduling">Shortest Remaining Time First (SRTF) Scheduling</h3>
<p>선점형 SJF 알고리즘은 때때로 최소 잔여 시간 우선(<strong>shortest remaining time first</strong>) 스케줄링이라고 불린다.</p>
<p>현재 실행하고 있는 프로세스의 남은 시간보다도 더 짧은 CPU burst를 가진 새로운 프로세스가 준비(ready) 큐에 도착하면, 현재 실행하고 있는 프로세스를 선점한다.</p>
<h2 id="round-robin-scheduling-rr-scheduling">Round-Robin Scheduling (RR) Scheduling</h2>
<ul>
<li><p><strong>RR</strong></p>
<ul>
<li>라운드 로빈(<strong>Round Robin</strong>, <strong>RR</strong>) 스케줄링 알고리즘은 FCFS 스케줄링과 유사하지만 시스템이 프로세스들 사이를 옮겨 다닐 수 있도록 선점이 추가된다.</li>
<li>시간 할당량(<strong>time quantum</strong>) 또는 <strong>time slice</strong>라고 하는 작은 단위를 시간을 정의한다. (일반적으로 10~100ms 동안)</li>
<li>준비(ready) 큐는 원형 큐(<strong>circular queue</strong>)로 동작하며, CPU 스케줄러는 준비 큐를 돌면서 한 번에 한 프로세스에 한 번의 시간 할당량 동안 CPU를 할당한다.
새로운 프로세스들은 준비 큐의 tail에 추가되며, CPU 스케줄러는 준비 큐에서 첫 번째 프로세스를 선택해 시간 할당량 이후에 인터럽트를 걸도록 timer를 설정한 후, 프로세스를 dispatch 한다.</li>
<li>두 가지 경우 중 하나가 발생할 것이다.<ul>
<li><strong>less than</strong> one time quantum
  프로세스의 CPU burst가 한 번의 시간 할당량보다 작을 수 있다.
  프로세스 자신이 CPU를 자발적으로 내보낼(release) 것이다. 스케줄러는 그 후 준비 큐에 있는 다음 프로세스로 진행할 것이다.</li>
<li><strong>longer than</strong> one time quantum
  현재 실행 중인 프로세스의 CPU burst가 한 번의 시간 할당량보다 길 수 있다.
  타이머가 끝나고 운영체제에 인터럽트를 발생할 것이다. 문맥 교환(context switch)이 일어나고 실행하던 프로세스는 준비 큐의 tail에 들어간다. 그 후 CPU 스케줄러는 준비 큐의 다음 프로세스를 선택할 것이다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong><code>Preemptive</code> or Nonpreemptive</strong></p>
<p>프로세스의 CPU burst가 한 번의 시간 할당량을 초과하면, 프로세스는 선점되고 준비 큐로 되돌아간다. 따라서 RR 스케줄링 알고리즘은 선점형이다.</p>
</li>
<li><p><strong>Cons</strong></p>
<ul>
<li>종종 RR 정책하에서 평균 대기 시간은 길다.</li>
</ul>
</li>
<li><p><strong>Performance</strong></p>
<p>RR 알고리즘의 성능은 시간 할당량(<strong>time quantum</strong>)의 크기(<strong>size</strong>)에 매우 많은 영향을 받는다.</p>
<ul>
<li>시간 할당량이 매우 크면 RR 정책은 FCFS 정책과 같다.</li>
<li>시간 할당량이 매우 작다면 RR 정책은 매우 많은 문맥 교환(context switch)을 야기한다.
그러므로 시간 할당량이 문맥 교환 시간과 비교해 더 큰 것이 바람직하다.</li>
</ul>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/a93c9261-3202-4fa2-ba32-0c82d9ae3f60/image.png" alt="**Figure 5.5** How a smaller time quantum increases context switches."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.5</strong> How a smaller time quantum increases context switches.</td>
</tr>
</tbody></table>
</li>
<li><p><strong>Turnaround time</strong></p>
<p>총처리 시간 또한 시간 할당량의 크기에 좌우되지만, 평균 총처리 시간과 시간 할댱량이 정비례하지는 않는다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/b9b1933b-9ede-48b9-bdd2-49d144b41c51/image.png" alt="**Figure 5.6** How turnaround time varies with the time quantum."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.6</strong> How turnaround time varies with the time quantum.</td>
</tr>
</tbody></table>
</li>
</ul>
<h2 id="priority-scheduling">Priority Scheduling</h2>
<ul>
<li><p><strong>Priority</strong></p>
<ul>
<li>우선순위(<strong>priority</strong>)가 각 프로세스들에 연관되어 있으며, CPU는 가장 높은 우선순위(<strong>highest priority</strong>)를 가진 프로세스에 할당된다.</li>
<li>Cf. 정하는 기준에 따라서 작은 수가 높은 우선순위를 나타낼 수도 있고, 큰 수가 높은 우선순위를 나타낼 수도 있다.*</li>
<li>우선순위가 같은(<strong>equal</strong>) 프로세스들은 <strong>FCFS</strong> 순서로 스케줄된다.</li>
<li><strong>SJF</strong> 알고리즘은 일반적인 우선순위 스케줄링 알고리즘의 특별한 경우이다.<ul>
<li>이 경우의 우선순위는 <strong>next CPU burst</strong>의 역(<strong>inverse</strong>)이다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong><code>Preemptive</code></strong> or <strong><code>Nonpreemptive</code></strong></p>
<p>우선순위 스케줄링은 선점형일 수도 있고 비선점형일 수도 있다.
  프로세스가 준비 큐에 도착하면, 새로 도착한 프로세스의 우선순위를 현재 실행 중인 프로세스의 우선순위와 비교한다.</p>
<ul>
<li>선점형 우선순위 스케줄링 알고리즘은 새로 도착한 프로세스의 우선순위가 현재 실행되는 프로세스의 우선순위보다 높다면 CPU를 선점한다.</li>
<li>비선점형 우선순위 스케줄링 알고리즘은 단순히 준비 큐의 head에 새로운 프로세스를 추가한다.</li>
</ul>
</li>
<li><p><strong>Cons</strong></p>
<ul>
<li><p>우선순위 스케줄링 알고리즘의 주요 문제는 무한 봉쇄(<strong>indefinite blocking</strong>) 또는 기아 상태(<strong>starvation</strong>)이다.
우선순위 스케줄링 알고리즘을 사용할 경우, 낮은 우선순위 프로세스들이 실행 준비 되어있더라도 CPU를 무한히 대기하는 경우가 발생한다.</p>
</li>
<li><p><em>Solution*</em></p>
<ul>
<li><strong>Aging</strong>
  무한 blocking 문제에 대한 한 가지 해결 방안은 노화(<strong>aging</strong>)다. Aging은 오랫동안 시스템에서 대기하는 프로세스들의 우선순위를 점진적으로 증가시킨다.</li>
<li>RR + Priority Scheduling
  다른 옵션은 라운드 로빈과 우선순위 스케줄링을 결합하는 방법이다. 시스템이 우선순위가 가장 높은 프로세스를 실행하고 우선순위가 같은 프로세스들은 라운드 로빈 스케줄링을 사용하여 스케줄 하는 방식이다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="multilevel-queue-scheduling">Multilevel Queue Scheduling</h2>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/f8cc5101-8561-47ca-b79f-1d896e365fba/image.png" alt="**Figure 5.7** Separate queues for each priority."></th>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/013f2799-29af-4018-8981-bb97f74b463b/image.png" alt="**Figure 5.8** Multilevel queue scheduling."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.7</strong> Separate queues for each priority.</td>
<td><strong>Figure 5.8</strong> Multilevel queue scheduling.</td>
</tr>
</tbody></table>
<ul>
<li><p><strong>MLQ</strong></p>
<ul>
<li><p>다단계 큐(<strong>multilevel queue</strong>) 스케줄링은 우선순위마다 별도의 큐를 갖고, 우선순위가 가장 높은 큐에서 프로세스를 스케줄하는 방식이다.
Cf. 모든 프로세스가 단일 큐에 배치된다면, 우선순위가 가장 높은 프로세스를 선택하기 위해 $O(n)$의 검색이 필요할 수 있다.</p>
<ul>
<li>이 방법은 우선순위 스케줄링이 라운드 로빈과 결합한 경우에도 효과적이다.
우선순위가 가장 높은 큐에 여러 프로세스가 있는 경우 라운드 로빈 순서로 실행된다. 보통 우선순위가 각 프로세스에 정적(영구적)으로 할당되며 프로세스는 실행시간(runtime) 동안 동일한 큐에 남아 있다.</li>
<li>프로세스 유형에 따라 프로세스를 여러 개의 개별 큐로 분할하기 위해 다단계 큐 스케줄링 알고리즘을 사용할 수도 있다. (e.g. <strong>foreground</strong>(interactive) 프로세스와 <strong>background</strong>(batch) 프로세스 구분)</li>
</ul>
</li>
<li><p>각 큐에는 자체 스케줄링 알고리즘이 있을 수 있다. (e.g. background 큐는 FCFS, foreground 큐는 RR)</p>
</li>
<li><p>큐와 큐 사이에 스케줄링도 반드시 있어야 한다.</p>
<ul>
<li>일반적으로 고정 우선순위의 선점형 스케줄링으로 구현된다.</li>
<li>다른 가능성은 큐들 사이에 시간을 나누어 사용하는 것이다. (e.g. foreground 큐에는 CPU 시간의 80%, background 큐에는 20% 할당)</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="multilevel-feedback-queue-scheduling">Multilevel Feedback Queue Scheduling</h2>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/c0876ced-7477-412f-8df1-faa8f88b1533/image.png" alt="**Figure 5.9** Multilevel feedback queues."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.9</strong> Multilevel feedback queues.</td>
</tr>
</tbody></table>
<ul>
<li><p><strong>MLFQ</strong></p>
<ul>
<li>다단계 피드백 큐(<strong>multilevel feedback queue</strong>) 스케줄링 알고리즘에서는 프로세스가 큐들 사이를 이동하는 것을 허용한다.    </li>
<li>프로세스들을 CPU burst 특징에 따라서 구분하여, CPU 시간을 너무 많이 사용하는 프로세스는 낮은 우선순위의 큐로 이동된다.</li>
<li>이 방법에서는 I/O 중심의 프로세스와 대화형 프로세스들을 높은 우선순위의 큐에 넣는다. (보통 짧은 CPU burst가 특징인 프로세스들)</li>
<li>낮은 우선순위의 큐에서 너무 오래 대기하는 프로세스는 높은 우선순위의 큐로 이동할 수 있다. aging을 통해 기아(starvation) 상태를 예방한다.</li>
<li>MLFQ 스케줄러의 정의에 의하면, 이 스케줄링 알고리즘이 가장 일반적인 CPU 스케줄링 알고리즘으로, 설계 중인 특정 시스템에 부합하도록 구성 가능하다.</li>
</ul>
</li>
<li><p><strong>Cons</strong></p>
<ul>
<li>가장 좋은 스케줄러로 동작하기 위해서는 모든 매개변수들의 값을 선정하는 특정 방법이 필요하기 때문에 가장 복잡한 알고리즘이다.</li>
</ul>
</li>
</ul>
<h1 id="multi-processor-scheduling">Multi-Processor Scheduling</h1>
<p>여러 개의 CPU를 사용할 수 있다면, 여러 스레드가 병렬로 실행될 수 있으므로 부하 공유(<strong>load sharing</strong>)가 가능해진다. 그러나 스케줄링 문제는 그만큼 더 복잡해진다.</p>
<p><strong>멀티프로세서</strong>는 다음 시스템 아키텍처들에 사용할 수 있다.</p>
<ul>
<li><p>프로세서가 기능 면에서 동일한(homogeneous) 시스템</p>
<p>  이 경우, 사용 가능한 CPU를 사용하여 큐에 있는 임의의 프로세스를 실행할 수 있다. </p>
<ul>
<li>Multicore CPUs</li>
<li>Multithreaded cores</li>
<li>NUMA 시스템</li>
</ul>
</li>
<li><p>프로세스의 기능이 동일하지 않은 시스템</p>
<ul>
<li>이기종(heterogeneous) 멀티프로세싱</li>
</ul>
</li>
</ul>
<h2 id="approaches-to-multiple-processor-scheduling">Approaches to Multiple-Processor Scheduling</h2>
<h3 id="비대칭-멀티프로세싱asymmetric-multiprocessing">비대칭 멀티프로세싱(<strong>asymmetric multiprocessing</strong>)</h3>
<p>멀티프로세서 시스템의 CPU 스케줄링에 관한 해결 방법으로, master server라는 단일 프로세서가 모든 스케줄링 결정, I/O 처리, 다른 시스템 활동을 처리하는 것이다. 다른 프로세서들은 사용자 코드만 수행한다. </p>
<ul>
<li>오직 하나의 코어만 시스템 자료구조에 접근하여 자료 공유의 필요성을 배제하기 때문에 간단하다.</li>
<li>하지만 마스터 서버가 전체 시스템 성능을 저하할 수 있는 병목이 된다는 단점이 있다.</li>
</ul>
<h3 id="대칭-멀티프로세싱symmetric-multiprocessing-smp">대칭 멀티프로세싱(<strong>symmetric multiprocessing</strong>, <strong>SMP</strong>)</h3>
<p>멀티프로세서를 지원하기 위한 표준 접근 방식으로, 각 프로세서는 스스로 스케줄링할 수 있다. 각 프로세서의 스케줄러가 준비(ready) 큐를 검사하고 실행할 스레드를 선택하여 스케줄링이 진행된다.</p>
<p>스케줄 대상이 되는 스레드를 관리하기 위한 두 가지 전략이 있다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/ddab2fa7-b2e1-4c7d-a9fd-fd838a5bc373/image.png" alt="**Figure 5.11** Organization of ready queues."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.11</strong> Organization of ready queues.</td>
</tr>
</tbody></table>
<ol>
<li><p>모든 스레드가 공통 준비(common ready) 큐에 있을 수 있다.</p>
<p> 공유 준비 큐에 경쟁 조건(race condition)이 생길 수 있으므로 두 개의 다른 프로세서가 동일한 스레드를 스케줄 하지 않도록, 큐에서 스레드가 없어지지 않도록 보장해야 한다.</p>
<p> 이 경쟁 조건으로부터 공통 준비 큐를 보호하기 위해 locking 기법 중 하나를 사용할 수 있으나, 큐에 대한 모든 액세스에 lock 소유권이 필요하므로 locking은 매우 경쟁이 심할 것이고 공유 큐 액세스가 성능의 병목이 될 수 있다.</p>
</li>
<li><p>각 프로세서는 자신만의 스레드 큐를 가질 수 있다.</p>
<p> 각 프로세서가 자신만의 실행 큐에서 스레드를 스케줄 할 수 있도록 허용하므로 공유 실행 큐와 관련되어 발생할 수 있는 성능 문제를 겪지 않는다. 따라서 SMP를 지원하는 시스템에서 가장 일반적인 접근 방식이다. 또한 자신만의 프로세스별 큐가 있으면 캐시 메모리를 보다 효율적으로 사용할 수 있다. 프로세스별 실행 큐마다 부하의 양이 다를 수 있으나, 균형 알고리즘을 사용하여 모든 프로세스 간에 부하를 균등하게 만들 수 있다. Cf. <a href="https://velog.io/@hee-suh/CPU-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81#load-balancing">Load Balancing</a> </p>
</li>
</ol>
<p>Cf. Windows, Linux 및 macOS와 Android, iOS의 모바일 시스템을 포함한 거의 모든 최신 운영체제는 SMP를 지원한다.</p>
<h2 id="multicore-processors">Multicore Processors</h2>
<p>SMP 시스템은 다수의 물리 프로세서를 제공함으로써 다수의 프로세스가 병렬로 실행되게 한다.</p>
<h3 id="multicore-processor"><strong>Multicore Processor</strong></h3>
<p>현대 컴퓨터 하드웨어는 동일한 물리적인 칩 안에 여러 개의 처리 코어를 장착하여 multicore 프로세서가 된다. 각 코어는 구조적인 상태를 유지하고 있어서 운영체제 입장에서는 개별적인 논리적 CPU처럼 보인다.</p>
<p>multicore 프로세서를 사용하는 SMP 시스템은 각 CPU가 자신의 물리 칩을 가지는 시스템과 비교해 비교적 속도가 빠르고 적은 전력을 소모한다.</p>
<p>그러나 스케줄링 문제를 복잡하게 하는 단점이 있다.</p>
<blockquote>
<h4 id="💡-메모리-스톨-memory-stall">💡 메모리 스톨 (Memory Stall)</h4>
</blockquote>
<p>프로세서가 메모리에 접근할 때 데이터가 가용해지기를 기다리면서 많은 시간을 허비하는 상황이다. 최신 프로세서가 메모리보다 훨씬 빠른 속도로 작동하기 때문에 주로 발생한다. 그러나 cache miss(캐시 메모리에 없는 데이터를 액세스)로 인해 메모리 스톨이 발생할 수도 있다.</p>
<h3 id="multithreaded-core"><strong>Multithreaded Core</strong></h3>
<p>메모리 스톨을 해결하기 위해 최근의 많은 하드웨어 설계는 멀티스레드 처리 코어를 구현하였다. 이러한 설계에서 하나의 코어에 2개 이상의 하드웨어 스레드(<strong>hardware threads</strong>)가 할당된다. 이렇게 하면 메모리를 기다리는 동안 하나의 하드웨어 스레드가 중단되면 코어가 다른 스레드로 전환되며, 각 스레드의 실행이 인터리브된다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/95cd2208-6d1a-45fe-8afa-984c125129a3/image.png" alt="**Figure 5.13** Multithreaded multicore system."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.13</strong> Multithreaded multicore system.</td>
</tr>
</tbody></table>
<p>운영체제 관점에서 각 하드웨어 스레드는 명령어 포인트 및 레지스터 집합과 같은 구조적 상태를 유지하므로 소프트웨어 스레드를 실행할 수 있는 논리적 CPU로 보인다. 이 기술은 <strong>chip multithreading</strong>(CMT)이라고 알려져있다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/722ea4f8-294d-47c3-aefe-33a883b05a12/image.png" alt="**Figure 5.14** Chip multithreading."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.14</strong> Chip multithreading.</td>
</tr>
</tbody></table>
<h2 id="load-balancing">Load Balancing</h2>
<p>SMP 시스템에서 프로세서가 하나 이상이라는 것을 최대한 활용하려면, 부하(workload)를 모든 프로세서에 균등하게 배분하는 것이 매우 중요하다. <strong>Load balancing</strong>은 SMP 시스템의 모든 처리기 사이에 부하가 고르게 배분되도록 시도한다. 로드 밸런싱은 통상 각 처리기가 실행할 스레드를 위한 자신만의 준비 큐를 가지고 있는 시스템에서만 필요한 기능이라는 것을 주의해야 한다. 공통의 실행 큐만 있는 시스템에서는 한 프로세서가 쉬게 되면 곧바로 공통 큐에서 새로운 프로세스를 선택하여 실행하기 때문에 로드 밸런싱은 필요하지 않다. Cf. <a href="https://velog.io/@hee-suh/CPU-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81#%EB%8C%80%EC%B9%AD-%EB%A9%80%ED%8B%B0%ED%94%84%EB%A1%9C%EC%84%B8%EC%8B%B1symmetric-multiprocessing-smp">대칭 멀티프로세싱(symmetric multiprocessing, SMP)</a></p>
<p>로드 밸런싱을 위해서는 push migration과 pull migration 방식의 두 가지 일반적인 접근법이 있다.</p>
<ul>
<li><p><strong>Push migration</strong></p>
<p>  특정 태스크가 주기적으로 각 프로세서의 부하를 감시하고 만일 불균형 상태로 밝혀지면 과부하인 프로세서에서, 쉬고(idle) 있거나 덜 바쁜 프로세서로 스레드를 이동(또는 push)시킴으로써 부하를 분배한다.</p>
</li>
<li><p><strong>Pull migration</strong></p>
<p>  쉬고(idle) 있는 프로세서가, 바쁜 프로세서를 기다리고 있는 프로세스를 pull할 때 일어난다.</p>
</li>
</ul>
<p>Push migration과 pull migration은 상호 배타적일 필요는 없으며 실제로 로드 밸런싱 시스템에서 종종 병렬적으로 구현된다.</p>
<p>“균등 부하(balanced load)”라는 개념은 다른 의미를 가질 수도 있다.</p>
<ul>
<li>모든 큐에 비슷한 수의 스레드가 있어야 한다.</li>
<li>모든 큐에 스레드 우선순위를 균등하게 분배해야 한다.</li>
<li>…</li>
</ul>
<h1 id="real-time-cpu-scheduling">Real-Time CPU Scheduling</h1>
<p>실시간 운영체제에서 CPU를 스케줄링 할 때는 일반적으로 soft 실시간 시스템과와 hard 실시간 시스템으로 구분한다.</p>
<ul>
<li><p><strong>Soft real-time system</strong></p>
<p>soft 실시간 시스템은 중요한 실시간 프로세스가 스케줄 되는 시점에 관해 아무런 보장을 하지 않는다. 오직 중요 프로세스가 그렇지 않은 프로세스들에 비해 우선권을 가진다는 것만 보장한다.</p>
</li>
<li><p><strong>Hard real-time system</strong></p>
<p>  hard 실시간 시스템은 더 엄격한 요구 조건을 만족시켜야 한다. 태스크는 반드시 마감시간(deadline)까지 받은 서비스에만 해당된다.</p>
</li>
</ul>
<h2 id="지연시간-최소화-minimizing-latency">지연시간 최소화 (Minimizing Latency)</h2>
<h3 id="이벤트-지연시간-event-latency">이벤트 지연시간 (Event latency)</h3>
<p>시스템은 일반적으로 실시간으로 발생하는 이벤트를 기다린다. 이벤트가 발생하면, 시스템은 가능한 빨리 그에 응답을 하고 그에 맞는 동작을 수행해야 한다. <strong>이벤트 지연시간</strong>은 이벤트가 발생해서 그에 맞는 서비스가 수행될 때까지의 시간을 말한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/5a5e4a7e-158f-43d2-b074-ef0d3e9e7072/image.png" alt="**Figure 5.17** Event latency. "></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.17</strong> Event latency.</td>
</tr>
</tbody></table>
<p>인터럽트 지연시간과 디스패치 지연시간이 실시간 시스템의 성능을 좌우한다.</p>
<h3 id="인터럽트-지연시간-interrupt-latency">인터럽트 지연시간 (Interrupt latency)</h3>
<p><strong>인터럽트 지연시간</strong>은 CPU에 인터럽트가 발생한 시점부터 해당 인터럽트 처리 루틴이 시작하기까지의 시간을 말한다. 인터럽트가 발생하면 운영체제는 수행 중인 명령어를 완수하고 발생한 인터럽트의 종류를 결정한다. 해당 인터럽트 서비스 루틴(ISR)을 사용하여 인터럽트를 처리하기 전에, 문맥 교환을 위해 현재 수행 중인 프로세스의 상태를 저장해 놓아야 한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/9f506a4c-bb8f-4239-b8fe-e09b45e8fba8/image.png" alt="**Figure 5.18** Interrupt latency."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.18</strong> Interrupt latency.</td>
</tr>
</tbody></table>
<p>실시간 태스크가 즉시 수행될 수 있도록 인터럽트 지연시간을 최소화하는 것은 실시간 운영체제에 매우 중요한 일이다. 인터럽트 지연시간에 영향을 주는 요인 중 하나는 커널 데이터 구조체를 갱신하는 동안 인터럽트가 사용 불가능해지는 시간이다. 실시간 운영체제는 인터럽트 불능 시간을 매우 짧게 해야 한다.</p>
<h3 id="디스패치-지연시간-dispatch-latency">디스패치 지연시간 (Dispatch latency)</h3>
<p><strong>디스패치 지연시간</strong>은 스케줄링 디스패처가 하나의 프로세스를 멈추게 하고 다른 프로세스를 시작하는 데까지 걸리는 시간을 말한다. CPU를 즉시 사용해야 하는 실시간 태스크가 있다면, 실시간 운영체제는 이 지연 시간을 최소화해야 한다. 디스패치 지연시간을 최소화하는 가장 효과적인 방법은 선점형 커널이다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/e27c3e76-202e-4826-a675-0d216d306487/image.png" alt="**Figure 5.19** Dispatch latency."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 5.19</strong> Dispatch latency.</td>
</tr>
</tbody></table>
<p>디스패치 지연시간의 충돌 단계(<strong>conflict phase</strong>)는 다음의 두 가지 요소로 구성되어 있다.</p>
<ol>
<li>커널에서 동작하는 프로세스에 대한 선점</li>
<li>낮은 우선순위 프로세스가 높은 우선순위의 프로세스가 필요한 자원 방출(release)</li>
</ol>
<p>충돌 단계에 이어 디스패치 단계는 우선순위가 높은 프로세스를 사용 가능한 CPU에 스케줄 한다.</p>
<h2 id="priority-based-scheduling">Priority-Based Scheduling</h2>
<p>실시간 운영체제에서 가장 중요한 기능은 실시간 프로세스에 CPU가 필요할 때 바로 응답을 해주는 것이다.</p>
<p>따라서 실시간 운영체제의 스케줄러는 선점을 이용한 우선순위 기반의 알고리즘을 지원해야 한다. 우선순위 기반의 스케줄링 알고리즘은 각각의 프로세스의 중요성에 따라서 우선순위를 부여한다. 스케줄러가 선점 기법을 제공한다면, 현재 CPU를 이용하고 있는 프로세스가 더 높은 우선순위를 갖는 프로세스에 선점될 수도 있다.</p>
<p>그러나 선점 및 우선순위 기반의 스케줄러를 제공하는 것은 soft 실시간 기능을 제공하는 것에 불과하다. hard 실시간 시스템에서는 실시간 태스크가 마감시간 내에 확실히 수행되는 것을 보장해야만 하기 때문에 부가적인 스케줄링 기법이 필요하다.</p>
<p>프로세스들은 주기적(<strong>periodic</strong>)이므로, 프로세스들은 일정한 간격으로 CPU가 필요하다. 각각의 주기 프로세스들은 CPU 사용권을 얻었을 때마다 고정된 수행 시간 $t$, CPU로부터 반드시 서비스를 받아야 하는 마감시간(deadline) $d$와 주기 $p$가 정해져 있어서, 스케줄러는 마감시간과 주기적 프로세스의 실행 빈도에 따라서 우선순위를 정한다. 이런 형식의 스케줄링에서 일반적이지 않은 것은 프로세스가 자신의 마감시간을 스케줄러에게 알려야만 할 수도 있다는 것이다. 따라서 승인 제어(<strong>admission-control</strong>) 알고리즘을 이용해서 스케줄러는 마감시간 이내에 완수할 수 있는 프로세스는 실행을 허락하고 그렇지 못한 경우에는 요구를 거절한다.</p>
<h2 id="rate-motonic-scheduling">Rate-Motonic Scheduling</h2>
<p><strong>Rate-monotonic</strong> 스케줄링 알고리즘은 선점 가능한 정적 우선순위 정책을 이용하여 주기 태스크들을 스케줄 한다. 각각의 주기 태스크들은 시스템에 진입하게 되면 주기에 따라서 우선순위가 고정된다. ($priority ∝ 1/period$) 이 정책은 CPU를 더 자주 필요로 하는 태스크에 더 높은 우선순위를 주려는 원리에 기반을 둔다.</p>
<p>Rate-monotonic 스케줄링 기법이 스케줄 할 수 없는 프로세스 집합의 경우 정적 우선순위를 이용하는 다른 알고리즘들 역시 스케줄 할 수 없는 측면에서 최적(optimal)이기는 하지만, 많은 제약이 있다. CPU 이용률은 한계가 있기 때문에 CPU 자원을 최대화해서 사용하는 것을 불가능하다. $N$개의 프로세스를 스케줄 하는 데 있어 최악의 경우 CPU 이용률은 $N(2^{1/N} - 1)$이다. </p>
<h2 id="earliest-deadline-first-scheduling">Earliest-Deadline-First Scheduling</h2>
<p><strong>Earliest-deadline-first</strong> (<strong>EDF</strong>) 스케줄링 기법은 마감시간(deadline)에 따라서 우선순위를 동적으로 부여한다. 마감시간이 빠를수록 우선순위는 높아지고, 늦을수록 낮아진다. EDF 정책에서는, 프로세스가 실행 가능하게 되면 자신의 마감시간을 시스템에 알려야 한다. 우선순위는 새로 실행 가능하게 된 프로세스의 마감시간에 맞춰서 다시 조정된다.</p>
<p>Rate-monotonic 알고리즘과는 달리 EDF 스케줄링 알고리즘은 프로세스들이 주기적일 필요도 없고, CPU 할당 시간도 상수 값으로 정해질 필요가 없다. 그러나 프로세스가 실행 가능해질 때 자신의 마감시간을 스케줄러에게 알려줘야 한다. EDF는 이론적으로, 모든 프로세스가 마감시간을 만족시키도록 스케줄 할 수 있고, CPU 이용률 역시 100%가 될 수 있으므로 최적이다. 그러나 실제로는 프로세스 사이, 또는 인터럽트 핸들링 때의 문맥 교환 비용 때문에 100%의 CPU 이용률은 불가능하다.</p>
<h2 id="proportionate-share-scheduling">Proportionate Share Scheduling</h2>
<p>일정 비율의 몫(<strong>proportional share</strong>) 스케줄러는 모든 애플리케이션들에게 $T$ 개의 시간 몫을 할당하여 동작한다. 한 개의 애플리케이션이 $N$ 개의 시간 몫을 할당받으면 그 응용은 모든 프로세스 시간 중 $N/T$ 시간을 할당받게 된다.</p>
<p>일정 비율의 몫 스케줄러는 애플리케이션이 시간 몫을 할당받는 것을 보장하는 승인 제어(admission-control) 정책과 함께 동작해야만 한다. 승인 제어 정책은 사용 가능한 충분한 몫이 존재할 때, 그 범위 내의 몫을 요구하는 클라이언트들에게만 실행을 허락한다.</p>
<h1 id="operating-system-examples">Operating-System Examples</h1>
<p>Linux, Windows 및 Solaris 운영체제의 스케줄링 정책을 알아보자. 사실 Solaris와 Windows 시스템의 경우에는 <strong>kernel threads</strong> 스케줄링이고, Linux 스케줄러의 경우에는 <strong>tasks</strong> 스케줄링이지만, 일반적인 의미로 <strong>process</strong> 스케줄링이라는 용어를 사용한다.</p>
<h2 id="linux-scheduling">Linux Scheduling</h2>
<p>표준 Linux 커널은 두 스케줄링 클래스를 구현한다.</p>
<ol>
<li><p>완전 공평 스케줄러(<strong>Completely Fair Scheduler</strong>, <strong>CFS</strong>)를 사용하는 디폴트 스케줄링 클래스</p>
<p> CFS 스케줄러는 각 태스크에 CPU 처리시간의 비율을 할당한다. 이 비율은 각 태스크에 할당된 <strong>nice 값</strong>에 기반을 두고 계산된다. Nice 값(-20 ≤ nice ≤ 19)이 작을 수록 우선순위는 높아지며, 우선순위가 높을수록 더 큰 비율의 CPU 처리시간을 할당받는다.</p>
<p> CFS는 이산 값인 시간 할당량(time slice) 대신에 목적 지연시간(<strong>targeted latency</strong>)을 찾는다. 목적 지연시간은 다른 모든 수행 가능한 태스크가 적어도 한 번씩은 실행할 수 있는 시간 간격을 나타낸다.</p>
<p> CFS는 직접 우선순위를 할당하지 않고, 각 태스크별로 <code>vruntime</code>이라는 변수에 태스크가 실행된 시간을 기록하여 가상 실행 시간(<strong>virtual run time</strong>)을 유지한다.</p>
<p> CFS 스케줄러는 처리 코어 간의 부하를 균등하게 유지하면서도 NUMA를 인식하고 스레드 이동을 최소화하는 정교한 기술을 사용하여 로드 밸런싱을 지원한다. 스케줄링 도메인(<strong>scheduling domain</strong>)의 계층적 시스템을 결정하여 다른 <strong>NUMA 노드</strong>(프로세서 수준 도메인) 간 스레드 이동을 최소화하며 부하를 균등화한다.</p>
</li>
<li><p>실시간 스케줄링 클래스(<strong>scheduling classes</strong>)</p>
<p> Linux 스케줄러는 각 클래스별로 특정 우선순위를 부여받는 스케줄링 클래스에 기반을 두고 동작한다. 다음에 실행될 태스크를 결정하기 위해 스케줄러는 높은 우선순위 클래스에 속한 가장 높은 우선순위의 태스크를 선택한다.</p>
</li>
</ol>
<h2 id="windows-scheduling">Windows Scheduling</h2>
<p>Windows는 우선순위에 기반을 둔 선점 스케줄링 알고리즘을 사용한다. Windows 스케줄러는 가장 높은 우선순위의 스레드가 항상 실행되도록 보장한다. Windows 커널 중 스케줄링을 담당하는 부분을 디스패처(<strong>dispatcher</strong>)라고 한다.</p>
<p>디스패처는 스레드의 실행 순서를 정하기 위하여 32단계의 우선순위를 두고 있으며, 두 클래스로 구분된다.</p>
<ul>
<li><p>가변 클래스(<strong>variable class</strong>) — 우선순위 1부터 15까지의 스레드를 포함한다.</p>
<p>이 클래스에 속한 스레드의 우선순위는 변할 수 있다. 스레드의 시간 할당량이 만료되면, 그 스레드는 인터럽트되면서 우선순위가 낮아진다. 우선순위를 낮추게 되면 계산 중심(compute-bound) 스레드들이 CPU를 독점하는 것을 제한하게 된다. 가변 우선순위 스레드가 대기(wait) 연산에서 풀려나면 디스패처는 그 우선순위를 높여준다.</p>
</li>
<li><p>실시간 클래스(<strong>real-time class</strong>) — 우선순위 16부터 31까지의 스레드를 포함한다.</p>
</li>
</ul>
<p>우선순위 0인 스레드는 메모리 관리를 위해 사용한다. 준비(ready) 상태에 있는 스레드가 없으면 디스패처는 <strong>idle 스레드</strong>라고 불리는 특수한 스레드를 실행시킨다.</p>
<p>Windows는 멀티 프로세서 시스템 스케줄링을 지원하여, 해당 스레드에 가장 적합한 처리 코어에 스레드가 스케줄 되도록 시도한다. 가장 적합한 처리 코어를 선정하기 위해 스레드의 선호도(preferred)와 최근에 실행된 프로세서를 유지하고 있어야 한다. Windows에서 사용하는 기법의 하나는 논리 프로세서 집합(<strong>SMT 집합</strong>)을 생성하는 것이다. 여러 논리 프로세서에 부하를 분배하기 위해 각 스레드에는 스레드가 선호하는 프로세서를 나타내는 숫자인 이상적인 프로세서(<strong>ideal processor</strong>)가 배정된다.</p>
<h2 id="solaris-scheduling">Solaris Scheduling</h2>
<p>Solaris는 우선순위 기반 스레드 스케줄링을 사용한다. Solaris는 우선순위에 따라 6개의 스케줄링 클래스를 정의하며, 각 클래스에는 서로 다른 우선순위와 서로 다른 스케줄링 알고리즘이 존재한다.</p>
<ol>
<li>시분할(time-sharing, TS)</li>
<li>대화형(interactive, IA)</li>
<li>실시간(real time, RT)</li>
<li>시스템(system, SYS)</li>
<li>공평 공유(fair share, FSS)</li>
<li>고정 우선순위(fixed priority, FP)</li>
</ol>
<p>스케줄러는 가장 높은 우선순위를 가진 스레드를 실행하도록 선택하고, 선택된 스레드는 (1) block 되거나, (2) 시간 할당량(time slice)를 전부 사용하거나, (3) 높은 우선순위의 스레드에 의해 선점될 때까지 CPU 상에서 실행된다.</p>
<h1 id="algorithm-evaluation">Algorithm Evaluation</h1>
<p>특정 시스템을 위한 CPU 스케줄링 알고리즘은 선택하기 위해서는, 선택 기준을 정의하고 고려 중인 여러 가지 알고리즘들을 평가해야 한다.</p>
<p>알고리즘을 선택하는 데 사용할 기준은 종종 CPU 이용률, 응답 시간 또는 처리량에 의해 정의된다. <em>(Cf. <a href="https://velog.io/@hee-suh/CPU-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%A7%81#scheduling-criteria">Scheduling Criteria</a>)</em> 알고리즘을 선택하기 위해, 먼저 이들 값의 상대적인 중요성을 반드시 정의해야 한다.</p>
<p>평가 방법으로는 결정론적 모델링(<strong>deterministic modeling</strong>), 큐잉 네트워크 분석(<strong>queueing-network analysis</strong>), 시뮬레이션 등이 있다.</p>
<h1 id="additional-resources">Additional Resources</h1>
<h2 id="react-scheduler">React Scheduler</h2>
<p>React에서 동시성을 위해 사용하는 Scheduler도 CPU Scheduler와 유사한 개념으로 구현되어 있다. 자세한 내용은 <a href="https://velog.io/@hee-suh/Scheduler">React Internals Deep Dive - Scheduler</a>를 참고하자.</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition"><strong>Operating System Concepts 10th</strong></a><ul>
<li><strong>PART TWO PROCESS MANAGEMENT</strong><ul>
<li>Chapter 5 CPU Scheduling</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98#curriculum"><strong>운영체제 공룡책 전공강의 | 주니온 - 인프런</strong></a><ul>
<li><strong>섹션 4. Chapter 5. CPU Scheduling</strong><ul>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/unit/63033">09. CPU 스케줄링: Chapter 5. CPU Scheduling (Part 1)</a></li>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/unit/63034">10. 스케줄링 알고리즘: Chapter 5. CPU Scheduling (Part 2)</a></li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스레드와 동시성]]></title>
            <link>https://velog.io/@hee-suh/%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%EB%8F%99%EC%8B%9C%EC%84%B1</link>
            <guid>https://velog.io/@hee-suh/%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%EB%8F%99%EC%8B%9C%EC%84%B1</guid>
            <pubDate>Sat, 08 Feb 2025 14:57:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="🦖-operating-system-concepts-10th">🦖 <a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition">Operating System Concepts 10th</a></h4>
<p><strong>PART TWO PROCESS MANAGEMENT</strong>
Chapter 4 Threads &amp; Concurrency</p>
</blockquote>
<h1 id="threads">Threads</h1>
<blockquote>
<h4 id="💡-thread">💡 Thread</h4>
</blockquote>
<p>스레드는 CPU 사용(utilization)의 기본 단위다.</p>
<blockquote>
</blockquote>
<ul>
<li>스레드는 스레드 ID(<strong>thread ID</strong>), 프로그램 카운터(<strong>program counter</strong>, PC), 레지스터 집합(<strong>register set</strong>), 스택(<strong>stack</strong>)으로 구성된다.</li>
<li>스레드는 같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션, 열린 파일이나 신호와 같은 운영체제 자원들을 공유한다.</li>
<li>전통적인 프로세스는 하나의 제어 스레드(단일 스레드)를 가지고 있으며, 프로세스가 다수의 제어 스레드(다중 스레드)를 가진다면 프로세스는 동시에 하나 이상의 작업을 수행할 수 있다.<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/b8d7fd17-ab46-4f4d-b776-47307c001ead/image.png" alt="Figure 4.1** Single-threaded and multithreaded processes."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.1</strong> Single-threaded and multithreaded processes.</td>
</tr>
</tbody></table>
</li>
</ul>
<h2 id="motivation">Motivation</h2>
<p>애플리케이션은 일반적으로 여러 개의 스레드를 가진 독립적인 프로세스로 구현된다. 다수의 CPU-intensive 작업을 병렬로 처리하며, 멀티 코어 시스템에서 처리 능력을 향상시키도록 설계될 수 있다.</p>
<p>또한 하나의 애플리케이션이 여러 개의 비슷한 작업을 수행할 필요가 있는 상황이 있다. 예를 들어, 웹 서버가 여러 개의 클라이언트로부터 요청을 받는 경우를 살펴보자.</p>
<ul>
<li>만약 웹 서버가 전통적인 단일 스레드 프로세스로 작동한다면, 자신의 단일 프로세스로 한 번에 하나의 클라이언트만 서비스할 수 있게 되어 매우 긴 시간이 소요된다.</li>
<li>하나의 해결책은 서버가 요청을 받아들이는 하나의 프로세스로 동작하게 해서, 서버에게 서비스 요청이 들어오면, 프로세스는 그 요청을 수행할 별도의 프로세스를 생성하는 것이다. 하지만 프로세스 생성 작업은 많은 시간과 자원을 필요로 하는 일이라 오버헤드가 크다.</li>
<li>대부분은 그렇게 하는 것보다는 프로세스 안에 여러 스레드를 만들어 나가는 것이 더 효율적이다. 웹 서버가 다중 스레드화 되면, 서버는 클라이언트의 요청을 listen 하는 별도의 스레드를 생성한다. 요청이 들어오면 프로세스 대신 스레드를 생성해서 추가적인 요청을 listen 하기 위한 작업을 재개한다.</li>
</ul>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/463158dc-c565-4677-a943-1bd5a677092a/image.png" alt="**Figure 4.2** Multithreaded server architecture."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.2</strong> Multithreaded server architecture.</td>
</tr>
</tbody></table>
<h2 id="benefits">Benefits</h2>
<p>멀티 스레드 프로그래밍의 이점은 다음과 같은 4가지 큰 카테고리로 나눌 수 있다.</p>
<ol>
<li><strong>Responsiveness.</strong> 애플리케이션의 일부분이 block 되거나, 긴 작업을 수행하더라도 프로그램의 수행이 계속되는 것을 허용함으로써, 사용자에 대한 응답성을 증가시킨다.</li>
<li><strong>Resource Sharing.</strong> 프로세스는 공유 메모리(shared memory)와 메시지 전달(message passing) 기법을 통해서만 자원을 공유할 수 있는 반면, 스레드는 그들이 속한 프로세스의 자원들과 메모리를 공유한다.</li>
<li><strong>Economy.</strong> 프로세스 생성을 위해 메모리와 자원을 할당하는 것은 비용이 많이 든다. 스레드는 자신이 속한 프로세스의 자원들을 공유하기 때문에, 스레드를 생성하고 문맥 교환하는 것이 더욱 경제적이다. 일반적으로 스레드 생성이 프로세스 생성보다 시간과 메모리를 적게 소비하고, 문맥 교환도 프로세스 사이보다 스레드 사이에서 더 빠르다.</li>
<li><strong>Scalability.</strong> 멀티 스레드의 이점은 멀티 프로세서 구조에서 더욱 증가할 수 있다. 멀티 프로세서 구조에서는 스레드가 각각의 다른 프로세서(처리기)에서 병렬로 수행될 수 있는 반면, 단일 스레드 프로세스는 프로세서가 아무리 많더라도 오직 한 프로세서에서만 실행된다.</li>
</ol>
<h2 id="process-vs-thread">Process vs Thread</h2>
<table>
<thead>
<tr>
<th></th>
<th>Process</th>
<th>Thread</th>
</tr>
</thead>
<tbody><tr>
<td>Definition</td>
<td>프로세스는 실행중인 프로그램이다.</td>
<td>스레드는 프로세스의 세그먼트(CPU 사용의 기본 단위)다.</td>
</tr>
<tr>
<td>Lightweight</td>
<td>X</td>
<td>O</td>
</tr>
<tr>
<td>Creation/Termination time</td>
<td>⬆️</td>
<td>⬇️</td>
</tr>
<tr>
<td>Context Switching</td>
<td>문맥 교환 시간이 더 많이 소요된다.</td>
<td>문맥 교환 시간이 더 적게 소요된다.</td>
</tr>
<tr>
<td>Resource</td>
<td>자원을 더 많이 소모한다.</td>
<td>자원을 더 적게 소모한다.</td>
</tr>
<tr>
<td>Sharing</td>
<td>프로세스끼리 자원을 공유하지 않으며, 일반적으로 독립적이다.</td>
<td>스레드끼리 데이터와 코드 등의 자원을 공유한다.</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/aac520db-ece9-410e-843d-fee6028657d6/image.png" alt="**Fig. [28.2](https://users.cs.cf.ac.uk/dave/C/node29.html#fig:sing_thr) Single and Multi- Thread Applications**"></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Fig. <a href="https://users.cs.cf.ac.uk/dave/C/node29.html#fig:sing_thr">28.2</a> Single and Multi- Thread Applications</strong></td>
</tr>
</tbody></table>
<h1 id="multicore-programming">Multicore Programming</h1>
<p><strong>멀티코어</strong>란, 단일 컴퓨팅 칩에 여러 컴퓨팅 코어를 배치하는 시스템이며, 각 코어는 운영체제에 별도의 CPU로 보인다. 멀티 스레드 프로그래밍은 이러한 여러 컴퓨팅 코어를 보다 효율적으로 사용하고 동시성(concurrency)을 향상시키는 기법을 제공한다.</p>
<ul>
<li><p>동시성(<strong>Concurrency</strong>) — 단일 컴퓨팅 코어가 있는 시스템에서는 처리 코어가 한 번에 하나의 스레드만 실행할 수 있기 때문에 시간이 지남에 따라 스레드의 실행이 인터리브(interleave)된다. concurrent 시스템은 모든 작업이 진행되게 하여 둘 이상의 작업을 지원하며, CPU 스케줄러가 프로세스 간에 빠르게 전환해 각 프로세스가 진행되도록 하여 구현된다.</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/3c04a44a-a1fa-4f68-898c-9ea2005aadd0/image.png" alt="**Figure 4.3** Concurrent execution on a single-core system."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.3</strong> Concurrent execution on a single-core system.</td>
</tr>
</tbody></table>
</li>
<li><p>병렬성(<strong>Parallelism</strong>) — 여러 코어가 있는 시스템에서는 일부 스레드가 병렬로 실행될 수 있다. 병렬 시스템은 둘 이상의 작업을 동시에 수행할 수 있다.</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/e8f68899-de41-4631-b4f6-5fa470a7c31b/image.png" alt="**Figure 4.4** Parallel execution on a multicore system."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.4</strong> Parallel execution on a multicore system.</td>
</tr>
</tbody></table>
</li>
</ul>
<h2 id="programming-challenges">Programming Challenges</h2>
<p>멀티 코어 시스템으로 발전하는 트렌드로 인해, 운영체제 설계자는 여러 코어를 활용하여 병렬 수행할 수 있도록 스케줄링 알고리즘을 개발해야 하고, 애플리케이션 프로그래머는 기존 프로그램을 멀티 스레드를 사용하도록 수정하고 멀티 스레드 프로그램을 설계해야 하는 도전 과제에 당면해 있다.</p>
<p>일반적으로 멀티 코어 시스템을 위해 프로그래밍에는 다섯 개의 극복해야 할 도전 과제가 있다.</p>
<ol>
<li>테스크 인식(<strong>Identifying tasks</strong>) — 애플리케이션을 분석하여 독립된 동시성(concurrent) 태스크로 나눌 수 있어야 한다. 이상적으로 태스크는 서로 독립적이고 개별 코어에서 병렬 실행될 수 있어야 한다.</li>
<li>균형(<strong>Balance</strong>) — 병렬로 실행될 수 있는 태스크를 찾은 후, 균등한 기여도로 작업할 수 있도록 나눠야 한다.</li>
<li>데이터 분리(<strong>Data splitting</strong>) — 애플리케이션이 독립된 태스크로 나누어지는 것처럼, 태스크가 접근하고 조작하는 데이터 또한 개별 코어에서 사용할 수 있도록 나누어져야 한다.</li>
<li>데이터 종속성(<strong>Data dependency</strong>) — 태스크가 접근하는 데이터는 둘 이상의 태스크 사이에 종속성이 없는지 검토되어야 한다. 종속적인 경우에는 태스크의 수행을 잘 동기화해야 한다.</li>
<li>테스팅 및 디버깅(<strong>Testing and debugging</strong>) — 프로그램이 멀티코어에서 병렬로 실행될 때, 다양한 실행 경로가 존재할 수 있으므로, 단일 스레드 애플리케이션을 테스팅하고 디버깅하는 것보다 훨씬 어렵다.</li>
</ol>
<h2 id="types-of-parallelism">Types of Parallelism</h2>
<p>일반적으로 데이터 병렬 실행과 태스크 병렬 실행의 두 가지 유형이 존재한다.</p>
<h3 id="data-parallelism">Data Parallelism</h3>
<p><strong>데이터 병렬 실행</strong>은 동일한 데이터의 부분집합을 다수의 컴퓨팅 코어에 분배한 뒤 각 코어에서 동일한 연산을 실행하는 데 초점을 맞춘다.</p>
<h3 id="task-parallelism">Task Parallelism</h3>
<p><strong>태스크 병렬 실행</strong>은 데이터가 아니라 태스크(스레드)를 다수의 코어에 분배한다. 각 스레드는 고유의 연산을 실행한다. 다른 스레드들이 동일한 데이터에 대해 연산을 실행할 수 있고, 서로 다른 데이터에 연산을 실행할 수도 있다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/3212fca4-9f70-4529-bd80-3cc043cef924/image.png" alt="**Figure 4.5** Data and task parallelism."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.5</strong> Data and task parallelism.</td>
</tr>
</tbody></table>
<p>기본적으로 데이터 병렬 처리에는 여러 코어에 데이터를 분배하는 것이 포함되고, 태스크 병렬 처리에는 여러 코어에 태스크를 분배하는 것이 포함된다. 그러나 데이터와 태스크 병렬 처리는 상호 배타적이지 않으며 실제로 애플리케이션은 이 두 가지 전략을 혼합하여 사용할 수 있다.</p>
<blockquote>
<h4 id="💡-amdahls-law">💡 Amdahl’s Law</h4>
</blockquote>
<p>암달의 법칙은 순차(serial) 작업과 병렬 작업으로 이루어진 애플리케이션에 컴퓨팅 코어를 추가했을 때 얻을 수 있는 잠재적인 성능 이득을 나타내는 공식이다. $N$ 개의 처리 코어를 가진 시스템에서 실행되는 응용 중 반드시 순차적으로 실행되어야만 하는 구성요소를 $S$ 라고 하면 이 공식은 다음과 같이 표현된다.</p>
<blockquote>
</blockquote>
<p>$speedup$ ≤ $\frac {1} {S+\frac {(1-s)} {N}}$</p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/b2627802-bd2b-4b0e-b8b1-d4874ffb9056/image.png" alt="Amdahl’s Law"></p>
<blockquote>
</blockquote>
<p>$N$ 이 무한대로 가까워지면 속도는 $1/S$ 에 수렴한다. 순차 작업이 차지하는 비율이 코어를 추가해서 얻을 수 있는 성능 향상에 불균형적인 영향을 미친다.</p>
<h1 id="multithreading-models">Multithreading Models</h1>
<p>사용자 스레드(<strong>user threads</strong>)를 위한 지원은 사용자 수준에서, 커널 스레드(<strong>kernel threads</strong>)를 위한 지원은 커널 수준에서 제공된다.</p>
<ul>
<li>사용자 스레드 — 커널 위에서(<strong>without kernel support</strong>) 관리된다.</li>
<li>커널 스레드 — 운영체제에 의해(<strong>by the operating system</strong>) 직접 지원되고 관리된다.</li>
</ul>
<p>궁극적으로 사용자 스레드와 커널 스레드는 어떤 연관 관계가 존재해야 한다. 이 연관 관계를 확립하는 세 가지 일반적인 방법으로 다대일, 일대일, 다대다 모델이 있다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/0d57d94b-e0d1-44e0-95a0-a3d51fc3b720/image.png" alt="**Figure 4.6** User and kernel threads."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.6</strong> User and kernel threads.</td>
</tr>
</tbody></table>
<h2 id="many-to-one-model">Many-to-One Model</h2>
<p>다대일 모델은 많은 사용자 수준 스레드를 하나의 커널 스레드로 매핑한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/7d06907d-0a7a-4cf3-8a4b-aa2af4a1b910/image.png" alt="**Figure 4.7** Many-to-one model."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.7</strong> Many-to-one model.</td>
</tr>
</tbody></table>
<ul>
<li>스레드 관리는 사용자 공간의 스레드 라이브러리에 의해 행해진다.</li>
<li>한 스레드가 blocking 시스템 콜을 할 경우, 전체 프로세스가 block 된다.</li>
<li>한 번에 하나의 스레드만이 커널에 접근할 수 있기 때문에, 멀티 스레드가 멀티코어 시스템에서 병렬로 실행될 수 없다.</li>
</ul>
<p>다중 처리 코어가 대부분의 컴퓨터 시스템에서 표준이 되었고, 다중 처리 코어의 이점을 살릴 수 없기 때문에 이 모델을 사용 중인 시스템은 거의 존재하지 않는다.</p>
<h2 id="one-to-one-model">One-to-One Model</h2>
<p>일대일 모델은 각 사용자 스레드를 각각 하나의 커널 스레드로 매핑한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/db9cf67c-c940-4a20-96be-2f8d687c3766/image.png" alt="**Figure 4.8** One-to-one model."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.8</strong> One-to-one model.</td>
</tr>
</tbody></table>
<ul>
<li>하나의 스레드가 blocking 시스템 콜을 호출하더라도 다른 스레드가 실행될 수 있기 때문에 다대일 모델보다 더 많은 병렬성을 제공한다.</li>
<li>멀티 프로세서에서 멀티 스레드가 병렬로 수행되는 것을 허용한다.</li>
<li>이 모델의 유일한 단점은 사용자 스레드를 만들려면 해당 커널 스레드를 만들어야 하며 많은 수의 커널 스레드가 시스템 성능에 부담을 줄 수 있다는 것이다.</li>
</ul>
<p>Linux와 Windows 운영체제 제품군은 일대일 모델을 구현한다.</p>
<h2 id="many-to-many-model">Many-to-Many Model</h2>
<p>다대다 모델은 여러 개의 사용자 수준 스레드를 그보다 작은 수, 혹은 같은 수의 커널 스레드로 멀티플렉스 한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/73987937-20ab-4210-845a-b0c4001ccc9a/image.png" alt="**Figure 4.9** Many-to-many model."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.9</strong> Many-to-many model.</td>
</tr>
</tbody></table>
<ul>
<li><p>스레드가 blocking 시스템 콜을 발생시켰을 때, 커널이 다른 스레드의 수행을 스케줄 할 수 있다.</p>
</li>
<li><p>개발자는 필요한 만큼 많은 사용자 수준 스레드를 생성할 수 있고, 상응하는 커널 스레드가 멀티 프로세서에서 병렬로 수행될 수 있다.</p>
</li>
<li><p>다대일 모델은 개발자가 원하는 만큼의 사용자 수준 스레드를 생성하도록 허용하지만, 커널은 한 번에 하나의 커널 스레드만 스케줄 할 수 있기 때문에 진정한 병렬 실행이 불가능하다. 또한 일대일 모델은 더 많은 concurrent 실행을 제공하지만, 개발자가 한 애플리케이션 내에 너무 많은 스레드를 생성하지 않도록 주의해야 한다. 다대다 모델은 이러한 두 가지의 단점들을 어느 정도 해결했다.*</p>
</li>
<li><p><strong>two-level model</strong>이라고 불리기도 하는 다대다 모델의 변형은 많은 사용자 스레드를 적거나 같은 수의 커널 스레드로 멀티플렉스 시킬뿐 아니라 한 사용자 스레드가 하나의 커널 스레드에만 연관되는 것을 허용한다.</p>
<blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/78b9a70e-16b2-429e-89a5-5fa97e1d42ef/image.png" alt="**Figure 4.10** Two-level model."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 4.10</strong> Two-level model.</td>
</tr>
</tbody></table>
</blockquote>
</li>
</ul>
<p>다대다 모델은 실제로 구현하기가 어렵고, 대부분의 시스템에서 처리 코어 수가 증가함에 따라 커널 스레드 수를 제한하는 것의 중요성이 줄어들었다. 결과적으로 대부분의 운영체제는 이제 일대일 모델을 사용한다.</p>
<h1 id="implicit-threading">Implicit Threading</h1>
<p>멀티 코어 처리의 성장에 따라 수천 개의 스레드를 가진 애플리케이션이 등장하게 되면서, 프로그래머는 <a href="https://velog.io/@hee-suh/%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%EB%8F%99%EC%8B%9C%EC%84%B1#programming-challenges">Programming Challenges</a> 뿐 아니라 추가 난관을 극복해야 한다.</p>
<p>이러한 어려움을 극복하고 동시성 및 병렬 애플리케이션 설계를 도와주는 한 가지 방법은 스레딩의 생성과 관리 책임을 애플리케이션 개발자로부터 컴파일러와 런타임 라이브러리에게 넘겨주는 것(<strong>transfer the difficulty</strong>)이다. 암묵적 스레딩(<strong>implicit threading</strong>)이라고 불리는 이 전략은 점점 널리 사용되고 있다.</p>
<p>암묵적 스레딩에서 애플리케이션 개발자가 병렬로 실행할 수 있는 태스크—not threads—를 식별해야 한다. 태스크는 일반적으로 함수로 작성되며, 런타임 라이브러리는 일반적으로 <a href="https://velog.io/@hee-suh/%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%EB%8F%99%EC%8B%9C%EC%84%B1#many-to-many-model">Many-to-Many Model</a> 을 사용하여 별도의 스레드에 매핑된다. 이 방법의 장점은 개발자는 병렬 작업만 식별하면 되고 라이브러리가 스레드 생성 및 관리에 대한 특정 세부 사항을 결정한다는 것이다.</p>
<h2 id="thread-pools">Thread Pools</h2>
<p>매 요청마다 새로운 스레드를 만들어주는 멀티 스레드 서버는 여러 문제를 갖고 있다.</p>
<ol>
<li>서비스할 때마다 스레드를 생성하는 시간이 소요된다. 심지어 이 일만 끝나면 바로 폐기될 스레드다.</li>
<li>모든 요청마다 새 스레드를 만들어서 서비스하다보면, 시스템에서 동시에 실행할 수 있는 최대 스레드 수의 한계를 정해야 한다. 스레드를 무한정 만들면 언젠가는 CPU 시간, 메모리 공간 같은 시스템 자원이 고갈된다.</li>
</ol>
<p>이러한 문제들을 해결해 줄 수 있는 방법의 하나가 스레드 풀(<strong>thread pool</strong>)이다.</p>
<p>스레드 풀의 기본 아이디어는 프로세스를 시작할 때 아예 일정한 수의 스레드들을 미리 풀로 만들어두고, 이 스레드들은 작업을 기다리는 것이다.</p>
<p>서버는 스레드를 생성하지 않고 요청을 받으면 대신 스레드 풀에 제출하고 추가 요청 대기를 재개한다. 풀에 사용 가능한 스레드가 있으면 깨어나고 요청이 즉시 서비스된다. 풀에 사용 가능한 스레드가 없으면 사용 가능한 스레드가 생길 때까지 작업이 대기된다.</p>
<blockquote>
<h4 id="💡-브라우저의-스레드-풀과-멀티스레딩-지원">💡 브라우저의 스레드 풀과 멀티스레딩 지원</h4>
</blockquote>
<p>자바스크립트는 싱글 스레드로 동작하지만, 브라우저는 스레드 풀과 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">Web Worker</a>를 통해 멀티스레딩을 지원한다. 이는 무거운 연산이나 블로킹 I/O 작업을 메인 스레드가 아닌 백그라운드 스레드에서 처리해 UI 반응성을 유지하기 위함이다. 스레드 풀에서 처리된 작업은 이벤트 루프(Event Loop)를 통해 결과를 메인 스레드로 전달된다.</p>
<blockquote>
</blockquote>
<p><strong>스레드 풀(Thread Pool)</strong>은 <strong>물리 스레드</strong>와 Task Queue로 구성되며, Chrome의 경우 <a href="https://source.chromium.org/chromium/chromium/src/+/main:base/task/thread_pool.h"><code>base::ThreadPoolInstance</code></a>가 이를 관리한다. 개발자는 직접 스레드를 생성하지 않고 Task Runner를 사용해 작업을 전송한다.</p>
<blockquote>
</blockquote>
<p>Cf. <a href="https://chromium.googlesource.com/chromium/src/+/main/docs/threading_and_tasks.md#direct-posting-to-the-thread-pool">Chromium Docs - Threading and Tasks in Chrome</a></p>
<h2 id="fork-join">Fork Join</h2>
<p><strong>fork-join</strong> 메소드를 사용하면 메인 부모 스레드가 하나 이상의 자식 스레드를 생성($fork$)한 다음 자식의 종료를 기다린 후 $join$하고 그 시점부터 자식의 결과를 확인하고 결합할 수 있다. 이 동기식 모델은 종종 명시적 스레드 생성이라고 특징지어지지만 암묵적 스레딩에도 사용될 수 있다. 후자의 상황에서, fork 단계에서는 스레드가 직접 구축되지 않고 대신 병렬 작업이 식별된다. 이 fork-join 모델은 라이브러리가 생성할 실제 스레드 수를 결정하는 동기 버전의 스레드 풀이다.</p>
<h2 id="openmp">OpenMP</h2>
<p>OpenMP는 C, C++ 또는 FORTRAN으로 작성된 API와 컴파일러 지시어(directives)의 집합이다. OpenMP는 공유 메모리 환경에서 병렬 프로그래밍을 할 수 있도록 도움을 준다.</p>
<p>OpenMP는 병렬로 실행될 수 있는 블록을 병렬 영역(<strong>parallel regions</strong>)이라고 부른다. 병렬 영역에 컴파일러 디렉티브를 삽입하면, OpenMP가 런타임 라이브러리에 해당 영역을 병렬로 실행하라고 지시한다.</p>
<pre><code class="language-c">#include &lt;omp.h&gt;
#include &lt;stdio.h&gt;

int main(int argc, char *argv[]) {
    /* sequential code */
    #pragma omp parallel
    {
        printf(&quot;I am a parallel region.&quot;);
    }

    /* sequential code */

    return 0;
}</code></pre>
<h2 id="grand-central-dispatch">Grand Central Dispatch</h2>
<p>Grand Central Dispatch (GCD)는 macOS 및 iOS 운영체제를 위해 Apple에서 개발한 기술이다. 개발자가 병렬로 실행될 코드 섹션(<strong>tasks</strong>)을 식별할 수 있도록 하는 런타임 라이브러리, API, 언어 확장(extensions)의 조합이다. OpenMP와 마찬가지로 GCD는 스레딩에 대한 대부분의 세부 사항을 관리한다.</p>
<p>GCD는 실행시간 수행을 위해 태스크를 디스패치 큐(<strong>dispatch queue</strong>)에 넣어서 스케줄 한다. 큐에서 태스크를 제거할 때 관리하는 스레드 풀에서 가용 스레드를 선택하여 태스크를 할당한다. GCD는 직렬(<strong>serial</strong>)과 병행(<strong>concurrent</strong>)의 두 가지 유형의 디스패치 큐를 유지한다.</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition"><strong>Operating System Concepts 10th</strong></a><ul>
<li><strong>PART TWO PROCESS MANAGEMENT</strong><ul>
<li>Chapter 4 Threads &amp; Concurrency</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98#curriculum"><strong>운영체제 공룡책 전공강의 | 주니온 - 인프런</strong></a><ul>
<li><strong>섹션 3. Chapter 4. Thread &amp; Concurrency</strong><ul>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/unit/63031">07. 쓰레드의 이해: Chapter 4. Thread &amp; Concurrency (Part 1)</a></li>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/unit/63032">08. 멀티쓰레딩: Chapter 4. Thread &amp; Concurrency (Part 2)</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="https://chromium.googlesource.com/chromium/src/+/main/docs/threading_and_tasks.md#direct-posting-to-the-thread-pool">Chromium Docs - Threading and Tasks in Chrome</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로세스]]></title>
            <link>https://velog.io/@hee-suh/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</link>
            <guid>https://velog.io/@hee-suh/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4</guid>
            <pubDate>Sat, 08 Feb 2025 14:07:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="🦖-operating-system-concepts-10th">🦖 <a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition">Operating System Concepts 10th</a></h4>
<p><strong>PART TWO PROCESS MANAGEMENT</strong>
Chapter 3 Processes</p>
</blockquote>
<h1 id="프로세스-개념-process-concept">프로세스 개념 (Process Concept)</h1>
<h2 id="프로세스">프로세스</h2>
<h3 id="background">Background</h3>
<p>초기의 컴퓨터 시스템은 한 번에 하나의 프로그램만을 실행하도록 허용했다. 반면 오늘날의 컴퓨터 시스템들은 메모리에 다수의 프로그램이 적재되어 동시에 실행되는 것을 허용한다. 다양한 프로그램을 보다 견고하게 제어하고 보다 구획화해야 했고, 이러한 필요성에 따라 프로세스의 개념이 등장했다.</p>
<blockquote>
<h4 id="💡-프로세스">💡 프로세스</h4>
</blockquote>
<p><strong>프로세스</strong>란 실행중인 프로그램을 말하며, 운영체제의 기본 작업 단위이다.
프로세스의 현재 활동 상태는 <strong>프로그램 카운터</strong>(PC) 값과 프로세서 레지스터의 내용으로 나타낸다.</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/4e4155e4-6f85-4138-a519-cdcfb89c4d8c/image.png" alt="**Figure 3.1** 프로세스 메모리 배치"></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.1</strong> 프로세스 메모리 배치</td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<ul>
<li><strong>텍스트 섹션</strong> — 실행 코드</li>
<li><strong>데이터 섹션</strong> — 전역 변수</li>
<li><strong>힙 섹션</strong> — 프로그램 실행 중에 동적으로 할당되는 메모리</li>
<li><strong>스택 섹션</strong> — 함수를 호출할 때 임시 데이터 저장소 (e.g. 함수 매개변수, 리턴 주소, 지역 변수)<blockquote>
</blockquote>
Cf. 스택에 <strong>활성화 레코드(activation record)</strong>가 push 되며 메모리가 동적으로 할당됨에 따라 힙이 커지고, 메모리가 시스템에 반환되면 축소된다. 스택 및 힙 섹션이 서로의 방향으로 커지더라도 운영체제는 서로 겹치지 않도록 해야 한다.</li>
</ul>
<h2 id="프로세스-상태-process-state">프로세스 상태 (Process State)</h2>
<p>프로세스는 실행되면서 그 <strong>상태(state)</strong>가 변한다. 프로세스의 상태는 현재의 활동에 따라서 정의되며, 다음 상태 중 하나에 있게 된다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/970a55a5-e918-4aa5-9799-a593cdeaf562/image.png" alt="**Figure 3.2** Diagram of process state."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.2</strong> Diagram of process state.</td>
</tr>
</tbody></table>
<ul>
<li><strong>New</strong> — 프로세스가 생성 중이다.</li>
<li><strong>Running</strong> — 명령어들이 실행되고 있다.</li>
<li><strong>Waiting</strong> — 프로세스가 어떤 이벤트(e.g. 입출력(I/O) 완료 또는 신호 수신)가 일어나기를 기다린다.</li>
<li><strong>Ready</strong> — 프로세스가 처리기(processor)에 할당되기를 기다린다.</li>
<li><strong>Terminated</strong> — 프로세스의 실행이 종료되었다.</li>
</ul>
<p>한 프로세서 코어에서는 오직 하나의 프로세스만이 실행(<em>running</em>)된다. 따라서 많은 프로세스가 준비완료(<em>ready</em>) 및 대기(<em>waiting</em>) 상태에 있을 수 있다.</p>
<h2 id="프로세스-제어-블록-process-control-block">프로세스 제어 블록 (Process Control Block)</h2>
<p><strong>프로세스 제어 블록</strong>(<strong>process control block</strong>, PCB)(a.k.a <strong>task control block</strong>)은 운영체제의 프로세스를 나타내는 커널 데이터 구조이다.</p>
<p>프로세스 제어 블록은 특정 프로세스와 연관된 여러 정보를 수록하며, 다음과 같은 것들을 포함한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/62ca9eb8-496b-4117-9d7d-e17ee4279bbb/image.png" alt="**Figure 3.3** Process control block (PCB)."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.3</strong> Process control block (PCB).</td>
</tr>
</tbody></table>
<ul>
<li><strong>프로세스 상태</strong> — 상태는 new, ready, running, waiting, halted 등이다.</li>
<li><strong>프로그램 카운터</strong> — 프로그램 카운터는 이 프로세스가 다음에 실행할 명령어의 주소를 가리킨다.</li>
<li><strong>CPU 레지스터들</strong> — CPU 레지스터는 컴퓨터 구조에 따라 다양한 개수와 유형을 가진다. 레지스터에는 누산기(accumulator), 인덱스 레지스터, 스택 레지스터, 범용(general-purpose) 레지스터들과 상태 코드(condition-code) 정보가 포함된다. 프로그램 카운터와 함께 이 상태 정보는, 나중에 프로세스가 다시 스케줄될 때 계속 올바르게 실행되도록 하기 위해서 인터럽트 발생 시 저장되어야 한다.</li>
<li><strong>CPU-스케줄링 정보</strong> — 이 정보는 프로세스 우선순위, 스케줄 큐에 대한 포인터와 다른 스케줄 매개변수를 포함한다.</li>
<li><strong>메모리 관리 정보</strong> — 이 정보는 운영체제에 의해 사용되는 메모리 시스템에 따라 기준(base) 레지스터와 한계(limit) 레지스터의 값, 운영체제가 사용하는 메모리 시스템에 따라 페이지 테이블 또는 세그먼트 테이블 등과 같은 정보를 포함한다.</li>
<li><strong>회계(accounting) 정보</strong> — 이 정보는 CPU 사용 시간과 경과된 실시간, 시간 제한, 계정 번호, job 또는 프로세스 번호 등을 포함한다.</li>
<li><strong>입출력(I/O) 상태 정보</strong> — 이 정보는 이 프로세스에 할당된 입출력 장치들과 열린 파일들의 목록 등을 포함한다.</li>
</ul>
<p>프로세스 제어 블록은 프로세스를 시작시키거나 다시 시작하는 데 필요한 모든 데이터와 함께 약간의 회계 데이터를 위한 저장소 역할을 한다.</p>
<h2 id="스레드-threads">스레드 (Threads)</h2>
<p>단일 제어 스레드는 프로세스가 한 번에 단지 한 가지 일만 실행하도록 허용한다. </p>
<p>대부분의 현대 운영체제는 한 프로세스가 다수의 실행 스레드를 가질 수 있도록 허용한다. 즉, 프로세스가 한 번에 하나 이상의 일을 수행할 수 있도록 허용한다. 이 특성은 특히 여러 스레드를 병렬로 실행할 수 있는 멀티코어 시스템에 유용하다. 다수의 스레드를 지원하는 시스템에서는 PCB가 각 스레드에 관한 정보를 포함하도록 확장된다. 스레드를 지원하기 위해서는 시스템 전반에 걸친 다른 수정도 필요한데, 이는 다음 글인 <a href="https://velog.io/@hee-suh/%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%EB%8F%99%EC%8B%9C%EC%84%B1">스레드와 동시성</a>에서 상세하게 살펴보자.</p>
<h1 id="프로세스-스케줄링-process-scheduling">프로세스 스케줄링 (Process Scheduling)</h1>
<p>멀티 프로그래밍의 목적은 CPU 이용을 최대화하기 위하여 항상 프로세스가 실행되도록 하는 데 있다.</p>
<p>시분할(time sharing)의 목적은 각 프로그램이 실행되는 동안 사용자가 상호 작용할 수 있도록 프로세스들 사이에서 CPU 코어를 빈번하게 교체(switch)하는 것이다. </p>
<p>멀티 프로그래밍과 시분할의 목적을 달성하기 위해 <strong>프로세스 스케줄러(process scheduler)</strong>는 코어에서 실행 가능한 여러 프로세스 중에서 하나의 프로세스를 선택한다. 각 CPU 코어는 한 번에 하나의 프로세스를 실행할 수 있다. 멀티 코어 시스템은 한 번에 여러 프로세스를 실행할 수 있다. 코어보다 많은 프로세스가 있는 경우, 초과 프로세스는 코어가 사용 가능해지고 다시 스케줄 될 때까지 기다려야 한다. 현재 메모리에 있는 프로세스 수를 <strong>다중 프로그래밍 정도(degree of multiprogramming)</strong>라고 한다.</p>
<h2 id="프로세스-스케줄러의-세-가지-유형"><strong>프로세스 스케줄러의 세 가지 유형</strong></h2>
<ol>
<li>Long Term Scheduler</li>
<li>Medium Term Scheduler</li>
<li>Short Term Scheduler</li>
</ol>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/126cdf83-c2b7-4b98-b6f7-b5a96f2ba0f6/image.png" alt="프로세스 스케줄러"></th>
</tr>
</thead>
<tbody><tr>
<td>출처: <a href="https://www.geeksforgeeks.org/process-schedulers-in-operating-system/">https://www.geeksforgeeks.org/process-schedulers-in-operating-system/</a></td>
</tr>
</tbody></table>
<h3 id="long-term-scheduler"><strong>Long Term Scheduler</strong></h3>
<p>장기 스케줄러는 <strong>job 스케줄러</strong>라고도 한다. 이 스케줄러는 프로그램을 제어하고 큐에서 프로세스를 선택하여 실행을 위해 메모리에 로드한다. 또한 멀티프로그래밍의 정도(degree)를 제어한다.</p>
<p><em>Cf. 멀티프로그래밍의 정도란 현재 메모리에 있는 프로세스 수다.</em></p>
<h3 id="medium-term-scheduler"><strong>Medium Term Scheduler</strong></h3>
<p>중기 스케줄링은 <strong>swapping</strong>에서 중요한 부분으로, 실행 중인 프로세스가 I/O 요청을 하면 일시 중단될 수 있다. 프로세스를 메모리에서 디스크로 “swap out”하여 다른 프로세스를 위한 공간을 확보한다. </p>
<p>핵심 아이디어는 때로는 메모리에서 (and from active contention for the CPU) 프로세스를 제거하여 멀티 프로그래밍의 정도(degree)를 감소시키는 것이 유리할 수 있다는 것이다. 프로세스를 메모리에서 디스크로 “swap out”하고 현재 상태를 저장하고, 이후 디스크에서 메모리로 “swap in”하여 상태를 복원한다. swapping은 일반적으로 메모리가 초과 사용되어 가용공간을 확보해야 할 때만 필요하다.</p>
<h3 id="short-term-scheduler"><strong>Short Term Scheduler</strong></h3>
<p>단기 스케줄러는 <strong>CPU 스케줄러</strong>라고도 한다. 이 스케줄러의 주요 목표는 설정된 기준(criteria)에 따라 시스템 기능을 향상시키는 것이다. 준비(ready) 큐에 있는 프로세스 중에서 하나의 프로세스를 선택해서 CPU를 할당하며, 디스패처는 단기 스케줄러가 선택한 프로세스에 CPU 제어권을 부여한다.</p>
<p><em>Cf. <a href="https://www.guru99.com/process-scheduling.html">https://www.guru99.com/process-scheduling.html</a></em></p>
<h2 id="스케줄링-큐-scheduling-queue">스케줄링 큐 (Scheduling Queue)</h2>
<p>프로세스가 시스템에 들어가면 <strong>준비 큐(ready queue)</strong>에 들어가서 준비(ready) 상태가 되어 CPU 코어에서 실행되기를 기다린다. 이 큐는 일반적으로 PCB의 연결 리스트로 저장된다. 준비 큐 헤더에는 리스트의 첫 번째 PCB에 대한 포인터가 저장되고 각 PCB에는 준비 큐의 다음 PCB를 가리키는 포인터 필드가 포함된다.</p>
<p>프로세스에 CPU 코어가 할당되면 프로세스는 잠시 동안 실행되어 결국 종료되거나 인터럽트 되거나 I/O 요청의 완료와 같은 특정 이벤트가 발생될 때까지 기다린다. 이러한 프로세스는 <strong>대기 큐(waiting queue)</strong>에 삽입된다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/5b5782ce-0a0e-479a-b37d-2659c5560d62/image.png" alt="**Figure 3.4** The ready queue and wait queues."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.4</strong> The ready queue and wait queues.</td>
</tr>
</tbody></table>
<p>프로세스 스케줄링의 일반적인 표현은 <strong>큐잉 다이어그램(queueing diagram)</strong>이다. 준비(ready) 큐와 대기(waiting) 큐 집합의 두 가지 유형의 큐가 제시되어 있다. 원은 큐에 서비스를 제공하는 자원을 나타내고 화살표는 시스템 프로세스의 흐름을 나타낸다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/03485179-2b31-48d2-9790-60bb51969739/image.png" alt="**Figure 3.5** Queueing-diagram representation of process scheduling."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.5</strong> Queueing-diagram representation of process scheduling.</td>
</tr>
</tbody></table>
<p>새 프로세스는 처음에 준비(ready) 큐에 놓인다. 프로세스는 실행을 위해 선택되거나 <strong>dispatch</strong>될 때까지 기다린다. 프로세스에 CPU 코어가 할당되고 실행 상태가 되면, 여러 이벤트 중 하나가 발생할 수 있다.</p>
<ul>
<li>프로세스가 I/O 요청을 발행(issue)한 다음, I/O 대기(wait) 큐에 놓일 수 있다.</li>
<li>프로세스는 새 자식 프로세스를 만든 다음 자식의 종료를 기다리는 동안 대기(wait) 큐에 놓일 수 있다.</li>
<li>인터럽트 또는 time slice 만료로 프로세스가 코어에서 강제로 제거되어 준비(ready) 큐로 돌아갈 수 있다.</li>
</ul>
<p>처음 두 경우에는 프로세스가 결국 대기(waiting) 상태에서 준비(ready) 상태로 전환된 다음 준비(ready) 큐로 다시 들어간다. 프로세스는 종료될 때까지 이 사이클을 계속한다. 종료(terminate) 시점에 모든 큐에서 제거되고 PCB 및 자원이 반환된다.</p>
<blockquote>
<h4 id="💡-문맥-교환-context-swtich">💡 문맥 교환 (context swtich)</h4>
</blockquote>
<p><strong>문맥 교환(context swtich)</strong>은 CPU 코어를 다른 프로세스로 교환(switch)하는 작업이다.</p>
<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/60a79cb3-55b0-4ff7-9f63-bfc858e10e2a/image.png" alt="**Figure 3.6** Diagram showing context switch from process to process."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.6</strong> Diagram showing context switch from process to process.</td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<ul>
<li>Context Switch는 interrupt 또는 system call에 의해 실행된다.</li>
<li>Context Switch가 일어나면, 커널은 과거 프로세스의 문맥을 PCB에 저장하고, 실행 예정인 새로운 프로세스의 저장된 문맥을 복구한다.</li>
<li>Context Switch가 일어날 동안 시스템이 유용한 일을 못하기 때문에 문맥 교환 시간은 순수 오버헤드다.</li>
</ul>
<h1 id="프로세스에-대한-연산-operations-on-processes">프로세스에 대한 연산 (Operations on Processes)</h1>
<p>대부분의 시스템 내의 프로세스들은 동시에 실행될 수 있으며, 동적으로 생성 및 삭제될 수 있다. 그러므로 운영체제는 <strong>프로세스</strong> <strong>생성</strong> 및 <strong>종료</strong>를 위한 기법을 반드시 제공해야 한다.</p>
<h2 id="프로세스-생성-process-creation">프로세스 생성 (Process Creation)</h2>
<p>프로세스는 실행되는 동안 여러 개의 새로운 프로세스들을 생성할 수 있다.</p>
<ul>
<li>부모 프로세스 — 생성하는 프로세스</li>
<li>자식 프로세스 — 새로운 프로세스</li>
</ul>
<p>생성된 새로운 프로세스들도 각각 새로운 프로세스들을 생성할 수 있으며, 그 결과 프로세스의 <strong>트리</strong>를 형성한다.</p>
<p>프로세스는 고유한 프로세스 식별자(<strong>process identifier</strong> or <strong>pid</strong>)를 사용하여 구분되고, 이 식별자는 보통 정수다. pid은 커널이 유지하고 있는 프로세스의 다양한 속성에 접근하기 위한 index로 사용된다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/8e90900a-d76b-4d13-9076-92252bea95c2/image.png" alt="**Figure 3.7** A tree of processes on a typical Linux system."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.7</strong> A tree of processes on a typical Linux system.</td>
</tr>
</tbody></table>
<p>일반적으로 프로세스가 자식 프로세스를 생성할 때, 그 자식 프로세스는 태스크 완수를 위해 특정 자원(CPU 시간, 메모리, 파일, 입출력 장치)이 필요하다. 자식 프로세스는 이 자원을 운영체제로부터 직접 얻거나, 부모 프로세스가 가진 자원의 부분 집합만을 사용하도록 제한될 수 있다.</p>
<p>프로세스가 새로운 프로세스를 생성할 때, 두 가지 실행 가능성이 존재한다.</p>
<ol>
<li>부모는 자식과 동시에 실행(<strong><em>execute concurrently</em></strong>)을 계속한다.</li>
<li>부모는 일부 또는 모든 자식이 실행을 종료할 때까지 기다린다(<strong><em>wait</em></strong>).</li>
</ol>
<p>새로운 프로세스들의 주소 공간(address-space) 측면에서도 두 가지 가능성이 았다.</p>
<ol>
<li>자식 프로세스는 부모 프로세스의 복사본(<strong><em>duplicate</em></strong>)이다(자식 프로세스는 부모와 똑같은 프로그램과 데이터를 가진다).</li>
<li>자식 프로세스가 자신에게 적재(load)될 새로운 프로그램(<strong><em>new program</em></strong>)을 가지고 있다.</li>
</ol>
<blockquote>
<h4 id="💻-unix의-fork-시스템-콜을-사용한-프로세스-생성">💻 UNIX의 <code>fork()</code> 시스템 콜을 사용한 프로세스 생성</h4>
</blockquote>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/84884132-46b4-4a22-b5b5-e3adf062a46e/image.png" alt="**Figure 3.9** Process creation using the fork() system call."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.9</strong> Process creation using the fork() system call.</td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<pre><code class="language-c">#include &lt;sys/types.h&gt;
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
&gt;
int main() {
    pid_t pid;
&gt;    
    /* fork a child process */
    pid = fork();
&gt;    
    if (pid &lt; 0) { /* error occurred */ 
        fprintf(stderr, &quot;Fork Failed&quot;);
        return 1;
    }
    else if (pid == 0) { /* child process */
        execlp(&quot;/bin/ls&quot;,&quot;ls&quot;,NULL); 
    }
    else { /* parent process */
        /* parent will wait for the child to complete */
        wait(NULL);
        printf(&quot;Child Complete&quot;);
    }
&gt;    
    return 0;
}</code></pre>
<p>Cf. <code>fork()</code> 시스템 콜의 return 값</p>
<ul>
<li>original process에서는 child process의 PID</li>
<li>child process에서는 0</li>
</ul>
<h2 id="프로세스-종료-process-termination">프로세스 종료 (Process Termination)</h2>
<p>프로세스가 마지막 문장의 실행을 끝내고, <code>exit()</code> 시스템 콜을 사용하여 운영체제에 자신의 삭제를 요청하면 종료된다. 이 시점에서, 프로세스는 자신을 기다리고 있는 부모 프로세스에(<code>wait()</code> 시스템 콜을 통해) 상태 값(통상 정수)을 반환할 수 있다.</p>
<p>물리 메모리와 가상 메모리, 열린 파일, 입출력 버퍼를 포함한 프로세스의 모든 자원이 할당 해제되고 운영체제로 반납된다. 그러나 프로세스의 종료 상태가 저장되는 프로세스 테이블의 해당 항목은 부모 프로세스가 <code>wait()</code>를 호출할 때까지 남아 있게 된다.</p>
<ul>
<li>좀비(<strong>zombie</strong>) 프로세스 — 종료되었지만 부모 프로세스가 아직 <code>wait()</code> 호출을 하지 않은 프로세스<ul>
<li>프로세스 종료 시 모든 프로세스는 좀비 상태가 되지만 아주 짧은 시간 동안만 머무른다.</li>
<li>부모가 <code>wait()</code>를 호출하면 좀비 프로세스의 프로세스 식별자(pid)와 프로세스 테이블의 해당 항목이 운영체제에 반환된다.</li>
</ul>
</li>
<li>고아(<strong>orphan</strong>) 프로세스 — 부모 프로세스가 <code>wait()</code>를 호출하는 대신 종료하는 경우의 자식 프로세스<ul>
<li>주기적으로 고아 프로세스의 종료 상태를 수집하고 새로운 부모 프로세스를 지정하여 문제를 해결한다.</li>
</ul>
</li>
</ul>
<h1 id="프로세스-간-통신-interprocess-communication">프로세스 간 통신 (Interprocess Communication)</h1>
<p>운영체제에서 동시에 실행되는 프로세스들은 독립적(independent)이거나, 협력적(cooperating)인 프로세스일 수 있다.</p>
<ul>
<li><p><strong>independent</strong> process — 시스템에서 실행 중인 다른 프로세스들과 데이터를 공유하지 않는 프로세스</p>
</li>
<li><p><strong>cooperating</strong> process — 시스템에서 실행 중인 다른 프로세스들에 영향을 주거나 받는 프로세스</p>
<p>  프로세스 협력(cooperation)을 허용하는 환경을 제공하는 데는 몇 가지 이유가 있다.</p>
<ul>
<li>정보 공유 (<strong>information sharing</strong>)</li>
<li>계산 가속화 (<strong>computation speedup</strong>)</li>
<li>모듈성 (<strong>modularity</strong>)</li>
</ul>
</li>
</ul>
<p>협력적(cooperating) 프로세스들은 데이터를 교환할 수 있는, 즉 서로 데이터를 주고 받을 수 있는 프로세스 간 통신(<strong>interprocess communication</strong>, <strong>IPC</strong>) 기법이 필요하다.</p>
<p>프로세스 간 통신에는 기본적으로 두 가지 모델인 공유 메모리(shared memory)와 메시지 전달(message passing)이 있다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/b60ead5e-603c-42a4-8253-10f66b2af59a/image.png" alt="**Figure 3.11** Communications models. (a) Shared memory. (b) Message passing."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.11</strong> Communications models. (a) Shared memory. (b) Message passing.</td>
</tr>
</tbody></table>
<h2 id="ipc-in-shared-memory-systems">IPC in Shared-Memory Systems</h2>
<ul>
<li>공유 메모리를 사용하는 프로세스 간 통신에서는 통신하는 프로세스들이 공유 메모리 영역을 구축해야 한다. 그런 후에 프로세스들은 공유 영역에 읽고 씀으로써 정보를 교환할 수 있다.</li>
<li>공유 메모리 영역을 구축할 때만 시스템 콜이 필요하고, 그 이후의 모든 접근은 일반적인 메모리 접근으로 취급되어 커널의 도움이 필요 없으므로, 메시지 전달보다 더 빠르다.</li>
<li>예시로는 POSIX Shared Memory가 있다.
Cf. POSIX: Portable Operating System Inteface (for uniX)</li>
</ul>
<h2 id="ipc-in-message-passing-systems">IPC in Message-Passing Systems</h2>
<ul>
<li>통신이 협력 프로세스들 사이에 교환되는 메시지를 통하여 이루어진다.</li>
<li>충돌을 회피할 필요가 없기 때문에 적은 양의 데이터를 교환하는 데 유용하다.</li>
<li>분산 시스템에서 공유 메모리보다 구현하기 쉽다.</li>
<li>통상 시스템 콜을 사용하여 구현되므로 커널 간섭 등의 부가적인 시간 소비 작업이 필요하기 때문에 공유 메모리 모델보다 더 느리다.</li>
<li>예시로는 Pipes, Socket, RPC, Message Queue, Signal 등이 있다.
Cf. Pipes: UNIX 시스템의 가장 오래된 IPC 기법 중 하나</li>
</ul>
<h3 id="소켓-socket">소켓 (Socket)</h3>
<p>소켓(<strong>socket</strong>)은 클라이언트 서버 통신의 endpoint를 뜻하며, 두 프로세스가 네트워크상에서 통신을 하려면 양 프로세스마다 하나씩, 총 두 개의 소켓이 필요해진다.</p>
<p>각 소켓은 IP 주소(<strong><em>IP address</em></strong>)와 연결된 포트(<strong><em>port</em></strong>) 번호를 통해 구별한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/aa02c1bf-ffc7-46f5-a7ee-a3f050273353/image.png" alt="**Figure 3.26** Communication using sockets."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 3.26</strong> Communication using sockets.</td>
</tr>
</tbody></table>
<p>Java는 세 가지 종류의 소켓을 제공한다.</p>
<ul>
<li><strong><em>Socket</em></strong> class: <strong>connection-oriented</strong> (<strong>TCP</strong>)</li>
<li><strong><em>DatagramSocket</em></strong> class: <strong>connectionless</strong> (<strong>UDP</strong>)</li>
<li><strong><em>MulticastSocket</em></strong> class: 데이터를 여러 수신자에게 보낼 수 있다.</li>
</ul>
<p>소켓은 스레드 간에 구조화되지 않은 바이트 스트림만을 통신하기 때문에, 원시적인 바이트 스트림 데이터를 구조화하여 해석하는 것은 클라이언트와 서버의 책임이 된다. 이에 대한 대안으로 더욱 높은 수준의 통신 기법인 원격 프로시저 호출(remote procedure call, RPC)이 있다.</p>
<h3 id="원격-프로시저-호출-remote-procedure-calls-rpc">원격 프로시저 호출 (Remote Procedure Calls, RPC)</h3>
<p>RPC는 원격 서비스와 관련한 가장 보편적인 형태 중 하나다. 네트워크에 연결된 두 시스템 사이의 통신에 사용하기 위하여 프로시저(함수) 호출 기법을 추상화하는 방법으로 설계되었다. 프로세스들이 서로 다른 시스템 위에서 돌아가기 때문에 원격 서비스를 제공하기 위해서는 메시지 기반 통신을 해야 한다.</p>
<p>RPC는 클라이언트가 원격 호스트의 프로시저 호출을 마치 자기의(local) 프로시저 호출처럼 해준다.</p>
<ul>
<li>RPC 시스템은 클라이언트 쪽에 <strong>stub</strong>을 제공하여 통신을 하는 데 필요한 자세한 사항들을 숨겨준다.</li>
<li>클라이언트가 원격 프로시저를 호출하면 RPC는 그에 대응하는 stub을 호출하고 원격 프로시저가 필요로 하는 매개변수를 건네준다. 그러면 stub이 원격 서버의 포트를 찾고 매개변수를 정돈(<strong>marshall</strong>)한다.</li>
<li>그 후 stub은 메시지 전달(message-passing) 기법을 사용하여 서버에게 메시지를 전송한다. 이에 대응되는 stub이 서버에도 존재하여 서브 측 stub이 메시지를 수신한 후 적절한 서버의 프로시저를 호출한다.</li>
</ul>
<p>Cf. 매개변수 정돈(parameter marshalling)은 클라이언트와 서버 기기의 데이터 표현 방식의 차이 문제를 해결한다. 어떤 기계는 <strong>big-endian</strong> 방식을, 어떤 기계는 <strong>little-endian</strong> 방식을 사용하더라도 <strong>XDR</strong> (<strong>external data representation</strong>)과 같은 중립적인 데이터 표현 방식으로 데이터를 바꾸어서 보낸다.</p>
<p>RPC는 분산 파일 시스템(distributed file system, DFS)을 구현하는 데 유용하다.</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition"><strong>Operating System Concepts 10th</strong></a><ul>
<li><strong>PART TWO PROCESS MANAGEMENT</strong><ul>
<li>Chapter 5 CPU Scheduling</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98#curriculum"><strong>운영체제 공룡책 전공강의 | 주니온 - 인프런</strong></a><ul>
<li><strong>섹션 4. Chapter 5. CPU Scheduling</strong><ul>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/unit/63033">09. CPU 스케줄링: Chapter 5. CPU Scheduling (Part 1)</a></li>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/unit/63034">10. 스케줄링 알고리즘: Chapter 5. CPU Scheduling (Part 2)</a></li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[REST API]]></title>
            <link>https://velog.io/@hee-suh/REST-API</link>
            <guid>https://velog.io/@hee-suh/REST-API</guid>
            <pubDate>Thu, 07 Nov 2024 02:53:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>REST API는 두 컴퓨터 시스템이 인터넷을 통해 데이터를 교환하기 위해 사용하는 인터페이스다. 먼저, REST가 무엇인지 살펴보자.</strong></p>
</blockquote>
<h1 id="rest-representational-state-transfer">REST (REpresentational State Transfer)</h1>
<h2 id="rest-등장-배경">REST 등장 배경</h2>
<h3 id="web">Web</h3>
<p>웹(World Wide Web, WWW)은 정보를 <strong>하이퍼텍스트</strong>로 연결하여 인터넷을 통해 공유할 수 있도록 설계된 시스템으로, 1990년대 초에 Tim Beners-Lee에 의해 등장했다. <strong>URL</strong>을 사용해 자원을 식별하고, <strong>HTTP</strong>로 자원을 전송하고, <strong>HTML</strong>을 통해 웹 페이지의 콘텐츠와 구조를 정의했다. </p>
<blockquote>
<h4 id="📖-웹의-주요-개념">📖 웹의 주요 개념</h4>
</blockquote>
<ul>
<li><strong>Hypertext</strong>: 텍스트 내에서 다른 문서나 리소스에 연결할 수 있는 링크를 포함하여, 사용자가 한 정보를 통해 관련된 다른 정보로 자연스럽게 이동할 수 있게 해준다.</li>
<li><strong>HTML(Hypertext Transfer Markup Language)</strong>: 표현 형식으로, 웹 페이지의 구조와 콘텐츠를 정의하는 언어로, 링크, 텍스트, 이미지 등을 하이퍼텍스트 형태로 연결할 수 있게 한다.</li>
<li><strong>HTTP(Hypertext Transfer Protocol)</strong>: 전송 방법으로, 웹 브라우저와 웹 서버가 정보를 주고받는 데 사용하는 프로토콜이다.</li>
<li><strong>URL(Uniform Resource Locator)</strong>: 식별자로, 각 정보의 위치를 식별하는 주소로, 웹 페이지나 파일 등 모든 웹 리소스를 고유하게 식별할 수 있다. </li>
</ul>
<h3 id="http10">HTTP/1.0</h3>
<p>HTTP/1.0은 웹에서 클라이언트와 서버가 자원을 요청하고 응답하는 기본 프로토콜로 설계되었지만, 웹이 확장되며 많은 단점이 나타났다. 예를 들어, 비연결형 통신 방식은 반복적인 연결과 종료로 인해 성능 문제가 발생했다. (Cf. <a href="https://velog.io/@hee-suh/HTTP#http-10">HTTP/1.0의 비지속 연결</a>)</p>
<p>Roy Fielding은 이를 극복하면서도 웹 전체의 아키텍처를 장기적으로 유지할 수 있는 접근 방식이 필요하다고 생각했고, HTTP Object Model을 해결책으로 제시했다.</p>
<h3 id="http-object-model">HTTP Object Model</h3>
<p>HTTP 오브젝트 모델은 요청과 응답을 독립적인 오브젝트로 관리하며, HTTP 프로토콜에서 리소스와 메타데이터를 구조화된 형식으로 정의했다. 그리고 이 HTTP 오브젝트 모델이 4년 후 REST라는 이름으로 발표된다.</p>
<p>📄 <a href="https://roy.gbiv.com/talks/webarch_9805/index.htm">Roy T. Fielding(1998). Representational State Transfer: An Architectural Style for Distributed Hypermedia Interaction</a></p>
<h2 id="rest">REST</h2>
<p>REST는 &#39;Representational State Transfer&#39;의 약자로, 분산 하이퍼미디어 시스템(e.g. 웹)의 아키텍처 스타일이다. </p>
<p>Roy T. Fielding의 박사 논문 <a href="https://ics.uci.edu/~fielding/pubs/dissertation/top.htm">Roy T. Fielding(2000). Architectural Styles and the Design of Network-based Software Architectures</a> 에서 구체화되었으며, REST를 통해 웹이 계속 발전하고 확장되더라도 기존 시스템과의 호환성을 잃지 않으면서도, 새로운 시스템들이 독립적이고 서로 다른 방향으로 진화할 수 있게 만들고자 했다.</p>
<h2 id="rest의-제약-조건">REST의 제약 조건</h2>
<p>아키텍처 스타일이란 제약조건의 집합을 의미한다. REST는 여섯 가지 제약 조건을 통해 시스템의 성능, 확장성, 신뢰성을 증대시키고, 독립적인 발전을 가능하게 한다.</p>
<h3 id="1-client-server">1. Client-Server</h3>
<p>클라이언트-서버 구조를 통해 관심사를 분리한다. 사용자 인터페이스와 데이터 저장을 분리해 컴포넌트 간 독립적인 발전과 확장성을 지원한다.</p>
<h3 id="2-stateless">2. Stateless</h3>
<p>서버는 클라이언트의 상태를 기억하지 않으며, 모든 요청이 독립적이어서 가시성, 확장성, 신뢰성이 향상된다.</p>
<h3 id="3-cache">3. Cache</h3>
<p>응답을 캐싱할 수 있도록 하여, 네트워크 효율성과 사용자 경험을 개선한다.</p>
<h3 id="4-uniform-interface">4. Uniform Interface</h3>
<p>일관된 인터페이스를 사용하여 컴포넌트 간 상호작용을 표준화하고, 독립적 발전을 가능하게 한다.</p>
<h4 id="uniform-interface의-제약-조건">Uniform Interface의 제약 조건</h4>
<ul>
<li>identification of resources</li>
<li>manipulation of resources through representations</li>
<li><strong>self-descriptive messages</strong></li>
<li><strong>hypermedia as the engine of application state (HATEOAS)</strong>
  클라이언트가 서버 응답 내 하이퍼미디어 링크를 통해 자원 상태를 탐색할 수 있도록 한다. 이를 통해 클라이언트는 사전에 서버의 URI 구조를 알지 않아도 다양한 상호작용을 수행할 수 있다.</li>
</ul>
<h3 id="5-layered-system">5. Layered System</h3>
<p>계층 구조를 통해 보안, 캐싱, 확장성을 지원한다.</p>
<h3 id="6-code-on-demand-optional">6. Code-On-Demand (Optional)</h3>
<p>클라이언트가 코드를 다운로드하고 실행해 기능을 확장할 수 있지만, 가시성을 떨어뜨리기 때문에 선택적 제약 조건으로 적용된다.</p>
<h2 id="rest의-의의">REST의 의의</h2>
<p>REST는 웹이 전 세계적 확장성과 지속 가능성을 갖춘 분산 아키텍처로 발전할 수 있도록 기여했으며, API 설계에서 자원 중심의 표준을 제시하며 다양한 서비스와 시스템 간의 상호 운용성을 높였다.</p>
<h1 id="rest-api">REST API</h1>
<p>REST API(또는 RESTful API)는 REST 아키텍처 스타일의 설계 원칙을 준수하는 API로, 두 컴퓨터 시스템이 인터넷을 통해 데이터를 교환하기 위해 사용하는 인터페이스다. REST API는 HTTP 프로토콜을 기반으로 하며, 자원을 HTTP 메서드로 조작하여 다양한 시스템 간에 일관된 상호작용을 가능하게 한다.</p>
<h2 id="rest-api-설계-원칙">REST API 설계 원칙</h2>
<p>REST API는 기본적으로 다음과 같은 원칙을 따른다.</p>
<h3 id="1-identification-of-resources---uri">1. Identification of Resources - URI</h3>
<p>모든 자원(Resources)은 <a href="https://developer.mozilla.org/ko/docs/Glossary/URI">URI</a>(URL)를 통해 고유하게 식별된다. REST API에서 URI는 자원을 나타내는 중요한 요소로, 일관성 있는 URI 구조는 자원의 가독성과 사용성을 높인다.</p>
<h3 id="2-statelessness">2. Statelessness</h3>
<p>REST API는 무상태성을 유지해야 한다. 클라이언트의 모든 요청은 독립적으로 처리되며, 각 요청에 필요한 모든 정보는 요청 자체에 포함되어야 한다. 이를 통해 서버는 클라이언트의 상태를 기억하지 않으며, 확장성과 관리가 용이해진다.</p>
<h3 id="3-uniform-interface---http">3. Uniform Interface - HTTP</h3>
<p>REST API는 HTTP 통신 규약에 따라 클라이언트와 서버 간 일관된 상호작용을 정의한다. 이를 위해 HTTP 메서드와 HTTP 상태 코드를 사용하여 자원의 상태를 CRUD 방식으로 조작하고, 요청 결과를 명확히 전달한다.</p>
<h4 id="http-method">HTTP Method</h4>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods">HTTP 메서드</a>는 자원에 대한 작업을 정의하는 표준 동작 방식으로, GET, POST, PUT, PATCH, DELETE 등을 포함한다.</p>
<ul>
<li><strong>GET</strong>: 자원의 조회</li>
<li><strong>POST</strong>: 자원의 생성</li>
<li><strong>PUT</strong>: 자원의 전체 수정</li>
<li><strong>PATCH</strong>: 자원의 일부 수정</li>
<li><strong>DELETE</strong>: 자원의 삭제</li>
</ul>
<h4 id="http-status-code">HTTP Status Code</h4>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">HTTP 상태 코드</a>는 요청의 성공 여부를 알리는 표준 코드로, 응답은 다섯 개의 그룹으로 나누어진다.</p>
<ol>
<li><strong>Informational Responses</strong> (<code>100</code>-<code>199</code>)</li>
<li><strong>Successful Responses</strong> (<code>200</code>-<code>299</code>)<ul>
<li>200 OK: 요청 성공</li>
<li>201 Created: 자원이 성공적으로 생성됨</li>
<li>204 No Content: 요청 성공, 반환할 데이터 없음</li>
</ul>
</li>
<li><strong>Redirection Messages</strong> (<code>300</code>-<code>399</code>)</li>
<li><strong>Client Error Responses</strong> (<code>400</code>-<code>499</code>)<ul>
<li>400 Bad Request: 잘못된 요청</li>
<li>404 Not Found: 요청한 자원을 찾을 수 없음</li>
</ul>
</li>
<li><strong>Server Error Responses</strong> (<code>500</code>-<code>599</code>)<ul>
<li>Internal Server Error: 서버 오류</li>
</ul>
</li>
</ol>
<h3 id="4-representation">4. Representation</h3>
<p>자원의 상태는 JSON, XML 등과 같은 표현 방식으로 전송된다. 표현은 자원의 데이터를 클라이언트에 전달하는 형식으로, 클라이언트가 자원을 이해하고 사용할 수 있게 한다. 대부분의 REST API에서는 JSON을 기본 형식으로 사용하여 데이터를 주고받는다.</p>
<h1 id="마치며">마치며</h1>
<p>REST의 개념과 등장 배경을 Roy T. Fielding의 논문을 바탕으로 정리했다. REST의 원칙을 잘 준수한 API는 확장성과 유지보수성을 높이고, 클라이언트와 서버 간의 일관된 상호작용을 가능하게 한다. 사실 대부분의 API는 REST보다는 HTTP API에 가까운 형태로 구현되곤 하는데, 이와 관련된 내용은 <a href="https://deview.kr/2017/schedule/212">그런 REST API로 괜찮은가</a>에서 확인해보자. REST API 설계 원칙을 고려해 더 견고하고 직관적인 API를 설계해보자!</p>
<h1 id="references">References</h1>
<p><a href="https://ics.uci.edu/~fielding/pubs/dissertation/top.htm">Roy T. Fielding(2000). Architectural Styles and the Design of Network-based Software Architectures</a> 
<a href="https://deview.kr/2017/schedule/212">[DEVIEW 2017] 그런 REST API로 괜찮은가</a>
<a href="https://meetup.nhncloud.com/posts/92">[NHN Cloud] REST API 제대로 알고 사용하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[운영체제 구조]]></title>
            <link>https://velog.io/@hee-suh/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@hee-suh/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Tue, 05 Nov 2024 14:44:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="🦖-operating-system-concepts-10th">🦖 <a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition">Operating System Concepts 10th</a></h4>
<p><strong>PART ONE OVERVIEW</strong>
Chapter 2 Operating-System Structures</p>
</blockquote>
<h1 id="운영체제-서비스-operating-system-services">운영체제 서비스 (Operating-System Services)</h1>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/8c4c79c0-0fae-484a-9892-592df08c4a10/image.png" alt="Figure 2.1** A view of operating system services."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 2.1</strong> A view of operating system services.</td>
</tr>
</tbody></table>
<p>운영체제는 프로그램 실행 환경을 제공한다. 운영체제는 프로그램과 그 프로그램의 사용자에게 특정 서비스를 제공한다.</p>
<ul>
<li><strong>사용자 인터페이스(user interface)</strong>: 거의 모든 운영체제는 <strong>사용자 인터페이스(UI)</strong>를 제공한다. 가장 일반적인 형태는 <strong>그래픽 사용자 인터페이스(GUI)</strong>이며, <strong>터치 스크린 인터페이스</strong>, <strong>명령러 라인 인터페이스(CLI)</strong>가 있다.</li>
<li><strong>프로그램 수행(program execution)</strong>: 시스템은 프로그램을 메모리에 적재해 실행한다.</li>
<li><strong>입출력 연산(I/O operation)</strong>: 수행 중인 프로그램은 입출력을 요구할 수 있다. 효율과 보호를 위해, 사용자들은 통상 입출력 장치를 직접 제어할 수 없으며, 운영체제가 입출력 수행의 수단을 제공한다.</li>
<li><strong>파일 시스템 조작(file system manipulation)</strong>: 프로그램은 파일을 읽고 쓰고, 이름에 의해 생성, 삭제, 검색을 할 수 있어야 하고, 파일의 정보를 열거할 수 있어야 한다. 또한 몇몇 프로그램은 파일 소유권에 기반을 둔 권한 관리를 이용하여 파일이나 디렉터리의 접근을 허가하거나 거부할 수 있게 한다. (e.g. <code>chmod</code>)</li>
<li><strong>통신(communication)</strong>: 한 프로세스가 다른 프로세스와 정보를 교환해야 할 필요가 있는 여러 상황이 있다. 1) 동일한 컴퓨터에서 수행되고 있는 프로세스들 사이에서 통신을 하거나 2) 네트워크에 의해 함께 묶여 있는 서로 다른 컴퓨터 시스템상에서 수행되는 프로세스들 사이에서 통신이 수행된다. 통신은 <strong>공유 메모리(shared memory)</strong>를 통해서 구현될 수도 있고, <strong>메시지 전달(message passing)</strong> 기법을 사용하여 구현될 수 있는데, 후자의 경우 정보의 패킷들이 운영체제에 의해 프로세스들 사이를 이동한다.</li>
<li><strong>오류 탐지(error detection)</strong>: 운영체제는 모든 가능한 오류를 항상 감지하고 고칠 수 있어야 한다. CPU 및 메모리 하드웨어, I/O 장치 또는 사용자 프로그램에서 에러가 발생한다.</li>
</ul>
<p>사용자에게 도움을 주는 것이 목적이 아니라 시스템 자체의 효율적인 동작을 보장하기 위한 운영체제 기능들도 존재한다. 다수의 프로세스가 사용하는 시스템에서는 프로세스들 간에 컴퓨터 자원을 공유하게 함으로써 효율성을 얻을 수 있다.</p>
<ul>
<li><strong>자원 할당(resource allocation)</strong>: 다수의 프로세스나 다수의 작업이 동시에 실행될 때, 그들 각각에 자원을 할당해 줘야 한다.</li>
<li><strong>기록 작성(logging)</strong>: 어떤 프로그램이 어떤 종류의 컴퓨터 자원을 얼마나 많이 사용하는지를 추적할 수 있는 기록 관리는 회계(accounting, 사용자에게 청구서 요청) 또는 단순히 통계를 내기 위해 사용된다. 사용 통계는 컴퓨팅 서비스를 개선하기 위해 시스템을 재구성하고자 하는 시스템 관리자가 유용하게 사용할 수 있다.</li>
<li><strong>보호(protection)와 보안(security)</strong>: 다중 사용자 컴퓨터 시스템 또는 네트워크로 연결된 컴퓨터 시스템에 저장된 정보의 소유자는 그 정보의 사용을 통제하길 원한다. 또한 서로 다른 여러 프로세스가 동시에 수행될 때, 한 프로세스가 다른 프로세스나 운영체제 자체를 방해해서는 안 된다. 보호는 시스템 자원에 대한 모든 접근이 통제되도록 보장하는 것을 필요로 한다. 보안은 네트워크 어댑터 등과 같은 외부 입출력 장치들을 부적합한 접근 시도로부터 지키고, 침입을 탐지하기 위해 모든 저복을 기록하는 것으로 범위를 넓힌다. 시스템이 보호되고 보안이 유지되려면, 시스템 전체에 걸쳐 예방책이 제정되어야 한다.</li>
</ul>
<h1 id="사용자와-운영체제-인터페이스-user-and-operating-system-interface">사용자와 운영체제 인터페이스 (User and Operating-System Interface)</h1>
<p>사용자가 운영체제와 접촉(interface)하는 3가지 기본적인 방법이 있다. 한 방식은 명령어 라인 인터페이스(CLI) 또는 <strong>명령 인터프리터(command interpreter)</strong>를 제공하는 것이다. 이 명령어 라인 인터페이스는 사용자가 운영체제가 수행할 명령어를 직접 입력할 수 있도록 한다. 다른 두 가지 방식은 사용자가 그래픽 기반 사용자 인터페이스를 통하여 운영체제와 접촉하게 하는 것이다.</p>
<h2 id="명령-인터프리터-command-interpreter">명령 인터프리터 (Command interpreter)</h2>
<p>Command Line Interface(CLI) 또는 Command interpreter는 <strong>셸</strong>(shell)이라고 불린다.</p>
<p>다양한 셸이 제공되며, sh, bash, csh, zsh, tcsh 등이 있다.</p>
<p>명령 인터프리터의 중요한 기능은 사용자가 지정한 명령을 가져와서 그것을 수행하는 것이다. 이 수준에서 제공된 많은 명령은 파일을 조작한다. </p>
<h2 id="그래픽-기반-사용자-인터페이스-graphical-user-interface">그래픽 기반 사용자 인터페이스 (Graphical User Interface)</h2>
<p>사용자 친화적인 그래픽 기반 사용자 인터페이스 또는 GUI 방식에서는 사용자가 CLI를 통하여 직접 명령어를 입력하는 것이 아니라, <strong>데스크톱</strong>의 마우스를 기반으로 하는 윈도 메뉴 시스템을 사용한다.</p>
<p>macOS의 Aqua, Microsoft의 Windows, GNU 프로젝트의 GNOME 등의 GUI 인터페이스가 있다.</p>
<h2 id="터치스크린-인터페이스-touch-screen-interface">터치스크린 인터페이스 (Touch Screen Interface)</h2>
<p>스마트폰 및 휴대용 태블릿 컴퓨터는 일반적으로 터치스크린 인터페이스를 사용한다. 사용자는 터치스크린에서 손가락을 누르거나 스와이프 하는 등의 <strong>제스처</strong>를 취하여 상호 작용한다.</p>
<p>Android UI나 Apple iPhone의 UI 등의 터치스크린 인터페이스가 있다.</p>
<h1 id="시스템-호출-system-calls">시스템 호출 (System Calls)</h1>
<p><strong>시스템 콜</strong>은 운영체제에 의해 사용할 수 있는 서비스에 대한 인터페이스를 제공한다.</p>
<p>컴퓨터 시스템이 사용자 애플리케이션을 대신하여 실행 중인 경우 시스템은 사용자 모드에 있다. 그러나 사용자 애플리케이션이 (system call을 통해) 운영 체제에 서비스를 요청하면 시스템은 요청을 이행하기 위해 <strong>사용자 모드(user mode)</strong>에서 <strong>커널 모드(kernel mode)</strong>로 전환해야 한다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/7b86da1e-f18e-4489-8a60-71c83157a8aa/image.png" alt="**Figure 1.13** Transition from user to kernel mode."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 1.13</strong> Transition from user to kernel mode.</td>
</tr>
</tbody></table>
<h2 id="응용-프로그래밍-인터페이스-application-programming-interface">응용 프로그래밍 인터페이스 (Application Programming Interface)</h2>
<p>대부분의 애플리케이션 개발자들은 <strong>응용 프로그래밍 인터페이스(application programming interface, API)</strong>에 따라 프로그램을 설계한다. API는 각 함수에 전달되어야 할 매개변수들과 프로그래머가 기대할 수 있는 반환 값을 포함하여 프로그래머가 사용 가능한 함수의 집합을 명시한다. 애플리케이션 프로그래머가 사용 가능한 가장 흔한 세 가지 API는 Windows API, POSIX API, Java API다. UNIX와 Linux 시스템에서 C 언어로 작성된 프로그램을 위해서 제공되는 라이브러리는 <strong>libc</strong>로 불린다.</p>
<p>API를 구성하는 함수들은 통상 애플리케이션 개발자를 대신하여 실제 시스템 콜을 호출한다. (e.g. UNIX <code>wait()</code>) 대부분의 POSIX와 Windows API는 UNIX, Linux 및 Windows 운영체제가 제공하는 고유의 시스템 콜과 유사하다.</p>
<pre><code class="language-c">// 표준 API의 예
#include &lt;unistd.h&gt;

// return value(ssize_t), function name(read), parameters(fd, buf, count)
ssize_t read(int fd, void *buf, size_t count)</code></pre>
<p><strong>실행시간 환경</strong>(<strong>run-time environement</strong>, <strong>RTE</strong>)은 시스템 콜을 처리하는 데 있어 중요한 요소다. <strong>RTE</strong>는 운영체제가 제공하는 시스템 콜에 대한 연결고리 역할을 하는 <strong>시스템 콜 인터페이스</strong>를 제공한다. 이 시스템 콜 인터페이스는 API 함수의 호출을 가로채어 필요한 운영체제 시스템 콜을 부른다.</p>
<p>운영체제 인터페이스에 대한 대부분의 자세한 내용은 API에 의해 프로그래머로부터 숨겨지고 RTE에 의해 관리된다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/6cc3be26-34ee-4a07-b8a0-bada6a894cde/image.png" alt="**Figure 2.6** The handling of a user application invoking the open() system call."></th>
</tr>
</thead>
<tbody><tr>
<td><strong>Figure 2.6</strong> The handling of a user application invoking the open() system call.</td>
</tr>
</tbody></table>
<p>운영체제에 매개변수를 전달할 때 사용하는 세 가지 일반적인 방법이 있다. </p>
<ul>
<li>가장 간단한 방법은 매개변수를 <strong>레지스터</strong> 내에 전달하는 것이다.</li>
<li>레지스터보다 더 많은 매개변수가 있는 경우에 매개변수는 메모리 내의 <strong>블록</strong>이나 테이블에 저장되고, 블록의 주소가 레지스터 내에 매개변수로 전달된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/f5fb6c90-bd69-4c22-b963-c4164c88dcb8/image.png" alt="**Figure 2.7** Passing of parameters as a table.">|
|---|
|<strong>Figure 2.7</strong> Passing of parameters as a table.|</p>
<ul>
<li>매개변수는 프로그램에 의해 스택(<strong>stack</strong>)에 넣어질(<strong>push</strong>) 수 있고, 운영체제에 의해 꺼내진다(<strong>pop off</strong>).</li>
</ul>
<p>Cf. Linux는 위 두 개 접근법을 조합하여, 매개변수가 5개 이하면 레지스터가 사용되고 5개를 넘으면 블록 방법이 사용된다.</p>
<p>Cf. 블록이나 스택 방법은 전달되는 매개변수들의 개수나 길이를 제한하지 않기에 일부 운영체제에서 선호된다.</p>
<h2 id="시스템-콜의-유형-types-of-system-calls">시스템 콜의 유형 (Types of System Calls)</h2>
<p>시스템 콜은 여섯 가지의 주요 카테고리로 묶을 수 있다.</p>
<ul>
<li><p><strong>프로세스 제어 (Process Control)</strong></p>
<p>  <code>fork()</code>, <code>exit()</code>, <code>wait()</code></p>
<ul>
<li>create process, terminate process</li>
<li>load, execute</li>
<li>get process attributes, set process attributes</li>
<li>wait event, signal event</li>
<li>allocate and free memory</li>
</ul>
</li>
<li><p><strong>파일 조작 (File management)</strong></p>
<p>  <code>open()</code>, <code>read()</code>, <code>write()</code>, <code>close()</code></p>
<ul>
<li>create file, delete file</li>
<li>open, close</li>
<li>read, write, reposition</li>
<li>get file attributes, set file attributes</li>
</ul>
</li>
<li><p><strong>장치 조작 (Device management)</strong></p>
<p>  <code>ioctl()</code>, <code>read()</code>, <code>write()</code></p>
<ul>
<li>request device, release device</li>
<li>read, write, reposition</li>
<li>get device attributes, set device attributes</li>
<li>logically attach or detach devices</li>
</ul>
</li>
<li><p><strong>정보 유지보수 (Information maintenance)</strong></p>
<p>  <code>getpid()</code>, <code>alarm()</code>, <code>sleep()</code></p>
<ul>
<li>get time or date, set time or date</li>
<li>get system data, set system data</li>
<li>get process, file, or device attributes</li>
<li>set process, file, or device attributes</li>
</ul>
</li>
<li><p><strong>통신 (Communications)</strong></p>
<p>  <code>pipe()</code>, <code>shm open()</code>, <code>mmap()</code></p>
<ul>
<li>create, delete communication connection</li>
<li>send, receive messages</li>
<li>transfer status information</li>
<li>attach or detach remote devices</li>
</ul>
</li>
<li><p><strong>보호 (Protection)</strong></p>
<p>  <code>chmod()</code>, <code>umask()</code>, <code>chown()</code></p>
<ul>
<li>get file permissions</li>
<li>set file permissions</li>
</ul>
</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li><a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition"><strong>Operating System Concepts 10th</strong></a><ul>
<li><strong>PART ONE OVERVIEW</strong><ul>
<li>Chapter 2 Operating-System Structures</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98#curriculum"><strong>운영체제 공룡책 전공강의 | 주니온 - 인프런</strong></a><ul>
<li><strong>섹션 1. Chapter 1-2. Introduction &amp; O/S Structures</strong><ul>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/unit/63028">02. 운영체제의 개념과 구조: Chapter 1-2. Introduction &amp; O/S structures.</a></li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Scheduler]]></title>
            <link>https://velog.io/@hee-suh/Scheduler</link>
            <guid>https://velog.io/@hee-suh/Scheduler</guid>
            <pubDate>Tue, 05 Nov 2024 14:22:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://jser.dev/">JSer.dev</a>의 <a href="https://jser.dev/series/react-source-code-walkthrough">React Internals Deep Dive</a>를 번역하여 정리한 글입니다.</p>
</blockquote>
<blockquote>
<p>⚠️ <a href="https://react.dev/blog/2024/04/25/react-19">React@19</a>의 <a href="https://github.com/facebook/react/tree/7608516479fb85bc40aa73d8a31a0c93397ee6ff">commit 7608516</a>을 기반으로 작성되었으며, 최신 버전에서는 구현이 변경되었을 수 있습니다.</p>
</blockquote>
<h4 id="📝-how-react-scheduler-works-internally">📝 <a href="https://jser.dev/react/2022/03/16/how-react-scheduler-works">How React Scheduler works internally?</a></h4>
<h1 id="1-why-react-scheduler-is-needed">1. Why React Scheduler is needed.</h1>
<h2 id="sync-render">Sync Render</h2>
<p>동기적인 렌더링에서는 작업이 중단(interrupt)되지 않고, while문에서 작업을 계속 진행한다. <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberWorkLoop.js#L2101"><code>workLoopSync()</code></a>에서 root로부터 모든 fiber를 순회하면서 <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberWorkLoop.js#L2374"><code>performUnitOfWork()</code></a>를 수행한 후 commit 단계에서 host DOM에 변경 사항을 적용한다. 이 작업은 일시 중지되거나 취소될 수 없어서, 이 콜 스택이 전부 처리되기 전까지 메인 스레드는 다른 작업을 할 수 없고, 앱은 일시적으로 무반응 상태가 되거나 버벅거리게 된다.</p>
<p>Cf. <a href="https://velog.io/@hee-suh/React-Internals-Deep-Dive-2-Initial-Mount">Initial mount</a>는 DefaultLane이 blocking lane이기 때문에, sync로 작동하는 경우 중 하나다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberWorkLoop.js#L2101">workLoopSync()</a></p>
<pre><code class="language-ts">function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}</code></pre>
<h2 id="concurrent-render">Concurrent Render</h2>
<p>렌더링 작업을 잘게 쪼개어 여러 프레임에 걸쳐 실행할 수 있고, 특정 작업에 “우선순위”를 매겨 <strong>작업의 작은 조각들을 concurrent하게</strong> “일시 정지”, “재가동”할 수 있게 하는 concurrent feature가 등장했고, 이때 스케줄러가 필요하다. Cf. <a href="https://velog.io/@hee-suh/React-Internals-Deep-Dive-2-Initial-Mount#concurrency-%EB%8F%99%EC%8B%9C%EC%84%B1">Concurrency 동시성</a> </p>
<p><strong>Concurrent</strong> 렌더에서는 우선순위가 높은 작업이 우선순위가 낮은 작업을 중단할 수 있으므로, 작업을 중단(interrupt)하고 재개(resume)하는 방법이 필요하다. 스케줄러가 우선순위에 따라 task를 처리하고, <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js#L458"><code>shouldYield()</code></a>를 통해 frame interval보다 task 처리 시간이 길어지면 <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberWorkLoop.js#L2366"><code>workLoopConcurrent()</code></a>를 중단한 후, Frame을 업데이트하거나 우선순위가 더 높은 task를 처리할 수 있도록 양보(yield)한다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberWorkLoop.js#L2366">workLoopConcurrent()</a></p>
<pre><code class="language-ts">function workLoopConcurrent() {
  while (workInProgress !== null &amp;&amp; !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}</code></pre>
<blockquote>
<h4 id="💡-세-가지-우선순위-시스템">💡 세 가지 우선순위 시스템</h4>
</blockquote>
<ol>
<li>Scheduler Priority — 스케줄러에서 task의 우선순위를 지정하는 데 사용된다.</li>
<li>Event Priority - 사용자 이벤트의 우선순위를 표시한다.</li>
<li>Lane Priority — 작업(work) 우선순위를 표시한다.<blockquote>
</blockquote>
💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberRootScheduler.js#L48">ReactFiberRootScheduler.js</a>
SyncLane이 아닌 경우에 <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberRootScheduler.js#L326"><code>getHighestPriorityLane()</code></a>를 이용해 가장 높은 우선순위를 가진 lane을 가져와서 callback 우선순위로 사용하고, lane을 event 우선순위로 매핑하여 <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberRootScheduler.js#L401"><code>scheduleCallback()</code></a>의 우선순위로 사용한다.<blockquote>
</blockquote>
우선순위를 이용해 스케줄러에서 작업을 예약하여 fiber 트리를 조정한다.<blockquote>
</blockquote>
Cf. <a href="https://jser.dev/react/2022/03/26/lanes-in-react/">What are Lanes in React source code?</a></li>
</ol>
<h1 id="2-background-knowledge">2. Background Knowledge</h1>
<h2 id="21-event-loop">2.1 Event Loop</h2>
<p>JavaScript 엔진은 다음과 같은 작업을 수행한다.</p>
<ol start="0">
<li><code>Call Stack</code>에 있는 <code>Sync</code> 작업이 모두 수행되고, 비어있게 되면(현재 실행중인 태스크가 없는 경우), <code>Event Loop</code>가 작업을 수행할 수 있다.</li>
<li>예약된 Microtask가 있으면 모두 실행한다. (FIFO)</li>
<li>Microtask Queue가 비었다면, Task Queue에서 Task(Macrotask)를 하나 가져와 실행한다. (FIFO)</li>
<li>리렌더링이 필요한지 확인하고 렌더한다.</li>
<li>task가 더 있으면 1을 반복하거나, task를 기다린다.</li>
</ol>
<p>Cf. <a href="https://www.lydiahallie.com/blog/event-loop">[lydiahallie] JavaScript Visualized: 
Event Loop, Web APIs, (Micro)task Queue</a></p>
<h2 id="22-setimmediate-to-schedule-a-new-task-without-blocking-the-rendering">2.2 <code>setImmediate()</code> to schedule a new task without blocking the rendering</h2>
<p>브라우저에서 렌더링을 막지 않고 task를 스케줄 하기 위해서는 해당 task를 macrotask로 등록하여 call stack이 비워진 후에 Event Loop를 거쳐 실행하도록 해야 한다. 이를 위해 흔히 <code>setTimeout(callback, 0)</code>을 사용한다. 그러나 <a href="https://developer.mozilla.org/en-US/docs/Web/API/setTimeout"><code>setTimeout()</code></a>은 <a href="https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers">HTML Standard</a>에 명세되어있듯이 중첩 호출에서 최소 4ms 지연이 발생하고, <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate"><code>setImmediate()</code></a>는 지연이 없기 때문에 React에서 더 선호된다.</p>
<p>하지만 <code>setImmediate()</code>는 브라우저 호환성이 좋지 않기 때문에, 대체 방법인 <a href="https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel"><code>MessageChannel()</code></a>)이나 <code>setTimeout()</code>을 주로 사용하여 새로운 macrotask를 예약한다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js#L516">Scheduler.js</a></p>
<pre><code class="language-ts">let schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === &quot;function&quot;) {
  // Node.js and old IE.
  // Unlike MessageChannel, it doesn&#39;t prevent a Node.js process from exiting.
  // But also, it runs earlier which is the semantic we want.
  // If other browsers ever implement it, it&#39;s better to use it.
  // Although both of these would be inferior to native scheduling.
  schedulePerformWorkUntilDeadline = () =&gt; {
    localSetImmediate(performWorkUntilDeadline);
  };
} else if (typeof MessageChannel !== &quot;undefined&quot;) {
  // DOM and Worker environments.
  // We prefer MessageChannel because of the 4ms setTimeout clamping.
  const channel = new MessageChannel();
  const port = channel.port2;
  channel.port1.onmessage = performWorkUntilDeadline;
  schedulePerformWorkUntilDeadline = () =&gt; {
    port.postMessage(null);
  };
} else {
  // We should only fallback here in non-browser environments.
  schedulePerformWorkUntilDeadline = () =&gt; {
    localSetTimeout(performWorkUntilDeadline, 0);
  };
}</code></pre>
<h2 id="23-priority-queue">2.3 Priority Queue</h2>
<p>우선순위 큐는 스케줄링을 위한 일반적인 데이터 구조다.</p>
<p>우선순위가 다른 이벤트가 큐에 들어오기 때문에, 처리할 우선순위가 가장 높은 이벤트를 빠르게 찾아야 하는 React의 요구 사항에 완벽하게 일치한다.</p>
<p>React는 Min Heap으로 우선순위 큐를 구현한다. (<a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/SchedulerMinHeap.js">SchedulerMinHeap.js</a>)</p>
<h1 id="3-call-stack-of-workloopconcurrent">3. Call stack of workLoopConcurrent</h1>
<p><code>workLoopConcurrent()</code> 가 어떻게 호출되는지 확인해보자. Cf. <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberWorkLoop.js">ReactFiberWorkLoop.js</a></p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/hee-suh/post/6cb7c0c5-4a80-4b40-858b-8672999694df/image.png" alt="workLoopConcurrent()"></th>
</tr>
</thead>
<tbody><tr>
<td>출처: <a href="https://jser.dev/react/2022/03/16/how-react-scheduler-works#3-call-stack-of-workloopconcurrent">https://jser.dev/react/2022/03/16/how-react-scheduler-works#3-call-stack-of-workloopconcurrent</a></td>
</tr>
</tbody></table>
<p><a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberRootScheduler.js#L86"><code>ensureRootIsScheduled()</code></a>는 업데이트가 있는 경우 React가 업데이트를 수행하도록 task를 예약한다. <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberWorkLoop.js#L849"><code>performConcurrentWorkOnRoot()</code></a>를 직접 호출하지 않고, <code>scheduleCallback(priority, callback)</code>의 콜백으로 처리한다는 점을 유의하자. <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js#L322"><code>scheduleCallback()</code></a>은 스케줄러의 API다. 일단 스케줄러가 적절한 시점에 task를 실행한다는 점만 명심하고 뒤에서 자세히 살펴보자.</p>
<h2 id="31-performconcurrentworkonroot-returns-a-closure-of-itself-if-interrupted"><strong>3.1 <code>performConcurrentWorkOnRoot()</code> returns a closure of itself if interrupted.</strong></h2>
<p><code>performConcurrentWorkOnRoot()</code>는 진행 상황에 따라 다른 값을 리턴한다.</p>
<ol>
<li><code>shouldYield()</code>가 true인 경우, <code>workLoopConcurrent()</code>가 중단되어, 불완전한 <code>update(RootInComplete)</code>가 발생하고, <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberWorkLoop.js#L849"><code>performConcurrentWorkOnRoot()</code></a> 는 클로저(<code>performConcurrentWorkOnRoot.bind(null, root)</code>)를 리턴한다.</li>
<li>만약 work loop가 완료되면, null을 리턴한다.</li>
</ol>
<p>task의 중단(interrupt)은 <code>shouldYield()</code>에 의해 발생하며, 재개(resume)는 스케줄러가 task 콜백의 리턴 값을 살펴보며 계속 진행해야 하는 task가 있다면 수행한다.</p>
<h1 id="4-scheduler">4. <strong>Scheduler</strong></h1>
<p>스케줄러가 어떻게 작동하는지 살펴보자. (<a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js">Scheduler.js</a>)</p>
<p>위에서 언급한 <code>scheduleCallback()</code>은 <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js#L322">unstable_scheduleCallback</a>이다.</p>
<p><em>Cf. 오픈소스에서 ‘unstable’로 표시된 기능이나 버전은 아직 완전하게 안정화되지 않았고, 앞으로 변경될 가능성이 크다는 것을 나타낸다.</em></p>
<h2 id="41-schedulecallback---scheduler-schedules-tasks-by-expirationtime"><strong>4.1 scheduleCallback() - Scheduler schedules tasks by expirationTime</strong></h2>
<p>스케줄러가 tasks를 예약하기 위해, 우선순위 큐를 이용하여 task를 우선순위와 함께 저장해야 한다.</p>
<p><code>expirationTime</code>을 이용하여 우선순위를 나타낸다. <strong>만료가 빠르면, 더 빨리 처리</strong>해야 하므로, 공평한 방법이다. 다음은 task가 생성되는 <code>scheduleCallback()</code> 내부 코드다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js#L322">unstable_scheduleCallback()</a></p>
<pre><code class="language-ts">var currentTime = getCurrentTime();

var startTime;

...

var timeout;
switch (priorityLevel) {
  case ImmediatePriority:
    // Times out immediately
    timeout = -1;
    break;
  case UserBlockingPriority:
    // Eventually times out
    timeout = userBlockingPriorityTimeout;
    break;
  case IdlePriority:
    // Never times out
    timeout = maxSigned31BitInt;
    break;
  case LowPriority:
    // Eventually times out
    timeout = lowPriorityTimeout;
    break;
  case NormalPriority:
  default:
    // Eventually times out
    timeout = normalPriorityTimeout;
    break;
}

var expirationTime = startTime + timeout;

// 📌 task는 스케줄러가 처리하는 작업(work)의 단위다.
var newTask: Task = {
  id: taskIdCounter++,
  callback,
  priorityLevel,
  startTime,
  expirationTime,
  sortIndex: -1,
};

...

return newTask;</code></pre>
<p>각 우선순위마다 다른 timeout을 갖고 있다. </p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/SchedulerFeatureFlags.js">SchedulerFeatureFlags.js</a></p>
<pre><code class="language-ts">export const frameYieldMs = 5;

export const userBlockingPriorityTimeout = 250;
// 📌 Default는 5초 timeout이다.
export const normalPriorityTimeout = 5000;
export const lowPriorityTimeout = 10000;</code></pre>
<p>💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js">Scheduler.js</a></p>
<pre><code class="language-ts">// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
var maxSigned31BitInt = 1073741823;</code></pre>
<p>default timeout은 5초이며, user blocking의 경우 250ms다. </p>
<p>task가 생성되면, 우선순위 큐에 넣어준다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js#L488">performWorkUntilDeadline()</a></p>
<pre><code class="language-ts">if (startTime &gt; currentTime) {
  // This is a delayed task.
  newTask.sortIndex = startTime;
  push(timerQueue, newTask);
  if (peek(taskQueue) === null &amp;&amp; newTask === peek(timerQueue)) {
    // All tasks are delayed, and this is the task with the earliest delay.
    if (isHostTimeoutScheduled) {
      // Cancel an existing timeout.
      cancelHostTimeout();
    } else {
      isHostTimeoutScheduled = true;
    }
    // Schedule a timeout.
    requestHostTimeout(handleTimeout, startTime - currentTime);
  }
} else {
  newTask.sortIndex = expirationTime;
  push(taskQueue, newTask);
  if (enableProfiling) {
    markTaskStart(newTask, currentTime);
    newTask.isQueued = true;
  }
  // Schedule a host callback, if needed. If we&#39;re already performing work,
  // wait until the next time we yield.
  if (!isHostCallbackScheduled &amp;&amp; !isPerformingWork) {
    isHostCallbackScheduled = true;
    requestHostCallback();
  }
}</code></pre>
<p>task를 예약할 때, <code>setTimeout()</code>처럼 delay 옵션을 가질 수 있다.</p>
<p><code>else</code> 분기에 있는 두 개의 중요한 호출을 보자.</p>
<ol>
<li><code>push(taskQueue, newTask)</code> — task를 큐에 추가한다.</li>
<li><code>requestHostCallback()</code> — task들을 처리한다!</li>
</ol>
<p>스케줄러가 host(e.g. 브라우저, Node.js)를 알 수 없기 때문에, 어떤 host에서도 실행될 수 있도록 독립적인 black box가 되어야 한다. 따라서 <code>requestHostCallback()</code>에서 host에게 요청을 해서,  <code>schedulePerformWorkUntilDeadline()</code>을 통해 Event Loop의 macrotask로 <code>flushWork()</code>를 넘긴다.</p>
<h2 id="42-requesthostcallback"><strong>4.2 <code>requestHostCallback()</code></strong></h2>
<pre><code class="language-ts">function requestHostCallback() {
  // 💬 상호 배제를 위한 변수인 것 같다.
  if (!isMessageLoopRunning) {
    isMessageLoopRunning = true;
    schedulePerformWorkUntilDeadline();
  }
}</code></pre>
<pre><code class="language-ts">const performWorkUntilDeadline = () =&gt; {
  if (isMessageLoopRunning) {
    const currentTime = getCurrentTime();
    // Keep track of the start time so we can measure how long the main thread
    // has been blocked.
    startTime = currentTime;

    // If a scheduler task throws, exit the current browser task so the
    // error can be observed.
    //
    // Intentionally not using a try-catch, since that makes some debugging
    // techniques harder. Instead, if `flushWork` errors, then `hasMoreWork` will
    // remain true, and we&#39;ll continue the work loop.
    let hasMoreWork = true;
    try {
      hasMoreWork = flushWork(currentTime);
    } finally {
      if (hasMoreWork) {
        // If there&#39;s more work, schedule the next message event at the end
        // of the preceding one.
        // 📌 스케줄러가 큐에 있는 task들을 예약해서 처리한다는 것을 알 수 있다.
        // 📌 여기에서 브라우저에게 페인트할 기회를 준다.
        schedulePerformWorkUntilDeadline();
      } else {
        isMessageLoopRunning = false;
      }
    }
  }
};</code></pre>
<p><code>schedulePerformWorkUntilDeadline()</code>은 <code>performWorkUntilDeadline()</code>의 wrapper일 뿐이다.</p>
<p><code>performWorkUntilDeadline()</code>에서 <code>flushWork()</code>가 바로 실행되고, 비동기적으로 진행되는 스케줄링에 따라 메인 스레드가 렌더할 수 있는 기회를 준다.</p>
<h2 id="43-flushwork">4.3 <code>flushWork()</code></h2>
<pre><code class="language-ts">function flushWork(initialTime: number) {
    try {
    // No catch in prod code path.
    return workLoop(initialTime);
  } finally {
      ...
  }
}</code></pre>
<p><code>flushWork()</code>는 <code>workLoop()</code>을 감싸고 있을 뿐이다.</p>
<h2 id="44-workloop---the-core-of-scheduler"><strong>4.4 <code>workLoop()</code> - the core of Scheduler</strong></h2>
<p><code>workLoopConcurrent()</code>은 재조정(reconciliation)을 위한 것이라면, <code>workLoop()</code>은 스케줄러의 핵심부다. 둘은 비슷한 일을 하기 때문에 비슷한 이름을 갖고 있다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js#L188">workLoop()</a></p>
<pre><code class="language-ts">if (currentTask.expirationTime &gt; currentTime &amp;&amp; shouldYieldToHost()) {
  // This currentTask hasn&#39;t expired, and we&#39;ve reached the deadline.
  break;
}</code></pre>
<p><code>workLoopConcurrent()</code>처럼, <code>shouldYieldToHost()</code>가 여기에서 체크된다.</p>
<pre><code class="language-ts">const callback = currentTask.callback;
if (typeof callback === &#39;function&#39;) {
  currentTask.callback = null;
  currentPriorityLevel = currentTask.priorityLevel;
  const didUserCallbackTimeout = currentTask.expirationTime &lt;= currentTime;
  const continuationCallback = callback(didUserCallbackTimeout);
  currentTime = getCurrentTime();
  // 📌 tasks의 리턴 값이 왜 중요한지 알 수 있다.
  // 이 분기에서는 task가 pop되지 않는다!
  if (typeof continuationCallback === &#39;function&#39;) {
    // If a continuation is returned, immediately yield to the main thread
    // regardless of how much time is left in the current time slice.
    currentTask.callback = continuationCallback;
    if (enableProfiling) {
      markTaskYield(currentTask, currentTime);
    }
    advanceTimers(currentTime);
    return true;
  } else {
    if (currentTask === peek(taskQueue)) {
      pop(taskQueue);
    }
    advanceTimers(currentTime);
  }
} else {
  pop(taskQueue);
}
    currentTask = peek(taskQueue);
}</code></pre>
<p><code>currentTask.callback</code>은 <code>performConcurrentWorkOnRoot()</code>다.</p>
<p>리렌더링이 필요할 때, <code>performConcurrentWorkOnRoot()</code>를 task로 등록하여 실행한다. 이때 이 함수를 바로 실행하는 것이 아니라, <code>scheduleCallback()</code>이라는 스케줄러 함수를 통해 실행을 예약한다. 이 함수는 <code>performConcurrentWorkOnRoot()</code>를 macrotask로 등록하고, call stack이 비워지면 Event Loop를 거쳐 실행된다.</p>
<pre><code class="language-ts">const didUserCallbackTimeout = currentTask.expirationTime &lt;= currentTime;
const continuationCallback = callback(didUserCallbackTimeout);</code></pre>
<p>콜백이 만료되었는지 여부를 나타내는 flag와 함께 호출된다.</p>
<p><code>performConcurrentWorkOnRoot()</code>는 timeout이 되면, sync 모드로 작동한다. 이제부터는 어떠한 중단(interruption)도 없다는 것을 의미한다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/react-reconciler/src/ReactFiberWorkLoop.js#L849">performConcurrentWorkOnRoot()</a></p>
<pre><code class="language-ts">const shouldTimeSlice =
  !includesBlockingLane(root, lanes) &amp;&amp;
  !includesExpiredLane(root, lanes) &amp;&amp;
  (disableSchedulerTimeoutInWorkLoop || !didTimeout);
let exitStatus = shouldTimeSlice
  ? renderRootConcurrent(root, lanes)
  : renderRootSync(root, lanes);</code></pre>
<p>다시 <code>workLoop()</code>로 돌아오자.</p>
<pre><code class="language-ts">if (typeof continuationCallback === &#39;function&#39;) {
  // If a continuation is returned, immediately yield to the main thread
  // regardless of how much time is left in the current time slice.
  currentTask.callback = continuationCallback;
  advanceTimers(currentTime);
  return true;
} else {
  if (currentTask === peek(taskQueue)) {
    pop(taskQueue);
  }
  advanceTimers(currentTime);
}</code></pre>
<p><strong>task는 콜백의 리턴 값이 함수가 아닌 경우에만 pop</strong>된다. 만약 함수라면, task의 콜백을 업데이트하기만 하고, pop되지 않았으므로, <code>workLoop()</code>의 다음 tick은 같은 task를 다시 수행한다.</p>
<p><strong>콜백의 리턴 값이 함수라면, task가 완료되지 않았고, 작업을 재개해야 한다</strong>는 것을 의미한다.</p>
<pre><code class="language-ts">advanceTimers(currentTime);</code></pre>
<p><code>advanceTimers()</code>는 지연된 task를 위한 것이다.</p>
<h2 id="45-how-shouldyield-work"><strong>4.5 how <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/forks/Scheduler.js#L458"><code>shouldYield()</code></a> work?</strong></h2>
<pre><code class="language-flow">let frameInterval = frameYieldMs;

function shouldYieldToHost(): boolean {
  const timeElapsed = getCurrentTime() - startTime;
  if (timeElapsed &lt; frameInterval) {
    // The main thread has only been blocked for a really short amount of time;
    // smaller than a single frame. Don&#39;t yield yet.
    return false;
  }
  // Yield now.
  return true;
}</code></pre>
<p>각 task에는 5ms(frameInterval)가 주어지고, 시간이 다 되면 양보해야 한다. 
<em>Cf. <a href="https://github.com/facebook/react/blob/7608516479fb85bc40aa73d8a31a0c93397ee6ff/packages/scheduler/src/SchedulerFeatureFlags.js">SchedulerFeatureFlags.js</a>에서 frameYieldMs는 5로 정의되어있다.</em></p>
<p>스케줄러가 실행하는 <code>task</code>를 말하는 것이며, 각 <code>performUnitOfWork()</code>를 의미하는 것이 아니라는 점을 유의하자. <code>startTime</code>이 <code>performWorkUntilDeadline()</code>에서만 설정되는 것을 보았을 때, <code>startTime</code>은 각 <code>flushWork()</code>마다 초기화되며, <strong><code>flushWork()</code>에서 여러 task들을 처리할 수 있다면, task 간 양보가 발생하지 않는다</strong>.</p>
<h1 id="5-summary">5. Summary</h1>
<table>
<thead>
<tr>
<th><img src="https://jser.dev/static/scheduler-2.png" alt="출처: https://jser.dev/react/2022/03/16/how-react-scheduler-works#5-summary"></th>
</tr>
</thead>
<tbody><tr>
<td>출처: <a href="https://jser.dev/react/2022/03/16/how-react-scheduler-works#5-summary">https://jser.dev/react/2022/03/16/how-react-scheduler-works#5-summary</a></td>
</tr>
</tbody></table>
<p>React Concurrent Feature의 핵심은 task를 수행하다가 우선순위가 더 높은 task가 들어오면 작업을 일시중지하고, 우선순위가 더 높은 task를 수행한 후, 중지되었던 task를 재개하는 것이다.</p>
<p>task의 우선순위는 <code>expirationTime</code>으로 정하고, Min Heap으로 구현한 우선순위 큐를 이용해 스케줄링한다.</p>
<p>각 task의 callback에는 재조정(reconciliation) 함수인 <code>performConcurrentWorkOnRoot()</code>가 할당된다. 해당 함수는 <code>workLoopConcurrent()</code>를 실행하면서 렌더 작업을 수행하다가 <code>shouldYield()</code> 에 의해 중단(interrupt)되면 일시중지(suspend)되는데, 이때 클로저를 리턴해서 상태를 기억하며 TaskQueue에서 대기하다가, 순서가 오면 실행을 마치고 null을 리턴하여 재조정을 마치고 pop된다.</p>
<h1 id="references">References</h1>
<p><a href="https://jser.dev/react/2022/03/16/how-react-scheduler-works/#5-summary">[JSer.dev] How React Scheduler works internally?</a></p>
<p><a href="https://jser.dev/react/2022/03/26/lanes-in-react/#2-what-is-lane-">[JSer.dev] What are Lanes in React source code?</a></p>
<p><a href="https://youtu.be/cCOL7MC4Pl0?si=idZUY9Mwctvtyz66">[JSConf] Jake Archibald on the web browser event loop, setTimeout, micro tasks, requestAnimationFrame, ...</a></p>
<p><a href="https://javascript.info/event-loop">[JAVASCRIPT.INFO] Event loop: microtasks and macrotasks</a></p>
<p><a href="https://sckimynwa.medium.com/concept-of-react-scheduler-2c887cbfe5a8">[Yeoul Kim] Concept of React Scheduler</a></p>
<p><a href="https://3perf.com/talks/react-concurrency/">[Ivan Akulov] React Concurrency, Explained: What useTransition and Suspense Hydration Actually Do</a></p>
<p><a href="https://www.lydiahallie.com/blog/event-loop">[lydiahallie] JavaScript Visualized: 
Event Loop, Web APIs, (Micro)task Queue</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useState()]]></title>
            <link>https://velog.io/@hee-suh/useState</link>
            <guid>https://velog.io/@hee-suh/useState</guid>
            <pubDate>Mon, 24 Jun 2024 13:51:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://jser.dev/">JSer.dev</a>의 <a href="https://jser.dev/series/react-source-code-walkthrough">React Internals Deep Dive</a>를 번역하여 정리한 글입니다.</p>
</blockquote>
<blockquote>
<p>⚠️ <a href="https://react.dev/blog/2024/04/25/react-19">React@19</a>의 <a href="https://github.com/facebook/react/commit/7608516479fb85bc40aa73d8a31a0c93397ee6ff">commit 7608516</a>을 기반으로 작성되었으며, 최신 버전에서는 구현이 변경되었을 수 있습니다.</p>
</blockquote>
<h4 id="📝-how-does-usestate-work-internally-in-react">📝 <a href="https://jser.dev/2023-06-19-how-does-usestate-work">How does useState() work internally in React?</a></h4>
<p><a href="https://react.dev/reference/react/useState"><code>useState</code></a> 는 컴포넌트에 <a href="https://react.dev/learn/state-a-components-memory">state variable</a>을 추가할 수 있게 하는 React Hook이다. 소스 코드를 살펴보며 <code>useState()</code>의 동작 원리를 알아보자.</p>
<pre><code class="language-tsx">const [state, setState] = useState(initialState)</code></pre>
<h1 id="1-usestate-in-initial-rendermount">1. <code>useState()</code> in initial render(mount)</h1>
<p>💻 src: <a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js">ReactFiberHooks.js</a></p>
<pre><code class="language-typescript">function mountState&lt;S&gt;(
  initialState: (() =&gt; S) | S,
): [S, Dispatch&lt;BasicStateAction&lt;S&gt;&gt;] {
  // 📌 새로운 hook이 생성된다.
  const hook = mountStateImpl(initialState);
  // 📌 이 queue는 hook을 위한 update queue임을 기억하자.
  const queue = hook.queue;

  // 📌 state setter의 구현체가 바로 dispatchSetState()다. 
  // current fiber에 바인딩되어있음을 확인하자.
  const dispatch: Dispatch&lt;BasicStateAction&lt;S&gt;&gt; = (dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any);
  queue.dispatch = dispatch;

  // 📌 다음은 useState()로부터 얻을 수 있는 익숙한 syntax다. 
  return [hook.memoizedState, dispatch];
}</code></pre>
<pre><code class="language-typescript">function mountStateImpl&lt;S&gt;(initialState: (() =&gt; S) | S): Hook {

  const hook = mountWorkInProgressHook();
  if (typeof initialState === &#39;function&#39;) {
    const initialStateInitializer = initialState;
    initialState = initialStateInitializer();
  }

  // 📌 hook의 memoizedState는 실제 state 값을 저장한다.
  hook.memoizedState = hook.baseState = initialState;

  // 📌 Update queue는 미래의 state 업데이트를 저장할 곳이다.
  // state를 설정(set)할 때, state 값이 바로 업데이트되지 않는 점을 기억하자.
  // 왜냐하면, 업데이트가 다른 우선순위를 가질 수 있기 때문에
  // state 업데이트가 바로 처리될 필요가 없을 수 있다. (Cf. [What are Lanes in React](https://jser.dev/react/2022/03/26/lanes-in-react/))
  // 그러므로 업데이트를 stash해뒀다가, 나중에 처리해야 한다.
  const queue: UpdateQueue&lt;S, BasicStateAction&lt;S&gt;&gt; = {
    pending: null,
    lanes: NoLanes, // 📌 lanes는 우선순위다.
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;

  return hook;
}</code></pre>
<h1 id="2-what-happens-in-setstate">2. What happens in <code>setState()</code>?</h1>
<p>상단의 코드를 보면, <code>setState()</code>는 바인딩된 <code>dispatchsetState()</code>다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js">ReactFiberHooks.js</a></p>
<pre><code class="language-typescript">function dispatchSetState&lt;S, A&gt;(
  fiber: Fiber,
  queue: UpdateQueue&lt;S, A&gt;,
  action: A,
): void {
  // 📌 lane은 업데이트의 우선순위를 정의한다.
  const lane = requestUpdateLane(fiber);

  // 📌 stash될 업데이트 객체
  const update: Update&lt;S, A&gt; = {
    lane,
    revertLane: NoLane,
    action,
    hasEagerState: false,
    eagerState: null,
    next: (null: any),
  };

  // 📌 render 중에 setState를 할 수 있다.
  // Cf. https://react.dev/reference/react/useState#storing-information-from-previous-renders
  // 이 패턴은 유용하지만, 무한 렌더링을 일으킬 수 있으므로 조심하자.
  if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    const alternate = fiber.alternate;
    // 📌 early bailout을 위한 조건문이다.
    // 이는 같은 state를 set하고 있다면 아무것도 하지 않는 것을 의미한다.
    // - Bailout은 더 이상 깊이 들어가지 않고, subtree의 리렌더링을 건너뛰는 것을 의미하며, 리렌더링 내부에서 발생한다.
    // - 반면에 early bailout은 리렌더링 스케줄링을 방지한다.
    // 하지만 이 조건문에는 더 엄격한 규칙이 필요한데, 이 조건문으로는
    // React가 리렌더링 스케줄링을 방지하려고 최대한 노력하는 것뿐이지, 보장하지는 않기 때문이다.
    // caveat section에서 더 자세히 살펴보자.
    if (
      fiber.lanes === NoLanes &amp;&amp;
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      // The queue is currently empty, which means we can eagerly compute the
      // next state before entering the render phase. If the new state is the
      // same as the current state, we may be able to bail out entirely.
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher = null;
        try {
          const currentState: S = (queue.lastRenderedState: any);
          const eagerState = lastRenderedReducer(currentState, action);
          // Stash the eagerly computed state, and the reducer used to compute
          // it, on the update object. If the reducer hasn&#39;t changed by the
          // time we enter the render phase, then the eager state can be used
          // without calling the reducer again.
          update.hasEagerState = true;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
            // Fast path. We can bail out without scheduling React to re-render.
            // It&#39;s still possible that we&#39;ll need to rebase this update later,
            // if the component re-renders for a different reason and by that
            // time the reducer has changed.
            enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
            // 📌 이 리턴은 업데이트가 스케줄링되는 것을 방지한다.
            return;
          }
        } catch (error) {
          // Suppress the error. It will throw again in the render phase.
        }
      }
    }

    // 📌 enqueueConcurrentHookUpdate는 업데이트들을 stash한다.
    // 업데이틀은 실제 리렌더링이 시작될 때, 처리되어 fiber에 attach된다.
    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      // 📌 scheduleUpdateOnFiber는 리렌더링을 예약한다. (리렌더링은 즉시 일어나지 않는다)
      // 실제 스케줄링은 React Scheduler에서 처리한다.
      // Cf. https://jser.dev/react/2022/03/16/how-react-scheduler-works/
      scheduleUpdateOnFiber(root, fiber, lane);
      entangleTransitionUpdate(root, queue, lane);
    }
  }
}</code></pre>
<blockquote>
<h4 id="💡-early-bailout-vs-bailout">💡 Early bailout <em>vs</em> Bailout</h4>
</blockquote>
<ul>
<li>early bailout<br>  같은 state를 set하고 있다면 아무것도 하지 않는 것을 의미한다.
  ⇒ 리렌더링 스케줄링을 방지한다.</li>
<li>bailout
  더 이상 깊이 들어가지 않고 subtree의 리렌더링을 건너뛰는 것을 의미하며, 리렌더링 내부에서 발생한다.</li>
</ul>
<p>업데이트 객체가 어떻게 처리되는지 더 자세히 살펴보자.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js">ReactFiberConcurrentUpdates.js</a></p>
<pre><code class="language-typescript">// If a render is in progress, and we receive an update from a concurrent event,
// we wait until the current render is over (either finished or interrupted)
// before adding it to the fiber/hook queue.
// Push to this array so we can access the queue, fiber, update, et al later.
const concurrentQueues: Array&lt;any&gt; = [];
let concurrentQueuesIndex = 0;

let concurrentlyUpdatedLanes: Lanes = NoLanes;

// 📌 이 함수는 prepareFreshStack()에서 호출되며, re-render의 첫 번째 과정 중 하나다.
// Cf. https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberWorkLoop.js
// 리렌더링이 시작되기 전에, 모든 state 업데이트들이 stash 되어있음을 의미한다.
export function finishQueueingConcurrentUpdates(): void {
  const endIndex = concurrentQueuesIndex;
  concurrentQueuesIndex = 0;

  concurrentlyUpdatedLanes = NoLanes;

  let i = 0;
  while (i &lt; endIndex) {
    const fiber: Fiber = concurrentQueues[i];
    concurrentQueues[i++] = null;
    const queue: ConcurrentQueue = concurrentQueues[i];
    concurrentQueues[i++] = null;
    const update: ConcurrentUpdate = concurrentQueues[i];
    concurrentQueues[i++] = null;
    const lane: Lane = concurrentQueues[i];
    concurrentQueues[i++] = null;

    if (queue !== null &amp;&amp; update !== null) {
      const pending = queue.pending;
      if (pending === null) {
        // This is the first update. Create a circular list.
        update.next = update;
      } else {
        update.next = pending.next;
        pending.next = update;
      }
      // 📌 hook.queue에 stash 되었던 업데이트들이
      // 드디어 여기에서 fiber에 attach된다.
      // 업데이트가 처리될 준비가 되었음을 의미한다.
      queue.pending = update;
    }

    if (lane !== NoLane) {
      // 📌 이 함수 호출은 fiber node 경로를 dirty로 표시한다.
      // Cf. https://jser.dev/react/2022/01/07/how-does-bailout-work/
      markUpdateLaneFromFiberToRoot(fiber, update, lane);
    }
  }
}</code></pre>
<pre><code class="language-typescript">function enqueueUpdate(
  fiber: Fiber,
  queue: ConcurrentQueue | null,
  update: ConcurrentUpdate | null,
  lane: Lane,
) {
  // Don&#39;t update the `childLanes` on the return path yet. If we already in
  // the middle of rendering, wait until after it has completed.
  // 📌 내부적으로 업데이트들은 리스트에 저장된다.
  // batch로 처리되는 message queue처럼 말이다.
  concurrentQueues[concurrentQueuesIndex++] = fiber;
  concurrentQueues[concurrentQueuesIndex++] = queue;
  concurrentQueues[concurrentQueuesIndex++] = update;
  concurrentQueues[concurrentQueuesIndex++] = lane;

  concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);

  // The fiber&#39;s `lane` field is used in some places to check if any work is
  // scheduled, to perform an eager bailout, so we need to update it immediately.
  fiber.lanes = mergeLanes(fiber.lanes, lane);
  // 📌 current와 alternate fibers 모두 dirty 표시되는 것을 볼 수 있다.
  // 이것은 caveat을 이해하기 위해 중요하다.
  const alternate = fiber.alternate;
  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
  }
}

export function enqueueConcurrentHookUpdate&lt;S, A&gt;(
  fiber: Fiber,
  queue: HookQueue&lt;S, A&gt;,
  update: HookUpdate&lt;S, A&gt;,
  lane: Lane,
): FiberRoot | null {
  const concurrentQueue: ConcurrentQueue = (queue: any);
  const concurrentUpdate: ConcurrentUpdate = (update: any);
  enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
  return getRootForUpdatedFiber(fiber);
}</code></pre>
<pre><code class="language-typescript">function markUpdateLaneFromFiberToRoot(
  sourceFiber: Fiber,
  update: ConcurrentUpdate | null,
  lane: Lane,
): void {
  // Update the source fiber&#39;s lanes
  sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
  let alternate = sourceFiber.alternate;
  // 📌 current fiber와 alternate fiber 모두 lanes가 업데이트된다.
  // dispatchSetState()가 source fiber에 바인딩되어있으므로,
  // state를 set할 때, 항상 current fiber 트리를 업데이트하지는 않을 수 있다.
  // 둘 다 set하면 정상 작동하게끔 보장은 할 수 있지만, 부작용이 있다.
  // caveat section에서 이 부분을 다시 다루자.
  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
  }
  // Walk the parent path to the root and update the child lanes.
  // 📌 Cf. https://jser.dev/react/2022/01/07/how-does-bailout-work/
  let isHidden = false;
  let parent = sourceFiber.return;
  let node = sourceFiber;
  while (parent !== null) {
    parent.childLanes = mergeLanes(parent.childLanes, lane);
    alternate = parent.alternate;
    if (alternate !== null) {
      alternate.childLanes = mergeLanes(alternate.childLanes, lane);
    }

    if (parent.tag === OffscreenComponent) {
      const offscreenInstance: OffscreenInstance | null = parent.stateNode;
      if (
        offscreenInstance !== null &amp;&amp;
        !(offscreenInstance._visibility &amp; OffscreenVisible)
      ) {
        isHidden = true;
      }
    }

    node = parent;
    parent = parent.return;
  }

  if (isHidden &amp;&amp; update !== null &amp;&amp; node.tag === HostRoot) {
    const root: FiberRoot = node.stateNode;
    markHiddenUpdate(root, update, lane);
  }
}</code></pre>
<p>💻 src: <a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberWorkLoop.js">ReactFiberWorkLoop.js</a></p>
<pre><code class="language-typescript">export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
) {
  ...
  // 📌 pending 업데이트가 있는 경우, re-render가 예약되었는지 확인한다.
  // 리렌더링이 아직 시작되지 않았으므로, 업데이트는 아직 처리되지 않았다.
  // 리렌더링의 시작은 이벤트의 종류와 스케줄러의 상태와 같은 몇 가지 요소에 따라 달라진다.
  // 더 자세한 내용은 다음 글에서 확인하자. https://jser.dev/2023-05-19-how-does-usetransition-work/#31-use-case-1---marking-a-state-update-as-a-non-blocking-transition
  ensureRootIsScheduled(root);
  ...
}</code></pre>
<h1 id="3-usestate-in-re-render">3. useState() in re-render</h1>
<p>업데이트가 stash 되었다면, 업데이트를 실행시켜서 state 값을 업데이트해야 한다.</p>
<p>이는 리렌더링의 <code>useState()</code>에서 일어난다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js">ReactFiberHooks.js</a></p>
<pre><code class="language-typescript">function updateState&lt;S&gt;(
  initialState: (() =&gt; S) | S,
): [S, Dispatch&lt;BasicStateAction&lt;S&gt;&gt;] {
  return updateReducer(basicStateReducer, initialState);
}

function updateReducer&lt;S, I, A&gt;(
  reducer: (S, A) =&gt; S,
  initialArg: I,
  init?: I =&gt; S,
): [S, Dispatch&lt;A&gt;] {
  // 📌 이전에 생성되었던 hook으로부터 값을 얻는다.
  const hook = updateWorkInProgressHook();
  return updateReducerImpl(hook, ((currentHook: any): Hook), reducer);
}

function updateReducerImpl&lt;S, A&gt;(
  hook: Hook,
  current: Hook,
  reducer: (S, A) =&gt; S,
): [S, Dispatch&lt;A&gt;] {
  // 📌 모든 업데이트가 저장되어 있는 update queue다.
  // useState()가 리렌더링이 시작된 후에 호출되었으므로, stash 된 업데이트는 fibers로 이동했다.
  const queue = hook.queue;
  ...
  queue.lastRenderedReducer = reducer;

  // The last rebase update that is NOT part of the base state.
  // 📌 가장 좋은 것은, 업데이트를 처리하고 바로 버리는 것이다.
  // 하지만 여러 업데이트가 서로 다른 우선순위를 가질 수 있으므로,
  // 어떤 업데이트는 나중에 처리되도록 건너뛰어야 하기 때문에, 업데이트들이 baseQueue에 저장된다.
  // 처리된 업데이트더라도, 최종 state가 올바른지 확인하기 위해서
  // 한 업데이트가 baseQueue에 들어가면, 그 뒤에 있는 모든 업데이트들도 함께 저장된다.
  // e.g. state 값이 1이고, 세 번의 업데이트가 있다고 하자. +1(low), *10(high), -2(low)
  // *10이 높은 우선순위를 갖기 때문에, 먼저 처리한다; 1 * 10 = 10
  // 그 다음에 낮은 우선순위를 가진 업데이트들을 처리한다.
  // *10을 큐에 넣지 않았다면, 1 + 1 - 2 = 0이 되겠지만,
  // 낮은 우선순위의 +1부터 모두 큐에 들어가므로, (1 + 1) * 10 - 2로 계산된다.
  let baseQueue = hook.baseQueue;

  // The last pending update that hasn&#39;t been processed yet.
  const pendingQueue = queue.pending;
  if (pendingQueue !== null) {
    // We have new updates that haven&#39;t been processed yet.
    // We&#39;ll add them to the base queue.
    if (baseQueue !== null) {
      // Merge the pending queue and the base queue.
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }
    current.baseQueue = baseQueue = pendingQueue;
    // 📌 pending queue가 지워지고, baseQueue에 merge된다.
    queue.pending = null;
  }

  const baseState = hook.baseState;
  if (baseQueue === null) {
    // If there are no pending updates, then the memoized state should be the
    // same as the base state. Currently these only diverge in the case of useOptimistic.
    hook.memoizedState = baseState;
  } else {
    // We have a queue to process.
    const first = baseQueue.next;
    let newState = baseState;

    let newBaseState = null;
    // 📌 baseQueue를 처리한 이후에, 새로운 baseQueue가 생성된다.
    let newBaseQueueFirst = null;
    let newBaseQueueLast: Update&lt;S, A&gt; | null = null;
    let update = first;
    let didReadFromEntangledAsyncAction = false;
    do {
      ...
      // Check if this update was made while the tree was hidden. If so, then
      // it&#39;s not a &quot;base&quot; update and we should disregard the extra base lanes
      // that were added to renderLanes when we entered the Offscreen tree.
      const shouldSkipUpdate = isHiddenUpdate
        ? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
        : !isSubsetOfLanes(renderLanes, updateLane);

      if (shouldSkipUpdate) {
        // 📌 우선순위가 낮은 업데이트
        // Priority is insufficient. Skip this update. If this is the first
        // skipped update, the previous update/state is the new base
        // update/state.
        const clone: Update&lt;S, A&gt; = {
          lane: updateLane,
          revertLane: update.revertLane,
          action: update.action,
          hasEagerState: update.hasEagerState,
          eagerState: update.eagerState,
          next: (null: any),
        };
        // 📌 업데이트가 처리되지 않았으므로, 새로운 baseQueue에 들어간다.
        if (newBaseQueueLast === null) {
          newBaseQueueFirst = newBaseQueueLast = clone;
          newBaseState = newState;
        } else {
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }
        // Update the remaining priority in the queue.
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        markSkippedUpdateLanes(updateLane);
      } else {
        // This update does have sufficient priority.

        // Check if this is an optimistic update.
        const revertLane = update.revertLane;
        if (!enableAsyncActions || revertLane === NoLane) {
          // This is not an optimistic update, and we&#39;re going to apply it now.
          // But, if there were earlier updates that were skipped, we need to
          // leave this update in the queue so it can be rebased later.

          // 📌 newBaseQueue가 비어있지 않으므로,
          // 그 다음에 오는 업데이트들은 나중에 사용하기 위해 모두 stash 된다.
          if (newBaseQueueLast !== null) {
            const clone: Update&lt;S, A&gt; = {
              // This update is going to be committed so we never want uncommit
              // it. Using NoLane works because 0 is a subset of all bitmasks, so
              // this will never be skipped by the check above.
              lane: NoLane,
              revertLane: NoLane,
              action: update.action,
              hasEagerState: update.hasEagerState,
              eagerState: update.eagerState,
              next: (null: any),
            };
            newBaseQueueLast = newBaseQueueLast.next = clone;
          }

          // Check if this update is part of a pending async action. If so,
          // we&#39;ll need to suspend until the action has finished, so that it&#39;s
          // batched together with future updates in the same action.
          if (updateLane === peekEntangledActionLane()) {
            didReadFromEntangledAsyncAction = true;
          }
        } else {
          // This is an optimistic update. If the &quot;revert&quot; priority is
          // sufficient, don&#39;t apply the update. Otherwise, apply the update,
          // but leave it in the queue so it can be either reverted or
          // rebased in a subsequent render.
          ...
        }

        // Process this update.
        const action = update.action;
        if (shouldDoubleInvokeUserFnsInHooksDEV) {
          reducer(newState, action);
        }
        if (update.hasEagerState) {
          // If this update is a state update (not a reducer) and was processed eagerly,
          // we can use the eagerly computed state
          newState = ((update.eagerState: any): S);
        } else {
          newState = reducer(newState, action);
        }
      }
      update = update.next;
    } while (update !== null &amp;&amp; update !== first);

    if (newBaseQueueLast === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }

    // Mark that the fiber performed work, but only if the new state is
    // different from the current state.
    if (!is(newState, hook.memoizedState)) {
      // 📌 리렌더링 중에 변경된 state가 없다면, bailout(NOT early bailout)이 발생한다.
      markWorkInProgressReceivedUpdate();

      // Check if this update is part of a pending async action. If so, we&#39;ll
      // need to suspend until the action has finished, so that it&#39;s batched
      // together with future updates in the same action.
      if (didReadFromEntangledAsyncAction) {
        ...
      }
    }

    hook.memoizedState = newState;     // 📌 드디어 새로운 state가 set 되었다.
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast; // 📌 새로운 baseQueue가 다음 리렌더링을 위해 set된다.

    queue.lastRenderedState = newState;
  }

  if (baseQueue === null) {
    // `queue.lanes` is used for entangling transitions. We can set it back to
    // zero once the queue is empty.
    queue.lanes = NoLanes;
  }

  const dispatch: Dispatch&lt;A&gt; = (queue.dispatch: any);

  // 📌 새로운 state가 생겼다! dispatch()는 stable하다.
  return [hook.memoizedState, dispatch];
}</code></pre>
<h1 id="4-summary">4. Summary</h1>
<ol>
<li>initial mount에서 state를 위한 새로운 hook을 생성한다.<ul>
<li>hook은 업데이트를 stash할 queue를 갖고 있다.</li>
<li><code>setState()</code>는 이 queue를 이용해 상태를 업데이트한다.<ul>
<li>state가 변경되지 않았다면, early bailout을 통해 리렌더링을 예약을 방지한다.</li>
<li>state가 변경되었다면, 업데이트를 stash하고 리렌더링을 예약한다.</li>
</ul>
</li>
</ul>
</li>
<li>re-render가 트리거되면, stash 되었던 업데이트들을 fiber에 attach한다.<ul>
<li>lanes를 이용하여 current와 alternate fibers 모두 dirty 표시를 해준다.</li>
</ul>
</li>
<li>stash된 업데이트가 있다면, 리렌더링의 <code>useState()</code>를 이용하여 state 값을 업데이트해준다.<ul>
<li>baseQueue를 이용하여 우선순위가 다른 업데이트를 관리한다.<ul>
<li>낮은 우선순위를 가진 업데이트 뒤에 있는 업데이트들은 우선순위와 상관 없이 모두 baseQueue에 유지해서 stash 해둔다.</li>
<li>낮은 우선순위를 가진 업데이트가 나중에 처리되도록 건너뛴 후에, 해당 업데이트를 처리할 때 baseQueue에서 해당 업데이트부터 시작해서 그 뒤에 있는 업데이트들을 처리한다.</li>
</ul>
</li>
<li>리렌더링 중에 변경된 state가 없다면, bailout(NOT early bailout)이 발생한다.</li>
</ul>
</li>
</ol>
<blockquote>
<p>JSer가 그린 useState() 순서도 슬라이드를 보면 이해에 큰 도움이 된다!
🔗 <a href="https://jser.dev/2023-06-19-how-does-usestate-work#how-usestate---works-internally">https://jser.dev/2023-06-19-how-does-usestate-work#how-usestate---works-internally</a></p>
</blockquote>
<h1 id="5-understanding-the-caveats">5. Understanding the caveats</h1>
<p>react.dev에서 나열해둔 <a href="https://react.dev/reference/react/useState#setstate-caveats">caveats</a>가 왜 존재하는지 이해해보자.</p>
<h2 id="51-state-update-is-not-sync">5.1 state update is not sync</h2>
<blockquote>
<p>The <code>set</code> function <strong>only updates the state variable for the <em>next</em> render</strong>. If you read the state variable after calling the <code>set</code> function, <a href="https://react.dev/reference/react/useState#ive-updated-the-state-but-logging-gives-me-the-old-value">you will still get the old value</a> that was on the screen before your call.</p>
</blockquote>
<p><code>setState()</code>가 다음 tick에 있는 re-render를 예약하기 때문에 동기적으로 작동하지 않으며, state 업데이트는 <code>setState()</code>가 아니라 <code>useState()</code>에서 이루어지기 때문에 업데이트된 값은 다음 렌더 때만 얻을 수 있다.   </p>
<h2 id="52-setstate-with-same-value-might-still-trigger-re-render">5.2 setState() with same value might still trigger re-render</h2>
<blockquote>
<p>If the new value you provide is identical to the current <code>state</code>, as determined by an <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is"><code>Object.is</code></a> comparison, React will <strong>skip re-rendering the component and its children.</strong> This is an optimization. Although in some cases React may still need to call your component before skipping the children, it shouldn’t affect your code.</p>
</blockquote>
<p>다음 퀴즈를 풀어보자. <a href="https://bigfrontend.dev/react-quiz/useState">https://bigfrontend.dev/react-quiz/useState</a></p>
<p>같은 값을 set 하는데 왜 리렌더링이 일어나는 것일까?</p>
<p><code>dispatchSetState()</code> 안에 있는 eager bailout 조건 때문이다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js">ReactFiberHooks.js</a></p>
<pre><code class="language-typescript">if (
  fiber.lanes === NoLanes &amp;&amp;
  // 📌 이 조건 하에서는 state가 변경되지 않았을 때, 리렌더링 예약을 하지 않는다.
  (alternate === null || alternate.lanes === NoLanes)
) {</code></pre>
<p>state 변경이 없다는 것을 가장 잘 확인할 수 있는 방법은, <em>pending update queue</em>와 <em>hook</em>의 <em>baseQueue</em>가 비어있는지 확인하는 것이다. 하지만 현재 코드에서는 실제로 리렌더링을 시작하기 전까지는 참인지 여부를 확인할 수 없다.</p>
<p>따라서 fiber nodes에 업데이트가 없는지 확인하는 곳으로 이동한다. 업데이트가 queue에 추가되면 fiber가 dirty로 표시되므로, 리렌더링이 시작될 때까지 기다릴 필요가 없다. (bailout)</p>
<p>하지만 부작용이 있다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberConcurrentUpdates.js">ReactFiberConcurrentUpdates.js</a></p>
<pre><code class="language-typescript">fiber.lanes = mergeLanes(fiber.lanes, lane);
const alternate = fiber.alternate;
if (alternate !== null) {
  alternate.lanes = mergeLanes(alternate.lanes, lane);
}</code></pre>
<p>업데이트를 queue에 추가할 때, current와 alternate fibers 모두 lanes로 dirty 표시된다. 이는 필요한 작업인데, <code>dispatchSetState()</code>가 source fiber에 바인딩되어있으므로, current와 alternate을 모두 업데이트하지 않으면 업데이트 처리를 보장할 수 없기 때문이다.</p>
<p>하지만 lanes을 지우는 건 <code>beginWork()</code>에서만 일어나고, 이는 실제 리렌더링이다.</p>
<p>💻 src: <a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js">ReactFiberBeginWork.js</a></p>
<pre><code class="language-typescript">function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
    ...
  // Before entering the begin phase, clear pending update priority.
  workInProgress.lanes = NoLanes;
  ...
}</code></pre>
<p>그러므로 <strong>업데이트가 한 번 예약되면, 최소한 두 차례의 리렌더링 이후에 dirty lanes flags가 완전히 없어진다</strong>.</p>
<p>단계는 대략 다음과 같다.</p>
<ol>
<li><strong>fiber1 (current, clean) / null (alternate)</strong> → fiber1은 <code>useState()</code>의 source fiber다.</li>
<li><strong><code>setState(true)</code></strong> → <code>true</code>와 <code>false</code>는 다르기 때문에, early bailout이 일어나지 않는다.</li>
<li><strong>fiber1 (current, dirty) / null (alternate)</strong> → queue에 업데이트를 추가한다.</li>
<li><strong>fiber1 (current, dirty) / fiber2 (workInProgress, dirty)</strong> → 리렌더링이 시작되며, 새로운 fiber를 workInProgress로 생성한다.</li>
<li><strong>fiber1 (current, dirty) / fiber2 (workInProgress, clean)</strong> → <code>beginWork()</code>에서 lanes이 없어진다.</li>
<li><strong>fiber1 (alternate, dirty) / fiber2 (current, clean)</strong> → commit 이후에 React는 두 버전의 fiber tree를 swap한다.</li>
<li><strong><code>setState(true)</code></strong> →  둘 중 한 fiber는 clean하지 않으므로, early bailout은 여전히 일어나지 않는다.</li>
<li><strong>fiber1 (alternate, dirty) / fiber2 (current, dirty)</strong> → queue에 업데이트를 추가한다.</li>
<li><strong>fiber1 (workInProgress, dirty) / fiber2 (current, dirty)</strong> → 리렌더링이 시작되며, fiber1은 fiber2로부터 lanes를 할당받는다.</li>
<li><strong>fiber1 (workInProgress, clean) / fiber2 (current, dirty)</strong> → <code>beginWork()</code>에서 lanes이 없어진다.</li>
<li><strong>fiber1 (workInProgress, clean) / fiber2 (current, clean)</strong> → state가 변경되지 않았으므로, <code>bailoutHooks()</code>에서 current fiber의 lanes가 삭제되고 bailout(NOT early bailout)이 발생한다.</li>
<li><strong>fiber1 (current, clean) / fiber2 (alternate, clean)</strong> → commit 이후에 React는 두 버전의 fiber tree를 swap한다.</li>
<li><strong><code>setState(true)</code></strong> → 이제 두 fibers 모두 clean하므로, early bailout을 할 수 있다!</li>
</ol>
<p>state가 변경되지 않았지만, 리렌더링이 발생하는 이슈를 해결하려면, fiber architecture와 hook의 작동 방식을 변경해야 해서 비용이 많이 들 수도 있다. 이미 <a href="https://github.com/facebook/react/issues/14994">discussion</a>이 있었으며, 대부분의 경우 해를 끼치지 않기 때문에 React 팀에서 이를 고칠 의도는 없어 보인다.</p>
<p><strong>React는 필요할 경우 리렌더링을 한다는 점</strong>을 명심해야 하며, <strong>성능 트릭이 항상 작동한다고 가정해서는 안 된다</strong>.</p>
<h2 id="53-react-batches-state-updates">5.3 React batches state updates</h2>
<blockquote>
<p>React <a href="https://react.dev/learn/queueing-a-series-of-state-updates">batches state updates.</a> It updates the screen <strong>after all the event handlers have run</strong> and have called their <code>set</code> functions. This prevents multiple re-renders during a single event. In the rare case that you need to force React to update the screen earlier, for example to access the DOM, you can use <a href="https://react.dev/reference/react-dom/flushSync"><code>flushSync</code>.</a></p>
</blockquote>
<p>업데이트는 처리되기 전에 stash 되며, 한 번에 처리된다.</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://jser.dev/2023-06-19-how-does-usestate-work">[JSer.dev] How does useState() work internally in React?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[운영체제 (Operating System)]]></title>
            <link>https://velog.io/@hee-suh/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C</link>
            <guid>https://velog.io/@hee-suh/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C</guid>
            <pubDate>Fri, 07 Jun 2024 12:00:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🦖 <a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition"><strong>Operating System Concepts 10th</strong></a>
<strong>PART ONE OVERVIEW</strong>
Chapter 1 Introduction</p>
</blockquote>
<blockquote>
<h4 id="💡-운영체제">💡 운영체제</h4>
</blockquote>
<ul>
<li><strong>컴퓨터</strong> 하드웨어를 관리(operate)하고 응용 프로그램 실행 환경을 제공하는 소프트웨어다.</li>
<li>컴퓨터 사용자와 컴퓨터 하드웨어 간의 매개체 역할을 한다.</li>
<li>“컴퓨터에서 항상 실행되고 있는 하나의 프로그램”으로, 운영체제의 핵심은 커널(<strong>kernel</strong>)이다.<ul>
<li>커널에는 <strong>시스템 프로그램</strong>과 응용 프로그램이 있다.</li>
<li><em>프로그램이란 명령(instruction)의 집합이다.</em></li>
<li><em>명령이란 컴퓨터의 하드웨어가 수행해야 할 작업을 알려주는 것이다.</em><blockquote>
</blockquote>
</li>
</ul>
</li>
<li>Cf.  <a href="https://en.wikipedia.org/wiki/Instruction_set_architecture">instruction set architecture (ISA)</a>*<blockquote>
</blockquote>
</li>
<li><em>응용 프로그램에 system services를 제공한다. (자세한 내용은 Chapter 2에서 살펴보자)</em>
<em>시스템 서비스란 운영체제가 프로그램 실행을 위해 제공한 환경을 통해, 프로그램과 해당 프로그램 사용자가 이용할 수 있는 서비스다.</em>
<img src="https://velog.velcdn.com/images/hee-suh/post/da19d870-7d8f-44df-b764-dcbe59a93a6d/image.png" alt="출처: **Figure 2.1** A view of operating system services."><pre><code>Figure 2.1 A view of operating system services.</code></pre><blockquote>
</blockquote>
</li>
<li><em><strong>프로세스</strong>, 자원, 사용자 인터페이스 등을 관리한다.</em></li>
</ul>
<h1 id="컴퓨터-시스템">컴퓨터 시스템</h1>
<p>컴퓨터 시스템은 크게 네 개의 컴포넌트 —하드웨어, 운영체제, 응용 프로그램, 사용자— 로 나눌 수 있다.</p>
<ul>
<li><strong>하드웨어</strong>는 중앙 처리 장치(CPU), 메모리 및 입출력(I/O) 장치로 구성되어, 시스템을 위한 기본적인 컴퓨팅(계산용) 자원을 제공한다.</li>
<li><strong>응용 프로그램</strong>인 워드 프로세서, 스프레드시트, 컴파일러, 웹 브라우저 등은 사용자의 컴퓨팅(계산) 문제를 해결하기 위해 이들 자원이 어떻게 사용될지를 정의한다.</li>
<li><strong>운영체제</strong>는 하드웨어를 제어하고 다양한 사용자를 위해 다양한 응용프로그램 간의 하드웨어 사용을 조정한다.</li>
</ul>
<img src='https://velog.velcdn.com/images/hee-suh/post/7dce1d30-7c5f-4cba-a9a1-6dc62df9d8ae/image.png' alt='Figure 1.1 Abstract view of the components of a computer system.' width='400px' />

<pre><code>Figure 1.1 Abstract view of the components of a computer system.</code></pre><h2 id="구성">구성</h2>
<blockquote>
<p><em>다음은 폰 노이만 구조를 따르는 전통적인 컴퓨터 시스템으로, 신경망 컴퓨터나 네트워크 컴퓨터나 양자 컴퓨터를 새로운 형태의 현대 컴퓨터 시스템이라고 일컫는다.</em></p>
</blockquote>
<img src='https://velog.velcdn.com/images/hee-suh/post/47a9bca4-5124-4c12-90aa-505ed5733bfe/image.png' alt='Figure 1.2 A typical PC computer system.' width='400px' />

<pre><code>Figure 1.2 A typical PC computer system.</code></pre><h2 id="인터럽트-interrupts">인터럽트 (Interrupts)</h2>
<blockquote>
<h4 id="💡-인터럽트">💡 인터럽트</h4>
</blockquote>
<p><em>I/O 작업을 시작하기 위해 장치 드라이버는 장치 컨트롤러에 적절한 레지스터를 로드한다. 그러면 장치 컨트롤러는 장치에서 로컬 버퍼로 데이터 전송을 시작한다. 데이터 전송이 완료되면 장치 컨트롤러는 장치 드라이버에게 작업이 완료되었음을 알린다. 이는 인터럽트를 통해 수행된다.</em></p>
<blockquote>
</blockquote>
<p>하드웨어는 언제든지 <strong>인터럽트</strong>를 트리거할 수 있다. 인터럽트는 하드웨어가 운영체제와 상호 작용하는 주요 방법으로, 하드웨어 장치는 CPU에 신호를 보내 인터럽트를 트리거한다.</p>
<blockquote>
</blockquote>
<ul>
<li>일반적으로 시스템 버스를 통해 CPU에 신호를 보낸다.<blockquote>
</blockquote>
<img src="https://velog.velcdn.com/images/hee-suh/post/a190c9e5-eded-4383-b55c-12cf2246628a/image.png" alt="Figure 1.3 Interrupt timeline for a single program doing output."> <pre><code>Figure 1.3 Interrupt timeline for a single program doing output.</code></pre><blockquote>
</blockquote>
인터럽트는 인터럽트 핸들러로 관리된다.</li>
</ul>
<h2 id="저장장치-구조-storage-structure">저장장치 구조 (Storage Structure)</h2>
<p>CPU는 메모리에서만 명령을 로드할 수 있으므로, 프로그램을 실행하려면 먼저 메모리에 로드해야 한다.</p>
<blockquote>
<h4 id="💡-폰-노이만-구조-von-neumann-architecture">💡 폰 노이만 구조 <em>(von Neumann architecture)</em></h4>
</blockquote>
<p>폰 노이만 구조는 명령 데이터와 프로그램 데이터가 동일한 메모리에 저장되는 <strong>stored-program</strong> 컴퓨터 개념을 기반으로 한다.</p>
<blockquote>
</blockquote>
<p>전형적인 instruction-execution 사이클이다.</p>
<blockquote>
</blockquote>
<p><img src="http://computerscience.gcse.guru/wp-content/uploads/2016/04/Von-Neumann-Architecture-Diagram.jpg" alt="출처: https://www.computerscience.gcse.guru/theory/von-neumann-architecture"></p>
<pre><code>출처: https://www.computerscience.gcse.guru/theory/von-neumann-architecture</code></pre><blockquote>
</blockquote>
<ul>
<li>처음에 메모리로부터 명령(instruction)을 <strong>fetch</strong>해온다.</li>
<li>그리고 해당 명령을 명령 레지스터(<strong>instruction register</strong>, IR)에 저장한다.</li>
<li>명령이 디코딩되고, 메모리로부터 피연산자를 불러와서 일부 내부 레지스터에 저장할 수도 있다.</li>
<li>피연산자에 대한 명령이 실행(<strong>execute</strong>)된 후 결과는 메모리에 다시 저장될 수 있다.<blockquote>
</blockquote>
⇒ CPU는 instruction-execution 사이클 동안 메인 메모리로부터 명령어를 읽고, data-fetch 사이클 동안 메인 메모리에서 데이터를 읽고 쓴다.</li>
</ul>
<p><strong>스토리지</strong> 시스템은 <strong>스토리지 용량</strong>과 <strong>접근 시간</strong>에 따라 다음과 같은 계층 구조로 구성될 수 있다.</p>
<ul>
<li>높은 레벨은 비용이 많이 들지만 빠르다. 계층 구조를 아래로 내려갈수록 비트당 비용은 일반적으로 감소하는 반면 액세스 시간은 일반적으로 증가한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/36a415c6-68ef-47db-a6ac-bb870a4927a9/image.png" alt="Figure 1.6 Storage-device hierarchy."></p>
<pre><code>Figure 1.6 Storage-device hierarchy.</code></pre><h3 id="주기억장치-main-memory">주기억장치 (Main Memory)</h3>
<p>범용 컴퓨터는 메인 메모리(<strong>Random Access Memory</strong> 또는 <strong>RAM</strong>이라고도 함)라고 하는 재기록 가능한(rewritable) 메모리에서 대부분의 프로그램을 실행한다. 메인 메모리는 일반적으로 <strong>DRAM</strong>(<strong>Dynamic Random-Access Memory</strong>)이라는 반도체 기술로 구현된다.</p>
<p>메인 메모리는 일반적으로 필요한 모든 프로그램과 데이터를 영구적으로 저장하기에는 너무 작고, 휘발성이므로 전원이 공급되지 않으면 내용을 잃어버린다.</p>
<h3 id="보조기억장치-secondary-storage">보조기억장치 (Secondary Storage)</h3>
<p>따라서 대부분의 컴퓨터 시스템은 메인 메모리의 확장으로 보조기억장치(<strong>secondary storage</strong>)를 제공한다. 보조기억장치의 주요 요구 사항은 대량의 데이터를 영구적으로 보관할 수 있어야 한다는 것이다.</p>
<p>가장 일반적인 보조기억장치는 프로그램과 데이터 모두에 저장 공간을 제공하는 하드 디스크 드라이브(<strong>hard-disk drives</strong>, <strong>HDDs</strong>)와 비휘발성 메모리(<strong>nonvolatile memory,</strong> <strong>NVM</strong>) 장치다. 대부분의 프로그램(시스템 및 애플리케이션)은 메모리에 로드될 때까지 보조기억장치에 저장된다. 보조기억장치는 메인 메모리보다 훨씬 느리다. 보조저장장치의 적절한 관리는 컴퓨터 시스템에서 매우 중요하다.</p>
<h3 id="3차-저장장치-tertiary-storage">3차 저장장치 (Tertiary Storage)</h3>
<p>다른 장치에 저장된 자료의 백업 복사본을 저장하는 등 특별한 목적으로만 사용할 수 있을 만큼 느리고 큰 저장소를 3차 저장장치(<strong>tertiary storage</strong>)라고 한다. 각 저장 시스템은 데이터를 저장하고 나중에 검색할 때까지 해당 데이터를 보관하는 기본 기능을 제공한다.</p>
<blockquote>
<h4 id="💡-volatile-vs-nonvolatile">💡 Volatile vs Nonvolatile</h4>
</blockquote>
<ul>
<li><strong>휘발성 저장 장치 (Volatile Storage)</strong><ul>
<li>메인 메모리는 일반적으로 휘발성 저장 장치로, 전원이 꺼지거나 손실되면 내용을 잃어버린다.</li>
</ul>
</li>
<li><strong>비휘발성 저장 장치 (Nonvolatile Storage)</strong><ul>
<li>비휘발성 저장 장치는 메인 메모리의 확장으로, 많은 양의 데이터를 영구적으로 유지할 수 있다.</li>
<li>가장 일반적인 비휘발성 저장 장치는 하드 디스크로, 프로그램과 데이터를 모두 저장할 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="io-구조">I/O 구조</h2>
<img src='https://velog.velcdn.com/images/hee-suh/post/d081121e-45ea-4c24-922f-cafd04caf437/image.png' alt='Figure 1.7 How a modern computer system works.' width='400px' />

<pre><code>Figure 1.7 How a modern computer system works.</code></pre><p>인터럽트 중심 I/O는 소량의 데이터를 이동하는 데 적합하지만 NVS(nonvolatile storage) I/O와 같은 대량 데이터 이동에 사용될 때 높은 오버헤드를 생성할 수 있다. 이 문제를 해결하기 위해 <strong>DMA</strong>(<strong>Direct Memory Access</strong>)가 사용된다. I/O 장치에 대한 버퍼, 포인터 및 카운터를 설정한 후 장치 컨트롤러가 CPU의 개입 없이 전체 데이터 블록을 장치 및 메인 메모리에 직접 전송한다.</p>
<blockquote>
<h4 id="❗-good-to-know">❗ Good to know</h4>
</blockquote>
<p>DMA는 비용이 큰 작업이므로, 사용을 지양하는 것이 좋다.</p>
<blockquote>
</blockquote>
<p>DMA는 캐시 일관성 문제를 야기할 수 있으며, DMA Controller는 시스템의 전반적인 비용과 소프트웨어의 복잡도를 증가시킨다.</p>
<h1 id="컴퓨터-시스템-아키텍처">컴퓨터 시스템 아키텍처</h1>
<blockquote>
<h4 id="📖-definitions-of-computer-system-components">📖 <em>DEFINITIONS OF COMPUTER SYSTEM COMPONENTS</em></h4>
</blockquote>
<ul>
<li><strong>CPU</strong> — 명령을 실행하는 하드웨어</li>
<li><strong>Processor</strong> — 하나 이상의 CPU를 포함하는 물리적 칩</li>
<li><strong>Core</strong> — CPU의 기본 계산 단위</li>
<li><strong>Multicore</strong> — 동일한 CPU에 여러 컴퓨팅 코어 포함</li>
<li><strong>Multiprocessor</strong> — 다중 프로세서 포함</li>
</ul>
<h2 id="단일-프로세서-시스템-single-processor-systems">단일 프로세서 시스템 (Single-Processor Systems)</h2>
<p>수년 전에는 대부분의 컴퓨터 시스템이 단일 처리 코어와 하나의 CPU를 포함하는 단일 프로세서를 사용했다. 코어(<strong>core</strong>)는 데이터를 로컬에 저장하기 위한 명령과 레지스터를 실행하는 구성 요소다. 코어가 있는 하나의 메인 CPU는 프로세스의 명령을 포함하여 범용 명령 세트를 실행할 수 있다. 디스크, 키보드, 그래픽 컨트롤러와 같은 장치별 프로세서의 형태로 제공될 수 있다.</p>
<h2 id="멀티프로세서-시스템-multiprocessor-systems">멀티프로세서 시스템 (Multiprocessor Systems)</h2>
<p>모바일 장치에서 서버에 이르기까지 최신 컴퓨터에서는 <strong>다중 프로세서 시스템(multiprocessor systems)</strong>이 이제 컴퓨팅 환경을 지배하고 있다. 전형적으로 이러한 시스템에는 각각 단일 코어 CPU가 있는 두 개 이상의 프로세서가 있다. 다중 프로세서 시스템의 주요 이점은 처리량 증가다. 즉, 프로세서 수를 늘리면 더 짧은 시간에 더 많은 작업을 수행할 수 있을 것으로 예상된다. 그러나, 여러 프로세서가 작업에 협력할 때 모든 부분이 올바르게 작동하도록 유지하는 데 발생하는 일정량의 오버헤드와 공유 리소스에 대한 경합으로 인해 추가 프로세서에서 예상되는 이득이 낮아진다.</p>
<h3 id="symmetric-multiprocessing-smp">Symmetric multiprocessing (SMP)</h3>
<p>가장 일반적인 다중 프로세서 시스템은 각 peer CPU 프로세서가 운영체제 기능 및 사용자 프로세스를 포함한 모든 작업을 수행하는 대칭적 다중 처리(<strong>SMP, symmetric multiprocessing</strong>)를 사용한다.</p>
<p>Cf. <strong><em>Asymmetric</em></strong> multiprocessing — 각 프로세서가 특정 작업만을 수행한다.</p>
<p>일반적인 SMP 아키텍처는 각각 자체 CPU가 있는 두 개의 프로세서로 구성되어 있으며, 각 CPU 프로세서에는 자체 레지스터 세트와 개인 또는 로컬 캐시가 있다. 그러나 모든 프로세서는 시스템 버스를 통해 물리적 메모리를 공유한다.</p>
<img src='https://velog.velcdn.com/images/hee-suh/post/df968f69-8a3e-49ee-8c57-b51092411665/image.png' alt='Figure 1.8 Symmetric multiprocessing architecture.' width='400px' />

<pre><code>Figure 1.8 Symmetric multiprocessing architecture.</code></pre><p>이 모델의 이점은 성능을 크게 저하시키지 않고 많은 프로세스를 동시에 실행할 수 있다는 것이다. 즉, N CPU가 있는 경우 N 프로세스를 실행할 수 있다. 그러나 CPU가 분리되어 있기 때문에 하나는 유휴 상태이고 다른 하나는 과부하되어 비효율성이 발생할 수 있다. 프로세서가 특정 데이터 구조를 공유하면 프로세스와 리소스(e.g. 메모리)를 다양한 프로세서 간에 동적으로 공유할 수 있으며 프로세서 간의 작업 부하 차이를 낮출 수 있다.</p>
<h3 id="multi-core-design">Multi-core design</h3>
<p>멀티프로세서의 정의는 시간이 지남에 따라 발전해 왔으며 이제는 여러 컴퓨팅 코어가 단일 칩에 상주하는 <strong>멀티코어</strong> 시스템을 포함한다. 온칩 통신이 칩 간 통신보다 빠르기 때문에 멀티코어 시스템은 단일 코어가 있는 다중 칩보다 더 효율적일 수 있다.</p>
<img src='https://velog.velcdn.com/images/hee-suh/post/bc234ccc-37f2-4733-88a8-69da926f5db2/image.png' alt='Figure 1.9 A dual-core design with two cores on the same chip.' width='400px' />

<pre><code>Figure 1.9 A dual-core design with two cores on the same chip.</code></pre><p>또한, 다중 코어를 가진 하나의 칩은 다중 단일 코어 칩에 비해 훨씬 적은 전력을 사용하는데, 이는 노트북은 물론 모바일 장치에 있어서 중요한 문제다. 듀얼 코어 설계는 동일한 프로세서 칩에 두 개의 코어가 있으며, 각 코어에는 자체 레지스터 세트는 물론 레벨 1 또는 L1 캐시라고도 하는 자체 로컬 캐시도 있다. 또한 레벨 2(L2) 캐시는 칩에 로컬이지만 두 개의 처리 코어에서 공유된다.  여기서 로컬, 하위 수준 캐시는 일반적으로 상위 수준 공유보다 작고 빠르다.</p>
<h2 id="클러스터형-시스템-clustered-systems">클러스터형 시스템 (Clustered Systems)</h2>
<p>멀티프로세서 시스템의 또 다른 유형은 여러 CPU를 함께 모으는 클러스터 시스템이다. 클러스터 시스템은 둘 이상의 개별 시스템(또는 노드)이 함께 구성된다는 점에서 위에서 설명한 멀티프로세서 시스템과 다르다. 각 노드는 일반적으로 멀티코어 시스템이며 느슨하게 결합되어 있다. 일반적인 클러스터형 시스템의 정의는 클러스터된 컴퓨터가 스토리지를 공유하고 LAN과 같은 더 빠른 상호 연결을 통해 밀접하게 연결되어 있다는 것이다.</p>
<img src='https://velog.velcdn.com/images/hee-suh/post/d4dd901a-6cb3-48a9-be2c-456dbbe76a60/image.png' alt='Figure 1.11 General structure of a clustered system.' width='400px' />

<pre><code>Figure 1.11 General structure of a clustered system.</code></pre><p>클러스터링은 일반적으로 고가용성 서비스, 즉 클러스터에 있는 하나 이상의 시스템에 장애가 발생하더라도 계속되는 서비스를 제공하는 데 사용된다. 일반적으로 시스템에 중복성 수준을 추가하여 고가용성을 얻는다. 각 노드는 (네트워크를 통해) 다른 노드 중 하나 이상을 모니터링하고, 오류가 발생한 시스템에서 실행 중이던 애플리케이션을 다시 시작할 수 있다.</p>
<h1 id="운영체제의-작동-operating-system-operations">운영체제의 작동 (Operating-System Operations)</h1>
<h2 id="multiprogramming-and-multiprocessing">Multiprogramming and Multiprocessing</h2>
<h3 id="multiprogramming">Multiprogramming</h3>
<p>멀티프로그래밍은 한 번에 두 개 이상의 프로그램을 동시에 실행하는 것이다.</p>
<ul>
<li>여러 프로세스를 메모리에 동시에 유지함으로써 CPU 사용량을 높인다.</li>
</ul>
<img src='https://velog.velcdn.com/images/hee-suh/post/23c4f400-5b4a-413b-91f9-57d7045bb2ab/image.png' alt='Figure 1.12 Memory layout for a multiprogramming system.' width='200px' />

<pre><code>Figure 1.12 Memory layout for a multiprogramming system.</code></pre><h3 id="multitasking--multiprocessing">Multitasking (= Multiprocessing)</h3>
<p>멀티태스킹은 <strong>CPU 스케줄링</strong> 알고리즘이 프로세스 간에 빠르게 전환(switch)하여 사용자에게 빠른 응답 시간을 제공하는 멀티프로그래밍의 확장이다.</p>
<blockquote>
<h4 id="💡-concurrency-vs-parallelism">💡 Concurrency vs Parallelism</h4>
</blockquote>
<p><strong>Concurrency</strong>(동시성)란 두 개 이상의 테스크를 <strong>동시에 지원함</strong>을 뜻한다.</p>
<blockquote>
</blockquote>
<p><strong>Parallelism</strong>(병렬성)이란 두 개 이상의 테스크를 <strong>동시에 실행</strong>할 수 있음을 뜻한다.</p>
<blockquote>
</blockquote>
<p><img src="https://www.baeldung.com/wp-content/uploads/sites/4/2022/01/vs-1024x462-1.png" alt="출처: https://www.baeldung.com/cs/concurrency-vs-parallelism"></p>
<blockquote>
</blockquote>
<p>출처: <a href="https://www.baeldung.com/cs/concurrency-vs-parallelism">https://www.baeldung.com/cs/concurrency-vs-parallelism</a></p>
<blockquote>
</blockquote>
<p><em>Cf. JavaScript는 single-thread 환경이라 여러 개의 thread를 사용하는 Parallelism이 불가능하고, Concurrency를 지원한다.</em></p>
<h2 id="two-separate-mode-of-operations">Two separate mode of operations</h2>
<p>시스템 하드웨어에는 <strong>user mode</strong>와 <strong>kernel mode</strong>(<strong>supervisor mode</strong>, <strong>system mode</strong>, <strong>privileged
mode</strong>라고도 불림)의 두 가지 모드가 있다.</p>
<p>operation의 dual mode는 잘못된 사용자로부터 운영 체제를 보호한다.</p>
<ul>
<li>해를 끼칠 수 있는 일부 기계 명령어를 특권 명령어(<strong>privileged instructions</strong>)로 지정한다.
해당 명령어에는 권한이 부여되며 커널 모드에서만 실행될 수 있다. 예로는 커널 모드로 전환하라는 명령, I/O 제어, 타이머 관리, 인터럽트 관리 등이 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/5c94f17f-5807-49fd-9d9a-1f38e888c602/image.png" alt="Figure 1.13 Transition from user to kernel mode."></p>
<pre><code>Figure 1.13 Transition from user to kernel mode.</code></pre><h2 id="timer">Timer</h2>
<p>운영체제가 CPU에 대한 제어를 유지하도록 보장해야 한다. 사용자 프로그램이 무한 루프에 빠지거나 시스템 서비스 호출에 실패하고 운영 체제에 제어권을 반환하지 못하게 하면 안 된다. 이 목표를 달성하기 위해 타이머를 사용할 수 있다. 지정된 기간이 지나면 컴퓨터를 중단하도록 타이머(<strong>timer</strong>)를 설정할 수 있다. 주기는 고정(예: 1/60초)되거나 가변(예: 1밀리초 ~ 1초)될 수 있다. 가변 타이머(<strong>variable timer</strong>)는 일반적으로 고정 속도 클럭과 카운터로 구현된다. 운영 체제가 카운터를 설정하고, 시계가 똑딱거릴 때마다 카운터가 감소한다. 카운터가 0에 도달하면 인터럽트가 발생한다.</p>
<p><a href="https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers">HTML Standard - timers</a></p>
<h1 id="자원-관리-resource-management">자원 관리 (Resource Management)</h1>
<p>운영 체제는 자원 관리자(<strong>resource manager</strong>)다. 시스템의 CPU, 메모리 공간, 파일 저장 공간, I/O 장치는 운영 체제가 관리해야 하는 리소스 중 하나다.</p>
<h2 id="프로세스-관리-process-management">프로세스 관리 (Process Management)</h2>
<p>실행 중인 프로그램은 프로세스다. 프로세스가 작업을 수행하려면 CPU 시간, 메모리, 파일, I/O 장치 등 특정 리소스가 필요하다. 이러한 리소스는 일반적으로 프로세스가 실행되는 동안 프로세스에 할당된다. 프로세스가 종료되면 운영체제는 재사용 가능한 리소스를 회수한다.</p>
<p>단일 스레드 프로세스에는 실행할 다음 명령을 지정하는 하나의 프로그램 카운터(<strong>program counter</strong>, PC)가 있다. CPU는 프로세스가 완료될 때까지 프로세스의 명령을 하나씩 차례로 실행한다. 다중 스레드 프로세스에는 여러 개의 프로그램 카운터가 있으며, 각각은 주어진 스레드에 대해 실행할 다음 명령을 가리킨다.</p>
<p>프로세스는 시스템의 작업 단위다. 시스템은 프로세스 모음으로 구성되며, 그 중 일부는 운영 체제 프로세스(시스템 코드를 실행하는 프로세스)이고 나머지는 사용자 프로세스(사용자 코드를 실행하는 프로세스)다. 이러한 모든 프로세스는 잠재적으로 단일 CPU 코어에서 다중화(multiplexing)하여 동시에 실행되거나 여러 CPU 코어에서 병렬로 실행될 수 있다.</p>
<p>운영 체제는 프로세스 관리와 관련하여 다음 활동을 담당한다.</p>
<ul>
<li>사용자 및 시스템 프로세스의 생성 및 삭제</li>
<li>CPU에서 프로세스 및 스레드 예약</li>
<li>프로세스 일시중단 및 재개</li>
<li>프로세스 동기화를 위한 메커니즘 제공</li>
<li>프로세스 통신을 위한 메커니즘 제공</li>
</ul>
<h2 id="메모리-관리-memory-management">메모리 관리 (Memory Management)</h2>
<p>메인 메모리는 현대 컴퓨터 시스템 작동의 핵심이다. 메인 메모리는 크기가 수십만에서 수십억에 이르는 대규모 바이트 배열이고, 각 바이트에는 고유한 주소가 있다. 메인 메모리는 CPU 및 I/O 장치에서 공유하는 빠르게 액세스할 수 있는 데이터 저장소다. 메인 메모리는 일반적으로 CPU가 직접 주소를 지정하고 액세스할 수 있는 유일한 대형 저장 장치다. 프로그램이 실행될 때 절대 주소를 생성하여 메모리의 프로그램 명령과 데이터에 액세스한다.</p>
<p>CPU 활용률과 사용자에 대한 컴퓨터의 응답 속도를 모두 향상시키기 위해 범용 컴퓨터는 여러 프로그램을 메모리에 유지해야 하므로 메모리 관리가 필요하다.</p>
<p>운영 체제는 메모리 관리와 관련하여 다음 활동을 담당한다.</p>
<ul>
<li>현재 메모리의 어느 부분이 사용되고 있는지, 어떤 프로세스가 이를 사용하고 있는지 추적</li>
<li>필요에 따라 메모리 공간 할당 및 할당 해제</li>
<li>메모리에 들어오고 나갈 프로세스(또는 프로세스의 일부)와 데이터 결정</li>
</ul>
<h2 id="파일-시스템-관리-file-system-management">파일 시스템 관리 (File-System Management)</h2>
<p>컴퓨터 시스템을 사용자에게 편리하게 만들기 위해 운영체제는 정보 저장에 대한 통일되고 논리적인 보기를 제공한다. 운영체제는 저장 장치의 물리적 속성을 추상화하여 논리적 저장 단위인 파일(<strong>file)</strong>을 정의한다. 운영 체제는 파일을 물리적 미디어에 매핑하고 저장 장치를 통해 이러한 파일에 액세스한다.</p>
<p>파일 관리는 운영체제에서 가장 눈에 띄는 구성 요소 중 하나다. 컴퓨터는 다양한 유형의 물리적 매체에 정보를 저장할 수 있다. 보조기억장치가 가장 일반적이지만 3차 저장장치도 가능하다.</p>
<p>운영 체제는 대용량 저장 매체(mass storage media)와 이를 제어하는 장치를 관리하여 파일의 추상적인 개념을 구현한다. 또한 파일은 일반적으로 사용하기 쉽도록 디렉토리로 구성된다. 마지막으로, 여러 사용자가 파일에 액세스할 수 있는 경우 어떤 사용자가 파일에 액세스할 수 있는지, 해당 사용자가 파일에 액세스할 수 있는 방법(예: 읽기, 쓰기, 추가)을 제어하는 것이 바람직할 수 있다.</p>
<p>운영 체제는 파일 관리와 관련하여 다음 활동을 담당한다.</p>
<ul>
<li>파일 생성 및 삭제</li>
<li>파일 정리를 위한 디렉토리 생성 및 삭제</li>
<li>파일 및 디렉터리 조작을 위한 기본 요소 지원</li>
<li>대용량 저장소에 파일 매핑</li>
<li>안정적인(비휘발성) 스토리지 미디어에 파일 백업</li>
</ul>
<h2 id="대용량-저장-장치-관리-mass-storage-management">대용량 저장 장치 관리 (Mass-Storage Management)</h2>
<p>컴퓨터 시스템은 메인 메모리를 백업하기 위한 보조저장장치를 제공해야 한다. 대부분의 최신 컴퓨터 시스템은 프로그램과 데이터에 대한 주요 온라인 저장 매체로 HDD와 NVM 장치를 사용한다. 컴파일러, 웹 브라우저, 워드 프로세서 및 게임을 포함한 대부분의 프로그램은 메모리에 로드될 때까지 이러한 장치에 저장된다. 따라서 보조저장장치를 적절하게 관리하는 것은 컴퓨터 시스템에서 매우 중요하다. </p>
<p>운영체제는 보조저장장치 관리와 관련하여 다음 활동을 담당한다.</p>
<ul>
<li>장착 및 탈착 (Mounting and unmounting)</li>
<li>여유 공간 관리 (Free-space management)</li>
<li>스토리지 할당</li>
<li>디스크 스케줄링</li>
<li>파티셔닝</li>
<li>보호</li>
</ul>
<h2 id="캐시-관리-cache-management">캐시 관리 (Cache Management)</h2>
<p>캐싱(<strong>Caching</strong>)은 컴퓨터 시스템의 중요한 원칙이다. 작동 방식은 다음과 같다. 정보는 일반적으로 일부 저장 시스템(예: 메인 메모리)에 보관된다. 사용되면서 일시적으로 더 빠른 저장 시스템인 캐시에 복사된다. 특정 정보가 필요할 때 먼저 해당 정보가 캐시에 있는지 확인한다. 그렇다면 캐시에서 직접 정보를 사용한다. 그렇지 않은 경우 소스의 정보를 사용하여 곧 다시 필요할 것이라는 가정하에 캐시에 복사본을 저장한다.</p>
<p>또한 내부 프로그래밍 가능 레지스터는 메인 메모리에 고속 캐시를 제공한다. 프로그래머(또는 컴파일러)는 레지스터에 보관할 정보와 메인 메모리에 보관할 정보를 결정하기 위해 레지스터 할당 및 레지스터 대체 알고리즘을 구현한다.</p>
<p>캐시는 크기가 제한되어 있으므로 캐시 관리(<strong>cache management</strong>)는 중요한 설계 문제다. 캐시 크기와 교체 정책을 신중하게 선택하면 성능이 크게 향상될 수 있다.</p>
<ul>
<li><p>Cf. 캐시 일관성(<strong>cache cohrerency</strong>)</p>
<p>  계층적 스토리지 구조에서는 동일한 데이터가 스토리지 시스템의 서로 다른 수준에 나타날 수 있다. 예를 들어, 1씩 증가할 정수 A가 파일 B에 있고 파일 B가 하드 디스크에 있다고 가정한다. 증분 연산은 먼저 I/O 연산을 실행하여 A가 있는 디스크 블록을 주 메모리에 복사함으로써 진행된다. 이 작업 후에는 A를 캐시와 내부 레지스터에 복사한다. 따라서 A의 복사본은 하드 디스크, 주 메모리, 캐시 및 내부 레지스터 등 여러 위치에 나타난다. 내부 레지스터에서 증가가 발생하면 A 값은 다양한 저장 시스템에서 다르다. A의 값은 A의 새로운 값이 내부 레지스터에서 하드 디스크로 다시 쓰여진 후에야 동일해진다.
  <img src="https://velog.velcdn.com/images/hee-suh/post/2a1a49b5-4997-4f37-aafb-83dfab0fa91c/image.png" alt="Figure 1.15 Migration of integer A from disk to register."></p>
<pre><code>  Figure 1.15 Migration of integer A from disk to register.</code></pre><p>  한 번에 하나의 프로세스만 실행되는 컴퓨팅 환경에서는 정수 A에 대한 액세스가 항상 계층 구조의 가장 높은 수준에 있는 복사본에 대한 것이기 때문에 어려움이 없다. 그러나 CPU가 다양한 프로세스 사이에서 앞뒤로 전환되는 멀티태스킹 환경에서는 여러 프로세스가 A에 액세스하려는 경우 각 프로세스가 가장 최근에 업데이트된 값을 얻도록 극도의 주의를 기울여야 한다.</p>
<p>  각 CPU에 내부 레지스터와 로컬 캐시가 포함되어 있는 멀티프로세서 환경에서는 A의 복사본이 여러 캐시에 동시에 존재할 수 있다. 다양한 CPU가 모두 병렬로 실행될 수 있으므로 한 캐시의 A 값에 대한 업데이트가 A가 있는 다른 모든 캐시에 즉시 반영되도록 해야 한다. 이러한 상황을 캐시 일관성(<strong>cache cohrerency</strong>)이라고 하며 일반적으로 하드웨어 문제다(운영체제 수준 아래에서 처리됨).</p>
<p>  분산 환경에서는 상황이 더욱 복잡해진다. 이 환경에서는 동일한 파일의 여러 복사본(또는 복제본)이 다른 컴퓨터에 보관될 수 있다. 다양한 복제본에 동시에 액세스하고 업데이트할 수 있으므로 일부 분산 시스템에서는 복제본이 한 곳에서 업데이트될 때 다른 모든 복제본도 최대한 빨리 최신 상태로 유지된다.</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>Level</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
</tr>
</thead>
<tbody><tr>
<td>Name</td>
<td>registers</td>
<td>cache</td>
<td>main memory</td>
<td>solid-state disk</td>
<td>magnetic disk</td>
</tr>
<tr>
<td>Typical size</td>
<td>&lt; 1 KB</td>
<td>&lt; 16MB</td>
<td>&lt; 64GB</td>
<td>&lt; 1 TB</td>
<td>&lt; 10 TB</td>
</tr>
<tr>
<td>Implementation technology</td>
<td>custom memory with multiple ports CMOS</td>
<td>on-chip or off-chip CMOS SRAM</td>
<td>CMOS SRAM</td>
<td>flash memory</td>
<td>magnetic disk</td>
</tr>
<tr>
<td>Access time (ns)</td>
<td>0.25-0.5</td>
<td>0.5-25</td>
<td>80-250</td>
<td>25,000-50,000</td>
<td>5,000,000</td>
</tr>
<tr>
<td>Bandwidth (MB/sec)</td>
<td>20,000-100,000</td>
<td>5,000-10,000</td>
<td>1,000-5,000</td>
<td>500</td>
<td>20-150</td>
</tr>
<tr>
<td>Managed by</td>
<td>compiler</td>
<td>hardware</td>
<td>operating system</td>
<td>operating system</td>
<td>operating system</td>
</tr>
<tr>
<td>Backed by</td>
<td>cache</td>
<td>main memory</td>
<td>disk</td>
<td>disk</td>
<td>disk or tape</td>
</tr>
<tr>
<td>```</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Figure 1.14 Characteristics of various types of storage.</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>```</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2 id="입출력-시스템-관리-io-system-management">입출력 시스템 관리 (I/O System Management)</h2>
<p>운영체제의 목적 중 하나는 사용자로부터 특정 하드웨어 장치의 특성을 숨기는 것이다. 예를 들어, UNIX에서 I/O 장치의 특성은 I/O 하위 시스템(<strong>I/O subsystem</strong>)에 의해 운영체제 자체에서 숨겨진다. I/O 하위 시스템은 여러 구성 요소로 구성된다.</p>
<ul>
<li>버퍼링, 캐싱, 스풀링(spooling)을 포함하는 메모리 관리 구성 요소</li>
<li>일반 장치 드라이버 인터페이스</li>
<li>특정 하드웨어 장치용 드라이버</li>
</ul>
<h1 id="가상화-virtualization">가상화 (Virtualization)</h1>
<p>가상화(<strong>Virtualization</strong>)는 단일 컴퓨터(CPU, 메모리, 디스크 드라이브, 네트워크 인터페이스 카드 등)의 하드웨어를 여러 다른 실행 환경으로 추상화하여 개별 환경이 별도의 컴퓨터에서 실행되고 있다는 환상을 만들 수 있는 기술이다.</p>
<ul>
<li><strong>Virtual Machine Manager</strong> (<strong>VMM</strong>)  <em>e.g. VMware, XEN, WSL, …</em></li>
</ul>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/67c799b6-dcb3-4088-8802-f484018f983f/image.png" alt="Figure 1.16 A computer running (a) a single operating system and (b) three virtual machines."></p>
<pre><code>Figure 1.16 A computer running (a) a single operating system and (b) three virtual machines.</code></pre><h1 id="컴퓨팅-환경-computing-environments">컴퓨팅 환경 (Computing Environments)</h1>
<ul>
<li><p><strong>전통적인 컴퓨팅</strong>
오늘날 웹 기술이 전통적인 컴퓨팅의 경계를 확장하고 있다.</p>
</li>
<li><p><strong>모바일 컴퓨팅</strong>
모바일 컴퓨팅은 휴대용 스마트폰과 태블릿 컴퓨터의 컴퓨팅 환경을 말한다.
현재 휴대 컴퓨팅에서 두 개의 지배적인 운영체제는 <strong>Apple iOS</strong>와 <strong>Google Android</strong>다.</p>
</li>
</ul>
<ul>
<li><p><strong>client-server 컴퓨팅</strong></p>
<img src='https://velog.velcdn.com/images/hee-suh/post/01518fb9-4fe8-46f3-8f44-d5ac6977d412/image.svg' alt='architecture client-server' width='400px' />
현대 네트워크 구조는 **서버 시스템**이 **클라이언트 시스템**이 생성한 요청을 만족시키는 배치를 특징으로 하며, **클라이언트-서버** 시스템이라 불린다.
</li>
<li><p><strong>peer-to-peer(p2p) 컴퓨팅</strong></p>
<img src='https://velog.velcdn.com/images/hee-suh/post/08bf7ecd-d6ae-49b3-8f8c-822b71c1ad2e/image.svg' alt='architecture peer-to-peer' width='400px' />
분산 시스템의 다른 구조는 피어 간 시스템이다. 이 모델에서는 클라이언트와 서버가 서로 구별되지 않으며, 시스템상의 모든 노드가 피어로 간주되고 각 피어는 서비스를 요청하느냐 제공하느냐에 따라 클라이언트 및 서버로 동작한다. 서비스가 네트워크에 분산된 여러 노드에 의해 제공될 수 있으므로, 서버가 병목으로 작용하는 클라이언트-서버 시스템에 비해 장점을 제공한다.
</li>
<li><p><strong>클라우드 컴퓨팅</strong>
클라우드 컴퓨팅은 계산, 저장장치는 물론 응용조차도 네트워크를 통한 서비스로 제공하는 계산 유형이다. 어떤 면에서 클라우드 컴퓨팅은 가상화를 그 기능의 기반으로 사용하기 때문에 가상화의 논리적 확장이다.</p>
<blockquote>
<p>클라우드 컴퓨팅의 유형</p>
</blockquote>
<ul>
<li><strong>Public cloud</strong> — 서비스를 위해 지불 가능한 사람은 누구나 인터넷을 통해 사용 가능한 클라우드</li>
<li><strong>Private cloud</strong> — 한 회사가 사용하기 위해 운영하는 클라우드</li>
<li><strong>Hybrid cloud</strong> — 공공(public)과 사유(private) 부분을 모두 포함하는 혼합형 클라우드</li>
<li>Software as a service (<strong>SaaS</strong>) — 인터넷을 통해 사용 가능한 하나 이상의 응용 프로그램 (e.g. spreadsheet)</li>
<li>Platform as a service (<strong>PaaS</strong>) — 인터넷을 통해 사용하도록 응용 프로그램에 맞게 준비된 소프트웨어 (e.g. database server)</li>
<li>Infrastructrue as a service (<strong>IaaS</strong>) — 인터넷을 통해 사용 가능한 서버나 저장장치 (e.g. 생산 데이터의 백업 복사본을 만들기 위한 저장장치)
<img src="https://velog.velcdn.com/images/hee-suh/post/2edba7d6-781e-4583-b000-77daea5db7b1/image.png" alt="Figure 1.24 Cloud computing."><pre><code>Figure 1.24 Cloud computing.</code></pre></li>
</ul>
</li>
<li><p><strong>실시간 임베디드 시스템</strong>
내장형 시스템은 현재 가장 유행하는 컴퓨터의 형태다. 이 장치들은 자동차 엔진, 공장용 로봇, 광학 드라이브, 전자파 오븐 등 어느 곳에서나 볼 수 있으며, 아주 특정한 작업만을 수행하는 경향이 있다. 일반적으로 사용자 인터페이스가 거의 없으며, 주로 자동차 엔진이나 로봇 팔과 같은 하드웨어 장치들을 모니터링하고 관리한다.</p>
</li>
</ul>
<h1 id="open-source-operating-system">Open-Source Operating System</h1>
<blockquote>
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Heckert_GNU_white.svg/1920px-Heckert_GNU_white.svg.png" alt="https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Heckert_GNU_white.svg/1920px-Heckert_GNU_white.svg.png" width="40px" />

</blockquote>
<h4 id="gnulinux">GNU/Linux</h4>
<p>소프트웨어 사용 및 재배포를 제한하려는 움직임에 대응하기 위해 1984년 Richard Stallman은 GNU라는 무료 UNIX 호환 운영체제(”GNU’s Not Unix!”의 약어)를 개발하기 시작했다. 자유 소프트웨어는 다음과 같은 네 가지 자유가 보장되어야 한다고 주장한다. (1) 자유롭게 소프트웨어를 실행시킬 권리, (2) 소스 코드를 분석하고 수정할 권리, (3) 코드 수정 없이 배포하거나 판매할 권리 또는 (4) 코드를 수정하여 배포하거나 판매할 권리. 1985년에 Stallman은 모든 소프트웨어가 자유로워야 한다고 주장하는 GNU 선언문을 발표하였다. 또한 자유 소프트웨어의 사용 및 개발을 장려하기 위해 <strong>자유 소프트웨어 재단</strong>(<strong>FSF</strong>)을 설립하였다.</p>
<blockquote>
</blockquote>
<p>1991년 핀란드의 Linus Torvalds 학생은 GNU 컴파일러와 도구를 사용하여 초보적인 UNIX 유사 커널을 출시했으며 전 세계에 공동 개발을 요청하였다. 1992년에 GLP에 따라 Linux를 무료 소프트웨어로 릴리스했다.</p>
<blockquote>
</blockquote>
<p>그 결과로 만들어진 GNU/Linux 운영체제(커널만 말할 때는 Linux라고 하지만 GNU 도구를 포함한 전체 운영체제는 GNU/Linux라고 부름)는 시스템의 수백 가지의 고유한 <strong>배포판</strong> 또는 사용자 맞춤 빌드를 생성하였다. 주요 배포판에는 Red Hat, Fedora, Debian, Slackware 및 Ubuntu가 있다. 배포판은 기능, 유틸리티, 설치된 응용 프로그램, 하드웨어 지원, 사용자 인터페이스 및 목적에 따라 다르다. 예를 들어, Red Hat Enterprise Linux는 대규모 상업적 용도로 사용된다.</p>
<blockquote>
</blockquote>
<p>시스템에서 Linux를 수행할 수 있는 간단하고 무료인 방법은 다음과 같다.</p>
<blockquote>
</blockquote>
<ol>
<li>Virtualbox VMM 도구를 다운로드 받아 시스템에 설치한다.
<a href="https://www.virtualbox.org/">https://www.virtualbox.org/</a></li>
<li>CD와 같은 설치 이미지를 기반으로 운영체제를 처음부터 새로 설치하거나, 더 빠르게 설치하고 실행할 수 있는 미리 설치된 운영체제 이미지를 선택할 수 있는 사이트를 이용한다.
<a href="http://virtualboxes.org/images/">http://virtualboxes.org/images/</a></li>
<li>Virtualbox 내에서 가상 머신을 부팅한다.</li>
</ol>
<h1 id="references">References</h1>
<ul>
<li><a href="https://www.academia.edu/42880365/Operating_System_Concepts_10th_Edition"><strong>Operating System Concepts 10th</strong></a><ul>
<li><strong>PART ONE OVERVIEW</strong><ul>
<li>Chapter 1 Introduction</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98#curriculum"><strong>운영체제 공룡책 전공강의 | 주니온 - 인프런</strong></a><ul>
<li><strong>섹션 1. Chapter 1-2. Introduction &amp; O/S Structures</strong><ul>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/unit/63027">01. 운영체제가 뭐길래?</a></li>
<li><a href="https://www.inflearn.com/course/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B3%B5%EB%A3%A1%EC%B1%85-%EC%A0%84%EA%B3%B5%EA%B0%95%EC%9D%98/unit/63028">02. 운영체제의 개념과 구조: Chapter 1-2. Introduction &amp; O/S structures.</a></li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[useEffect()]]></title>
            <link>https://velog.io/@hee-suh/useEffect</link>
            <guid>https://velog.io/@hee-suh/useEffect</guid>
            <pubDate>Thu, 06 Jun 2024 08:14:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://jser.dev/">JSer.dev</a>의 <a href="https://jser.dev/series/react-source-code-walkthrough">React Internals Deep Dive</a>를 번역하여 정리한 글입니다.</p>
</blockquote>
<blockquote>
<p>⚠️ <a href="https://react.dev/blog/2024/04/25/react-19">React@19</a>의 <a href="https://github.com/facebook/react/commit/7608516479fb85bc40aa73d8a31a0c93397ee6ff">commit 7608516</a>을 기반으로 작성되었으며, 최신 버전에서는 구현이 변경되었을 수 있습니다.</p>
</blockquote>
<h4 id="📝-how-does-useeffect-work-internally-in-react">📝 <a href="https://jser.dev/2023-07-08-how-does-useeffect-work">How does useEffect() work internally in React?</a></h4>
<p><code>useEffect()</code>는 <code>useState()</code> 다음으로 가장 많이 사용되는 hook일 것이다. 내부 동작 원리를 알아보자.</p>
<pre><code class="language-ts">useEffect(() =&gt; {
    // ...
}, [deps]);</code></pre>
<p><code>useEffect()</code> 실행 시 render와 commit 단계에서 호출되는 주요 함수는 다음과 같다.</p>
<ul>
<li><p><strong>render phase</strong><br>  $if$ initial mount <code>mountEffect()</code> initial mount
  $else$ $if$ re-render <code>updateEffect()</code></p>
</li>
<li><p><strong>commit phase</strong>
  <code>commitPassiveUnmountEffects()</code> (only if re-render) ⇒ <code>commitPassiveMountEffects()</code> (both initial mount &amp; re-render)</p>
</li>
</ul>
<h1 id="1-useeffect-in-initial-mount">1. <code>useEffect()</code> in initial mount.</h1>
<p><code>useEffect()</code>는 initial mount에서 <code>mountEffect()</code>를 사용한다.</p>
<pre><code class="language-ts">function mountEffect(
  create: () =&gt; (() =&gt; void) | void,
  deps: Array&lt;mixed&gt; | void | null,
): void {
  return mountEffectImpl(
    PassiveEffect | PassiveStaticEffect, // 📌 이 flag는 Layout Effects와의 차이점을 구분하는 데 중요하다.
    HookPassive,
    create,
    deps,
  );
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {
  // 📌 새로운 hook을 생성한다.
  const hook = mountWorkInProgressHook();

  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.flags |= fiberFlags;
  // 📌 pushEffect()가 만든 Effect 객체를 hook에 설정(set)한다.
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags, // 📌 HookHasEffect flag는 initial mount에 이 effect를 실행해야 한다는 것을 의미하기에 중요하다.
    create,
    undefined,
    nextDeps,
  );
}</code></pre>
<pre><code class="language-ts">function pushEffect(tag, create, destroy, deps) {
  const effect: Effect = {
    tag,     // 📌 tag는 이 effect의 실행 여부를 표시하는 데 사용되기 때문에 중요하다.
    create,  // 📌 전달한 callback이다. 
    destroy, // 📌 callback에서 return한 cleanup 함수다.
    deps,    // 📌 전달한 dependency array다.
    // Circular
    next: (null: any),  // 📌 하나의 컴포넌트에 여러 effect가 있는 경우, 이를 연결(chain)한다.
  };
  let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
  if (componentUpdateQueue === null) {
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    // 📌 Effect는 fiber에 있는 updateQueue에 저장된다.
    // 이는 hooks의 memoizedState와 다르다는 점을 유의하자.
    currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      componentUpdateQueue.lastEffect = effect;
    }
  }
  return effect;
}</code></pre>
<p>Initial mount의 경우, <code>useEffect()</code>가 필요한 flag를 사용하여 Effect 객체를 생성하는 것을 볼 수 있다. 여기에서 생성된 passive effects는 commit phase에서 스케줄러에 의해 처리된다.</p>
<h1 id="2-useeffect-in-re-render">2. <code>useEffect()</code> in re-render</h1>
<pre><code class="language-ts">function updateEffect(
  create: () =&gt; (() =&gt; void) | void,
  deps: Array&lt;mixed&gt; | void | null,
): void {
  return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps): void {
  // 📌 현재 current를 가져온다.
  const hook = updateWorkInProgressHook();

  const nextDeps = deps === undefined ? null : deps;
  let destroy = undefined;
  if (currentHook !== null) {
    // 📌 effect hook의 memoizedState가 Effect 객체라는 사실을 기억하자.
    const prevEffect = currentHook.memoizedState;

    destroy = prevEffect.destroy;
    if (nextDeps !== null) {
      const prevDeps = prevEffect.deps;
      // 📌 deps가 변경되지 않으면, 아무것도 하지 않고 Effect 객체만 다시 생성한다.
      // Effect 객체를 다시 생성하는 이유는, updateQueue를 다시 생성하고
      // 업데이트된 create()를 가져와야 하기 때문이다.
      // 여기에서 이전 destroy()를 사용하고 있다는 점을 확인하자.
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
        return;
      }
    }
  }
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags, // 📌 deps가 변경되면, HookHasEffect는 이 effect가 실행되어야함을 표시한다.
    create,
    destroy,
    nextDeps,
  );
}</code></pre>
<p>deps 배열이 어떻게 작동하는지 확인했다. re-render에서는 항상 Effect 객체를 다시 생성하고, deps가 변경된 경우에만 생성된 Effect에 이전 cleanup 함수를 사용하여 실행되어야 함을 표시한다.</p>
<h1 id="3-when-and-how-does-effects-get-run-and-cleaned-up">3. When and how does Effects get run and cleaned up?</h1>
<p><code>useEffect()</code>는 단지 fiber node에 추가 데이터 구조를 생성할 뿐이라는 것을 알았다.</p>
<p>이제 이러한 Effect 객체들이 어떻게 처리되는지 살펴보자.</p>
<h2 id="31-flusing-of-passive-effects-are-triggered-in-commitroot">3.1 Flusing of passive effects are triggered in <code>commitRoot()</code></h2>
<blockquote>
<h4 id="💡-passive-effect">💡 Passive Effect</h4>
</blockquote>
<p>두 가지 유형의 effect가 존재한다.</p>
<blockquote>
</blockquote>
<ul>
<li><code>useEffect</code> = &quot;effects&quot;</li>
<li><code>useLayoutEffect</code> = &quot;layout effects&quot;<blockquote>
</blockquote>
“effects”라고 하면 둘 중에 어떤 effect를 의미하는 건지 불명확할 때가 있어서, <code>useEffect</code>로부터 생성된 effects를 “passive effects”라고 부르기도 한다.<blockquote>
</blockquote>
Cf. <a href="https://github.com/reactwg/react-18/discussions/46#discussioncomment-847365">https://github.com/reactwg/react-18/discussions/46#discussioncomment-847365</a></li>
</ul>
<p>두 fiber 트리를 비교(reconciliation)하여 diffing 결과를 얻은 후, commit 단계에서 host DOM에 변경 사항을 반영해야 한다. passive effects의 flushing(한꺼번에 반영)을 시작하는 코드를 쉽게 찾을 수 있다.</p>
<pre><code class="language-ts">function commitRootImpl(
  root: FiberRoot,
  recoverableErrors: null | Array&lt;CapturedValue&lt;mixed&gt;&gt;,
  transitions: Array&lt;Transition&gt; | null,
  renderPriorityLevel: EventPriority,
) {
  // If there are pending passive effects, schedule a callback to process them.
  // Do this as early as possible, so it is queued before anything else that
  // might get scheduled in the commit phase. (See #16714.)
  if (
    (finishedWork.subtreeFlags &amp; PassiveMask) !== NoFlags ||
    (finishedWork.flags &amp; PassiveMask) !== NoFlags
  ) {
    if (!rootDoesHavePassiveEffects) {
      rootDoesHavePassiveEffects = true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
      ...
      // 📌 여기에서 useEffect()로부터 생성된 passive effects를 flush한다.
      // 스케줄러에서 지금 당장 말고, 다음 tick에서 flush하도록 예약한다.
      scheduleCallback(NormalSchedulerPriority, () =&gt; {
        flushPassiveEffects();
        // This render triggered passive effects: release the root cache pool
        // *after* passive effects fire to avoid freeing a cache pool that may
        // be referenced by a node in the tree (HostRoot, Cache boundary etc)
        return null;
      });
    }
  }
  ...
}</code></pre>
<h2 id="32-flushpassiveeffects">3.2 <code>flushPassiveEffects()</code></h2>
<pre><code class="language-ts">function flushPassiveEffectsImpl() {
  if (rootWithPendingPassiveEffects === null) {
    return false;
  }

  // Cache and clear the transitions flag
  const transitions = pendingPassiveTransitions;
  pendingPassiveTransitions = null;

  const root = rootWithPendingPassiveEffects;
  const lanes = pendingPassiveEffectsLanes;
  rootWithPendingPassiveEffects = null;
  pendingPassiveEffectsLanes = NoLanes;

  const prevExecutionContext = executionContext;
  executionContext |= CommitContext;

  // 📌 여기에서 effect cleanup이 먼저 실행되고, callback이 나중에 실행된다는 것을 확인할 수 있다.
  commitPassiveUnmountEffects(root.current);
  commitPassiveMountEffects(root, root.current, lanes, transitions);
  ...
}
</code></pre>
<h2 id="33-commitpassiveunmounteffects">3.3 <code>commitPassiveUnmountEffects()</code></h2>
<pre><code class="language-ts">export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
  setCurrentDebugFiberInDEV(finishedWork);
  commitPassiveUnmountOnFiber(finishedWork);
  resetCurrentDebugFiberInDEV();
}
function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
      // 📌 children의 effect가 먼저 clean up된다는 것을 알 수 있다.
      recursivelyTraversePassiveUnmountEffects(finishedWork);

      if (finishedWork.flags &amp; Passive) {
        commitHookPassiveUnmountEffects(
          finishedWork,
          finishedWork.return,
          HookPassive | HookHasEffect, // 📌 HookHasEffect flag는 deps가 변경되지 않으면, callback이 실행되지 않도록 한다.
        );
      }
      break;
    }
    ...
  }
}
function commitHookPassiveUnmountEffects(
  finishedWork: Fiber,
  nearestMountedAncestor: null | Fiber,
  hookFlags: HookFlags,
) {
  if (shouldProfile(finishedWork)) {
    startPassiveEffectTimer();
    commitHookEffectListUnmount(
      hookFlags,
      finishedWork,
      nearestMountedAncestor,
    );
    recordPassiveEffectDuration(finishedWork);
  } else {
    commitHookEffectListUnmount(
      hookFlags,
      finishedWork,
      nearestMountedAncestor,
    );
  }
}
function commitHookEffectListUnmount(
  flags: HookFlags,
  finishedWork: Fiber,
  nearestMountedAncestor: Fiber | null,
) {
  const updateQueue: FunctionComponentUpdateQueue | null =
    (finishedWork.updateQueue: any);
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      if ((effect.tag &amp; flags) === flags) {
        // Unmount
        const inst = effect.inst;
        const destroy = inst.destroy;
        if (destroy !== undefined) {
          inst.destroy = undefined;
          safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
        }
      }
      effect = effect.next;
    } while (effect !== firstEffect);
    // 📌 updateQueue의 있는 모든 Effect를 돌면서
    // flag를 이용하여 필요한 것들을 필터링한다.
  }
}
function safelyCallDestroy(
  current: Fiber,
  nearestMountedAncestor: Fiber | null,
  destroy: () =&gt; void,
) {
  try {
    destroy();
  } catch (error) {
    captureCommitPhaseError(current, nearestMountedAncestor, error);
  }
}</code></pre>
<h2 id="34-commitpassivemounteffects">3.4 <code>commitPassiveMountEffects()</code></h2>
<p><code>commitPassiveMountEffects()</code>도 <code>commitPassiveUnmountEffects()</code>와 같은 방식으로 작동한다.</p>
<pre><code class="language-tsx">export function commitPassiveMountEffects(
  root: FiberRoot,
  finishedWork: Fiber,
  committedLanes: Lanes,
  committedTransitions: Array&lt;Transition&gt; | null,
): void {
  setCurrentDebugFiberInDEV(finishedWork);
  commitPassiveMountOnFiber(
    root,
    finishedWork,
    committedLanes,
    committedTransitions,
  );
  resetCurrentDebugFiberInDEV();
}
function commitPassiveMountOnFiber(
  finishedRoot: FiberRoot,
  finishedWork: Fiber,
  committedLanes: Lanes,
  committedTransitions: Array&lt;Transition&gt; | null,
): void {
  const flags = finishedWork.flags;
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
      // 📌 children의 effect가 먼저 실행된다는 것을 알 수 있다.
      recursivelyTraversePassiveMountEffects(
        finishedRoot,
        finishedWork,
        committedLanes,
        committedTransitions,
      );
      if (flags &amp; Passive) {
        commitHookPassiveMountEffects(
          finishedWork,
          HookPassive | HookHasEffect, // 📌 HookHasEffect flag는 deps가 변경되지 않으면, callback이 실행되지 않도록 한다.
        );
      }
      break;
    }
    ...
  }
}
function commitHookPassiveMountEffects(
  finishedWork: Fiber,
  hookFlags: HookFlags,
) {
  ...
  try {
    commitHookEffectListMount(hookFlags, finishedWork);
  } catch (error) {
    captureCommitPhaseError(finishedWork, finishedWork.return, error);
  }
}
function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
  const updateQueue: FunctionComponentUpdateQueue | null =
    (finishedWork.updateQueue: any);
  const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  if (lastEffect !== null) {
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      if ((effect.tag &amp; flags) === flags) {
        // Mount
        const create = effect.create;
        const inst = effect.inst;
        const destroy = create(); // 📌 callback이 여기에서 실행된다!
        inst.destroy = destroy;
      }
      effect = effect.next;
    } while (effect !== firstEffect);
    // 📌 여기에서도, 필요한 Effects만 필터링해서 실행한다.
  }
}
</code></pre>
<h1 id="4-summary">4. Summary</h1>
<p><code>useEffect()</code>의 작동 원리는 다음과 같다.</p>
<ol>
<li><p><code>useEffect()</code>는 Effect 객체를 생성하고 fiber에 저장한다.</p>
<ul>
<li>Effect의 <code>tag</code> 를 통해 해당 Effect의 실행이 필요한지 여부를 나타낸다.</li>
<li>Effect의 <code>create()</code>는 useEffect에 전달하는 첫 번째 인자인 callback이다.</li>
<li>Effect의 <code>destroy()</code>는 <code>create()</code>의 cleanup으로, <code>create()</code>가 실행될 때만 설정된다.</li>
</ul>
</li>
<li><p><code>useEffect()</code>는 매번 새로운 Effect 객체를 생성하되, deps 배열이 변경될 때 다른 <code>tag</code>를 설정한다.</p>
</li>
<li><p>host DOM에 업데이트를 commit할 때, 다음 tick의 job은 <code>tag</code>를 기반으로 모든 Effects를 다시 실행하도록 예약된다.</p>
<ul>
<li>child 컴포넌트에 있는 Effects가 먼저 처리된다.</li>
<li>cleanup이 callback보다 먼저 실행된다.</li>
</ul>
</li>
</ol>
<h1 id="5-quiz-challenge">5. Quiz Challenge</h1>
<p><a href="https://bigfrontend.dev/react-quiz">React quizzes  | BFE.dev - prepare for Front-End job interviews.</a></p>
<ul>
<li><a href="https://bigfrontend.dev/react-quiz/useeffect-iii">https://bigfrontend.dev/react-quiz/useeffect-iii</a></li>
<li><a href="https://bigfrontend.dev/react-quiz/useEffect">https://bigfrontend.dev/react-quiz/useEffect</a></li>
<li><a href="https://bigfrontend.dev/react-quiz/useEffect-II">https://bigfrontend.dev/react-quiz/useEffect-II</a></li>
</ul>
<h1 id="additional-resources">Additional Resources</h1>
<blockquote>
<h4 id="💡-why-are-effects-named-as-passive-effects-in-react-source-code">💡 <a href="https://stackoverflow.com/questions/67329882/why-are-effects-named-as-passive-effects-in-react-source-code">Why are effects named as &quot;passive effects&quot; in React source code?</a></h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/fb1b593c-beec-47ef-93e1-4bf595513e3c/image.png" alt="Cf. https://github.com/reactwg/react-18/discussions/46#discussioncomment-847365"></p>
<blockquote>
</blockquote>
<p>Cf. <a href="https://github.com/reactwg/react-18/discussions/46#discussioncomment-847365">https://github.com/reactwg/react-18/discussions/46#discussioncomment-847365</a></p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://jser.dev/2023-07-08-how-does-useeffect-work">[JSer.dev] How does useEffect() work internally in React?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Debugging] Initial mount and re-render]]></title>
            <link>https://velog.io/@hee-suh/Debugging-Initial-mount-and-re-render</link>
            <guid>https://velog.io/@hee-suh/Debugging-Initial-mount-and-re-render</guid>
            <pubDate>Tue, 04 Jun 2024 15:41:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>⚠️ <a href="https://github.com/facebook/react/releases/tag/v18.2.0">React@18.2.0</a>을 기반으로 작성되었으며, 최신 버전에서는 구현이 변경되었을 수 있습니다.</p>
</blockquote>
<h4 id="💡-fiber-tree가-initial-mount와-re-render-시에-어떻게-구성되는지-개발자-도구를-통해-확인해보자">💡 Fiber Tree가 initial mount와 re-render 시에 어떻게 구성되는지 개발자 도구를 통해 확인해보자.</h4>
<h2 id="1-initial-mount">1. Initial mount</h2>
<h3 id="11-renderrootsync">1.1 <code>renderRootSync()</code></h3>
<p><code>root.current</code>: <code>FiberNode</code> — 비어있는 Fiber 트리 (<code>child</code>: <code>null</code>)</p>
<p><code>root.current.alternate</code>: <code>null</code></p>
<blockquote>
<p>1.1 Initial mount — <code>renderRootSync()</code></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/4681c09f-ac26-4539-86cc-0168777367d7/image.png" alt="1.1 Initial mount — `renderRootSync()`"></p>
<h3 id="12-preparefreshstack">1.2 <code>prepareFreshStack()</code></h3>
<p><code>root.current.alternate</code>: <strong><code>FiberNode</code></strong> — <strong>비어있는 Fiber 트리 생성!</strong></p>
<blockquote>
<p>1.2 Initial mount — <code>prepareFreshStack()</code></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/8f2334ad-25e3-44a0-8e4d-0adaa1c9a969/image.png" alt="1.2 Initial mount — `prepareFreshStack()`"></p>
<h3 id="13-performconcurrentworkonroot">1.3 <code>performConcurrentWorkOnRoot()</code></h3>
<p><code>root.current.alternate</code>: <code>FiberNode</code> — <strong>Fiber 트리 완성!</strong></p>
<blockquote>
<p>1.3 Initial mount — <code>performConcurrentWorkOnRoot()</code> (1)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/df5c72a0-a392-4dee-8e6b-e79a2e7e4145/image.png" alt="1.3 Initial mount — `performConcurrentWorkOnRoot()` (1)"></p>
<p><em><code>root.current</code>: <code>FiberNode</code> — 여전히 비어있는 Fiber 트리</em></p>
<blockquote>
<p>1.3 Initial mount — <code>performConcurrentWorkOnRoot()</code> (2)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/99e5cd99-0e65-4f63-8596-b6aa11678839/image.png" alt="1.3 Initial mount — `performConcurrentWorkOnRoot()` (2)"></p>
<h3 id="14-commitroot">1.4 <code>commitRoot()</code></h3>
<p><strong><code>root.current</code> 업데이트 및 화면에 commit 완료!</strong></p>
<p><strong>⇒ workInProgress였던 <code>root.current.alternate</code>과, <code>root.current</code>가 가리키는 트리 서로 switch!</strong> </p>
<table>
<thead>
<tr>
<th><img src="https://jser.dev/static/initial-mount/26.avif" alt="출처: https://jser.dev/2023-07-14-initial-mount/#5-summary"></th>
<th><img src="https://jser.dev/static/initial-mount/27.avif" alt="출처: https://jser.dev/2023-07-14-initial-mount/#5-summary"></th>
</tr>
</thead>
<tbody><tr>
<td>```</td>
<td></td>
</tr>
<tr>
<td>출처: <a href="https://jser.dev/2023-07-14-initial-mount/#5-summary">https://jser.dev/2023-07-14-initial-mount/#5-summary</a></td>
<td></td>
</tr>
<tr>
<td>```</td>
<td></td>
</tr>
</tbody></table>
<p><code>root.current</code>: <code>FiberNode</code> — <strong><code>root.current.alternate</code>가 만든 Fiber 트리 포인트!</strong></p>
<blockquote>
<p>1.4 Initial mount — <code>commitRoot()</code> (1)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/c9c5f73c-9902-4a38-8843-5bec9f2842a5/image.png" alt="1.4 Initial mount — `commitRoot()` (1)"></p>
<p><code>root.current.alternate</code>: <code>FiberNode</code> — <strong><code>root.current</code>가 가리켰던 비어있는 Fiber 트리 포인트!</strong></p>
<blockquote>
<p>1.4 Initial mount — <code>commitRoot()</code> (2)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/16dfbc49-e872-4f17-a364-6785b98c0f2a/image.png" alt="1.4 Initial mount — `commitRoot()` (2)"></p>
<h2 id="2-re-render">2. Re-render</h2>
<h3 id="21-renderrootsync">2.1 <code>renderRootSync()</code></h3>
<p><code>root.current</code>: <code>FiberNode</code> — initial mount에서 완성한 Fiber 트리</p>
<p><code>root.current.alternate</code>: <code>FiberNode</code> — 비어있는 Fiber 트리 (<code>child</code>: <code>null</code>)</p>
<blockquote>
<p>2.1 Re-render — <code>renderRootSync()</code></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/8880534b-1e2d-4f89-b1a1-d7b23b37faa6/image.png" alt="2.1 Re-render — `renderRootSync()`"></p>
<h3 id="22-preparefreshstack">2.2 <code>prepareFreshStack()</code></h3>
<p><code>root.current.alternate</code>: <strong><code>FiberNode</code></strong> — <code>root.current</code>와 같은 모양의 Fiber 트리로 업데이트!</p>
<p>Cf. <strong><code>prepareFreshStack()</code>에서 호출하는 <code>createWorkInProgress()</code>에서 <code>root.current</code> 트리 복사!</strong></p>
<blockquote>
<p>💻 src: ReactFiber.js - createWorkInProgress()</p>
</blockquote>
<pre><code class="language-ts">export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    // We use a double buffering pooling technique because we know that we&#39;ll
    // only ever need at most two versions of a tree. We pool the &quot;other&quot; unused
    // node that we&#39;re free to reuse. This is lazily created to avoid allocating
    // extra objects for things that are never updated. It also allow us to
    // reclaim the extra memory if needed.
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
&gt;
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps;
    // Needed because Blocks store data on type.
    workInProgress.type = current.type;
&gt;
    // We already have an alternate.
    // Reset the effect tag.
    workInProgress.flags = NoFlags;
&gt;
    // The effects are no longer valid.
    workInProgress.subtreeFlags = NoFlags;
    workInProgress.deletions = null;
&gt;
  // Reset all effects except static ones.
  // Static effects are not specific to a render.
  workInProgress.flags = current.flags &amp; StaticMask;
  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;
&gt;
  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;
&gt;
  // Clone the dependencies object. This is mutated during the render phase, so
  // it cannot be shared with the current fiber.
  const currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
      ? null
      : {
          lanes: currentDependencies.lanes,
          firstContext: currentDependencies.firstContext,
        };
&gt;
  // These will be overridden during the parent&#39;s reconciliation
  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;
  workInProgress.refCleanup = current.refCleanup;
&gt;
  return workInProgress;
}</code></pre>
<blockquote>
<p>2.2 Re-render — <code>prepareFreshStack()</code></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/e8f80cf4-98a9-44d1-90ca-a867b0a6fcce/image.png" alt="2.2 Re-render — `prepareFreshStack()`"></p>
<h3 id="23-performconcurrentworkonroot">2.3 <code>performConcurrentWorkOnRoot()</code></h3>
<p><code>root.current.alternate</code>: <strong><code>FiberNode</code></strong> — 트리에 Effect 반영 (e.g. TextNode 0 → 1)</p>
<blockquote>
<p>2.3 Re-render — <code>performConcurrentWorkOnRoot()</code> (1)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/c241b947-269e-4e2b-b9b4-f75f10482d83/image.png" alt="2.3 Re-render — `performConcurrentWorkOnRoot()` (1)"></p>
<p><em><code>root.current</code>: <code>FiberNode</code> — initial mount에서 완성한 Fiber 트리 그대로</em></p>
<blockquote>
<p>2.3 Re-render — <code>performConcurrentWorkOnRoot()</code> (2)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/a41df0e2-1a6c-4379-aec6-0775d12b15c5/image.png" alt="2.3 Re-render — `performConcurrentWorkOnRoot()` (2)"></p>
<h3 id="24-commitroot">2.4 <code>commitRoot()</code></h3>
<p><strong><code>root.current</code> 업데이트 및 화면에 commit 완료!</strong></p>
<p><strong>⇒ workInProgress였던 <code>root.current.alternate</code>과, <code>root.current</code>가 가리키는 트리 서로 switch!</strong> </p>
<blockquote>
<p>2.4 Re-render — <code>commitRoot()</code> (1)</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/d48025fb-f1fc-4015-a57c-7d3c903a2282/image.png" alt="2.4 Re-render — `commitRoot()` (1)"></p>
<p><code>root.current.alternate</code>: <code>FiberNode</code> — <strong><code>root.current</code>가 가리켰던 Fiber 트리 포인트!</strong></p>
<blockquote>
<p>2.4 Re-render — <code>commitRoot()</code> (2)</p>
</blockquote>
<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F2ab0f16f-ced4-4e3e-aff7-9cbe6fdeb5cb%2Fe3c7f3a3-d026-4778-a6d6-fb1dcd24a0d8%2F%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2024-05-07_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.24.36.png?table=block&id=3e5c31c7-e0c2-4adf-aeec-6ea1e869f977&spaceId=2ab0f16f-ced4-4e3e-aff7-9cbe6fdeb5cb&width=2000&userId=d89edac7-7359-4f30-a38e-a572d6a07e37&cache=v2" alt="2.4 Re-render — `commitRoot()` (2)"></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Re-render]]></title>
            <link>https://velog.io/@hee-suh/React-Internals-Deep-Dive-3-Re-render</link>
            <guid>https://velog.io/@hee-suh/React-Internals-Deep-Dive-3-Re-render</guid>
            <pubDate>Tue, 04 Jun 2024 15:14:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://jser.dev/">JSer.dev</a>의 <a href="https://jser.dev/series/react-source-code-walkthrough">React Internals Deep Dive</a>를 번역하여 정리한 글입니다.</p>
</blockquote>
<blockquote>
<p>⚠️ <a href="https://react.dev/blog/2024/04/25/react-19">React@19</a>의 <a href="https://github.com/facebook/react/commit/7608516479fb85bc40aa73d8a31a0c93397ee6ff">commit 7608516</a>을 기반으로 작성되었으며, 최신 버전에서는 구현이 변경되었을 수 있습니다.</p>
</blockquote>
<h4 id="📝-how-does-react-re-render-internally">📝 <a href="https://jser.dev/2023-07-18-how-react-rerenders/">How does React re-render internally?</a></h4>
<p>Initial mount에서는 전체 DOM을 처음부터 생성하는 반면, initial mount 이후의 리렌더링의 경우, React에서 재조정(reconciliation)이라는 과정을 통해 DOM을 최대한 많이 재사용하려고 한다.</p>
<p><a href="https://jser.dev/demos/react/overview/re-render">Demo</a>를 이용하여, 리렌더링의 과정을 Trigger, Render, Commit 세 단계로 나눠서 확인해보자.</p>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/4cbdcbee-0a0e-498f-8d01-c4786aa05569/image.png" alt="출처: https://react.dev/learn/render-and-commit Illustrated by Rachel Lee Nabors"></p>
<pre><code>출처: https://react.dev/learn/render-and-commit Illustrated by Rachel Lee Nabors</code></pre><blockquote>
<p>💻 Demo Code</p>
</blockquote>
<pre><code class="language-ts">import { useState } from &#39;react&#39;
&gt;
function Link() {
  return &lt;a href=&quot;https://jser.dev&quot;&gt;jser.dev&lt;/a&gt;;
}
&gt;
function Component() {
  const [count, setCount] = useState(0);
  return (
    &lt;div&gt;
    &lt;button onClick={() =&gt; setCount((count) =&gt; count + 1)}&gt;
      click me - {count} 
    &lt;/button&gt; ({count % 2 === 0 ? &lt;span&gt;even&lt;/span&gt; : &lt;b&gt;odd&lt;/b&gt;})
    &lt;/div&gt;
  );
}
&gt;
export default function App() {
  return (
    &lt;div&gt;
      &lt;Link /&gt;
      &lt;br /&gt;
      &lt;Component /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h1 id="1-re-render-in-trigger-phase">1. Re-render in Trigger phase</h1>
<p>React는 initial mount에서 다음과 같은 Fiber 트리와 DOM 트리를 구성한다.</p>
<p><img src="https://jser.dev/static/rerender/1.avif" alt="https://jser.dev/2023-07-18-how-react-rerenders/#1-re-render-in-trigger-phase"></p>
<pre><code>출처: https://jser.dev/2023-07-18-how-react-rerenders/#1-re-render-in-trigger-phase</code></pre><h2 id="11-lanes와-childlanes">1.1 <code>lanes</code>와 <code>childLanes</code></h2>
<p>Lane은 보류 중인 작업(pending work)의 우선순위를 나타낸다. </p>
<p>Fiber Node의 경우 다음이 포함된다.</p>
<ul>
<li><code>lanes</code> ⇒ 해당 노드가 보류 중인 작업에 대한 것이다.</li>
<li><code>childLanes</code> =&gt; 해당 노드의 subtree가 보류 중인 작업에 대한 것이다.</li>
</ul>
<p>Cf. <a href="https://jser.dev/react/2022/03/26/lanes-in-react/">What are Lanes in React source code?</a></p>
<p>버튼을 클릭하면 <code>setState()</code>가 호출된다.</p>
<ol>
<li><p>root에서 타겟 fiber까지의 경로는 다음 렌더링에서 확인해야 하는 곳들을 나타내기 위해 <code>lanes</code> 및 <code>childLanes</code>로 표시된다.</p>
</li>
<li><p>업데이트는 <code>ScheduleUpdateOnFiber()</code>에 의해 예약되며, <code>ensureRootIsScheduled()</code>가 호출되고 난 후에야 <code>PerformConcurrentWorkOnRoot()</code>가 스케줄러에서 예약된다. 이는 initial mount와 매우 유사하다.</p>
</li>
</ol>
<p>명심해야 할 중요한 점은 이벤트의 우선순위에 따라 업데이트의 우선순위가 결정된다는 것이다. <code>click</code> 이벤트의 경우 <code>DiscreteEventPriority</code>이며, <code>SyncLane</code>(높은 우선순위)에 매핑된다.</p>
<p><img src="https://jser.dev/static/rerender/2.avif" alt="https://jser.dev/2023-07-18-how-react-rerenders/#11-lanes-and-childlanes"></p>
<pre><code>출처: https://jser.dev/2023-07-18-how-react-rerenders/#11-lanes-and-childlanes</code></pre><h1 id="2-re-render-in-render-phase">2. Re-render in Render phase</h1>
<h2 id="21-기본-렌더링-로직은-initial-mount와-동일하다">2.1 기본 렌더링 로직은 initial mount와 동일하다.</h2>
<p><code>click</code> 이벤트이므로, 렌더링 lane은 SyncLane이며, blocking lane에 해당된다. 그러므로 initial mount에서와 동일하게, <code>performConcurrentWorkOnRoot()</code> 내부에서 concurrent feature가 여전히 활성화되지 않고 동기적으로 작동한다.</p>
<p><em>Cf. concurrent feature가 활성화된 경우를 확인하고 싶다면, 다음 글을 참고하자. <a href="https://jser.dev/2023-05-19-how-does-usetransition-work/">How does useTransition() work internally in React?</a></em></p>
<p>다음은 전체 프로세스를 요약한 코드다.</p>
<pre><code class="language-ts">do {
  try {
    workLoopSync();
    break;
  } catch (thrownValue) {
    handleError(root, thrownValue);
  }
} while (true);

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;

  next = beginWork(current, unitOfWork, subtreeRenderLanes);

  // 📌 Cf. 2.5 memoizedProps vs pendingProps
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    // If this doesn&#39;t spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
}</code></pre>
<p>React는 Fiber 트리를 순회하고 필요한 경우에만 fiber를 업데이트한다.</p>
<h2 id="22-react는-새로운-fiber-nodes를-생성하기-전에-중복-fiber-nodes를-재사용한다">2.2 React는 새로운 Fiber Nodes를 생성하기 전에 중복 Fiber Nodes를 재사용한다.</h2>
<p>Initial mount에서는 Fiber가 처음부터 생성됐다. 하지만 실제로 React는 Fiber Node를 먼저 재사용하려고 한다.</p>
<pre><code class="language-ts">export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  // 📌 current는 현재 버전이고, current의 alternate은 그 이전 버전이다.
  let workInProgress = current.alternate;

  // 📌 처음부터 생성해야 하는 경우 (initial mount)
  if (workInProgress === null) {
    // We use a double buffering pooling technique because we know that we&#39;ll
    // only ever need at most two versions of a tree. We pool the &quot;other&quot; unused
    // node that we&#39;re free to reuse. This is lazily created to avoid allocating
    // extra objects for things that are never updated. It also allow us to
    // reclaim the extra memory if needed.
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    ...
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  // 📌 이전(previous) 버전을 사용할 수 있는 경우 (re-rendering) 
  } else {
    // 📌 재사용이 가능하므로, Fiber Node를 생성하지 않고
    // properties 업데이트만 하면 된다.
    workInProgress.pendingProps = pendingProps;

    // Needed because Blocks store data on type.
    workInProgress.type = current.type;
    // We already have an alternate.
    // Reset the effect tag.
    workInProgress.flags = NoFlags;
    // The effects are no longer valid.
    workInProgress.subtreeFlags = NoFlags;
    workInProgress.deletions = null;
  }
  // Reset all effects except static ones.
  // Static effects are not specific to a render.
  workInProgress.flags = current.flags &amp; StaticMask;
  // 📌 lanes와 childLanes는 복사되었다.
  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;

  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;
  ...
  return workInProgress;
}</code></pre>
<p>FiberRootNode가 <code>current</code>를 통해 현재 Fiber 트리를 가리키고 있기 때문에, current 트리에 없는 모든 Fiber Node는 재사용이 가능하다.</p>
<p>리렌더링 과정에서, 중복되는 <code>HostRoot</code>는 <code>prepareFreshStack()</code>에서 재사용될 것이다.</p>
<pre><code class="language-ts">function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
  ...
  workInProgressRoot = root;
  // 📌 root의 current는 HostRoot의 FiberNode다.
  const rootWorkInProgress = createWorkInProgress(root.current, null);
  ...
  finishQueueingConcurrentUpdates();
  return rootWorkInProgress;
}</code></pre>
<p>따라서 다음과 같이 리렌더링이 시작된다.</p>
<p><img src="https://jser.dev/static/rerender/4.avif" alt="https://jser.dev/2023-07-18-how-react-rerenders/#22-react-reuses-redundant-fiber-nodes-before-creating-new-ones"></p>
<pre><code>출처: https://jser.dev/2023-07-18-how-react-rerenders/#22-react-reuses-redundant-fiber-nodes-before-creating-new-ones</code></pre><h2 id="23-beginwork의-update-분기">2.3 <code>beginWork()</code>의 Update 분기</h2>
<p><code>beginWork()</code>에는 업데이트들을 처리하는 중요한 분기가 있다. (initial mount에서는 다뤄지지 않는다.)</p>
<pre><code class="language-ts">function beginWork(
  // 📌 current는 현재 버전으로, 페인트되어 있다.
  current: Fiber | null,
  // 📌 workInProgress는 새로운 버전으로, 페인트될 것이다.
  workInProgress: Fiber,

  renderLanes: Lanes,
): Fiber | null {
  // 📌 current가 null이 아니라는 것은, NOT initial mount를 의미한다.
  // 이전 버전의 Fiber 노드가 있고, HostComponent인 경우 DOM 노드도 있다.
  // HostComponent의 경우 DOM nodes의 이전 버전도 갖고 있다.
  // 따라서 React는 subtree에서 더 깊은 곳으로 들어가는 것을 피함으로써 - bailout!
  // 최적화할 수 있다. 
  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;
    if (
      // 📌 여기에서 shallowEqual()이 아니라, === 을 사용한다.
      // 이는 React 렌더링의 중요한 동작으로 이어진다.
      oldProps !== newProps ||

      hasLegacyContextChanged() ||
      // Force a re-render if the implementation changed due to hot reload:
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      // If props or context changed, mark the fiber as having performed work.
      // This may be unset if the props are determined to be equal later (memo).
      didReceiveUpdate = true;
    } else {
      // 📌 checkScheduledUpdatedOrContext()는 fibers에 있는 lanes를 체크한다.
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );
      if (
        !hasScheduledUpdateOrContext &amp;&amp;
        // If this is the second pass of an error or suspense boundary, there
        // may not be work scheduled on `current`, so we check for this flag.
        (workInProgress.flags &amp; DidCapture) === NoFlags
      ) {
        // No pending updates or context. Bail out now.
        didReceiveUpdate = false;
        // 📌 이 fiber에 업데이트가 없다면, React는 bailout를 시도하지만,
        // props나 context 변경이 없는 경우에만 가능하다.
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
     ...
    }
  } else {
      // 📌 initial mount에서 다뤘던 mount 분기다.
    didReceiveUpdate = false;
    ...
  }
  workInProgress.lanes = NoLanes;
  switch (workInProgress.tag) {
    ...
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);
    ...
  }
}</code></pre>
<h2 id="24-attemptearlybailoutifnoscheduledupdate-안에-있는-bailout-로직">2.4 <code>attemptEarlyBailoutIfNoScheduledUpdate()</code> 안에 있는 Bailout 로직</h2>
<p>이 함수는 불필요한 경우 렌더링을 더 빨리 중지하려고 한다.</p>
<pre><code class="language-ts">function attemptEarlyBailoutIfNoScheduledUpdate(
  current: Fiber,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  switch (workInProgress.tag) {
    case HostRoot:
      pushHostRootContext(workInProgress);
      const root: FiberRoot = workInProgress.stateNode;
      pushRootTransition(workInProgress, root, renderLanes);

      if (enableCache) {
        const cache: Cache = current.memoizedState.cache;
        pushCacheProvider(workInProgress, cache);
      }
      resetHydrationState();
      break;
    case HostComponent:
      pushHostContext(workInProgress);
      break;
    ...
  }
  return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}</code></pre>
<pre><code class="language-ts">function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  if (current !== null) {
    // Reuse previous dependencies
    workInProgress.dependencies = current.dependencies;
  }
  // Check if the children have any pending work.
  // 📌 여기에서 childLanes가 체크된다.
  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
    // The children don&#39;t have any work either. We can skip them.
    if (enableLazyContextPropagation &amp;&amp; current !== null) {
      // Before bailing out, check if there are any context changes in
      // the children.
      lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
      if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
        return null;
      }
    } else {
      // 📌 fiber 자체와 해당 subtree에 대한 업데이트가 없으면,
      // null을 반환하여 더 깊은 트리로 이동하는 것을 멈출 수 있다.
      return null;
    }
  }
  // This fiber doesn&#39;t have work, but its subtree does. Clone the child
  // fibers and continue.
  // 📌 이름은 클론이지만, 실제로는 새 children 노드를 생성하거나 이전 노드를 재사용한다.
  cloneChildFibers(current, workInProgress);

  // 📌 child를 직접 반환하고, React는 이를 다음 Fiber로 처리한다.
  return workInProgress.child;
}

export function cloneChildFibers(
  current: Fiber | null,
  workInProgress: Fiber,
): void {
  if (current !== null &amp;&amp; workInProgress.child !== current.child) {
    throw new Error(&#39;Resuming work not yet implemented.&#39;);
  }

  if (workInProgress.child === null) {
    return;
  }

  // 📌 cloneChildFibers()에서, child fibers는 이전 버전에서 생성되지만
  // reconciliation 중에 설정되는 새로운 pendingProps를 사용하여 생성된다.
  let currentChild = workInProgress.child;
  let newChild = createWorkInProgress(currentChild, currentChild.pendingProps);
  workInProgress.child = newChild;
  newChild.return = workInProgress;
  while (currentChild.sibling !== null) {
    currentChild = currentChild.sibling;
    newChild = newChild.sibling = createWorkInProgress(
      currentChild,
      currentChild.pendingProps,
    );
    newChild.return = workInProgress;
  }
  newChild.sibling = null;
}</code></pre>
<p>bailout 과정을 요약해보자.</p>
<ol>
<li><p>$if$ fiber에 props/context 변경이 없고, 보류중인 작업이 없다면(empty <code>lanes</code>)</p>
<ul>
<li><p>$if$ children에 보류중인 작업이 없다면(empty <code>childLanes</code>), bailout이 발생하고 React가 트리에서 더 깊이 이동하지 않는다.</p>
</li>
<li><p>$otherwise$ React는 이 fiber를 리렌더링하지 않고 바로 children으로 이동한다.</p>
</li>
</ul>
</li>
<li><p>$otherwise$ React는 먼저 리렌더링을 시도한 다음 children으로 이동한다. </p>
</li>
</ol>
<p>Cf. <a href="https://jser.dev/react/2022/01/07/how-does-bailout-work">How does React bailout work in reconciliation?</a></p>
<h2 id="25-memoizedprops-vs-pendingprops">2.5 <code>memoizedProps</code> vs <code>pendingProps</code></h2>
<p><code>beginWork()</code>에서, <code>workInProgress</code>는 <code>current</code>와 비교(diff)된다. props의 경우, <code>workInProgress.pendingProps</code>와 <code>current.memoizedProps</code>다. <code>memoizedProps</code>를 현재 props로,  <code>pendingProps</code>는 다음 버전의 props라고 생각할 수 있다.</p>
<p>React는 Render 단계에서 새로운 Fiber 트리를 생성한 다음, 현재 Fiber 트리와 비교한다. <code>pendingProps</code>는 실제로 workInProgress 생성을 위한 매개변수임을 알 수 있다. </p>
<pre><code class="language-ts">export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  // 📌 current는 현재 버전이고, current.alternate은 그 이전 버전이다.
  let workInProgress = current.alternate;
    // 📌 처음부터 생성해야 하는 경우 (initial mount)
  if (workInProgress === null) {
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    ...
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  // 📌 이전 버전 재사용이 가능한 경우 (re-rendering)
  } else {
    // 📌 재사용이 가능하므로, Fiber Node를 생성하지 않고
    // 필요한 properties 업데이트만 하고 재사용하면 된다.
    workInProgress.pendingProps = pendingProps;
    ...
  }
  ...
  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;
  ...
  return workInProgress;
}</code></pre>
<p>사실, root FiberNode constructor는 <code>pendingProps</code>를 매개변수로 갖고 있다.</p>
<pre><code class="language-ts">function createFiber(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
): Fiber {
  return new FiberNode(tag, pendingProps, key, mode);
}

function FiberNode(
  this: $FlowFixMe,
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  ...
}</code></pre>
<p>즉, Fiber Node를 만드는 것이 첫 번째 단계고, 나중에 작업해야 한다.</p>
<p>그리고 <code>memoizedProps</code>는 <code>PerformUnitOfWork()</code> 내부에 있는 fiber에 대한 리렌더링이 완료될 때, <code>pendingProps</code>로 설정된다.</p>
<pre><code class="language-ts">function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  if (enableProfilerTimer &amp;&amp; (unitOfWork.mode &amp; ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentDebugFiberInDEV();
  // 📌 작업이 끝나면, memoizedProps가 업데이트된다.
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    // If this doesn&#39;t spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
  ReactCurrentOwner.current = null;
}</code></pre>
<p>이제 데모를 살펴보자.</p>
<ol>
<li><p>React는 HostRoot(lanes: 0, childLanes: 1)에서 작업을 수행한다. HostRoot에는 props가 없고 <code>memoizedProps</code>와 <code>pendingProps</code>는 모두 null이므로,  React는 복제(clone)된 <code>App</code>인 child로 바로 이동한다.</p>
</li>
<li><p>React는 <code>&lt;App/&gt;</code>(lanes: 0, childLanes: 1)에서 작업을 수행한다. App component는 리렌더링되지 않아서 <code>memoizedProps</code>와 <code>pendingProps</code>는 동일하므로, React는 복제된 <code>div</code>인 child로 바로 이동한다.</p>
</li>
<li><p>React는 <code>&lt;div/&gt;</code>(lanes: 0, childLanes: 1)에서 작업을 수행한다. App에서 children을 가져오지만 App은 다시 실행되지 않아서 해당 children(<code>&lt;Link&gt;</code>, <code>&lt;br/&gt;</code> 및 <code>&lt;Component/&gt;</code>) 중 어느 것도 변경되지 않으므로 React는 다시 <code>&lt;Link/&gt;</code>로 바로 이동한다.</p>
</li>
<li><p>React는 <code>&lt;Link/&gt;</code>(lanes: 0, childLanes: 0)에서 작업을 수행한다. 이번에는 React가 더 깊이 들어갈 필요도 없으므로, 여기에서 멈추고 sibling인 <code>&lt;br/&gt;</code>로 이동한다.</p>
</li>
<li><p>React는 <code>&lt;br/&gt;</code>(lanes: 0, childLanes: 0)에서 작업을 수행하고 bailout이 다시 발생하며, React는 <code>&lt;Component/&gt;</code>로 이동한다.</p>
</li>
</ol>
<p>이제 뭔가가 조금 달라졌다. <code>&lt;Component/&gt;</code>의 <code>lanes</code>는 <code>1</code>이다. 이는 React가 <code>updateFunctionComponent(current, workInProgress)</code>에 의해 수행되는 children을 다시 렌더링하고 재조정(reconcile)해야 함을 의미한다.</p>
<p>지금까지 우리는 다음과 같은 상태를 얻었다.</p>
<p><img src="https://jser.dev/static/rerender/10.avif" alt="https://jser.dev/2023-07-18-how-react-rerenders/#25-memoizedprops-vs-pendingprops"></p>
<pre><code>출처: https://jser.dev/2023-07-18-how-react-rerenders/#25-memoizedprops-vs-pendingprops</code></pre><h2 id="26-updatefunctioncomponent는-function-컴포넌트를-리렌더링하고-children을-재조정한다">2.6 <code>updateFunctionComponent()</code>는 function 컴포넌트를 리렌더링하고 children을 재조정한다.</h2>
<pre><code class="language-ts">function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderLanes,
) {

  let context;
  if (!disableLegacyContext) {
    const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
    context = getMaskedContext(workInProgress, unmaskedContext);
  }

  let nextChildren;
  let hasId;
  prepareToReadContext(workInProgress, renderLanes);

  // 📌 Component가 새로운 children을 생성하기 위해 실행된다는 것을 의미한다.
  nextChildren = renderWithHooks(
    current,
    workInProgress,
    Component,
    nextProps,
    context,
    renderLanes,
  );

  hasId = checkDidRenderIdHook();
  if (enableSchedulingProfiler) {
    markComponentRenderStopped();
  }

  if (current !== null &amp;&amp; !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  // React DevTools reads this flag.
  workInProgress.flags |= PerformedWork;
  // 📌 nextChildren을 전달하고, reconcileChildren()이 호출된다.
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);

  return workInProgress.child;
}</code></pre>
<p><code>reconcileChildren()</code>은 initial mount에서 이미 만난 적이 있다. 내부적으로는 children의 type에 따라 약간의 변형이 있는데, 그 중 3가지에 집중해보자.</p>
<p>React에서 새로운 child fiber들을 생성하는 것뿐 아니라 기존 fiber들을 재사용하려고 시도한다는 것을 기억하자.</p>
<pre><code class="language-ts">function reconcileChildFibersImpl(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChild: any,
  lanes: Lanes,
): Fiber | null {
  ...
  // Handle object types
  if (typeof newChild === &#39;object&#39; &amp;&amp; newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE:
        return placeSingleChild(
          // 📌 single child의 경우
          reconcileSingleElement(
            returnFiber,
            currentFirstChild,
            newChild,
            lanes,
          ),
        );
      case REACT_PORTAL_TYPE:
       ...
      case REACT_LAZY_TYPE:
        ...
    }
    // 📌 children이 elements의 배열인 경우
    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }
    ...
  }

  if (
    (typeof newChild === &#39;string&#39; &amp;&amp; newChild !== &#39;&#39;) ||
    typeof newChild === &#39;number&#39;
  ) {
    // 📌 children이 text인 경우
    return placeSingleChild(
      reconcileSingleTextNode(
        returnFiber,
        currentFirstChild,
        &#39;&#39; + newChild,
        lanes,
      ),
    );
  }
  // Remaining cases are all treated as empty.
  return deleteRemainingChildren(returnFiber, currentFirstChild);
}</code></pre>
<p><code>&lt;Component/&gt;</code>의 경우, 단일 <code>div</code>를 리턴하므로, <code>reconcileSingleElement()</code>로 이동하자.</p>
<h2 id="27-reconcilesingleelement">2.7 <code>reconcileSingleElement()</code></h2>
<pre><code class="language-tsx">function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  // 📌 여기서 element는 Component()의 리턴 값으로, &lt;div/&gt;의 element다.
  element: ReactElement,
  lanes: Lanes,
): Fiber {
  const key = element.key;
  let child = currentFirstChild;
  while (child !== null) {
    if (child.key === key) {
      const elementType = element.type;
      if (elementType === REACT_FRAGMENT_TYPE) {
        ...
      } else {
        // 📌 type이 같다면 재사용할 수 있고, 같지 않다면 그냥 deleteChild()를 한다.
        if (child.elementType === elementType || ...) {
          deleteRemainingChildren(returnFiber, child.sibling);
          // 📌 새로운 props와 기존 fiber를 사용해본다.
          // element.props는 &lt;div/&gt;의 props다.
          const existing = useFiber(child, element.props);
          existing.ref = coerceRef(returnFiber, child, element);
          existing.return = returnFiber;
          return existing;
        }
      }
      // Didn&#39;t match.
      deleteRemainingChildren(returnFiber, child);
      break;
    } else {
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }
  ...
}</code></pre>
<p><code>useFiber</code>에서, React는 이전 버전을 생성하거나 재사용한다. 전에 언급했듯이, <code>pendingProps</code>(children을 포함하고 있음)가 설정된다.</p>
<pre><code class="language-ts">function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
  const clone = createWorkInProgress(fiber, pendingProps);
  clone.index = 0;
  clone.sibling = null;
  return clone;
}</code></pre>
<p>따라서 Component가 리렌더링된 후, React는 새로운 <code>&lt;div/&gt;</code>인 child로 이동하며 현재 버전에서 <code>lanes</code>와 <code>childLanes</code>가 모두 비어있다.</p>
<h2 id="28-컴포넌트가-리렌더링되면-해당-subtree는-기본적으로-리렌더링된다">2.8 컴포넌트가 리렌더링되면, 해당 subtree는 기본적으로 리렌더링된다.</h2>
<p><code>&lt;div/&gt;</code>와 해당 children에 예약된 작업이 없으므로, bailout이 발생할 것이라고 생각할 수 있지만, 그렇지 않다.</p>
<p><code>beginWork()</code>에서 <code>memoizedProps</code>와 <code>pendingProps</code>를 체크했던 것을 기억하자.</p>
<pre><code class="language-tsx">const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
  // 📌 여기에서 shallowEqual()이 아니라, === 을 사용한다.
  oldProps !== newProps ||
  hasLegacyContextChanged() || ...
) {
  // If props or context changed, mark the fiber as having performed work.
  // This may be unset if the props are determined to be equal later (memo).
  didReceiveUpdate = true;
} 
</code></pre>
<p>props를 비교할 때, <a href="https://github.com/facebook/react/blob/d779eba4b375134f373b7dfb9ea98d01c84bc48e/packages/shared/shallowEqual.js#L18">shallowEqual</a>이 사용되지 않으며, component가 렌더링될 때마다, React elements를 포함하는 새로운 객체(object)가 생성되므로 <code>pendingProps</code>는 매번 새로 생성된다.</p>
<p><code>&lt;div/&gt;</code>의 경우, <code>Component()</code>가 실행되면 항상 새로운 props를 가져오므로 bailout이 전혀 발생하지 않는다.</p>
<p>따라서 React는 update 분기인 <code>updateHostComponent()</code>로 이동한다.</p>
<h2 id="29-updatehostcomponent">2.9 <code>updateHostComponent()</code></h2>
<pre><code class="language-tsx">function updateHostComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  pushHostContext(workInProgress);

  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }

  const type = workInProgress.type;
  const nextProps = workInProgress.pendingProps;
  const prevProps = current !== null ? current.memoizedProps : null;

  let nextChildren = nextProps.children;
  const isDirectTextChild = shouldSetTextContent(type, nextProps);

  if (isDirectTextChild) {
    // We special case a direct text child of a host node. This is a common
    // case. We won&#39;t handle it as a reified child. We will instead handle
    // this in the host environment that also has access to this prop. That
    // avoids allocating another HostText fiber and traversing it.
    nextChildren = null;
  } else if (prevProps !== null &amp;&amp; shouldSetTextContent(type, prevProps)) {
    // If we&#39;re switching from a direct text child to a normal child, or to
    // empty, we need to schedule the text content to be reset.
    workInProgress.flags |= ContentReset;
  }

  markRef(current, workInProgress);
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}</code></pre>
<p><code>nextChildren</code>은 다음과 같다:</p>
<pre><code class="language-ts">[
  {$$typeof: Symbol(react.element), type: &#39;button&#39;},
  &quot; (&quot;, 
  {$$typeof: Symbol(react.element), type: &#39;b&#39;}, 
  &quot;)&quot;
]</code></pre>
<p>따라서 React는 <code>reconcileChildrenArray()</code>를 이용하여 재조정한다.</p>
<p>그리고 <code>current</code>의 <code>memoizedProps</code>는 다음과 같다.</p>
<pre><code class="language-ts">[
  {$$typeof: Symbol(react.element), type: &#39;button&#39;},
  &quot; (&quot;, 
  {$$typeof: Symbol(react.element), type: &#39;span&#39;}, 
  &quot;)&quot;
]</code></pre>
<h2 id="210-reconcilechildrenarray는-필요에-따라-fiber를-생성하거나-삭제한다">2.10 <code>reconcileChildrenArray()</code>는 필요에 따라 fiber를 생성하거나 삭제한다.</h2>
<p><code>reconcileChildrenArray()</code>는 약간 복잡하다. element의 재정렬이 있는지 체크함으로써 추가 최적화를 하고, <code>key</code>가 존재하면 fiber를 재사용하려고 시도한다.</p>
<p>demo에는 <code>key</code>가 없으므로, 기본 분기로 간다.</p>
<p><em>Cf. <code>key</code>가 있는 경우가 궁금하다면, 다음 글을 참고하자. <a href="https://jser.dev/react/2022/02/08/the-diffing-algorithm-for-array-in-react/">How does ‘key’ work internally? List diffing in React</a></em> </p>
<pre><code class="language-ts">function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array&lt;any&gt;,
    lanes: Lanes,
  ): Fiber | null {
    let resultingFirstChild: Fiber | null = null;
    let previousNewFiber: Fiber | null = null;

    let oldFiber = currentFirstChild;
    let lastPlacedIndex = 0;
    let newIdx = 0;
    let nextOldFiber = null;
    // 📌 children elements에 대해 현재 fiber를 확인한다.
    for (; oldFiber !== null &amp;&amp; newIdx &lt; newChildren.length; newIdx++) {
      if (oldFiber.index &gt; newIdx) {
        nextOldFiber = oldFiber;
        oldFiber = null;
      } else {
        nextOldFiber = oldFiber.sibling;
      }
      // 📌 여기에서 list에 있는 각 fiber는 새로운 props로 확인된다.
      const newFiber = updateSlot(
        returnFiber,
        oldFiber,
        newChildren[newIdx],
        lanes,
      );
      if (newFiber === null) {
        if (oldFiber === null) {
          oldFiber = nextOldFiber;
        }
        break;
      }
      if (shouldTrackSideEffects) {
        if (oldFiber &amp;&amp; newFiber.alternate === null) {
          // We matched the slot, but we didn&#39;t reuse the existing fiber, so we
          // need to delete the existing child.
          // 📌 fiber가 재사용될 수 없다면, Deletion으로 표시된다.
          // commit 단계에서 해당 DOM 노드가 삭제된다.
          deleteChild(returnFiber, oldFiber);
        }
      }
      // 📌 placeChild()는 fiber를 Insertion으로 표시하려고 한다.
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);

      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
      oldFiber = nextOldFiber;
    }

    if (newIdx === newChildren.length) {
      // We&#39;ve reached the end of the new children. We can delete the rest.
      deleteRemainingChildren(returnFiber, oldFiber);
      if (getIsHydrating()) {
        const numberOfForks = newIdx;
        pushTreeFork(returnFiber, numberOfForks);
      }
      return resultingFirstChild;
    }
    ...
    return resultingFirstChild;
  }</code></pre>
<p><code>updateSlot()</code>은 기본적으로 <code>key</code>를 고려하여, 새로운 props로 fiber를 생성하거나 재사용한다.</p>
<pre><code class="language-ts">function updateSlot(
  returnFiber: Fiber,
  oldFiber: Fiber | null,
  newChild: any,
  lanes: Lanes,
): Fiber | null {
  // Update the fiber if the keys match, otherwise return null.
  const key = oldFiber !== null ? oldFiber.key : null;

  if (
    (typeof newChild === &#39;string&#39; &amp;&amp; newChild !== &#39;&#39;) ||
    typeof newChild === &#39;number&#39;
  ) {
    // Text nodes don&#39;t have keys. If the previous node is implicitly keyed
    // we can continue to replace it without aborting even if it is not a text
    // node.
    if (key !== null) {
      return null;
    }
    return updateTextNode(returnFiber, oldFiber, &#39;&#39; + newChild, lanes);
  }

  if (typeof newChild === &#39;object&#39; &amp;&amp; newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE: {
        if (newChild.key === key) {
          return updateElement(returnFiber, oldFiber, newChild, lanes);
        } else {
          return null;
        }
      }
      ...
    }
  }

  return null;
}

function updateElement(
  returnFiber: Fiber,
  current: Fiber | null,
  element: ReactElement,
  lanes: Lanes,
): Fiber {
  const elementType = element.type;
  if (elementType === REACT_FRAGMENT_TYPE) {
    return updateFragment(
      returnFiber,
      current,
      element.props.children,
      lanes,
      element.key,
    );
  }
  if (current !== null) {
    // 📌 재사용될 수 있다.
    if (current.elementType === elementType || ...) {
      // Move based on index
      // 📌 여기에서 useFiber()가 또 등장한다.
      const existing = useFiber(current, element.props);

      existing.ref = coerceRef(returnFiber, current, element);
      existing.return = returnFiber;
      return existing;
    }
  }
  // Insert
  // 📌 type이 달라서 재사용할 수 없다면, fiber를 처음부터 생성한다.
  const created = createFiberFromElement(element, returnFiber.mode, lanes);

  created.ref = coerceRef(returnFiber, current, element);
  created.return = returnFiber;
  return created;
}</code></pre>
<p><code>&lt;div/&gt;</code>의 경우, <code>updateSlot()</code>은 세 children을 성공적으로 재사용했다. <code>current</code>는 <code>span</code>이지만, <code>b</code>를 원하기 때문에 네 번째 child는 재사용되지 않았으며, 따라서 b의 fiber는 처음부터 생성되고 span의 fiber는 <code>deleteChild()</code>에 의해 제거된다. 새로 생성된 <code>b</code>는 <code>placeChild()</code>로 표시된다.   </p>
<h2 id="211-placechild와-deletechild는-fiber를-flag로-표시한다">2.11 <code>placeChild()</code>와 <code>deleteChild()</code>는 fiber를 flag로 표시한다.</h2>
<p><code>Component</code> 아래 있는 <code>&lt;div&gt;</code>의 children의 경우, fiber nodes를 표시하는 두 가지 함수가 있다.</p>
<pre><code class="language-ts">function placeChild(
  newFiber: Fiber,
  lastPlacedIndex: number,
  newIndex: number,
): number {
  newFiber.index = newIndex;
  ...
  const current = newFiber.alternate;
  if (current !== null) {
    const oldIndex = current.index;
    if (oldIndex &lt; lastPlacedIndex) {
      // This is a move.
      newFiber.flags |= Placement;
      return lastPlacedIndex;
    } else {
      // This item can stay in place.
      return oldIndex;
    }
  } else {
    // This is an insertion.
    newFiber.flags |= Placement;
    return lastPlacedIndex;
  }
}</code></pre>
<pre><code class="language-ts">function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
  ...
  const deletions = returnFiber.deletions;
  if (deletions === null) {
    returnFiber.deletions = [childToDelete];
    returnFiber.flags |= ChildDeletion;
  } else {
    deletions.push(childToDelete);
  }
}</code></pre>
<p>삭제되어야 하는 fiber가 임시로 해당 parent의 배열 안에 들어가는 것을 봤다. 이것이 필요한 이유는, 삭제 후 더 이상 새로운 fiber 트리에 해당 fiber가 존재하지 않지만, Commit 단계에서 처리가 되어야 하므로, 어딘가에 넣어두는 것이다.</p>
<p><code>&lt;div&gt;</code>는 완료되었다.</p>
<p><img src="https://jser.dev/static/rerender/13.avif" alt="https://jser.dev/2023-07-18-how-react-rerenders/#211-placechild-and-deletechild-marks-fiber-with-flags"></p>
<pre><code>출처: https://jser.dev/2023-07-18-how-react-rerenders/#211-placechild-and-deletechild-marks-fiber-with-flags</code></pre><p>다음으로 React는 <code>button</code>으로 이동한다. 이번에도 예약된 작업은 없지만 React는 여전히 <code>updateHostComponent()</code>를 사용하여 작업한다. 왜냐하면 props가 <code>[&quot;click me-&quot;, &quot;1&quot;]</code>에서 <code>[&quot;click me-&quot;, &quot;1&quot;]</code>로 변경되었기 떄문이다.</p>
<p>HostText의 경우 props가 문자열이므로, 첫 번째 <code>&quot;click me-&quot;</code>는 bailout한다. 그리고 차례로, React는 <code>updateHostText()</code>를 사용하여 텍스트를 재조정하려고 시도한다.</p>
<h2 id="212-updatehosttext">2.12 <code>updateHostText()</code></h2>
<pre><code class="language-tsx">function updateHostText(current, workInProgress) {
  if (current === null) {
    tryToClaimNextHydratableInstance(workInProgress);
  }
  // Nothing to do here. This is terminal. We&#39;ll do the completion step
  // immediately after.
  return null;
}</code></pre>
<p>업데이트가 완료 단계(<code>completeWork()</code>)에서 표시되기 때문에, 여기에서는 아무 일이 일어나지 않는다. 이는 initial mount에서도 설명한 적 있다.</p>
<h2 id="213-completework는-hostcomponent의-업데이트를-표시하고-필요하다면-dom-노드를-생성한다">2.13 <code>completeWork()</code>는 HostComponent의 업데이트를 표시하고, 필요하다면 DOM 노드를 생성한다.</h2>
<pre><code class="language-ts">function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;
  popTreeContext(workInProgress);
  switch (workInProgress.tag) {
    ...
    case FunctionComponent:
    ...
    case HostComponent: {
      popHostContext(workInProgress);
      const rootContainerInstance = getRootHostContainer();
      const type = workInProgress.type;
      // 📌 업데이트 지점이다.
      if (current !== null &amp;&amp; workInProgress.stateNode != null) {
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          rootContainerInstance,
        );
        if (current.ref !== workInProgress.ref) {
          markRef(workInProgress);
        }
      // 📌 initial mount 분기다.
      } else {
        ...
      }
      bubbleProperties(workInProgress);
      return null;
    }
    case HostText: {
      const newText = newProps;
      // 📌 업데이트 지점이다.
      if (current &amp;&amp; workInProgress.stateNode != null) {
        const oldText = current.memoizedProps;
        // If we have an alternate, that means this is an update and we need
        // to schedule a side-effect to do the updates.
        updateHostText(current, workInProgress, oldText, newText);
      // 📌 initial mount 분기다.
      } else {
        ...
        if (wasHydrated) {
          if (prepareToHydrateHostTextInstance(workInProgress)) {
            markUpdate(workInProgress);
          }
        } else {
          workInProgress.stateNode = createTextInstance(
            newText,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
        }
      }
      bubbleProperties(workInProgress);
      return null;
    }
    ...
  }
}</code></pre>
<pre><code class="language-ts">updateHostText = function(
  // 📌 complete 단계에 있는 updateHostText()이며,
  // beginWork()에 있는 것과 다르다는 것을 주의하자.
  current: Fiber,
  workInProgress: Fiber,
  oldText: string,
  newText: string,
) {
  // If the text differs, mark it as an update. All the work in done in commitWork.
  if (oldText !== newText) {
    markUpdate(workInProgress);
  }
};

updateHostComponent = function(
  current: Fiber,
  workInProgress: Fiber,
  type: Type,
  newProps: Props,
  rootContainerInstance: Container,
) {
  // If we have an alternate, that means this is an update and we need to
  // schedule a side-effect to do the updates.
  const oldProps = current.memoizedProps;
  if (oldProps === newProps) {
    // In mutation mode, this is sufficient for a bailout because
    // we won&#39;t touch this node even if children changed.
    return;
  }

  // If we get updated because one of our children updated, we don&#39;t
  // have newProps so we&#39;ll have to reuse them.
  const instance: Instance = workInProgress.stateNode;
  const currentHostContext = getHostContext();
  const updatePayload = prepareUpdate(
    instance,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    currentHostContext,
  );
  // 📌 업데이트는 updateQueue에 들어간다.
  // 이것은 실제로 Effect Hook과 같은 hook에서도 사용된다.
  workInProgress.updateQueue = (updatePayload: any);
  // If the update payload indicates that there is a change or if there
  // is a new ref we mark this as an update. All the work is done in commitWork.
  if (updatePayload) {
    markUpdate(workInProgress);
  }
};
function markUpdate(workInProgress: Fiber) {
  // Tag the fiber with an update effect. This turns a Placement into
  // a PlacementAndUpdate.
  // 📌 다른 flag다!
  workInProgress.flags |= Update;
}</code></pre>
<p>Render 단계가 끝났고, 다음을 얻었다.</p>
<ol>
<li><code>b</code>의 삽입</li>
<li><code>span</code>의 삭제</li>
<li>HostText의 업데이트</li>
<li><code>button</code>의 업데이트 (empty update, do nothing)</li>
</ol>
<p>한 가지 지적하고 싶은 점은 <code>prepareUpdate()</code>가 <code>button</code>과 해당 parent인 <code>div</code> 모두에 대해 실행되지만, <code>div</code>에 대해서는 <code>null</code>을 생성하고 <code>button</code>에 대해서는 <code>[]</code>를 생성한다는 것이다. 여기서는 다루지 않을 edge case다.</p>
<p><img src="https://jser.dev/static/rerender/13.avif" alt="https://jser.dev/2023-07-18-how-react-rerenders/#213-completework-marks-the-update-of-hostcomponent-and-creates-dom-nodes-if-necessary"></p>
<pre><code>출처: https://jser.dev/2023-07-18-how-react-rerenders/#213-completework-marks-the-update-of-hostcomponent-and-creates-dom-nodes-if-necessary</code></pre><p>이제 Commit 단계에서 업데이트들을 반영할 시간이다.</p>
<h1 id="3-re-render-in-commit-phase">3. Re-render in Commit Phase</h1>
<h2 id="31-commitmutationeffectsonfiber는-insertiondeletionupdate-반영commit을--시작한다">3.1 <code>commitMutationEffectsOnFiber()</code>는 Insertion/Deletion/Update 반영(commit)을  시작한다.</h2>
<pre><code class="language-tsx">function commitMutationEffectsOnFiber(
  finishedWork: Fiber,
  root: FiberRoot,
  lanes: Lanes,
) {
  const current = finishedWork.alternate;
  const flags = finishedWork.flags;
  // The effect flag should be checked *after* we refine the type of fiber,
  // because the fiber tag is more specific. An exception is any flag related
  // to reconciliation, because those can be set on all fiber types.
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
      // 📌 children부터 재귀적으로 처리한다.
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      // 📌 이후에 삽입(Insertion)을 한다.
      commitReconciliationEffects(finishedWork);

      // 📌 업데이트는 마지막에 한다.
      if (flags &amp; Update) {
        try {
          commitHookEffectListUnmount(
            HookInsertion | HookHasEffect,
            finishedWork,
            finishedWork.return,
          );
          commitHookEffectListMount(
            HookInsertion | HookHasEffect,
            finishedWork,
          );
        } catch (error) {
          captureCommitPhaseError(finishedWork, finishedWork.return, error);
        }
        ...
      }
      return;
    }
    case HostComponent: {
      // 📌 children부터 재귀적으로 처리한다.
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      // 📌 이후에 삽입(Insertion)을 한다.
      commitReconciliationEffects(finishedWork);

      if (supportsMutation) {
        if (finishedWork.flags &amp; ContentReset) {
          const instance: Instance = finishedWork.stateNode;
          try {
            resetTextContent(instance);
          } catch (error) {
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
        }
        // 📌 업데이트는 마지막에 한다.
        if (flags &amp; Update) {
          const instance: Instance = finishedWork.stateNode;
          if (instance != null) {
            // Commit the work prepared earlier.
            const newProps = finishedWork.memoizedProps;
            // For hydration we reuse the update path but we treat the oldProps
            // as the newProps. The updatePayload will contain the real change in
            // this case.
            const oldProps =
              current !== null ? current.memoizedProps : newProps;
            const type = finishedWork.type;
            const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
            finishedWork.updateQueue = null;
            if (updatePayload !== null) {
              try {
                // 📌 HostComponent의 경우, props를 업데이트한다.
                commitUpdate(
                  instance,
                  updatePayload,
                  type,
                  oldProps,
                  newProps,
                  finishedWork,
                );
              } catch (error) {
                captureCommitPhaseError(
                  finishedWork,
                  finishedWork.return,
                  error,
                );
              }
            }
          }

        }
      }
      return;
    }
    case HostText: {
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      if (flags &amp; Update) {
        if (supportsMutation) {
          if (finishedWork.stateNode === null) {
            throw new Error(
              &#39;This should have a text node initialized. This error is likely &#39; +
                &#39;caused by a bug in React. Please file an issue.&#39;,
            );
          }
          const textInstance: TextInstance = finishedWork.stateNode;
          const newText: string = finishedWork.memoizedProps;
          // For hydration we reuse the update path but we treat the oldProps
          // as the newProps. The updatePayload will contain the real change in
          // this case.
          const oldText: string =
            current !== null ? current.memoizedProps : newText;
          try {
            // 📌 HostText의 경우, textContent를 업데이트한다.
            commitTextUpdate(textInstance, oldText, newText);
          } catch (error) {
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
        }
      }
      return;
    }
  }
}</code></pre>
<p>이것이 재귀적인 과정임을 알 수 있으며, 각 mutation 유형을 자세히 살펴보자.</p>
<h2 id="32-children과-본인을-처리하기-전에-삭제deletion가-가장-먼저-수행된다">3.2 children과 본인을 처리하기 전에, 삭제(Deletion)가 가장 먼저 수행된다.</h2>
<pre><code class="language-ts">function recursivelyTraverseMutationEffects(
  root: FiberRoot,
  parentFiber: Fiber,
  lanes: Lanes,
) {
  // Deletions effects can be scheduled on any fiber type. They need to happen
  // before the children effects hae fired.
  const deletions = parentFiber.deletions;
  if (deletions !== null) {
    for (let i = 0; i &lt; deletions.length; i++) {
      const childToDelete = deletions[i];
      try {
        commitDeletionEffects(root, parentFiber, childToDelete);
      } catch (error) {
        captureCommitPhaseError(childToDelete, parentFiber, error);
      }
    }
  }
  const prevDebugFiber = getCurrentDebugFiberInDEV();
  if (parentFiber.subtreeFlags &amp; MutationMask) {
    let child = parentFiber.child;
    while (child !== null) {
      setCurrentDebugFiberInDEV(child);
      commitMutationEffectsOnFiber(child, root, lanes);
      child = child.sibling;
    }
  }
  setCurrentDebugFiberInDEV(prevDebugFiber);
}</code></pre>
<p>삭제는 children을 처리하기 전이라도, 가장 먼저 처리된다.</p>
<pre><code class="language-ts">function commitDeletionEffects(
  root: FiberRoot,
  returnFiber: Fiber,
  deletedFiber: Fiber,
) {
  if (supportsMutation) {
    // We only have the top Fiber that was deleted but we need to recurse down its
    // children to find all the terminal nodes.
    // Recursively delete all host nodes from the parent, detach refs, clean
    // up mounted layout effects, and call componentWillUnmount.
    // We only need to remove the topmost host child in each branch. But then we
    // still need to keep traversing to unmount effects, refs, and cWU. TODO: We
    // could split this into two separate traversals functions, where the second
    // one doesn&#39;t include any removeChild logic. This is maybe the same
    // function as &quot;disappearLayoutEffects&quot; (or whatever that turns into after
    // the layout phase is refactored to use recursion).
    // Before starting, find the nearest host parent on the stack so we know
    // which instance/container to remove the children from.
    let parent = returnFiber;
    // 📌 parent 노드가 필요하지 않다는 것은 backing DOM이 있다는 의미다.
    // 여기에서 backing DOM을 가진 가장 가까운 Fiber 노드를 찾는다.
    findParent: while (parent !== null) {
      switch (parent.tag) {
        case HostComponent: {
          hostParent = parent.stateNode;
          hostParentIsContainer = false;
          break findParent;
        }
        case HostRoot: {
          hostParent = parent.stateNode.containerInfo;
          hostParentIsContainer = true;
          break findParent;
        }
        case HostPortal: {
          hostParent = parent.stateNode.containerInfo;
          hostParentIsContainer = true;
          break findParent;
        }
      }
      parent = parent.return;
    }
    if (hostParent === null) {
      throw new Error(
        &#39;Expected to find a host parent. This error is likely caused by &#39; +
          &#39;a bug in React. Please file an issue.&#39;,
      );
    }
    commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
    hostParent = null;
    hostParentIsContainer = false;
  } else {
    // Detach refs and call componentWillUnmount() on the whole subtree.
    commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
  }

  detachFiberMutation(deletedFiber);
}</code></pre>
<pre><code class="language-ts">function commitDeletionEffectsOnFiber(
  finishedRoot: FiberRoot,
  nearestMountedAncestor: Fiber,
  deletedFiber: Fiber,
) {
  onCommitUnmount(deletedFiber);
  // The cases in this outer switch modify the stack before they traverse
  // into their subtree. There are simpler cases in the inner switch
  // that don&#39;t modify the stack.
  switch (deletedFiber.tag) {
    case HostComponent: {
      if (!offscreenSubtreeWasHidden) {
        safelyDetachRef(deletedFiber, nearestMountedAncestor);
      }
      // Intentional fallthrough to next branch
    }
    case HostText: {
      // We only need to remove the nearest host child. Set the host parent
      // to `null` on the stack to indicate that nested children don&#39;t
      // need to be removed.
      if (supportsMutation) {
        const prevHostParent = hostParent;
        const prevHostParentIsContainer = hostParentIsContainer;
        hostParent = null;
        recursivelyTraverseDeletionEffects(
          finishedRoot,
          nearestMountedAncestor,
          deletedFiber,
        );
        hostParent = prevHostParent;
        hostParentIsContainer = prevHostParentIsContainer;
        if (hostParent !== null) {
          // Now that all the child effects have unmounted, we can remove the
          // node from the tree.
          if (hostParentIsContainer) {
            removeChildFromContainer(
              // 📌 이 hostParent는 이전 while문에서 찾았다.
              ((hostParent: any): Container),
              (deletedFiber.stateNode: Instance | TextInstance),
            );
          } else {
            removeChild(
              ((hostParent: any): Instance),
              (deletedFiber.stateNode: Instance | TextInstance),
            );
          }
        }
      } else {
        recursivelyTraverseDeletionEffects(
          finishedRoot,
          nearestMountedAncestor,
          deletedFiber,
        );
      }
      return;
    }
    ...
    default: {
      recursivelyTraverseDeletionEffects(
        finishedRoot,
        nearestMountedAncestor,
        deletedFiber,
      );
      return;
    }
  }
}</code></pre>
<p><img src="https://jser.dev/static/rerender/26.avif" alt="https://jser.dev/2023-07-18-how-react-rerenders/#32-deletion-are-processed-first-before-processing-children-and-self"></p>
<pre><code>출처: https://jser.dev/2023-07-18-how-react-rerenders/#32-deletion-are-processed-first-before-processing-children-and-self</code></pre><h2 id="33-삽입insertion이-그-다음에-처리된다">3.3 삽입(Insertion)이 그 다음에 처리된다.</h2>
<p>이는 새로 생성된 노드를 트리 구조로 설정할 수 있도록 하기 위한 것이다.</p>
<pre><code class="language-tsx">function commitReconciliationEffects(finishedWork: Fiber) {
  // Placement effects (insertions, reorders) can be scheduled on any fiber
  // type. They needs to happen after the children effects have fired, but
  // before the effects on this fiber have fired.
  const flags = finishedWork.flags;
  if (flags &amp; Placement) {
    try {
      commitPlacement(finishedWork);
    } catch (error) {
      captureCommitPhaseError(finishedWork, finishedWork.return, error);
    }
    // Clear the &quot;placement&quot; from effect tag so that we know that this is
    // inserted, before any life-cycles like componentDidMount gets called.
    finishedWork.flags &amp;= ~Placement;
  }
  if (flags &amp; Hydrating) {
    finishedWork.flags &amp;= ~Hydrating;
  }
}
function commitPlacement(finishedWork: Fiber): void {
  if (!supportsMutation) {
    return;
  }
  // Recursively insert all host nodes into the parent.
  const parentFiber = getHostParentFiber(finishedWork);
  // Note: these two variables *must* always be updated together.
  switch (parentFiber.tag) {
    case HostComponent: {
      const parent: Instance = parentFiber.stateNode;
      if (parentFiber.flags &amp; ContentReset) {
        // Reset the text content of the parent before doing any insertions
        resetTextContent(parent);
        // Clear ContentReset from the effect tag
        parentFiber.flags &amp;= ~ContentReset;
      }

      // 📌 이것은 중요하다. Node.insertBefore()는 sibling 노드가 필요하다.
      // sibling 노드를 찾을 수 없다면, 끝에 추가하면 된다.
      const before = getHostSibling(finishedWork);
      // We only have the top Fiber that was inserted but we need to recurse down its
      // children to find all the terminal nodes.
      insertOrAppendPlacementNode(finishedWork, before, parent);
      break;
    }
    case HostRoot:
    case HostPortal: {
      const parent: Container = parentFiber.stateNode.containerInfo;
      const before = getHostSibling(finishedWork);
      insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
      break;
    }
    default:
      throw new Error(
        &#39;Invalid host parent fiber. This error is likely caused by a bug &#39; +
          &#39;in React. Please file an issue.&#39;,
      );
  }
}

function insertOrAppendPlacementNodeIntoContainer(
  node: Fiber,
  before: ?Instance,
  parent: Container,
): void {
  const {tag} = node;
  const isHost = tag === HostComponent || tag === HostText;
  if (isHost) {
    const stateNode = node.stateNode;
    if (before) {
      insertInContainerBefore(parent, stateNode, before);
    } else {
      appendChildToContainer(parent, stateNode);
    }
  } else if (tag === HostPortal) {
    // If the insertion itself is a portal, then we don&#39;t want to traverse
    // down its children. Instead, we&#39;ll get insertions from each child in
    // the portal directly.
  } else {
    const child = node.child;
    if (child !== null) {
      insertOrAppendPlacementNodeIntoContainer(child, before, parent);
      let sibling = child.sibling;
      while (sibling !== null) {
        insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
        sibling = sibling.sibling;
      }
    }
  }
}

function insertOrAppendPlacementNode(
  node: Fiber,
  before: ?Instance,
  parent: Instance,
): void {
  const {tag} = node;
  const isHost = tag === HostComponent || tag === HostText;
  if (isHost) {
    const stateNode = node.stateNode;
    if (before) {
      insertBefore(parent, stateNode, before);
    } else {
      appendChild(parent, stateNode);
    }
  } else if (tag === HostPortal) {
    // If the insertion itself is a portal, then we don&#39;t want to traverse
    // down its children. Instead, we&#39;ll get insertions from each child in
    // the portal directly.
  } else {
    const child = node.child;
    if (child !== null) {
      insertOrAppendPlacementNode(child, before, parent);
      let sibling = child.sibling;
      while (sibling !== null) {
        insertOrAppendPlacementNode(sibling, before, parent);
        sibling = sibling.sibling;
      }
    }
  }
}
</code></pre>
<p><img src="https://jser.dev/static/rerender/30.avif" alt="https://jser.dev/2023-07-18-how-react-rerenders/#33-insertions-are-processed-next"></p>
<pre><code>출처: https://jser.dev/2023-07-18-how-react-rerenders/#33-insertions-are-processed-next</code></pre><h2 id="34-업데이트update가-마지막으로-처리된다">3.4 업데이트(Update)가 마지막으로 처리된다.</h2>
<p>Update 분기는 <code>commitMutationEffectsOnFiber()</code>에 있다.</p>
<pre><code class="language-ts">function commitMutationEffectsOnFiber(
  finishedWork: Fiber,
  root: FiberRoot,
  lanes: Lanes,
) {
  const current = finishedWork.alternate;
  const flags = finishedWork.flags;

  // The effect flag should be checked *after* we refine the type of fiber,
  // because the fiber tag is more specific. An exception is any flag related
  // to reconciliation, because those can be set on all fiber types.
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent: {
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);

      // 📌 FunctionComponent의 경우, hooks이 실행되어야 한다는 것을 의미한다.
      if (flags &amp; Update) {
        try {
          commitHookEffectListUnmount(
            HookInsertion | HookHasEffect,
            finishedWork,
            finishedWork.return,
          );
          commitHookEffectListMount(
            HookInsertion | HookHasEffect,
            finishedWork,
          );
        } catch (error) {
          captureCommitPhaseError(finishedWork, finishedWork.return, error);
        }
        // Layout effects are destroyed during the mutation phase so that all
        // destroy functions for all fibers are called before any create functions.
        // This prevents sibling component effects from interfering with each other,
        // e.g. a destroy function in one component should never override a ref set
        // by a create function in another component during the same commit.
        if (
          enableProfilerTimer &amp;&amp;
          enableProfilerCommitHooks &amp;&amp;
          finishedWork.mode &amp; ProfileMode
        ) {
          try {
            startLayoutEffectTimer();
            commitHookEffectListUnmount(
              HookLayout | HookHasEffect,
              finishedWork,
              finishedWork.return,
            );
          } catch (error) {
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
          recordLayoutEffectDuration(finishedWork);
        } else {
          try {
            commitHookEffectListUnmount(
              HookLayout | HookHasEffect,
              finishedWork,
              finishedWork.return,
            );
          } catch (error) {
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
        }
      }
      return;
    }
    case HostComponent: {
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      if (flags &amp; Ref) {
        if (current !== null) {
          safelyDetachRef(current, current.return);
        }
      }
      if (supportsMutation) {
        if (finishedWork.flags &amp; ContentReset) {
          const instance: Instance = finishedWork.stateNode;
          try {
            resetTextContent(instance);
          } catch (error) {
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
        }

        // 📌 HostComponent의 경우, element 속성들이 업데이트되어야 한다는 것을 의미한다.
        if (flags &amp; Update) {
          const instance: Instance = finishedWork.stateNode;
          if (instance != null) {
            // Commit the work prepared earlier.
            const newProps = finishedWork.memoizedProps;
            // For hydration we reuse the update path but we treat the oldProps
            // as the newProps. The updatePayload will contain the real change in
            // this case.
            const oldProps =
              current !== null ? current.memoizedProps : newProps;
            const type = finishedWork.type;
            const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
            finishedWork.updateQueue = null;
            if (updatePayload !== null) {
              try {
                commitUpdate(
                  instance,
                  updatePayload,
                  type,
                  oldProps,
                  newProps,
                  finishedWork,
                );
              } catch (error) {
                captureCommitPhaseError(
                  finishedWork,
                  finishedWork.return,
                  error,
                );
              }
            }
          }
        }
      }
      return;
    }
    case HostText: {
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);

      if (flags &amp; Update) {
        if (supportsMutation) {
          if (finishedWork.stateNode === null) {
            throw new Error(
              &#39;This should have a text node initialized. This error is likely &#39; +
                &#39;caused by a bug in React. Please file an issue.&#39;,
            );
          }
          const textInstance: TextInstance = finishedWork.stateNode;
          const newText: string = finishedWork.memoizedProps;
          // For hydration we reuse the update path but we treat the oldProps
          // as the newProps. The updatePayload will contain the real change in
          // this case.
          const oldText: string =
            current !== null ? current.memoizedProps : newText;
          try {
            commitTextUpdate(textInstance, oldText, newText);
          } catch (error) {
            captureCommitPhaseError(finishedWork, finishedWork.return, error);
          }
        }
      }
      return;
    }
    ...
    default: {
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      return;
    }
  }
}</code></pre>
<pre><code class="language-ts">export function commitUpdate(
  domElement: Instance,
  updatePayload: Array&lt;mixed&gt;,
  type: string,
  oldProps: Props,
  newProps: Props,
  internalInstanceHandle: Object,
): void {
  // Apply the diff to the DOM node.
  updateProperties(domElement, updatePayload, type, oldProps, newProps);
  // Update the props handle so that we know which props are the ones with
  // with current event handlers.
  updateFiberProps(domElement, newProps);
}

export function commitTextUpdate(
  textInstance: TextInstance,
  oldText: string,
  newText: string,
): void {
  textInstance.nodeValue = newText;
}</code></pre>
<p>트리 구조이기 때문에, demo의 mutations는 다음 순서로 처리된다.</p>
<ol>
<li><code>span</code> 삭제</li>
<li>HostText 업데이트</li>
<li><code>button</code> 업데이트 (empty update, do nothing)</li>
<li><code>b</code> 삽입</li>
</ol>
<blockquote>
<h4 id="💡-mutation-순서">💡 Mutation 순서</h4>
</blockquote>
<ul>
<li>Deletion → Insertion → Update</li>
<li>Deletion의 경우, children에 Update나 Insertion이 있더라도 먼저 수행
_ Cf. <a href="https://jser.dev/2023-07-18-how-react-rerenders#32-deletion-are-processed-first-before-processing-children-and-self">Deletion are processed first, before processing children and self.</a>_  </li>
<li>Insertion의 경우,<ul>
<li>children에 Update가 있다면 children의 Update 먼저 수행</li>
<li>sibling에 Update가 있다면 Insertion 수행 후 Update 수행</li>
</ul>
</li>
</ul>
<h1 id="4-summary">4. Summary</h1>
<p>리렌더링 과정을 요약해보면 다음과 같다.</p>
<ol>
<li><p>state가 변경되면, target Fiber Node로 가는 경로가 <code>lanes</code>와 <code>childLanes</code>로 표시되어, 해당 노드 또는 subtree의 리렌더링 여부를 나타낸다.</p>
</li>
<li><p>React는 불필요한 리렌더링을 피하기 위해 bailout으로 최적화를 하며, 전체 Fiber 트리를 리렌더링한다.</p>
</li>
<li><p>컴포넌트가 리렌더링되면, 새 React elements가 생성되고, 해당 children은 변경 사항이 없더라도 모두 새로운 props를 얻으므로, React는 기본적으로 전체 Fiber 트리를 리렌더링한다. 이런 경우 <code>useMemo()</code>가 필요할 수 있다.</p>
</li>
<li><p>“리렌더링”함으로써, Reacts는 현재의 Fiber 트리에서 새로운 Fiber 트리를 생성하고, 필요한 경우 <code>Placement</code>, <code>ChildDeleteion</code> 및 <code>Update</code> flag로 Fiber 노드에 표시를 남긴다.</p>
</li>
<li><p>새로운 Fiber 트리가 완성되면, React는 위의 flag를 사용하여 Fiber 노드를 처리하고 Commit 단계에서 Host DOM에 변경 사항을 적용한다.</p>
</li>
<li><p>그런 다음 새 Fiber 트리가 현재 Fiber 트리로 지정된다. 이전 Fiber 트리의 노드는 다음 렌더링에 재사용될 수 있다.</p>
</li>
</ol>
<blockquote>
<p>JSer가 그린 re-render 순서도 슬라이드를 보면 이해에 큰 도움이 된다!
🔗 <a href="https://jser.dev/2023-07-18-how-react-rerenders/#how-react-re-renders-internally">https://jser.dev/2023-07-18-how-react-rerenders/#how-react-re-renders-internally</a></p>
</blockquote>
<h1 id="references">References</h1>
<ul>
<li><a href="https://jser.dev/2023-07-18-how-react-rerenders/">[JSer.dev] How does React re-render internally?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Initial Mount]]></title>
            <link>https://velog.io/@hee-suh/React-Internals-Deep-Dive-2-Initial-Mount</link>
            <guid>https://velog.io/@hee-suh/React-Internals-Deep-Dive-2-Initial-Mount</guid>
            <pubDate>Tue, 04 Jun 2024 12:13:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://jser.dev/">JSer.dev</a>의 <a href="https://jser.dev/series/react-source-code-walkthrough">React Internals Deep Dive</a>를 번역하여 정리한 글입니다.</p>
</blockquote>
<blockquote>
<p>⚠️ <a href="https://react.dev/blog/2024/04/25/react-19">React@19</a>의 <a href="https://github.com/facebook/react/commit/7608516479fb85bc40aa73d8a31a0c93397ee6ff">commit 7608516</a>을 기반으로 작성되었으며, 최신 버전에서는 구현이 변경되었을 수 있습니다.</p>
</blockquote>
<h4 id="📝-how-does-react-do-the-initial-mount-internally">📝 <a href="https://jser.dev/2023-07-14-initial-mount/">How does React do the initial mount internally?</a></h4>
<h1 id="1-fiber">1. Fiber</h1>
<p>!codepen[ejilee/embed/eYMXJPN?default-tab=html%1Cresult]</p>
<pre><code>Fiber reconciler 가 각 fiber의 return child sibling 을 참조하여 트리를 탐색하는 순서를 시각화한 애니메이션
출처: https://blog.mathpresso.com/react-deep-dive-fiber-88860f6edbd0</code></pre><h2 id="background">Background</h2>
<h3 id="concurrency-동시성">Concurrency 동시성</h3>
<p><strong>Concurrency</strong>(동시성)란 두 개 이상의 task를 <strong>동시에 지원함</strong>을 뜻한다.</p>
<p><strong>Parallelism</strong>(병렬성)이란 두 개 이상의 task를 <strong>동시에 실행</strong>할 수 있음을 뜻한다.</p>
<p>JavaScript는 single-thread 환경이라 여러 개의 thread를 사용하는 Parallelism이 불가능하고, Concurrency를 지원한다.</p>
<p><img src="https://www.baeldung.com/wp-content/uploads/sites/4/2022/01/vs-1024x462-1.png" alt="출처: https://www.baeldung.com/cs/concurrency-vs-parallelism"></p>
<pre><code>출처: https://www.baeldung.com/cs/concurrency-vs-parallelism</code></pre><h3 id="incremenetal-rendering-증분-렌더링">Incremenetal Rendering 증분 렌더링</h3>
<p>애플리케이션의 concurrent 렌더링이 가능하다는 것은 화면 렌더링 task에 우선순위를 매겨서 중요한 것을 먼저 처리하고 덜 중요한 것을 나중에 처리하는 것이 가능하다는 것을 의미한다. 리액트 공식 문서에서는 이것을 incremental rendering이라고 부르고, “<strong>일시 정지</strong>”, “<strong>재가동</strong>”, “<strong>우선순위</strong>”는 <strong>증분 렌더링</strong>을 위한 필수 기능이다.</p>
<p>리액트 라이브러리는 내부적인 구조 조정으로 성능을 위한 최적화를 하고 있고, 이를 위해 <strong>Fiber</strong>를 사용한다.</p>
<h2 id="stack-reconciler-vs-fiber-reconciler">Stack Reconciler vs Fiber Reconciler</h2>
<p>현 상태의 트리와 업데이트될 상태의 트리를 비교하기 위해서 reconciler(재조정)를 사용한다. </p>
<p>리액트 v16 이전에 사용되던 Stack reconciler와, 기존 reconciler의 취약점을 보완하기 위해서 리액트 팀에서 재작성한 Fiber reconciler를 비교해보자.</p>
<h3 id="stack-reconciler-스택-재조정">Stack Reconciler 스택 재조정</h3>
<p>Stack reconciler는 <strong>virtual DOM 트리를 비교하고 화면에 변경 사항을 푸시</strong>하는 이 모든 작업을 <strong>동기적으로, 하나의 테스크로 실행</strong>한다. 이는 현 상태의 트리와 작업 중인 트리를 DFS 패턴으로 재귀적으로 탐색하며 굉장히 <strong>깊은 콜 스택</strong>을 만들곤 한다. 이런 작업은 일시 중지되거나 취소될 수 없어서, 이 콜 스택이 전부 처리되기 전까지 메인 스레드는 다른 작업을 할 수 없고, 앱은 일시적으로 무반응 상태가 되거나 버벅거리게 된다.</p>
<h3 id="fiber-reconciler-파이버-재조정">Fiber Reconciler 파이버 재조정</h3>
<p>Fiber는 이러한 기존 reconciler의 취약점을 보완하기 위해서 리액트 팀에서 재작성한 리액트 코어의 reconciler 알고리즘이다. 렌더링 작업을 잘게 쪼개어 여러 프레임에 걸쳐 실행할 수 있고, 특정 작업에 “우선순위”를 매겨 <strong>작업의 작은 조각들을 concurrent하게</strong> “일시 정지”, “재가동”할 수 있게 해준다. 주목할 점은 Fiber 트리에서는 각 노드가 return, sibling, child 포인터 값을 사용하여 <strong>체인 형태의 singly linked list</strong>를 이룬다는 점이다.</p>
<p>이 트리에서는 단순히 깊이 우선으로 탐색하는 것이 아니라, 각 노드의 return, sibling, child 포인터를 이용해서 child가 있으면 child, child가 없으면 sibling, sibling이 없으면 return… 의 순으로 다음 Fiber Node로 이동한다. 각 <code>FiberNode</code>는 다음으로 처리해야 할 <code>FiberNode</code>를 가리키고 있기 때문에, 이 긴 일련의 작업이 중간에 멈춰도, 지금 작업 중인 <code>FiberNode</code>만 알고 있다면 돌아와서 같은 위치에서 작업을 이어가는 것이 가능하다. 각 <code>FiberNode</code>는 이 과정에서 각자의 ‘변경 사항에 대한 정보 (effect)’를 들고 있고, 이를 DOM에 바로바로 반영하지 않고, 모아뒀다가 모든 <code>FiberNode</code> 탐색이 끝난 후, 마지막 commit 단계에서 한 번에 반영하기 때문에, reconciliation 작업이 commit 단계 전에 중단되어도 실제 렌더된 화면에는 영향을 미치지 않는다. </p>
<p><img src="https://miro.medium.com/v2/resize:fit:1400/format:webp/1*4zMk7hgGRdmQk3XuqP86MA.png" alt="출처: https://blog.mathpresso.com/react-deep-dive-fiber-88860f6edbd0"></p>
<pre><code>출처: https://blog.mathpresso.com/react-deep-dive-fiber-88860f6edbd0</code></pre><p>💡 Fiber는 기존의 virtual DOM 트리의 <strong>화면 요소에 대응하는 역할</strong>도 하고, 작업(work)을 관리하는 가상 스택 프레임의 역할도 한다. Stack reconciler는 하나의 콜 스택에서 모든 작업을 한 번에 실행하고, <strong>Fiber reconciler는 제어 가능한 가상의 스택 프레임 구조를 만들어서 작업을 잘게 나누어서 실행</strong>한다.</p>
<h2 id="architecture">Architecture</h2>
<p>리액트 앱 내 모든 요소에는 이에 대응하는 <code>FiberNode</code>가 존재한다. Fiber는 인스턴스에 대한 정보뿐만 아니라 다음 Fiber Node로 향하는 포인터, 변경 사항에 대한 정보도 갖고 있다.</p>
<p>Cf. Fiber Node는 리액트 앱의 DOM 요소를 선택한 다음, <code>__reactFiber$…</code>라는 키값을 콘솔로 찍으면 확인 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/c3e36369-8326-441a-b7b7-f3f9be65dc1f/image.png" alt="콘솔에서 확인한 &lt;h1&gt;2. Initial Mount&lt;/h1&gt; Fiber Node의 모습"></p>
<pre><code>콘솔에서 확인한 &lt;h1&gt;2. Initial Mount&lt;/h1&gt; Fiber Node의 모습</code></pre><p><code>FiberNode</code>에 있는 키값을 살펴보자.</p>
<pre><code class="language-ts">type Fiber = {
  // Instance
  tag: WorkTag,
  key: null | string,
  ...
  type: any,
  stateNode: any,
  ...

  // Stack Frame  
  return: Fiber | null,
  child: Fiber | null,
  sibling: Fiber | null,

  ...
  memoizedState: any,
  ...

  // Effect
  flags: Flags,
  ...
  lanes: Lanes,
  alternate: Fiber | null,
  ...
};</code></pre>
<h3 id="instance-관련-항목">Instance 관련 항목</h3>
<ul>
<li><p><strong><code>tag</code></strong><code>: workTag</code> — <code>FiberNode</code>를 구분하는 컴포넌트/요소 인스턴스의 유형, 0~25의 정수. 각 숫자는 FunctionComponent, HostComponent, ContextProvider, MemoComponent, SuspenseComponent, … 같은 이름에 할당되어 있다. (HostComponent <code>&lt;div/&gt;</code> 같은 html 요소를 의미한다.)</p>
</li>
<li><p><strong><code>stateNode</code></strong><code>: any</code> — 다른 백업 데이터를 가리키며, <code>HostComponent</code>의 경우 <code>stateNode</code>는 실제 백업 DOM 노드를 가리킨다.</p>
</li>
<li><p><strong><code>key</code></strong><code>: null | string</code> — <code>FiberNode</code>가 가리키는 인스턴스인 child의 고유한 식별자.</p>
</li>
<li><p><strong><code>type</code></strong><code>: any</code> — 컴포넌트의 경우, 이 인스턴스를 만드는 함수나 클래스. HTML 요소의 경우, &#39;div&#39;와 같은 DOM 요소를 나타내는 문자열. root fiber의 경우 null.</p>
</li>
</ul>
<h3 id="stack-frame-관련-항목">Stack Frame 관련 항목</h3>
<p><code>return</code>, <code>child</code>, <code>sibling</code> 포인터들을 사용하면, 리액트의 트리 구조를 Singly Linked List로 탐색할 수 있고, 이를 하나의 가상 스택 프레임처럼 사용하게 된다.</p>
<ul>
<li><p><strong><code>return</code></strong><code>: Fiber | null</code> — 현 <code>FiberNode</code>의 작업 처리가 끝나면 돌아갈 <code>FiberNode</code>를 가리킨다. 사실상 부모 fiber이지만, 컴포넌트/요소에는 부모가 한 개 이상 있을 수 있을 수 있으므로, stack frame의 주소와 더 유사하다.</p>
</li>
<li><p><strong><code>child</code></strong><code>: Fiber | null</code> — 첫 자식 <code>FiberNode</code>에 대한 포인터다.</p>
</li>
<li><p><strong><code>sibling</code></strong><code>: Fiber | null</code> — 현 <code>FiberNode</code> 옆 형제 <code>FiberNode</code>를 가리킨다. child <code>FiberNode</code>가 없다면 sibling <code>FiberNode</code>를 탐색한다.</p>
</li>
</ul>
<h3 id="effect-관련-항목">Effect 관련 항목</h3>
<p>Effect는 두 트리를 비교한 후 감지된 ‘바뀐 점, 바뀌어야 할 작업’을 뜻한다.</p>
<ul>
<li><p><strong><code>flags</code></strong><code>: Flags</code> — 커밋 단계에서 적용할 업데이트를 나타낸다. Effect, 정수. 각 숫자는 Placement, Update, ChildDeletion, ContentReset, … 같은 이름에 할당되어 있다. 바이너리 숫자로 표기되어있고, bitewise OR assignment (<code>|=</code>)를 사용해서 여러 변화의 조합을 <code>workInProgress.flags |= Placement</code> 이런 식으로 축적한다. 하위 트리인 경우 <code>subtreeFlags</code>.</p>
</li>
<li><p><strong><code>lanes</code></strong><code>: Lanes</code> — 보류 중인 업데이트의 우선 순위를 나타낸다. 하위 트리의 경우 <code>childLanes</code>.</p>
</li>
<li><p><strong><code>alternate</code></strong><code>: Fiber | null</code> — 해당 fiber의 교대 fiber다.
트리 비교 작업이 끝나고 변경된 사항이 render commit phase를 통해서 화면까지 반영이 되면, <strong>현재의 앱 상태를 대변하는 fiber를 ‘flushed fiber’</strong>라고 부른다. 이와 반대로 <strong>아직 작업 중인 상태, 화면까지 반영되지 않은 fiber를 ‘workInProgress fiber’</strong>라고 부르는데, 즉 특정 컴포넌트에 대응하는 <strong>fiber가 최대 두 버전 존재</strong>한다는 뜻이다.  (<strong>Double Buffering</strong>) flushed fiber의 <code>alternate</code>는 workInProgress fiber를 가리키고, workInProgress fiber의 <code>alternate</code>는 flushed fiber를 가리킨다.</p>
</li>
<li><p><em><strong><code>memoizedState</code></strong><code>: any</code> — 중요한 데이터를 가리키며, FunctionComponent의 경우 훅을 의미한다.</em></p>
</li>
</ul>
<h2 id="algorithm">Algorithm</h2>
<p>Fiber는 reconciliation 작업을 2단계로 나눠서 실행한다.</p>
<ul>
<li><p><strong>Phase 1 Render</strong> — 실제로 두 fiber 트리를 비교하고 변경된 Effect들을 수집하는 작업을 한다. 이 단계는 concurrent하게 일시 정지되고 재가동될 수 있다. 리액트 scheduler로 인해 허용되는 시간 동안 작업하고 수시로 멈춰서 메인 스레드에 user input, animation 같은 더 급한 작업이 있는지 확인해가며 실행되기 때문에 아무리 트리가 커도 비교 작업이 메인 스레드를 막을 걱정이 없다. Phase 1의 목적은 <strong>이펙트 정보를 포함한 새로운 fiber 트리를 만들어내는 것</strong>이다.</p>
</li>
<li><p><strong>Phase 2 Commit</strong> — Phase 1에서 만든 트리에 표시된 이펙트들을 모아 <strong>실제 DOM에 반영하는 작업</strong>을 한다. 이 단계는 synchronous하게 한 번에 이루어지기 때문에 일시 정지하거나 취소할 수 없다.</p>
</li>
</ul>
<blockquote>
<h4 id="references"><strong>References</strong></h4>
<p><a href="https://blog.mathpresso.com/react-deep-dive-fiber-88860f6edbd0"><strong>[Team QANDA] React Deep Dive — Fiber</strong></a></p>
</blockquote>
<p>앱이 mount 되는 시점에서 Fiber 트리의 생성과 비교 과정을 Trigger, Render, Commit 세 단계로 나눠서 소스 코드와 함께 확인해보자.</p>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/4cbdcbee-0a0e-498f-8d01-c4786aa05569/image.png" alt="출처: https://react.dev/learn/render-and-commit Illustrated by Rachel Lee Nabors"></p>
<pre><code>출처: https://react.dev/learn/render-and-commit Illustrated by Rachel Lee Nabors</code></pre><h1 id="2-initial-mount-in-trigger-phase">2. Initial Mount in Trigger Phase</h1>
<p>👩🏻‍💻 <strong>User source code</strong></p>
<pre><code class="language-jsx">ReactDOM.createRoot(document.getElementById(&#39;container&#39;)).render(&lt;App /&gt;);</code></pre>
<ul>
<li><p><a href="https://react.dev/reference/react-dom/client/createRoot"><code>createRoot</code></a>를 사용하여 브라우저 DOM 노드 내에 React 컴포넌트를 표시하는 루트를 생성한다.</p>
</li>
<li><p><a href="https://react.dev/reference/react-dom/client/createRoot#root-render"><code>root.render</code></a>를 호출하여 <a href="https://react.dev/learn/writing-markup-with-jsx">JSX</a> 조각(&quot;React 노드&quot;)을 React 루트의 브라우저 DOM 노드에 표시한다.</p>
</li>
</ul>
<p><code>createRoot</code>부터 시작해서 코드를 하나씩 살펴보며, initial mount 내부 작동 원리를 이해해보자!</p>
<blockquote>
<p>💻 Demo Code</p>
</blockquote>
<pre><code class="language-jsx">// App.jsx
import {useState} from &#39;react&#39;
function Link() {
  return &lt;a href=&quot;https://jser.dev&quot;&gt;jser.dev&lt;/a&gt;
}
export default function App() {
  const [count, setCount] = useState(0)
  return (
    &lt;div&gt;
      &lt;p&gt;
        &lt;Link/&gt;
        &lt;br/&gt;
        &lt;button onClick={() =&gt; setCount(count =&gt; count + 1)}&gt;click me - {count}&lt;/button&gt;
      &lt;/p&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="21-createroot">2.1 <code>createRoot()</code></h3>
<p><code>createRoot()</code>는 <code>FiberRootNode</code>를 생성한다. </p>
<blockquote>
<h4 id="💡-fiberrootnode">💡 <code>FiberRootNode</code></h4>
<p>React root 역할을 하는 특별한 노드로, 전체 앱에 대한 필요한 메타 정보를 보유한다. 이 노드의 <code>current</code>는 실제 Fiber 트리를 가리키며, 새로운 Fiber 트리가 생성될 때마다 <code>current</code>가 새로운 <code>HostRoot</code>를 다시 가리키게 한다.</p>
</blockquote>
<p><img src="https://jser.dev/static/initial-mount/2.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></p>
<pre><code>출처: https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-</code></pre><blockquote>
<p>💻 src: createRoot, createContainer, createFiberRoot</p>
</blockquote>
<pre><code class="language-ts">function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {
  // container 관련 Error / 경고 코드
  ...
  // options(createContainer에 입력되는 parameter) 업데이트 코드
  ...
  const root = createContainer(  // 📌 FiberRootNode
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    transitionCallbacks,
  );
  markContainerAsRoot(root.current, container);
&gt;
  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  listenToAllSupportedEvents(rootContainerElement);
&gt;
  return new ReactDOMRoot(root);
}</code></pre>
<h3 id="22-rootrender">2.2 <code>root.render()</code></h3>
<p><code>root.render()</code>는 HostRoot 업데이트를 예약한다. element의 argument는 update payload에 저장된다.</p>
<p><img src="https://jser.dev/static/initial-mount/3.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></p>
<blockquote>
<p>💻 src: ReactDOMRoot, updateContainer, updateContainerImpl</p>
</blockquote>
<pre><code class="language-ts">function ReactDOMRoot(internalRoot: FiberRoot) {
  this._internalRoot = internalRoot;
}
&gt;
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =
  function (children: ReactNodeList): void {
    const root = this._internalRoot;
&gt;    
    if (root === null) {
      throw new Error(&#39;Cannot update an unmounted root.&#39;);
    }
&gt;
    updateContainer(children, root, null, null);
  };</code></pre>
<pre><code class="language-ts">function updateContainerImpl(
  rootFiber: Fiber,
  lane: Lane,
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component&lt;any, any&gt;,
  callback: ?Function,
): void {
  ...
  const update = createUpdate(lane);
  // 📌 render()의 argument가 update payload에 저장된다.
  update.payload = {element};
&gt;
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }
&gt;
  // 📌 update가 queue에 삽입되어 대기한다.
  const root = enqueueUpdate(rootFiber, update, lane);
  if (root !== null) {
    // 📌 업데이트를 예약한다.
    scheduleUpdateOnFiber(root, rootFiber, lane);
    entangleTransitions(root, rootFiber, lane);
  }
}</code></pre>
<p>container 업데이트 마지막 부분에 있는 <code>scheduleUpdateOnFiber()</code>를 이용해 업데이트를 예약한다. </p>
<p>“작업 생성” 단계로 생각할 수 있으며, <code>ensureRootIsScheduled()</code>가 수행되면 작업 생성이 완료되고, 해당 작업은 <code>scheduleCallback()</code>에 의해 Scheduler로 전달되면서, <code>performConcurrentWorkOnRoot()</code>이 생성된다.</p>
<blockquote>
<p>💻 src: scheduleUpdateOnFiber, ensureRootIsScheduled</p>
</blockquote>
<pre><code class="language-ts">function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
) {
  // Check if the work loop is currently suspended and waiting for data to
  // finish loading.
  if (
    // Suspended render phase
    (root === workInProgressRoot &amp;&amp;
      workInProgressSuspendedReason === SuspendedOnData) ||
    // Suspended commit phase
    root.cancelPendingCommit !== null
  ) {
    // The incoming update might unblock the current render. Interrupt the
    // current attempt and restart from the top.
    prepareFreshStack(root, NoLanes);
    markRootSuspended(
      root,
      workInProgressRootRenderLanes,
      workInProgressDeferredLane,
    );
  }
&gt; 
  // Mark that the root has a pending update.
  markRootUpdated(root, lane);
&gt;
  if (
    (executionContext &amp; RenderContext) !== NoLanes &amp;&amp;
    root === workInProgressRoot
  ) {
    // Track lanes that were updated during the render phase
    workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
      workInProgressRootRenderPhaseUpdatedLanes,
      lane,
    );
  } else {
    if (enableTransitionTracing) {
      const transition = ReactSharedInternals.T;
      if (transition !== null &amp;&amp; transition.name != null) {
        if (transition.startTime === -1) {
          transition.startTime = now();
        }
&gt;        
        addTransitionToLanesMap(root, transition, lane);
      }
    }
&gt;
    if (root === workInProgressRoot) {
      // Received an update to a tree that&#39;s in the middle of rendering. Mark
      // that there was an interleaved update work on this root.
      if ((executionContext &amp; RenderContext) === NoContext) {
        workInProgressRootInterleavedUpdatedLanes = mergeLanes(
          workInProgressRootInterleavedUpdatedLanes,
          lane,
        );
      }
      if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
        markRootSuspended(
          root,
          workInProgressRootRenderLanes,
          workInProgressDeferredLane,
        );
      }
    }
&gt;
    // 📌 ensureRootIsScheduled()가 수행되면 작업 생성이 완료된다.
    ensureRootIsScheduled(root);
    ...
  }
}</code></pre>
<h1 id="3-initial-mount-in-render-phase">3. Initial Mount in Render Phase</h1>
<h3 id="31-performconcurrentworkonroot">3.1 <code>performConcurrentWorkOnRoot()</code></h3>
<p><code>performConcurrentWorkOnRoot()</code>는 initial mount과 re-render에 대한 렌더링을 시작하는 진입점이다.</p>
<p><img src="https://jser.dev/static/initial-mount/5.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></p>
<p>한 가지 명심해야 할 점은 이름이 <code>concurrent</code>로 지정되어 있더라도, 필요하다면 <code>sync</code>로 돌아간다는 점이다. Initial mount는 DefaultLane이 blocking lane이기 때문에, sync로 작동하는 경우 중 하나다.</p>
<blockquote>
<p>💻 src: performConcurrentWorkOnRoot, includesBlockingLane</p>
</blockquote>
<pre><code class="language-ts">function performConcurrentWorkOnRoot(
  root: FiberRoot,
  didTimeout: boolean,
): RenderTaskFn | null {
  ...
  let lanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  ...
  const shouldTimeSlice =
    !includesBlockingLane(root, lanes) &amp;&amp;
    !includesExpiredLane(root, lanes) &amp;&amp;
    (disableSchedulerTimeoutInWorkLoop || !didTimeout);
  let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
    ...
}</code></pre>
<blockquote>
</blockquote>
<pre><code class="language-ts">// 📌 Blocking은 중요하다는 것을 의미하고, 방해되면(interrupted) 안 된다.
function includesBlockingLane(root: FiberRoot, lanes: Lanes): boolean {
  ...
  const SyncDefaultLanes =
    InputContinuousHydrationLane |
    InputContinuousLane |
    DefaultHydrationLane |
    DefaultLane;  // 📌 DefaultLane이 blocking lane이다.
  return (lanes &amp; SyncDefaultLanes) !== NoLanes;
}</code></pre>
<blockquote>
<p>💡 <strong><code>Lane</code></strong>은 업데이트의 우선 순위를 표시하는 것으로, 작업의 우선 순위를 표시한다고도 할 수 있다. <em>Cf. <a href="https://jser.dev/react/2022/03/26/lanes-in-react/">What are Lanes in React</a></em></p>
</blockquote>
<p>위 코드에서 봤듯이, initial mount에서는 concurrent feature가 아직 사용되지 않는다. Initial mount에서는 UI를 가능한 빨리 그려야 하기 때문에, 이를 미루는 것은 도움이 되지 않는다.</p>
<h3 id="32-renderrootsync">3.2 <code>renderRootSync()</code></h3>
<p><code>renderRootSync()</code>는 내부적으로 단지 while loop일뿐이다. <code>workInProgress</code>가 존재하면, 즉 아직 render phase라면 <code>performUnitOfWork()</code>를 계속 반복한다.</p>
<p><img src="https://jser.dev/static/initial-mount/6.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></p>
<blockquote>
<p>💻 src: renderRootSync, workLoopSync</p>
</blockquote>
<pre><code class="language-ts">function renderRootSync(root: FiberRoot, lanes: Lanes) {
  const prevExecutionContext = executionContext;
  executionContext |= RenderContext;
  const prevDispatcher = pushDispatcher(root.containerInfo);
  const prevCacheDispatcher = pushCacheDispatcher();
  // If the root or lanes have changed, throw out the existing stack
  // and prepare a fresh one. Otherwise we&#39;ll continue where we left off.
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    ...
    workInProgressTransitions = getTransitionsForLanes(root, lanes);
    // 📌 새로운 렌더링이 시작될 때마다 생성되는 workInProgress의 root를 반환한다.
    prepareFreshStack(root, lanes);
  }
    ...
  outer: do {
    try {
      // work loop가 suspend된 경우, 일반 work loop를 돌기 전에 특정 작업 수행 
      ...  
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleThrow(root, thrownValue);
    }
  } while (true);
  ...
  return workInProgressRootExitStatus;
}
&gt;
// The work loop is an extremely hot path. Tell Closure not to inline it.
/** @noinline */
function workLoopSync() {
  // Perform work without checking if we need to yield between fiber.
&gt;  
  // 📌 workInProgress가 존재하면, 즉 아직 render phase라면
  // performUnitOfWork()를 계속 반복하는 while loop
  while (workInProgress !== null) {
    // 📌 하나의 Fiber Node 단위에서 작동한다.
    performUnitOfWork(workInProgress);
  }
}</code></pre>
<blockquote>
<h4 id="💡-workinprogress">💡 workInProgress</h4>
<p>React는 내부적으로 현재 상태를 표현하기 위해 Fiber 트리를 사용하기 때문에 업데이트가 있을 때마다 새로운 트리를 생성하고 이전 트리와 비교해야 한다. <strong>따라서 <code>current</code>는 UI에 표시되는 현재 버전을 의미하고, <code>workInProgress</code>는 빌드 중이며 다음 <code>current</code>로 사용될 버전을 의미한다.</strong></p>
</blockquote>
<h3 id="33-performunitofwork">3.3 <code>performUnitOfWork()</code></h3>
<p><code>performUnitOfWork()</code>에서는 React가 하나의 Fiber Node 단위에서 작동하며, 수행해야 할 작업이 있는지 확인한다. <code>beginWork()</code>를 통해 element type에 따라, 실제 렌더링을 시작한다.</p>
<blockquote>
<p>💻 src: performUnitOfWork, beginWork</p>
</blockquote>
<pre><code class="language-ts">function performUnitOfWork(unitOfWork: Fiber): void {
  // The current, flushed, state of this fiber is the alternate.
  const current = unitOfWork.alternate;
&gt;
  let next;
  ...
  next = beginWork(current, unitOfWork, entangledRenderLanes);
&gt;
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn&#39;t spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    // 📌 앞서 언급했듯이, workLoopSync()는 단지 while loop로서
    // workInProgress에서 completeUnitOfWork()를 계속 실행한다.
    // 따라서 여기서 workInProgress를 할당한다는 것은
    // 작업할 다음 파이버 노드를 설정하는 것을 의미한다.
    workInProgress = next;
  }
}</code></pre>
<pre><code class="language-ts">function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  if (current !== null) {
    // 📌 current가 null이 아니라는 것은, initial mount가 아니라는 뜻이다. 
    ...
  } else {
    // 📌 initial mount의 경우, 당연히 업데이트가 없다.
    didReceiveUpdate = false;
    ...
  }
  // Before entering the begin phase, clear pending update priority.
  workInProgress.lanes = NoLanes;
&gt;
  // 📌 다른 타입의 element를 다르게 처리한다.
  switch (workInProgress.tag) {
    // 📌 우리가 작성한 커스텀 함수형 컴포넌트다.
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        disableDefaultPropsExceptForClasses ||
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    // 📌 FiberRootNode 아래의 HostRoot다.
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    // 📌 &lt;p/&gt;, &lt;div/&gt;와 같은 고유(intrinsic) HTML 태그다.
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    // 📌 HTML text 노드다.
    case HostText:
      return updateHostText(current, workInProgress);
    // 📌 이외에도 다양한 타입이 존재한다.
    // e.g. SuspenseComponent, LazyComponent, ContextProvider, ...
      ...
  }
  ...
}</code></pre>
<h3 id="34-preparefreshstack">3.4 <code>prepareFreshStack()</code></h3>
<p><code>renderRootSync()</code>에서 <code>prepareFreshStack()</code>이 호출되었다.</p>
<blockquote>
<p>💻 src: prepareFreshStack</p>
</blockquote>
<pre><code class="language-ts">function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
  ...
  workInProgressRoot = root;
  // 📌 root의 current는 HostRoot의 FiberNode다.
  const rootWorkInProgress = createWorkInProgress(root.current, null);
  ...
  finishQueueingConcurrentUpdates();
  return rootWorkInProgress;
}</code></pre>
<p><code>prepareFreshStack()</code>은 새로운 렌더링이 시작될 때마다 생성되는 <code>workInProgress</code>의 root를 반환한다. 이는 새로운 Fiber 트리의 root처럼 작동한다.
따라서 <code>beginWork()</code>의 다음을 먼저 살펴보자.</p>
<pre><code class="language-ts">case HostRoot:
    return updateHostRoot(current, workInProgress, renderLanes);</code></pre>
<h3 id="35-updatehostroot">3.5 <code>updateHostRoot()</code></h3>
<p><code>root.render()</code>에서 예약해둔 HostRoot 업데이트를 처리한다.</p>
<blockquote>
<p>💻 src: updateHostRoot</p>
</blockquote>
<pre><code class="language-ts">function updateHostRoot(
  current: null | Fiber,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  pushHostRootContext(workInProgress);
&gt;
  const nextProps = workInProgress.pendingProps;
  const prevState = workInProgress.memoizedState;
  const prevChildren = prevState.element;
  cloneUpdateQueue(current, workInProgress);   
  // 📌 root.render()에서 예약해둔 HostRoot 업데이트를 처리한다. 
  // 예약된 업데이트가 처리되고 payload가 추출되면,
  // element는 메모화된 상태로 할당된다는 점을 기억하자.
  processUpdateQueue(workInProgress, nextProps, null, renderLanes);
&gt;
  const nextState: RootState = workInProgress.memoizedState;
  const root: FiberRoot = workInProgress.stateNode;
  pushRootTransition(workInProgress, root, renderLanes);
  ...
  // Caution: React DevTools currently depends on this property
  // being called &quot;element&quot;.
  // 📌 ReactDOMRoot.render()의 argument를 가져올 수 있게 되었다!
  const nextChildren = nextState.element;
  ...
  // 📌 여기에서 current와 workInProgress는 둘 다 child를 갖고 있지 않다.
  // 그리고 nextChildren은 &lt;App/&gt;이다.
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
&gt;
  // 📌 reconciling 이후에, workInProgress를 위한 새로운 child가 생성된다.
  // 여기에서 child를 리턴한다는 것은 workLoopSync()가 다음에 처리한다는 것을 의미한다.
  return workInProgress.child;
}</code></pre>
<h3 id="36-reconcilechildren">3.6 <code>reconcileChildren()</code></h3>
<p>React 내부에서 매우 중요한 함수다. 이름에 따라 대략적으로 reconcile을 diff라고 생각할 수 있다. 이 함수는 새 child와 이전 child를 비교하고 올바른 <code>child</code>을 <code>workInProgress</code>에 설정한다.</p>
<blockquote>
<p>💻 src: reconcileChildren</p>
</blockquote>
<pre><code class="language-ts">function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  // 📌 current가 없다는 것은, initial mount라는 뜻이다. 
  if (current === null) {
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  // 📌 current가 있으면, re-render를 의미하고, reconcile(재조정)을 한다.
  } else {
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}</code></pre>
<p>위에서 언급했듯이 FiberRootNode는 항상 <code>current</code>를 가지고 있으므로, <code>reconcileChildFibers</code>로 이동한다. 그러나 이것은 initial mount이므로 그 자식인 <code>current.child</code>는 null이다.</p>
<p>또한 <code>workInProgress</code>가 생성 중이고 아직 <code>child</code>가 없으므로 <code>workInProgress</code>에 <code>child</code>를 설정하고 있음을 알 수 있다.</p>
<h3 id="37-reconcilechildfibers-vs-mountchildfibers">3.7 <code>reconcileChildFibers()</code> vs <code>mountChildFibers()</code></h3>
<p><code>reconcile</code>의 목표는 이미 가지고 있는 것을 재사용하는 것이며, <code>mount</code>를 항상 모든 것을 refresh하는 특별한 원시 버전의 <code>reconcile</code>로 취급할 수 있다.</p>
<pre><code class="language-ts">export const reconcileChildFibers: ChildReconciler = createChildReconciler(true);
export const mountChildFibers: ChildReconciler = createChildReconciler(false);</code></pre>
<p>사실 코드에서 이 둘은 동일한 클로저이지만, insertions과 같은 것들을 추적해야 하는지 여부를 제어하는 <code>shouldTrackSideEffects</code> 플래그만 다르다.</p>
<blockquote>
<p>💻 src: createChildReconciler</p>
</blockquote>
<p>전체 Fiber 트리를 구성해야 한다고 상상해보자. 모든 노드는 재조정 후 &quot;needed to insert&quot;로 표시되어야 할까? 꼭 그럴 필요는 없다. root만 삽입하면 끝이다! 따라서 <code>mountChildFibers</code>는 상황을 더욱 명확하게 하기 위한 내부 개선이다.</p>
<blockquote>
<p>💻 src: reconcileChildFibersImpl</p>
</blockquote>
<pre><code class="language-ts">function reconcileChildFibersImpl(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
    debugInfo: ReactDebugInfo | null,
  ): Fiber | null {
    ...
    // Handle object types
    if (typeof newChild === &#39;object&#39; &amp;&amp; newChild !== null) {
      // 📌 이 $$typeof는 React Element의 typeof를 의미한다.
      switch (newChild.$$typeof) {
        // 📌 children이 &lt;App/&gt;과 같은 React Element인 경우
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
              mergeDebugInfo(debugInfo, newChild._debugInfo),
            ),
          );
        ...
      }
      // 📌 children이 배열인 경우
      if (isArray(newChild)) {
        return reconcileChildrenArray(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
          mergeDebugInfo(debugInfo, newChild._debugInfo),
        );
      }
      ...
    }
    // 📌 가장 원시적인(primitive) 경우를 처리한다. - Text Node 업데이트
    if (
      (typeof newChild === &#39;string&#39; &amp;&amp; newChild !== &#39;&#39;) ||
      typeof newChild === &#39;number&#39; ||
      typeof newChild === &#39;bigint&#39;
    ) {
      return placeSingleChild(
        reconcileSingleTextNode(
          returnFiber,
          currentFirstChild,
          // $FlowFixMe[unsafe-addition] Flow doesn&#39;t want us to use `+` operator with string and bigint
          &#39;&#39; + newChild,
          lanes,
        ),
      );
    }
&gt;
    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }</code></pre>
<p><code>placeSingleChild(reconcileXXX(…))</code> 두 단계에서, <code>reconcileXXX()</code>를 사용하여 차이점을 확인(diffing)하고 <code>placeSingleChild()</code>를 사용하여 fiber가 DOM에 insertion을 해야함을 표시한다.</p>
<h3 id="38-reconcilesingleelement">3.8 <code>reconcileSingleElement()</code></h3>
<blockquote>
<p>💻 src: reconcileSingleElement</p>
</blockquote>
<p>Initial mount를 위한 <code>reconcileSingleElement()</code>는 매우 간단하다. 새로 생성된 Fiber 노드는 <code>workInProgress</code>의 <code>child</code>가 된다.</p>
<blockquote>
<p>💻 src: createFiberFromElement, createFiberFromTypeAndProps</p>
</blockquote>
<h3 id="39-placesinglechild">3.9 <code>placeSingleChild()</code></h3>
<p><code>reconcileSingleElement()</code>는 Fiber Node reconciliation만 수행하며 <code>placeSingleChild()</code>는 child Fiber Node가 DOM에 삽입되도록 표시되는 곳이다.</p>
<blockquote>
<p>💻 src: placeSingleChild</p>
</blockquote>
<pre><code class="language-ts">function placeSingleChild(newFiber: Fiber): Fiber {
  // This is simpler for the single child case. We only need to do a
  // placement for inserting new children.
  // 📌 shouldTrackSideEffects 플래그는 여기에서도 사용된다 (다른 곳에서도 사용된다)
  if (shouldTrackSideEffects &amp;&amp; newFiber.alternate === null) {
      // 📌 Placement는 DOM sub-tree에 삽입이 필요하다는 것을 의미한다.
    newFiber.flags |= Placement | PlacementDEV;
  }
  return newFiber;
}</code></pre>
<p>이는 <code>child</code>에서 수행되므로, initial mount에서 <code>HostRoot</code>의 child가 Placement로 표시된다. Demo Code에서는 <code>&lt;App/&gt;</code>이다.</p>
<h3 id="310-updatehostcomponent">3.10 <code>updateHostComponent()</code></h3>
<p><em><code>performUnitOfWork()</code>가 Fiber 트리를 탐색하며 계속 수행되는데, 다음 타자는 <code>App()</code>이 리턴한 <code>&lt;div/&gt;</code>다. <code>&lt;div/&gt;</code>는 intrinsic HTML 태그여서, <code>beginWork()</code>에서 HostComponent로 처리된다.</em></p>
<blockquote>
<p>💻 src: updateHostComponent</p>
</blockquote>
<pre><code class="language-ts">function updateHostComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  if (current === null) {
    // 📌 hydration은 How basic hydration works internally in React를 참조하자.
    // - https://jser.dev/react/2023/03/17/how-does-hydration-work-in-react/
    tryToClaimNextHydratableInstance(workInProgress);
  }
&gt;
  pushHostContext(workInProgress);
&gt;
  const type = workInProgress.type;
  // 📌 pendingProps는 &lt;div/&gt;의 children인 &lt;p/&gt;를 보유한다.
&gt;  
  const nextProps = workInProgress.pendingProps;
  const prevProps = current !== null ? current.memoizedProps : null;
&gt;
  let nextChildren = nextProps.children;
  // 📌 &lt;a/&gt;와 같이 children이 정적 텍스트인 경우 이는 개선된 사항이다.
  const isDirectTextChild = shouldSetTextContent(type, nextProps);
&gt;
  if (isDirectTextChild) {
    nextChildren = null;
  } else if (prevProps !== null &amp;&amp; shouldSetTextContent(type, prevProps)) {
    workInProgress.flags |= ContentReset;
  }
  ...
  markRef(current, workInProgress);
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}</code></pre>
<p>위 프로세스는 <code>&lt;p/&gt;</code>에 대해 반복된다. 단, <code>nextChildren</code>이 이제 배열이므로 <code>reconcileChildrenArray()</code>가 <code>reconcileChildFibers()</code> 내부에서 시작된다.</p>
<p><code>reconcileChildrenArray()</code>는 <code>key</code>가 있기 때문에 좀 더 복잡하다. 자세한 내용은 <a href="https://jser.dev/react/2022/02/08/the-diffing-algorithm-for-array-in-react/">How does ‘key’ work internally? List diffing in React</a>에서 확인하자.</p>
<p><code>key</code> 처리 외에 기본적으로 첫 번째 child fiber를 반환하고 계속 진행되며, React가 트리 구조를 linked list로 평면화하므로 siblings는 나중에 처리된다. 자세한 내용은 <a href="https://jser.dev/2023-07-14-initial-mount/react/2022/01/16/fiber-traversal-in-react/">How does React traverse Fiber tree internally</a>에서 확인하자.</p>
<p><code>&lt;Link/&gt;</code>의 경우 <code>&lt;App/&gt;</code>처럼 프로세스를 반복한다.</p>
<p><code>&lt;a/&gt;</code> 및 <code>&lt;button/&gt;</code>에 대해서는 해당 텍스트를 더 자세히 살펴보자.</p>
<p>둘은 약간 다른데, <code>&lt;a/&gt;</code>에는 children으로 정적 텍스트가 있고, <code>&lt;button/&gt;</code>에는 JSX 표현식 <code>{count}</code>가 있다. 그렇기 때문에 Demo Code에서 <code>&lt;a/&gt;</code>의 <code>nextChildren</code>은 null이지만, <code>&lt;button/&gt;</code>은  children으로 이어진다.</p>
<h3 id="311-updatehosttext">3.11 <code>updateHostText()</code></h3>
<p><code>&lt;button/&gt;</code>의 children은 배열이다 — <code>[”click me - “, “0”]</code>, 둘 다 <code>beginWork()</code>의 <code>updateHostText()</code>에서 처리된다.</p>
<pre><code class="language-ts">function updateHostText(current: null | Fiber, workInProgress: Fiber) {
  if (current === null) {
    tryToClaimNextHydratableTextInstance(workInProgress);
  }
  // Nothing to do here. This is terminal. We&#39;ll do the completion step
  // immediately after.
  return null;
}</code></pre>
<p>하지만 실질적으로 <code>updateHostText()</code>는 hydration 외에는 아무것도 하지 않는다. <code>&lt;a/&gt;</code> 및 <code>&lt;button/&gt;</code>은  Commit 단계에서 처리된다.</p>
<h3 id="312-completework에서-화면-밖에-생성되는-dom-노드">3.12 <code>completeWork()</code>에서 화면 밖에 생성되는 DOM 노드</h3>
<p><code>child</code> fiber가 없을 때까지 <code>chlid</code>를 <code>workInProgress</code> fiber로 설정하고 또다시 <code>beginWork</code>를 실행하는 것을 반복하다가, <code>child</code>가 없는 fiber에 다다르면 <code>completeWork()</code>를 실행한다.</p>
<p><img src="https://jser.dev/static/initial-mount/12.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></p>
<p><code>completeWork()</code> fiber 하위에 있는 모든 이펙트에 대한 정보를 모아준다. 해당 fiber에 <code>sibling</code> 이 있다면 해당 <code>sibling</code>을 <code>workInProgress</code> fiber로 설정하고 다시 <code>beginWork</code>를 실행한다. 이렇게 작업을 반복하다 보면, 마지막 <code>completeWork</code>를 실행하고 루트로 돌아오게 된다.</p>
<table>
<thead>
<tr>
<th><img src="https://jser.dev/static/initial-mount/13.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></th>
<th><img src="https://jser.dev/static/initial-mount/24.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></th>
</tr>
</thead>
</table>
<p>Cf. Parent fiber node가 <code>beginWork</code>는 children보다 먼저 수행하지만 <code>completeWork</code>는 나중에 수행한다. 각 노드는 <code>beginWork</code>와 <code>completeWork</code>를 실행하게 되는데, capture와 bubble 단계가 있는 DOM event로 생각할 수 있다. <code>beginWork</code>가 capture 단계고, <code>completeWork</code>가 bubble 단계에 해당한다.
<em><a href="https://jser.dev/react/2022/01/16/fiber-traversal-in-react/">https://jser.dev/react/2022/01/16/fiber-traversal-in-react/</a></em></p>
<p>Fiber Node에는 하나의 중요한 속성인 <code>stateNode</code>가 있다. 이는 intrinsic HTML 태그의 경우 실제 DOM 노드를 참조한다. 그리고 실제 DOM 노드 생성은 <code>completeWork()</code>에서 이루어진다.</p>
<blockquote>
<p>💻 src: completeWork</p>
</blockquote>
<pre><code class="language-ts">function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;
  popTreeContext(workInProgress);
  switch (workInProgress.tag) {
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    ...
    case HostRoot: {
      ...
      return null;
    }
    ...
    // 📌 HTML tags를 위한 type
    case HostComponent: {
      popHostContext(workInProgress);
      const type = workInProgress.type;
      // 📌 current 버전이 있다면, branch를 업데이트하러 간다.
      if (current !== null &amp;&amp; workInProgress.stateNode != null) {
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          renderLanes,
        );
      // 📌 하지만 아직 current 버전이 없으므로, branch를 mount하러 간다.
      } else {
        ...
        if (wasHydrated) {
            ...
        } else {
          const rootContainerInstance = getRootHostContainer();
          // 📌 실제 DOM 노드
          const instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
          // 📌 appendAllChildren은 DOM 노드가 생성될 때 중요하다.
          // 하위 트리의 직접 연결된 모든 DOM 노드의 parent여야 한다.
          appendAllChildren(instance, workInProgress, false, false);
          workInProgress.stateNode = instance;
&gt;
          if (
            // 📌 &lt;a/&gt;가 텍스트 노드를 어떻게 처리하는지 확인할 때 살펴볼 함수
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
        }
      }
      bubbleProperties(workInProgress);
      ...      
      return null;
    }
    case HostText: {
      const newText = newProps;
      if (current &amp;&amp; workInProgress.stateNode != null) {
        const oldText = current.memoizedProps;
        // If we have an alternate, that means this is an update and we need
        // to schedule a side-effect to do the updates.
        updateHostText(current, workInProgress, oldText, newText);
      } else {
        ...
        const rootContainerInstance = getRootHostContainer();
        const currentHostContext = getHostContext();
        const wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          prepareToHydrateHostTextInstance(workInProgress);
        } else {
          workInProgress.stateNode = createTextInstance(
            newText,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
        }
      }
      bubbleProperties(workInProgress);
      return null;
    }
  }
}</code></pre>
<p><code>&lt;a/&gt;</code>와 <code>&lt;button/&gt;</code>은 텍스트 노드를 다르게 처리한다.</p>
<p><code>&lt;button/&gt;</code>의 경우 <code>HostText</code> 분기로 이동하고 <code>createTextInstance()</code>가 새 텍스트 노드를 생성한다. 그러나 <code>&lt;a/&gt;</code>의 경우에는 조금 다르다. 더 깊이 들어가 보자.</p>
<p><code>completeWork()</code>의 <code>HostComponent</code>에 <code>finalizeInitialChildren()</code>이 있다.</p>
<blockquote>
<p>💻 src: finalizeInitialChildren, setInitialProperties, setProp</p>
</blockquote>
<pre><code class="language-ts">function setProp(
  domElement: Element,
  tag: string,
  key: string,
  value: mixed,
  props: any,
  prevValue: mixed,
): void {
  switch (key) {
      // 📌 문자열이나 숫자인 children은 컴포넌트의 text content로 처리된다.
      // 표현식이 있는 children은 배열이므로 이 분기에 속하지 않는다.
    case &#39;children&#39;: {
      if (typeof value === &#39;string&#39;) {
        const canSetTextContent =
          tag !== &#39;body&#39; &amp;&amp; (tag !== &#39;textarea&#39; || value !== &#39;&#39;);
        if (canSetTextContent) {
          setTextContent(domElement, value);
        }
      } else if (typeof value === &#39;number&#39; || typeof value === &#39;bigint&#39;) {
        const canSetTextContent = tag !== &#39;body&#39;;
        if (canSetTextContent) {
          setTextContent(domElement, &#39;&#39; + value);
        }
      }
      break;
    }
    // ...
  }
}</code></pre>
<h1 id="4-initial-mount-in-commit-phase">4. Initial Mount in Commit Phase</h1>
<p>지금까지:</p>
<ol>
<li><code>workInProgress</code> 버전의 Fiber 트리가 드디어 구축되었다!</li>
<li>backing DOM 노드도 생성되고 구성된다!</li>
<li>플래그는 필수 fiber에 설정되어 DOM 조작을 안내하는 데 도움이 된다!</li>
</ol>
<p>이제 React가 DOM을 어떻게 조작하는지 실제로 볼 시간이다.</p>
<h3 id="41-commitmutationeffects">4.1 <code>commitMutationEffects()</code></h3>
<p><code>commitMutationEffects()</code>는 DOM 조작을 처리한다.</p>
<blockquote>
<p>💻 src: commitMutationEffects, commitMutationEffectsOnFiber, recursivelyTraverseMutationEffects</p>
</blockquote>
<pre><code class="language-ts">function commitMutationEffects(
  root: FiberRoot,
  finishedWork: Fiber, // 📌 새로 구축된 Fiber Tree를 보유하는 HostRoot의 Fiber Node
  committedLanes: Lanes,
) {
  inProgressLanes = committedLanes;
  inProgressRoot = root;
&gt;
  setCurrentDebugFiberInDEV(finishedWork);
  commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
  setCurrentDebugFiberInDEV(finishedWork);
&gt;
  inProgressLanes = null;
  inProgressRoot = null;
}</code></pre>
<pre><code class="language-ts">function commitMutationEffectsOnFiber(
  finishedWork: Fiber,
  root: FiberRoot,
  lanes: Lanes,
) {
  const current = finishedWork.alternate;
  const flags = finishedWork.flags;
  switch (finishedWork.tag) {
    case FunctionComponent:
    ...
    case SimpleMemoComponent: {
      // 📌 이 재귀 호출은 subtree가 먼저 처리되도록 한다.
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      // 📌 ReconciliationEffects는 Insertion 등을 의미한다.
      commitReconciliationEffects(finishedWork);
      ...
      return;
    }
    ...
    case HostComponent: {
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      ...
      return;
    }
    case HostText: {
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      ...
      return;
    }
    case HostRoot: {
      if (supportsResources) {
        prepareToCommitHoistables();
&gt;
        const previousHoistableRoot = currentHoistableRoot;
        currentHoistableRoot = getHoistableRoot(root.containerInfo);
&gt;
        recursivelyTraverseMutationEffects(root, finishedWork, lanes);
        currentHoistableRoot = previousHoistableRoot;
&gt;
        commitReconciliationEffects(finishedWork);
      } else {
        recursivelyTraverseMutationEffects(root, finishedWork, lanes);
        commitReconciliationEffects(finishedWork);
      }
      ...
      return;
    }
    ...
    default: {
      recursivelyTraverseMutationEffects(root, finishedWork, lanes);
      commitReconciliationEffects(finishedWork);
      return;
    }
  }
}</code></pre>
<h3 id="42-commitreconciliationeffects">4.2 <code>commitReconciliationEffects()</code></h3>
<p><code>commitReconciliationEffects()</code>는 삽입, 재정렬 등을 처리한다.</p>
<blockquote>
<p>💻 src: commitReconciliationEffects</p>
</blockquote>
<pre><code class="language-ts">function commitReconciliationEffects(finishedWork: Fiber) {
  // Placement effects (insertions, reorders) can be scheduled on any fiber
  // type. They needs to happen after the children effects have fired, but
  // before the effects on this fiber have fired.
  const flags = finishedWork.flags;
  // 📌 플래그는 여기에서 체크한다!
  if (flags &amp; Placement) {
    try {
      commitPlacement(finishedWork);
    } catch (error) {
      captureCommitPhaseError(finishedWork, finishedWork.return, error);
    }
    finishedWork.flags &amp;= ~Placement;
  }
  if (flags &amp; Hydrating) {
    finishedWork.flags &amp;= ~Hydrating;
  }
}</code></pre>
<p>Demo Code에 있는 <code>&lt;App/&gt;</code>의 Fiber Node가 실제로 커밋된다.</p>
<p><img src="https://jser.dev/static/initial-mount/25.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></p>
<h3 id="43-commitplacement">4.3 <code>commitPlacement()</code></h3>
<blockquote>
<p>💻 src: commitPlacement</p>
</blockquote>
<pre><code class="language-ts">function commitPlacement(finishedWork: Fiber): void {
  ...  
  // Recursively insert all host nodes into the parent.
  const parentFiber = getHostParentFiber(finishedWork);
  // 📌 여기에서 parentFiber의 타입을 체크하고 있다.
  // 왜냐하면 Insertion은 parent node에 수행되기 때문이다.
  switch (parentFiber.tag) {
    case HostSingleton: {
      if (supportsSingletons) {
        const parent: Instance = parentFiber.stateNode;
        const before = getHostSibling(finishedWork);
        // We only have the top Fiber that was inserted but we need to recurse down its
        // children to find all the terminal nodes.
        insertOrAppendPlacementNode(finishedWork, before, parent);
        break;
      }
      // Fall through
    }
    // 📌 Initial mount에서는 이 분기를 건드리지 않는다.
    case HostComponent: {
      const parent: Instance = parentFiber.stateNode;
      if (parentFiber.flags &amp; ContentReset) {
        // Reset the text content of the parent before doing any insertions
        resetTextContent(parent);
        // Clear ContentReset from the effect tag
        parentFiber.flags &amp;= ~ContentReset;
      }
&gt;
      const before = getHostSibling(finishedWork);
      // We only have the top Fiber that was inserted but we need to recurse down its
      // children to find all the terminal nodes.
      insertOrAppendPlacementNode(finishedWork, before, parent);
      break;
    }
    // 📌 Initial mount를 위한 Placement 플래그를 가진 Fiber Node는 &lt;App/&gt;이다.
    // &lt;App/&gt;의 parentFiber는 HostRoot다.
    case HostRoot:
    case HostPortal: {
      // 📌 HostRoot의 stateNode는 FiberRootNode를 가리킨다.
      const parent: Container = parentFiber.stateNode.containerInfo;
      const before = getHostSibling(finishedWork);
&gt;      insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
      break;
    }
    default:
      throw new Error(
        &#39;Invalid host parent fiber. This error is likely caused by a bug &#39; +
          &#39;in React. Please file an issue.&#39;,
      );
  }
}</code></pre>
<p>아이디어는 <code>finishedWork</code>의 DOM을 parent container의 올바른 위치에 insert나 append하는 것이다.</p>
<blockquote>
<p>💻 src: insertOrAppendPlacementNodeIntoContainer</p>
</blockquote>
<pre><code class="language-ts">function insertOrAppendPlacementNodeIntoContainer(
  node: Fiber,
  before: ?Instance,
  parent: Container,
): void {
  const {tag} = node;
  const isHost = tag === HostComponent || tag === HostText;
  if (isHost) {
    const stateNode = node.stateNode;
    // 📌 DOM element의 경우, 그냥 삽입하면 된다.
    if (before) {
      insertInContainerBefore(parent, stateNode, before);
    } else {
      appendChildToContainer(parent, stateNode);
    }
  } else if (
    tag === HostPortal ||
    (supportsSingletons ? tag === HostSingleton : false)
  ) {
    ...
  } else {
    // 📌 non-DOM element의 경우, 재귀적으로 children을 처리해야 한다.
    const child = node.child;
    if (child !== null) {
      insertOrAppendPlacementNodeIntoContainer(child, before, parent);
      let sibling = child.sibling;
      while (sibling !== null) {
        insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
        sibling = sibling.sibling;
      }
    }
  }
}</code></pre>
<p>그리고 여기에서 최종적으로 DOM이 삽입된다.</p>
<p><img src="https://jser.dev/static/initial-mount/26.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></p>
<h1 id="5-summary">5. Summary</h1>
<p>드디어 DOM이 어떻게 생성되고 컨테이너에 삽입되는지 살펴보았다.</p>
<p>따라서 initial mount의 경우,</p>
<ol>
<li><p>Fiber 트리는 reconciliation 중에 lazy하게 생성되며, 동시에 backing DOM 노드가 생성 및 구성된다.</p>
</li>
<li><p><code>HostRoot</code>의 직계 child는 <code>Placement</code>로 표시된다. (Demo Code에서는 <code>&lt;App/&gt;</code>에 해당된다.)</p>
</li>
<li><p>Commit 단계에서는 <code>Placement</code>를 통해 fiber를 찾는다. parent가 HostRoot이므로 해당 DOM 노드가 컨테이너에 삽입된다.</p>
</li>
</ol>
<p><img src="https://jser.dev/static/initial-mount/27.avif" alt="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-"></p>
<blockquote>
<p>JSer가 그린 initial mount 순서도 슬라이드를 보면 이해에 큰 도움이 된다!
🔗 <a href="https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-">https://jser.dev/2023-07-14-initial-mount/#how-react-does-initial-mount-first-time-render-</a></p>
</blockquote>
<h1 id="references-1">References</h1>
<ul>
<li><p><a href="https://jser.dev/2023-07-14-initial-mount/">[JSer.dev] How does React do the initial mount internally?</a></p>
</li>
<li><p><a href="https://blog.mathpresso.com/react-deep-dive-fiber-88860f6edbd0">[Team QANDA] React Deep Dive — Fiber</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Overview]]></title>
            <link>https://velog.io/@hee-suh/React-Internals-Deep-Dive-1-Overview</link>
            <guid>https://velog.io/@hee-suh/React-Internals-Deep-Dive-1-Overview</guid>
            <pubDate>Tue, 04 Jun 2024 07:03:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://jser.dev/">JSer.dev</a>의 <a href="https://jser.dev/series/react-source-code-walkthrough">React Internals Deep Dive</a>를 번역하여 정리한 글입니다.</p>
</blockquote>
<blockquote>
<p>💡 <a href="https://jser.dev/series/react-source-code-walkthrough">React Internals Deep Dive</a>는 React 소스 코드를 살펴보며 React 동작 원리에 딥다이브하는 시리즈입니다. 1번 에피소드인 <a href="https://jser.dev/2023-07-11-overall-of-react-internals">Overview</a>부터 에피소드를 하나씩 정리해서 포스팅하려고 합니다.</p>
</blockquote>
<ul>
<li>&quot;💬&quot;로 시작하는 문장은 제가 추가한 문장입니다.</li>
<li>&quot;📌&quot;로 시작하는 코드 주석은 JSer가 추가한 주석입니다.</li>
<li>⚠️ <a href="https://github.com/facebook/react/releases/tag/v18.2.0">React@18.2.0</a>을 기반으로 작성되었으며, 최신 버전에서는 구현이 변경되었을 수 있습니다.</li>
</ul>
<h4 id="📝-how-does-react-work-under-the-hood--the-overview-of-react-internals">📝 <a href="https://jser.dev/2023-07-11-overall-of-react-internals">How does React work under the hood ? The Overview of React internals</a></h4>
<h1 id="1-tips-on-learning-react-internals">1. Tips on learning React internals</h1>
<h3 id="official-materials">Official materials</h3>
<p><a href="https://react.dev/">React.dev</a>는 React API뿐만 아니라, React 코어 팀의 생각도 배울 수 있는 곳이다. 예제, 주의 사항와 함께 선택의 근거를 자세히 설명하고 있다.</p>
<p><a href="https://github.com/reactwg">React Working Group</a>은 React 18을 위해 만들어졌으며, 새로운 아이디어에 대한 토론을 확인할 수 있다.</p>
<h3 id="react-team">React team</h3>
<p><a href="https://react.dev/community/team">React core team members</a>를 팔로우해서 React 팀이 무엇을 하고 있는지 계속 업데이트하자.</p>
<h3 id="react-repo">React repo</h3>
<p><a href="https://github.com/facebook/react">React repo@github</a>에 있는 PR과 코드 리뷰에, 코드에 있는 주석보다 더 나은 설명이 포함되어있다.</p>
<h3 id="react-source-code">React source code</h3>
<p>인터넷에 있는 대부분의 글은 몇 가지 일반적인 아이디어만 설명하고 있기 때문에, 글을 읽은 후에도 여전히 <a href="https://bigfrontend.dev/react-quiz">React Quizzes</a>를 풀 수 없다.</p>
<p>React 소스 코드를 직접 살펴보면 분명 도움이 될 것이다.</p>
<p><em>Cf. <a href="https://bigfrontend.dev/react-quiz">https://bigfrontend.dev/react-quiz</a></em></p>
<h3 id="find-the-critical-path">Find the critical path</h3>
<p>한 번에 모든 걸 이해하지 않아도 된다. 전반적인 작동 방식을 파악하고 퍼즐을 하나씩 맞춰나가면서 이해의 폭을 넓혀보자!</p>
<h1 id="2-debugging-the-overview-of-react-internals-with-breakpoints">2. Debugging the overview of React internals with breakpoints</h1>
<p><a href="https://jser.dev/demos/react/overview">Demo</a>를 이용해서 같이 디버깅해보자!</p>
<h2 id="21-set-up-breakpoints">2.1 Set up breakpoints</h2>
<pre><code class="language-jsx">// 💬 디버깅할 코드
const { useState, useEffect } = React;

function App() {
  const [count, setCount] = useState(1);
  debugger;   // 📌 컴포넌트가 실행되는 시점을 알려주는 debugger
  useEffect(() =&gt; {
    debugger; // 📌 effect hooks가 실행되는 시점을 알려주는 debugger
    setCount((count) =&gt; count + 1);
  }, []);
  return &lt;button&gt;{count}&lt;/button&gt;;
}

ReactDOM.createRoot(document.getElementById(&quot;container&quot;)).render(&lt;App /&gt;);</code></pre>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/6c7d1efc-adad-4be0-a805-59881b66363f/image.png" alt="DOM container에 breakpoint를 추가하여, DOM이 조작될 때 어떤 일이 일어나는지 call stack을 통해 파악"></p>
<pre><code>DOM container에 breakpoint를 추가하여, DOM이 조작될 때 어떤 일이 일어나는지 call stack을 통해 파악</code></pre><h2 id="22-first-pause-at-rendering-the-component">2.2 First pause at rendering the component</h2>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/7110ca37-aee5-452b-84b5-f7ba1228aadb/image.png" alt="컴포넌트가 mount 되면 debugger에 의해 정지"></p>
<pre><code>컴포넌트가 mount 되면 debugger에 의해 정지</code></pre><ol>
<li><p><code>ReactDOMRoot.render()</code> → 직접 작성한 사용자 측 코드로, 우선 <code>createRoot()</code>를 한 후 <code>render()</code>를 한다.</p>
</li>
<li><p><code>scheduleUpdateOnFiber()</code> → 앱의 어느 부분을 렌더링해야 하는지 React 런타임에 알려준다. Initial mount의 경우, 이전 버전이 없으므로 root에서 호출된다.</p>
</li>
<li><p><code>ensureRootIsScheduled()</code> → <code>performConcurrentWorkOnRoot()</code> 가 스케줄링되도록 “보장”하는 중요한 호출이다.</p>
</li>
<li><p><code>scheduleCallback()</code> → 스케줄링이 일어나는 곳으로, <a href="https://jser.dev/react/2022/03/16/how-react-scheduler-works/">React Scheduler</a>의 일부분이며, <a href="https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel"><code>MessageChannel</code></a>과 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage"><code>postMessage()</code></a>를 이용하여 비동기로 macrotask를 예약한다. <em>Cf. <a href="https://v8docs.nodesource.com/node-12.0/db/d08/classv8_1_1_microtask_queue.html">v8 macrotask queue </a></em></p>
</li>
<li><p><code>workLoop()</code> → <a href="https://jser.dev/react/2022/03/16/how-react-scheduler-works/">React Scheduler</a>가 예약된 작업을 처리하는 곳이다.</p>
</li>
<li><p><code>performConcurrentWorkOnRoot()</code> → 예약된 작업이 실행되며, 컴포넌트가 실제로 렌더링된다.</p>
</li>
</ol>
<h2 id="23-second-pause-at-dom-manipulation">2.3 Second pause at DOM manipulation</h2>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/14780dfc-450f-4f36-8926-ed4a86e5669b/image.png" alt="DOM이 조작될 때 debugger에 의해 정지"></p>
<pre><code>DOM이 조작될 때 debugger에 의해 정지</code></pre><p>2.2의 “render” 단계 이후에 DOM을 업데이트하는 “commit” 단계다.</p>
<ol>
<li><p><code>commitRoot()</code> → 이전 렌더링 단계에서 파생된 필수 DOM 업데이트를 커밋할뿐만 아니라, effect 처리와 같은 더 많은 작업도 수행한다.</p>
</li>
<li><p><code>commitMutationEffects()</code> → host DOM을 업데이트한다.</p>
</li>
</ol>
<h2 id="24-third-pause-at-execution-of-effects">2.4 Third pause at execution of effects</h2>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/7ad73c07-635a-4b30-9501-dc284b5c10a3/image.png" alt="useEffect() 호출에서 debugger에 의해 정지"></p>
<pre><code>useEffect() 호출에서 debugger에 의해 정지</code></pre><ol>
<li><code>flushPassiveEffects()</code> → <code>useEffect()</code>에서 생성된 모든 effects를 flush한다.</li>
</ol>
<p><code>flushPassiveEffects()</code>는 <code>postMessage()</code>에 의해 비동기화되기 때문에 즉시 실행되지 않고 스케줄링되며, <code>commitRoot()</code> 내부에 있다.</p>
<h2 id="25-pause-again-at-rendering-component">2.5 Pause again at rendering component</h2>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/5cb321e2-53da-46f8-b4b8-315109bdf017/image.png" alt="컴포넌트가 리렌더링 되면 debugger에 의해 정지"></p>
<pre><code>컴포넌트가 리렌더링 되면 debugger에 의해 정지</code></pre><p><code>useEffect()</code>에서 re-render를 trigger하기 위해 <code>setState()</code>를 호출했을 때, 콜 스택을 확인해보면 전체 re-render는 <code>performConcurrentWorkOnRoot()</code>에서 <code>mountIndeterminateComponent()</code>대신 <code>updateFunctionComponent()</code>를 호출하는 것을 제외하고 initial render와 유사하다.</p>
<p>React 소스 코드에서 <code>mount</code>는 initial render를 뜻하는데, initial render에는 비교(diff)해야 할 이전 버전이 없기 때문이다.</p>
<h1 id="3-the-overview-of-react-internals">3. The overview of React internals</h1>
<blockquote>
<p>Cf. Render and Commit in <a href="https://react.dev/learn/render-and-commit">react.dev</a>
<img src="https://velog.velcdn.com/images/hee-suh/post/4cbdcbee-0a0e-498f-8d01-c4786aa05569/image.png" alt="출처: https://react.dev/learn/render-and-commit Illustrated by Rachel Lee Nabors"></p>
</blockquote>
<pre><code>출처: https://react.dev/learn/render-and-commit Illustrated by Rachel Lee Nabors</code></pre><ul>
<li>Step 1: Trigger a render</li>
<li>Step 2: React renders your components </li>
<li>Step 3: React commits changes to the DOM</li>
</ul>
<blockquote>
<p>💬 React 공식 문서에서는 컴포넌트가 화면에 표시되는 과정을 세 단계인 Trigger, Render, Commit으로 설명하고 있다. JSer는 Trigger와 Render 사이에 있는 Schedule 단계를 명시하여 내부 동작 원리를 설명하고 있다.</p>
</blockquote>
<p><img src="https://jser.dev/static/react-internals-overview-light.png" alt="출처: https://jser.dev/2023-07-11-overall-of-react-internals"></p>
<pre><code>출처: https://jser.dev/2023-07-11-overall-of-react-internals</code></pre><h2 id="31-trigger">3.1 Trigger</h2>
<p>컴포넌트가 렌더링되는 이유는 두 가지가 있다.</p>
<ul>
<li>initial render</li>
<li>state 업데이트로 인한 re-render</li>
</ul>
<p>렌더링되는 이유와 상관 없이, “Trigger” 단계에서 모든 작업이 시작된다. “Trigger”는 앱의 어느 부분을(<code>scheduleUpdateOnFiber()</code>) 어떻게 렌더링 해야 하는지 React 런타임에 알려주는 단계다.</p>
<p>“작업 생성” 단계로 생각할 수 있으며, <code>ensureRootIsScheduled()</code>가 수행되면 작업 생성이 완료되고, 해당 작업은 <code>scheduleCallback()</code>에 의해 Scheduler로 전달된다.</p>
<h2 id="32-schedule">3.2 Schedule</h2>
<p>React Scheduler는 task를 우선순위에 따라 처리하는 우선순위 큐다.</p>
<p><code>scheduleCallback()</code>은 렌더링이나 effect 실행 같은 task를 예약하기 위해 런타임 코드에서 호출된다.</p>
<p>Scheduler 내부의 <code>workLoop()</code>는 task들이 실제로 실행되는 곳이다.</p>
<p>Cf. <a href="https://jser.dev/react/2022/03/16/how-react-scheduler-works/">How React Scheduler works?</a></p>
<h2 id="33-render">3.3 Render</h2>
<p>Render는 스케줄링(예약)된 작업이다.</p>
<p>새로운 Fiber Tree를 계산하고, host DOM에 적용하기 위해 어떤 업데이트가 필요한지 파악한다.</p>
<blockquote>
<p>ℹ️ <strong>Fiber Tree</strong>
앱의 현재 상태를 나타내는 내부 트리와 같은 구조다.
<em>Cf. 예전에는 Virtual DOM이라고 불렸으나, 이제는 DOM에만 해당되는 것이 아니라서 React team에서 더 이상 Virutal DOM이라고 부르지 않는다.</em></p>
</blockquote>
<p><code>performConcurrentWorkOnRoot()</code>는 Trigger 단계에서 생성되고, Scheduler에서 우선순위가 조정되며, Render에서 실제로 실행된다.</p>
<p>Concurrent feature가 사용되는 경우, “Render” 단계는 중단(interrupt)되었다가 다시 시작될 수 있으므로 단계가 상당히 복잡해진다.</p>
<h2 id="34-commit">3.4 Commit</h2>
<p>새로운 Fiber Tree가 구성되고 최소한의 업데이트가 나왔다면, host DOM에 업데이트를 “commit”할 시간이다.</p>
<p>이 단계에서는 DOM 조작(<code>commitMutationEffects()</code>)과 Effects(<code>flushPassiveEffects()</code>, <code>commitLayoutEffects()</code>) 처리 등 여러 작업이 수행된다.</p>
<h1 id="4-summary">4. Summary</h1>
<p><a href="https://jser.dev/series/react-source-code-walkthrough">React Internals Deep Dive</a>의 첫 번째 에피소드로, debugger를 이용하여 리액트 동작 원리를 파악하는 방법을 알아보았다.</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://jser.dev/2023-07-11-overall-of-react-internals">[JSer.dev] How does React work under the hood ? The Overview of React internals</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Load Balancing]]></title>
            <link>https://velog.io/@hee-suh/Load-Balancing</link>
            <guid>https://velog.io/@hee-suh/Load-Balancing</guid>
            <pubDate>Mon, 29 Apr 2024 15:03:29 GMT</pubDate>
            <description><![CDATA[<h1 id="로드-밸런싱이란">로드 밸런싱이란</h1>
<p>로드 밸런싱은 애플리케이션을 지원하는 리소스 풀 전체에 네트워크 트래픽을 균등하게 배포하는 방법이다. 로드 밸런서는 사용자와 서버 그룹 사이에 위치하며 보이지 않는 촉진자 역할을 하여 모든 리소스 서버가 동일하게 사용되도록 하는 디바이스다.</p>
<p><img src="https://miro.medium.com/v2/format:webp/0*FaLx60hNj5A83_lF.png" alt="출처: https://miro.medium.com/v2/format:webp/0*FaLx60hNj5A83_lF.png"></p>
<h1 id="로드-밸런싱의-이점">로드 밸런싱의 이점</h1>
<p>로드 밸런싱은 애플리케이션 서버와 방문자 또는 클라이언트 간의 인터넷 트래픽을 지시하고 제어한다. 결과적으로 애플리케이션의 <strong>가용성, 확장성, 보안 및 성능이 향상</strong>된다.</p>
<p>Cf. 자세한 내용이 알고 싶다면, <a href="https://aws.amazon.com/ko/what-is/load-balancing/">로드 밸런싱이란 무엇인가요? - 로드 밸런싱 알고리즘 설명 - AWS</a>의 <strong>로드 밸런싱의 이점은 무엇인가요?</strong> 섹션을 확인하자.    </p>
<h1 id="algorithm">Algorithm</h1>
<p>고정된 규칙을 따르며 현재 서버 상태와 무관한 <strong>정적 로드밸런싱</strong>과, 트래픽을 분산하기 전에 서버의 현재 상태를 검사하는 <strong>동적 로드밸런싱</strong>으로 나뉜다. 라운드 로빈과 같은 정적 알고리즘은 단순성이 핵심인 상태 비저장 애플리케이션에 이상적이다. 응답성과 가용성에 대한 요구가 높은 복잡한 애플리케이션의 경우 동적 알고리즘을 통해 보다 세밀하게 조정된 부하 분산을 제공한다.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https://substack-post-media.s3.amazonaws.com/public/images/12dffcce-f231-48cc-915f-d53c0f8bce0c_3735x3573.jpeg" alt="출처: https://blog.bytebytego.com/p/ep47-common-load-balancing-algorithms"></p>
<h2 id="정적-부하-분산-static-load-balancing"><strong>정적 부하 분산 Static Load Balancing</strong></h2>
<p>정적 로드 밸런싱 알고리즘은 고정된 규칙을 따르며 현재 서버 상태와 무관하다.</p>
<ol>
<li><p><strong>Round Robin</strong></p>
<p> 클라이언트 요청이 다른 서비스 인스턴스에 순차적으로 전송되며, 권한 있는 이름 서버(authoritative name server)가 특수 하드웨어나 소프트웨어 대신 로드 밸런싱을 수행한다.</p>
<p> <a href="http://cnn.com">cnn.com</a> 같은 인기 있는 사이트는 여러 서버에 중복되어 있어서, 각 서버가 다른 종단 시스템에서 수행되고 다른 IP 주소를 갖는다. 중복 웹 서버의 경우, 여러 IP 주소가 하나의 정식 호스트 이름과 연관되어 있고, DNS 데이터베이스는 이 IP 주소 집합을 갖고 있다. Name Server는 서버 팜에 있는 여러 서버의 IP 주소를 차례대로 또는 라운드 로빈 방식으로 반환한다.</p>
</li>
</ol>
<ul>
<li><p>RR 로드밸런싱은 구현하기 쉽다.</p>
</li>
<li><p>서버들이 거의 동일한 컴퓨팅 성능과 저장 용량을 가진 경우에 잘 작동한다.</p>
</li>
</ul>
<blockquote>
<p><strong>ℹ️ 라운드 로빈 스케줄링</strong><br>    시분할 시스템을 위해 설계된 선점형 스케줄링의 하나로서, 프로세스들 사이에 우선순위를 두지 않고, 순서대로 시간단위로 CPU를 할당하는 방식의 CPU 스케줄링 알고리즘이다.</p>
</blockquote>
<ol start="2">
<li><p><strong>Sticky round-robin</strong></p>
<p> RR 알고리즘을 개선한 것으로, 동일한 세션 기반 애플리케이션에 대한 동일한 클라이언트의 후속 요청은 고정(sticky) 요청으로 간주되며, 동일한 인스턴스로 라우팅된다. Stickiness는 쿠키를 사용하거나 명시적 URL rewriting을 통해 이루어진다.</p>
</li>
</ol>
<ul>
<li><p>애플리케이션이 세션 정보나 요청 간 상태를 유지해야 할 때 유용하다.
(e.g. e-commerce 장바구니, 웹 기반 이메일 서비스)</p>
</li>
<li><p>특정 사용자 세션이 특히 많은 트래픽을 일으키면 불균형을 가져올 수 있으므로, 트래픽이 예측하기 어렵거나 트래픽 강도 변화의 폭이 큰 경우에는 좋은 선택이 아닐 수 있다.</p>
</li>
</ul>
<ol start="3">
<li><p><strong>Weighted round-robin</strong></p>
<p> 우선순위 또는 용량에 따라 각 서버에 서로 다른 가중치를 할당할 수 있다. 가중치가 높은 서버는 이름 서버에서 들어오는 애플리케이션 트래픽을 더 많이 수신한다.</p>
</li>
</ol>
<ul>
<li><p>서버들의 성능이 다를 때 사용하면 좋다.</p>
</li>
<li><p>수동으로 가중치를 설정해줘야 하고 실시간으로 조절하기 어렵다.</p>
</li>
</ul>
<ol start="4">
<li><p><strong>Hash</strong></p>
<p> 클라이언트 IP 주소나 URL에 해시 함수를 적용하여 숫자로 변환한 다음 개별 서버에 매핑한다.</p>
</li>
</ol>
<ul>
<li><p>서버의 개수가 변하지 않는다면 해시 함수는 같은 입력에 대해 같은 결과를 리턴하므로, 지정된 서버로만 계속 접속하게 된다. 세션을 유지해야하는 사이트(인증, 보안)에서 주로 사용한다.</p>
</li>
<li><p>최적의 해시 함수를 선택하기 어렵다. 해싱 연산이 비싸서 성능 저하를 일으킬 수 있으며, key collision이 발생하면 데이터 불균형과 성능 저하가 발생할 수 있다.</p>
</li>
</ul>
<blockquote>
<p>💡 <strong>sticky session vs consistent hash</strong></p>
</blockquote>
<ul>
<li>공통점: 세션을 유지해야 하는 사이트에서 주로 사용한다.</li>
<li>차이점: 해싱은 서버를 scale-out 또는 scale-in 하더라도, 알고리즘에 의해 대부분의 요청이 원래 사용하던 서버에 전송된다.
Cf. <a href="https://stackoverflow.com/questions/58361938/why-do-we-need-consistent-hashing-when-round-robin-can-distribute-the-traffic-ev">why do we need consistent hashing when round robin can distribute the traffic evenly</a>    </li>
</ul>
<h2 id="동적-부하-분산-dynamic-load-balancing"><strong>동적 부하 분산 Dynamic Load Balancing</strong></h2>
<p>동적 로드 밸런싱 알고리즘은 트래픽을 분산하기 전에 서버의 현재 상태를 검사한다.</p>
<ol>
<li><p><strong>Least connections</strong></p>
<p> 연결은 클라이언트와 서버 간의 개방형 통신 채널이다. 클라이언트는 서버에 첫 번째 요청을 전송할 때 서로 활성 연결을 인증하고 설정한다. 최소 연결 방법에서 로드 밸런서는 활성 연결이 가장 적은 서버를 확인하고 해당 서버로 트래픽을 전송한다.</p>
</li>
</ol>
<ul>
<li><p>트래픽을 예측할 수 없고 강도가 매우 다양한 환경에서는 좋은 옵션이 될 수 있다.</p>
</li>
<li><p>RR 알고리즘과 마찬가지로 최소 연결 수 알고리즘은 각 서버의 처리 능력을 고려하지 않는다. 따라서 서버의 성능이 서로 다른 환경에서는 최선의 선택이 아닐 수 있다.</p>
</li>
</ul>
<ol start="2">
<li><p><strong>Least response time</strong></p>
<p> 응답 시간은 서버가 들어오는 요청을 처리하고 응답을 전송하는 데 걸리는 총 시간이다. 최소 응답 시간 방법은 서버 응답 시간과 활성 연결을 결합하여 최상의 서버를 결정한다. 로드 밸런서는 이 알고리즘을 사용하여 모든 사용자에게 더 빠른 서비스를 보장한다.</p>
</li>
</ol>
<ul>
<li><p>현재 서버 성능에 매우 유연하게 적응한다.</p>
</li>
<li><p>지속적인 모니터링이 필요하므로, 복잡성과 오버헤드가 늘어난다.</p>
</li>
</ul>
<h1 id="l4l7-스위치"><strong>L4/L7 스위치</strong></h1>
<p>서버 부하 분산을 담당하는 Network Switch를 L4/L7 Switch라고 부르며, Cloud에서는 이를 Load Balancer라고 부른다.</p>
<p>Cf. <a href="http://wiki.hash.kr/index.php/OSI_7_%EA%B3%84%EC%B8%B5">OSI 7 계층</a>을 기반으로 하여, OSI Layer 4, Layer 7 스위치다.</p>
<blockquote>
<p><strong>💡 스위치의 분류</strong></p>
</blockquote>
<pre><code>MAC 주소            IP 주소      TCP/UDP 포트번호(1024)    패킷의 내용
0000-0C12-3456     10.1.11     80                      GET/ images/abc.gif
|----------------|-----------|
     L2 스위칭        L3 스위칭
                 |----------------------------------|
                               L4 스위칭
                 |-------------------------------------------------------|
                                     L5 - L7 스위칭</code></pre><ul>
<li>L2: OSI Layer 2에 속하는 MAC 주소를 참조하여 스위칭하는 장비</li>
<li>L3: OSI Layer 3에 속하는 IP 주소를 참조하여 스위칭하는 장비  </li>
<li>L4: OSI Layer 3-4에 속하는 IP 주소 및 TCP/UDP 포트 정보를 참조하여 스위칭하는 장비</li>
<li>L7: OSI Layer 3-7에 속하는 IP 주소, TCP/UDP 포트 정보와 패킷 내용까지 참조하여 스위칭</li>
</ul>
<h2 id="l4-load-balancing">L4 Load Balancing</h2>
<p>전송 계층(L4)에서 로드밸런서는 방화벽 같이 작동한다. </p>
<p><strong>IP 주소와 TCP/UDP port 정보</strong>를 이용해서 서버와 클라이언트 연결을 라우팅하며, Source IP 또는 Destination IP를 NAT(Network Address Translation)하여 보낸다.</p>
<p>클라이언트와 서버 간 하나의 TCP 연결이 형성된다.</p>
<blockquote>
<p>💡 <strong>NAT(Network Address Translation)</strong>
<a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=194&amp;id=424">IP 주소</a>를 변환하는 기술로, 1개의 실제 <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=2260&amp;id=426">공인 IP 주소</a>에, 다량의 가상 <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=2260&amp;id=426">사설 IP 주소</a>를 할당 및 <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=608&amp;id=1488">매핑</a>한다. 
<a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=5183&amp;id=1064">IPv4</a>의 <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=5511&amp;id=426">IPv4 주소 고갈</a> 및 <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=1327&amp;id=853">라우팅 테이블</a> 대형화 (<a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=539&amp;id=345">Routing</a> <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=868&amp;id=1472">Scalability</a>)에 대한 해소책으로 사용하기 시작했다.
통상, <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=722&amp;id=833">방화벽</a> 등과 결합되어 함께 수행되며, 내/외 <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=2745&amp;id=434">주소변환</a> 규칙을 외부에서 알 수 없으므로, 내부 망에 대한 <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=3660&amp;id=781">정보</a>(내부망 <a href="http://www.ktword.co.kr/test/view/view.php?m_temp1=1939&amp;id=772">주소</a>,숫자 설정 등)가 외부에 노출되지 않는다. (프라이버시 보호, 은닉)
<em>Cf. <a href="http://www.ktword.co.kr/test/view/view.php?no=1676">http://www.ktword.co.kr/test/view/view.php?no=1676</a></em></p>
</blockquote>
<p><img src="https://assets.digitalocean.com/articles/HAProxy/layer_4_load_balancing.png" alt="출처: https://www.digitalocean.com/community/tutorials/an-introduction-to-haproxy-and-load-balancing-concepts#types-of-load-balancing"></p>
<h3 id="advantages"><strong>Advantages</strong></h3>
<ul>
<li>단순하다.</li>
<li>패킷의 내용을 확인하지 않으므로, 빠르다.</li>
<li>비교적 저렴하다.</li>
</ul>
<h3 id="limitations"><strong>Limitations</strong></h3>
<ul>
<li>기능이 제한된다. (no caching, …)</li>
</ul>
<h3 id="when-to-use">When to Use</h3>
<p>기본 요구사항을 가지며 트래픽이 높은 경우에 사용한다. (e.g. 데이터베이스 쿼리 분산 또는 일반 웹 트래픽 분산)</p>
<h2 id="l7-load-balancing">L7 Load Balancing</h2>
<p>OSI 모델의 최상위 계층인 애플리케이션 계층(L7)에서 로드밸런서는 reverse proxy처럼 작동한다. 따라서 두 개의 TCP 연결을 유지한다. (하나는 클라이언트와, 하나는 서버와 연결)</p>
<p>L7 로드밸런서는 <strong>HTTP 헤더</strong>와 <strong>메시지 내용</strong>에 포함된 다양한 특성(e.g. URL, 데이터 타입(text, video, graphics), cookie에 있는 정보)을 이용하여 라우팅한다.</p>
<p><img src="https://assets.digitalocean.com/articles/HAProxy/layer_7_load_balancing.png" alt="출처: https://www.digitalocean.com/community/tutorials/an-introduction-to-haproxy-and-load-balancing-concepts#types-of-load-balancing"></p>
<h3 id="advatanges">Advatanges</h3>
<ul>
<li>다양한 기능을 제공한다. (e.g. caching, security filtering, application health check)</li>
<li>content-based 로드밸런싱이 가능하기 때문에, 서로 다른 서버가 서로 다른 유형의 요청을 처리할 수 있다.</li>
</ul>
<h3 id="limitations-1">Limitations</h3>
<ul>
<li>비용이 많이 든다.</li>
</ul>
<h3 id="when-to-use-1">When to Use</h3>
<p>유저 데이터, content type, 특정 서버 기능에 기반한 지능형 라우팅이 필요한 복잡한 애플리케이션에 사용한다.</p>
<h1 id="로드-밸런싱-유형">로드 밸런싱 유형</h1>
<p>로드 밸런서가 트래픽을 리디렉션하기 위해 클라이언트 요청에서 확인하는 콘텐츠에 따라 로드 밸런싱을 분류할 수 있다.</p>
<h3 id="application-load-balancer-alb">Application Load Balancer, ALB</h3>
<p>복잡한 최신 애플리케이션에는 단일 애플리케이션 기능을 전담하는 여러 서버를 포함하는 여러 서버 팜이 있다. Application Load Balancer는 HTTP 헤더 또는 SSL 세션 ID와 같은 요청 콘텐츠를 확인하여 트래픽을 리디렉션한다.</p>
<p>예를 들어, 전자 상거래 애플리케이션에는 제품 디렉터리, 장바구니 및 결제 기능이 있다. Application Load Balancer는 이미지와 비디오를 포함하지만 열린 연결을 유지할 필요가 없는 서버에 제품 검색 요청을 전송한다. 이에 비해 많은 클라이언트 연결을 유지하고 장바구니 데이터를 오랫동안 저장할 수 있는 서버로 장바구니 요청을 전송한다.</p>
<h3 id="network-load-balancer-nlb">Network Load Balancer, NLB</h3>
<p>Network Load Balancer는 IP 주소 및 기타 네트워크 정보를 검사하여 트래픽을 최적으로 리디렉션한다. 애플리케이션 트래픽의 소스를 추적하고 여러 서버에 고정 IP 주소를 할당할 수 있다. Network Load Balancer는 정적 및 동적 로드 밸런싱 알고리즘을 사용하여 서버 로드를 배포한다.</p>
<h3 id="글로벌-서버-로드-밸런싱">글로벌 서버 로드 밸런싱</h3>
<p>글로벌 서버 로드 밸런싱은 지리적으로 분산된 여러 서버에서 발생한다. 예를 들어, 회사는 여러 데이터 센터, 여러 국가 및 전 세계의 타사 클라우드 제공업체에 서버를 둘 수 있다. 이 경우 로컬 로드 밸런서는 리전 또는 영역 내에서 애플리케이션 로드를 관리한다. 그리고 클라이언트와 지리적으로 더 가까운 서버 대상으로 트래픽을 리디렉션하려고 한다. 서버 장애가 발생한 경우에만 클라이언트의 지리적 영역 외부의 서버로 트래픽을 리디렉션할 수 있다.</p>
<h3 id="dns-로드-밸런싱">DNS 로드 밸런싱</h3>
<p>DNS 로드 밸런싱에서는 도메인의 리소스 풀에서 네트워크 요청을 라우팅하도록 도메인을 구성한다. 도메인은 웹 사이트, 메일 시스템, 인쇄 서버 또는 인터넷을 통해 액세스할 수 있는 다른 서비스에 해당할 수 있다. DNS 로드 밸런싱은 애플리케이션 가용성을 유지하고 전역적으로 분산된 리소스 풀에서 네트워크 트래픽을 분산하는 데 유용하다.</p>
<h1 id="aws-로드-밸런싱">AWS 로드 밸런싱</h1>
<p><a href="https://aws.amazon.com/elasticloadbalancing/">Elastic Load Balancing(ELB)</a>은 수신 애플리케이션 트래픽을 AWS 및 온프레미스 리소스 전반의 여러 대상 및 가상 어플라이언스에 자동으로 배포하는 완전 관리형 로드 밸런싱 서비스다. 이를 사용하여 복잡한 구성이나 API 게이트웨이 없이 최신 애플리케이션을 조정할 수 있다. ELB를 사용하여 네 가지 유형의 소프트웨어 로드 밸런서를 설정할 수 있다.</p>
<ul>
<li><p>Application Load Balancer(ALB)는 HTTP 기반 요청에 대한 트래픽을 라우팅한다.</p>
</li>
<li><p>Network Load Balancer(NLB)는 IP 주소를 기반으로 트래픽을 라우팅한다. TCP와 UDP(User Datagram Protocol) 기반 요청의 균형을 맞추는 데 이상적이다.</p>
</li>
<li><p>Gateway Load Balancer(GLB)는 투명한 네트워크 게이트웨이(모든 트래픽의 단일 진입점 및 종료점) 역할을 하며, 트래픽을 분산하는 동시에 수요에 따라 가상 어플라이언스의 규모를 조정한다.</p>
<p>  <em>Cf. 어플라이언스: 특정한 컴퓨터 자원을 제공하기 위해 설계된 소프트웨어나 펌웨어를 갖춘 컴퓨터이다.</em></p>
</li>
<li><p>Classic Load Balancer(CLB)는 다른 고객과 공유하는 단일 플랫 네트워크인 <a href="https://aws.amazon.com/ec2/">Amazon EC2</a> Classic 네트워크의 애플리케이션으로 트래픽을 라우팅한다.</p>
</li>
<li><p><strong>ALB vs NLB vs GLB</strong> (클라우드에서 사용되는 세 가지 유형의 로드 밸런서)</p>
</li>
</ul>
<pre><code>|  | Application load balancer(ALB) | Network Load Balancer(NLB) | Gateway Load Balancer(GLB) |
| --- | --- | --- | --- |
| OSI 계층 | 애플리케이션 계층인 계층 7에서 작동 | 전송 계층인 계층 4에서 작동 | 네트워크 계층인 계층 3 및 계층 7에서 작동 |
| 대상 유형 | IP, 인스턴스 및 Lambda 대상 유형에 작동 | IP, 인스턴스 및 ALB 대상 유형에 작동 | IP 및 인스턴스 대상 유형에 작동 |
| 프록시 동작 | 연결을 종료함 | 연결을 종료함 | 흐름을 종료하지 않음 |
| 프로토콜 | HTTP, HTTPS 및 gRPC 프로토콜 지원 | TCP, UDP 및 TLS 프로토콜 지원 | IP 기반 라우팅 지원  |
| 알고리즘 | 라운드 로빈 | 플로우 해시 | 라우팅 테이블 조회 |

Cf. [Application Load Balancing, Network Load Balancing 및 Gateway Load Balancing - 로드 밸런싱 유형 간의 차이 - AWS](https://aws.amazon.com/ko/compare/the-difference-between-the-difference-between-application-network-and-gateway-load-balancing/)</code></pre><h1 id="additional-resources">Additional Resources</h1>
<ul>
<li><p><a href="https://www.designgurus.io/blog/load-balancer-reverse-proxy-api-gateway">Load Balancer vs. Reverse Proxy vs. API Gateway: Demystifying Web Architectures</a></p>
</li>
<li><p>L4/L7 스위치와 AWS Load Balancer를 자세히 알아보고 싶다면 <a href="https://aws-hyoh.tistory.com/"><strong>네트워크 엔지니어 환영의 기술블로그</strong></a>의 다음 시리즈를 참고하자.</p>
<ul>
<li><p><a href="https://aws-hyoh.tistory.com/category/Network%20Infra%20%EC%89%BD%EA%B2%8C%20%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0/L4%20%26%20L7%20Network%20%EC%89%BD%EA%B2%8C%20%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0">https://aws-hyoh.tistory.com/category/Network Infra 쉽게 이해하기/L4 %26 L7 Network 쉽게 이해하기</a></p>
</li>
<li><p><a href="https://aws-hyoh.tistory.com/category/Amazon%20Web%20Service%20Network%20%EC%89%BD%EA%B2%8C%20%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0">https://aws-hyoh.tistory.com/category/Amazon Web Service Network 쉽게 이해하기</a></p>
</li>
</ul>
</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li><p><a href="https://blog.bytebytego.com/p/ep47-common-load-balancing-algorithms">EP47: Common Load-balancing Algorithms</a></p>
</li>
<li><p><a href="https://aws.amazon.com/ko/what-is/load-balancing/">로드 밸런싱이란 무엇인가요? - 로드 밸런싱 알고리즘 설명 - AWS</a></p>
</li>
<li><p><a href="https://www.alibabacloud.com/tech-news/a/load_balancer/gu0idw1t3r-load-balancing-algorithms-which-to-choose">Load Balancing Algorithms: Which to Choose? - Alibaba Cloud</a></p>
</li>
<li><p><a href="https://www.loadbalancer.org/blog/comparing-layer-4-layer-7-and-gslb-load-balancing-techniques/">Comparing Layer 4, Layer 7, and GSLB techniques</a></p>
</li>
<li><p><a href="https://aws-hyoh.tistory.com/#google_vignette">네트워크 엔지니어 환영의 기술블로그</a></p>
</li>
<li><p><a href="https://www.freeism.co.kr/wp/archives/698">L4/L7 스위치 개요 (로드밸런서) - ThinkCUBES</a></p>
</li>
<li><p><a href="https://miro.medium.com/v2/format:webp/0*FaLx60hNj5A83_lF.png">miro.medium.com</a></p>
</li>
<li><p><a href="https://blog.bytebytego.com/p/ep47-common-load-balancing-algorithms">EP47: Common Load-balancing Algorithms</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TCP/UDP]]></title>
            <link>https://velog.io/@hee-suh/TCP-UDP</link>
            <guid>https://velog.io/@hee-suh/TCP-UDP</guid>
            <pubDate>Mon, 29 Apr 2024 14:49:39 GMT</pubDate>
            <description><![CDATA[<h1 id="tcpudp">TCP/UDP</h1>
<p>인터넷(그리고 일반적인 TCP/IP 네트워크)은 애플리케이션에게 2개의 <strong>전송(transport) 프로토콜</strong>을 제공한다.</p>
<ol>
<li><strong>UDP(User Datagram Protocol)</strong> — 비신뢰적이고 비연결형인 서비스를 요청한 애플리케이션에게 제공한다.</li>
<li><strong>TCP(Transmission Control Protocol)</strong> — 신뢰적이고 연결지향형 서비스를 요청한 애플리케이션에게 제공한다.</li>
</ol>
<p><em>Cf. 네트워크 애플리케이션을 설계할 때 애플리케이션 개발자는 이 두 가지 트랜스포트 프로토콜 중 하나를 명시해야 한다.</em></p>
<p>UDP와 TCP의 가장 기본적인 기능은 종단 시스템 사이의 IP 전달 서비스를 종단 시스템에서 동작하는 두 프로세스 간의 전달 서비스로 확장하는 것이다. ‘호스트 대 호스트 전달’을 ‘프로세스 대 프로세스 전달’로 확장하는 것을 <strong>트랜스포트 계층 다중화(transport-layer multiplexing)</strong>와 <strong>역다중화(demultiplexing)</strong>라고 부른다. UDP와 TCP는 헤더에 오류 검출 필드를 포함함으로써 무결성 검사를 제공한다. </p>
<p>이러한 최소한의 두 가지 트랜스포트 계층 서비스(프로세스 대 프로세스 데이터 전달과 오류 검출)가 <strong>UDP</strong>가 제공하는 유일한 두 가지 서비스다. UDP는 IP와 마찬가지로 비신뢰적인 서비스며, 하나의 프로세스에 의해 전송된 데이터가 손상되지 않고(일부 또는 데이터 전부) 목적지 프로세스에 도착한다는 것을 보장하지 않는다.</p>
<p>반면에, <strong>TCP</strong>는 애플리케이션에 몇 가지 추가적인 서비스를 제공한다. 가장 먼저 TCP는 <strong>신뢰적인 데이터 전송(reliable data transfer)</strong>을 제공한다. 흐름 제어, 순서 번호, 확인 응답, 타이머를 사용함으로써 TCP는 송신하는 프로세스로부터 수신하는 프로세스에게 데이터가 순서대로 정확하게 전달되도록 한다. 이처럼 TCP는 종단 시스템 간에 IP의 비신뢰적인 서비스를 프로세스 사이의 신뢰적인 데이터 전송 서비스로 만들어준다. 또한 TCP는 <strong>혼잡 제어(congestion control)</strong>를 사용한다. 송신측의 TCP가 네트워크에 보낼 수 있는 트래픽을 조절함으로써 각 TCP 연결이 링크의 대역폭을 공유하여 통과하도록 도와준다. 반면에 UDP 트래픽은 조절되지 않으며, UDP 트랜스포트 프로토콜을 사용하는 애플리케이션은 허용이 되는 한 그것이 만족하는 어떤 속도로든 전송할 수 있다.</p>
<blockquote>
<p>💡 <strong>다중화(Multiplexing)와 역다중화(Demultiplexing)</strong>
트랜스포트 계층 세그먼트의 데이터를 올바른 소켓으로 전달하는 작업을 <strong>역다중화(demultiplexing)</strong>라고 한다. 출발지 호스트에서 소켓으로부터 데이터를 모으고, 이에 대한 세그먼트를 생성하기 위해 각 데이터에 헤더 정보(나중에 역다중화에 사용)로 캡슐화하고, 그 세그먼트들을 네트워크 계층으로 전달하는 작업을 <strong>다중화(multiplexing)</strong>라고 한다.</p>
</blockquote>
<h1 id="비연결형-트랜스포트-udp">비연결형 트랜스포트: UDP</h1>
<h2 id="user-datagram-protocol">User Datagram Protocol</h2>
<p>UDP는 트랜스포트 계층 프로토콜이 할 수 있는 최소 기능으로 동작한다.</p>
<ul>
<li>unreliable, unordered delivery</li>
<li><strong><em>connectionless</em></strong><ul>
<li>no handshaking between UDP sender, receiver</li>
<li>each UDP segment handled independently of others</li>
</ul>
</li>
</ul>
<h2 id="udp를-사용하는-이유">UDP를 사용하는 이유</h2>
<p>TCP는 신뢰적인 데이터 전송 서비스를 제공하지만 UDP는 그렇지 않으므로 TCP가 항상 더 선호될까? 그렇지 않다. 왜냐하면 많은 애플리케이션은 다음과 같은 이유로 UDP에 더 적합하다.</p>
<ul>
<li><strong>무슨 데이터를 언제 보낼지에 대해 애플리케이션 레벨에서 더 정교한 제어</strong> — 최소 전송률을 요구할 때도 있고, 지나치게 지연되는 세그먼트 전송을 원하지 않으며, 조금의 데이터 손실은 허용할 수도 있는 실시간 애플리케이션의 경우 UDP를 사용하고, 애플리케이션의 한 부분으로서 UDP의 기본 세그먼트 전달 외에 필요한 어떤 추가 기능을 구현할 수 있다.</li>
<li><strong>연결 설정이 없음</strong> — TCP는 데이터 전송을 시작하기 전에 3-way handshake를 사용하는 반면, UDP는 연결을 설정하기 위한 어떤 지연도 없다.</li>
<li><strong>연결 상태가 없음</strong> — TCP는 종단 시스템에서 연결 상태를 유지하기 위해 수신 버퍼와 송신 버퍼, 혼잡 제어 파라미터, 순서 번호화 확인응답 번호 파라미터를 포함한다. 이에 반하여, UDP는 연결 상태를 유지하지 않으며 이 파라미터 중 어떤 것도 기록하지 않아서 특정 애플리케이션 전용 서버는 애플리케이션 프로그램이 TCP보다 UDP에서 동작할 때 일반적으로 좀 더 많은 액티브 클라이언트를 수용할 수 있다.</li>
<li><strong>작은 패킷 헤더 오버헤드</strong> — TCP는 세그먼트마다 20바이트의 헤더 오버헤드를 갖지만, UDP는 단지 8바이트의 오버헤드를 갖는다.</li>
</ul>
<h2 id="인터넷-애플리케이션과-그-하위-트랜스포트-프로토콜">인터넷 애플리케이션과 그 하위 트랜스포트 프로토콜</h2>
<table>
<thead>
<tr>
<th>애플리케이션</th>
<th>애플리케이션 계층 프로토콜</th>
<th>하위 트랜스포트 프로토콜</th>
</tr>
</thead>
<tbody><tr>
<td>전자메일</td>
<td>SMTP</td>
<td>TCP</td>
</tr>
<tr>
<td>원결 터미널 접속</td>
<td>텔넷</td>
<td>TCP</td>
</tr>
<tr>
<td>보안 원격 터미널 접속</td>
<td>SSH</td>
<td>TCP</td>
</tr>
<tr>
<td>웹</td>
<td>HTTP, HTTP/3</td>
<td>TCP(HTTP), UDP(HTTP/3)</td>
</tr>
<tr>
<td>파일 전송</td>
<td>FTP</td>
<td>TCP</td>
</tr>
<tr>
<td>원격 파일 서버</td>
<td>NFS</td>
<td>일반적으로 UDP</td>
</tr>
<tr>
<td>스트리밍 멀티미디어</td>
<td>DASH</td>
<td>TCP</td>
</tr>
<tr>
<td>인터넷 폰</td>
<td>통상 독점 프로토콜</td>
<td>UDP 또는 TCP</td>
</tr>
<tr>
<td>네트워크 관리</td>
<td>SNMP</td>
<td>일반적으로 UDP</td>
</tr>
<tr>
<td>이름 변환</td>
<td>DNS</td>
<td>일반적으로 UDP</td>
</tr>
</tbody></table>
<h2 id="udp-segment-structure">UDP Segment Structure</h2>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/59096ee2-f403-4cf6-aab5-585679de5574/image.png" alt="출처: Computer Networking a Top-Down Approach: Transport Layer 3-35"></p>
<p>UDP 세그먼트 구조는 RFC 768에 정의되어 있다. 애플리케이션 데이터는 UDP 데이터그램의 데이터 필드에 위치한다. UDP 헤더는 2바이트씩 구성된 단 4개의 필드만을 갖는다. </p>
<ul>
<li><strong>port 번호</strong>는 목적지 호스트가 목적지 종단 시스템에서 동작하는(역다중화 기능을 수행하는) 정확한 프로세스에게 애플리케이션 데이터를 넘기게 해준다.</li>
<li><strong>length</strong> 필드는 헤더를 포함하는 UDP 세그먼트의 길이(바이트 단위)를 나타낸다.</li>
<li><strong>checksum</strong>은 세그먼트에 오류가 발생했는지를 검사하기 위해 수신 호스트가 사용한다.</li>
</ul>
<blockquote>
<p><strong>✅ Internet Checksum</strong>
<strong>Goal</strong>: 전송된 세그먼트에서 오류를 감지한다. (<em>i.e.,</em> flipped bits) 
<img src="https://velog.velcdn.com/images/hee-suh/post/48d87a84-47fc-452f-b570-0361319c078f/image.png" alt="출처: Computer Networking a Top-Down Approach: Transport Layer 3-38"></p>
</blockquote>
<ul>
<li>송신자:<ul>
<li>UDP 세그먼트(UDP 헤더 필드 및 IP 주소 포함)의 내용을 16비트 정수 시퀀스로 처리</li>
<li><strong>checksum</strong>: 세그먼트 내용에 덧셈(1의 보수합)</li>
<li>UDP checksum 필드에 checksum 값 입력</li>
</ul>
</li>
<li>수신자:<ul>
<li>수신한 세그먼트의 checksum 계산</li>
<li>계산된 checksum이 checksum 필드 값과 같은지 확인:<ul>
<li>not equal - error detected</li>
<li>equal - no error detected</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="연결지향형-트랜스포트-tcp">연결지향형 트랜스포트: TCP</h1>
<h2 id="transmission-control-protocol">Transmission Control Protocol</h2>
<p>TCP는 연결형, 신뢰적인 트랜스포트 프로토콜이다. 신뢰적인 데이터 전송을 제공하기 위해 오류 검출, 재전송, 누적 확인응답, 타이머, 순서 번호와 확인응답 번호를 위한 헤더 필드를 포함한다.</p>
<ul>
<li>reliable, in-order delivery</li>
<li>congestion control</li>
<li>flow control</li>
<li>connection setup</li>
</ul>
<h2 id="tcp-segment-structure">TCP Segment Structure</h2>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/4ee522ff-c268-4775-9f36-c902abcb5611/image.png" alt="출처: Computer Networking a Top-Down Approach: Transport Layer 3-83"></p>
<p>TCP 세그먼트는 헤더 필드와 데이터 필드로 구성되어 있다. </p>
<p>세그먼트 데이터 필드의 크기는 <strong>MSS</strong>(maximum segment size, 최대 세그먼트 크기)로 제한된다. </p>
<blockquote>
<p>💡 <strong>MSS(maximum segment size, 최대 세그먼트 크기)</strong>
MSS는 일반적으로 로컬 송신 호스트에 의해 전송될 수 있는 가장 큰 링크 계층 프레임의 길이(<strong>최대 전송단위</strong>(maximum transmision unit, MTU))에 의해 일단 결정되고, 그런 후에 TCP 세그먼트(IP 데이터그램 안에 캡슐화되었을 때)와 TCP/IP 헤더 길이(통상 40바이트)가 단일 링크 계층 프레임에 딱 맞도록 하여 정해진다.</p>
</blockquote>
<p>UDP처럼 헤더는 상위 계층 애플리케이션으로부터 다중화와 역다중화를 하는 데 사용하는 <strong>source and destionation port number(출발지와 목적지 포트 번호)</strong>를 포함한다. 또한 UDP처럼 헤더는 <strong>checksum field(체크섬 필드)</strong>를 포함한다. TCP 세그먼트 헤더는 또한 다음과 같은 필드를 포함한다.</p>
<ul>
<li><p>32비트 <strong>sequence number field</strong>(순서 번호 필드)와 32비트 <strong>acknowledgement number field</strong>(확인응답 번호 필드)는 신뢰적인 데이터 전송 서비스 구현에서 TCP 송신자와 수신자에 의해 사용된다.</p>
</li>
<li><p>16비트 <strong>receive window</strong>(수신 윈도) 필드는 흐름 제어에 사용된다. 이는 수신자가 받아들이려는 바이트의 크기를 나타내는 데 사용된다.</p>
</li>
<li><p>4비트 <strong>header length field</strong>(헤더 길이 필드)는 32비트 word 단위로 TCP 헤더의 길이를 나타낸다. TCP 헤더는 TCP option 필드 때문에 가변적인 길이가 될 수 있다(일반적으로, 옵션 필드는 일반적인 TCP 길이가 20바이트가 되도록 비어 있다).</p>
</li>
<li><p>선택적이고 가변적인 길이의 <strong>option field</strong>(옵션 필드)는 송신자와 수신자가 최대 세그먼트 크기(<strong>MSS</strong>)를 협상하거나 고속 네트워크에서 사용하기 위한 윈도 확장 요소로 이용된다. 타임스탬프 옵션 또한 정의된다.</p>
</li>
<li><p><strong>flag field</strong>(플래그 필드)는 6비트를 포함한다. <strong>ACK 비트</strong>는 확인응답 필드에 있는 값이 유용함을 가리키는 데 사용된다. 즉, 이 세그먼트는 성공적으로 수신된 세그먼트에 대한 확인응답을 포함한다. <strong>RST, SYN, FIN</strong>비트는 연결 설정과 해제에 사용된다. <strong>PSH</strong> 비트가 설정될 때, 이것은 수신자와 데이터를 상위 계층에 즉시 전달해야 한다는 것을 가리킨다. 마지막으로 <strong>URG</strong> 비트는 이 세그먼트에서 송신 측 상위 계층 개체가 ‘긴급’으로 표시하는 데이터임을 가리킨다. 이 긴급 데이터의 마지막 바이트의 위치는 16비트의 <strong>urgent data point field</strong>(긴급 데이터 포인터 필드)에 의해 가리켜진다.</p>
</li>
</ul>
<h2 id="tcp-연결-및-해제-과정">TCP 연결 및 해제 과정</h2>
<p>데이터를 교환하기 전에, 송신자와 수신자는 “<strong>handshake</strong>”를 수행하여 연결을 설정한다.</p>
<h3 id="3-way-handshake">3-way handshake</h3>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/421b3811-4446-48e7-b300-a77acbf96272/image.png" alt="출처: Computer Networking a Top-Down Approach: Transport Layer 3-106"></p>
<p>하나의 호스트(클라이언트)에서 운영되는 프로세스가 다른 호스트(서버) 안의 또 다른 프로세스와 연결을 시작하길 원한다고 가정하자. 먼저 클라이언트 애플리케이션 프로세스는 서버에 있는 프로세스와 연결 설정하기를 원한다는 것을 클라이언트 TCP에게 알린다. 그러면 클라이언트 안의 TCP는 다음과 같은 방법으로 TCP를 이용해 서버와 TCP 연결 설정을 시작한다.</p>
<ul>
<li><p><strong>1단계</strong> — 먼저 클라이언트 측 TCP는 서버 TCP에게 <strong>SYN</strong> 세그먼트를 송신한다. TCP 세그먼트의 헤더에 SYN 비트라고 불리는 하나의 flag 비트를 1로 설정하고, 최초의 순서 번호(<code>client_isn</code>)를 임의로 선택하여 최초의 TCP SYN 세그먼트의 순서 번호(seq #) 필드에 이 번호를 넣는다. 이 세그먼트는 IP 데이터그램 안에서 캡슐화되고 서버로 송신된다.
  <em>Cf. 특정 보안 공격[CERT 2001-09, RFC 4987]을 피하고자 client_isn을 임의 추출하는 것에 유의해야 한다.</em></p>
</li>
<li><p><strong>2단계</strong> — TCP SYN 세그먼트를 포함하는 IP 데이터그램이 서버 호스트에 도착하면, 서버는 데이터그램으로부터 TCP SYN 세그먼트를 추출하고, 연결에 TCP 버퍼와 변수를 할당한다. 그리고 클라이언트 TCP로 연결 승인 세그먼트를 송신한다. 세그먼트 헤더에는 3개의 중요한 정보가 포함되어 있다. 첫째, SYN 비트는 1로 설정된다. 둘째, TCP 세그먼트 헤더의 확인응답 필드는 <code>client_isn+1</code>로 설정된다. 마지막으로, 서버는 자신의 최초의 순서 번호(<code>server_isn</code>)를 선택하고,  TCP 세그먼트 헤더의 순서 번호 필드에 이 값을 넣는다. 연결 승인 세그먼트는 때때로 <strong>SYNACK 세그먼트</strong>로 불린다.</p>
</li>
<li><p><strong>3단계</strong> — 연결 승인 세그먼트를 수신하면, 클라이언트는 연결에 버퍼와 변수를 할당한다. 그다음에 클라이언트 호스트는 서버로 또 다른 세그먼트를 송신한다. 이 마지막 세그먼트가 서버의 연결 승인 세그먼트를 확인(<code>ack=server_isn+1</code>)하는 것이다. 연결이 설정되었기 때문에 SYN 비트는 0으로 설정된다. 3-way handshake의 세 번째 단계는 클라이언트에서 서버로의 데이터를 세그먼트 페이로드에 운반할 수 있다.</p>
</li>
</ul>
<p>위의 세 단계가 완료되면, 클라이언트와 서버 호스트들은 각각 서로에게 데이터를 포함하는 세그먼트를 보낼 수 있다. 이 각각의 다음의 세그먼트에서 SYN 비트는 0으로 설정된다. 연결 설정을 위해 두 호스트 사이에서 3개의 패킷이 송신되므로 TCP 연결 설정 절차를 <strong>3-way handshake</strong>라고 부른다.</p>
<h3 id="4-way-handshake">4-way handshake</h3>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/490d960d-2733-4056-93a3-3788f516f3f5/image.png" alt="출처: Computer Networking a Top-Down Approach: Transport Layer 3-152"></p>
<p>TCP 연결에 참여하는 두 프로세스 중 하나가 연결을 끝낼 수 있다. 연결이 끝날 때, 호스트의 ‘자원’(버퍼와 변수)은 회수된다.</p>
<p>클라이언트가 연결 종료를 결정한다고 가정하자.</p>
<ul>
<li><p><strong>1단계</strong> — 클라이언트 애플리케이션 프로세스는 종료 명령을 내리고, 이것은 클라이언트 TCP가 서버 프로세스에게 특정 TCP 세그먼트를 보내도록 한다. 이 세그먼트는 1로 설정된 FIN 비트라 불리는 flag 비트를 세그먼트 헤더에 포함한다.</p>
</li>
<li><p><strong>2단계</strong> — 서버가 이 세그먼트를 수신하면, 서버는 클라이언트에게 ACK(확인응답) 세그먼트를 보낸다.</p>
</li>
<li><p><strong>3단계</strong> — 그다음에 FIN 비트가 1로 설정된 자신의 종료 세그먼트를 송신한다.</p>
</li>
<li><p><strong>4단계</strong> — 마지막으로 클라이언트는 서버의 종료 세그먼트에 ACK(확인응답)을 한다. 이 시점에서 두 호스트의 모든 자원은 할당이 해제된다.</p>
</li>
</ul>
<h2 id="tcp-흐름-제어">TCP 흐름 제어</h2>
<p>TCP 연결의 각 종단에서 호스트들은 연결에 대한 개별 수신 버퍼를 설정한다. TCP 연결이 순서대로 올바르게 바이트를 수신할 때 TCP는 데이터를 수신 버퍼에 저장한다. 애플리케이션이 데이터를 읽는 속도가 비교적 느리다면, 송신자가 점점 데이터를 빠르게 전송함으로써 연결의 수신 버퍼에 아주 쉽게 오버플로를 발생시킨다.</p>
<p>이처럼 TCP는 송신자가 수신자의 버퍼를 오버플로시키는 것을 방지하기 위해 애플리케이션에게 <strong>흐름 제어 서비스(flow-control service)</strong>를 제공한다. 흐름 제어는 수신하는 애플리케이션이 읽는 속도와 송신자가 전송하는 속도를 같게 한다.</p>
<p>TCP는 송신자가 receive window(수신 윈도)라는 변수를 유지하여 흐름 제어를 제공한다. 수신 윈도는 수신 측에서 가용한 버퍼 공간이 얼마나 되는지를 송신자에게 알려주는 데 사용된다.</p>
<p>흐름 제어는 혼잡이 실제로 발생할 때 데이터의 흐름을 통제하기 위해 사용되는 혼잡 제어와는 구별된다.</p>
<h3 id="stop-and-wait">Stop and Wait</h3>
<p>매번 전송한 패킷에 대해 확인 응답을 받아야만 그 다음 패킷을 전송하는 방법이다.</p>
<h3 id="sliding-window-go-back-n">Sliding Window (Go-Back-N)</h3>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/8acf9537-7d32-462a-968d-b1aaa8d9430c/image.png" alt="출처: Computer Networking a Top-Down Approach: Transport Layer 3-74"></p>
<p>수신측에서 설정한 window 크기만큼 송신측에서 확인응답(ACK) 없이 세그먼트를 전송할 수 있게 하여 데이터 흐름을 동적으로 조절하는 제어 기법이다.</p>
<p>전송은 되었지만, ACK을 받지 못한 byte의 숫자를 파악하기 위해 사용하는 프로토콜이다.</p>
<p>먼저 window에 포함되는 모든 패킷을 전송하고, 그 패킷들의 전달이 확인되는대로 이 window를 옆으로 옮기면서 그 다음 패킷들을 전송한다.</p>
<blockquote>
<p>💡 <strong>window</strong>
TCP/IP를 사용하는 모든 호스트들은 송신하기 위한 window 하나와 수신하기 위한 window 하나를 갖고 있다. 호스트들은 실제 데이터를 보내기 전에 3-way handshaking을 통해 수신 호스트의 receive window size에 자신의 send window size를 맞추게 된다.</p>
</blockquote>
<p>Cf. <a href="https://media.pearsoncmg.com/ph/esm/ecs_kurose_compnetwork_8/cw/content/interactiveanimations/flow-control/index.html">[Animation] Flow Control</a></p>
<h2 id="tcp-혼잡-제어">TCP 혼잡 제어</h2>
<p>각 호스트는 정보를 빨리 보내기 위하여 정해진 시간 내에 보낼 수 있는 최대의 패킷을 보내는데, 만약 한 라우터에 데이터가 몰릴 경우, 혼잡 현상이 발생하여 정해진 시간 내에 받은 패킷들을 모두 처리할 수 없게 된다. 이런 경우 호스트들은 데이터 재전송을 하게 되고 결국 혼잡만 가중시켜 오버플로우나 데이터 손실을 발생시킨다. 따라서 이러한 네트워크의 혼잡을 피하기 위해 송신측에서 보내는 데이터의 전송속도를 강제로 줄이게 되는데, 이러한 작업을 <strong>혼잡 제어(congestion control)</strong>라고 한다.</p>
<p>흐름 제어가 송신측과 수신측 사이의 전송 속도를 조절하는 반면, 혼잡 제어는 호스트와 라우터를 포함한 보다 넓은 관접에서 전송 문제를 다룬다.</p>
<p>TCP 혼잡 제어의 핵심은 바로 <strong>cwnd(congestion window) 값을 잘 조절하는 것</strong>이다.</p>
<h3 id="aimdadditive-increasemultiplicative-decrease">AIMD(Additive Increase/Multiplicative Decrease)</h3>
<p>처음에 패킷을 하나씩 보내고 이것이 문제없이 도착하면 window 크기(단위 시간 내에 보내는 패킷의 수)를 1씩 증가시켜가면서 전송하는 방법이다. 만일 패킷 전송을 실패하거나 일정한 시간을 넘으면 패킷을 보내는 속도를 절반으로 줄인다.</p>
<p>이 방식은 공평한 방식으로, 여러 호스트가 한 네트워크를 공유하고 있으면 나중에 진입하는 쪽이 처음에는 불리하지만 시간이 흐르면 평형 상태로 수렴하게 되는 특징이 있다.</p>
<p>문제점은 초기에 네트워크의 높은 대역폭을 사용하지 못하여 오랜 시간이 걸리게 되고, 네트워크가 혼잡해지는 상황을 미리 감지하지는 못한다. 즉, 네트워크가 혼잡해지고 나서야 대역폭을 줄이는 방식이다.</p>
<h3 id="slow-start느린-시작">Slow Start(느린 시작)</h3>
<p>AIMD 방식이 네트워크의 수용량 주변에서는 효율적으로 작동하지만, 처음에 전송 속도를 올리는 데 걸리는 시간이 너무 길다는 단점이 있다. </p>
<p>Slow Start 방식은 AIMD 방식과 마찬가지로 패킷을 하나씩 보내는 것부터 시작한다. 그러나 이 방식은 패킷이 문제없이 도착하면 각각의 ACK 패킷마다 window 크기를 1씩 늘린다. 즉, 한 주기가 지나면 window 크기가 2배로 된다. 따라서 전송 속도는 AIMD와는 다르게 지수 함수 꼴로 증가한다. 대신에 혼잡 현상이 발생하면 창 크기를 1로 떨어뜨린다. 처음에는 네트워크의 수용량을 예상할 수 있는 정보가 없지만 한번 혼잡 현상이 발생하고 나면 네트워크의 수용량을 어느 정도 예상할 수 있으므로 혼잡 현상이 발생하였던 창 크기의 절반까지는 이전처럼 지수 함수 꼴로 window 크기를 증가시키고 그 이후부터는 완만하게 1씩 증가시킨다.</p>
<p>이전의 TCP 동작은 처음에 최대한 보낼 수 있는 만큼의 패킷을 보내는 것으로 시작하고, Slow Start는 이것과 달리 window 크기를 1에서부터 시작하여 지수함수 꼴로 증가시켜가면서 네트워크의 수용량을 감지하기에 Slow Start라고 불린다.</p>
<p>이 방식은 AIMD 방식보다 더 효율적인 방법이지만 마찬가지로 혼잡한 상황이 된 경우에는 타임아웃이 될 때까지 기다리는 동안 큰 시간의 공백이 있다.</p>
<h3 id="fast-retransmit빠른-재전송"><strong>Fast Retransmit(빠른 재전송)</strong></h3>
<p>빠른 재전송(Fast Retransmit)은 TCP의 혼잡 제어에 추가된 정책이다. </p>
<p>패킷을 받는 쪽에서 먼저 도착해야 할 패킷이 도착하지 않고 다음 패킷이 도착한 경우에도 ACK 패킷을 보낸다. 단, 순서대로 잘 도착한 마지막 패킷의 다음 패킷의 순번을 ACK 패킷에 실어서 보낸다. 따라서 중간에 패킷 하나가 손실되게 되면 보내는 측에서는 순번이 중복된 ACK 패킷을 받게 되고, 이것을 감지하는 순간 문제가 되는 순번의 패킷을 재전송해 줄 수 있다. 빠른 재전송은 중복된 순번의 패킷을 3개 받으면 재전송을 한다. 그리고 이런 현상이 일어나는 것은 약간 혼잡한 상황이 일어난 것이므로 혼잡을 감지하고 window 크기를 줄이게 된다.</p>
<h3 id="fast-recovery빠른-회복"><strong>Fast Recovery(빠른 회복)</strong></h3>
<p>빠른 회복 정책(Fast Recovery)은 혼잡한 상태가 되면 window 크기를 1로 줄이지 않고 반으로 줄이고 선형 증가시키는 방법이다. 빠른 회복 정책까지 적용하면 혼잡 상황을 한번 겪고 나서부터는 순수한 AIMD 방식으로 동작한다.</p>
<p>Cf. <a href="https://media.pearsoncmg.com/ph/esm/ecs_kurose_compnetwork_8/cw/content/interactiveanimations/tcp-congestion/index.html">[Animation] TCP Congestion Control</a></p>
<h1 id="references">References</h1>
<ul>
<li><p><a href="https://gaia.cs.umass.edu/kurose_ross/ppt.php">Jim Kurose Homepage</a></p>
</li>
<li><p><a href="https://product.kyobobook.co.kr/detail/S000061694627">컴퓨터 네트워킹 하향식 접근</a></p>
</li>
<li><p><a href="https://ko.wikipedia.org/wiki/%ED%98%BC%EC%9E%A1_%EC%A0%9C%EC%96%B4">혼잡 제어</a></p>
</li>
<li><p><a href="https://www.brianstorti.com/tcp-flow-control/">TCP Flow Control</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Proxy]]></title>
            <link>https://velog.io/@hee-suh/Proxy</link>
            <guid>https://velog.io/@hee-suh/Proxy</guid>
            <pubDate>Sun, 28 Apr 2024 08:15:02 GMT</pubDate>
            <description><![CDATA[<h1 id="proxy">Proxy</h1>
<h2 id="proxy란">Proxy란</h2>
<p><strong>프록시 서버</strong>란 인터넷 상의 여러 네트워크들에 접속할 때 중계 역할을 해주는 프로그램 또는 컴퓨터를 의미한다. 프록시는 요청을 가로챈 뒤 응답을 되돌려준다. 이렇게 가로챈 요청을 전달하거나, 하지 않거나(e.g. 캐시), 수정할 수도 있다(e.g. HTTP 헤더 수정).</p>
<p>프록시는 사용자의 로컬 컴퓨터에 존재할 수도 있고, 인터넷 상에서 사용자의 컴퓨터와 기점 서버 사이 그 어느 곳에든 존재할 수 있다. 일반적으로 크게 주로 2가지 종류의 프록시 서버가 존재한다. </p>
<ul>
<li><p><strong>포워드 프록시(forward proxy)</strong>는 인터넷 상에서 어디로든지 요청을 전송해주는 프록시다.</p>
</li>
<li><p><strong>리버스 프록시(reverse proxy)</strong>는 인터넷에서 요청을 받으면, 내부망 내의 서버로 전송해준다.</p>
</li>
</ul>
<p><em>Cf. <a href="https://developer.mozilla.org/en-US/docs/Glossary/Proxy_server">Proxy server - MDN Web Docs Glossary: Definitions of Web-related terms | MDN</a></em></p>
<h2 id="web-cache">Web Cache</h2>
<p><strong>프록시 서버(Proxy server)</strong>라고도 불리는 웹 캐시(Web Cache)는 기점 웹 서버(Origin Web server)를 대신하여 HTTP 요구를 충족시키는 네트워크 개체다.</p>
<ul>
<li><p>사용자가 (로컬) 웹 캐시를 가리키도록 브라우저를 구성한다.</p>
</li>
<li><p>브라우저는 모든 HTTP 요청을 캐시로 보낸다.</p>
<ul>
<li>$if$ 캐시에 object가 있는 경우: 캐시가 클라이언트에 object를 반환한다.</li>
<li>$else$ 기점(origin) 서버로부터 object 요청을 받고, 수신된 object를 캐시한 다음 object를 클라이언트에 반환한다.</li>
</ul>
</li>
</ul>
<img src='https://velog.velcdn.com/images/hee-suh/post/30c66266-6a9f-44dd-bf26-39aad4b57209/image.png' alt='web cache' title='출처: Computer Networking a Top-Down Approach: Application Layer 2-42' />

<ul>
<li><p>웹 캐시는 클라이언트와 서버의 역할을 모두 수행한다.</p>
<ul>
<li>server for original requesting client</li>
<li>client to origin server</li>
</ul>
</li>
<li><p>서버는 응답 헤더에서 object의 허용 가능한 캐싱에 대해 캐시에 알려준다.</p>
<ul>
<li><code>Cache-Control: max-age=&lt;seconds&gt;</code></li>
<li><code>Cache-Control: no-cache</code>
  <strong>no-cache</strong> 값은 대부분의 브라우저에서 max-age=0 과 동일한 뜻을 가진다. 즉, 캐시는 저장하지만 사용하려고 할 때마다 서버에 재검증 요청을 보내야 한다.</li>
<li><code>Cache-Control: no-store</code>
  <strong>no-store</strong> 값은 캐시를 절대로 해서는 안 되는 리소스일 때 사용한다. 캐시를 만들어서 저장조차 하지 말라는 가장 강력한 Cache-Control 값이다. no-store를 사용하면 브라우저는 어떤 경우에도 캐시 저장소에 해당 리소스를 저장하지 않는다.
  max-age=0 값이 Cache-Control 헤더로 설정되었을 때, 매번 리소스를 요청할 때마다 서버에 재검증 요청을 보내야 한다. 네트워크 요청을 아끼고 사용자에게 빠른 웹 경험을 제공하기 위해서 no-store를 사용한다.</li>
</ul>
</li>
</ul>
<h3 id="웹-캐싱을-사용하는-이유">웹 캐싱을 사용하는 이유</h3>
<ol>
<li><p>클라이언트 요청에 대한 응답 시간이 단축된다. (∵ 캐시가 클라이언트에 더 가깝게 위치)</p>
</li>
<li><p>한 기관에서 인터넷으로의 접속하는 링크(access link)상의 웹 트래픽을 대폭으로 줄일 수 있다. 
 트래픽을 줄여주면, 기관(회사나 대학)은 자주 대역폭을 개선할 필요가 없어져 비용을 줄일 수 있고, 애플리케이션 성능을 개선한다.</p>
</li>
<li><p>콘텐츠 제공업체가 콘텐츠를 보다 효과적으로 전송할 수 있도록 지원한다. (∵ 인터넷에 캐시 밀집)
 콘텐츠 전송 네트워크(Content Distribution Network, CDN) 회사는 인터넷 전역을 통해 많은 지역적으로 분산된 캐시를 설치하고 있으며, 이를 통해 많은 트래픽을 지역화하고 있다.</p>
</li>
</ol>
<h3 id="조건부-get">조건부 GET</h3>
<p>웹 캐싱이 사용자가 느끼는 응답 시간을 줄일 수 있지만, 캐시 내부에 있는 객체의 복사본이 새것이 아닐 수 있다는 문제를 야기한다. 다행히도, HTTP는 클라이언트가 브라우저로 전달되는 모든 객체가 최신의 것임을 확인하면서 캐싱을 하게 해주는 방식을 갖고 있다. 이러한 방식을 조건부 GET이라고 한다.</p>
<ul>
<li><p>클라이언트: HTTP 요청에서 브라우저에 캐시된 복사본의 날짜를 지정한다.</p>
<pre><code class="language-http">  GET /fruit/strawberry.gif HTTP/1.1
  Host: www.suhhee.com
  If-modified-since: Fri, 19 April 2024 22:29:52</code></pre>
<p>  HTTP 요청이 GET 방식을 사용하고, <code>If-modified-since: &lt;date&gt;</code> 헤더 라인을 포함하고 있다면, 그것이 조건부 GET 메세지다. 
  Cf. <code>date</code>는 같은 요청에 대한 직전 응답에 포함되어 있는 <code>Last-Modified</code> 날짜다.</p>
</li>
<li><p>서버: 브라우저에 캐시된 복사본이 최신인 경우 응답에 object가 포함되어 있지 않다.</p>
<pre><code class="language-http">  HTTP/1.1 304 Not Modified
  Date: Mon, 22 April 2024 23:59:11
  Server: Apache/1.3.0 (Unix)
  (empty object body)</code></pre>
<p>  <code>HTTP/1.0 304 Not Modified</code>는 클라이언트에게 요청 object의 캐싱된 복사본을 사용하라는 것을 의미한다. 조건부 GET에 대한 응답으로 웹 서버가 여전히 응답 메시지를 보내지만 응답 메시지에 요청된 object를 포함하지 않음을 볼 수 있다. ⇒ object 전송 지연(또는 네트워크 리소스 사용)이 없다.</p>
</li>
</ul>
<h1 id="foward-proxy">Foward Proxy</h1>
<h2 id="포워드-프록시란">포워드 프록시란</h2>
<p><img src="https://cf-assets.www.cloudflare.com/slt3lc6tev37/2MZmHGnCdYbQBIsZ4V11C6/25b48def8b56b63f7527d6ad65829676/forward_proxy_flow.png" alt="출처: https://www.cloudflare.com/ko-kr/learning/cdn/glossary/reverse-proxy/"></p>
<p>포워드 프록시는 클라이언트 시스템 그룹 앞에 위치하는 서버다. 이러한 컴퓨터가 인터넷의 사이트 및 서비스에 요청하면 프록시 서버가 이러한 요청을 가로채고 중개자처럼 해당 클라이언트를 대신하여 웹 서버와 통신한다.</p>
<p>포워드 프록시 서버를 사용하면 서버는 실제 클라이언트를 인식하지 못하고 프록시를 클라이언트라고 생각한다.</p>
<p>Cf. 흔히 말하는 ‘프록시 서버’란 포워드 프록시 서버를 의미한다.</p>
<h2 id="포워드-프록시를-사용하는-이유">포워드 프록시를 사용하는 이유</h2>
<p>포워드 프록시는 프라이빗 네트워크 데이터를 외부인으로부터 보호하고 익명화한다.</p>
<ul>
<li><strong>클라이언트 보안</strong> — 일부 정부, 학교, 기타 조직에서는 방화벽을 사용하여 사용자에게 제한적인 인터넷 액세스 권한을 부여한다. 포워드 프록시 서버를 통해 사용자 그룹이 특정 사이트에 액세스하는 것을 차단하도록 설정할 수도 있다.</li>
<li><strong>주 당국 또는 기관의 검색 제한 우회</strong> — 포워드 프록시를 사용하여 위와 같은 액세스 제한을 우회할 수 있다. 사용자가 방문하는 사이트에 직접 연결하지 않고 프록시에 연결할 수 있으므로 이러한 제한을 피할 수 있다.</li>
<li><strong>캐싱</strong> — 프록시 서버는 해당 페이지 서버의 정보를 캐싱(임시보관)해둔다. 그래서 똑같이 해당 페이지에 접근하거나, 다른 클라이언트가 해당 페이지를 요청할 때 , 캐시된 정보(페이지)를 그대로 반환할 수 있고, 이는 서버의 부하를 줄이는 효과도 낼 수 있다. 포워드 프록시를 이용하면 프록시내 캐싱 된 페이지를 불러오기 때문에 훨씬 빠르게 조회할수 있다.</li>
<li><strong>암호화</strong> — 클라이언트의 요청은 포워드 프록시 서버를 통과할 때 암호화된다. 암호화된 요청은 다른 서버를 통과할 때 필요한 최소한의 정보만 갖게 되는데, 이는 클라이언트의 ip 를 (보안을 위해) 감춰주는 보안 효과를 내준다. 따라서 본 서버에서 IP 주소를 역추적해도 포워드 프록시 서버를 사용하면  정체를 파악하기 어렵게 된다. 프록시 서버의 IP 주소만 표시된다.</li>
</ul>
<h2 id="use-cases">Use Cases</h2>
<h3 id="vpn">VPN</h3>
<p><img src="https://yarro.org/wp-content/uploads/2022/10/business-vpn-how-it-works.jpg" alt="출처: https://yarro.org/steps-for-selecting-and-setting-up-a-small-business-vpn/"></p>
<p>가상 프라이빗 네트워크(VPN)는 암호화를 프록시 서버와 결합하여 보다 안전한 통신 채널을 만든다. 기본 기술은 클라이언트 트래픽을 암호화하여 VPN 서버로 라우팅한다. 그러면 VPN 서버가 IP 주소를 추가로 익명화하여 타사 웹 사이트로 라우팅한다. 이러한 사용 사례에서는 VPN 서버를 데이터도 암호화하는 포워드 프록시 서버라고 생각하면 된다.</p>
<h1 id="reverse-proxy">Reverse Proxy</h1>
<h2 id="리버스-프록시란">리버스 프록시란</h2>
<p><img src="https://cf-assets.www.cloudflare.com/slt3lc6tev37/3msJRtqxDysQslvrKvEf8x/f7f54c9a2cad3e4586f58e8e0e305389/reverse_proxy_flow.png" alt="출처: https://www.cloudflare.com/ko-kr/learning/cdn/glossary/reverse-proxy/"></p>
<p>리버스 프록시는 하나 이상의 웹 서버 앞에 위치하여 클라이언트의 요청을 가로채는 서버다. 이것은 프록시가 클라이언트 앞에 위치하는 포워드 프록시와 다르다. 리버스 프록시를 사용하면 클라이언트가 웹 사이트의 원본(기점, origin) 서버에 요청을 보낼 때 리버스 프록시 서버가 <a href="https://www.cloudflare.com/learning/serverless/glossary/what-is-edge-computing/">네트워크 에지</a>에서 해당 요청을 가로챈다. 그런 다음 리버스 프록시 서버가 원본 서버에 요청을 보내고 응답을 받는다.</p>
<p>요약하면 포워드 프록시는 클라이언트 앞에 위치하며 원본 서버가 해당 특정 클라이언트와 직접 통신하지 못하도록 하는 것이다. 반면에 리버스 프록시는 원본 서버 앞에 위치하며 어떤 클라이언트도 원본 서버와 직접 통신하지 못 하도록 한다.</p>
<h2 id="리버스-프록시를-사용하는-이유">리버스 프록시를 사용하는 이유</h2>
<ul>
<li><p><strong>로드 밸런싱</strong> — 매일 수백만 명의 사용자를 확보하는 인기 있는 웹 사이트에서는 단일 원본 서버로 들어오는 모든 사이트 트래픽을 처리하지 못할 수 있다. 대신, 사이트에서는 동일한 사이트에 대한 요청을 모두 처리하는 서로 다른 서버 풀에 분산될 수 있다. 이 경우 리버스 프록시는 단일 서버에 과부하가 걸리는 것을 방지하기 위해 들어오는 트래픽을 여러 서버에 고르게 분산하는 부하 분산 솔루션을 제공할 수 있다. 서버가 완전히 실패하는 경우 다른 서버가 트래픽을 처리하기 위해 나설 수 있다.</p>
</li>
<li><p><strong>서버 보안</strong> — 리버스 프록시를 사용하면 웹 사이트 또는 서비스에서 원본 서버의 IP 주소를 공개할 필요가 없다. 이로 인해 공격자가 <a href="https://www.cloudflare.com/learning/ddos/what-is-a-ddos-attack/">DDoS 공격</a>과 같은 표적 공격을 활용하기가 훨씬 더 어려워진다. 대신 공격자는 Cloudflare의 <a href="https://www.cloudflare.com/learning/cdn/what-is-a-cdn/">CDN</a>과 같은 리버스 프록시만 대상으로 지정할 수 있다. 이는 사이버 공격을 방어하기 위해 더 엄격한 보안과 더 많은 자원을 갖게 된다.</p>
</li>
<li><p><strong>캐싱</strong> - 리버스 프록시도 콘텐츠를 캐시할 수 있으므로 성능이 향상된다. 예를 들어, 파리에 있는 사용자가 로스앤젤레스에 있는 웹 서버가 있는 리버스 프록시 웹 사이트를 방문하는 경우, 사용자는 실제로 파리에 있는 로컬 역방향 리버스 서버에 연결할 수 있다. 그러면 이 서버는 LA에 있는 원본 서버와 통신해야 한다. 프록시 서버는 그런 다음 응답 데이터를 캐시(또는 임시 저장)할 수 있다. 사이트를 탐색하는 파리의 후속 사용자는 파리의 역 프록시 서버에서 로컬로 캐시된 버전을 가져오므로 성능이 훨씬 빨라진다.</p>
</li>
<li><p><strong>SSL 암호화</strong> - 각 클라이언트에 대한 SSL(또는 TLS) 통신의 암호화 및 암호 해독은 원본 서버의 경우에 계산 비용이 많이 들 수 있다. 리버스 프록시는 들어오는 모든 요청을 해독하고 나가는 모든 응답을 암호화하여 원본 서버의 귀중한 리소스를 확보하도록 구성할 수 있다.</p>
</li>
</ul>
<h2 id="use-cases-1">Use Cases</h2>
<h3 id="load-balancer">Load Balancer</h3>
<img src='https://velog.velcdn.com/images/hee-suh/post/7c399443-ba68-4fe3-bd9b-4eeb0056abec/image.png' alt='load balancer' title='출처: https://www.linkedin.com/pulse/load-balancers-different-networking-layers-umang-agarwal/' />

<p><em>Cf. 로드 밸런서는 <a href="https://velog.io/@hee-suh/Load-Balancing">다음 글</a>에서 자세히 알아볼 수 있다.</em></p>
<h3 id="cdn">CDN</h3>
<img src='https://velog.velcdn.com/images/hee-suh/post/805c90a2-a497-4cd4-a6f4-6be77735c00f/image.png' alt='CDN' title='출처: Computer Networking a Top-Down Approach: Application Layer 2-124' />

<h3 id="cors-proxy-server">CORS Proxy Server</h3>
<p><img src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*Gr3T_pkgMBbZw4v6Mjcm0g.png" alt="출처: https://medium.com/nodejsmadeeasy/a-simple-cors-proxy-for-javascript-applications-9b36a8d39c51"></p>
<p>프록시 서버를 사용하여, 서버에서 서버로 요청을 보낼 때는 CORS 에러가 발생하지 않는다는 것을 이용한다.</p>
<p>웹 애플리케이션이 리소스를 직접적으로 요청하는 대신, 프록시 서버를 사용하여 웹 애플리케이션에서 리소스로의 요청을 전달하면, 웹 애플리케이션이 리소스와 동일한 출처에서 요청을 보내는 것처럼 보이므로 CORS 에러를 방지할 수 있다.</p>
<ul>
<li><strong>Nginx:</strong> reverse proxy 기능을 제공하고, CORS 우회를 위한 설정도 간편하게 수행할 수 있다.</li>
<li><strong>Apache:</strong> reverse proxy 기능을 제공한다. Nginx보다 설정이 다소 복잡할 수 있다. (<a href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html">https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html</a>)</li>
<li><strong>HAProxy:</strong> 고성능 로드 밸런싱 및 프록시 서버다. CORS 우회 기능도 제공하지만, 설정이 다소 복잡할 수 있다. (<a href="https://www.haproxy.org/">https://www.haproxy.org/</a>)</li>
</ul>
<p><em>Cf. <a href="https://nordicapis.com/10-free-to-use-cors-proxies/">10 Free to Use CORS Proxies | Nordic APIs |</a></em></p>
<h1 id="forward-proxy-vs-reverse-proxy">Forward Proxy vs Reverse Proxy</h1>
<p><img src="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/257642d6-9742-432b-9ca8-2a866dea04dd_1445x1536.jpeg" alt="출처: https://blog.bytebytego.com/p/ep25-proxy-vs-reverse-proxy"></p>
<table>
<thead>
<tr>
<th>기능</th>
<th>Forward Proxy</th>
<th>Reverse Proxy</th>
</tr>
</thead>
<tbody><tr>
<td>위치</td>
<td>클라이언트 측</td>
<td>서버 측</td>
</tr>
<tr>
<td>감춰지는(대신 작동하는) 대상</td>
<td>클라이언트</td>
<td>웹 서버</td>
</tr>
<tr>
<td>요청 전달 대상</td>
<td>인터넷</td>
<td>적절한 웹 서버</td>
</tr>
<tr>
<td>사용 사례</td>
<td>IP 숨기기, 콘텐츠 필터링, 캐싱</td>
<td>로드 밸런싱, 보안, 정적 콘텐츠 제공</td>
</tr>
</tbody></table>
<h1 id="additional-resources">Additional Resources</h1>
<ul>
<li><p><a href="https://www.designgurus.io/blog/load-balancer-reverse-proxy-api-gateway">Load Balancer vs. Reverse Proxy vs. API Gateway: Demystifying Web Architectures</a></p>
</li>
<li><p><a href="https://aws.amazon.com/ko/compare/the-difference-between-proxy-and-vpn/">프록시와 VPN - 중개 기술 간의 차이점 - AWS</a></p>
</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li><p><a href="https://gaia.cs.umass.edu/kurose_ross/ppt.php">Jim Kurose Homepage</a></p>
</li>
<li><p><a href="https://product.kyobobook.co.kr/detail/S000061694627">컴퓨터 네트워킹 하향식 접근</a></p>
</li>
<li><p><a href="https://inpa.tistory.com/entry/NETWORK-%F0%9F%93%A1-Reverse-Proxy-Forward-Proxy-%EC%A0%95%EC%9D%98-%EC%B0%A8%EC%9D%B4-%EC%A0%95%EB%A6%AC">🌐 Reverse Proxy / Forward Proxy 정의 &amp; 차이 정리</a></p>
</li>
<li><p><a href="https://www.cloudflare.com/ko-kr/learning/cdn/glossary/reverse-proxy/">리버스 프록시란? | 프록시서버 설명 | Cloudflare</a></p>
</li>
<li><p><a href="https://toss.tech/article/smart-web-service-cache">웹 서비스 캐시 똑똑하게 다루기</a></p>
</li>
<li><p><a href="https://blog.bytebytego.com/p/ep25-proxy-vs-reverse-proxy">EP26: Proxy vs reverse proxy</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Cookie, Session]]></title>
            <link>https://velog.io/@hee-suh/Cookie-Session</link>
            <guid>https://velog.io/@hee-suh/Cookie-Session</guid>
            <pubDate>Sat, 27 Apr 2024 14:21:00 GMT</pubDate>
            <description><![CDATA[<p>HTTP 서버는 상태를 유지하지 않는다. 이것은 서버 설계를 간편하게 하고 동시에 수천 개의 TCP 연결을 다룰 수 있는 고성능의 웹 서버를 개발하게 해줬다. 그러나 서버가 사용자 접속을 제한하거나 사용자에 따라 콘텐츠를 제공하기 위해 웹사이트가 사용자를 확인하는 것이 바람직할 때가 있다. 이 목적으로 HTTP는 쿠키 또는 세션을 사용한다.</p>
<h1 id="cookie">Cookie</h1>
<h2 id="쿠키를-사용하는-이유">쿠키를 사용하는 이유</h2>
<p>웹 사이트와 클라이언트 브라우저는 쿠키(cookie)를 이용하여 <strong>사용자 상태를 유지</strong>한다.</p>
<p>HTTP 쿠키(웹 쿠키, 브라우저 쿠키)는 서버가 사용자의 웹 브라우저에 보내는 작은 데이터 조각이다. 브라우저는 쿠키를 저장했다가 나중에 요청할 때 동일한 서버로 다시 보낼 수 있다.</p>
<p>쿠키는 주로 다음 세 가지 목적으로 사용된다.</p>
<ul>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#session_management">세션 관리</a>: 로그인, 장바구니, 게임 점수 또는 서버가 기억해야 하는 기타 모든 것</p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#personalization">개인화</a>: 사용자 기본 설정, 테마 및 기타 설정</p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#tracking">추적</a>: 사용자 행동 기록 및 분석</p>
</li>
</ul>
<p><em>Cf. 클라이언트 스토리지
쿠키는 한때 일반 클라이언트측 저장소로 사용되었다. 클라이언트에 데이터를 저장하는 유일한 방법이었을 때는 이것이 합리적이었지만 이제는 모던 스토리지 API가 권장된다. 쿠키는 요청이 있을 때마다 전송되므로 성능이 저하될 수 있다(특히 모바일 데이터 연결의 경우). 클라이언트 스토리지용 모던 API는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a> (<code>localStorage</code> 및 <code>sessionStorage</code>)와 <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a>다.</em></p>
<h2 id="쿠키-기술의-네-가지-요소">쿠키 기술의 네 가지 요소</h2>
<ol>
<li><p>HTTP 응답 메시지 쿠키 헤더</p>
<p> <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie"><code>Set-Cookie</code></a>를 이용하여 HTTP 응답 헤더는 서버에서 사용자 에이전트로 쿠키를 보낸다.</p>
<pre><code class="language-http"> HTTP/2.0 200 OK
 Content-Type: text/html
 Set-Cookie: yummy_cookie=choco
 Set-Cookie: tasty_cookie=strawberry

 [page content]</code></pre>
</li>
<li><p>HTTP 요청 메시지 쿠키 헤더</p>
<p> 이후 서버에 요청할 때마다 브라우저는 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie"><code>Cookie</code></a> 헤더를 사용하여 이전에 저장된 모든 쿠키를 서버로 다시 전송한다.</p>
<pre><code class="language-http"> GET /sample_page.html HTTP/2.0
 Host: www.example.org
 Cookie: yummy_cookie=choco; tasty_cookie=strawberry</code></pre>
</li>
<li><p>사용자의 host(end-system)에 보관되며 사용자의 브라우저에서 관리되는 쿠키 파일</p>
</li>
<li><p>웹사이트의 백엔드 데이터베이스</p>
</li>
</ol>
<h3 id="쿠키를-이용한-사용자-상태-유지">쿠키를 이용한 사용자 상태 유지</h3>
<img src='https://velog.velcdn.com/images/hee-suh/post/0540a2f8-46a4-4ca7-96a1-1c8444e7cf9d/image.png' alt='Maintaining user/server state: cookies' title='출처: Computer Networking a Top-Down Approach: Application Layer 2-34' />

<h2 id="보안">보안</h2>
<h3 id="session-hijacking--xss"><strong><a href="https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#session_hijacking">Session hijacking</a> &amp;</strong> <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss">XSS</a></h3>
<p>쿠키는 대개 웹 애플리케이션에서 사용자와 그들의 인증된 세션을 식별하기 위해 사용되곤 한다. 그래서 쿠키를 가로채는 것은 인증된 사용자의 세션 하이재킹으로 이어질 수 있다. 쿠키를 가로채는 일반적인 방법은 <a href="https://ko.wikipedia.org/wiki/%EC%82%AC%ED%9A%8C%EA%B3%B5%ED%95%99_(%EB%B3%B4%EC%95%88)">사회공학(social engineering)</a> 혹은 애플리케이션 내 XSS 취약점을 이용하는 것을 포함한다.</p>
<p><code>HttpOnly</code> 쿠키 속성은 JavaScript를 통해 쿠키 값에 접근하는 것을 막아 이런 공격을 감소시킬 수 있다.</p>
<h3 id="cross-site-요청-위조-csrf-xsrf"><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies#cross-site_%EC%9A%94%EC%B2%AD_%EC%9C%84%EC%A1%B0_csrf">Cross-site 요청 위조 (CSRF, XSRF)</a></h3>
<p>XSRF라고도 불리는 CSRF는 사용자의 브라우저가 사용자의 동의나 인지 없이 웹사이트의 백엔드에 대한 요청을 수행하도록 한다. 공격자는 XSS 페이로드를 사용하여 CSRF 공격을 시작할 수 있다.</p>
<p>이런 일들이 벌어지는 것을 방지하기 위한 몇 가지 기술이 있다:</p>
<ul>
<li>XSS와 마찬가지로, 입력 필터를 사용한다. (e.g. <a href="https://naver.github.io/lucy-xss-filter/kr/">Lucy XSS Filter</a>)</li>
<li>모든 민감한 동작에 확인 절차가 항상 수행되도록 한다.</li>
<li>민감한 정보에 사용되는 쿠키는 수명을 짧게 설정한다.</li>
</ul>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#session_fixation">Types of attacks - Security on the web | MDN</a></p>
<h2 id="단점">단점</h2>
<ul>
<li><strong>보안</strong>: 쿠키는 클라이언트의 디바이스에 저장되기 때문에, 도난과 변조 등의 보안 위협에 취약할 수 있다.</li>
<li><strong>개인 정보 보호</strong>: 쿠키는 사용자 행동을 추적하는 데 사용될 수 있으며 사용자의 개인 정보를 손상시킬 수 있다.</li>
<li><strong>용량 제한</strong>: 쿠키는 보통 4KB의 용량 제한이 있어서, 많은 정보를 담을 수 없다.</li>
</ul>
<h2 id="third-party-cookies"><em>Third-party Cookies</em></h2>
<p><em>쿠키 도메인이 현재 보고 있는 페이지의 도메인과 동일하다면, 그 쿠키는 퍼스트파티 쿠키로, 만약 도메인이 다르다면 서드파티 쿠키라고 불린다. 다른 도메인의 서버 상에 저장된 (광고 배너와 같은) 이미지 혹은 컴포넌트를 통해 전송되는 쿠키들을 서드파티 쿠키라고 부르며 웹을 통한 광고와 트래킹에 주로 사용된다.</em></p>
<p><a href="https://developers.google.com/privacy-sandbox/3pcd">Prepare for phasing out third-party cookies  |  Privacy Sandbox  |  Google for Developers</a></p>
<h1 id="session">Session</h1>
<h2 id="세션-id를-사용하는-이유">세션 ID를 사용하는 이유</h2>
<p>쿠키의 값은 브라우저에서 접근할 수 있어서 데이터 유출 및 조작 위험이 존재하기 때문에, 이를 해결하기 위해 세션 ID를 사용한다.</p>
<p>세션 ID(or 세션 토큰)는 현재 상호 작용 세션을 식별하기 위해 서버에서 생성되어 클라이언트로 전송되는 고유 식별자다. 클라이언트는 일반적으로 ID를 HTTP 쿠키로 저장하여 전송하거나 GET 또는 POST 쿼리의 매개변수로 전송한다.</p>
<p>세션 ID를 사용하면 클라이언트는 식별자만 처리하면 되고 모든 <strong>세션 데이터는</strong> 해당 식별자에 연결된 서버(일반적으로 <strong>클라이언트가 직접 액세스할 수 없는</strong> 데이터베이스에 저장)에 저장된다.</p>
<p>세션은 사용자 자격 증명 및 금융 데이터와 같은 민감한 정보를 저장하는 데 자주 사용된다.</p>
<h2 id="세션-작동-방식">세션 작동 방식</h2>
<img src='https://www.baeldung.com/wp-content/uploads/sites/4/2023/04/Working_of_Session.png' alt='' title='출처: https://www.baeldung.com/cs/web-sessions' />


<p>사용자가 <a href="https://www.baeldung.com/cs/application-server-vs-web-server">애플리케이션 서버</a>(백엔드) 데이터에 액세스하려고 한다고 가정해보자. </p>
<ol>
<li><p>비밀 자격 증명(e.g. id/password)을 사용하여 웹 브라우저에서 애플리케이션 서버에 로그인한다.</p>
</li>
<li><p>서버는 자격 증명을 확인하고 로그인에 성공하면 웹 브라우저에 고유 세션 ID를 포함한 응답을 제공한다.</p>
<p> 이 고유 세션 ID는 서버가 특정 세션에 대한 사용자의 요청을 추적하는 데 도움이 된다. 또한 <strong>서버는 여러 세션을 동시에 효율적으로 관리하기 위해 세션 ID를 활용</strong>한다.</p>
</li>
<li><p>웹사이트는 일반적으로 세션 ID를 쿠키에 저장하고, 세션 ID를 이용하여 서버에 접근한다.</p>
</li>
<li><p>브라우저는 사용자가 서버의 데이터 액세스를 완료하는 즉시 세션 종료 요청을 보낸다. (세션 만료)</p>
<ul>
<li>timeout 초과 (보통 30분)</li>
<li>브라우저 닫기</li>
<li>로그아웃</li>
</ul>
</li>
<li><p>애플리케이션 서버는 브라우저에 승인 응답을 보내고 세션을 종료한다. (세션 삭제)</p>
</li>
</ol>
<h2 id="보안-1">보안</h2>
<h3 id="session-hijacking"><strong><a href="https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#session_hijacking">Session hijacking</a></strong></h3>
<p>세션 ID는 웹사이트에 로그인한 사용자를 식별하는 데 자주 사용되므로 공격자가 세션을 탈취(<a href="https://en.wikipedia.org/wiki/Session_hijacking">session hijacking</a>)하여 잠재적인 권한을 획득하는 데 사용할 수 있다. 많은 서버는 공격자가 세션 ID를 획득한 경우에 대비하여 클라이언트에 대한 추가 확인을 수행한다. 세션 ID를 클라이언트의 IP 주소에 잠그는 것은 공격자가 동일한 주소로 서버에 연결할 수 없는 한 간단하고 효과적인 조치이지만, 반대로 클라이언트가 서버에 여러 경로로 연결되어 있고(e.g. 중복 인터넷 연결) 클라이언트의 IP 주소가 네트워크 주소 변환(NAT)을 거치는 경우 클라이언트에게 문제를 일으킬 수 있다.</p>
<h2 id="단점-1">단점</h2>
<ul>
<li><strong>지속성</strong>: 세션은 서버에 저장되고, 사용자가 브라우저를 닫거나 디바이스를 끄면 손실된다.</li>
<li><strong>확장성</strong>: 너무 많은 사용자가 동시에 웹 사이트에 접속하는 경우 세션은 확장성 문제를 일으킬 수 있다. 각 세션은 서버에 저장되어야 하기 때문이다.</li>
</ul>
<h1 id="cookie-vs-session">Cookie vs Session</h1>
<table>
<thead>
<tr>
<th>구분</th>
<th>쿠키</th>
<th>세션</th>
</tr>
</thead>
<tbody><tr>
<td>저장 위치</td>
<td>브라우저 (클라이언트 측)</td>
<td>웹 서버 (서버 측)</td>
</tr>
<tr>
<td>유지 기간</td>
<td>브라우저 종료 후 유지 (만료 기간 설정 가능), 사용자 삭제 가능</td>
<td>브라우저 닫거나 서버 연결 끊김 시 만료</td>
</tr>
<tr>
<td>용도</td>
<td>로그인 정보, 쇼핑 카트, 사이트 설정 등 사용자 정보 저장</td>
<td>로그인 여부 확인, 쇼핑 카트 관리, 최근 방문 페이지 기록 등</td>
</tr>
<tr>
<td>장점</td>
<td>사용자 편의성 향상, 서버 부하 감소</td>
<td>높은 보안성</td>
</tr>
<tr>
<td>단점</td>
<td>보안 취약성 (사용자 정보 도용 가능성), 브라우저 용량 증가</td>
<td>서버 부하 증가 가능성</td>
</tr>
<tr>
<td>보안</td>
<td>HTTPS 사용 시 상대적으로 안전, 사용자 정보 노출 위험 존재</td>
<td>서버 보안 강화 시 안전, 쿠키 도용 위험 감소</td>
</tr>
</tbody></table>
<h1 id="references">References</h1>
<ul>
<li><p><a href="https://gaia.cs.umass.edu/kurose_ross/ppt.php">Jim Kurose Homepage</a></p>
</li>
<li><p><a href="https://product.kyobobook.co.kr/detail/S000061694627">컴퓨터 네트워킹 하향식 접근</a></p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">Using HTTP cookies - HTTP | MDN</a></p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#session_fixation">Types of attacks - Security on the web | MDN</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Session_(computer_science)">Session (computer science)</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Session_ID">Session ID</a></p>
</li>
<li><p><a href="https://www.baeldung.com/cs/web-sessions">What Are Sessions? How Do They Work? | Baeldung on Computer Science</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP (HyperText Transfer Protocol)]]></title>
            <link>https://velog.io/@hee-suh/HTTP</link>
            <guid>https://velog.io/@hee-suh/HTTP</guid>
            <pubDate>Sat, 27 Apr 2024 14:06:07 GMT</pubDate>
            <description><![CDATA[<h1 id="overview-of-http">Overview of HTTP</h1>
<p><strong><em>HyperText Transfer Protocol</em></strong></p>
<ul>
<li><p>웹의 애플리케이션 계층 프로토콜</p>
</li>
<li><p>client/server model</p>
  <img src='https://velog.velcdn.com/images/hee-suh/post/ec7d1abc-d5ce-4888-91bf-e63d88340b39/image.png' title='Computer Networking a Top-Down Approach: Application Layer 2-19' alt='client/server model' />    
</li>
<li><p><strong>stateless</strong></p>
<p>  HTTP 서버는 클라이언트에 대한 정보를 유지하지 않으므로, HTTP를 무상태 프로토콜이라고 한다.</p>
</li>
</ul>
<h1 id="evolution-of-http">Evolution of HTTP</h1>
<h2 id="http-10">HTTP 1.0</h2>
<p><strong><em>Non-persistent HTTP 비지속 연결</em></strong></p>
<img src='https://velog.velcdn.com/images/hee-suh/post/9ab46ade-7231-48a6-a11a-3aa9eb4f3f9e/image.png' title='Computer Networking a Top-Down Approach: Application Layer 2-24' alt='Non-persistent HTTP' />   

<p>Non-persistent HTTP response time =  2RTT + file transmission time</p>
<p><em>* RTT (Round Trip Time): 작은 패킷이 클라이언트에서 서버로 갔다 오는 왕복 시간</em></p>
<blockquote>
<p>❗ <strong>issues</strong></p>
</blockquote>
<ul>
<li>각 요청 객체에 대해 새로운 TCP 연결이 설정되고 유지되어야 한다.<br>  TCP 버퍼가 할당되어야 하고 TCP 변수들이 클라이언트와 서버 양쪽에 유지되어야 하므로, 수많은 클라이언트들의 요청을 동시에 서비스하는 웹 서버에게 심각한 부담을 줄 수 있다.</li>
<li>각 객체는 2 RTT를 필요로 한다. (TCP 연결 설정에 1 RTT, 객체를 요청하고 받는 데 1 RTT)</li>
</ul>
<h2 id="http-11">HTTP 1.1</h2>
<p><strong><em>Persistent HTTP 지속 연결</em></strong></p>
<p>서버는 응답을 보낸 후에 TCP 연결을 그대로 유지한다. 같은 클라이언트와 서버 간의 이후 요청과 응답은 같은 연결을 통해 보내진다. <em>(default <code>Connection: keep-alive</code> header)</em></p>
<p>HTTP/1.1에서 도입된 <strong>pipelining</strong>을 통해 단일 TCP 연결을 통해 대응되는 응답을 기다리지 않고 여러 HTTP 요청을 보냄으로써, 웹 페이지 로딩 시간 성능을 개선했다.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:970/format:webp/0*o_SXSRWubbTLknac.png" alt="출처: https://engineering.cred.club/head-of-line-hol-blocking-in-http-1-and-http-2-50b24e9e3372"></p>
<blockquote>
<p>ℹ️ <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Connection_management_in_HTTP_1.x#http_%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B4%EB%8B%9D">HTTP 파이프라이닝은 모던 브라우저에서 기본적으로 활성화되어있지 않다.</a></p>
</blockquote>
<ul>
<li>버그가 있는 <a href="https://en.wikipedia.org/wiki/Proxy_server">프록시</a>들이 여전히 많은데, 이들은 웹 개발자들이 쉽게 예상하거나 분석하기 힘든 이상하고 오류가 있는 동작을 야기한다.</li>
<li>파이프라이닝은 정확히 구현해내기 복잡하다.
전송 중인 리소스의 크기, 사용될 효과적인 <a href="https://en.wikipedia.org/wiki/Round-trip_delay_time">RTT</a>, 그리고 효과적인 대역폭은 파이프라인이 제공하는 성능 향상에 직접적으로 영향을 미친다. 이런 내용을 모른다면, 중요한 메시지가 덜 중요한 메시지에 밀려 지연될 수 있다. 중요성에 대한 생각은 페이지 레이아웃 중에도 진전된다. 그러므로 파이프라이닝은 대부분의 경우 미미한 수준의 향상만을 가져다 준다.</li>
<li>파이프라이닝은 <a href="https://en.wikipedia.org/wiki/Head-of-line_blocking">HOL</a> 문제에 영향을 받는다.</li>
</ul>
<blockquote>
<p>❗ <strong>HOL (head-of-line) blocking issue</strong>
하나의 (느린) 오브젝트가 다른/따라오는 오브젝트의 진행을 방해해서 웹 성능이 저하되는 문제</p>
</blockquote>
<ul>
<li>HTTP HOL Blocking (Application Layer)<pre><code>                      |------------a.png------------|
              |-b.png-|
  |---c.png---|</code></pre></li>
<li>TCP HOL Blocking (Transport Layer)<pre><code>                        |----packet1----|xxx lost xxx|----packet1----|
              |-packet2-|
  |--packet3--|</code></pre></li>
</ul>
<blockquote>
<p>💡 <a href="https://developer.mozilla.org/en-US/docs/Glossary/Domain_sharding">Domain Sharding</a>
여러 개의 병렬 TCP 연결을 동시에 생성해, 여러 subdomain을 이용하여 여러 리소스를 한 번에 다운로드하는 기술이다.
HTTP/1.1에서 <a href="https://developer.mozilla.org/en-US/docs/Glossary/Domain_sharding">domain sharding</a>을 이용하여 HOLB를 해결해왔으나, 도메인의 주소를 찾기 위해 DNS Lookup 과정에서 시간을 잡아먹을수도 있으며, 브라우저별로 Domain당 Connection 개수의 제한(Chrome은 max 6개)이 존재하여 근본적인 해결책은 아니었다.</p>
</blockquote>
<h2 id="http-20">HTTP 2.0</h2>
<p><strong><em>⭐️</em> HTTP/2 주요 목표: 하나의 TCP 연결상에서 멀티플렉싱 요청/응답 지연 시간을 줄이는 데 있다.</strong></p>
<ul>
<li><p><strong>Binary protocol</strong>
텍스트로 작성된 메시지로 구성된 HTTP/1.1과 달리, HTTP/2는 binary protocol을 사용한다. 파싱을 단순화하고 에러를 줄이면서 클라이언트와 서버 간 통신을 더 효율적이게 만들었다.
<img src="https://hpbn.co/assets/diagrams/ae09920e853bee0b21be83f8e770ba01.svg" alt="출처: https://hpbn.co/http2/">
HTTP/2는 각 메시지를 독립적인 <strong>프레임</strong>으로 쪼개고, 다른 쪽 끝에서 다시 조립할 수 있도록 한다. 또한 프레임을 바이너리 인코딩한다.    </p>
</li>
<li><p>Frame &lt; Message &lt; Stream*</p>
<ul>
<li><p><em>Frame: HTTP/2에서 최소 통신 단위이며, Header 혹은 Data 둘 중 하나다.</em></p>
</li>
<li><p><em>Message: HTTP/1.1과 마찬가지로 요청 혹은 응답의 단위이며, 다수의 Frame으로 이루어져있다.</em></p>
</li>
<li><p><em>Stream: 클라이언트와 서버 사이에 설정된 연결을 통해 양방향으로 주고받는 하나 이상의 메시지다.</em></p>
<p><img src="https://hpbn.co/assets/diagrams/47ba5b32e42cf5a06c3741d29ef9b94a.svg" alt="출처: https://hpbn.co/http2/"></p>
</li>
</ul>
</li>
<li><p><strong>Multiplexing, 다중화</strong>
HTTP/2의 새로운 바이너리 프레이밍 레이어를 이용하여, 하나의 TCP 연결로 동시에 여러 개의 스트림을 응답 순서에 상관 없이 주고 받는 것을 의미한다.</p>
</li>
<li><p>Cf. <strong>HTTP/1.1</strong> 은 하나의 TCP 연결 상에서 여러 파이프라인 구조의 GET을 보내며, HOLB 문제를 야기했으나, HTTP/2로 전환하면 네트워크 지연 시간이 줄어들 뿐만 아니라 처리량이 개선되고 운영 비용이 절감된다.*</p>
</li>
<li><p><strong>Server Push</strong>
HTTP/2는 클라이언트가 요청하기 전에 서버가 클라이언트에 콘텐츠를 &quot;push&quot;할 수 있다.
❗️HTTP/2 서버 푸시는 <a href="https://developer.chrome.com/blog/removing-push?hl=ko">Chrome에서 더 이상 지원되지 않는다.</a> 서버 푸시의 대안으로는 <a href="https://developer.chrome.com/blog/early-hints">103 Early Hints</a>과 <a href="https://web.dev/articles/preload-critical-assets">Preloading</a>이 있다.</p>
</li>
<li><p><strong>Stream Prioritization</strong>
HTTP/2는 각 스트림, 즉 요청과 응답에 우선순위를 매겨서, 서버는 가장 높은 우선순위의 요청을 위한 프레임을 제일 먼저 보낼 수 있다. 이는 HTML이나 주요 스크립트와 같은 중요 자원들이 높은 우선순위를 받아서, 먼저 도착하는 것을 보장하고 사용자 경험을 향상시킬 수 있다는 것을 의미한다.</p>
</li>
<li><p><strong>Header Compression</strong>
작은 파일은 큰 파일보다 더 빨리 로드된다. 웹 성능 속도를 높이기 위해 HTTP/1.1 및 HTTP/2는 모두 HTTP 메시지를 압축하여 더 작게 만든다. 그러나 HTTP/2는 HTTP 헤더 패킷에서 중복 정보를 제거하는 HPACK이라는 고급 압축 방법을 사용한다. 이렇게 하면 모든 HTTP 패킷에서 몇 바이트가 제거된다. 단일 웹 페이지를 로드하는 데 관련되는 HTTP 패킷의 양을 고려할 때, 이러한 바이트는 빠르게 합산되어 로드 속도가 빨라진다.</p>
</li>
</ul>
<blockquote>
<p>❗ <strong>issues</strong></p>
</blockquote>
<ul>
<li>TCP HOC(Head of line) blocking</li>
<li>no security over vanilla TCP connection</li>
</ul>
<h2 id="http-30-quic">HTTP 3.0 (QUIC)</h2>
<p><strong>QUIC(Quick UDP Internet Connections, 빠른 인터넷 연결)</strong></p>
<p>QUIC은 UDP 프로토콜 위에 구현한 트랜스포트 계층 프로토콜이다. </p>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/29a42ac1-4708-458c-aced-ec5d634bfc3f/image.png" alt="출처: https://web.eecs.umich.edu/~xumiao/docs/imc23-quic-poster.pdf"></p>
<ul>
<li><p><strong>HTTP over TCP + TLS</strong></p>
<ul>
<li><p>TCP (reliability, congestion control state) + TLS (authentication, crypto state)</p>
</li>
<li><p>2 serial handshakes</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>HTTP over QUIC</strong></p>
<ul>
<li><p>QUIC: reliability, congestion control, authentication, crypto state</p>
</li>
<li><p>1 handshake</p>
</li>
</ul>
</li>
</ul>
<img src='https://velog.velcdn.com/images/hee-suh/post/f29cb57b-2987-4871-a4af-4b9bd35f1ca0/image.png' alt='http over TCP + TLS' title='출처: Computer Networking a Top-Down Approach: Transport Layer 3-144' />


<p><strong>QUIC 주요 기능</strong></p>
<ul>
<li><p><strong>연결지향적 &amp; 보안</strong>
  TCP와 마찬가지로 QUIC은 두 종단 간의 연결지향 프로토콜이다. 대신 QUIC은 연결 상태를 설정하는 데 필요한 핸드셰이크와 인증 및 암호화에 필요한 핸드셰이크를 결합하여, 더 빠른 설정(1RTT)을 제공한다.</p>
</li>
<li><p><strong>Stream Multiplexing</strong>
  QUIC을 사용하면 단일 QUIC 연결을 통해 여러 애플리케이션 레벨의 스트림들을 다중화하고, 이를 통해 동시에 여러 스트림이 독립적으로 전송된다.</p>
</li>
<li><p><strong>신뢰적이고 TCP 친화적인 혼잡 제어 데이터 전송</strong><br>  QUIC은 스트림별로 데이터를 신뢰적이고 순서대로 전달하기 때문에 손실된 UDP 세그먼트는 해당 세그먼트에서 데이터가 전달된 스트림에만 영향을 준다. 다른 스트림의 HTTP 메시지는 계속 수신되어 애플리케이션에 전달될 수 있으므로, <strong>TCP HOL Blocking을 해결</strong>한다.</p>
</li>
</ul>
<blockquote>
<p>❗ <strong>Limitations</strong></p>
</blockquote>
<ul>
<li>QUIC은 패킷별로 암호화하기 때문에 패킷을 묶어서 암호화하는 TLS-TCP보다 리소스 소모가 더 클 수 있다.</li>
<li>CPU 사용량이 많다.</li>
<li>UDP를 사용하기 때문에 <a href="https://www.cloudflare.com/ko-kr/learning/ddos/what-is-a-quic-flood/">UDP Flood DDoS 공격</a>에 취약하다.</li>
</ul>
<h2 id="http-10-➡︎-http-11-➡︎-http-20-➡︎-http-30-quic">HTTP 1.0 ➡︎ HTTP 1.1 ➡︎ HTTP 2.0 ➡︎ HTTP 3.0 (QUIC)</h2>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/641bd7c1-f965-4144-99c3-06d733c3be14_3486x2853.png" alt="출처: https://blog.bytebytego.com/p/http-10-http-11-http-20-http-30-quic"></p>
<h1 id="additional-resources">Additional Resources</h1>
<ul>
<li><p>HTTP Delay 비교 애니메이션</p>
<p>  <a href="https://media.pearsoncmg.com/ph/esm/ecs_kurose_compnetwork_8/cw/content/interactiveanimations/http-delay-estimation/index.html">HTTP Delay Estimation</a></p>
</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li><p><a href="https://gaia.cs.umass.edu/kurose_ross/ppt.php">Jim Kurose Homepage</a></p>
</li>
<li><p><a href="https://product.kyobobook.co.kr/detail/S000061694627">컴퓨터 네트워킹 하향식 접근</a></p>
</li>
<li><p><a href="https://web.dev/articles/performance-http2?hl=ko">HTTP/2 소개  |  Articles  |  web.dev</a></p>
</li>
<li><p><a href="https://www.cloudflare.com/ko-kr/learning/performance/http2-vs-http1.1/">HTTP/2와 HTTP/1.1의 비교 | Cloudflare</a></p>
</li>
<li><p><a href="https://www.linkedin.com/pulse/http1-vs-http2-protocols-ofweb-aditya-joshi/">HTTP/1 vs. HTTP/2: Protocols of Web</a></p>
</li>
<li><p><a href="https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-HTTP-20-%ED%86%B5%EC%8B%A0-%EA%B8%B0%EC%88%A0-%EC%9D%B4%EC%A0%9C%EB%8A%94-%ED%99%95%EC%8B%A4%ED%9E%88-%EC%9D%B4%ED%95%B4%ED%95%98%EC%9E%90">Inpa Dev 🌐 HTTP 2.0 소개 &amp; 통신 기술 알아보기</a></p>
</li>
<li><p><a href="https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-HTTP-30-%ED%86%B5%EC%8B%A0-%EA%B8%B0%EC%88%A0-%EC%9D%B4%EC%A0%9C%EB%8A%94-%ED%99%95%EC%8B%A4%ED%9E%88-%EC%9D%B4%ED%95%B4%ED%95%98%EC%9E%90">Inpa Dev 🌐 HTTP 3.0 소개 &amp; 통신 기술 알아보기</a></p>
</li>
<li><p><a href="https://engineering.cred.club/head-of-line-hol-blocking-in-http-1-and-http-2-50b24e9e3372">HOL(head-of-line) blocking in http</a></p>
</li>
<li><p><a href="https://hpbn.co/http2/">HTTP: HTTP/2 - High Performance Browser Networking (O&#39;Reilly)</a></p>
</li>
<li><p><a href="https://web.eecs.umich.edu/~xumiao/docs/imc23-quic-poster.pdf">web.eecs.umich.edu</a></p>
</li>
<li><p><a href="https://blog.bytebytego.com/p/http-10-http-11-http-20-http-30-quic">HTTP 1.0 -&gt; HTTP 1.1 -&gt; HTTP 2.0 -&gt; HTTP 3.0 (QUIC)</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TCP/IP Model]]></title>
            <link>https://velog.io/@hee-suh/TCP-IP-Model</link>
            <guid>https://velog.io/@hee-suh/TCP-IP-Model</guid>
            <pubDate>Sat, 27 Apr 2024 13:33:23 GMT</pubDate>
            <description><![CDATA[<h1 id="프로토콜-계층화">프로토콜 계층화</h1>
<p>네트워크 프로토콜의 설계 구조를 제공하기 위해, 네트워크 설계자는 프로토콜(프로토콜을 구현하는 네트워크 하드웨어와 소프트웨어)을 <strong>계층(layer)</strong>으로 조직한다.</p>
<p>프로토콜 계층화는 시스템 구성요소에 대해 논의하기 위한 구조화된 방법을 제공한다. 모듈화는 시스템 구성요소의 갱신을 더 쉽게 해준다.</p>
<p>다양한 계층의 프로토콜을 모두 합하여 <strong>프로토콜 스택(protocol stack)</strong>이라고 한다. TCP/IP 프로토콜 스택은 4계층(Application, Transport, Network, Link)으로 구성된다.</p>
<h1 id="tcpip-model">TCP/IP Model</h1>
<img src='https://velog.velcdn.com/images/hee-suh/post/ae2370eb-0f31-47b2-be26-60ae1bb12b92/image.svg' alt='tcp/ip model' />

<ul>
<li><p><strong>application-layer</strong> 프로토콜은 transport layer의 서비스를 사용하여 일부 애플리케이션 서비스를 구현하기 위해 <strong>메시지(message)</strong>를 교환한다.</p>
</li>
<li><p><strong>transport-layer</strong> 프로토콜은 transport-layer <strong>세그먼트(segment)</strong>를 생성하기 위해 transport-layer 헤더 $H_t$를 사용하여 application-layer 메시지 $M$을 <strong>캡슐화</strong>한다.</p>
</li>
<li><p><strong>network-layer</strong> 프로토콜은 transport-layer 세그먼트 $[H_t | M]$와 network-layer 헤더 $H_n$을 사용하여 network-layer <strong>데이터그램(또는 패킷)</strong>을 생성한다.</p>
</li>
<li><p><strong>link-layer</strong> 프로토콜은 link-layer <strong>프레임</strong>을 생성하기 위해 link-layer 헤더 $H_l$을 사용하여 네트워크 데이터그램 $[H_n | [H_t |M]]$을 <strong>캡슐화</strong>한다. </p>
</li>
</ul>
<h1 id="application-layer">Application Layer</h1>
<blockquote>
<p><strong>Protocols</strong> HTTP, FTP, SMTP, DNS, TLS, CDN</p>
</blockquote>
<p>애플리케이션 계층은 네트워크 애플리케이션과 애플리케이션 계층 프로토콜이 있는 곳이다. 애플리케이션 계층 프로토콜은 여러 종단 시스템에 분산되어 있어서 한 종단 시스템에 있는 애플리케이션이 다른 종단 시스템에 있는 애플리케이션과 정보 패킷을 교환하는 데 이 프로토콜을 사용한다. 애플리케이션 계층에서의 이 정보 패킷을 메시지(message)라고 부른다.</p>
<h2 id="두-가지-애플리케이션-아키텍처">두 가지 애플리케이션 아키텍처</h2>
<p><img src="https://velog.velcdn.com/images/hee-suh/post/754c4a3c-8266-4c77-b0d7-b19ad0731ef7/image.svg" alt="Application Architectures"></p>
<h3 id="client-server-architecture">Client-Server Architecture</h3>
<p><em>HTTP, IMAP, FTP</em></p>
<p>항상 동작하고 있는 host를 <strong>server</strong>라 부르고, 서버와의 서비스는 <strong>client</strong>라는 다른 host들로부터 서비스 요청을 받는다.</p>
<h3 id="peer-to-peer-p2p-architecture">Peer-to-Peer, P2P Architecture</h3>
<p><em>P2P file sharing [BitTorrent]</em></p>
<p>항상 켜져 있는 infrastructure server에 최초로 의존한다(혹은 전혀 의존하지 않는다). 대신 애플리케이션은 peer라는 간헐적으로 연결된 host 쌍이 서로 직접 통신하게 한다.</p>
<h1 id="transport-layer">Transport Layer</h1>
<blockquote>
<p><strong>Protocols</strong> TCP, UDP</p>
</blockquote>
<p>인터넷의 트랜스포트 계층은 클라이언트와 서버 간에 애플리케이션 계층 메시지를 전송하는 서비스를 제공한다. 인터넷에는 TCP와 UDP라는 두 가지 트랜스포트 프로토콜이 있으며 이들은 애플리케이션 계층 메시지를 전달한다. TCP는 애플리케이션에게 연결지향형 서비스를 제공한다. 또한 TCP는 긴 메시지를 짧은 메시지로 나누고 혼잡 제어 기능을 제공하여 네트워크가 혼잡할 때 출발지의 전송률을 줄이게 한다. UDP 프로토콜은 애플리케이션에 비연결형 서비스를 제공한다. 이 서비스는 신뢰성, 흐름 제어, 혼잡 제어를 제공하지 않는 아주 간단한 서비스다. 트랜스포트 계층 패킷은 <strong>세그먼트(segment)</strong>라고 한다.</p>
<p><em>Cf. 인터넷 문서에서(e.g. RFC) TCP에 대한 PDU(Protocol Data Unit)는 segment, UDP에 대한 PDU는 datagram이라고 표현하기도 하지만, network layer의 datagram과 혼란을 줄이고자 TCP와 UDP 패킷을 모두 segment로 지칭한다.</em></p>
<h1 id="network-layer">Network Layer</h1>
<blockquote>
<p><strong>Protocols</strong> IP (IPv4, IPv6)</p>
</blockquote>
<p>인터넷의 네트워크 계층은 한 호스트에서 다른 호스트로 <strong>IP 데이터그램(datagram)</strong>을 라우팅하는 책임을 진다. 출발지(source) 호스트에서 인터넷 트랜스포트 계층 프로토콜은 트랜스포트 계층 세그먼트와 목적지(destination) 주소를 네트워크 계층으로 전달한다. 그런 다음 네트워크 계층은 목적지 호스트의 트랜스포트 계층으로 세그먼트를 운반하는 서비스를 제공한다. </p>
<p>인터넷 네트워크 계층은 두 가지 주요 요소를 갖는다. 이 계층은 IP 데이터그램의 필드를 정의하며 종단 시스템과 라우터가 이 필드에 어떻게 동작하는지를 정의하는 프로토콜인, IP 프로토콜을 갖고 있다. 또한 인터넷 네트워크 계층은 라우팅 프로토콜을 포함한다. 비록 네트워크 계층이 IP 프로토콜과 여러 라우팅 프로토콜을 모두 갖고 있지만, IP가 인터넷을 함께 묶는 역할을 한다는 사실을 반영하여 흔히 IP 계층으로 불린다.</p>
<h1 id="link-layer">Link Layer</h1>
<blockquote>
<p><strong>Protocols</strong> Ethernet, Wireless LAN</p>
</blockquote>
<p>인터넷의 네트워크 계층은 출발지와 목적지 간 일련의 패킷 스위치(라우터)를 통해 데이터그램을 라우트한다. 경로상의 한 노드(호스트 혹은 패킷 스위치)에서 다른 노드로 패킷을 이동하기 위해 네트워크 계층은 링크 계층 서비스에 의존해야 한다. 특히 각 노드에서 네트워크 계층은 데이터그램을 아래 링크 계층으로 보내고 링크 계층은 그 데이터그램을 경로상의 다음 노드에 전달한다. 다음 노드에서 링크 계층은 그 데이터그램을 상위 네트워크 계층으로 보낸다.</p>
<p>데이터그램이 출발지에서 목적지로 가는데 여러 링크를 거치므로 데이터그램은 경로상의 각기 다른 링크에서 다른 링크 계층 프로토콜에 의해 처리될 수 있다. 네트워크 계층은 각기 다른 링크 계층 프로토콜로부터 다른 서비스를 제공받을 것이다. 링크 계층 패킷은 <strong>프레임(frame)</strong>이라고 한다.</p>
<h1 id="references">References</h1>
<ul>
<li><p><a href="https://gaia.cs.umass.edu/kurose_ross/ppt.php">Jim Kurose Homepage</a></p>
</li>
<li><p><a href="https://product.kyobobook.co.kr/detail/S000061694627">컴퓨터 네트워킹 하향식 접근</a></p>
</li>
<li><p><a href="https://blog.bytebytego.com/p/network-protocols-run-the-internet">Network Protocols Run the Internet</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴퓨터 네트워크와 인터넷]]></title>
            <link>https://velog.io/@hee-suh/computer-network-and-internet</link>
            <guid>https://velog.io/@hee-suh/computer-network-and-internet</guid>
            <pubDate>Sat, 27 Apr 2024 05:59:06 GMT</pubDate>
            <description><![CDATA[<h1 id="인터넷">인터넷</h1>
<p>인터넷은 분산 애플리케이션에 서비스를 제공하는 infrastructure로서, <strong>네트워크의 네트워크(network of networks)</strong>다.</p>
<figure>
  <img src='https://velog.velcdn.com/images/hee-suh/post/3422213b-f831-431d-9fe7-90564345c575/image.png' alt='network of networks, internet' width='320' />
  <!--
  <figcaption style='font-size:0.9rem; color:gray; margin-top:-0.5rem'>
    출처: Computer Networking a Top-Down Approach: Introduction 1-5
  </figcaption>
  -->
</figure>


<h2 id="구성-요소">구성 요소</h2>
<blockquote>
<p>인터넷의 구성 요소를 통해, 컴퓨터 네트워크의 개요와 컴퓨터 네트워크에서 사용되는 용어를 살펴보자. </p>
</blockquote>
<p>인터넷은 전 세계적으로 수십억 개의 컴퓨팅 장치를 연결하는 컴퓨터 네트워크다.
모든 장치는 인터넷 용어로 <strong>호스트(host)</strong> 혹은 <strong>종단 시스템(end system)</strong>이라고 부른다.</p>
<p>종단 시스템은 <strong>통신 링크(communication link)</strong>와 <strong>패킷 스위치(packet switch)</strong>의 네트워크로 연결된다. 이때 각각의 링크들은 다양한 <strong>전송률</strong>(transmission rate, 링크 대역폭)을 이용하여 데이터를 전송하며 전송률은 초당 비트 수를 의미하는 <strong>bps(bit per second)</strong> 단위를 사용한다. 한 종단 시스템이 다른 종단 시스템으로 보낼 데이터를 갖고 있을 때, 송신 종단 시스템은 그 데이터를 세그먼트(segment)로 나누고 각 세그먼트에 헤더(header)를 붙인다. 이렇게 만들어진 정보 패키지는 컴퓨터 네트워크에서 <strong>패킷(packet)</strong>이라고 부른다. 패킷은 목적지 종단 시스템으로 네트워크를 통해 보내지고 목적지에서 원래의 데이터로 다시 조립된다.</p>
<p>패킷 교환기(또는 스위치)는 입력 통신 링크의 하나로 도착하는 패킷을 받아서 출력 통신 링크의 하나로 그 패킷을 전달한다. 오늘날의 인터넷에서 가장 널리 사용되는 두 가지 패킷 스위치는 <strong>라우터(router)</strong>와 <strong>링크 계층 스위치(link-layer switch)</strong>가 있다. 두 형태의 스위치는 최종 목적지 방향으로 패킷을 전달한다. 링크 계층 스위치는 보통 접속 네트워크에서 사용되고 라우터는 네트워크 코어에서 사용된다. 패킷이 송신 종단 시스템에서 수신 종단 시스템에 도달하는 동안 거쳐온 일련의 통신 링크와 패킷 스위치를 네트워크상의 <strong>경로(route 혹은 path)</strong>라고 한다.</p>
<p>종단 시스템은 <strong>ISP(Internet Service Provider)</strong>를 통해 인터넷에 접속한다. ISP는 종단 시스템에게 케이블 모뎀이나 DSL 같은 가정용 초고속 접속, 고속 LAN 접속, 이동 무선 접속을 포함하는 다양한 네트워크 접속을 제공한다. 인터넷은 종단 시스템을 서로 연결하는 것이므로 종단 시스템에 접속을 제공하는 ISP들도 서로 연결되어야만 한다.</p>
<p>종단 시스템, 패킷 스위치를 비롯한 인터넷의 다른 구성요소는 인터넷에서 정보 송수신을 제어하는 여러 <strong>프로토콜(procotol)</strong>을 수행한다. 특히 <strong>TCP(Transmission Control Protocol)</strong>와 <strong>IP(Internet Protocol)</strong>는 인터넷에서 가장 중요한 프로토콜이다. 이러한 인터넷의 주요 프로토콜을 통칭하여 <strong>TCP/IP</strong>라고 한다.</p>
<p>인터넷에서 프로토콜의 중요성을 감안할 때, 상호 호환되는 시스템과 제품을 만들기 위해서는 <strong>표준화</strong>가 매우 중요하다. 인터넷 표준은 <strong>IEFT(Internet Engineering Task Force)</strong>에서 개발하며, IEFT 표준 문서를 <strong>RFC(requests for comments)</strong>라고 한다. 이들은 TCP, IP, HTTP, SMTP 같은 프로토콜을 정의하고, 현재 약 9,000개 이상의 RFC가 있다. 다른 기구들도 네트워크 구성요소에 대한 표준을 기술하며, 예를 들어 IEEE 802 LAN 표준위원회는 이더넷과 무선 와이파이(WiFi) 표준을 기술한다.</p>
<h1 id="네트워크-구성">네트워크 구성</h1>
<img src='https://velog.velcdn.com/images/hee-suh/post/33892b86-7c7b-4adc-a923-cb9f7e7d7063/image.svg' alt='네트워크 구성' width='320' />

<h2 id="network-edge">Network Edge</h2>
<!--
<details>
  <summary>
    네트워크의 가장자리에 있는 종단 시스템(end-system)
  </summary>
  <div markdown="1">
    <figure>
      <img src="https://velog.velcdn.com/images/hee-suh/post/548e641f-53d4-4267-b958-6477b0a3b20d/image.png"  alt="network edge" width='320px' alt='Computer Networking a Top-Down Approach: Introduction 1-10' />
      <figcaption style='font-size:0.9rem; color:gray; margin-top:-0.5rem'>
        출처: Computer Networking a Top-Down Approach: Introduction 1-10
      </figcaption>
    </figure>
  </div>
</details>
-->

<p>네트워크의 가장자리에 있는 종단 시스템(end-system)</p>
<ul>
<li>host(end-system)는 때때로 client와 server로 구분된다.</li>
</ul>
<img src="https://velog.velcdn.com/images/hee-suh/post/548e641f-53d4-4267-b958-6477b0a3b20d/image.png"  alt="network edge" width='320px' alt='Computer Networking a Top-Down Approach: Introduction 1-10' />

<h2 id="access-network">Access Network</h2>
<details>
  <summary>종단 시스템을 그 종단 시스템으로부터 다른 먼 거리의 종단 시스템까지의 경로상에 있는 첫 번째 라우터(edge router)에 연결하는 네트워크</summary>
  <div markdown="1">
    <figure>
      <img src="https://velog.velcdn.com/images/hee-suh/post/649b8437-88c5-4444-ba65-28c9bce21758/image.png"  alt="access network" width='320px' alt='Computer Networking a Top-Down Approach: Introduction 1-11' />
      <!--
      <figcaption style='font-size:0.9rem; color:gray; margin-top:-0.5rem'>
        출처: Computer Networking a Top-Down Approach: Introduction 1-11
      </figcaption>
      -->
    </figure>
  </div>
</details>

<ul>
<li><p><strong>가정 접속: DSL 케이블, FTTH, 5G 고정 무선</strong></p>
<ul>
<li><strong>DSL(digital subsriber line)</strong>과 <strong>케이블</strong>이 오늘날 가장 널리 보급된 광대역 가정 접속 유형이다.</li>
<li>더 빠른 속도를 제공하는 미래 개술은 <strong>FTTH(fiber to the home)</strong>다.</li>
<li><strong>5G 고정 무선(5G fixed wireless, 5G-FW)</strong>은 서비스 제공자의 기지국에서 가정 내의 모뎀으로 데이터를 무선으로 전송한다.</li>
</ul>
</li>
<li><p><strong>기업(그리고 가정) 접속: 이더넷과 와이파이</strong></p>
<ul>
<li><p>여러 유형의 <strong>LAN(Local Area network)</strong> 기술 중 <strong>이더넷</strong> 기술이 기업, 대학, 홈 네트워크에서 가장 널리 사용되는 접속 기술이다.</p>
<p>  이더넷은 이더넷 스위치에 연결하기 위해 Twisted Pair(꼬임 쌍선)을 이용한다.</p>
</li>
<li><p><strong>무선 랜(wireless LAN)</strong> 환경에서 사용자들은 기업 네트워크(대부분 유선 이더넷을 포함)에 연결된 <strong>AP(acess point)</strong>로 패킷을 송신/수신하고 이 AP는 유선 네트워크에 다시 연결된다.</p>
<p>  IEEE 802. 11 기술에 기반한 무선 랜 접속은 와이파이라고 더 잘 알려져있다.</p>
</li>
</ul>
</li>
<li><p><strong>광역 무선 접속: 3G와 LTE 4G와 5G</strong></p>
<p>  이동 장치들은 이동 전화망 사업자들이 운영하는 기지국을 통해 패킷을 송수힌하는 데 사용하는 것과 같은 무선 infrastructure를 채택하고 있다. 와이파이(수십 미터 반경)와 달리, 사용자는 기지국의 수십 킬로미터 반경 내에 있으면 된다.</p>
</li>
</ul>
<h2 id="network-core">Network Core</h2>
<details>
  <summary>인터넷의 종단 시스템을 연결하는 패킷 스위치들과 링크들의 연결망(mesh)
</summary>
  <div markdown="1">
    <figure>
      <img src="https://velog.velcdn.com/images/hee-suh/post/e781e9f7-3c3b-4bba-9ba7-1516d4559f2d/image.png"  alt="access network" width='320px' alt='Computer Networking a Top-Down Approach: Introduction 1-12' />
      <!--
      <figcaption style='font-size:0.9rem; color:gray; margin-top:-0.5rem'>
        출처: Computer Networking a Top-Down Approach: Introduction 1-12
      </figcaption>
      -->
    </figure>
  </div>
</details>

<p>link와 switch의 네트워크를 통해 데이터를 이동시키는 방식에는 <strong>패킷 교환(packet switching)</strong>과 <strong>회선 교환(circuit swtching)</strong>이라는 두 가지 기본 방식이 있다.</p>
<h3 id="packet-switching-패킷-교환">Packet Switching (패킷 교환)</h3>
<p>네트워크 애플리케이션에서 종단 시스템들은 서로 <strong>메시지(message)</strong>를 교환한다. host는 application-layer message를 <strong>패킷(packet)</strong>으로 쪼갠다. 송신 측과 수신 측 사이에서 각 패킷은 통신 링크와 <strong>packet switch</strong>(router와 link-layer switch의 두 가지 유형이 있음)를 거치게 된다.</p>
<p><em>network <strong>forward</strong>s(a.k.a. <strong>switch</strong>es) packets from one router to the next, across links on <strong>path</strong> from <strong>source to destination</strong></em></p>
<ul>
<li><p><strong>store-and-forward</strong> (저장-후-전달 전송)</p>
<p>  대부분의 packet switch가 이용하는 방식으로, switch가 출력 link로 packet의 첫 bit를 전송하기 전에 전체 packet을 받아야 함을 의미한다.</p>
<ul>
<li><strong>packet transmission delay</strong>: 종단 시스템 혹은 패킷 스위치가 $L$ bit packet을 link에서 $R$ bps(비트/초)의 속도로 송신한다면, 그 packet을 전송하는 데 걸리는 시간은 $L/R$ 초다. (이때 $R$ bps는 link의 최대 전송률이다.)</li>
</ul>
</li>
<li><p><strong>queueing</strong></p>
  <figure>
    <img src="https://velog.velcdn.com/images/hee-suh/post/f9509bbe-776e-433c-903d-8454d411e85b/image.png"  alt="access network" alt='Computer Networking a Top-Down Approach: Introduction 1-31' />
      <!--
    <figcaption style='font-size:0.9rem; color:gray; margin-top:-0.5rem'>
      출처: Computer Networking a Top-Down Approach: Introduction 1-31
    </figcaption>
    -->
</figure>

<p>  <strong>packet queuing and loss</strong>:</p>
<p>  라우터에 도착하는 packet의 전송률<em>(in bps)*이 link의 전송률</em>(bps)*을 초과하는 시점에</p>
<ul>
<li>packet은 출력 버퍼에서 대기해야 하며, <strong>큐잉 지연(queueing delay)</strong>을 겪게 된다.</li>
<li>라우터의 메모리(buffer)가 꽉 차 있다면, 도착하는 packet 또는 이미 큐에 대기 중인 packet을 폐기(drop)하는 <strong>packet 손실(loss)</strong>이 발생한다.</li>
</ul>
</li>
</ul>
<h3 id="circuit-switching-회선-교환">Circuit Switching (회선 교환)</h3>
<p>종단 시스템 간에 통신을 제공하기 위해 경로상에 필요한 자원(버퍼, link 전송률)은 통신 세션(session) 동안에 확보 또는 예약(reservation)된다.</p>
<p>link 내 한 회선은 주파수 분할 다중화(frequency-division multiplexing, FDM) 혹은 시분할 다중화(time-division multiplexing, TDM)로 구현된다.</p>
<figure>
  <img src='https://velog.velcdn.com/images/hee-suh/post/2e9b0c7f-edca-4841-8fbc-81d4854e0515/image.png' width='320px' alt='Computer Networking a Top-Down Approach: Introduction 1-34' />
  <!--
  <figcaption style='font-size:0.9rem; color:gray; margin-top:-0.5rem'>
    출처: Computer Networking a Top-Down Approach: Introduction 1-34
  </figcaption>
  -->
</figure>

<ul>
<li><strong>FDM</strong>에서 각 회선은 지속적으로 대역폭(주파수 대역의 폭)의 일부를 얻는다.</li>
<li><strong>TDM</strong>에서 각 회선은 짧은 시간 동안(i.e. 슬롯 동안) 주기적으로 전체 대역폭을 얻는다.</li>
</ul>
<h3 id="packet-switching-vs-circuit-switching">Packet Switching vs Circuit Switching</h3>
<p>회선 교환이 요구에 관계없이 전송 link의 사용을 할당하는 반면에 패킷 교환은 요구할 때만 link의 사용을 할당하기 때문에 패킷 교환의 성능이 회선 교환보다 더 우수하다.</p>
<p>전기통신 네트워크에서 추세는 패킷 교환으로 바뀌고 있으며, 오늘날의 많은 회선 교환 전화망이 패킷 교환 방식으로 전환되고 있다.</p>
<h2 id="network-of-networks">Network of Networks</h2>
<p>종단 시스템(PC, 스마트폰, 웹 서버, 메일 서버 등)은 접속 ISP를 통해 인터넷에 연결된다.</p>
<p>접속 ISP는 DSL, 케이블, FTTH, 와이파이, 셀룰러(이동 통신)를 포함하는 다양한 접속 기술을 이용하여 유선 혹은 무선 연결을 제공한다.</p>
<p>이러한 접속 ISP들은 서로 연결되어야 하고, 이를 위해 네트워크의 네트워크(network of networks)가 탄생하게 되었다.</p>
<figure>
  <img src='https://velog.velcdn.com/images/hee-suh/post/2f461371-d03f-4dab-84e4-9e0123ca0a8b/image.png'  width='320px' alt='Computer Networking a Top-Down Approach: Introduction 1-45' />
  <!--
  <figcaption style='font-size:0.9rem; color:gray; margin-top:-0.5rem'>
    출처: Computer Networking a Top-Down Approach: Introduction 1-45
  </figcaption>
  -->
</figure>


<h1 id="네트워크-프로토콜">네트워크 프로토콜</h1>
<p><strong>프로토콜</strong>은 둘 이상의 통신 개체 간에 교환되는 메시지 포맷과 순서뿐 아니라, 메시지의 송수신과 다른 이벤트에 따른 행동들을 정의한다.</p>
<p><em>i.e. 통신하는 둘 이상의 원격 개체가 포함된 인터넷에서의 모든 활동은 프로토콜이 제어한다.</em></p>
<h2 id="네트워크-프로토콜의-종류">네트워크 프로토콜의 종류</h2>
<ul>
<li><p><strong>통신 프로토콜(Communication protocols)</strong>은 <em>TCP/IP</em>나 <em>HTTP</em>와 같은 기본적인 데이터 통신을 포함한다.</p>
</li>
<li><p><strong>관리 프로토콜(Management protocols)</strong>은 <em>ICMP</em>나 <em>SMNP</em>와 같은 프로토콜을 통해 네트워크를 유지 및 관리한다.</p>
</li>
<li><p><strong>보안 프로토콜(Security protocols)</strong>은 <em>HTTPS</em>, <em>SFTP</em>, <em>SSL</em>을 포함한다.</p>
</li>
</ul>
<h2 id="프로토콜-계층화-protocol-layering">프로토콜 계층화 (Protocol Layering)</h2>
<p>네트워크 프로토콜의 설계에 대한 구조를 제공하기 위해, 네트워크 설계자는 프로토콜을 계층으로 조직한다.</p>
<p>계층구조의 장점</p>
<ul>
<li>크고 복잡한 시스템의 잘 정의된 특정 부분을 논의할 수 있게 해 준다.</li>
<li>시스템의 다른 요소에 영향을 주지 않고 서비스 구현을 변경할 수 있다.</li>
</ul>
<p>e.g. TCP/IP Model, OSI Model</p>
<h2 id="tcpip-vs-osi-model">TCP/IP vs OSI Model</h2>
<figure>
  <img src='https://www.researchgate.net/publication/327483011/figure/fig2/AS:668030367436802@1536282259885/The-logical-mapping-between-OSI-basic-reference-model-and-the-TCP-IP-stack.jpg' alt='osi vs tcp/ip' />
  <!--
  <figcaption style='font-size:0.9rem; color:gray; margin-top:-0.5rem'>
    출처: 
    <a href=''>
      https://www.researchgate.net/figure/The-logical-mapping-between-OSI-basic-reference-model-and-the-TCP-IP-stack_fig2_327483011
    </a>
  </figcaption>
  -->
</figure>


<h3 id="공통점">공통점</h3>
<ul>
<li>계층별 역할<ul>
<li>캡슐화, 프로토콜 사용</li>
<li>계층간 역할을 정의</li>
</ul>
</li>
<li>통신 역할<ul>
<li>다중화(multiplexing), 역다중화(demultiplexing)</li>
<li>페이로드 전송</li>
</ul>
</li>
</ul>
<h3 id="차이점">차이점</h3>
<ul>
<li>OSI 모델은 역할을 기반으로 각 계층을 구성하고, TCP/IP 모델을 프로토콜의 집합을 기반으로 구성된다.</li>
<li>전체적인 통신전반에 대한 표준화 방식이 OSI 모델이라면 TCP/IP 모델은 데이터 전송에 특화되어있다.</li>
</ul>
<h1 id="references">References</h1>
<ul>
<li><p><a href="https://gaia.cs.umass.edu/kurose_ross/ppt.php">Jim Kurose Homepage</a></p>
</li>
<li><p><a href="https://product.kyobobook.co.kr/detail/S000061694627">컴퓨터 네트워킹 하향식 접근</a></p>
</li>
<li><p><a href="https://www.researchgate.net/figure/The-logical-mapping-between-OSI-basic-reference-model-and-the-TCP-IP-stack_fig2_327483011">Figure 2. The logical mapping between OSI basic reference model and the...</a></p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>