<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>WhatISYourName?</title>
    <link>https://0xffffffff.tistory.com/</link>
    <description>Github ID: whatisyourname0</description>
    <language>ko</language>
    <pubDate>Mon, 8 Jun 2026 21:53:21 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>whatisyourname</managingEditor>
    <image>
      <title>WhatISYourName?</title>
      <url>https://tistory1.daumcdn.net/tistory/5320898/attach/2527c33d775d4d30833e246052448fd9</url>
      <link>https://0xffffffff.tistory.com</link>
    </image>
    <item>
      <title>[JS / ECMAScript] 동시성과 이벤트 처리 문제에 대한 탐구. - 1. Node.js의 구조, Event Loop의 원리와 구조</title>
      <link>https://0xffffffff.tistory.com/99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;[※ 주의 ※] 아래를 이해하지 않고 이 글을 볼 경우, 이해가 되지 않는 부분이 있을 수 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://0xffffffff.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.01.08 - [Javascript] - [JS / ECMAScript] 비동기 처리를 조금 더 효율적으로 해보자.&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. &lt;/span&gt;&lt;a href=&quot;https://0xffffffff.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.01.26 - [모두보기] - 동시성과 이벤트 처리 문제에 대한 탐구. - 0. GIL vs Proactor/Reactor&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JavaScript는 기본적으로 싱글 쓰레드 언어이다. 즉, 쓰레드를 여러 개 열 수 없고 동시에 여러 개의 일을 처리하려면 새로운 런타임을 생성해야한다는 암시를 가진다. 그러나, Node.js는 I/O 작업이나 커널에 접근해야 하는 작업이 생긴다면 이를 비동기로 처리가 가능하다. 생각해보면 JS는 싱글 쓰레드인데 어떻게 서버로 괜찮은 성능이 나오는지 궁금하지 않는가? 어떻게 이게 가능할까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Event loop에 대하여 자세히 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 어떠한 경우든 JS는 &quot;절대&quot; 쓰레드를 가지지 않는다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;제목을 이해하기 위해서는 Node.js의 구조를 알아야 하고, worker에 대한 개념을 탐구하여야 한다.&lt;/b&gt; Node.js 프로세스가 새로 생성된다는 것은, 다음 의미를 함축하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 프로세스이다.&lt;/li&gt;
&lt;li&gt;하나의 쓰레드이다.&lt;/li&gt;
&lt;li&gt;하나의 Event loop를 가진다.&lt;/li&gt;
&lt;li&gt;하나의 JS 엔진 인스턴스를 가진다.&lt;/li&gt;
&lt;li&gt;하나의 Node.js 인스턴스를 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js는 하나의 프로세스, 싱글 쓰레드이고 컴퓨터는 하나의 JS 엔진 인스턴스를 생성하여 JS 코드를 실행하고, Node.js 인스턴스를 생성하여 Node.js 코드를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여기서 가장 중요한 부분은 하나의 event loop을 생성한다는 부분이다. 하나의 코드는 하나의 event loop를 통해 한 번만 실행되며, 코드는 여러 개의 쓰레드로 돌지 않는다. 처음부터 싱글 쓰레드로 설계되었기도 하고, 브라우저 단에서는 굳이 쓰레드를 여러 개 열어 동작할 이유가 없기 때문이다. 그러나, 이러한 경우 경우 CPU 연산이 많이 필요한 작업을 할 경우, blocking 동작으로 인하여 다른 handler 동작에 영향을 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서, Node.js와 브라우저는 CPU 연산이 많은 blocking 작업을 외부의 쓰레드에 도움을 받기로 하였고, 이들을 worker, worker thread라고 칭한다. 이들은 고유한 자신만의 런타임, event loop를 가지며, 각자만의 JS 엔진 인스턴스를 가진다. 우리가 실행한 코드는 main thread에서 실행되며, 사용자가 worker thead에 비동기 task를 던져주면 thread pool 안의 worker thread가 비동기적으로 처리하여서 main thread에 작업 결과를 반환한다. 자세한 건 밑의 단원에서 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Node.js Architecture&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;사실, Node.js Event loop 관련 여러 잘못된 정보 혹은 누락된 정보가 너무 많이 돌아다닌다. 가령, 다음과 같은 사진들이던가... 이러한 사진들을 힐난하는 건 아니지만, 그래도 오해를 불러일으키기 좋은 사진들이라 생각한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRxdOS/btrXgSEB5b6/qlp4bA6mlYw6c0pjNrm8K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRxdOS/btrXgSEB5b6/qlp4bA6mlYw6c0pjNrm8K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRxdOS/btrXgSEB5b6/qlp4bA6mlYw6c0pjNrm8K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRxdOS%2FbtrXgSEB5b6%2Fqlp4bA6mlYw6c0pjNrm8K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;498&quot; height=&quot;257&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRj4dm/btrXibQXAFs/nIe065scUMFw1kpDo1R7y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRj4dm/btrXibQXAFs/nIe065scUMFw1kpDo1R7y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRj4dm/btrXibQXAFs/nIe065scUMFw1kpDo1R7y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRj4dm%2FbtrXibQXAFs%2FnIe065scUMFw1kpDo1R7y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;512&quot; height=&quot;212&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #000000; color: #ffffff;&quot;&gt;&lt;b&gt;직접 Node.js의 Architecture를 다시 그리면 다음과 같이 그릴 수 있다. 이외에도 crypto, zlib과 같은 여러 컴포넌트들이 존재하지만 중요한 부분만 표시하였다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Node.js Architecture.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIqJEz/btrXgGYzjiu/W4yGTA9uYAXgnXzY9qsRl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIqJEz/btrXgGYzjiu/W4yGTA9uYAXgnXzY9qsRl1/img.png&quot; data-alt=&quot;Node.js의 추상화된 Architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIqJEz/btrXgGYzjiu/W4yGTA9uYAXgnXzY9qsRl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIqJEz%2FbtrXgGYzjiu%2FW4yGTA9uYAXgnXzY9qsRl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;720&quot; data-filename=&quot;Node.js Architecture.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Node.js의 추상화된 Architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JS는 이벤트 처리시 reactor 패턴을 활용한다. Event Queue에 알맞은 이벤트를 쌓은 후, handler에 queue에서 이벤트를 가져가 이를 처리하는 방식을 사용한다. Node.js도 비슷하다. 하나의 스레드로 여러 비동기작업을 non-blocking하게 처리를 한다. 그리고, 그 기저에는 event loop가 활용된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js의 구조는 위와 같이 생겼다. Node.js는 C++로 작성되었고, Node.js 안에는 여러 개의 부품들이 담겨있다. 먼저, 크롬으로 인하여 알려진 엔진 V8이 담겨있고, C / C++로 작성된 코드와 JS간의 인터페이스과 같은 여러 인터페이스를 제공해주는 Node.js Bindings(Node API), 그리고 &lt;b&gt;비동기&lt;/b&gt; I/O 처리를 도와주는 libuv 라이브러리, 하나의 main event loop, 그리고 worker thread가 존재한다. Main event loop 안에는 event queue와 여러 개의 queue가 담겨있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1) JS code &amp;amp; V8 engine&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js에서는 사용자가 작성한 코드를 V8 JS엔진을 활용하여 컴파일한다. V8 엔진은 현재 크롬 브라우저, Node.js의 핵심 JS 엔진이며, 이번 글은 V8이 주제가 아니기에 V8 엔진의 low-level 부분은 탐구하지 않겠다. 다만, V8 엔진은 우리가 아는 call stack, memory heap와 관련된 곳이며, 바이트코드 생성 등 JS 코드 컴파일 관련 부분을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-2) libuv&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp60kV/btrXc8uIEMG/msUqQDaXkG08vEkqN6Kjj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp60kV/btrXc8uIEMG/msUqQDaXkG08vEkqN6Kjj1/img.png&quot; data-alt=&quot;libuv의 추상화된 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp60kV/btrXc8uIEMG/msUqQDaXkG08vEkqN6Kjj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp60kV%2FbtrXc8uIEMG%2FmsUqQDaXkG08vEkqN6Kjj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1020&quot; height=&quot;493&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;libuv의 추상화된 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 중 우리가 유심히 지켜볼만한 곳은 libuv이다. 다시 말하지만, &lt;b&gt;libuv는 비동기 I/O 처리를 도와주는 라이브러리이자 인터페이스&lt;/b&gt;이다. 즉 여러 종류의 I/O 를 도와주는 라이브러리이고, 쉽게 말해 I/O 관련 커널 함수들을 추상화해놓은 라이브러리라 생각하면 된다. 구체적으로 파일 시스템에 접근하거나, epoll, kqueue, IOCP에 접근하거나, TCP/UDP와 같은 네트워크 관련 I/O, IPC, DNS 접근, signal handling과 같은 작업 모두 libuv를 통해 쉽게 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Node.js는 비동기 I/O 관련 항목들은 libuv를 통해 작업한다.&lt;/b&gt; libuv를 활용할 수 있으면 최대한 libuv의 도움을 받아 libuv가 대신 비동기 작업을 진행한다. libuv는 해당 작업의 종류를 확인한 다음, 커널이 지원하는 비동기 I/O 작업일 경우 커널에 해당 작업을 커널이 하도록 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;만약&lt;/b&gt; &lt;b&gt;도착한 작업이&lt;/b&gt;&lt;b&gt; file system 관련 작업이거나, DNS 관련 요청이거나, 사용자가 직접 쓰레드 풀에 작업하도록 요청하거나, CPU-bound 코드(ex. crypto)이면&lt;/b&gt;&amp;nbsp;&lt;b&gt;libuv는 대신 내부 쓰레드 풀에 해당 작업을 해달라 요청한다. 이들은 non-pollable하거나,  event loop의 동작을 막을 수 있는 가능성이 있는 작업들이기 때문이다. &lt;/b&gt;해당 작업이 마무리 될 경우 쓰레드 풀에서 작업이 되었다면 작동한 쓰레드가 libuv에게 콜백을 통해 작업을 완료했음을 알리고, libuv는 Node.js에게 알린다. Asynchronous 하고 Non-blocking 한 방법을 활용했으며, event loop의 동작과도 잘 맞아 떨어지는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쓰레드 풀은 uv_io라는 이름으로 제공되며, 기본적으로 4개의 worker thread를 가지고 있다. 다만, Node.js를 처음 시작할 때 최대 1024개까지 생성이 가능해진다. (최신 버젼 기준 1024개까지 생성이 가능하고, 1.30.0 버젼 이하일 경우 128개까지 생성이 가능하다.).이 쓰레드 풀은 모든 event loop에 거쳐 공유되는 전역 worker이며, 만약 특정 함수가 쓰레드 풀을 활용하면 메모리를 최대한으로 사용하여 쓰레드를 한도까지 최대한 많이 생성하여 성능을 극대화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쓰레드 풀에 요청할 때 uv_queue_work 함수 API를 활용해 event loop에 대응되는 쓰레드 풀에 request를 보낼 수 있으며 , 이후 worker thread가 queue에서 작업을 꺼내 request type과 작업의 내용을 보내고, 이후 콜백을 통해 해당 작업을 한 worker한테 결과를 받을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-3) The Event Loop&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wSWr8/btrXhmZJpiU/m3xsjQFhkqSYHf2NHUXY10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wSWr8/btrXhmZJpiU/m3xsjQFhkqSYHf2NHUXY10/img.png&quot; data-alt=&quot;libuv의 Event Loop Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wSWr8/btrXhmZJpiU/m3xsjQFhkqSYHf2NHUXY10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwSWr8%2FbtrXhmZJpiU%2Fm3xsjQFhkqSYHf2NHUXY10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;372&quot; height=&quot;527&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;libuv의 Event Loop Diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;싱글 스레드를 채택한 언어는 하나의 스레드 안에 여러 이벤트에 대한 핸들링을 해야하기에, 여러 이벤트 handling을 round robin 방법으로 처리한다. 즉, 여러 개의 handler queue를 만들고, 해당 차례일 때 handler queue를 하나씩 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저, Node.js를 실행하면 Node.js 어플리케이션은 초기화를 진행하고, 모듈을 불러오고 이벤트 콜백 핸들러를 등록하면서 main event loop을 생성한다. 이후 JS 코드가 올라가면 V8 엔진에서 해당 JS 코드를 실행하고, 비동기 I/O 처리 코드 혹은 콜백 관련 비동기 요청을 만나면 뒷 단의 libuv 안에 event loop에 등록한다. &lt;b&gt;같은 방법으로 모든 JS 코드를 끝까지 실행시킨다. 다시 말해 일단 모든 JS 코드를 본 이후 event loop를 처리한다는 의미이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674750880695&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let a = 1;
let b = 2;
let c = a+b;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 위 코드 같이 아무 이벤트도 event loop안에 포함되지 않는다면, Node.js는 event loop을 없애 그대로 런타임을 종료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674752109591&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fs = require('fs');

function someAsyncOperation(callback) {
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() =&amp;gt; {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);

setImmediate(() =&amp;gt; {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback &amp;lt; 10) {
    // do nothing
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 HTTP 요청을 하거나, setTimeout과 같은 timer 관련 함수를 호출하거나, process.nextTick()과 같은 함수들이 실행되었다면, event loop안의 queue안에 카테고리에 맞게 안에 등록이 된 다음, event loop가 시작된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIxuB1/btrXhO2NJ23/EjAd5uGWTr5lY1eAsi0Fu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIxuB1/btrXhO2NJ23/EjAd5uGWTr5lY1eAsi0Fu0/img.png&quot; data-alt=&quot;Node.js의 Event Loop 실행 순서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIxuB1/btrXhO2NJ23/EjAd5uGWTr5lY1eAsi0Fu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIxuB1%2FbtrXhO2NJ23%2FEjAd5uGWTr5lY1eAsi0Fu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;694&quot; height=&quot;440&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Node.js의 Event Loop 실행 순서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Event Loop 안에는 5종류의 queue가 존재한다. 또한, 각 queue는 자신의 차례가 되었을 경우에 실행되며, 순서는 위 사진과 같은 순서를 따른다. 구체적으로 timer queue -&amp;gt; pending callbacks queue -&amp;gt; (idle, prepare) -&amp;gt; poll queue -&amp;gt; check queue -&amp;gt; close callbacks queue -&amp;gt; timer queue ... 와 같이 보통&amp;nbsp;&lt;b&gt;무한히&lt;/b&gt; 돌아가는 round robin 형식을 채택한다. &lt;b&gt;또한, 다음 queue로 넘어가는 것을 하나의 Tick이 흘렀다고 표현한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;다만, OS 별로 구현되어 있는 방법이 미묘하게 살짝 다르고, 여기에 작성되지 않은 다른 단계들이 있지만 중요하지 않기에 Node.js 측도 무시하라고 한다. 실제로는 7~8단계를 거친다고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674883743062&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  int timeout;
  int r;
  int can_sleep;

  r = uv__loop_alive(loop); // check if loop is alive
  if (!r) uv__update_time(loop); // if loop is alive, update time

  while (r != 0 &amp;amp;&amp;amp; loop-&amp;gt;stop_flag == 0) { // infinite loop until event loop stops
    uv__update_time(loop); // update timers
    uv__run_timers(loop); // timer phase

    can_sleep = QUEUE_EMPTY(&amp;amp;loop-&amp;gt;pending_queue) &amp;amp;&amp;amp; QUEUE_EMPTY(&amp;amp;loop-&amp;gt;idle_handles);

    uv__run_pending(loop); // Pending Callbacks phase
    uv__run_idle(loop); // Idle Phase
    uv__run_prepare(loop); // Prepare Phase

    timeout = 0;
    if ((mode == UV_RUN_ONCE &amp;amp;&amp;amp; can_sleep) || mode == UV_RUN_DEFAULT)
      timeout = uv__backend_timeout(loop);

    // ...

    uv__io_poll(loop, timeout); // Poll phase

    for (r = 0; r &amp;lt; 8 &amp;amp;&amp;amp; !QUEUE_EMPTY(&amp;amp;loop-&amp;gt;pending_queue); r++)
      uv__run_pending(loop); // Do immediate callbacks to prevent event loop starvation.

    uv__metrics_update_idle_time(loop); // ahandle IO poll related edge cases.

    uv__run_check(loop); // Check Phase
    uv__run_closing_handles(loop); // Close Callbacks phase.

    if (mode == UV_RUN_ONCE) { // if loop runs once, handle timers
      uv__update_time(loop);
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop); // check loop is alive
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

  if (loop-&amp;gt;stop_flag != 0) // if stop_flag is set, unset flag, making GCC cache happy.
    loop-&amp;gt;stop_flag = 0;

  return r;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제 event loop을 굴리는 함수인 uv_run 함수 코드를 살펴보면, 공식 문서대로의 phase 순서대로 함수 순서가 정해져 있고, 코드를 round robin 형태로 처리하는 것을 살펴볼 수 있다. 또한 중간에 poll phase에 작은 pending callback phase가 존재하는 것을 살펴볼 수 있고, 빠른 시간 내로 처리할 수 있는 콜백 함수들만 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js는 각 queue를 처리할 때 FIFO(First in, First Out) 방식을 사용한다. 먼저 등록된 작업이 먼저 처리된다. Queue 안에 있는 모든 작업이 처리되거나, 일정 threshold가 끝나거나, queue에 작업이 너무 많이 쌓여 있다면 다음 queue로 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-3-1) Timer Phase&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Timer phase는 Node.js에서 첫 번째 phase로, 시간 관련 함수들의 콜백을 실행할 지를 처리하거나 낮은 확률로 콜백을 처리하는 phase&lt;/b&gt;이다. Node.js에서 제공하는 시간 함수는 setTimeout, setInterval과 같은 작업들이며, 해당 작업들에 대한 콜백을 poll queue에 넣을 지 event loop이 확인한다. 만약 이례적인 상황이 발생한다면 poll phase에서 처리하지 않고 직접 timer phase에서 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;libuv는 uv_timer 관련 API 및 여러 type들을 제공하며, 실제 uv_run 함수에서 timer phase와 관련된 함수는 uv__update_time과 uv__run_timers이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;uv__update_time는 시간 단위를 변한하는 코드로, 시간 단위를 ms 단위로 통일시켜주는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제 uv__update_time은 다음 코드로 구현되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1674884553295&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UV_UNUSED(static void uv__update_time(uv_loop_t* loop)) {
  /* Use a fast time source if available.  We only need millisecond precision.
   */
  loop-&amp;gt;time = uv__hrtime(UV_CLOCK_FAST) / 1000000;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;uv__hrtime에는 Unix인지 Windows인지에 따라 다르게 구현되어 있는 OS-depenedent 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;두 번째 함수는 uv__run_timers로, 다음과 같은 코드로 이루어져 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1674885426956&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void uv__run_timers(uv_loop_t* loop) {
  struct heap_node* heap_node;
  uv_timer_t* handle;

  for (;;) { // infinite loop
    heap_node = heap_min(timer_heap(loop)); // get timer from min_heap
    if (heap_node == NULL) // if no timer in heap, break the loop
      break;

    handle = container_of(heap_node, uv_timer_t, heap_node);
    if (handle-&amp;gt;timeout &amp;gt; loop-&amp;gt;time) // if timer exceeds threshold, break the loop.
      break;

    uv_timer_stop(handle); // remove the timer from heap.
    uv_timer_again(handle); // handle repeat case.
    handle-&amp;gt;timer_cb(handle); // register callback to poll phase
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;흥미로운 부분은 heap_node를 꺼내오는 부분으로, timer 관련으로 등록된 함수들은 min-heap 자료구조로 저장되어 있는 것을 확인할 수 있다. min-heap 구조는 O(logN) 시간복잡도를 가지기에, 빠른 실행을 보장하기 위한 자료구조라 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;timer_heap안에서 등록된 timer을 꺼낼 때, 해당 delay가 가장 작은 timer를 가장 꺼낸다. 가령 setTimeout(cb, 100)이라는 함수를 호출함에 따라 timer에 넣는다면, 세 가지 변수에 의해 해당 콜백을 동작할 지 안 할지 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;currentTime (현재 시간)&lt;/li&gt;
&lt;li&gt;registeredTime (등록된 시간)&lt;/li&gt;
&lt;li&gt;delay(지연 시간), 여기서는 100ms이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이때, heap_node 안의 struct를 통해 registeredTime과 delay정보를 얻고, 현재 시간 current Time을 비교하여 현재 콜백을 등록할 수 있는지 if 문을 통해 작성한다. 즉, if문을 다음과 같은 쉽게 알아볼 수 있게 바꾸어 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674886348952&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (registeredTime + delay &amp;gt; currentTime) {
  break;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, registeredTime + delay가 currentTime보다 작으면 해당 timer를 poll phase에 콜백으로 등록할 수 있다는 의미이다. 이 경우 timer를 업데이트 하며, Timer phase에 머무르게 된다. 다만, Node.js와 OS에 따른 hard limit으로 인하여 다음 phase로 넘어갈 수 있고, 정확한 실행 시간은 보장할 수 없다. setTimeout의 delay를 100으로 설정하여도, 100ms 뒤에 실행된다는 보장을 할 수 없는 일. 보장 할 수 있는 것은 &lt;b&gt;적어도&lt;/b&gt; 100ms 뒤에 실행할 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-3-2) Pending Callbacks Phase&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;Pending callbacks phase는 이전 event loop iteration에서 처리하지 못한 I/O 콜백들을 수행한다. 만약 OS-dependent hard limit로 인하여 처리하지 못한 콜백들을 처리하는 곳이다. 실제 libuv의 코드에선 uv__run_pending으로 구현되어 있으며 주석을 달면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1674888185098&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void uv__run_pending(uv_loop_t* loop) {
  QUEUE* q;
  QUEUE pq;
  uv__io_t* w;

  QUEUE_MOVE(&amp;amp;loop-&amp;gt;pending_queue, &amp;amp;pq); // move pending callbacks to queue pq.

  while (!QUEUE_EMPTY(&amp;amp;pq)) { // while queue is not empty
    q = QUEUE_HEAD(&amp;amp;pq); // get head item of queue.
    QUEUE_REMOVE(q); // remove item from queue
    QUEUE_INIT(q); // cleanup relations from queue.
    w = QUEUE_DATA(q, uv__io_t, pending_queue); // pass item to worker thread queue
    w-&amp;gt;cb(loop, w, POLLOUT); // register callback
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;loop-&amp;gt;pending_queue에서 이전에 처리못한 작업들을 가져오며, 이를 임시로 Queue 구조 pq에 담고 pq가 빌때까지 하나씩 worker thread에 던져준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-3-3) Idle &amp;amp; Prepare Phase&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Idle &amp;amp; Prepare phase는 아무런 JS 코드를 실행하지 않는다. Node.js도 내부적으로만 사용한다. 다만, idle이라는 이름답지않게 idle callback과 prepare callback을 실행한다. 쉽게 말해 시스템에게 idle 상태를 피드백한다고 할 수 있다. 이 phase는 또한 process.nextTick과 같은 함수에 영향을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-3-4) Poll Phase&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co2FeB/btrXn8NNHj5/Fpp91872fNLb13FQFhKeH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co2FeB/btrXn8NNHj5/Fpp91872fNLb13FQFhKeH1/img.png&quot; data-alt=&quot;poll phase의 여러 가지 구현&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co2FeB/btrXn8NNHj5/Fpp91872fNLb13FQFhKeH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco2FeB%2FbtrXn8NNHj5%2FFpp91872fNLb13FQFhKeH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;338&quot; height=&quot;385&quot; data-origin-width=&quot;472&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;poll phase의 여러 가지 구현&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Poll phase는&lt;b&gt; poll queue에 담긴 작업들을 처리하고, I/O blocking, polling 관련 작업들을 처리&lt;/b&gt;한다. 이벤트 루프가 poll phase에 들어온다면, I/O 관련 입력들을 다룬다. uv__io_poll이라는 함수를 활용하며, OS에 따라 같은 이름의 여러 종류의 API를 구현해놓았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;공통적으로 watcher_queue 이름을 가진 queue 자료구조를 사용하여 I/O 관련 콜백들을 모두 저장한다. 이 phase에 진입할 때다른 phase queue에 저장되는 timer 관련 함수, close 관련 함수를 제외하면 모두 poll queue에 저장되며, 대부분의 시간이 여기에 할당되고 hard limit도 다른 phase에 비해 널널하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 경우 I/O를 사용하기에 OS에서 제공하는 소켓을 사용한다. 각 소켓에 대한 정보와 연결된 file descriptor를 가진 watcher가 watcher_queue에 저장되어 있다. 이후 fd의 return 값을 통해 파일 정보에 관한 signal을 보내고, event loop는 watcher를 통해 각 fd 배열마다 for문으로 순회하여 fd의 결과 및 signal watcher에 따라 알맞게 핸들링을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js에서의 poll phase는 다른 phase과 다르게, 단순하게 동작하지 않는다. 몇 가지에 따라 분기점이 결정되어, 해당 phase에서 대기할 지 다음 phase로 넘어갈지 결정한다. 이때 대기시간은 timeout이라는 변수에 담기며, 여러 경우에 따라 timeout의 값을 직접 변경한다. &lt;b&gt;정말 많은 분기점으로 결정한다&lt;/b&gt;. (&lt;a href=&quot;https://github.com/libuv/libuv/blob/v1.x/src/unix/linux.c#L300&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;300줄 부터 Linux에서 사용하는, epoll과의 통신은 다음github 링크와 같다.&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;간단히 요약하면, 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 루프 종료 시 다음 phase로 이동&lt;/li&gt;
&lt;li&gt;close callbacks phase, pending callbacks phase queue에 작업이 있다면 다음 phase로 이동&lt;/li&gt;
&lt;li&gt;Timer phase에 즉시 실행할 수 있는 타이머가 있다면 바로 다음 phase로 이동.&lt;/li&gt;
&lt;li&gt;Timer phase에 n초 이후 실행 가능 타이머가 있다면 n초 후 다음 phase로 이동.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;timeout 이름에 의하여 오해할 수 있는 부분은 대기 시간이 아닌, I/O block 시간이다. 싱글쓰레드 환경이기에 이벤트를 처리하는 동안 I/O 요청을 막아야 하며, fd를 처리한다. linux의 epoll의 경우 timeout 값에 따라 반환값이 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;timeout = 0일 경우 완료된 이벤트의 수를 반환한다.&lt;/li&gt;
&lt;li&gt;timeout &amp;gt; 0일 경우 완료된 I/O 요청없다면 요청이 생길때까지 block하고, I/O 요청이 완료되거나 모든 I/O 요청이 완료되지 않고 timeout이 지나면 완료된 이벤트의 수를 반환한다.&lt;/li&gt;
&lt;li&gt;timeout &amp;lt; 0일 경우 I/O 요청이 완료될 때까지 대기한다. epoll의 경우 max_safe_time의 값은 1789569이고, 최대 30분이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이외에도 OS, file descriptor에 따라 구현 방법이 다르며, 세부 구현은 각 함수에 따라 확인해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-3-5) Check Phase&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Check phase는 setImmediate 전용 queue이다. setImmediate의 경우 poll queue가 비어있고 setImmediate 콜백을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js 공식 문서에서 process.nextTick과 setImmediate을 활용한 콜백을 비교하는데, 두 차이는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;process.nextTick의 경우 즉시 실행된다.&lt;/li&gt;
&lt;li&gt;setImmediate는 여러 틱에 거쳐 Check Phase에 돌아오면 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;uv__run_check라는 함수를 통해 구현되어 있다. libuv 안에는 uv__run_check 함수가 따로 생성되어 있지 않고, loop-watcher.c라는 파일에서 생성해서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2-3-6) Close Callbacks Phase&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Close Callbacks Phase는 close 관련 작업을 처리하는 phase이다. 예를 들어 소켓 close나 handle close 관련 작업들이다. 실제 코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1674927118940&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void uv__run_closing_handles(uv_loop_t* loop) {
  uv_handle_t* p;
  uv_handle_t* q;

  p = loop-&amp;gt;closing_handles;
  loop-&amp;gt;closing_handles = NULL;

  while (p) {
    q = p-&amp;gt;next_closing;
    uv__finish_close(p);
    p = q;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하나의 linked list 형태로 이루어져있다고 추정할 수 있고, head부터 순회하며 close를 uv__finish_close 함수로 처리하는 것을 할 수 있다. uv__finish_close 함수는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1674927414669&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void uv__finish_close(uv_handle_t* handle) {
  uv_signal_t* sh;

  assert(handle-&amp;gt;flags &amp;amp; UV_HANDLE_CLOSING);
  assert(!(handle-&amp;gt;flags &amp;amp; UV_HANDLE_CLOSED));
  handle-&amp;gt;flags |= UV_HANDLE_CLOSED;

  switch (handle-&amp;gt;type) {
    case UV_PREPARE:
    case UV_CHECK:
    case UV_IDLE:
    case UV_ASYNC:
    case UV_TIMER:
    case UV_PROCESS:
    case UV_FS_EVENT:
    case UV_FS_POLL:
    case UV_POLL:
      break;

    case UV_SIGNAL:
      sh = (uv_signal_t*) handle;
      if (sh-&amp;gt;caught_signals &amp;gt; sh-&amp;gt;dispatched_signals) {
        handle-&amp;gt;flags ^= UV_HANDLE_CLOSED;
        uv__make_close_pending(handle);  /* Back into the queue. */
        return;
      }
      break;

    case UV_NAMED_PIPE:
    case UV_TCP:
    case UV_TTY:
      uv__stream_destroy((uv_stream_t*)handle);
      break;

    case UV_UDP:
      uv__udp_finish_close((uv_udp_t*)handle);
      break;

    default:
      assert(0);
      break;
  }

  uv__handle_unref(handle);
  QUEUE_REMOVE(&amp;amp;handle-&amp;gt;handle_queue);

  if (handle-&amp;gt;close_cb) {
    handle-&amp;gt;close_cb(handle);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에 코드를 살펴보면 case에 따라 close 관련 적절하게 처리하는 것을 볼 수 있다. 또한, 마지막에 close 콜백이 있다면 이에 관하여 처리하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Event loop는 위와 같은 phase를 반복하며 작업을 계속 처리하고 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-4) MicroTaskQueue, NextTickQueue&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js는 위의 queue 이외에서 event loop가 아닌 다른 message queue들을 가지고 있고, 두 개의 message queue인 MicroTaskQueue, NextTickQueue가 존재한다. 두 queue는 libuv에 포함되어 있지 않고, &lt;b&gt;MicroTaskQueue는 resolved된 프로미스, NextTickQueue는 process.nextTick의 콜백&lt;/b&gt;을 가지고 있다. nextTickQueue와 microTaskQueue는 event loop의 phase와 상관 없이 수행 중인 작업이 끝나자마자 call stack로 넣는다. Node.js v11.0.0 이전 버전에는 phase 변화가 생길 때 nextTickQueue, microTaskQueue를 확인하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;두 queue는 OS hard limit의 제한을 받지 않기에 이 queue가 비워질때까지 순차적으로 처리하며, nextTickQueue 안의 작업들이 더 우선순위가 높이 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;V8 엔진에서 microTaskQueue에 담긴 작업들을 실행하는 함수인 RunMicrotasks는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674930806603&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int MicrotaskQueue::RunMicrotasks(Isolate* isolate) {

  if (!size()) {
    OnCompleted(isolate);
    return 0;
  }
  
  intptr_t base_count = finished_microtask_count_;
  HandleScope handle_scope(isolate);
  MaybeHandle&amp;lt;Object&amp;gt; maybe_exception;
  MaybeHandle&amp;lt;Object&amp;gt; maybe_result;
  int processed_microtask_count;
  {
    SetIsRunningMicrotasks scope(&amp;amp;is_running_microtasks_);
    v8::Isolate::SuppressMicrotaskExecutionScope suppress(
        reinterpret_cast&amp;lt;v8::Isolate*&amp;gt;(isolate));
    HandleScopeImplementer::EnteredContextRewindScope rewind_scope(
        isolate-&amp;gt;handle_scope_implementer());
    TRACE_EVENT_BEGIN0(&quot;v8.execute&quot;, &quot;RunMicrotasks&quot;);
    TRACE_EVENT_CALL_STATS_SCOPED(isolate, &quot;v8&quot;, &quot;V8.RunMicrotasks&quot;);
    maybe_result = Execution::TryRunMicrotasks(isolate, this, &amp;amp;maybe_exception);
    processed_microtask_count =
        static_cast&amp;lt;int&amp;gt;(finished_microtask_count_ - base_count);
    TRACE_EVENT_END1(&quot;v8.execute&quot;, &quot;RunMicrotasks&quot;, &quot;microtask_count&quot;,
                     processed_microtask_count);
  }
  // If execution is terminating, clean up and propagate that to TryCatch scope.
  if (maybe_result.is_null() &amp;amp;&amp;amp; maybe_exception.is_null()) {
    delete[] ring_buffer_;
    ring_buffer_ = nullptr;
    capacity_ = 0;
    size_ = 0;
    start_ = 0;
    isolate-&amp;gt;SetTerminationOnExternalTryCatch();
    OnCompleted(isolate);
    return -1;
  }
  DCHECK_EQ(0, size());
  OnCompleted(isolate);
  return processed_microtask_count;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;코드 가운데 Execution::TryRunMicrotasks를 통해 실제 작업을 수행하여, 이후에 작업한 microtask를 반환하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 대표적인 오해들&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js의 Event loop에 대한 잘못된 정보가 널리 퍼져있고, 오해하기 쉬운 부분들이 많다보니 따로 정리하였다. 오개념 잡기 스타트.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) Event loop는 libuv만의 특별한 기능이다. &amp;amp; 모든 event loop는 같은 꼴을 띤다. &amp;amp; Event loop는 JS 엔진 내부에 존재한다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Event loop는 싱글쓰레드 언어와 궁합이 잘 맞는, 하나의 프로그래밍 패러다임이며 구현 방법일 뿐이다. &lt;/b&gt;V8 엔진, libuv 모두 event loop가 구현이 되어있다. V8 엔진의 경우 event loop를 사용하며, 이는 브라우저 내에서의 비동기 I/O 처리 스케쥴링때 사용한다. &lt;b&gt;Node.js의 경우 libuv가 제공하는 event loop을 사용할 뿐이다.&lt;/b&gt; 어떻게 event loop를 구현했는지는 차이가 존재한다. 당장 V8 엔진의 event loop와 libuv의 event loop에서 phase 종류에도 차이가 존재한다. Webkit 엔진의 event loop와도 차이가 있다. 헷갈리지 말자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) Event loop는 JS의 코드와 별도로 작동하는, 새로운 쓰레드이다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;JS는 어떠한 경우든 싱글쓰레드이며, 단일 쓰레드에서 event loop가 작동한다. 세상에 싱글쓰레드이면서 멀티쓰레드인 프로그래밍 언어는 없다. 멀티쓰레드인 것처럼 착시현상을 일으키는 것이다. 이 착시현상이 가능케 하는 방법이 event loop이다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음과 같은 코드를 Node.js에서 실행하면 어떤 결과가 나오는지 관찰을 하여보아라.&lt;/p&gt;
&lt;pre id=&quot;code_1674931883392&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setTimeout(() =&amp;gt; {
  console.log(&quot;runs in separate thread?&quot;);
 }, 1000);
 
 while (true) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 event loop가 별개의 thread에서 작동한다면 console.log가 작동이 되겠지만, 실제로는 무한루프에 의하여 아무것도 출력이 되지 않는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;쓰레드가 여러 개 라는 것은 JS가 아닌, Node.js 내부에 프레임워크와 라이브러리, 컴포넌트의 이야기이다. &lt;/b&gt;용어와 맥락을 정확히 파악하자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) Timer는 OS API를&amp;nbsp; 활용한다. &amp;amp; setTimeout, setInterval은 정해진 delay에 실행 / 반복실행된다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Timer들은 min heap 자료구조로 libuv 안에서 관리된다. &lt;/b&gt; 따라서 setTimeout, setInterval은 정확한 시간에 작동하지 않는다. 이에 대한 원리와 설명은 &quot;Timer Phase&quot; 부분을 읽길 바란다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) 모든 비동기 I/O 작업은 Thread pool에서 처리한다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Node.js는 비동기 I/O 관련 항목들은 libuv를 통해 작업한다.&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;libuv를 활용할 수 있으면 최대한 libuv의 도움을 받아 libuv가 대신 비동기 작업을 진행한다.&lt;span&gt; &lt;b&gt;만약&lt;/b&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;도착한 작업이&lt;/b&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;file system 관련 작업이거나, DNS 관련 요청이거나, 사용자가 직접 쓰레드 풀에 작업하도록 요청하거나, CPU-intensive 코드이면&lt;/b&gt;&amp;nbsp;&lt;b&gt;libuv는 대신 내부 쓰레드 풀에 해당 작업을 해달라 요청한다. 쉽게 말해 blocking intensive(I/O-intensive, CPU-intensive) 작업은 event loop을 막을 가능성이 크기에 쓰레드 풀에 넘기는 것이다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5) libuv의 Thread Pool은 Node.js의 worker_threads 모듈이다. &amp;amp; Node.js의 worker_threads모듈로 쓰레드를 추가하면 libuv의 thread pool이 증가하는 것이다. &amp;amp; Node.js에는 비동기 작업을 관리하는 별도의 쓰레드 풀이 있다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;둘은 별개이다. libuv의 thread pool은 libuv 내부에서 사용하는 쓰레드이며, Node.js가 기본으로 제공하는 worker_threads 모듈을 활용한 멀티쓰레딩은 Node.js 런타임을 새로 만드는 것이다.&amp;nbsp;&lt;/b&gt; 다음 그림을 통해 어떤 차이가 있는지 이해하여보자. worker_threads 모듈로 새로운 스레드를 만드는 것은 런타임을 여러 개 생성하고, 각자만의 V8 엔진과 libuv를 가진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kQ0II/btrXnIowONU/EqyGzBk2C1yrR23kApGgbK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kQ0II/btrXnIowONU/EqyGzBk2C1yrR23kApGgbK/img.jpg&quot; data-alt=&quot;Worker Threads&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kQ0II/btrXnIowONU/EqyGzBk2C1yrR23kApGgbK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkQ0II%2FbtrXnIowONU%2FEqyGzBk2C1yrR23kApGgbK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;682&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Worker Threads&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다만, Node.js에서 칭하는 쓰레드 풀은 libuv 내부의 쓰레드 풀이다. 헷갈리지 말자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6) Event loop는 자신만의 call stack, memory heap을 가진다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Call stack과 memory heap은 V8 엔진에 존재하며, event loop는 그 어떤 것도 가지고 있지 않고 스케쥴러에 가깝다. 다만 스케쥴링 정책이 round robin으로 고정되어있다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7) Event loop는 스택, 큐와 같은 자료구조이다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Event loop 내부에는 여러 개의 메시지 큐를 사용하지만, 하나의 자료 구조가 아닌 phase의 집합과 동작이라고 생각하는 편이 맞다. &lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8) Event loop로 인하여 실행되는 콜백 큐는 FIFO이므로, 실행 순서를 보장할 수 있다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;실행 순서는 큐의 순서에 맞추어 FIFO로 실행되지만, 언제 큐에 등록되느냐에 따라 실행 순서가 보장되지 않는다.&amp;nbsp;&lt;/b&gt;비동기 처리 방식이므로, 언제 큐에 등록되는지는 시스템과 같은 여러 변수들에 의해 달라질 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9) Event loop안에 microTaskQueue, nextTickQueue가 존재한다.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;둘의 개념은 V8엔진으로부터 왔고, event loop와는 상관이 없다. 다만, event loop의 작업을 한 번 끝내고(tick 포함), 이 들을 처리한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/99</guid>
      <comments>https://0xffffffff.tistory.com/99#entry99comment</comments>
      <pubDate>Fri, 27 Jan 2023 02:04:53 +0900</pubDate>
    </item>
    <item>
      <title>동시성과 이벤트 처리 문제에 대한 탐구. - 0. GIL vs Proactor/Reactor</title>
      <link>https://0xffffffff.tistory.com/98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;[※ 주의 ※] 아래를 이해하지 않고 이 글을 볼 경우, 이해가 되지 않는 부분이 있을 수 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://0xffffffff.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.01.08 - [Javascript] - [JS / ECMAScript] 비동기 처리를 조금 더 효율적으로 해보자.&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;자바스크립트는 싱글 쓰레드 언어이다. 따라서, 쓰레드를 새로 만든다건가, 이벤트를 처리할 때 한 번에 단 한번의 이벤트만 처리할 수 있다. 그러나, 현대 언어는 이벤트 핸들링을 위하여 여러 가지 방법 및 디자인 패턴을 발전시켰고, 이를 알기 위해서는 여러 사전지식을 갖추어야 한다. Node.js나 Dart / Flutter에서 사용하는 event loop, 파이썬에서 활용하는 Global Interpreter Lock 등도 여러 배경 속에서 탄생한 이벤트 처리 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이벤트 처리 문제를 알기 전, 먼저 두 가지 중요한 디자인 패턴에 대하여 알아보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 이벤트 핸들링 = 동시성 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이벤트를 처리하거나 핸들링 해야 한다는 것은 결국 동시성 문제와 연결된다. OS의 기저에 깔린 철학 중 하나가 Event-driven handling이기 때문에, 다른 일을 하고 있는 OS가 이벤트를 만나면 기존에 진행중인 작업과 새로 들어온 작업(이벤트)를 어떻게 처리해야 하는 지 정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;기존의 언어들은 멀티쓰레드를 활용하고, C 또한 이벤트 핸들링을 보통 새로운 쓰레드를 만들어 해결한다. 그러나, 멀티쓰레드는 기본적으로 동기화 문제가 까다롭다는 단점도 있고, 이를 위하여 뮤텍스, 세마포어 등 여러 자료구조를 활용하지만 CPU의 자원을 많이 먹기에 CPU-bound 작업을 하면 안 쓰느니만 못할 수 있다는 단점이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이를 위하여 여러 현대 스크립트 언어는 다양한 방법을 차용하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p4mDV/btrXgqVa08K/wRK2AezZnCE1cRGIszeXUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p4mDV/btrXgqVa08K/wRK2AezZnCE1cRGIszeXUk/img.png&quot; data-alt=&quot;파이썬의 GIL 동작 원리 1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p4mDV/btrXgqVa08K/wRK2AezZnCE1cRGIszeXUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp4mDV%2FbtrXgqVa08K%2FwRK2AezZnCE1cRGIszeXUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;371&quot; height=&quot;401&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;파이썬의 GIL 동작 원리 1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JSQfi/btrXa1WAkCA/GcIRuojn9G0kSzcWZcjSGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JSQfi/btrXa1WAkCA/GcIRuojn9G0kSzcWZcjSGK/img.png&quot; data-alt=&quot;파이썬의 GIL 동작 원리 2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JSQfi/btrXa1WAkCA/GcIRuojn9G0kSzcWZcjSGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJSQfi%2FbtrXa1WAkCA%2FGcIRuojn9G0kSzcWZcjSGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;213&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;파이썬의 GIL 동작 원리 2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저,&lt;b&gt; Python의 경우 전역 인터프리터 잠금(GIL)&lt;/b&gt;을 도입하였다. 파이썬의 경우도 여러 개의 쓰레드를 만들 수 있다. 다만, 기존의 멀티쓰레드 방식과 다르게 &lt;b&gt;쓰레드 자체에 binary semaphore(mutex 아님)를 도입&lt;/b&gt;하여, 하나의 연속된 시간 속에 하나의 쓰레드만 실행되게 작동하도록 정하였다. 마치 코루틴을 실행하는 것처럼 말이다. 만약 쓰레드 하나가 I/O 작업을 커널에 요청하거나, CPU-intensive 작업을 해야하거나, timer interrupt가 발생한다면 GIL을 반납하고, 다른 쓰레드가 실행되도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 작업은 I/O 작업에는 여러 이득을 볼 수 있다. 다만, CPU-bound 작업을 처리하면 오히려 느려진다는 단점이 있고, 멀티프로세스보다 멀티쓰레드가 더 느려지는 경우가 여럿 발생하였다. 이러한 문제는 C 라이브러리들과의 바인딩을 통해 해결한다. 다만, 근본적인 해결책은 되지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 최근에는 Python에서 GIL을 없애고 이와 관련된 코드를 제거하자는 목소리가 높아지기 시작했다. GIL이 도입된 이유가 garbage collection이기도 하여 GC의 알고리즘을 수정하고, thread-safe하게 함수를 작성하여 Python을 더욱 빠르게 하자는 이야기가 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;파이썬 3.10부터 GIL을 차츰 버릴 준비를 하고있으며, 속도 향상에 매력을 느낀 파이썬 코어 개발자들은 서서히 GIL에 관한 업데이트를 진행중인 분위기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TVlhd/btrXdMYJKgl/eunVQ1rpzTYIWTtmVvT3j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TVlhd/btrXdMYJKgl/eunVQ1rpzTYIWTtmVvT3j0/img.png&quot; data-alt=&quot;Ruby의 Guild Model&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TVlhd/btrXdMYJKgl/eunVQ1rpzTYIWTtmVvT3j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTVlhd%2FbtrXdMYJKgl%2FeunVQ1rpzTYIWTtmVvT3j0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;207&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Ruby의 Guild Model&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjDjus/btrXaZdy6LO/7V3GrFkX6Lv416nS8VIxoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjDjus/btrXaZdy6LO/7V3GrFkX6Lv416nS8VIxoK/img.png&quot; data-alt=&quot;Ruby의 멀티쓰레드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjDjus/btrXaZdy6LO/7V3GrFkX6Lv416nS8VIxoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjDjus%2FbtrXaZdy6LO%2F7V3GrFkX6Lv416nS8VIxoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;189&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Ruby의 멀티쓰레드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 GIL의 부작용을 보며, GIL의 장점과 기존 멀티쓰레드의 장점을 합칠 방법을 생각하기 시작했다. 한 가지 예시로 Ruby는 Ruby3가 업데이트되며 Ractor(Guild)라는 개념을 도입하여, 부분적인 멀티쓰레드를 도입하기 시작하였다. 해결책은 객체를 공유하는 쓰레드끼리는 lock을 걸되, 다른 쓰레드는 해당 객체에 접근하지 못하는 대신 쓰레드를 동시에 실행시킬 수 있는 방법을 제시하였다. 객체를 공유할 수 있는 쓰레드의 묶음을 Guild라고 부르기로 하였고, 각 Guild에서는 GGL(Giant Guild Lock)을 걸어 하나의 쓰레드만 실행시킬 수 있었다.&amp;nbsp; 대신, 서로 다른 길드 안의 쓰레드는 병렬적으로 실행할 수 있도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;각 길드간 통신은 message passing channel을 만들거나, 해당 객체의 reference만 공유하거나, 해당 객체의 guild 소유권을 바꾸는 방식을 사용하거나, 특별한 자료구조 buffer를 만드는 것으로 해결하였다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2.&amp;nbsp; Reactor &amp;amp; Proactor&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다른 스크립트 언어, 특히 싱글쓰레드 언어들은 이벤트 핸들링을 위해 I/O 멀티플렉싱에서 아이디어를 따왔다. 이벤트 핸들링은 non-blocking 형태로 작동해야 하기에, non-blocking 하면서 synchronous한 방식인 reactor 패턴, asynchronous한 방식인 proactor 패턴 방식이 제시되었다. (Busy-waiting은 비효율적인 방법이기에 제외하였다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저, 이벤트 처리는 다음 다섯 단계로 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이벤트가 발생하면 입력을 처리할 수 있도록 다중화한다. (Initiate &amp;amp; multiplex)&lt;/li&gt;
&lt;li&gt;이벤트가 발생하기까지 대기한다. (Receive)&lt;/li&gt;
&lt;li&gt;이벤트가 발생 시 event handler를 호출하기 위하여 객체로 분할한다. (demultiplex)&lt;/li&gt;
&lt;li&gt;해당 이벤트에 해당되는 event handler를 호출한다. (dispatch)&lt;/li&gt;
&lt;li&gt;Event handling을 수행한다. (process)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGYU2/btrXc8t7iE0/YRsdGwe4TzxMxK8qNs2ov0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGYU2/btrXc8t7iE0/YRsdGwe4TzxMxK8qNs2ov0/img.png&quot; data-alt=&quot;Reactor 패턴 흐름도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGYU2/btrXc8t7iE0/YRsdGwe4TzxMxK8qNs2ov0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGYU2%2FbtrXc8t7iE0%2FYRsdGwe4TzxMxK8qNs2ov0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1262&quot; height=&quot;414&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Reactor 패턴 흐름도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Reactor 패턴은 동적으로 event handler를 생성하고 지우는 방법을 활용하여 여러 이벤트를 처리한다. 즉, 이벤트에 반응하는 객체(reactor)를 생성하고, 이벤트 발생 시 해당 이벤트 target 대신 reactor가 해당 이벤트를 처리한다. 따라서 reactor 패턴의 가장 큰 참여자는 reactor 자체와 이벤트에 따른 handler이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 프로그램의 흐름이 역전되는것과 같은 효과를 주며, 해당 이벤트 처리에 대한 책임은 reactor에 있고, 어플레케이션을 설계하는 사람은 이벤트 handler에 대한 책임이 있다. 이러한 원칙을 &quot;할리우드 원칙(Hollywood principle)&quot;이다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Don't call us, we'll call you.&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;- Hollywood principle -&lt;/b&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 5단계로 설명하면 다음과 같이 설명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이벤트에 반응하는 Reactor 객체를 생성하고 이벤트에 알맞은 handler를 등록한다. (Initiate &amp;amp; multiplex)&lt;/li&gt;
&lt;li&gt;이벤트가 발생하기까지 Reactor가 대기한다. (Receive)&lt;/li&gt;
&lt;li&gt;이벤트 발생 시 Reactor는 이벤트 처리를 위해 handler 단위로 이벤트를 분리한다. (demultiplex)&lt;/li&gt;
&lt;li&gt;분할된 이벤트에 따라 handler가 처리하도록 신호를 보낸다. (dispatch)&lt;/li&gt;
&lt;li&gt;handler에 따른 이벤트를 처리한다. (process)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Reactor 패턴을 활용하는 대표적인 예제는 Node.js나 Dart의 event loop이다. 이벤트가 발생하면 JS엔진 안의 reactor가 알맞게 event handler를 호출하며, 해당 이벤트 핸들링 콜백 함수를 CallbackQueue에 등록하는 방법으로 작동이 된다. 자세한 내용은 event loop에 대한 글에서 찾아뵙도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Linux 안의 epoll도 reactor 패턴을 사용하며, 이 경우 fd마다 event handler를 만들어, 이벤트 발생 시 연결한 event handler에 등록된 fd와 이벤트 종류를 포함한 이벤트 구조체를 반환한다. 따라서, 이벤트와 handler에 대한 정보를 리액터가 관리해야 하며 수많은 요청을 여러 개의 handler로 처리하면 부담이 그만큼 커지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한, 멀티쓰레드의 경우 쓰레드 별 reactor를 사용해야 하는데, 쓰레드 별 reactor를 사용할 경우 비효율적인 I/O 처리를 하므로 직접 스케쥴링 정책을 만들어야 한다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, Reactor 패턴은 직관적이라는 장점이 있지만 여러 개의 client를 핸들링하거나, 멀티쓰레드로 동작하는 경우 적절하지 않는 패턴이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNSH5T/btrXc8VhPzn/E7sYkzuhu1JkXq7Ml4vbl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNSH5T/btrXc8VhPzn/E7sYkzuhu1JkXq7Ml4vbl0/img.png&quot; data-alt=&quot;Proactor 패턴 흐름도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNSH5T/btrXc8VhPzn/E7sYkzuhu1JkXq7Ml4vbl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNSH5T%2FbtrXc8VhPzn%2FE7sYkzuhu1JkXq7Ml4vbl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;234&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Proactor 패턴 흐름도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Proactor 패턴은 Reactor 패턴의 단점을 해결할 수 있다. Proactor는 반대로 이벤트를 수동적으로 기다리지 않고, event handling을 능동적으로(비동기적으로) 한다. OS가 비동기 작업 프로세스를 지원하는 경우, 특정 작업을 시키며 콜백 함수를 직접 넘겨주며 해당 일을 비동기적으로 처리하고 작업이 끝났는지 콜백을 받으면서 알게된다. 즉, proactor 패턴에서는 &lt;b&gt;event가 하나의 작업 완료 콜백&lt;/b&gt;이라고 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 5단계로 설명하면 다음과 같이 설명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;완료 이벤트를 받을 completion handler를 등록한다. (Initiate &amp;amp; multiplex)&lt;/li&gt;
&lt;li&gt;비동기 프로세스가 작업을 대기하거나 작업 발생 시 처리한다. (Receive)&lt;/li&gt;
&lt;li&gt;가능한 작업 생길 시 비동기 프로세스가 작업을 알아서 분리하여 비동기 처리한다.(demultiplex)&lt;/li&gt;
&lt;li&gt;작업 완료 시 비동기 프로세스는 작업이 완료되었다고 completion dispatcher에게 신호를 주며, dispatcher는 해당 이벤트에 대한 적절한 completion handler에게 넘긴다.&lt;/li&gt;
&lt;li&gt;completion handler는 사전에 정해진 콜백 함수를 호출하여 마무리한다. (process)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, 이벤트가 발생하면 어플리케이션은 OS의 비동기 작업 API에 이벤트를 던져주면 비동기적으로 작동하며 &quot;알아서&quot; 처리해서 통지하고, 이후 completion handler에 도착한 이벤트만 처리하면 된다. 마치 친구에게 부재중 전화를 남기면 친구가 부재중 전화를 보고 나에게 다시 전화를 걸어 통화하는 방식에 비유할 수 있겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Proactor 패턴의 장점은 콜백에 따른 작업만 처리하면 되게에 간결하게 설계할 수 있다. 또한, 확장성이 좋고 이벤트 제어권이 어플리케이션에 있으므로 다른 동기화 작업이나 동시성 작업 처리가 쉬워진다. 다만, 비동기성을 활용하다 보니 작동이 어떻게 할지에 대하여는 무작위적이고 디버깅 / 테스트가 어렵다는 단점이 존재한다. 또한, 비동기 작업에 따른 오버헤드도 당연히 존재하고, 생각보다 퍼포먼스 발목을 잡는 부분이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;보통 Proactor 패턴은 비동기 작업 쓰레드 혹은 쓰레드 풀을 만들고 비동기 쓰레드에 작업을 던져주는 방식으로 동작한다. C++ Boost 라이브러리의 Asio(Asynchronous IO)도 해당 방식을 활용하며, 뒷 단의 worker에서 비동기 I/O API를 활용하거나 직접 쓰레드가 비동기 작업을 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제, JS의 Event Loop을 이해하여보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Reference (감사합니다)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;a href=&quot;https://www.datacamp.com/tutorial/python-global-interpreter-lock&quot;&gt;https://www.datacamp.com/tutorial/python-global-interpreter-lock&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;a href=&quot;https://www.dabeaz.com/python/UnderstandingGIL.pdf&quot;&gt;https://www.dabeaz.com/python/UnderstandingGIL.pdf&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;a href=&quot;https://docs.google.com/document/d/18CXhDb1ygxg-YXNBJNzfzZsDFosB5e6BfnXLlejd9l0/edit&quot;&gt;https://docs.google.com/document/d/18CXhDb1ygxg-YXNBJNzfzZsDFosB5e6BfnXLlejd9l0/edit&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;a href=&quot;http://www.atdot.net/~ko1/activities/2016_rubykaigi.pdf&quot;&gt;http://www.atdot.net/~ko1/activities/2016_rubykaigi.pdf&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.naver.com/n_cloudplatform/222189669084&quot;&gt;https://blog.naver.com/n_cloudplatform/222189669084&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://www.dre.vanderbilt.edu/~schmidt/POSA/POSA2/event-patterns.html&quot;&gt;http://www.dre.vanderbilt.edu/~schmidt/POSA/POSA2/event-patterns.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@myner/42&quot;&gt;https://brunch.co.kr/@myner/42&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://didawiki.cli.di.unipi.it/lib/exe/fetch.php/magistraleinformatica/tdp/tpd_reactor_proactor.pdf&quot;&gt;http://didawiki.cli.di.unipi.it/lib/exe/fetch.php/magistraleinformatica/tdp/tpd_reactor_proactor.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.boost.org/doc/libs/1_72_0/doc/html/boost_asio/overview/core/async.html&quot;&gt;https://www.boost.org/doc/libs/1_72_0/doc/html/boost_asio/overview/core/async.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>Concurrency</category>
      <category>Event Loop</category>
      <category>JS</category>
      <category>Proactor</category>
      <category>reactor</category>
      <category>Thread</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/98</guid>
      <comments>https://0xffffffff.tistory.com/98#entry98comment</comments>
      <pubDate>Thu, 26 Jan 2023 16:53:37 +0900</pubDate>
    </item>
    <item>
      <title>[JS / ECMAScript] 패키지 설치와 의존성, npm, yarn에 관한 고찰</title>
      <link>https://0xffffffff.tistory.com/97</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js와 그에 파생된 여러 프레임워크를 사용하다 보면, 패키지 설치와 의존성 관리를 담당하는 패키지 매니저를 자주 볼 수 있다. (package.json으로부터 비롯된 매니징 툴 npm, yarn, pnpm 등등...) 다만, 이러한 라이브러리 패키지 매니징과 node_modules 폴더에 대한 깊은 고찰을 한 적은 없어서 이번 글을 통해 해보려 한다. 이번 글에서는 npm, yarn의 역사와 의존성, 패키지 관리에 대하여 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;채널 로고_복사본 (3)-001.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuJzaL/btrWIOWu5kB/vybeVN8g3bJOR0CKj1Wnk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuJzaL/btrWIOWu5kB/vybeVN8g3bJOR0CKj1Wnk1/img.png&quot; data-alt=&quot;NPM &amp;amp;amp; Yarn에 대하여 알아보자.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuJzaL/btrWIOWu5kB/vybeVN8g3bJOR0CKj1Wnk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuJzaL%2FbtrWIOWu5kB%2FvybeVN8g3bJOR0CKj1Wnk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;채널 로고_복사본 (3)-001.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NPM &amp;amp; Yarn에 대하여 알아보자.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. import, require는 Node.js에서 어떤 원리로 동작하는가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현대 JS에서 사용하고 있는 모듈 불러오기 문법은 두 가지가 있다. Import 문과 Require 문이 그 둘이다. 두 문법에는 여러 작은 차이가 있지만, 이번 글의 목적은 두 문구의 비교가 아니기에 자세한 내용은 적지 않겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1674098880516&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import fs from 'fs';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Import 또는 require를 통해 모듈을 불러올 경우, Node.j는 먼저 Node.js 자체 내장 모듈인지 검색한다. 예를 들어, Node.js가 기본으로 제공하는 fs, http 모듈을 불러올 경우 Node.js는 이들을 우선적으로 불러오게 된다. Node.js가 기본으로 제공하는 모듈들을 &lt;a title=&quot;다음 링크&quot; href=&quot;https://www.tutorialsteacher.com/nodejs/nodejs-modules&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 링크&lt;/a&gt;에서 제공한다.&lt;/p&gt;
&lt;pre id=&quot;code_1674098897224&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import App from &quot;./App.js&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음으로, Node.js는 파일 경로인 지 검색한다. App.js라는 파일이 동일 경로에 존재한다고 가정할 경우, Node.js는 파일 경로임을 인식하면 해당 파일을 불러오는 방법으로 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1674099094182&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from &quot;React&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;다음으로, Node.js가 기본으로 제공하지 않는 string이 인식될 경우, Node.js는 node_modules 폴더 안의 경로를 모두 탐색하기 시작한다. Node.js 내부의 module resolver 함수가 해당 string을 받으면, module.paths에 등록되어 있는 모든 폴더를 탐색하기 시작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1674099496840&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ node
&amp;gt; module.paths
[
	&quot;/some/path/node_modules&quot;
    &quot;/some/node_modules&quot;
    &quot;/node_modules&quot;
    ...
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Node.js는 &lt;b&gt;현재 모듈의 상위 디렉토리&lt;/b&gt;에 해당하는 node_modules부터 검색을 시작하며, 찾는 string이 발견될 때까지 반복한다.&amp;nbsp; 따라서, 어디서 import 하냐에 따라 다른 결과가 나올 수 있고, import문은 상대적이라는 의미를 함축하고 있다. 달리 말해, 다른 폴더에서 node &amp;gt; module.paths를 실행한다면 다른 결과를 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쉽게 말해, 가장 가까운 node_modules를 탐색한다고 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 방식의 장점은 각 패키지 마다 자신만의 의존성을 가질 수 있고, 다른 패키지에 의해 간섭받지 아니하며, 여러 파일을 import해도 그리 큰 문제가 되는 일은 없다. 다만, 여러 버전을 동시에 들고 있다는 것 자체가 최적화에 안좋은 영향을 미치며, 동일한 코드를 여러 번 다른 곳에 쓰면 낭비가 아닐 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;모던 웹사이트는 속도 및 최적화를 굉장히 중요시하기에, 파일 크기 및 번들링된 js 크기를 최대한 줄일려고 한다. 혹은 싱글턴 방식으로 동작하는 패키지 (ex. graphql) , 글로벌 패키지는 위 방식과 잘 맞지 않는다. 또한, 각 패키지마다의 독립적인 의존성을 가지니, 각 패키지 안에 어떤 일이 발생하는 지 아무도 모른다. 또한, 어떤 패키지에 작은 패치가 이루어져도 이에 의존하는 패키지들은 모두 빌드를 다시 해야하므로 공간의 낭비로 이루어진다. 또한, 이러한 패키지가 많아질수록 버전이 꼬일 것은 불 보듯 뻔하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이러한 배경 속에서 JS의 폭발적인 성장을 이끈 패키지 매니저가 나왔다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Npm &amp;amp; Yarn의 등장&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2010년 1월, 패키지 레지스트리 및 의존성 관리 관리 툴 npm이 등장하였다. Npm이 등장한 이유는 글로벌 패키징이 아닌, 로컬 패키지를 사용하도록 하고, 패키지 버젼을 관리하고, 중복된 패키지를 관리하기 위함이다. 사실 npm은 &lt;a title=&quot;Node Package Manager의 약자가 아니며&quot; href=&quot;https://github.com/npm/cli#is-npm-an-acronym-for-node-package-manager&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Node Package Manager의 약자가 아니며&lt;/a&gt;, bash 유틸 중 하나인 pm(pkgmakeinst)를 node에 맞게 구현한 것이라 생각하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;기존처럼 하나의 글로벌 패키징을 할 경우 보통 한 가지 버젼만 사용할 수 있도록 하였으며, 이러한 방식은 여러 부작용을 촉발하였다. 예를 들어, 여러 패키지가 의존하고 있는 하나의 모듈에 에러를 발생시키거나, 그 패키지의 API가 크게 바뀌거나, 그 패키지에 누군가가 악의적인 코드를 심었다면? 그야말로 난리가 날 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들어, 2020년에 유명 개발자 Marak Squires가 패키지 colors.js에 일부러 악의적인 무한루프 코드를 심어 이에 의존하는 &lt;a title=&quot;패키지를 모두 망가뜨린 사건&quot; href=&quot;https://yceffort.kr/2022/01/npm-colors-fakerjs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;패키지를 모두 골로 보낸 사건&lt;/a&gt;이 있다. 개발자 말로는 대기업들이 자기의 오픈소스를 쓰면서, 아무런 보상이 없다는 것에 잔뜩 화가 난 모습인데, 이에 대한 진실은 오리무중이다. 아무튼 이러한 패키지 사보타주가 있을 경우, 이에 영향을 받는 패키지 (jest, netlify와 같은 유명 패키지까지) 모두 &lt;a title=&quot;폭&quot; href=&quot;https://github.com/aws/aws-cdk/issues/18322&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;폭~&lt;/a&gt;&lt;a title=&quot;삭&quot; href=&quot;https://github.com/facebook/jest/issues/12226&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;삭~&lt;/a&gt; &lt;a title=&quot;망&quot; href=&quot;https://github.com/netlify/cli/issues/3981&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;망~&lt;/a&gt; &lt;a title=&quot;해&quot; href=&quot;https://github.com/hexojs/hexo/issues/4865&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;해~&lt;/a&gt; 버리는 상태가 될 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 각 패키지마다의 일정 테스트를 통과하게 하거나, 기존 레거시 API를 계속 지원하거나, SHA-512 알고리즘 도입으로 패키지 무결성을 검사하는 방식을 활용하였다. (package-lock.json에서 보이는 외계어들이 이런 맥락을 포함하고 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Npm은 여러 영리한 방법으로, 각 버젼의 패키지를 싹 다 설치하는 방향을 채택하였다. 각 패키지마다 고유한 해쉬값을 부여햐여, 여러가지 버젼, 변형을 각각 설치하도록 하였다. 이러한 경우 디스크 공간을 많이 잡아먹는다는 단점이 존재하지만, 다른 방법으로 최대한 디스크 공간 차지를 줄이려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;한 가지 방법으로 package.json에서 버젼을 살펴보면 틸다(~)와 캐럿(^) 표시, 부등호 표시들을 적극 활용하여 최대한 현재 키지를 재활용하려 한다. 이러한 방식을 &lt;b&gt;시맨틱 버전 관리(Semantic Versioning)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cx5Rhn/btrWCH5yFH9/RYu18etYkrQhsStiEcAg9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cx5Rhn/btrWCH5yFH9/RYu18etYkrQhsStiEcAg9k/img.png&quot; data-alt=&quot;패키지 버젼 읽는 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cx5Rhn/btrWCH5yFH9/RYu18etYkrQhsStiEcAg9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcx5Rhn%2FbtrWCH5yFH9%2FRYu18etYkrQhsStiEcAg9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;451&quot; height=&quot;250&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;패키지 버젼 읽는 방법&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;버전은 점으로 구분된 세 가지 숫자와 추가적인 정보로 이루어져 있다.&amp;nbsp;&lt;b&gt; 첫 번째 숫자는 Major 버전&lt;/b&gt;으로, 이 숫자가 바뀔 경우 정말 큰 변화가 발생했다고 할 수 있다. 아예 새로운 패키지라고 생각해도 좋을 정도로 많은 변화 및 내용 확장이 발생할 때 이 숫자가 바뀐다고 생각해도 된다. &lt;b&gt;두 번째 숫자는 Minor 버전&lt;/b&gt;으로, Major보다 적지만 기능이 추가되거나, 내부에 커다린 리팩토링을 진행하였거나, 오래된 함수를 관리할 때 바뀌는 숫자이다. 이 숫자가 바뀌었을 경우 기존의 API는 사용이 가능하지만, 가급적 새로운 기능을 이용을 권장한다. &lt;b&gt;세 번째 숫자는 Patch 버전&lt;/b&gt;으로, 미미한 변화가 생기거나 버그 수정과 같이 사소한 변화가 생겼을 때 바뀌는 숫자이다. 그 뒤에는 추가적으로 베타 버전, 사전 등록 버전이란 뜻을 붙일 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;npm에서는 시맨틱 버전 관리를 할 시 위 세 숫자를 기준으로 패키지를 관리한다. 틸다(~)의 경우 &lt;b&gt;같은 Major, Minor 버전까지는 사용하겠다는 의미&lt;/b&gt;로, ~1.2.3이라 적혀 있는 경우 &amp;lt; 1.3.0 버전까지는 동일한 릴리즈를 사용하겠다는 의미를 가진다. 캐럿(^)의 경우 &lt;b&gt;같은 Major 버전까지는 사용하겠다는 의미&lt;/b&gt;로, ^1.2.3이라 적혀있는 경우 &amp;lt; 2.0.0 비전까지는 동일한 릴리즈를 사용하겠다는 의미를 가진다. 다만 버전이 1.0.0 미만일 경우, 다시 말해 해당 패키지가 pre-release의 경우 API 변경 및 리팩토링이 자주 발생하므로 캐럿이 틸다처럼 동작한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FZQfX/btrWDPaUiBx/hfKMdFg65PQKjcDQK0oBFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FZQfX/btrWDPaUiBx/hfKMdFg65PQKjcDQK0oBFK/img.png&quot; data-alt=&quot;npmv3의 종속성 트리 호이스팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FZQfX/btrWDPaUiBx/hfKMdFg65PQKjcDQK0oBFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFZQfX%2FbtrWDPaUiBx%2FhfKMdFg65PQKjcDQK0oBFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;565&quot; height=&quot;372&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;npmv3의 종속성 트리 호이스팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또 다른 방법으로, 서로 다른 버전으로 종속성을 가질 경우, 해당 패키지에 대하여 여러가지 버전을 가지도록 한다. npm3부터는 각 패키지들을 호이스팅하여 종속성 트리를 최대한 평평하게 만들어 중복 패키지를 제거하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Yarn(Yet Another Resource Negotiator)는 구글, 페이스북과 같은 대기업 빨(?)을 받아 더 발전된 패키지 매니징을 지원한다. 특히, Yarn은 npm 보다 더 나은 속도, 보안, 신뢰성을 제공한다. 그 비결 중 하나로 다운로드 받은 패키지를 캐시에 저장하고 이후 추가된 패키지가 이에 대한 종속성이 있을 경우 이를 복사하여 제공하고, 여러 패키지를 병렬적으로 다운로드 받을 수 있도록 하여 속도를 더욱 증가시켰다. 또한, 더 엄격한 의존성 관리로 각 패키지을 확인하여 신뢰성 있고 등록된 패키지만 의존성으로 설정할 수 있도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;특히, 정확한 버전이 명시되어 있는 의존성 파일 yarn.lock을 제공함으로써 일관된 개발 환경을 만들 수 있고, npm도 package.lock 파일을 npm5부터 제공하기 시작했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다만, 이러한 시스템은 여전히 문제를 일으켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Npm과 yarn이 제공하는 패키지 매니지 시스템은, 토스의 표현을 빌리자면 &lt;b&gt;&quot;깨져 있다.&quot;&lt;/b&gt; 1번에서 알아보았듯 node_modules를 탐새가는 것은 자원과 시간이 많이 드는 I/O 호출을 반복하는 행위이고, 현재 경로에 따라 상이한 결과가 나올 수 있다는 점에서 문제를 일으킬 확률이 컸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한, 패키지 호이스팅을 통해 종속성을 관리하다 보니 해당 어플리케이션이 직접 의존하지 않는 라이브러리를 import할 수 있고, 이를 우린 유령 의존성(phantom dependency)라 한다. 다른 말로, package.json에 명시되지 않는 import할 수 있고, 만약 명시되지 않는 의존성을 import할 경우 코드에 혼란이 올 수 있으며, 해당 패키지를 사용하는 패키지를 package.json에서 제거하면 같이 사라지는 현상을 볼 수 있다. 따라서, 효과적인 의존성 시스템에 대한 요구가 지속되었고, 다른 방법을 활용한 여러 패키지 매니저가 등장하기 시작했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. pnpm &amp;amp; yarn berry의 등장 및 동작&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Pnpm(Performant NPM)은 NPM의 다양한 기능들을 향상시킨 패키지 매니저 툴이며, npm의 호이스팅 방식을 활용하여 의존성을 평탄화 하는 대신, 심볼릭 링크 및 파일 시스템을 영리하게 활용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;pnpm의 경우 심볼릭 링크, 하드 링크를 활용하여 각 패키지에 접근한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ykbhk/btrWDEHJ4vu/umZLT99oMtiMKSfuVKUGP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ykbhk/btrWDEHJ4vu/umZLT99oMtiMKSfuVKUGP1/img.png&quot; data-alt=&quot;심볼릭 링크와 하드 링크&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ykbhk/btrWDEHJ4vu/umZLT99oMtiMKSfuVKUGP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fykbhk%2FbtrWDEHJ4vu%2FumZLT99oMtiMKSfuVKUGP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;385&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;심볼릭 링크와 하드 링크&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;심볼릭 링크란 FS 상의 특정 파일을 가리키는 것을 의미하며, 쉽게 말해 '바로 가기'에 해당하는 개념이라 생각하면 된다. 하드 링크는 반대로 실제 물리적인 하드 디스크에 저장되어 있는 특정 파일의 위치를 가리키는 것으로, 원본 파일을 지워도 원본 파일의 inode에 접근하기에 데이터에 접근이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2920&quot; data-origin-height=&quot;1392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MMfFC/btrWI1OzR7k/bE6zkP5kHoO6kaWdxWmrNK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MMfFC/btrWI1OzR7k/bE6zkP5kHoO6kaWdxWmrNK/img.jpg&quot; data-alt=&quot;pnpm의 구현 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MMfFC/btrWI1OzR7k/bE6zkP5kHoO6kaWdxWmrNK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMMfFC%2FbtrWI1OzR7k%2FbE6zkP5kHoO6kaWdxWmrNK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;330&quot; data-origin-width=&quot;2920&quot; data-origin-height=&quot;1392&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pnpm의 구현 방법&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;pnpm의 경우 위와 같은 방법으로 패키지에 접근하며, 위의 그림에서 살펴보면 하나의 .pnpm store라는 공간에 실제 패키지에 접근하는 것을 알 수 있다. 실제로 pnpm을 사용할 경우 ~/Home 디렉토리에 하나의 node_modules를 사용하며, 글로벌 패키징과 독립된 패키징의 장점을 합친 결과라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다만, 이러한 방법은 OS, 파일 시스템마다 다르게 구현되어야 하며, 다른 툴과의 호환성 문제, 네트워크 파일 시스템 접근 관련해서 여러 자잘한 문제를 일으키기에 pnpm은 생각보다 큰 반향을 일으키진 못했다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Yarn의 경우 Yarn v2, yarn berry로 버젼을 업그레이드하며 몇 가지 추가적인 기능들을 도입하였다. Yarn에서 사용하는 node_modules 파일 시스템 대신, Plug'n'Play 방식과 Zip Filesystem을 도입하였다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SaTKG/btrWJduyjdf/WvRq7m8ntD8hI0LZMGn1Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SaTKG/btrWJduyjdf/WvRq7m8ntD8hI0LZMGn1Q1/img.png&quot; data-alt=&quot;Yarn Berry의 의존성 정보&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SaTKG/btrWJduyjdf/WvRq7m8ntD8hI0LZMGn1Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSaTKG%2FbtrWJduyjdf%2FWvRq7m8ntD8hI0LZMGn1Q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;349&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Yarn Berry의 의존성 정보&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Plug'n'Play는 node_modules를 생성하는 대신, .yarn/cache 폴더에 의존성 정보를 저장하고, .pnp.cjs에 의존성 관련 lookup table을 만든다는 방식을 취하였다. 따라서, 파일 시스템을 통해 경로를 일일히 찾아볼 필요 없이 룩업 테이블 한 방이면 해당 패키지로 바로 접근할 수 있다는 의미이다. 이는 yarn이 Node.js의 require문을 덮어 씀으로써 가능해졌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SPyom/btrWEgs6nMe/lsXnMSeuR8SoxvnhMzSvYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SPyom/btrWEgs6nMe/lsXnMSeuR8SoxvnhMzSvYk/img.png&quot; data-alt=&quot;Yarn의 Zip FileSystem&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SPyom/btrWEgs6nMe/lsXnMSeuR8SoxvnhMzSvYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSPyom%2FbtrWEgs6nMe%2FlsXnMSeuR8SoxvnhMzSvYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;339&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;573&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Yarn의 Zip FileSystem&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또 다른 기능으로 ZipFS(Zip FileSystem)을 활용하며, 의존성은 ZIP 형태로 저장된다. 룩업 테이블로 찾아본 파일 경로에는 ZIP 파일이 존재하며, 압축된 패키지를 가지고 있으므로 용량이 대폭 감소하며, 의존성 관련 구성 파일 수가 감소하므로 의존성 변경이 매우 용이해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 줄어든 용량을 토대로 의존성을 git으로 버젼 관리를 할 수 있게 되었으며, 이처럼 의존성을 따로 설치하지 않고 모든 설치파일을 버전 관리에 포함시키는 것을 Zero-install 전략이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다만, 이 yarn berry도 호환성 문제로 PnP API를 통해 의존성 관리를 할 경우 node 명령어 대신 yarn node 라는 명령어를 사용해야 하며, 패키지가 yarn berry를 지원하지 않는 경우 동작하지 않는 문제가 존재한다. 이외에도 ESLint와의 호환성, VSCode / IntelliJ와의 호환성 등 여러 과제가 존재한다. yarn berry가 require문을 덮어쓰다보니 require 문을 쓰는 다른 툴에서 문제를 일으키는 것으로 보인다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 그래서 뭐써요?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;현재 npm이든, yarn이든, pnpm이든, yarn berry이든, 사실 기능 상에는 큰 문제가 없다. 다만, 디스크 효율성을 생각하면 더 이후에 나온 pnpm, yarn berry를 활용하는 것이 좋아보이고, 아직 패키지 매니징에 익숙하지 않으면 npm과 yarn을 써도 무방하다. 작은 프로젝트에는 눈에 가시적인 성능 차이를 보이지 않지만, 여러 라이브러리들을 활용하면서 패키지 의존성이 복잡해진다면 최신 패키징 방법을 도입해 보는 것을 고려해보는 것을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Reference (감사합니다.)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://soshace.com/setting-up-automated-semantic-versioning-for-your-nodejs-project/&quot;&gt;https://soshace.com/setting-up-automated-semantic-versioning-for-your-nodejs-project/&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://javascript.plainenglish.io/an-abbreviated-history-of-javascript-package-managers-f9797be7cf0e&quot;&gt;https://javascript.plainenglish.io/an-abbreviated-history-of-javascript-package-managers-f9797be7cf0e&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://yceffort.kr/2022/01/npm-colors-fakerjs&quot;&gt;https://yceffort.kr/2022/01/npm-colors-fakerjs&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://toss.tech/article/node-modules-and-yarn-berry&quot;&gt;https://toss.tech/article/node-modules-and-yarn-berry&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://npm.github.io/how-npm-works-docs/npm3/how-npm3-works.html&quot;&gt;https://npm.github.io/how-npm-works-docs/npm3/how-npm3-works.html&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://simpleisit.tistory.com/72&quot;&gt;https://simpleisit.tistory.com/72&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://github.com/npm/cli#is-npm-an-acronym-for-node-package-manager&quot;&gt;https://github.com/npm/cli#is-npm-an-acronym-for-node-package-manager&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://pnpm.io/motivation&quot;&gt;https://pnpm.io/motivation&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://github.com/yarnpkg/yarn/issues/1761&quot;&gt;https://github.com/yarnpkg/yarn/issues/1761&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;a href=&quot;https://github.com/yarnpkg/berry/issues?page=16&amp;amp;q=is%3Aissue+is%3Aopen&quot;&gt;https://github.com/yarnpkg/berry/issues?page=16&amp;amp;q=is%3Aissue+is%3Aopen&lt;/a&gt;&lt;/i&gt;&lt;/p&gt;</description>
      <category>Javascript</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/97</guid>
      <comments>https://0xffffffff.tistory.com/97#entry97comment</comments>
      <pubDate>Thu, 19 Jan 2023 14:43:38 +0900</pubDate>
    </item>
    <item>
      <title>[React] useMemo를 활용하여 성능을 향상시켜보자.</title>
      <link>https://0xffffffff.tistory.com/96</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;최근 React를 활용한 웹사이트를 만드는 데, 급격한 스크롤을 할 시 성능이 급격하게 내려간 현상을 발견할 수 있었다. 마우스 관련 이벤트에 쓰로틀을 걸어도, 디바운스를 활용해도 성능이 개선되지 않았으나, useMemo를 활용하여 성능을 개선시킬 수 있었다. 그 컴포넌트는 무거운 연산을 하는데 컴포넌트가 다시 렌더링될때마다 다시 무거운 연산을 하기 때문이었던걸로 문제가 발생했기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;useMemo을 활용한 성능 차이를 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;채널-로고_복사본-_4_-001.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxsknf/btrWxs7nQsf/JOPcB8rjNCbWqd8mM2rmJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxsknf/btrWxs7nQsf/JOPcB8rjNCbWqd8mM2rmJ1/img.png&quot; data-alt=&quot;useMemo에 대하여 알아보자.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxsknf/btrWxs7nQsf/JOPcB8rjNCbWqd8mM2rmJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxsknf%2FbtrWxs7nQsf%2FJOPcB8rjNCbWqd8mM2rmJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;thumbnail&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;채널-로고_복사본-_4_-001.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;useMemo에 대하여 알아보자.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. useMemo&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;useMemo는 React에서 공식적으로 제공하는 hook의 일종으로, 컴포넌트에서 이미 계산된 값을 재사용할 시 활용가능한 함수이다. 즉, 만약 어떠한 이벤트 때문에 React가 컴포넌트를 리렌더링해야 하는 경우 useMemo를 활용하여 자원이 많이 드는 계산을 이전의 값을 활용하도록 강제할 수 있다. 이전의 값은 메모리 속에 메모이제이션되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;공식적인 설명은 아래와 같이 되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1674004663839&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const memoizedValue = useMemo(() =&amp;gt; computeExpensiveValue(a, b), [a, b]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;내가 계산하고자 하는 값을 콜백 함수로 감싼 다음, React답게 두 번째 인자로 의존성 배열을 넣어주면 된다. 이 경우 useMemo로 감싼 함수는 이후 재렌더링시 메모이제이션 된 값을 재사용하게 된다. 즉, 컴포넌트 리렌더링 시 새로 계산할 필요가 없다는 이야기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 함수 계산을 해야하는 경우는 의존성 배열을 참고한다. 동작 방법은 의존성 배열을 Object.is를 통해 비교하고, 만약 다를 경우 재계산을 수행한다. 따라서, 의존성 배열을 넘겨주지 않을 경우 항상 재계산을 수행하게 된다. &lt;b&gt;따라서, useMemo를 쓸 때는 반드시 의존성 배열을 넘겨주자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다만, React는 useMemo를 활용한다고 모든 함수 재계산을 수행하지 않는 것은 아니다. useMemo로 감싼 함수가 만약 오랫동안 쓰이지 않았다면 자동으로 메모리를 해제하고, useMemo가 비효율적이라고 생각된다면 그 부분도 자동으로 메모리를 해제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 성능 차이&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제 테스트 코드를 작성하여 useMemo 사용한 경우와 사용하지 않은 경우 어떤 차이가 있는지 알아보자. CRA로 React@18.2.0 버전 보일러플레이트를 활용하였고, 성능이 많이 요구되는 함수 하나를 임의로 넣었다.&lt;/p&gt;
&lt;pre id=&quot;code_1674015113390&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

const longComp = (val) =&amp;gt; {
  console.time(&quot;long Comp&quot;);
  // Bottleneck
  for (let i = 0; i &amp;lt; 10 ** 9; i++) {
    val++;
  }
  console.timeEnd(&quot;long Comp&quot;);

  return val;
};

function App() {
  const [toggle, setToggle] = useState(0);
  const [count, setCount] = useState(0);

  const longCompValue = longComp(toggle);

  const toggled = () =&amp;gt; {
    if (toggle === 0) {
      setToggle(1);
    } else {
      setToggle(0);
    }
  };

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;button onClick={toggled}&amp;gt;Toggle&amp;lt;/button&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;span&amp;gt;count : {count}&amp;lt;/span&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;button
        onClick={() =&amp;gt; {
          setCount((prev) =&amp;gt; prev + 1);
        }}
      &amp;gt;
        increase count
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 코드를 실행하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xtfqT/btrWx7bb6t7/Ik2bnP47jEWBPmGB0jOCy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xtfqT/btrWx7bb6t7/Ik2bnP47jEWBPmGB0jOCy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xtfqT/btrWx7bb6t7/Ik2bnP47jEWBPmGB0jOCy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxtfqT%2FbtrWx7bb6t7%2FIk2bnP47jEWBPmGB0jOCy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;686&quot; height=&quot;284&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위에 Toggle을 눌렀을 경우에 당연히 고비용의 연산이 수행되지만, 이 경우 'increase count' 버튼만 눌러도 해당 컴포넌트가 재렌더링되면서 Toggle 관련 함수인 longComp까지 실행된다. 따라서, 실제 렌더링 성능은 기대하는 것보다 매우 낮아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;increase count 버튼을 3번 누르고, 크롬의 '성능(performance)' 탭을 통하여 실제 렌더링 시간과 여러 지표를 살펴보면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;1278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l69Rs/btrWBbc30Yd/fNdZaqKA6rkFUxdChhGJ1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l69Rs/btrWBbc30Yd/fNdZaqKA6rkFUxdChhGJ1k/img.png&quot; data-alt=&quot;useMemo 사용하지 않을 시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l69Rs/btrWBbc30Yd/fNdZaqKA6rkFUxdChhGJ1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl69Rs%2FbtrWBbc30Yd%2FfNdZaqKA6rkFUxdChhGJ1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1590&quot; height=&quot;1278&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;1278&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;useMemo 사용하지 않을 시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제 지표를 살펴보면 각 버튼 클릭마다 4.5초가 평균적으로 걸렸으며, 따라서 불필요한 렌더링으로 인하여 성능이 확 낮아지는 것을 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제, useMemo를 사용하여 함수를 감싸보자. toggle에 따라 함수가 새로 계산되어야 하므로 의존성 배열에 toggle을 추가하고, useMemo를 활용하여 불필요한 렌더링을 줄여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1674016284932&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useMemo, useState } from &quot;react&quot;;

...

function App() {
  const [toggle, setToggle] = useState(0);
  const [count, setCount] = useState(0);

  // const longCompValue = longComp(toggle);
  // Wrapped with useMemo
  const longCompValue = useMemo(() =&amp;gt; longComp(toggle), [toggle]);
  ...
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; 이제, 똑같은 과정을 반복하여보자. increase count 버튼을 3번 누른 뒤 성능을 측정하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2566&quot; data-origin-height=&quot;1296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciIQZ6/btrWBcb2DeA/WDtb6Ev5h5Q6b3YQzDWkE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciIQZ6/btrWBcb2DeA/WDtb6Ev5h5Q6b3YQzDWkE0/img.png&quot; data-alt=&quot;useMemo를 사용할 시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciIQZ6/btrWBcb2DeA/WDtb6Ev5h5Q6b3YQzDWkE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciIQZ6%2FbtrWBcb2DeA%2FWDtb6Ev5h5Q6b3YQzDWkE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2566&quot; height=&quot;1296&quot; data-origin-width=&quot;2566&quot; data-origin-height=&quot;1296&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;useMemo를 사용할 시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;useMemo를 사용할 경우 렌더링 시간이 1초도 걸리지 않았으며, 성능 차이가 확연히 나는 것을 볼 수 있다. 측정이 잘 되지 않을 만큼 렌더링 시간이 매우 짧은 것을 확인할 수 있다. 실제 toggle의 바뀌지 않을 때의 데이터가 메모이제이션되므로, 성능이 급격히 좋아진 모습을 확인할 수 있다. 다만, toggle 버튼을 눌렀을 때의 성능은 변하지 않는다. 의존성 배열로 toggle을 넣어놨기 때문이다. 실제로 리렌더링을 처리해야 하는 부분이기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 메모리를 통해 성능을 더 끌여올려야 할 때&amp;nbsp; 유용하게 사용할 수 있는 hook이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. useMemo는 무적인가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결론부터 말하자면 아니다.&amp;nbsp;&lt;/b&gt;뭐든지 하나의 성능이 끌어올려지면, 다른 하나의 성능은 내려가기 마련이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;useMemo는 메모이제이션을 활용한다. 다이나믹 프로그래밍에서 활용하는 기법인데, 자주 사용하는 데이터를 '메모'해 놓음으로써 시간을 절약하는 기법이다. 시간을 줄일 수 있는 만큼, 메모리 사용량은 늘어나게 되고, React가 알아서 메모리를 해제하긴 하지만 무분별하게 낭비할 경우 메모리 점유 공간만 늘어날 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한, useMemo를 활용할 정도의 연산을 직접 프론트엔드에서 처리할 일이 많지 않고, 고연산의 렌더링은 렌더링된 결과를 받아오거나 비동기로 처리하는 편이 더 좋은 방법이다. 또한, 고연산의 항목은 별도의 컴포넌트로 분리하여 다른 부분까지 재렌더링하지 않고 필요한 부분만 재렌더링을 하는 방법을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>react</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/96</guid>
      <comments>https://0xffffffff.tistory.com/96#entry96comment</comments>
      <pubDate>Wed, 18 Jan 2023 16:43:24 +0900</pubDate>
    </item>
    <item>
      <title>[JS / ECMAScript] 비동기 처리를 조금 더 효율적으로 해보자.</title>
      <link>https://0xffffffff.tistory.com/95</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;JS의 쓰임새 중 많은 부분을 차지하고 있는 부분은 &quot;비동기 처리&quot;이다. JS에서는 비동기 처리 방식을 크게 3가지로 구분짓는다. 첫 번째는 콜백 함수, 두 번째는 프로미스, 세 번째는 async/await이다. 이 중 가독성과 편의성을 위해서 보통 두 번째 방법과 세 번째 방법을 섞어서 사용하는 방법을 위주로 현대에선 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여러 JS 앱들을 만들었을 경우, 반드시 한 번 쯤은 비동기 처리 관련 코드를 작성해봤을 것이고, JS의 비동기 처리 3가지를 안다고 해서 비동기 처리 관련 코드를 만드는 것과 효율적인 비동기 처리 관련 코드를 만드는 것은 다른 의미를 지니고 있다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일단, 비동기 처리에 대한 기본 지식(콜백 함수, 프로미스, async/await)에 대한 지식을 가지고 있다는 전제 하에 글을 작성하고, 현대 시대에 맞추어 async/await와 프로미스를 중심으로 설명하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;채널 로고_복사본 (3)-001.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bap4Uz/btrVzF117LM/Eap0HMB3ViYWgvuRbjoKwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bap4Uz/btrVzF117LM/Eap0HMB3ViYWgvuRbjoKwk/img.png&quot; data-alt=&quot;Async를 효율적으로 처리하여보자.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bap4Uz/btrVzF117LM/Eap0HMB3ViYWgvuRbjoKwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbap4Uz%2FbtrVzF117LM%2FEap0HMB3ViYWgvuRbjoKwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;thumbnail&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-filename=&quot;채널 로고_복사본 (3)-001.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Async를 효율적으로 처리하여보자.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; Promise &amp;amp; async/await Quick Revision&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;비동기 프로그래밍에서는 언어에 구애받지 않고 Coroutine, Promise와 Future에 대한 개념이 항상 등장한다. 코루틴부터 설명하면 글이 너무 길이지므로 잠깐 생략하고, Promise와 Future부터 다시 살펴보자. 사전에 찾아보면 Promise는 약속, Future는 미래라는 뜻을 가지고 있고, 실제로 두 용어 모두 비동기 프로그래밍에서 비슷한 맥락을 지니고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Promise와 Future는 &lt;b&gt;미래에 어떤 데이터를 생산 혹은 반환하겠다는 약속&lt;/b&gt;이다. 둘의 차이점은 Future의 경우 미래에 실행이 완료될 것이라 예상되는 객체이고, Promise의 경우 Future가 참조하는 객체를 한 번 쓰기가 가능한 컨테이너라 생각할 수 있다. 그러니까, Future는 읽기 전용 객체, Promise의 경우 할당 공간이라 생각할 수 있다. Java의 경우 Promise는 CompletableFuture, Completer와 비슷한 개념이라 생각해도 된다. 다른 언어에서는 Promise를 deferred, task 등등 여러 이름으로 불리고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;JS의 프로미스는 엄밀히 말하면 promise와 future의 개념을 모두 포함하고 있다.&lt;/b&gt; JS의 프로미스 객체에 resolve, reject 함수를 넘겨주는 것은 일반적인 promise의 개념을 담고 있고(데이터를 어떻게 write 것인지), then이나 catch 등 코드 순차 실행 개념이 들어가는 것은 future의 개념이 들어가있다(받아온 데이터를 어떻게 read하고 활용할 것인지). resolve, reject의 경우 받아온 값을 어떻게 쓸 것인지에 대한 맥락을 담고 있고, then과 catch를 통해 프로미스 체이닝을 한 다는 것은 &quot;나보다 앞선 코드에서 분명 데이터를 쓰겠지.&quot;라는 암시를 담고 있다. JS에서는 두 개념을 구분하지 않고 뭉뚱그려 사용하고 있으니 이번 글에서도 비동기 처리 방식으로 프로미스를 사용한다까지만 짚고 넘어가자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;ES6에서는 async/await 방식이 도입되었다.&lt;/b&gt; 프로미스가 프로미스 체이닝을 통해 정해진 순서대로 코드를 실행하도록 하지만, 다른 언어에서 사용하는 비동기 처리 방식을 사용하지 않고 굳이 프로미스를 써야하나? 에 대한 생각으로부터 async/await가 제안되기 시작했다.&amp;nbsp; async/await가 없던 경우 코루틴 방식으로 비동기 코드를 처리하고, 기존에 이를 활용하기 위해서는 제너레이터 형태를 취했어야 했지만,&lt;b&gt; async/await를 도입하여 키워드만 붙이면 마치 동기 코드처럼 코드가 굉장히 깔끔해지는 장점이 있었다.&lt;/b&gt; 특히 if문을 통해 제어문을 도입할 수 있었고, 프로미스를 여러 개 호출할 수 있었고, 에러 핸들링을 더 세세하게 할 수 있었다. 마치 비동기 코드가 동기 실행되는 것처럼 보이고 코드를 작성할 수 있는 것이다. 즉, async/await는 예쁜 프로미스 보다 한 단락으로 이쁘게 핀, 예쁜 콜백 지옥에 더 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;async/await의 작동 방식은 다음 그림이 가장 깔끔하게 설명한다고 생각한다. main 함수 안에서 async 함수 하나를 호출하고, async 함수 안에서 두 개의 await 함수를 호출한다고 가정하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ka17t/btrVDhFhX4y/JNY7n6ysVZkrxmwkeqmlqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ka17t/btrVDhFhX4y/JNY7n6ysVZkrxmwkeqmlqk/img.png&quot; data-alt=&quot;async/await의 작동 방식.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ka17t/btrVDhFhX4y/JNY7n6ysVZkrxmwkeqmlqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKa17t%2FbtrVDhFhX4y%2FJNY7n6ysVZkrxmwkeqmlqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;async1&quot; loading=&quot;lazy&quot; width=&quot;582&quot; height=&quot;437&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;async/await의 작동 방식.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위 그림에서 main함수가 async 함수를 만나면 몇 가지 instruction을 수행한 후, await를 만난다. await를 만나면 아직 promise1가 수행되지 않았다는 것이기에, IO 처리나 비동기 처리를 하러 다른 쓰레드에 해당 비동기 처리를 하라고 프로미스를 만든 다음, main 함수에게 yield하여 실행이 main으로 돌아온다. 이후 다른 쓰레드가 promise1이 완료되었다는 것을 관찰하고 알리면, promise1은 해당 async 함수의 실행 차례일 때 중단 지점 다음 줄부터 실행하라고 &quot;점프&quot;한다. 이후 async 함수의 코드를 순차 실행하다가 다른 await를 만나면, 같은 방법으로 promise2를 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;착각하기 쉬운 부분 중 하나가 await의 경우 정말로 wait의 의미를 가지지 않는다는 것이다. 해당 작업이 처리될때까지&amp;nbsp; CPU는 기다리지 않고, CPU는 호율적으로 일하기 위하여 다른 작업을 처리하러 간다. 만약 프로미스가 이미 처리가 완료되어있다면 CPU는 async 함수를 계속 실행할 것이고, 만약 프로미스를 처리해야 한다면 현재 stack과 instruction pointer와 같은 정보들을 메모리에 저장한 다음, 다른 일을 처리하러 간다. 즉, context switching이 발생하기 안성맞춤인 공간이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wH9Ix/btrVF97Tfug/rfKcPUNC2gK602TsJSMMKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wH9Ix/btrVF97Tfug/rfKcPUNC2gK602TsJSMMKk/img.png&quot; data-alt=&quot;async 함수가 반환값이 있는 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wH9Ix/btrVF97Tfug/rfKcPUNC2gK602TsJSMMKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwH9Ix%2FbtrVF97Tfug%2FrfKcPUNC2gK602TsJSMMKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;async2&quot; loading=&quot;lazy&quot; width=&quot;597&quot; height=&quot;448&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;async 함수가 반환값이 있는 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;async 함수가 만약 반환값을 가지고 있다면, 프로미스 형태로 반환하게 된다. 아마 Typescript를 써본 프로그래머들은 async 함수의 반환값을 프로미스 타입인 것에 익숙할 것이다. async 함수 내부 동작은 앞에서 설명한 대로이고, 만약 async 함수의 작동이 끝나게 되면 전체적인 프로미스를 반환하게 된다. 만약 async 함수가 프로미스를 반환하기 않아도, JS 엔진은 자동으로 promise를 반환하도록 코드를 약간 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러나, &lt;b&gt;async/await의 근본은 제너레이터 및 코루틴&lt;/b&gt;이다. 실제로 Babel이나 다른 트랜스파일러로 async/await를 넣으면, (_asyncToGenerator) 함수를 호출해 제너레이터 형태로 비동기 함수를 처리한다. 코루틴에 대한 설명은 다른 글로 찾아뵙겠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;What's the bottleneck?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 내용을 이미 알고 있거나, 이해했다면 함수의 성능에 영향을 주는 부분이 단 번에 보일 것이다. 예상한 대로다. await 부분은 보통 하나의 임계 지점 역할을 한다. 비동기 처리를 위하여 새로운 임시 프로미스 객체 제작, 다른 쓰레드 넘어가기 위한 context switching 비용 등등을 고려하면, 상당히 많은 자원이 들어간다는 것을 알 수 있다. 따라서, &lt;b&gt;최대한 await 키워드를 활용한 호출은 줄이고,&lt;/b&gt; 비동기 처리를 여러 번 해야할 경우 한 번의 await를 통해 처리해야 한다고 예상할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;밑의 코드 예시가 여러 개 나올 텐데, await 키워드는 async 함수로 감싸져 있고, myFetch() 함수는 다른 공간에 정의되어 있다고 가정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 병렬 처리 이후 await를 사용하자.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Promise는 약속이므로, 우리가 myFetch(file)을 하기 되면 OS에게 파일 입출력을 부탁하게 된다. myFetch 함수 앞에 await를 붙임으로써 프로미스가 resolve될 때 까지 그 동안 다른 일을 하러 가야 한다. 만약 3개의 파일을 불러와야 한다면, OS를 순차적으로 3번 부르는 것 보다 병렬적으로 3개의 파일을 들고오도록 하면 더욱 효율적이지 않을까? 다음 예시를 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#000000;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #4f4f4f&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#aaa;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;4&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;5&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;6&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;7&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;8&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;9&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;10&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;getFile&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;(file)&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;Promise&lt;/span&gt;(res&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;myFetch(file,&amp;nbsp;res));&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;getFile1&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;getFile(&lt;span style=&quot;color:#82C961&quot;&gt;'file1'&lt;/span&gt;);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;file1&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;getFile1;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;getFile2&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;getFile(&lt;span style=&quot;color:#82C961&quot;&gt;'file2'&lt;/span&gt;);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;file2&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;getFile2;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;getFile3&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;getFile(&lt;span style=&quot;color:#82C961&quot;&gt;'file3'&lt;/span&gt;);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;file3&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;getFile3;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align:right;margin-top:-13px;margin-right:5px;font-size:9px;font-style:italic&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;color:#4f4f4ftext-decoration:none&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#4f4f4f;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/center&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 코드를 다음과 같이 수정하면 어떤 장점이 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#000000;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #4f4f4f&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#aaa;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;4&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;5&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;6&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;7&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;8&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;9&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;getFile&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;(file)&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;Promise&lt;/span&gt;(res&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;myFetch(file,&amp;nbsp;res));&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;getFile1&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;getFile(&lt;span style=&quot;color:#82C961&quot;&gt;'file1'&lt;/span&gt;);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;getFile2&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;getFile(&lt;span style=&quot;color:#82C961&quot;&gt;'file2'&lt;/span&gt;);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;getFile3&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;getFile(&lt;span style=&quot;color:#82C961&quot;&gt;'file3'&lt;/span&gt;);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;file1&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;getFile1;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;file2&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;getFile2;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;file3&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;getFile3;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align:right;margin-top:-13px;margin-right:5px;font-size:9px;font-style:italic&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;color:#4f4f4ftext-decoration:none&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#4f4f4f;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/center&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;첫 번째 코드와 두 번째 코드의 가장 큰 차이점은 await의 위치이다. 첫 번째 코드의 경우 file1부터 file2, file3까지 순차적으로 파일을 읽도록 하였고, 두 번째 코드의 경우 병렬적으로 file1, file2, file3를 읽도록 코드를 작정하였다. 첫 번째 코드의 경우 file1을 읽을 때까지 다음 코드를 실행하지 못하지만, 두 번째 코드의 경우 file1을 읽는 과정에 생성된 프로미스가 아직 resolve되지 않아도 두 번째 file2를 읽도록 실행시킬 수 있다. 결국 await가 하나의 중단점이 되기 때문이다. 만약 file2가 file1의 영향을 받거나 연관되어 있다면 두 번째 코드와 같이 작성하면 안되지만, 독립적인 파일이라면 아래와 같은 병렬 처리 방법으로 조금 더 빠른 입출력이 가능해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여러 개의 Promise를 처리하는 방법으로 Promise.all() 메서드를 활용해 await의 횟수를 줄일 수 있고, 가독성을 높일 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#000000;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #4f4f4f&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#aaa;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;fileArray&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;Promise&lt;/span&gt;.all([getFile(&lt;span style=&quot;color:#82C961&quot;&gt;'file1'&lt;/span&gt;),&amp;nbsp;getFile(&lt;span style=&quot;color:#82C961&quot;&gt;'file2'&lt;/span&gt;),&amp;nbsp;getFile(&lt;span style=&quot;color:#82C961&quot;&gt;'file3'&lt;/span&gt;)]);&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#4f4f4f;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/center&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Promise가 담긴 Array를 생성하여 처리하자.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 경우, 파일 3개를 읽도록 하였지만, 만약 임의의 여러 개의 파일을 처리해야 한다면 어떻게 해야할까? 임의의 길이를 가진 객체를 생성하여 이 객체를 처리하는 방식을 활용하면 된다. 다른 말로, 프로미스가 담긴 객체를 만들어 이를 한 번에 처리하면 된다. 다음 예시를 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#000000;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #4f4f4f&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#aaa;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;4&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;5&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;6&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;getFile&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;(file)&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;new&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;Promise&lt;/span&gt;(res&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;myFetch(file,&amp;nbsp;res));&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;fileArray&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;[&lt;span style=&quot;color:#82C961&quot;&gt;'file1'&lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;color:#82C961&quot;&gt;'file2'&lt;/span&gt;,&amp;nbsp;&lt;span style=&quot;color:#82C961&quot;&gt;'file3'&lt;/span&gt;,&amp;nbsp;...&amp;nbsp;,&amp;nbsp;&lt;span style=&quot;color:#82C961&quot;&gt;'fileN'&lt;/span&gt;];&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;fetchAll&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;fileArray.map(getFile);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;files&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;Promise&lt;/span&gt;.all(fetchAll);&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#4f4f4f;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/center&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;프로미스 여러 개를 array로 묶은 다음, 이를 map을 활용해서 파일 읽기를 병렬적으로 처리할 수 있도록 하였다. 이후 이터러블을 받는 프로미스 메서드를 활용하여 한 번에 파일을 array에 담아올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&amp;nbsp; Loop over Promises&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 경우 map을 활용하면 병렬적으로 처리할 수 있지만, 만약 프로미스를 순차적으로 해야한다면? 혹은 정해진 순서대로 프로미스를 수행해야 한다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;무엇을 순차적으로 할 때는 for ... in 문을 활용하거나 for ... of 문을 활용하여 순차적으로 처리할 수 있다. for ... of 문을 통해 프로미스를 처리하면 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#000000;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #4f4f4f&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#aaa;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;for&lt;/span&gt;&amp;nbsp;(&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;promise&amp;nbsp;of&amp;nbsp;promises)&amp;nbsp;{&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;console&lt;/span&gt;.log(await&amp;nbsp;promise);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;}&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align:right;margin-top:-13px;margin-right:5px;font-size:9px;font-style:italic&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;color:#4f4f4ftext-decoration:none&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#4f4f4f;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/center&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그러나, 이는 for문이 비동기 작업을 기다려 준다는 전제 하에 작성된 코드이다. 실제로 for문과 forEach는 모든 비동기 작업이 끝날 때 까지 기다려주지 않는다. 따라서, 다른 방법을 사용해야 한다. 이 경우 사용하는 문법이 for await ... of ... 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MDN에 따른 for await ... of ... 문은 다음과 같이 설명한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;for await ... of 구문은 보통 비동기에 대응하는 열거자를 나열할 때 쓰인다. ... 일반적인 for ... of 문과 마찬가지로 열거자 심볼이 정의한 속성을 실행하게 되어 열거한 값을 변수로 받아 처리한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서 위의 코드를 다시 작성하게 되면 원하는 순서대로 실행이 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#000000;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #4f4f4f&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#aaa;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;for&lt;/span&gt;&amp;nbsp;await&amp;nbsp;(&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;res&amp;nbsp;of&amp;nbsp;promises)&amp;nbsp;{&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;console&lt;/span&gt;.log(res);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;}&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align:right;margin-top:-13px;margin-right:5px;font-size:9px;font-style:italic&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;color:#4f4f4ftext-decoration:none&quot;&gt;Colored by Color Scripter&lt;/a&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#4f4f4f;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/center&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. Promise.allSettled()를 활용하자.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise의 allSettled 메서드는 ES2020에 도입된 메서드로, Promise.all()과는 유사하지만 조금 다른 동작은 하는 메서드이다. Promise.all()의 경우 여러 개의 프로미스 중 reject된 프로미스가 있다면 reject를 최종적으로 하고 첫 번째 rejected 프로미스를 반환하지만, 다른 프로미스를 처리하지는 못한다. 근데 다른 resolved된 프로미스에 대한 값을 받고 싶다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 방법은 catch 단에서 rejected된 프로미스를 따로 처리하여 다른 프로미스에 영향이 가지 않도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#000000;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #4f4f4f&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#aaa;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;4&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;fetchFiles&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;filesArray.map(file&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;fetchFile(file).&lt;span style=&quot;color:#E28964&quot;&gt;catch&lt;/span&gt;(()&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color:#82C961&quot;&gt;'rejected'&lt;/span&gt;));&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;result&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;Promise&lt;/span&gt;.all(fetchFiles);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;resolvedResult&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;result.filter(res&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;res&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color:#82C961&quot;&gt;'rejected'&lt;/span&gt;);&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#4f4f4f;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/center&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;만약 Promise.allSettled()를 사용하게 된다면 코드의 로직 분리가 매우 쉬워진다. 각 프로미스에 대하여 reject가 하나 발생하면, 전체적인 프로미스 결과는 rejected가 아니라 다른 프로미스를 모두 기다렸다가, 이에 대한 결과 array를 반환한다. Array 내부의 프로미스가 resolved면 결과를 담고, rejected이면 그 이유를 담는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Promised.allSettled()를 활용하면 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;center&gt;&lt;div class=&quot;colorscripter-code&quot; style=&quot;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important; position:relative !important;overflow:auto&quot;&gt;&lt;table class=&quot;colorscripter-code-table&quot; style=&quot;margin:0;padding:0;border:none;background-color:#000000;border-radius:4px;&quot; cellspacing=&quot;0&quot; cellpadding=&quot;0&quot;&gt;&lt;tr&gt;&lt;td style=&quot;padding:6px;border-right:2px solid #4f4f4f&quot;&gt;&lt;div style=&quot;margin:0;padding:0;word-break:normal;text-align:right;color:#aaa;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;line-height:130%&quot;&gt;1&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;2&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;3&lt;/div&gt;&lt;div style=&quot;line-height:130%&quot;&gt;4&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;padding:6px 0;text-align:left&quot;&gt;&lt;div style=&quot;margin:0;padding:0;color:#FFFFFF;font-family:Consolas, 'Liberation Mono', Menlo, Courier, monospace !important;line-height:130%&quot;&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;fetchFiles&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;filesArray.map(file&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;fetchFile(file));&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;result&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;await&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;Promise&lt;/span&gt;.allSettled(fetchFiles);&lt;/div&gt;&lt;div style=&quot;padding:0 6px; white-space:pre; line-height:130%&quot;&gt;&lt;span style=&quot;color:#E28964&quot;&gt;const&lt;/span&gt;&amp;nbsp;resolvedResult&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;result.filter(res&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&amp;gt;&lt;/span&gt;&amp;nbsp;res.status&amp;nbsp;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;!&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color:#E28964&quot;&gt;=&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color:#82C961&quot;&gt;'rejected'&lt;/span&gt;);&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;&lt;td style=&quot;vertical-align:bottom;padding:0 2px 4px 0&quot;&gt;&lt;a href=&quot;http://colorscripter.com/info#e&quot; target=&quot;_blank&quot; style=&quot;text-decoration:none;color:white&quot;&gt;&lt;span style=&quot;font-size:9px;word-break:normal;background-color:#4f4f4f;color:white;border-radius:10px;padding:1px&quot;&gt;cs&lt;/span&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/center&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 내용에서 핵심만 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;병렬적으로 처리하려면 await는 async한 함수를 모두 호출한 다면 뒤에 사용하자.&lt;/li&gt;
&lt;li&gt;Promise가 여러 개라면 Array에 담아 처리해라.&lt;/li&gt;
&lt;li&gt;Promise가 순차적으로 실행되어야 하면 for await ... of 문을 활용하자.&lt;/li&gt;
&lt;li&gt;모든 Promise에 대한 결과가 필요하다면 Promise.all() 대신 Promise.allSettled()를 활용하자.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이외에도 여러 종류의 비동기 처리 최적화가 있지만, 선험적인 최적화는 자칫 코드 흐름을 망칠 수 있으니 이 정도의 최적화만 한다면 흐름 내에 조금 더 빠른 비동기 함수 처리가 가능할 것이다.&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>async</category>
      <category>effective async</category>
      <category>JS</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/95</guid>
      <comments>https://0xffffffff.tistory.com/95#entry95comment</comments>
      <pubDate>Sun, 8 Jan 2023 12:34:03 +0900</pubDate>
    </item>
    <item>
      <title>[CSAPP] 2. Bomb lab (Assembly Reversing &amp;amp; GDB Tool) - Phase 5</title>
      <link>https://0xffffffff.tistory.com/94</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;다른 phase와 다르게, phase 5는 조금 특별하다. 왜 그런지는 문제와 함께 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하던대로, phase_5의 함수부터 분해하여보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669893701430&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0000000000401062 &amp;lt;phase_5&amp;gt;:
  401062:	53                   	push   %rbx
  401063:	48 83 ec 20          	sub    $0x20,%rsp
  401067:	48 89 fb             	mov    %rdi,%rbx
  40106a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  401071:	00 00 
  401073:	48 89 44 24 18       	mov    %rax,0x18(%rsp)
  401078:	31 c0                	xor    %eax,%eax
  40107a:	e8 9c 02 00 00       	callq  40131b &amp;lt;string_length&amp;gt;
  40107f:	83 f8 06             	cmp    $0x6,%eax
  401082:	74 4e                	je     4010d2 &amp;lt;phase_5+0x70&amp;gt;
  401084:	e8 b1 03 00 00       	callq  40143a &amp;lt;explode_bomb&amp;gt;
  401089:	eb 47                	jmp    4010d2 &amp;lt;phase_5+0x70&amp;gt;
  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx
  40108f:	88 0c 24             	mov    %cl,(%rsp)
  401092:	48 8b 14 24          	mov    (%rsp),%rdx
  401096:	83 e2 0f             	and    $0xf,%edx
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)
  4010a4:	48 83 c0 01          	add    $0x1,%rax
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax
  4010ac:	75 dd                	jne    40108b &amp;lt;phase_5+0x29&amp;gt;
  4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp)
  4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi
  4010b8:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi
  4010bd:	e8 76 02 00 00       	callq  401338 &amp;lt;strings_not_equal&amp;gt;
  4010c2:	85 c0                	test   %eax,%eax
  4010c4:	74 13                	je     4010d9 &amp;lt;phase_5+0x77&amp;gt;
  4010c6:	e8 6f 03 00 00       	callq  40143a &amp;lt;explode_bomb&amp;gt;
  4010cb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
  4010d0:	eb 07                	jmp    4010d9 &amp;lt;phase_5+0x77&amp;gt;
  4010d2:	b8 00 00 00 00       	mov    $0x0,%eax
  4010d7:	eb b2                	jmp    40108b &amp;lt;phase_5+0x29&amp;gt;
  4010d9:	48 8b 44 24 18       	mov    0x18(%rsp),%rax
  4010de:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  4010e5:	00 00 
  4010e7:	74 05                	je     4010ee &amp;lt;phase_5+0x8c&amp;gt;
  4010e9:	e8 42 fa ff ff       	callq  400b30 &amp;lt;__stack_chk_fail@plt&amp;gt;
  4010ee:	48 83 c4 20          	add    $0x20,%rsp
  4010f2:	5b                   	pop    %rbx
  4010f3:	c3                   	retq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;모든 함수가 그러하듯, 스택 공간 확보하고, old stack pointer 저장하고... 하는 과정을 거친 다음, 401078 줄을 수행하여 %eax를 0으로 초기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후, 다른 함수처럼 &quot;sscanf&quot; 대신 &quot;string_length&quot;라는 함수를 호출하여, 일단 문자열의 길이를 확인하는듯 하였다. 이 함수를 호출하기 전, %rdi에 담긴 정보를 %rbx에 옮기는 듯 하여, 옮기기 전 %rdi에 어떤 것이 담기는지 확인하여보자. 문자열 &quot;testin&quot;이라는 문자열을 넣고, 401063을 실행하기 전에 %rdi에 어떤 것이 담기는 지 확인하여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669894137936&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/s $rdi
0x6038c0 &amp;lt;input_strings+320&amp;gt;:   &quot;testin&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아하! &lt;b&gt;%rdi에는 처음에 넣은 문자열이 들어있는 것을 확인할 수 있다.&amp;nbsp;&lt;/b&gt;이제, 다른 퍼즐을 맞추러 가자. 문자열 &quot;string_length&quot; 이후에는 %rax의 값과 상수 0x6을 비교하는데, 만약 같지 않을 경우 폭탄이 터진다. 즉, %rax = 6이 담겨있어야 한다는 이야기다. 그럼, string_length 함수를 분해하여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669894633081&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;000000000040131b &amp;lt;string_length&amp;gt;:
  40131b:	80 3f 00             	cmpb   $0x0,(%rdi)
  40131e:	74 12                	je     401332 &amp;lt;string_length+0x17&amp;gt;
  401320:	48 89 fa             	mov    %rdi,%rdx
  401323:	48 83 c2 01          	add    $0x1,%rdx
  401327:	89 d0                	mov    %edx,%eax
  401329:	29 f8                	sub    %edi,%eax
  40132b:	80 3a 00             	cmpb   $0x0,(%rdx)
  40132e:	75 f3                	jne    401323 &amp;lt;string_length+0x8&amp;gt;
  401330:	f3 c3                	repz retq 
  401332:	b8 00 00 00 00       	mov    $0x0,%eax
  401337:	c3                   	retq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저, %rdi에 접근하여 0과 비교한다. 만약 0일 경우 401332로 건너뛰어, %rax에 0을 담고 return을 하게 된다. 만약 0이 아닐경우, %rdi의 주소값을 %rdx으로 옮긴 후, %rdx의 값을 1만큼 증가시킨다. 이후에 %rax에 %rdx를 옮긴 후, %rax에 %rdi를 빼준다. 그리고 나서 %rdx로 접근하였을 때 값이 0인지 체크한다. 만약 0이 아니라면 401323의 과정을 반복한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;repz retq는 branch prediction관련 instruction으로, 이 경우 40132b가 false일 때동안은 계속 반복된다고 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저, 40131b에서 %rdi에 담긴 값을 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669895328065&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/x $rdi
0x6038c0 &amp;lt;input_strings+320&amp;gt;:   0x74&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;0x74라는 hex 값을 추출하였다. 문자열 관련 함수이므로 이를 ascii로 변환하면, 소문자 &quot;t&quot;이다. 즉, 0x0임을 확인하는 것은 input이 0x0(null)인 경우를 확인한다고 할 수 있다. 이후 한 문자씩 돌면서, null char가 나올때까지 반복문이 순회되는 것을 알 수 있다. (C에서는 모든 문자열의 끝은 NULL(\0)이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번 input으로 &quot;testin&quot;이라는 문자열을 넣었으므로, 정말로 %rdi 근처에 이러한 값이 있는지 확인하자.&lt;/p&gt;
&lt;pre id=&quot;code_1669895538788&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/6x $rdi
0x6038c0 &amp;lt;input_strings+320&amp;gt;:   0x74    0x65    0x73    0x74    0x69    0x6e

(gdb) x/6s $rdi
0x6038c0 &amp;lt;input_strings+320&amp;gt;:   &quot;testin&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;아하!&amp;nbsp;&lt;/b&gt;정말로 문자열이 담겨있는 것을 확인할 수 있고, %rax에는 문자열의 길이가 담긴다는 것을 암시한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 본론으로 돌아와, &quot;string_length&quot; 함수를 호출한 다음, %rax와 상수 0x6을 비교한다. 만약 같을 경우 점프하고, 같지 않으면 폭탄이 터진다. 따라서, &lt;b&gt;입력 문자열의 길이는 6이라고 추측할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;6개 문자열이 들어온다면, 4010d2로 점프하여 %rax = 0을 한 다음, 40108b로 점프하고, %rcx = %rbx + %rax를 한다. 현재 %rax = 0이므로, %rcx = %rbx이라 할 수 있다. 이후 %rdx에 %cl을 넣은 뒤, &lt;b&gt;%rdx = %rdx &amp;amp; 0xf 연산을 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, %rdx의 값의 마지막 4비트를 추출하였고, 그 다음 instruction에서 %rdx += 0x4024b0을 한다. 이후 *(%rsp + %rax + 0x10) = %dl을 한 다음, %rax에 1을 더한 뒤 이 과정을 %rax = 6이 될때까지 한다. (6번의 loop을 암시한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;무슨 말인지 모르겠다고? 정상이다. 천천히 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일단, %rdx += 0x4024b0을 하고 있다. 일단, 0x4024b0의 값을 살펴보자!&lt;/p&gt;
&lt;pre id=&quot;code_1669896310078&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/16c 0x4024b0
0x4024b0 &amp;lt;array.3449&amp;gt;:  &quot;maduiersnfotvbyl&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;무언가 나왔고, 문자열은 &quot;maduiersnfotvbyl&quot;이다. 이걸 기억하고 가자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 과정을 다시 천천히 정리하여보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 401067에서 %rdi(&quot;testin&quot;의 시작 주소)를 %rbx로 옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. %rbx(&quot;testin&quot;의 시작 주소) + %rax를 %rcx에 옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. %rcx의 가장 오른쪽 8비트(%cl)를 %rdx로 옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. 이 중 하위 4비트만큼 뽑아낸다. (AND 연산의 의미이다. Data lab에서 많이 했지않나!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;5. %rdx에 0x4024b0을 더한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;6. %rdx의 가장 오른쪽 8비트(%dl)을 (%rsp+%rax+10)의 주소에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;7. 루프를 %rax = 6이될때까지 반복한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;무슨 말인지 아직도 모르겠는가? %rdx는 하나의 offset 역할을 하는 것을 알 수 있다. 즉, %rdx = 0이면 m, %rdx = 1이면 a, %rdx = 2이면 d... &lt;b&gt;즉, %rdx는 위의 문자열의 index 역할을 하는 것이다. 이를 차곡차곡 쌓아 stack 안에 쌓는 것이라고 추측할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼 무슨 문자열과 같아야 하는가? 일단 루프를 모두 돌았다는 가정 하에 나아가보자. 4010b3에서 %rsi 안에 0x40245e의 값이 들어가는 것을 확인할 수 있다. 이후 %rdi 안에는 (%rsp+10) (쌓은 문자열의 시작 메모리 주소, 왜인지는 위의 6번을 보면 이해가 갈것이다.)를 담고, strings_not_equal을 호출한다. 즉, %rsi 안에 넣는 0x40245e에 어떤 값이 들어가 있는 지 확인하여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4010b3까지 실행한 후, x 명령어를 사용하여 확인하면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1669897464349&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/s 0x40245e
0x40245e:       &quot;flyers&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우리의 목표는 어떤 문자열을 처리한 결과 문자열 &quot;flyers&quot;를 만드는 것이다. 해쉬 테이블과 같이 만들면 다음과 같이 해석할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669897627220&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hex index : 0	1	2	3	4	5	6	7	8	9	A	B	C	D	E	F
hash str  : m	a	d	u	i	e	r	s	n	f	o	t	v	b	y	l&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&quot;flyers&quot; 문자열은 idx 기준 9, F, E, 4, 5, 6이 나와야 한다. 따라서, input_string[i] &amp;amp; 0xf는 9, F, E, 4, 5, 6이 나와야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;ASCII 테이블을 바탕으로 다음과 같이 만들 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;0x69&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;0x6F&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;0x6E&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;0x64&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;0x65&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;0x66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;i&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;o&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;n&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;d&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;e&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;f&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;입력 문자열은 &quot;iondef&quot;가 가능하고, ASCII 기준 끝의 4비트만 맞추어 준다면 어떤 답이든 가능하다. 예를 들면, '9?&amp;gt;456&quot;도 가능한 정답이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;마지막으로, phase_5를 우리에게 익숙한 코드로 변환하여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669897965749&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void phase_5(const char* input) {

    if (string_length(input) != 6) {
        explode_bomb();
    }

    const char* hashmap = &quot;maduiersnfotvbyl&quot;;
    char arr[7];
    for (int i = 0; i &amp;lt; 6; i++) {
        arr[i] = hashmap[input[i] &amp;amp; 0xf];
    }
    
    if (string_not_equal(arr, &quot;flyers&quot;)) {
        explode_bomb();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제로 푸는데 진땀 좀 뺐던 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;답 : &quot;iondef&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>PS 이야기/CSAPP</category>
      <category>bomblab</category>
      <category>CSAPP</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/94</guid>
      <comments>https://0xffffffff.tistory.com/94#entry94comment</comments>
      <pubDate>Thu, 1 Dec 2022 21:33:24 +0900</pubDate>
    </item>
    <item>
      <title>[CSAPP] 2. Bomb lab (Assembly Reversing &amp;amp; GDB Tool) - Phase 4</title>
      <link>https://0xffffffff.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;이제까진 그래도 괜찮았다. 근데, phase 4부터는 난이도가 조금 어려워진다. 사실 꼼수는 많이 있지만, 정확히 알고가기에는어려운 부분이 많이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;각설은 여기까지만 하고, 일단 하던대로 함수 phase_4부터 분해하여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669886815128&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;000000000040100c &amp;lt;phase_4&amp;gt;:
  40100c:	48 83 ec 18          	sub    $0x18,%rsp
  401010:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  401015:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  40101a:	be cf 25 40 00       	mov    $0x4025cf,%esi
  40101f:	b8 00 00 00 00       	mov    $0x0,%eax
  401024:	e8 c7 fb ff ff       	callq  400bf0 &amp;lt;__isoc99_sscanf@plt&amp;gt;
  401029:	83 f8 02             	cmp    $0x2,%eax
  40102c:	75 07                	jne    401035 &amp;lt;phase_4+0x29&amp;gt;
  40102e:	83 7c 24 08 0e       	cmpl   $0xe,0x8(%rsp)
  401033:	76 05                	jbe    40103a &amp;lt;phase_4+0x2e&amp;gt;
  401035:	e8 00 04 00 00       	callq  40143a &amp;lt;explode_bomb&amp;gt;
  40103a:	ba 0e 00 00 00       	mov    $0xe,%edx
  40103f:	be 00 00 00 00       	mov    $0x0,%esi
  401044:	8b 7c 24 08          	mov    0x8(%rsp),%edi
  401048:	e8 81 ff ff ff       	callq  400fce &amp;lt;func4&amp;gt;
  40104d:	85 c0                	test   %eax,%eax
  40104f:	75 07                	jne    401058 &amp;lt;phase_4+0x4c&amp;gt;
  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp)
  401056:	74 05                	je     40105d &amp;lt;phase_4+0x51&amp;gt;
  401058:	e8 dd 03 00 00       	callq  40143a &amp;lt;explode_bomb&amp;gt;
  40105d:	48 83 c4 18          	add    $0x18,%rsp
  401061:	c3                   	retq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;매번 분석의 시작은 sscanf이고, 인수의 개수부터 파악을 하고 시작하였다. 함수 sscanf를 호출한 결과는 %rax이므로, 이 값과 상수 0x2를 비교하여 같지 않으면 폭탄이 터진다는 것을 파악하였다. &lt;b&gt;따라서, input 2개를 받는다는 것을 확인할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼, 이게 정수인지 아닌지 확인하기 위하여 phase_2때 사용한 방법을 다시 활용하여보자. Instruction 40101a까지 실행한 이후, %rsi에 담긴 문자열을 확인하여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669887053682&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/s $rsi
0x4025cf:       &quot;%d %d&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;정수 input 2개를 받는 것을 확인할 수 있다.&lt;/b&gt; 테스트를 위하여 &quot;100 200&quot;을 입력하여보자. 이후 instruction 0x40102e에서, 상수 0xe와 *(%rsp+0x8)을 비교하는 것을 볼 수 있다. 이때 jbe를 통해 비교하므로 x명령어로 *(%rsp+0x8)에 어떤 값이 들어있는지 먼저 확인하자.&lt;/p&gt;
&lt;pre id=&quot;code_1669887412846&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/d $rsp+8
0x7fffffffdf28: 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;첫 번째 input이 들어와 있음을 확인할 수 있다. 현재 폭탄이 터지지 않으려면 jbe가 true가 되어야 하므로, &lt;b&gt;첫 번째 input은 0xe = 14보다 작아야 함을 알 수 있다.&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 새로운 테스트로 &quot;1 345&quot;를 넣어보도록 하자. 무사히 통과가 되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후에 %edx 레지스터에 14, %esi 레지스터에 0, %edi 레지스터에 첫 번째 input을 넣고 func4 함수를 호출하는 모습을 볼 수 있다. 함수 호출 이후 %eax를 검사하여, jne (ZF = 0)일 경우 폭탄이 터지게 된다. (이는 phase_1에서 설명하였다.) 즉, func4 함수의 결과값으로 0이 나오지 않아야지 통과가 가능하다는 것을 유추할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼, 함수 func4를 분석하여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669891990120&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0000000000400fce &amp;lt;func4&amp;gt;:
  400fce:	48 83 ec 08          	sub    $0x8,%rsp
  400fd2:	89 d0                	mov    %edx,%eax
  400fd4:	29 f0                	sub    %esi,%eax
  400fd6:	89 c1                	mov    %eax,%ecx
  400fd8:	c1 e9 1f             	shr    $0x1f,%ecx
  400fdb:	01 c8                	add    %ecx,%eax
  400fdd:	d1 f8                	sar    %eax
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx
  400fe2:	39 f9                	cmp    %edi,%ecx
  400fe4:	7e 0c                	jle    400ff2 &amp;lt;func4+0x24&amp;gt;
  400fe6:	8d 51 ff             	lea    -0x1(%rcx),%edx
  400fe9:	e8 e0 ff ff ff       	callq  400fce &amp;lt;func4&amp;gt;
  400fee:	01 c0                	add    %eax,%eax
  400ff0:	eb 15                	jmp    401007 &amp;lt;func4+0x39&amp;gt;
  400ff2:	b8 00 00 00 00       	mov    $0x0,%eax
  400ff7:	39 f9                	cmp    %edi,%ecx
  400ff9:	7d 0c                	jge    401007 &amp;lt;func4+0x39&amp;gt;
  400ffb:	8d 71 01             	lea    0x1(%rcx),%esi
  400ffe:	e8 cb ff ff ff       	callq  400fce &amp;lt;func4&amp;gt;
  401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
  401007:	48 83 c4 08          	add    $0x8,%rsp
  40100b:	c3                   	retq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;연산을 하나씩 따라가보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;400fd2: %rax = %rdx&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;400fd4: %rax = %rax - %rsi&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;400df6: %rcx = %rax&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;400fd8: %rcx &amp;gt;&amp;gt;= 31 (%rax /= 2^31)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;400fdb: %rax += %rcx&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;400fdd: %rax &amp;gt;&amp;gt;= 1 ( %rax /= 2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;400fdf: %rcx = %rax + %rsi ( * 1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 이후부터는 test를 통해 분기점을 가른다. %rcx가 %rdi보다 크다면 400ff2로 점프하고, 아니라면 %rdx에 *(%rdx - 1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;가 담기고 func4를 재호출한다. &lt;b&gt;즉, 재귀함수 꼴이라는 이야기이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;함수 func4를 우리가 보기에 친숙한 형태로 C코드로 변환하면 다음과 같이 바꿀 수 있다. 위의 논리적인 구조를 천천히 따라와보자. 추가적인 정보로, shr라는 instruction을 쓴다는것 자체가 unsigned라는 것을 암시하고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1669892477213&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;unsigned int func4(unsigned int input, unsigned int* rdx, unsigned int* rsi) {
    unsigned int rax = ((*rdx - *rsi) + ((*rdx - *rsi) &amp;gt;&amp;gt; 31)) / 2;
    unsigned int rcx = rax + *rsi;
    
    if (rcx &amp;lt; input) {
    	*rsi = ++rcx;
        func4(input, rdx, rsi);
        return 2*rax + 1;
    } else if (rcx &amp;gt; input) {
        *rdx = --rcx;
        func4(input, rdx, rsi);
        return 2*rax + 1;
    } else {
        return 0;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;func4를 최초로 호출할 때 %rdx 레지스터에 14, %rsi 레지스터에 0, %rdi 레지스터에 첫 번째 input을 넣고 시작한다. 따라서, 위의 경우 최대한 재귀함수를 피하기 위해서는 rcx와 input값이 같아야 한다. 즉, rsi = 0이므로 rax와 input값이 같아야 하고, rdx에 14, rsi에 0을 대입하고 rax를 계산하여보면 rax = 0이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;즉, &lt;b&gt;첫 번째 input이 0이면 모든 문제가 아주 쉬워진다는 것이다. 재귀함수를 호출하지 않는 유일한 경우는, 첫 번째 input으로 0을 넣는 경우이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아하! 이제 첫 번째 input에 0을 넣어다고 치면 func4를 통해 %rax 레지스터 안에 7이 담기므로, 폭탄을 피할 수 있다. 이후, 401051에서 [cmpl 0x0 0xc(%rsp)]를 하므로, *(%rsp + 0xc)를 살펴보아야 한다. phase_3때의 기억을 떠올려 보면 두 번째 input이 있을 가능성이 아주 높고, 실제로 phase_3때 썼던 방법을 사용하면 두 번째 input이 저장되어 있는 것을 알 수 있다. cmpl이 true여야지 폭탄을 피할 수 있으므로, 두 번째 input은 0이어야 하는 것을 추측할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 첫 번째 input과 두 번째 input 모두 0이라 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;답 : 0 0&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼, &quot;첫 번째 input 0이 아닐 경우 재귀함수를 풀어서 그 값을 구해야하냐!&quot;라는 질문이 나올 수 있는데, 사실 func4를 복구하였으면, 다음과 같은 방법으로 답의 후보를 뽑을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1669893463628&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int solution() {
    for (int temp = 0; temp &amp;lt;= 15; temp++) {
        int res = func4(0xe, 0, temp);
        if (res == 0) {
            printf(&quot;%d &quot;, res);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;실제로 코드를 실행시켜보면, temp = 0, 1, 3, 7이 가능하다. 즉, 첫 번째 input으로는 0, 1, 3, 7이 가능하다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;답 : 0 0 / 1 0 / 3 0 / 7 0&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>PS 이야기/CSAPP</category>
      <category>bomblab</category>
      <category>CSAPP</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/93</guid>
      <comments>https://0xffffffff.tistory.com/93#entry93comment</comments>
      <pubDate>Thu, 1 Dec 2022 20:19:34 +0900</pubDate>
    </item>
    <item>
      <title>[CSAPP] 2. Bomb lab (Assembly Reversing &amp;amp; GDB Tool) - Phase 3</title>
      <link>https://0xffffffff.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;세 번째 phase를 살펴보자. 가보자 가보자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;phase_3 함수를 어셈블리어로 추출하여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669835061037&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0000000000400f43 &amp;lt;phase_3&amp;gt;:
  400f43:	48 83 ec 18          	sub    $0x18,%rsp
  400f47:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  400f4c:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi
  400f56:	b8 00 00 00 00       	mov    $0x0,%eax
  400f5b:	e8 90 fc ff ff       	call   400bf0 &amp;lt;__isoc99_sscanf@plt&amp;gt;
  400f60:	83 f8 01             	cmp    $0x1,%eax
  400f63:	7f 05                	jg     400f6a &amp;lt;phase_3+0x27&amp;gt;
  400f65:	e8 d0 04 00 00       	call   40143a &amp;lt;explode_bomb&amp;gt;
  400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp)
  400f6f:	77 3c                	ja     400fad &amp;lt;phase_3+0x6a&amp;gt;
  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax
  400f75:	ff 24 c5 70 24 40 00 	jmp    *0x402470(,%rax,8)
  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax
  400f81:	eb 3b                	jmp    400fbe &amp;lt;phase_3+0x7b&amp;gt;
  400f83:	b8 c3 02 00 00       	mov    $0x2c3,%eax
  400f88:	eb 34                	jmp    400fbe &amp;lt;phase_3+0x7b&amp;gt;
  400f8a:	b8 00 01 00 00       	mov    $0x100,%eax
  400f8f:	eb 2d                	jmp    400fbe &amp;lt;phase_3+0x7b&amp;gt;
  400f91:	b8 85 01 00 00       	mov    $0x185,%eax
  400f96:	eb 26                	jmp    400fbe &amp;lt;phase_3+0x7b&amp;gt;
  400f98:	b8 ce 00 00 00       	mov    $0xce,%eax
  400f9d:	eb 1f                	jmp    400fbe &amp;lt;phase_3+0x7b&amp;gt;
  400f9f:	b8 aa 02 00 00       	mov    $0x2aa,%eax
  400fa4:	eb 18                	jmp    400fbe &amp;lt;phase_3+0x7b&amp;gt;
  400fa6:	b8 47 01 00 00       	mov    $0x147,%eax
  400fab:	eb 11                	jmp    400fbe &amp;lt;phase_3+0x7b&amp;gt;
  400fad:	e8 88 04 00 00       	call   40143a &amp;lt;explode_bomb&amp;gt;
  400fb2:	b8 00 00 00 00       	mov    $0x0,%eax
  400fb7:	eb 05                	jmp    400fbe &amp;lt;phase_3+0x7b&amp;gt;
  400fb9:	b8 37 01 00 00       	mov    $0x137,%eax
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax
  400fc2:	74 05                	je     400fc9 &amp;lt;phase_3+0x86&amp;gt;
  400fc4:	e8 71 04 00 00       	call   40143a &amp;lt;explode_bomb&amp;gt;
  400fc9:	48 83 c4 18          	add    $0x18,%rsp
  400fcd:	c3                   	ret&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;길이가 길어진 거 같은 느낌이 들지만, 천천히 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;400f5b에서 sscanf 함수를 호출하고, 이후 %rax 레지스터와 상수 0x1을 비교한다. AT&amp;amp;T 기준이므로 만약 %rax가 1보다 클 경우, 400f6a로 점프하게 된다. 폭탄이 터지지 않으려면 jg가 true가 되어야 하고, 또한 phase_2에서 sscanf을 호출 한 뒤에 %rax에는 인수의 개수가 담긴다고 하였으므로, &lt;b&gt;최소한 두 개의 인수를 받는다는 것을 유추할 수 있다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 2개의 인수를 넣어보자. 테스트를 위하여 &quot;100 200&quot;를 넣어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 상수 0x7과 *[%rsp+0x8]을 비교하여, 오른쪽의 값이 더 크면 점프한다는 사실을 알 수 있다. 점프를 할 경우 폭탄이 터지기 때문에, 적어도 %rsp+0x8의 값은 7보다 작은 값을 지녀야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 일단 %rsp-8안에는 어떤 값이 들어가 있는지 확인하여보자. x 명령어를 이용하여 직접 확인해보면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1669883770629&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/d $rsp+8
0x7fffffffdf28: 100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아하! %rsp+8에는 첫 번째 input 정수가 들어가 있다는 것을 추측할 수 있다! 그러면, %rsp+4에는? 스택에 값이 push되는 원리를 생각해보면 두 번째 input 정수가 들어가 있을 확률이 매우 크다. 직접 찍어보자!&lt;/p&gt;
&lt;pre id=&quot;code_1669883895839&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/d $rsp+4
0x7fffffffdf24: 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;흠... 우리가 넣은 &quot;200&quot;이라는 정수 대신 다른 값이 들어가 있는 것을 확인할 수 있다. 따라서, 일단은 잠깐 넘기고, 계속하여 실행하여보자. 400f6f이 true일 경우 폭탄이 터지는 지점으로 jmp하므로, jg가 false가 나야한다. &lt;b&gt;즉, 첫 번째 input은 7보다 작은 숫자여야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼, 테스트를 위하여 &quot;1 345&quot;라는 값을 넣어보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우리가 분석하지 않은 지점부터 계속하여 실행하자. %rax 레지스터 안에 첫 번째 input 값을 옮긴 다음, jmpq를 통해 이상한 위치로 jmp한다. 도착해보니 400fb9에 도착하였고, %rax 안에 0x137 =&amp;nbsp; 311이란 값이 들어간다. 이후, *(%rsp+0xc)와 %rax의 값이 비교하여, 같지 않을 경우 폭탄이 터지는 것을 볼 수 있다. 그럼, 먼저 *(%rsp+0xc)에 어떤 값이 들어가는지 부터 살펴보아야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1669885044466&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/d $rsp+12
0x7fffffffdf2c: 200&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아하! 우리가 넣은 두 번째 input은 *(%rsp+0xc)에 위치해 있는 것까지 확인하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;정리하자면, 첫 번째 input은 1과 비교, 두 번째 input은 %rax = 311과 비교하여, 하나라도 다르면 폭탄이 터진다고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;답은 쉽게 구할 수 있었지만, 아직 완전한 이해를 하지 않았으므로 미심쩍은 부분을 파헤쳐보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;위의 설명에서 뭉게면서 넘어간 부분은 해당 instruction이다.&lt;/p&gt;
&lt;pre id=&quot;code_1669885175869&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;분석해보면, (0x402470 + %rax * 8)의 메모리 주소로 점프하라는 내용이다. 그럼, 0x402470에서 앞뒤로 담긴 내용을 살펴보면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1669885683955&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/16gx 0x402470
0x402470:       0x0000000000400f7c      0x0000000000400fb9
0x402480:       0x0000000000400f83      0x0000000000400f8a
0x402490:       0x0000000000400f91      0x0000000000400f98
0x4024a0:       0x0000000000400f9f      0x0000000000400fa6
0x4024b0 &amp;lt;array.3449&amp;gt;:  0x737265697564616d      0x6c796276746f666e
0x4024c0:       0x7420756f79206f53      0x756f79206b6e6968
0x4024d0:       0x6f7473206e616320      0x6f62206568742070
0x4024e0:       0x206874697720626d      0x202c632d6c727463&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;여러 개의 메모리 주소가 나와있다. 예를 들어, %rax에 1이 담겨있을 경우 0x402478의 값인 0x400fb9로 점프하고, %rax에 2가 담겨 있을 경우 0x402480의 값인 0x400f83으로 점프한다. 이러한 점프 테이블을 가지고 있는, 우리에게 친숙한 문법이 있지 않나?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그렇다. &lt;b&gt;Phase 3는 switch문으로 구현되어 있는 함수라 할 수 있다.&lt;/b&gt; 우리에게 친숙한 코드로 변환하면 다음과 같이 변환할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1669886133100&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;unsigned int input1, input2;
unsigned int res;

sscanf(&quot;%d %d&quot;, &amp;amp;input1, &amp;amp;input2);

if (input1 &amp;gt; 7) {
   explode_bomb();
}
else {
    switch(input1) {
    	case 0:
            res = 0xcf; // 207
            break;
        case 1:
            res = 0x137; // 311
             break;
        case 2:
            res = 0x2c3; // 707
            break;
        case 3:
            res = 0x100; // 256
            break;
        case 4:
            res = 0x185; // 389
            break;
        case 5:
       	    res = 0xce; // 206
            break;
        case 6:
            res = 0x2aa; // 682
            break;
        case 7:
            res = 0x147; // 327
            break;
     }
     
     if (input2 == res) {
     	return;
     } else {
     	explode_bomb();
     }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;답은 해당 input1과 input2를 잘 대응시켜 입력한다면, 총 7가지의 답이 존재할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;답 : 0 207 / 1 311 / 2 707 / 3 256 / 4 389 / 5 206 / 6 682 / 7 327&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아직까진 할만한가?&lt;/span&gt;&lt;/p&gt;</description>
      <category>PS 이야기/CSAPP</category>
      <category>bomblab</category>
      <category>CSAPP</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/92</guid>
      <comments>https://0xffffffff.tistory.com/92#entry92comment</comments>
      <pubDate>Thu, 1 Dec 2022 18:23:27 +0900</pubDate>
    </item>
    <item>
      <title>[CSAPP] 2. Bomb lab (Assembly Reversing &amp;amp; GDB Tool) - Phase 2</title>
      <link>https://0xffffffff.tistory.com/91</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;두 번째 phase를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일단, phase_2를 &quot;(gdb) $ disas phase_2&quot; 명령어로 어셈블리어를 추출하여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669832448829&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0000000000400efc &amp;lt;phase_2&amp;gt;:
  400efc:	55                   	push   %rbp
  400efd:	53                   	push   %rbx
  400efe:	48 83 ec 28          	sub    $0x28,%rsp
  400f02:	48 89 e6             	mov    %rsp,%rsi
  400f05:	e8 52 05 00 00       	call   40145c &amp;lt;read_six_numbers&amp;gt;
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  400f0e:	74 20                	je     400f30 &amp;lt;phase_2+0x34&amp;gt;
  400f10:	e8 25 05 00 00       	call   40143a &amp;lt;explode_bomb&amp;gt;
  400f15:	eb 19                	jmp    400f30 &amp;lt;phase_2+0x34&amp;gt;
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax
  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 &amp;lt;phase_2+0x29&amp;gt;
  400f20:	e8 15 05 00 00       	call   40143a &amp;lt;explode_bomb&amp;gt;
  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 &amp;lt;phase_2+0x1b&amp;gt;
  400f2e:	eb 0c                	jmp    400f3c &amp;lt;phase_2+0x40&amp;gt;
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp
  400f3a:	eb db                	jmp    400f17 &amp;lt;phase_2+0x1b&amp;gt;
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	ret&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;흠... 일단 중요한 부분은 함수 &quot;read_six_numbers&quot;부터 시작하는 것 같다. 앞의 부분은 스택 관련 operation이고, 실제로 400f05 instruction까지 실행하고 나면 이때부터 농간이 시작되는 것으로 추정된다. 그럼, 일단 &quot;read_six_numbers&quot;를 보고 오는 게 인지상정. 이 함수부터 분해하여보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669832820708&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;000000000040145c &amp;lt;read_six_numbers&amp;gt;:
  40145c:	48 83 ec 18          	sub    $0x18,%rsp
  401460:	48 89 f2             	mov    %rsi,%rdx
  401463:	48 8d 4e 04          	lea    0x4(%rsi),%rcx
  401467:	48 8d 46 14          	lea    0x14(%rsi),%rax
  40146b:	48 89 44 24 08       	mov    %rax,0x8(%rsp)
  401470:	48 8d 46 10          	lea    0x10(%rsi),%rax
  401474:	48 89 04 24          	mov    %rax,(%rsp)
  401478:	4c 8d 4e 0c          	lea    0xc(%rsi),%r9
  40147c:	4c 8d 46 08          	lea    0x8(%rsi),%r8
  401480:	be c3 25 40 00       	mov    $0x4025c3,%esi
  401485:	b8 00 00 00 00       	mov    $0x0,%eax
  40148a:	e8 61 f7 ff ff       	call   400bf0 &amp;lt;__isoc99_sscanf@plt&amp;gt;
  40148f:	83 f8 05             	cmp    $0x5,%eax
  401492:	7f 05                	jg     401499 &amp;lt;read_six_numbers+0x3d&amp;gt;
  401494:	e8 a1 ff ff ff       	call   40143a &amp;lt;explode_bomb&amp;gt;
  401499:	48 83 c4 18          	add    $0x18,%rsp
  40149d:	c3                   	ret&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;함수 이름 자체는 굉장히 직관적이지만, 그래도 공부 목적으로 한번 꼼꼼하게 따져보자. 우리에게 익숙한 sscanf 함수를 호출하기 전, lea 명령어와 mov 명령어로 %rsi, %rax 레지스터에 값을 넣는 모습을 확인할 수 있다. 0x401485까지 실행시킨 이후, %rsi 레지스터 안에 무엇을 넣는 지 확인하자.&lt;/p&gt;
&lt;pre id=&quot;code_1669833187964&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/s $rsi
0x4025c3:       &quot;%d %d %d %d %d %d&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아하! scanf에서 익숙한 문구가 보인다. 해석해보면 입력값은 6개의 정수라고 추측할 수 있다. 또한, sscanf를 의 반환값이 담긴 레지스터 %rax에는 입력한 정수의 개수가 담기는 것을 확인할 수 있다. 이는 여러 번의 노가다로 알아낸 사실이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;0x40148f에서 [cmp $0x5 %eax]를 수행하여 jg를 통해 점프하지 않을 경우 폭탄이 터지고, 점프할 경우 0x401499로 가므로 반드시 점프를 해야한다. &lt;b&gt;즉, 입력값은 6개를 넘어야 한다. &lt;/b&gt;어차피 6개보다 많은 입력을 하였을 경우 뒤의 입력은 잘리기 때문에 6개를 입력한 것과 같은 효과이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제, read_six_numbers를 분석하였으므로 다시 원래의 함수 phase_2로 돌아오도록 하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669833581260&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
  400f05:	e8 52 05 00 00       	call   40145c &amp;lt;read_six_numbers&amp;gt;
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  400f0e:	74 20                	je     400f30 &amp;lt;phase_2+0x34&amp;gt;
  400f10:	e8 25 05 00 00       	call   40143a &amp;lt;explode_bomb&amp;gt;
  400f15:	eb 19                	jmp    400f30 &amp;lt;phase_2+0x34&amp;gt;
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax
  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 &amp;lt;phase_2+0x29&amp;gt;
  400f20:	e8 15 05 00 00       	call   40143a &amp;lt;explode_bomb&amp;gt;
  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 &amp;lt;phase_2+0x1b&amp;gt;
  400f2e:	eb 0c                	jmp    400f3c &amp;lt;phase_2+0x40&amp;gt;
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp
  400f3a:	eb db                	jmp    400f17 &amp;lt;phase_2+0x1b&amp;gt;
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	ret&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;폭탄이 터지는 곳을 보면 0x400f10, 0x400f20이다. 따라서, 이 부분은 건들지 않도록 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저, %rsp에 담긴 값과 0x1을 비교한다. 테스트를 위하여 &quot;&lt;u&gt;10 20 40 80 160 320&lt;/u&gt;&quot; 이란 글자를 넣고, x 명령어로 %rsp에 담긴 값을 확인하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1669834107285&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(gdb) x/d $rsp
0x7fffffffd8e0: 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;흠... &lt;b&gt;%rsp에는 첫 input에 대한 메모리 주소를 가지고 있다고 유추할 수 있다.&lt;/b&gt; 0x1과 같아야지 폭탄이 터지지 않으므로, &lt;b&gt;첫&lt;/b&gt; &lt;b&gt;번째 input 정수는 1이라 할 수 있다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;0x400f0e에서의 [je 400f30] 부터 살펴보면, %rbx에 *(%rsp+0x4)을 담는다. 다음으로 [jmp 400f17]을 수행하고,&amp;nbsp; %rax에 *(%rbx-0x4)를 담는다. 이후 %rax 자기 자신을 더한 후, %rbx의 메모리 주소와 %rax를 비교한다. 같지 않으면 폭탄이 터지므로 같아야 한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;생각해보자. %rsp에는 첫 input에 대한 메모리 주소를 가지고 있고, 현재 정수를 input으로 받으므로 이를 저장한 버퍼는 정수의 크기인 4바이트 단위로 건너뛸 것이다. %rbx는 0x4씩 더해지므로, &lt;b&gt;%rbx는 input 버퍼를 하나씩 index를 늘려가며 순회한다는 추측을 할 수 있다. 그리고 %rax에는 %rbx-0x4를 주소로 한 값을 담으므로, %rbx가 순회하는 배열의 (index-1) 값이라 할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이후 %rax는 자기 자신을 더하고 이를 index에 담긴 값과 비교하므로, 우리에게 친숙한 언어로 표현하면 &lt;b&gt;arr[i-1] + arr[i-1] == arr[i]&lt;/b&gt; 인지를 확인하는 것이다. 이 과정을 5번 반복하며 총 6개의 input을 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;바꿔 말하면, 각 input은 전 input의 2배여야 한다는 의미가 된다.&amp;nbsp;&lt;/b&gt;어려워 보이는 코드가 단순 배열 순환의 의미를 지니고 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우리에게 친숙한 c 코드로 변환하면 다음과 같이 쓸 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1669884944056&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int arr[6];

if (arr[0] != 1) {
	explode_bomb();
 }
 
 for (int i = 1; i &amp;lt; 6; i++) {
 	if (arr[i] != arr[i-1]*2) {
    	explode_bomb();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;첫 번째 input은 1로 고정되어 있으므로, 두 번째 input은 1+1 = 2, 세 번쨰 input은 2+2 = 4 ... 이런식으로 답을 구하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;답 : 1 2 4 8 16 32&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>PS 이야기/CSAPP</category>
      <category>bomblab</category>
      <category>CSAPP</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/91</guid>
      <comments>https://0xffffffff.tistory.com/91#entry91comment</comments>
      <pubDate>Thu, 1 Dec 2022 04:01:03 +0900</pubDate>
    </item>
    <item>
      <title>[CSAPP] 2. Bomb lab (Assembly Reversing &amp;amp; GDB Tool) - Settings &amp;amp; Phase 1</title>
      <link>https://0xffffffff.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;CSAPP 랩의 두 번째 실습은 bomb lab이다. &quot;폭탄 랩&quot;이라는 무시무시한 이름을 달고 있고, 실제 난이도도 무시무시하게 어렵다. 왜냐면 object code를 리버싱하여, 각 레지스터에 담긴 데이터를 확인한 다음, 각 레지스터에 알맞은 값이 있도록 해야하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 랩의 목적은 binary 파일인 &quot;bomb&quot;을 해제하는 것이다. 정확히 말하면, 각 함수에 대하여 알맞은 input을 넣어주어야 하며, 알맞은 input은 어셈블리어를 분석하여 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번 실습의 특이한 점으로는 실수를 허용하지 않는다는 것이다. &lt;span&gt;만약 잘못된 input을 넣을 경우, &quot;폭탄이 터진다&quot;.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;실시간으로 폭탄이 터지는 횟수를 세며, 만약 폭탄이 터질 경우 0.5점이 감점된다. 즉 2번 터뜨릴때마다 1점 감점이라는 이야기란 말씀.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다만, 폭탄이 터지는 것을 막는 것은 gdb tool을 이용하면 쉽게 할 수 있다. 각 함수들은 직관적인 이름을 가지고 있고, (예를 들면 explode_bomb은 폭탄을 터뜨리는 함수이고, read_six_numbers는 6개의 정수를 읽는 함수이다.) 폭탄이 터지는 함수에 breakpoint을 걸거나, 하나의 함수에 breakpoint을 걸고 instruction 하나하나씩 천천히 실행하면서 함수가 어떤 control flow를 가지는지 확인하면서 해도 된다. 어느 것을 선택하든 자유다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;분석해야 하는 함수는 총 6개로, 총 6개의 phase가 있다고 해도 무방하다. 6개의 phase는 각각 다른 테마를 가지고 있으며, 모두 자주 사용하는 함수들이다. 다만 어셈블리어로 되어있어 우리가 많이 사용하는 함수라고 알아차리는 것은 눈썰미가 좋지 않은 이상 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;0. 폭탄 해체에 앞서&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이번 실습에서는 어셈블리어에 대한 내용이므로 이와 관련된 함수들을 알아보아야 한다. 어셈블리어는 AT&amp;amp;T 기준으로 작성되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;0-1) Objdump&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Objdump는 object file을 dump해주는 함수로, 뒤의 추가 옵션에 따라 다른 결과를 얻을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1669628683206&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ objdump -t bomb&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Objdump -t 옵션을 실행하면 bomb의 symbol table을 얻을 수 있다. Symbol table 안에는 bomb 안의 전역 변수와 모든 함수에 대한 정보, 각 함수의 주소를 알 수 있으므로 각 함수의 이름으로 어떤 phase인지 유추할 수 있다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;pre id=&quot;code_1669628763458&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ objdump -d bomb

$ objdump -d bomb &amp;gt; bomb.s (to asm file)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;-d 옵션은 binary file을 어셈블리어로 바꾸어주는 명령어이다. 이 어셈블리의 control flow를 따라가다 보면, 어떤 역할을 하는 코드인 지 알 수 있을 것이다. 하지만, 어셈블리어가 모든 것을 말해주진 않으니 본인만의 추리력으로 각 함수가 어떤 역할을, 어떤 레지스터에 어떤 정보를 가지고 있는지 알아내야한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예를 들면, sscanf 함수는 다음과 같은 방식으로 출력된다.&lt;/p&gt;
&lt;pre id=&quot;code_1669628935085&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;8048c36 : e8 99 fc ff ff	call	80488d4 &amp;lt;_init+0x1a0&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서 call instruction이 어떤 함수를 호출하는지 제대로 알아보기 위해서는, gdb를 이용하여 disassemble 해봐야한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;pre id=&quot;code_1669629088264&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ strings bomb&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Strings 명령어는 bomb 안에 담겨있는 모든 출력 가능 문자열을 보여준다. 대부분은 쓸모 없는 정보이지만, 안을 뒤지다보면 쓸모있는 정보를 찾을 수도?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;0-2) GDB&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;GDB는 디버깅 툴이다. 프로그램을 실행하면서 레지스터 혹은 메모리 주소와 같은 정보를 얻을 수 있는 도구라 생각하면 된다. 다음과 같이 명령어를 입력하여 bomb 관련 gdb를 실행할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1669629235328&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gdb bomb&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;gdb와 관련된 명령어는 너무나도 많으니, 이에 대한 항목은 구글링해서 찾아보면 알 수 있다. 다만, 몇 가지 주요 명령어들을 모아보면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1669629590912&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ run(=r) &amp;lt;args&amp;gt; : &amp;lt;args&amp;gt; 인자를 넘긴 상태로 프로그램을 실행한다.

$ break(=b) &amp;lt;location&amp;gt; : 프로그램이 &amp;lt;location&amp;gt;전까지 실행되고 &amp;lt;location&amp;gt;에 멈춘다.
이때 location은 실제 메모리 주소이거나, 함수 이름이거나, 줄을 넣을 수도 있다.

$ step(=s) &amp;lt;cnt&amp;gt; / next(=n) &amp;lt;cnt&amp;gt; : &amp;lt;cnt&amp;gt;줄만큼 프로그램을 실행할 수 있다.
s와 n의 차이는 s는 그 줄까지 실행하고, n은 그 줄 전까지 실행한다.

$ continue(=c) : 다음 breakpoint까지 실해한다.

$ info register : 모든 레지스터 안에 담긴 값을 hex로 표현한다.
$ info stack : stack의 backtrace를 보여준다.
$ info breakpoint : breakpoint 상태를 알려준다.

$ print(=p) (/x or /d) (register or address) : 레지스터 혹은 메모리에 담긴 값을 hex(/x) 혹은 dec(/d)으로 나타낸다.

$ x ($register or 0x....) : 레지스터 혹은 메모리에 어떤 값이 담겨있는지 살펴본다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;어셈블리 지옥에 온 것을 환영한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(중요! 여러분들이 받은 bomb은 저와 다른 bomb이고, 다른 답을 가지고 있을 겁니다! 저의 답을 그대로 입력하시지 말고, 논리적인 흐름을 따라와 주세요!)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Phase 1&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Objdump을 실행하여 &amp;lt;phase_1&amp;gt; 함수의 어셈블리어를 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1669630333204&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0000000000400ee0 &amp;lt;phase_1&amp;gt;:
  400ee0:	48 83 ec 08          	sub    $0x8,%rsp
  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi
  400ee9:	e8 4a 04 00 00       	call   401338 &amp;lt;strings_not_equal&amp;gt;
  400eee:	85 c0                	test   %eax,%eax
  400ef0:	74 05                	je     400ef7 &amp;lt;phase_1+0x17&amp;gt;
  400ef2:	e8 43 05 00 00       	call   40143a &amp;lt;explode_bomb&amp;gt;
  400ef7:	48 83 c4 08          	add    $0x8,%rsp
  400efb:	c3                   	ret&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;핵심 줄을 보면, %esi 레지스터 안에 0x402400이 담기는 것을 확인할 수 있다. 그리고 함수 &quot;strings_not_equal&quot;을 호출하고, test를 통해 %eax를 테스트한 이후, 그 결과 같으면 0x17만큼 만큼, 아닐 경우 계속 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;explode_bomb는 je가 false일 때 실행되므로, [test %eax, %eax]에서 반드시 true가 나와야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;je는 ZF가 1일 경우 실행된다. 또한, test 명령어는 두 피연산자(여기서 %eax 레지스터)에 대하여 AND 연산을 수행한다. 따라서, %eax가 0일 경우 0, 1일 경우 1을 return하므로 %eax의 값이 0인지 아닌지 판단하는 구절이라 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결국, &amp;lt;strings_not_equal&amp;gt;의 반환값이 1이 되면 je 명령어가 수행되지 않아 폭탄이 터지게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼, strings_not_equal의 구조를 살피러 가보자!&lt;/p&gt;
&lt;pre id=&quot;code_1669831551621&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0000000000401338 &amp;lt;strings_not_equal&amp;gt;:
  401338:	41 54                	push   %r12
  40133a:	55                   	push   %rbp
  40133b:	53                   	push   %rbx
  40133c:	48 89 fb             	mov    %rdi,%rbx
  40133f:	48 89 f5             	mov    %rsi,%rbp
  401342:	e8 d4 ff ff ff       	call   40131b &amp;lt;string_length&amp;gt;
  401347:	41 89 c4             	mov    %eax,%r12d
  40134a:	48 89 ef             	mov    %rbp,%rdi
  40134d:	e8 c9 ff ff ff       	call   40131b &amp;lt;string_length&amp;gt;
  401352:	ba 01 00 00 00       	mov    $0x1,%edx
  401357:	41 39 c4             	cmp    %eax,%r12d
  40135a:	75 3f                	jne    40139b &amp;lt;strings_not_equal+0x63&amp;gt;
  40135c:	0f b6 03             	movzbl (%rbx),%eax
  40135f:	84 c0                	test   %al,%al
  401361:	74 25                	je     401388 &amp;lt;strings_not_equal+0x50&amp;gt;
  401363:	3a 45 00             	cmp    0x0(%rbp),%al
  401366:	74 0a                	je     401372 &amp;lt;strings_not_equal+0x3a&amp;gt;
  401368:	eb 25                	jmp    40138f &amp;lt;strings_not_equal+0x57&amp;gt;
  40136a:	3a 45 00             	cmp    0x0(%rbp),%al
  40136d:	0f 1f 00             	nopl   (%rax)
  401370:	75 24                	jne    401396 &amp;lt;strings_not_equal+0x5e&amp;gt;
  401372:	48 83 c3 01          	add    $0x1,%rbx
  401376:	48 83 c5 01          	add    $0x1,%rbp
  40137a:	0f b6 03             	movzbl (%rbx),%eax
  40137d:	84 c0                	test   %al,%al
  40137f:	75 e9                	jne    40136a &amp;lt;strings_not_equal+0x32&amp;gt;
  401381:	ba 00 00 00 00       	mov    $0x0,%edx
  401386:	eb 13                	jmp    40139b &amp;lt;strings_not_equal+0x63&amp;gt;
  401388:	ba 00 00 00 00       	mov    $0x0,%edx
  40138d:	eb 0c                	jmp    40139b &amp;lt;strings_not_equal+0x63&amp;gt;
  40138f:	ba 01 00 00 00       	mov    $0x1,%edx
  401394:	eb 05                	jmp    40139b &amp;lt;strings_not_equal+0x63&amp;gt;
  401396:	ba 01 00 00 00       	mov    $0x1,%edx
  40139b:	89 d0                	mov    %edx,%eax
  40139d:	5b                   	pop    %rbx
  40139e:	5d                   	pop    %rbp
  40139f:	41 5c                	pop    %r12
  4013a1:	c3                   	ret&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이 함수를 잘 보면, 두 문자열의 길이을 통해 비교하여 같은 문자열이면 0, 같지 않은 문자열이면 1을 반환하는 구조를 띄고 있다. 굳이 모든 글자를 해석하지 않아도, %rdi, %rsi, %eax와 %edx에 어떤 값이 담겨있는지를 중심으로 살펴보자.&lt;b&gt;&amp;nbsp;(지금 이해가 안가도 뒤에서 이해될 것이다!)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;따라서, 두 문자열은 같아야 폭탄이 터지지 않는다는 결론에 이르게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 본래의 phase_1을 보자. 원래 %rdi, %rsi에는 함수의 인자값(arguments)가 들어가는 것이 국룰이다. 따라서, x/s 명령어를 통해 어떤 값이 들어있는지 살펴보자. 400ee4까지 실행한 후, 각각에 대한 string에 담긴 값을 보면 다음과 같다. 테스트를 위하여 &quot;testString&quot;을 넣어주었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcjaJc/btrSyif68qS/JFGUK0U5NxgbzNYxBa85r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcjaJc/btrSyif68qS/JFGUK0U5NxgbzNYxBa85r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcjaJc/btrSyif68qS/JFGUK0U5NxgbzNYxBa85r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcjaJc%2FbtrSyif68qS%2FJFGUK0U5NxgbzNYxBa85r1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;433&quot; height=&quot;64&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아하!&lt;b&gt; %rdi 레지스터에는 우리가 넣어준 값을, %rsi 레지스터에는 메모리 0x402400에서 문자열을 가져와, 이 둘을 비교하는구나!&lt;/b&gt; 라고 결론지을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그럼 이걸 바탕으로 첫 번째 답으로 문자열을 넣어주면? 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;답 : Border&amp;nbsp;relations&amp;nbsp;with&amp;nbsp;Canada&amp;nbsp;have&amp;nbsp;never&amp;nbsp;been&amp;nbsp;better.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>PS 이야기/CSAPP</category>
      <category>bomblab</category>
      <category>CSAPP</category>
      <author>whatisyourname</author>
      <guid isPermaLink="true">https://0xffffffff.tistory.com/90</guid>
      <comments>https://0xffffffff.tistory.com/90#entry90comment</comments>
      <pubDate>Thu, 1 Dec 2022 03:19:12 +0900</pubDate>
    </item>
  </channel>
</rss>