<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>GoWoong의 개발 블로그</title>
    <link>https://gowoong.tistory.com/</link>
    <description>2022년 커리어를 시작한 개발자 고웅의 기록 공간</description>
    <language>ko</language>
    <pubDate>Mon, 29 Jun 2026 05:37:47 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>고웅</managingEditor>
    <image>
      <title>GoWoong의 개발 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/5293482/attach/3c4ade3ab2754fd08b45bf14dbea765b</url>
      <link>https://gowoong.tistory.com</link>
    </image>
    <item>
      <title>[SMTP] 6. 메일은 어디에 남아야 할까</title>
      <link>https://gowoong.tistory.com/240</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서는 DATA 명령을 구현했다.&lt;/p&gt;
&lt;p data-end=&quot;325&quot; data-start=&quot;273&quot; data-ke-size=&quot;size16&quot;&gt;이제 SMTP 클라이언트는 서버에게 봉투를 건넨 뒤, 실제 메일 내용을 보낼 수 있게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;325&quot; data-start=&quot;273&quot; data-ke-size=&quot;size16&quot;&gt;이 흐름만 보면 꽤 그럴듯하다.&lt;/p&gt;
&lt;p data-end=&quot;750&quot; data-start=&quot;615&quot; data-ke-size=&quot;size16&quot;&gt;서버는 MAIL FROM으로 봉투 발신자를 받았다.&lt;br /&gt;RCPT TO로 봉투 수신자도 받았다.&lt;br /&gt;DATA 이후에는 제목과 본문이 들어 있는 메일 내용도 받았다.&lt;br /&gt;그리고 마지막 마침표 한 줄을 만나면 250 OK를 돌려줬다.&lt;/p&gt;
&lt;p data-end=&quot;776&quot; data-start=&quot;752&quot; data-ke-size=&quot;size16&quot;&gt;하지만 지난 구현에는 중요한 빈틈이 있었다.&lt;/p&gt;
&lt;p data-end=&quot;809&quot; data-start=&quot;778&quot; data-ke-size=&quot;size16&quot;&gt;메일을 받기는 했지만, 그 메일은 어디에도 남지 않았다.&lt;/p&gt;
&lt;p data-end=&quot;824&quot; data-start=&quot;811&quot; data-ke-size=&quot;size16&quot;&gt;로그에 찍히고 끝이었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;메일 수신 완료:
From: Alice &amp;lt;alice@example.com&amp;gt;
To: Bob &amp;lt;bob@example.net&amp;gt;
Subject: Hello

Hello SMTP.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;947&quot; data-start=&quot;935&quot; data-ke-size=&quot;size16&quot;&gt;서버 화면에는 보인다.&lt;/p&gt;
&lt;p data-end=&quot;1000&quot; data-start=&quot;949&quot; data-ke-size=&quot;size16&quot;&gt;하지만 서버를 종료하면 사라진다.&lt;br /&gt;로그를 지우면 사라진다.&lt;br /&gt;다시 꺼내볼 방법도 없다.&lt;/p&gt;
&lt;p data-end=&quot;1026&quot; data-start=&quot;1002&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 이 서버는 정말 메일을 받은 걸까?&lt;/p&gt;
&lt;p data-end=&quot;1048&quot; data-start=&quot;1028&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이 질문에서 시작한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1072&quot; data-start=&quot;1050&quot; data-section-id=&quot;dcybyc&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;로그에 찍힌 메일은 메일함이 아니다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1102&quot; data-start=&quot;1074&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 로그에 찍히는 것만으로도 꽤 만족스러웠다.&lt;/p&gt;
&lt;p data-end=&quot;1168&quot; data-start=&quot;1104&quot; data-ke-size=&quot;size16&quot;&gt;직접 만든 SMTP 서버가 클라이언트와 대화했고, DATA까지 읽었고, 본문 종료를 나타내는 마침표도 알아봤다.&lt;/p&gt;
&lt;p data-end=&quot;1184&quot; data-start=&quot;1170&quot; data-ke-size=&quot;size16&quot;&gt;그 자체로 큰 진전이었다.&lt;/p&gt;
&lt;p data-end=&quot;1221&quot; data-start=&quot;1186&quot; data-ke-size=&quot;size16&quot;&gt;하지만 조금만 생각해 보면 로그는 메일을 저장하는 장소가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1253&quot; data-start=&quot;1223&quot; data-ke-size=&quot;size16&quot;&gt;로그는 서버가 무슨 일을 했는지 남기는 기록에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;1364&quot; data-start=&quot;1255&quot; data-ke-size=&quot;size16&quot;&gt;메일 내용이 로그에 찍혔다고 해서, 그 메일이 사용자의 메일함에 들어간 것은 아니다.&lt;br /&gt;나중에 사용자가 읽을 수 있는 형태로 보관된 것도 아니다.&lt;br /&gt;서버가 책임지고 보관한다고 말하기도 어렵다.&lt;/p&gt;
&lt;p data-end=&quot;1414&quot; data-start=&quot;1366&quot; data-ke-size=&quot;size16&quot;&gt;SMTP에서 메일을 받는다는 것은 단순히 문자열을 한 번 읽는 것보다 조금 더 무겁다.&lt;/p&gt;
&lt;p data-end=&quot;1645&quot; data-start=&quot;1416&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5321은 SMTP가 메일 객체를 전송하며, 이 메일 객체가 봉투와 내용으로 나뉜다고 설명한다. 봉투는 MAIL, RCPT 같은 SMTP 명령으로 전달되고, 내용은 DATA 이후에 전달된다. 그리고 최종 배달 시스템은 메일을 사용자 에이전트에 넘기거나, 사용자가 나중에 접근할 수 있는 메시지 저장소에 보관하는 역할을 한다.&lt;/p&gt;
&lt;p data-end=&quot;1672&quot; data-start=&quot;1647&quot; data-ke-size=&quot;size16&quot;&gt;아직 우리가 만든 것은 진짜 메일함은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1707&quot; data-start=&quot;1674&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이제 최소한의 메시지 저장소를 흉내 내볼 수는 있다.&lt;/p&gt;
&lt;p data-end=&quot;1725&quot; data-start=&quot;1709&quot; data-ke-size=&quot;size16&quot;&gt;가장 단순한 방법은 파일이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1751&quot; data-start=&quot;1727&quot; data-section-id=&quot;8gtvad&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 목표: 받은 메일을 파일로 남기기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1770&quot; data-start=&quot;1753&quot; data-ke-size=&quot;size16&quot;&gt;이번 구현의 목표는 크지 않다.&lt;/p&gt;
&lt;p data-end=&quot;1867&quot; data-start=&quot;1772&quot; data-ke-size=&quot;size16&quot;&gt;Gmail처럼 메일함을 만들지 않는다.&lt;br /&gt;IMAP 서버를 만들지도 않는다.&lt;br /&gt;사용자별 받은편지함을 만들지도 않는다.&lt;br /&gt;검색 기능도, 읽음 처리도, 삭제 기능도 없다.&lt;/p&gt;
&lt;p data-end=&quot;1896&quot; data-start=&quot;1869&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 그저 받은 메일을 파일 하나로 저장한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;mails/
  ├── 20260507-001.eml
  ├── 20260507-002.eml
  └── 20260507-003.eml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2014&quot; data-start=&quot;1987&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 파일로 남기면 적어도 한 가지는 달라진다.&lt;/p&gt;
&lt;p data-end=&quot;2035&quot; data-start=&quot;2016&quot; data-ke-size=&quot;size16&quot;&gt;서버를 종료해도 메일이 남아 있다.&lt;/p&gt;
&lt;p data-end=&quot;2080&quot; data-start=&quot;2037&quot; data-ke-size=&quot;size16&quot;&gt;이전까지는 메일이 네트워크 연결 안에서 잠깐 나타났다가 로그와 함께 사라졌다.&lt;/p&gt;
&lt;p data-end=&quot;2108&quot; data-start=&quot;2082&quot; data-ke-size=&quot;size16&quot;&gt;이제는 서버의 파일 시스템 안에 흔적이 남는다.&lt;/p&gt;
&lt;p data-end=&quot;2145&quot; data-start=&quot;2110&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 서버가 처음으로 &amp;ldquo;받은 것&amp;rdquo;을 보관하기 시작한 것이다.&lt;/p&gt;
&lt;p data-end=&quot;2365&quot; data-start=&quot;2147&quot; data-ke-size=&quot;size16&quot;&gt;구현 지침서에서도 Phase 1의 최소 구현 흐름 안에 DATA 처리, 본문 수신, 마침표 종료 감지, 투명성 처리, 그리고 본문 파일 저장을 포함하고 있다. 즉, 파일 저장은 거창한 확장 기능이 아니라, 우리가 만든 작은 SMTP 서버가 메일을 실제로 받아들였다고 말하기 위한 첫 번째 마무리에 가깝다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2389&quot; data-start=&quot;2367&quot; data-section-id=&quot;wv9vcf&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;메일 파일에는 무엇을 저장해야 할까&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2405&quot; data-start=&quot;2391&quot; data-ke-size=&quot;size16&quot;&gt;이제 고민이 하나 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;2424&quot; data-start=&quot;2407&quot; data-ke-size=&quot;size16&quot;&gt;파일에는 무엇을 저장해야 할까?&lt;/p&gt;
&lt;p data-end=&quot;2452&quot; data-start=&quot;2426&quot; data-ke-size=&quot;size16&quot;&gt;DATA 이후에 받은 내용만 저장하면 될까?&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;From: Alice &amp;lt;alice@example.com&amp;gt;
To: Bob &amp;lt;bob@example.net&amp;gt;
Subject: Hello

Hello SMTP.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2580&quot; data-start=&quot;2553&quot; data-ke-size=&quot;size16&quot;&gt;아니면 SMTP 봉투 정보도 함께 저장해야 할까?&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;MAIL FROM:&amp;lt;alice@example.com&amp;gt;
RCPT TO:&amp;lt;bob@example.net&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2684&quot; data-start=&quot;2651&quot; data-ke-size=&quot;size16&quot;&gt;이전 글에서 봤듯이, SMTP에는 봉투와 편지가 따로 있다.&lt;/p&gt;
&lt;p data-end=&quot;2714&quot; data-start=&quot;2686&quot; data-ke-size=&quot;size16&quot;&gt;MAIL FROM과 RCPT TO는 봉투다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;MAIL FROM:&amp;lt;alice@example.com&amp;gt;
RCPT TO:&amp;lt;bob@example.net&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2832&quot; data-start=&quot;2785&quot; data-ke-size=&quot;size16&quot;&gt;DATA 이후의 From:, To:, Subject:는 편지 내용이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;From: Alice &amp;lt;alice@example.com&amp;gt;
To: Bob &amp;lt;bob@example.net&amp;gt;
Subject: Hello&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2949&quot; data-start=&quot;2920&quot; data-ke-size=&quot;size16&quot;&gt;둘은 같아 보일 수 있지만 같은 층의 정보가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;2973&quot; data-start=&quot;2951&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번에는 파일에 둘 다 남겨본다.&lt;/p&gt;
&lt;p data-end=&quot;3002&quot; data-start=&quot;2975&quot; data-ke-size=&quot;size16&quot;&gt;완성된 메일 파일은 대략 이런 모양이면 충분하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;MAIL FROM: &amp;lt;alice@example.com&amp;gt;
RCPT TO: &amp;lt;bob@example.net&amp;gt;

From: Alice &amp;lt;alice@example.com&amp;gt;
To: Bob &amp;lt;bob@example.net&amp;gt;
Subject: Hello

Hello SMTP.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3209&quot; data-start=&quot;3162&quot; data-ke-size=&quot;size16&quot;&gt;엄밀히 말하면 실제 .eml 파일 형식이나 메일박스 저장 방식과는 다를 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3228&quot; data-start=&quot;3211&quot; data-ke-size=&quot;size16&quot;&gt;하지만 지금 단계에서는 괜찮다.&lt;/p&gt;
&lt;p data-end=&quot;3324&quot; data-start=&quot;3230&quot; data-ke-size=&quot;size16&quot;&gt;이번 시즌의 목표는 완전한 메일 시스템을 만드는 것이 아니라, SMTP 대화를 직접 구현하면서 메일 한 통이 서버 안에서 어떤 형태를 갖추는지 확인하는 것이기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;3358&quot; data-start=&quot;3326&quot; data-ke-size=&quot;size16&quot;&gt;지금은 봉투와 내용을 함께 볼 수 있는 파일이면 충분하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3375&quot; data-start=&quot;3360&quot; data-section-id=&quot;12s8dvd&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;저장할 디렉터리 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;3398&quot; data-start=&quot;3377&quot; data-ke-size=&quot;size16&quot;&gt;먼저 메일을 저장할 디렉터리를 만든다.&lt;/p&gt;
&lt;p data-end=&quot;3427&quot; data-start=&quot;3400&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 mails라는 디렉터리를 사용한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;if err := os.MkdirAll(&quot;mails&quot;, 0755); err != nil {
	log.Fatal(err)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3547&quot; data-start=&quot;3509&quot; data-ke-size=&quot;size16&quot;&gt;이 코드는 서버가 시작될 때 mails 디렉터리가 없으면 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;mails/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3607&quot; data-start=&quot;3569&quot; data-ke-size=&quot;size16&quot;&gt;아직 아무 메일도 없지만, 이제 서버에는 메일을 남길 장소가 생겼다.&lt;/p&gt;
&lt;p data-end=&quot;3632&quot; data-start=&quot;3609&quot; data-ke-size=&quot;size16&quot;&gt;이전까지 서버는 클라이언트와 대화만 했다.&lt;/p&gt;
&lt;p data-end=&quot;3662&quot; data-start=&quot;3634&quot; data-ke-size=&quot;size16&quot;&gt;이제는 대화의 결과를 어딘가에 저장할 준비를 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3676&quot; data-start=&quot;3664&quot; data-section-id=&quot;nqhjug&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;파일 이름 정하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;3704&quot; data-start=&quot;3678&quot; data-ke-size=&quot;size16&quot;&gt;메일을 파일로 저장하려면 파일 이름도 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;3731&quot; data-start=&quot;3706&quot; data-ke-size=&quot;size16&quot;&gt;가장 단순하게는 현재 시간을 사용할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;filename := fmt.Sprintf(&quot;mails/%d.eml&quot;, time.Now().UnixNano())&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3842&quot; data-start=&quot;3807&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 메일을 받을 때마다 다른 이름의 파일이 만들어진다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;mails/1778123456789000000.eml
mails/1778123456791000000.eml
mails/1778123456793000000.eml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3981&quot; data-start=&quot;3947&quot; data-ke-size=&quot;size16&quot;&gt;사람이 읽기 좋은 이름은 아니지만, 지금 단계에서는 충분하다.&lt;/p&gt;
&lt;p data-end=&quot;4007&quot; data-start=&quot;3983&quot; data-ke-size=&quot;size16&quot;&gt;중요한 것은 메일이 덮어써지지 않는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;4061&quot; data-start=&quot;4009&quot; data-ke-size=&quot;size16&quot;&gt;만약 모든 메일을 같은 이름으로 저장하면, 새 메일이 올 때마다 이전 메일이 사라질 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;mails/message.eml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4112&quot; data-start=&quot;4094&quot; data-ke-size=&quot;size16&quot;&gt;이런 방식은 간단하지만 위험하다.&lt;/p&gt;
&lt;p data-end=&quot;4141&quot; data-start=&quot;4114&quot; data-ke-size=&quot;size16&quot;&gt;그래서 지금은 시간 기반 이름으로 파일을 만든다.&lt;/p&gt;
&lt;p data-end=&quot;4201&quot; data-start=&quot;4143&quot; data-ke-size=&quot;size16&quot;&gt;나중에는 메시지 ID를 만들 수도 있고, 수신자별 디렉터리를 만들 수도 있고, 날짜별로 나눌 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;4220&quot; data-start=&quot;4203&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이번에는 단순하게 간다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;받은 메일 하나 &amp;rarr; 파일 하나&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4277&quot; data-start=&quot;4252&quot; data-ke-size=&quot;size16&quot;&gt;이 정도면 시즌 1의 마지막 목표로 충분하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4300&quot; data-start=&quot;4279&quot; data-section-id=&quot;u4y4xh&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DATA가 끝나면 파일로 저장한다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;4322&quot; data-start=&quot;4302&quot; data-ke-size=&quot;size16&quot;&gt;이제 DATA 처리 부분을 보자.&lt;/p&gt;
&lt;p data-end=&quot;4364&quot; data-start=&quot;4324&quot; data-ke-size=&quot;size16&quot;&gt;지난 글에서 우리는 마침표 하나만 있는 줄을 만날 때까지 본문을 읽었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;msgLines := []string{}

for {
	line, err := reader.ReadString('\n')
	if err != nil {
		log.Println(&quot;error reading data&quot;, err)
		return
	}

	line = strings.TrimRight(line, &quot;\r\n&quot;)

	if line == &quot;.&quot; {
		break
	}

	line = strings.TrimPrefix(line, &quot;.&quot;)
	msgLines = append(msgLines, line)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4677&quot; data-start=&quot;4662&quot; data-ke-size=&quot;size16&quot;&gt;여기까지는 이전과 비슷하다.&lt;/p&gt;
&lt;p data-end=&quot;4713&quot; data-start=&quot;4679&quot; data-ke-size=&quot;size16&quot;&gt;다른 점은, 이제 이 메시지를 로그에만 찍지 않는다는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;4735&quot; data-start=&quot;4715&quot; data-ke-size=&quot;size16&quot;&gt;먼저 본문을 하나의 문자열로 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;message = strings.Join(msgLines, &quot;\r\n&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4816&quot; data-start=&quot;4789&quot; data-ke-size=&quot;size16&quot;&gt;그리고 봉투 정보와 함께 저장할 내용을 구성한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1778146911847&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mailFileName := fmt.Sprintf(&quot;%d.eml&quot;, time.Now().UnixNano())
err := os.WriteFile(&quot;mails/&quot;+mailFileName, []byte(message), 0666)
if err != nil {
    log.Println(&quot;error writing mail file&quot;, err)
    if err := writeLine(&quot;554 Error on DATA write&quot;); err != nil {
        return
    }
    return
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5299&quot; data-start=&quot;5273&quot; data-ke-size=&quot;size16&quot;&gt;저장에 성공하면 그때 250 OK를 보낸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;if err := writeLine(&quot;250 OK&quot;); err != nil {
	return
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5377&quot; data-start=&quot;5366&quot; data-ke-size=&quot;size16&quot;&gt;이 순서가 중요하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;메일 데이터 수신
  &amp;darr;
파일 저장 시도
  &amp;darr;
저장 성공
  &amp;darr;
250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5448&quot; data-start=&quot;5436&quot; data-ke-size=&quot;size16&quot;&gt;반대로 하면 안 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;메일 데이터 수신
  &amp;darr;
250 OK
  &amp;darr;
파일 저장 실패&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5552&quot; data-start=&quot;5497&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 되면 서버는 클라이언트에게 &amp;ldquo;메일을 받았다&amp;rdquo;라고 말해놓고 실제로는 메일을 잃어버린 셈이 된다.&lt;/p&gt;
&lt;h2 data-end=&quot;5578&quot; data-start=&quot;5554&quot; data-section-id=&quot;1unnoob&quot; data-ke-size=&quot;size26&quot;&gt;250 OK는 저장한 뒤에 보내야 한다&lt;/h2&gt;
&lt;p data-end=&quot;5627&quot; data-start=&quot;5580&quot; data-ke-size=&quot;size16&quot;&gt;지난 글에서도 잠깐 이야기했지만, DATA 이후의 250 OK는 가볍지 않다.&lt;/p&gt;
&lt;p data-end=&quot;5665&quot; data-start=&quot;5629&quot; data-ke-size=&quot;size16&quot;&gt;EHLO에 대한 250 OK는 &amp;ldquo;인사를 받았다&amp;rdquo;에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;5712&quot; data-start=&quot;5667&quot; data-ke-size=&quot;size16&quot;&gt;MAIL FROM에 대한 250 OK는 &amp;ldquo;봉투 발신자를 받았다&amp;rdquo;에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;5756&quot; data-start=&quot;5714&quot; data-ke-size=&quot;size16&quot;&gt;RCPT TO에 대한 250 OK는 &amp;ldquo;이 수신자를 받겠다&amp;rdquo;에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;5793&quot; data-start=&quot;5758&quot; data-ke-size=&quot;size16&quot;&gt;하지만 DATA가 끝난 뒤의 250 OK는 조금 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: .
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5852&quot; data-start=&quot;5823&quot; data-ke-size=&quot;size16&quot;&gt;이 응답은 서버가 메일 데이터를 받아들였다는 뜻이다.&lt;/p&gt;
&lt;p data-end=&quot;6010&quot; data-start=&quot;5854&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5321은 서버가 DATA 종료 후 긍정적인 완료 응답을 보내면 메시지 전달, 재시도, 실패 알림 등에 대한 책임을 수락한다고 설명한다. 즉, 이 시점 이후에는 서버가 메시지를 가볍게 잃어버리면 안 된다.&lt;/p&gt;
&lt;p data-end=&quot;6040&quot; data-start=&quot;6012&quot; data-ke-size=&quot;size16&quot;&gt;우리가 만든 서버는 아직 실제 전달도 하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;6052&quot; data-start=&quot;6042&quot; data-ke-size=&quot;size16&quot;&gt;재시도 큐도 없다.&lt;/p&gt;
&lt;p data-end=&quot;6067&quot; data-start=&quot;6054&quot; data-ke-size=&quot;size16&quot;&gt;배달 실패 알림도 없다.&lt;/p&gt;
&lt;p data-end=&quot;6097&quot; data-start=&quot;6069&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 이 원칙은 지금부터 신경 쓰는 편이 좋다.&lt;/p&gt;
&lt;p data-end=&quot;6141&quot; data-start=&quot;6099&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이번 구현에서는 파일 저장에 성공한 뒤에만 250 OK를 보낸다.&lt;/p&gt;
&lt;p data-end=&quot;6170&quot; data-start=&quot;6143&quot; data-ke-size=&quot;size16&quot;&gt;저장에 실패하면 250 OK를 보내지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;6224&quot; data-start=&quot;6172&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 디스크 오류가 났거나, 권한 문제로 파일을 만들 수 없다면 이렇게 응답할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;451 Requested action aborted: local error in processing&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6324&quot; data-start=&quot;6295&quot; data-ke-size=&quot;size16&quot;&gt;지금은 아주 단순한 오류 처리지만, 방향은 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;6359&quot; data-start=&quot;6326&quot; data-ke-size=&quot;size16&quot;&gt;서버가 처리하지 못한 메일에 대해 처리했다고 말하지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오후 6.21.53.png&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9qrIM/dJMcajoxc2s/A6fB3vjvwwgj8IKdM9B3k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9qrIM/dJMcajoxc2s/A6fB3vjvwwgj8IKdM9B3k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9qrIM/dJMcajoxc2s/A6fB3vjvwwgj8IKdM9B3k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9qrIM%2FdJMcajoxc2s%2FA6fB3vjvwwgj8IKdM9B3k0%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;1454&quot; height=&quot;480&quot; data-filename=&quot;스크린샷 2026-05-07 오후 6.21.53.png&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mails 폴더 내에 메일 파일이 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;7450&quot; data-start=&quot;7430&quot; data-section-id=&quot;1ec8qo0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;아직 이것은 받은편지함이 아니다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;7472&quot; data-start=&quot;7452&quot; data-ke-size=&quot;size16&quot;&gt;여기서 너무 빨리 만족하면 안 된다.&lt;/p&gt;
&lt;p data-end=&quot;7507&quot; data-start=&quot;7474&quot; data-ke-size=&quot;size16&quot;&gt;파일로 저장했다고 해서 완전한 메일 서버가 된 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;7525&quot; data-start=&quot;7509&quot; data-ke-size=&quot;size16&quot;&gt;아직 사용자별 메일함이 없다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;mails/bob/
mails/alice/&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7574&quot; data-start=&quot;7564&quot; data-ke-size=&quot;size16&quot;&gt;이런 구조도 없다.&lt;/p&gt;
&lt;p data-end=&quot;7604&quot; data-start=&quot;7576&quot; data-ke-size=&quot;size16&quot;&gt;수신자 주소가 실제로 존재하는지 확인하지도 않는다.&lt;/p&gt;
&lt;p data-end=&quot;7667&quot; data-start=&quot;7606&quot; data-ke-size=&quot;size16&quot;&gt;bob@example.net으로 보냈다고 해서 bob이라는 사용자의 받은 편지함에 들어가는 것도 아니다.&lt;/p&gt;
&lt;p data-end=&quot;7687&quot; data-start=&quot;7669&quot; data-ke-size=&quot;size16&quot;&gt;메일 파일 형식도 아직 단순하다.&lt;/p&gt;
&lt;p data-end=&quot;7932&quot; data-start=&quot;7689&quot; data-ke-size=&quot;size16&quot;&gt;실제 메일 시스템이라면 Received 헤더를 추가해야 할 수도 있고, 최종 배달 시 Return-Path를 다뤄야 할 수도 있다. RFC 5321은 SMTP 서버가 메시지를 전달 또는 추가 처리를 위해 수신하면 메시지 앞에 추적 정보인 Received: 헤더를 추가해야 한다고 설명한다. 또한 최종 배달 시에는 Return-Path 정보도 중요해진다.&lt;/p&gt;
&lt;p data-end=&quot;7954&quot; data-start=&quot;7934&quot; data-ke-size=&quot;size16&quot;&gt;하지만 지금은 여기까지 가지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;7988&quot; data-start=&quot;7956&quot; data-ke-size=&quot;size16&quot;&gt;시즌 1의 목표는 실제 운영 가능한 메일 서버가 아니었다.&lt;/p&gt;
&lt;p data-end=&quot;8012&quot; data-start=&quot;7990&quot; data-ke-size=&quot;size16&quot;&gt;우리가 확인하고 싶었던 것은 이것이었다.&lt;/p&gt;
&lt;p data-end=&quot;8147&quot; data-start=&quot;8014&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 서버는 어떻게 대화를 시작하는가.&lt;br /&gt;클라이언트는 어떻게 자신을 소개하는가.&lt;br /&gt;메일의 봉투는 어떻게 만들어지는가.&lt;br /&gt;본문은 어떻게 전달되는가.&lt;br /&gt;본문의 끝은 어떻게 표시되는가.&lt;br /&gt;그리고 받은 메일은 어디에 남길 수 있는가.&lt;/p&gt;
&lt;p data-end=&quot;8172&quot; data-start=&quot;8149&quot; data-ke-size=&quot;size16&quot;&gt;이번 구현으로 그 첫 번째 흐름이 닫혔다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;연결
  &amp;darr;
220
  &amp;darr;
EHLO / HELO
  &amp;darr;
MAIL FROM
  &amp;darr;
RCPT TO
  &amp;darr;
DATA
  &amp;darr;
.
  &amp;darr;
파일 저장
  &amp;darr;
250 OK
  &amp;darr;
QUIT&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8308&quot; data-start=&quot;8285&quot; data-ke-size=&quot;size16&quot;&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-end=&quot;8324&quot; data-start=&quot;8310&quot; data-section-id=&quot;19hz9hj&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;시즌 1에서 만든 것&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;8360&quot; data-start=&quot;8326&quot; data-ke-size=&quot;size16&quot;&gt;시즌 1을 시작할 때는 이메일이 어떻게 보내지는지 잘 몰랐다.&lt;/p&gt;
&lt;p data-end=&quot;8390&quot; data-start=&quot;8362&quot; data-ke-size=&quot;size16&quot;&gt;나에게 이메일 발송은 대체로 이런 코드에 가까웠다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;SendEmail(to, subject, body)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8462&quot; data-start=&quot;8432&quot; data-ke-size=&quot;size16&quot;&gt;외부 메일 발송 서비스를 사용하면 이 정도로 충분했다.&lt;/p&gt;
&lt;p data-end=&quot;8521&quot; data-start=&quot;8464&quot; data-ke-size=&quot;size16&quot;&gt;API를 호출하면 메일이 갔다.&lt;br /&gt;사용자는 인증 메일을 받았다.&lt;br /&gt;비밀번호 재설정 링크도 도착했다.&lt;/p&gt;
&lt;p data-end=&quot;8557&quot; data-start=&quot;8523&quot; data-ke-size=&quot;size16&quot;&gt;그때는 그 뒤에서 무슨 일이 일어나는지 깊게 생각하지 않았다.&lt;/p&gt;
&lt;p data-end=&quot;8604&quot; data-start=&quot;8559&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이번 시즌에서는 그 API 뒤쪽에 있는 가장 작은 대화를 직접 만들어봤다.&lt;/p&gt;
&lt;p data-end=&quot;8621&quot; data-start=&quot;8606&quot; data-ke-size=&quot;size16&quot;&gt;먼저 TCP 서버를 열었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;S: 220 localhost Simple Mail Transfer Service Ready&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8709&quot; data-start=&quot;8688&quot; data-ke-size=&quot;size16&quot;&gt;서버가 먼저 인사한다는 것도 확인했다.&lt;/p&gt;
&lt;p data-end=&quot;8731&quot; data-start=&quot;8711&quot; data-ke-size=&quot;size16&quot;&gt;그다음 클라이언트가 자신을 소개했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: EHLO client.local
S: 250-localhost greets client.local
S: 250 HELP&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8839&quot; data-start=&quot;8816&quot; data-ke-size=&quot;size16&quot;&gt;메일에는 봉투가 있다는 것도 알게 되었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: MAIL FROM:&amp;lt;alice@example.com&amp;gt;
S: 250 OK

C: RCPT TO:&amp;lt;bob@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8965&quot; data-start=&quot;8937&quot; data-ke-size=&quot;size16&quot;&gt;그리고 봉투와 편지 내용이 다르다는 것도 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;MAIL FROM  &amp;rarr; 봉투 발신자
RCPT TO    &amp;rarr; 봉투 수신자

From:      &amp;rarr; 메일 내용의 발신자 표시
To:        &amp;rarr; 메일 내용의 수신자 표시&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9092&quot; data-start=&quot;9075&quot; data-ke-size=&quot;size16&quot;&gt;본문은 DATA로 시작했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;C: DATA
S: 354 Start mail input; end with &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9185&quot; data-start=&quot;9163&quot; data-ke-size=&quot;size16&quot;&gt;그리고 마침표 하나만 있는 줄로 끝났다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: .
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9258&quot; data-start=&quot;9215&quot; data-ke-size=&quot;size16&quot;&gt;중간에는 RSET, NOOP, VRFY 같은 보조 명령도 추가했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;RSET &amp;rarr; 현재 트랜잭션 취소
NOOP &amp;rarr; 아무것도 하지 않고 응답
VRFY &amp;rarr; 사용자를 확인할 수 있는지 요청&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9364&quot; data-start=&quot;9337&quot; data-ke-size=&quot;size16&quot;&gt;그리고 마지막으로, 받은 메일을 파일로 저장했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;mails/1778123456789000000.eml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9419&quot; data-start=&quot;9409&quot; data-ke-size=&quot;size16&quot;&gt;아주 작은 서버다.&lt;/p&gt;
&lt;p data-end=&quot;9490&quot; data-start=&quot;9421&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이제 이 서버는 SMTP 클라이언트와 대화하고, 메일의 봉투를 받고, 본문을 받고, 그 결과를 파일로 남길 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;10058&quot; data-start=&quot;10046&quot; data-section-id=&quot;q9lyqo&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;시즌 1을 마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;10265&quot; data-start=&quot;10234&quot; data-ke-size=&quot;size16&quot;&gt;시즌 1에서는 아직 수신자의 받은편지함까지 가지 못했다.&lt;/p&gt;
&lt;p data-end=&quot;10302&quot; data-start=&quot;10267&quot; data-ke-size=&quot;size16&quot;&gt;하지만 적어도 메일이 서버에 도착하는 장면까지는 직접 확인했다.&lt;/p&gt;
&lt;p data-end=&quot;10331&quot; data-start=&quot;10304&quot; data-ke-size=&quot;size16&quot;&gt;그리고 그 메일이 사라지지 않도록 파일로 남겼다.&lt;/p&gt;
&lt;p data-end=&quot;10351&quot; data-start=&quot;10333&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 이런 질문에서 시작했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;우리 서버가 직접 이메일을 보낼 수 있었을까?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10415&quot; data-start=&quot;10392&quot; data-ke-size=&quot;size16&quot;&gt;아직 이 질문에 완전히 답한 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;10445&quot; data-start=&quot;10417&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이제는 질문이 조금 더 구체적으로 바뀌었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;우리 서버가 SMTP 대화를 이해할 수 있을까?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10510&quot; data-start=&quot;10487&quot; data-ke-size=&quot;size16&quot;&gt;여기에는 어느 정도 답할 수 있게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;10517&quot; data-start=&quot;10512&quot; data-ke-size=&quot;size16&quot;&gt;가능했다.&lt;/p&gt;
&lt;p data-end=&quot;10533&quot; data-start=&quot;10519&quot; data-ke-size=&quot;size16&quot;&gt;아주 작게나마, 가능했다.&lt;/p&gt;
&lt;p data-end=&quot;10557&quot; data-start=&quot;10535&quot; data-ke-size=&quot;size16&quot;&gt;우리 서버는 이제 이렇게 말할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;220 localhost Simple Mail Transfer Service Ready&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10653&quot; data-start=&quot;10621&quot; data-ke-size=&quot;size16&quot;&gt;그리고 클라이언트가 메일을 건네면, 이렇게 답할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10707&quot; data-start=&quot;10675&quot; data-ke-size=&quot;size16&quot;&gt;단, 이제는 그 OK가 로그 한 줄로 사라지지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;10717&quot; data-start=&quot;10709&quot; data-ke-size=&quot;size16&quot;&gt;파일로 남는다.&lt;/p&gt;
&lt;p data-end=&quot;10735&quot; data-start=&quot;10719&quot; data-ke-size=&quot;size16&quot;&gt;시즌 1은 여기서 마무리한다.&lt;/p&gt;
&lt;p data-end=&quot;10780&quot; data-start=&quot;10737&quot; data-ke-size=&quot;size16&quot;&gt;다음 시즌에서는 이 작은 서버를 조금 더 서버답게 만들어볼 수 있을 것 같다.&lt;/p&gt;
&lt;p data-end=&quot;10883&quot; data-start=&quot;10782&quot; data-ke-size=&quot;size16&quot;&gt;저장된 메일에 Received 헤더를 붙이고, 크기 제한과 타임아웃을 넣고, 수신자를 더 제대로 다루고, 언젠가는 직접 다른 메일 서버로 전달하는 흐름까지 가볼 수 있을 것이다.&lt;/p&gt;
&lt;p data-end=&quot;10901&quot; data-start=&quot;10885&quot; data-ke-size=&quot;size16&quot;&gt;하지만 지금은 여기서 멈춘다.&lt;/p&gt;
&lt;p data-end=&quot;10938&quot; data-start=&quot;10903&quot; data-ke-size=&quot;size16&quot;&gt;처음 만든 SMTP 서버가 메일 한 통을 받아서 파일로 남겼다.&lt;/p&gt;
&lt;p data-end=&quot;10966&quot; data-start=&quot;10940&quot; data-ke-size=&quot;size16&quot;&gt;그 작은 파일 하나가, 이번 시즌의 결과물이다.&lt;/p&gt;</description>
      <category>Deep Dive/기타</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/240</guid>
      <comments>https://gowoong.tistory.com/240#entry240comment</comments>
      <pubDate>Thu, 7 May 2026 18:46:05 +0900</pubDate>
    </item>
    <item>
      <title>[SMTP] 5.5. 부록-메일을 보내지는 않지만 필요한 명령들</title>
      <link>https://gowoong.tistory.com/239</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 DATA까지 구현했다.&lt;/p&gt;
&lt;p data-end=&quot;608&quot; data-start=&quot;531&quot; data-ke-size=&quot;size16&quot;&gt;이제 서버는 SMTP 클라이언트와 인사하고, 봉투를 받고, 본문을 받은 뒤, 마침표 한 줄로 메일 데이터가 끝났다는 것도 알아볼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;608&quot; data-start=&quot;531&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 오면 메일 한 통을 받는 가장 중요한 흐름은 대략 보인다.&lt;/p&gt;
&lt;p data-end=&quot;1126&quot; data-start=&quot;1041&quot; data-ke-size=&quot;size16&quot;&gt;하지만 RFC 5321의 최소 구현 목록을 보면 아직 몇 가지 명령이 더 남아 있다.&lt;br /&gt;이번에 추가한 것은 RSET, NOOP, VRFY다.&lt;/p&gt;
&lt;p data-end=&quot;1189&quot; data-start=&quot;1128&quot; data-ke-size=&quot;size16&quot;&gt;이 명령들은 MAIL FROM, RCPT TO, DATA처럼 메일을 직접 구성하는 주인공은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1259&quot; data-start=&quot;1191&quot; data-ke-size=&quot;size16&quot;&gt;대신 SMTP 세션 중간에서 상태를 정리하거나, 연결이 살아 있는지 확인하거나, 주소 확인을 요청하는 보조 명령에 가깝다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1287&quot; data-start=&quot;1261&quot; data-section-id=&quot;1ql1h8m&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;RSET: 지금 쓰던 편지는 취소하겠습니다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1301&quot; data-start=&quot;1289&quot; data-ke-size=&quot;size16&quot;&gt;먼저 RSET이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: RSET
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1370&quot; data-start=&quot;1334&quot; data-ke-size=&quot;size16&quot;&gt;RSET은 현재 진행 중인 메일 트랜잭션을 취소하는 명령이다.&lt;/p&gt;
&lt;p data-end=&quot;1425&quot; data-start=&quot;1372&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 클라이언트가 봉투를 만들다가 중간에 이 메일을 더 이상 보내지 않기로 했다고 해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: MAIL FROM:&amp;lt;alice@example.com&amp;gt;
S: 250 OK

C: RCPT TO:&amp;lt;bob@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1559&quot; data-start=&quot;1523&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 오면 서버는 현재 트랜잭션 안에 이런 정보를 들고 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;봉투 발신자: alice@example.com
봉투 수신자: bob@example.net&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1679&quot; data-start=&quot;1624&quot; data-ke-size=&quot;size16&quot;&gt;그런데 이 상태에서 클라이언트가 RSET을 보내면, 서버는 지금까지 쌓아둔 봉투 정보를 지운다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: RSET
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1729&quot; data-start=&quot;1712&quot; data-ke-size=&quot;size16&quot;&gt;편지에 비유하면 이런 느낌이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;방금 쓰던 봉투와 편지는 취소하겠습니다.
연결은 끊지 말고, 새 편지를 다시 시작하겠습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1830&quot; data-start=&quot;1796&quot; data-ke-size=&quot;size16&quot;&gt;중요한 점은 RSET이 연결 종료 명령은 아니라는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;1996&quot; data-start=&quot;1832&quot; data-ke-size=&quot;size16&quot;&gt;서버는 상태만 초기화하고 연결은 유지한다. RFC 5321도 RSET은 현재 메일 트랜잭션을 중단하고 저장된 발신자, 수신자, 메일 데이터를 삭제해야 하지만, 이 명령을 받았다는 이유로 연결을 닫아서는 안 된다고 설명한다.&lt;/p&gt;
&lt;p data-end=&quot;2016&quot; data-start=&quot;1998&quot; data-ke-size=&quot;size16&quot;&gt;이번 구현에서는 이렇게 처리했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;case &quot;RSET&quot;:
	resetSession()
	if err := writeLine(&quot;250 OK&quot;); err != nil {
		return
	}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2134&quot; data-start=&quot;2115&quot; data-ke-size=&quot;size16&quot;&gt;아직 우리 서버의 상태는 단순하다.&lt;/p&gt;
&lt;p data-end=&quot;2196&quot; data-start=&quot;2136&quot; data-ke-size=&quot;size16&quot;&gt;from, rcpt, message를 비우고 다시 명령을 받을 수 있는 상태로 되돌리면 충분하다.&lt;/p&gt;
&lt;p data-end=&quot;2221&quot; data-start=&quot;2198&quot; data-ke-size=&quot;size16&quot;&gt;다만 구현하면서 한 가지는 조심해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;2349&quot; data-start=&quot;2223&quot; data-ke-size=&quot;size16&quot;&gt;만약 resetSession()이 무조건 상태를 StateReady로 바꾸는 함수라면, EHLO나 HELO를 하기 전에 RSET을 보낸 클라이언트도 바로 MAIL FROM으로 들어갈 수 있게 될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2412&quot; data-start=&quot;2351&quot; data-ke-size=&quot;size16&quot;&gt;그래서 조금 더 정확히 구현하려면 &amp;ldquo;세션 전체 초기화&amp;rdquo;와 &amp;ldquo;현재 메일 트랜잭션만 초기화&amp;rdquo;를 나누는 편이 좋다.&lt;/p&gt;
&lt;p data-end=&quot;2427&quot; data-start=&quot;2414&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 이런 식이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;resetSession := func(keepState bool) {
    if !keepState {
        state = StateReady
    }
    from = &quot;&quot;
    rcpt = []string{}
    message = &quot;&quot;
}

// EHLO/HELO에서
resetSession(false)  // state도 변경

// RSET에서
resetSession(true)   // state는 유지&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2665&quot; data-start=&quot;2588&quot; data-ke-size=&quot;size16&quot;&gt;지금 단계에서는 큰 문제가 되지 않을 수 있지만, SMTP는 상태가 중요한 프로토콜이기 때문에 이런 작은 차이가 나중에 의미를 갖게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2686&quot; data-start=&quot;2667&quot; data-section-id=&quot;1965n78&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;NOOP: 아무것도 하지 않기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2701&quot; data-start=&quot;2688&quot; data-ke-size=&quot;size16&quot;&gt;다음은 NOOP이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: NOOP
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2758&quot; data-start=&quot;2734&quot; data-ke-size=&quot;size16&quot;&gt;이름 그대로 아무 일도 하지 않는 명령이다.&lt;/p&gt;
&lt;p data-end=&quot;2774&quot; data-start=&quot;2760&quot; data-ke-size=&quot;size16&quot;&gt;처음 보면 조금 이상하다.&lt;/p&gt;
&lt;p data-end=&quot;2803&quot; data-start=&quot;2776&quot; data-ke-size=&quot;size16&quot;&gt;아무 일도 하지 않을 거라면 왜 명령이 필요할까?&lt;/p&gt;
&lt;p data-end=&quot;2880&quot; data-start=&quot;2805&quot; data-ke-size=&quot;size16&quot;&gt;하지만 네트워크 연결에서는 &amp;ldquo;아무 일도 하지 않았지만, 서버가 아직 살아 있고 응답할 수 있다&amp;rdquo;는 사실 자체가 의미가 있을 때가 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: NOOP
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2934&quot; data-start=&quot;2913&quot; data-ke-size=&quot;size16&quot;&gt;이 대화는 대략 이렇게 읽을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;클라이언트: 아직 거기 있나요?
서버: 네, 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3155&quot; data-start=&quot;2980&quot; data-ke-size=&quot;size16&quot;&gt;NOOP은 현재 트랜잭션의 발신자, 수신자, 본문 데이터에 영향을 주지 않는다. RFC 5321도 NOOP은 매개변수나 이전 명령에 영향을 주지 않으며, 서버가 250 OK를 반환하는 것 외에는 어떤 작업도 지정하지 않는다고 설명한다.&lt;/p&gt;
&lt;p data-end=&quot;3173&quot; data-start=&quot;3157&quot; data-ke-size=&quot;size16&quot;&gt;그래서 구현도 가장 단순하다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;case &quot;NOOP&quot;:
	if err := writeLine(&quot;250 OK&quot;); err != nil {
		return
	}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3274&quot; data-start=&quot;3256&quot; data-ke-size=&quot;size16&quot;&gt;이 명령은 상태를 바꾸지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;3288&quot; data-start=&quot;3276&quot; data-ke-size=&quot;size16&quot;&gt;봉투도 지우지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;3303&quot; data-start=&quot;3290&quot; data-ke-size=&quot;size16&quot;&gt;본문도 건드리지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;3315&quot; data-start=&quot;3305&quot; data-ke-size=&quot;size16&quot;&gt;그저 응답만 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3372&quot; data-start=&quot;3337&quot; data-ke-size=&quot;size16&quot;&gt;SMTP를 구현하다 보면 이런 명령도 필요하다는 점이 재미있다.&lt;/p&gt;
&lt;p data-end=&quot;3395&quot; data-start=&quot;3374&quot; data-ke-size=&quot;size16&quot;&gt;모든 명령이 무언가를 바꾸지는 않는다.&lt;/p&gt;
&lt;p data-end=&quot;3424&quot; data-start=&quot;3397&quot; data-ke-size=&quot;size16&quot;&gt;어떤 명령은 아무것도 바꾸지 않기 위해 존재한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3447&quot; data-start=&quot;3426&quot; data-section-id=&quot;2ztpsj&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;VRFY: 이 주소가 존재하나요?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;3462&quot; data-start=&quot;3449&quot; data-ke-size=&quot;size16&quot;&gt;마지막은 VRFY다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;C: VRFY bob@example.net
S: 252 Cannot VRFY user, but will accept message and attempt delivery&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3610&quot; data-start=&quot;3571&quot; data-ke-size=&quot;size16&quot;&gt;VRFY는 이름 그대로 verify, 즉 확인을 요청하는 명령이다.&lt;/p&gt;
&lt;p data-end=&quot;3628&quot; data-start=&quot;3612&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 서버에게 묻는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;이 사용자를 확인할 수 있나요?&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3712&quot; data-start=&quot;3661&quot; data-ke-size=&quot;size16&quot;&gt;정상적으로 확인할 수 있는 서버라면 250 응답과 함께 메일박스 정보를 돌려줄 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;3777&quot; data-start=&quot;3714&quot; data-ke-size=&quot;size16&quot;&gt;하지만 지금 우리가 만든 서버에는 아직 사용자 DB도 없고, 실제 메일박스도 없고, 도메인별 수신자 검증도 없다.&lt;/p&gt;
&lt;p data-end=&quot;3806&quot; data-start=&quot;3779&quot; data-ke-size=&quot;size16&quot;&gt;그러니 여기서 250 OK를 보내면 안 된다.&lt;/p&gt;
&lt;p data-end=&quot;3832&quot; data-start=&quot;3808&quot; data-ke-size=&quot;size16&quot;&gt;그건 &amp;ldquo;확인했다&amp;rdquo;고 말하는 것이기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;3862&quot; data-start=&quot;3834&quot; data-ke-size=&quot;size16&quot;&gt;지금 우리 서버가 할 수 있는 말은 이것에 가깝다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;확인은 못 합니다.
하지만 메시지를 받는 흐름 자체는 시도할 수 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3935&quot; data-start=&quot;3919&quot; data-ke-size=&quot;size16&quot;&gt;그래서 252를 반환한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;case &quot;VRFY&quot;:
	if err := writeLine(&quot;252 Cannot VRFY user, but will accept message and attempt delivery&quot;); err != nil {
		return
	}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4091&quot; data-start=&quot;4078&quot; data-ke-size=&quot;size16&quot;&gt;이 응답은 꽤 정직하다.&lt;/p&gt;
&lt;p data-end=&quot;4128&quot; data-start=&quot;4093&quot; data-ke-size=&quot;size16&quot;&gt;서버가 실제로 주소를 확인하지 않았으면서 확인한 척하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;4302&quot; data-start=&quot;4130&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5321도 서버가 실제로 주소를 확인하지 않는 한 VRFY나 EXPN에 대해 250을 반환해서는 안 된다고 설명한다. 주소가 유효해 보이지만 실시간으로 합리적으로 확인할 수 없는 경우에는 252 응답을 사용할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;4351&quot; data-start=&quot;4304&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 SMTP 구현을 하면서 처음 만나는 &amp;ldquo;모른다고 말하는 응답&amp;rdquo;처럼 느껴진다.&lt;/p&gt;
&lt;p data-end=&quot;4370&quot; data-start=&quot;4353&quot; data-ke-size=&quot;size16&quot;&gt;성공도 아니고, 실패도 아니다.&lt;/p&gt;
&lt;p data-end=&quot;4383&quot; data-start=&quot;4372&quot; data-ke-size=&quot;size16&quot;&gt;그냥 이렇게 말한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;나는 지금 확인할 수 없다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4434&quot; data-start=&quot;4414&quot; data-ke-size=&quot;size16&quot;&gt;이건 실제 서버 구현에서도 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;4463&quot; data-start=&quot;4436&quot; data-ke-size=&quot;size16&quot;&gt;확인할 수 없는 것을 확인했다고 말하면 안 된다.&lt;/p&gt;
&lt;h2 data-end=&quot;4478&quot; data-start=&quot;4465&quot; data-section-id=&quot;vql6ix&quot; data-ke-size=&quot;size26&quot;&gt;이번에 추가한 코드&lt;/h2&gt;
&lt;p data-end=&quot;4498&quot; data-start=&quot;4480&quot; data-ke-size=&quot;size16&quot;&gt;이번에 추가한 코드는 아주 짧다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;case &quot;RSET&quot;:
	resetSession(true)
	if err := writeLine(&quot;250 OK&quot;); err != nil {
		return
	}

case &quot;NOOP&quot;:
	if err := writeLine(&quot;250 OK&quot;); err != nil {
		return
	}

case &quot;VRFY&quot;:
	if err := writeLine(&quot;252 Cannot VRFY user, but will accept message and attempt delivery&quot;); err != nil {
		return
	}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4814&quot; data-start=&quot;4799&quot; data-ke-size=&quot;size16&quot;&gt;짧지만 의미는 각각 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;RSET &amp;rarr; 현재 메일 트랜잭션을 취소한다.
NOOP &amp;rarr; 아무것도 하지 않고 응답만 한다.
VRFY &amp;rarr; 사용자를 확인할 수 있는지 묻지만, 지금은 확인하지 못한다고 답한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5076&quot; data-start=&quot;4926&quot; data-ke-size=&quot;size16&quot;&gt;이 셋을 추가하면 현재 서버는 RFC 5321의 최소 구현 명령 목록에 조금 더 가까워진다. 작업 지침서에서도 Phase 1 체크리스트에 RSET, NOOP, VRFY 처리를 포함하고 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;5833&quot; data-start=&quot;5823&quot; data-section-id=&quot;8s1g70&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;부록을 마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;5870&quot; data-start=&quot;5835&quot; data-ke-size=&quot;size16&quot;&gt;이번 부록에서 추가한 명령들은 메일을 직접 만들어내지는 않는다.&lt;/p&gt;
&lt;p data-end=&quot;5892&quot; data-start=&quot;5872&quot; data-ke-size=&quot;size16&quot;&gt;RSET은 쓰던 메일을 취소한다.&lt;/p&gt;
&lt;p data-end=&quot;5914&quot; data-start=&quot;5894&quot; data-ke-size=&quot;size16&quot;&gt;NOOP은 아무것도 하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;5943&quot; data-start=&quot;5916&quot; data-ke-size=&quot;size16&quot;&gt;VRFY는 사용자를 확인할 수 있는지 묻는다.&lt;/p&gt;
&lt;p data-end=&quot;5972&quot; data-start=&quot;5945&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이 명령들은 본문 흐름의 주인공은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;6021&quot; data-start=&quot;5974&quot; data-ke-size=&quot;size16&quot;&gt;하지만 SMTP 서버가 하나의 대화 상대라면, 이런 말들도 알아들을 수 있어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;6033&quot; data-start=&quot;6023&quot; data-ke-size=&quot;size16&quot;&gt;메일을 보내는 말.&lt;/p&gt;
&lt;p data-end=&quot;6046&quot; data-start=&quot;6035&quot; data-ke-size=&quot;size16&quot;&gt;메일을 취소하는 말.&lt;/p&gt;
&lt;p data-end=&quot;6070&quot; data-start=&quot;6048&quot; data-ke-size=&quot;size16&quot;&gt;아무것도 하지 않고 연결만 확인하는 말.&lt;/p&gt;
&lt;p data-end=&quot;6100&quot; data-start=&quot;6072&quot; data-ke-size=&quot;size16&quot;&gt;확인할 수 없는 것을 확인할 수 없다고 말하는 말.&lt;/p&gt;
&lt;p data-end=&quot;6144&quot; data-start=&quot;6102&quot; data-ke-size=&quot;size16&quot;&gt;이런 작은 명령들이 모여서 SMTP 세션은 조금 더 서버다운 모양을 갖춘다.&lt;/p&gt;
&lt;p data-end=&quot;6191&quot; data-start=&quot;6146&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 이제 로그에만 찍고 있던 메시지를 조금 더 제대로 다뤄보려고 한다.&lt;/p&gt;
&lt;p data-end=&quot;6230&quot; data-start=&quot;6193&quot; data-ke-size=&quot;size16&quot;&gt;서버가 메일을 받았다고 말하려면, 그 메일은 어디엔가 남아야 한다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;6286&quot; data-start=&quot;6232&quot; data-ke-size=&quot;size16&quot;&gt;250 OK라고 말한 뒤에도 사라지지 않도록, 이제 받은 메시지를 저장하는 쪽으로 나아가보자.&lt;/p&gt;</description>
      <category>Deep Dive/기타</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/239</guid>
      <comments>https://gowoong.tistory.com/239#entry239comment</comments>
      <pubDate>Thu, 7 May 2026 17:41:04 +0900</pubDate>
    </item>
    <item>
      <title>[SMTP] 5. 본문은 언제 끝났다고 말할까</title>
      <link>https://gowoong.tistory.com/238</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서는 메일의 봉투를 만들었다.&lt;/p&gt;
&lt;p data-end=&quot;276&quot; data-start=&quot;235&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 서버와 인사를 나눈 뒤, 클라이언트는 먼저 봉투 발신자를 말했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: MAIL FROM:&amp;lt;alice@example.com&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;350&quot; data-start=&quot;334&quot; data-ke-size=&quot;size16&quot;&gt;그리고 봉투 수신자를 말했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: RCPT TO:&amp;lt;bob@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;431&quot; data-start=&quot;404&quot; data-ke-size=&quot;size16&quot;&gt;이때까지 우리는 아직 메일 본문을 보내지 않았다.&lt;/p&gt;
&lt;p data-end=&quot;480&quot; data-start=&quot;433&quot; data-ke-size=&quot;size16&quot;&gt;제목도 없었다.&lt;br /&gt;내용도 없었다.&lt;br /&gt;사용자가 실제로 읽을 수 있는 문장도 없었다.&lt;/p&gt;
&lt;p data-end=&quot;508&quot; data-start=&quot;482&quot; data-ke-size=&quot;size16&quot;&gt;하지만 서버는 이미 중요한 정보를 알고 있었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;이 메일은 누구에게서 왔는가
이 메일은 누구에게 배달되어야 하는가&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;577&quot; data-start=&quot;560&quot; data-ke-size=&quot;size16&quot;&gt;이제 남은 것은 편지 내용이다.&lt;/p&gt;
&lt;p data-end=&quot;646&quot; data-start=&quot;579&quot; data-ke-size=&quot;size16&quot;&gt;메일 화면에서 우리가 보는 것들.&lt;br /&gt;From:, To:, Subject: 같은 헤더.&lt;br /&gt;그리고 실제 본문.&lt;/p&gt;
&lt;p data-end=&quot;672&quot; data-start=&quot;648&quot; data-ke-size=&quot;size16&quot;&gt;이제 드디어 그것을 서버에게 건네볼 차례다.&lt;/p&gt;
&lt;p data-end=&quot;694&quot; data-start=&quot;674&quot; data-ke-size=&quot;size16&quot;&gt;그런데 여기서 새로운 질문이 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;716&quot; data-start=&quot;696&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본문은 언제 끝났다고 말할까?&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;741&quot; data-start=&quot;718&quot; data-section-id=&quot;o7c405&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DATA는 편지지를 건네겠다는 말이다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;781&quot; data-start=&quot;743&quot; data-ke-size=&quot;size16&quot;&gt;SMTP에서 메일 본문을 보내기 위해 사용하는 명령은 DATA다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;C: DATA&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;824&quot; data-start=&quot;804&quot; data-ke-size=&quot;size16&quot;&gt;처음 이 명령을 보면 조금 심심하다.&lt;/p&gt;
&lt;p data-end=&quot;847&quot; data-start=&quot;826&quot; data-ke-size=&quot;size16&quot;&gt;발신자 주소처럼 인자가 붙지도 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;MAIL FROM:&amp;lt;alice@example.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;914&quot; data-start=&quot;892&quot; data-ke-size=&quot;size16&quot;&gt;수신자 주소처럼 목적지가 붙지도 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;RCPT TO:&amp;lt;bob@example.net&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;966&quot; data-start=&quot;955&quot; data-ke-size=&quot;size16&quot;&gt;그저 이렇게 말한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;DATA&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1017&quot; data-start=&quot;986&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 짧은 명령은 SMTP 대화의 분위기를 바꾼다.&lt;/p&gt;
&lt;p data-end=&quot;1065&quot; data-start=&quot;1019&quot; data-ke-size=&quot;size16&quot;&gt;지금까지는 클라이언트가 한 줄 명령을 보내고, 서버가 한 줄 응답을 주는 식이었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: MAIL FROM:&amp;lt;alice@example.com&amp;gt;
S: 250 OK

C: RCPT TO:&amp;lt;bob@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1207&quot; data-start=&quot;1163&quot; data-ke-size=&quot;size16&quot;&gt;그런데 DATA 이후에는 클라이언트가 여러 줄의 메일 내용을 보낼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1239&quot; data-start=&quot;1209&quot; data-ke-size=&quot;size16&quot;&gt;서버는 DATA 명령을 받으면 보통 이렇게 답한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;S: 354 Start mail input; end with &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1321&quot; data-start=&quot;1302&quot; data-ke-size=&quot;size16&quot;&gt;여기서 354는 성공이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1344&quot; data-start=&quot;1323&quot; data-ke-size=&quot;size16&quot;&gt;아직 메일을 다 받았다는 뜻도 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1360&quot; data-start=&quot;1346&quot; data-ke-size=&quot;size16&quot;&gt;오히려 이런 뜻에 가깝다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;좋다.
이제 본문을 보내라.
본문은 &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;로 끝내라.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1609&quot; data-start=&quot;1415&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5321에서도 SMTP 메일 트랜잭션은 MAIL, 하나 이상의 RCPT, 그리고 DATA 순서로 진행되며, DATA가 수락되면 서버가 354 응답을 반환하고 이후 줄들을 메일 데이터로 처리한다고 설명한다. 메일 데이터는 마침표 하나만 있는 줄로 종료된다.&lt;/p&gt;
&lt;p data-end=&quot;1631&quot; data-start=&quot;1611&quot; data-ke-size=&quot;size16&quot;&gt;오늘 구현할 것은 바로 이 부분이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;EHLO
  &amp;darr;
MAIL FROM
  &amp;darr;
RCPT TO
  &amp;darr;
DATA
  &amp;darr;
메일 헤더와 본문
  &amp;darr;
.
  &amp;darr;
250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1741&quot; data-start=&quot;1717&quot; data-section-id=&quot;11a70in&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;354는 &amp;ldquo;계속 보내도 된다&amp;rdquo;는 신호다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1779&quot; data-start=&quot;1743&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 응답 코드를 처음 보면 250 OK가 가장 익숙하다.&lt;/p&gt;
&lt;p data-end=&quot;1796&quot; data-start=&quot;1781&quot; data-ke-size=&quot;size16&quot;&gt;성공했다는 뜻이기 때문이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1859&quot; data-start=&quot;1821&quot; data-ke-size=&quot;size16&quot;&gt;하지만 DATA 명령 뒤에는 바로 250 OK가 오지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;1874&quot; data-start=&quot;1861&quot; data-ke-size=&quot;size16&quot;&gt;먼저 354가 온다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;C: DATA
S: 354 Start mail input; end with &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1959&quot; data-start=&quot;1945&quot; data-ke-size=&quot;size16&quot;&gt;이 응답은 중간 응답이다.&lt;/p&gt;
&lt;p data-end=&quot;1977&quot; data-start=&quot;1961&quot; data-ke-size=&quot;size16&quot;&gt;서버가 이렇게 말하는 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;DATA 명령은 받았다.
하지만 아직 메일을 받은 것은 아니다.
이제 내용을 보내라.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2051&quot; data-start=&quot;2040&quot; data-ke-size=&quot;size16&quot;&gt;이 차이가 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;2107&quot; data-start=&quot;2053&quot; data-ke-size=&quot;size16&quot;&gt;354는 &amp;ldquo;본문 입력을 시작하라&amp;rdquo;는 신호다.&lt;br /&gt;250은 &amp;ldquo;본문까지 다 받았다&amp;rdquo;는 신호다.&lt;/p&gt;
&lt;p data-end=&quot;2130&quot; data-start=&quot;2109&quot; data-ke-size=&quot;size16&quot;&gt;그래서 DATA 흐름은 이렇게 나뉜다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;C: DATA
S: 354 Start mail input

C: From: Alice &amp;lt;alice@example.com&amp;gt;
C: To: Bob &amp;lt;bob@example.net&amp;gt;
C: Subject: Hello
C:
C: Hello, SMTP.
C: .

S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2331&quot; data-start=&quot;2295&quot; data-ke-size=&quot;size16&quot;&gt;서버가 354를 보낸 뒤에는 클라이언트가 메일 내용을 보낸다.&lt;/p&gt;
&lt;p data-end=&quot;2357&quot; data-start=&quot;2333&quot; data-ke-size=&quot;size16&quot;&gt;그리고 서버는 본문이 끝날 때까지 기다린다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2382&quot; data-start=&quot;2359&quot; data-section-id=&quot;12u7ae9&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DATA 이후에는 명령이 잠시 멈춘다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2402&quot; data-start=&quot;2384&quot; data-ke-size=&quot;size16&quot;&gt;여기서 재미있는 점이 하나 있다.&lt;/p&gt;
&lt;p data-end=&quot;2455&quot; data-start=&quot;2404&quot; data-ke-size=&quot;size16&quot;&gt;DATA 이후에는 클라이언트가 보내는 줄들이 더 이상 SMTP 명령으로 해석되지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;2489&quot; data-start=&quot;2457&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 DATA 입력 중에 이런 줄을 보냈다고 해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cos&quot;&gt;&lt;code&gt;QUIT&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2533&quot; data-start=&quot;2509&quot; data-ke-size=&quot;size16&quot;&gt;평소라면 QUIT은 연결 종료 명령이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: QUIT
S: 221 localhost Bye&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2597&quot; data-start=&quot;2577&quot; data-ke-size=&quot;size16&quot;&gt;하지만 DATA 이후라면 다르다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;C: DATA
S: 354 Start mail input; end with &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;
C: QUIT
C: .
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2716&quot; data-start=&quot;2691&quot; data-ke-size=&quot;size16&quot;&gt;이때 QUIT은 연결 종료 명령이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;2742&quot; data-start=&quot;2718&quot; data-ke-size=&quot;size16&quot;&gt;그냥 메일 본문에 들어간 한 줄의 텍스트다.&lt;/p&gt;
&lt;p data-end=&quot;2804&quot; data-start=&quot;2744&quot; data-ke-size=&quot;size16&quot;&gt;서버는 DATA 모드에 들어간 뒤부터는 명령어를 찾지 않는다.&lt;br /&gt;본문의 끝을 나타내는 특별한 줄만 찾는다.&lt;/p&gt;
&lt;p data-end=&quot;2823&quot; data-start=&quot;2806&quot; data-ke-size=&quot;size16&quot;&gt;그 특별한 줄이 바로 이것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2853&quot; data-start=&quot;2840&quot; data-ke-size=&quot;size16&quot;&gt;마침표 하나만 있는 줄.&lt;/p&gt;
&lt;p data-end=&quot;2914&quot; data-start=&quot;2855&quot; data-ke-size=&quot;size16&quot;&gt;이 줄이 나오기 전까지는 MAIL FROM도, RCPT TO도, QUIT도 모두 그냥 본문이다.&lt;/p&gt;
&lt;p data-end=&quot;2941&quot; data-start=&quot;2916&quot; data-ke-size=&quot;size16&quot;&gt;이 부분에서 SMTP가 조금 다르게 느껴진다.&lt;/p&gt;
&lt;p data-end=&quot;2965&quot; data-start=&quot;2943&quot; data-ke-size=&quot;size16&quot;&gt;프로토콜은 단순히 명령어 목록이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;3000&quot; data-start=&quot;2967&quot; data-ke-size=&quot;size16&quot;&gt;같은 문자열도 어느 상태에서 오느냐에 따라 의미가 달라진다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cos&quot;&gt;&lt;code&gt;DATA 이전의 QUIT &amp;rarr; 연결 종료 명령
DATA 이후의 QUIT &amp;rarr; 메일 본문 한 줄&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3111&quot; data-start=&quot;3066&quot; data-ke-size=&quot;size16&quot;&gt;지난 글에서 상태가 필요하다고 느꼈다면, 이번 글에서는 그 이유가 더 분명해진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3132&quot; data-start=&quot;3113&quot; data-section-id=&quot;vqbwe0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;본문은 마침표 한 줄로 끝난다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;3174&quot; data-start=&quot;3134&quot; data-ke-size=&quot;size16&quot;&gt;SMTP에는 HTTP의 Content-Length 같은 방식이 없다.&lt;/p&gt;
&lt;p data-end=&quot;3215&quot; data-start=&quot;3176&quot; data-ke-size=&quot;size16&quot;&gt;본문이 몇 바이트인지 먼저 알려주고, 그 길이만큼 읽는 구조가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;3250&quot; data-start=&quot;3217&quot; data-ke-size=&quot;size16&quot;&gt;대신 SMTP는 아주 오래된 방식으로 본문의 끝을 표시한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;&amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3304&quot; data-start=&quot;3279&quot; data-ke-size=&quot;size16&quot;&gt;사람이 직접 입력할 때는 보통 이렇게 보인다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3361&quot; data-start=&quot;3321&quot; data-ke-size=&quot;size16&quot;&gt;즉, 한 줄에 마침표 하나만 입력하고 Enter를 누르면 본문이 끝난다.&lt;/p&gt;
&lt;p data-end=&quot;3379&quot; data-start=&quot;3363&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 대화가 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: DATA
S: 354 Start mail input; end with &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;
C: From: Alice &amp;lt;alice@example.com&amp;gt;
C: To: Bob &amp;lt;bob@example.net&amp;gt;
C: Subject: Hello SMTP
C:
C: Hello.
C: This is my first SMTP message.
C: .
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3621&quot; data-start=&quot;3599&quot; data-ke-size=&quot;size16&quot;&gt;서버가 저장해야 하는 본문은 여기까지다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;From: Alice &amp;lt;alice@example.com&amp;gt;
To: Bob &amp;lt;bob@example.net&amp;gt;
Subject: Hello SMTP

Hello.
This is my first SMTP message.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3776&quot; data-start=&quot;3753&quot; data-ke-size=&quot;size16&quot;&gt;마지막의. 은 본문에 포함되지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;3794&quot; data-start=&quot;3778&quot; data-ke-size=&quot;size16&quot;&gt;그것은 내용이 아니라 신호다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;본문은 여기서 끝났다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3848&quot; data-start=&quot;3822&quot; data-section-id=&quot;1upm060&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;그럼 진짜 마침표 한 줄을 보내고 싶으면?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;3869&quot; data-start=&quot;3850&quot; data-ke-size=&quot;size16&quot;&gt;여기서 바로 이상한 상황이 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;3913&quot; data-start=&quot;3871&quot; data-ke-size=&quot;size16&quot;&gt;메일 본문에 정말로 마침표 하나만 있는 줄을 넣고 싶다면 어떻게 해야 할까?&lt;/p&gt;
&lt;p data-end=&quot;3940&quot; data-start=&quot;3915&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 본문을 보내고 싶다고 해보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;첫 번째 줄
.
세 번째 줄&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4009&quot; data-start=&quot;3971&quot; data-ke-size=&quot;size16&quot;&gt;하지만 DATA 모드에서 마침표 하나만 있는 줄은 본문 종료 신호다.&lt;/p&gt;
&lt;p data-end=&quot;4065&quot; data-start=&quot;4011&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 본문 안에 있는 마침표 한 줄과, 본문 종료를 나타내는 마침표 한 줄을 어떻게 구분할까?&lt;/p&gt;
&lt;p data-end=&quot;4110&quot; data-start=&quot;4067&quot; data-ke-size=&quot;size16&quot;&gt;SMTP는 이 문제를 해결하기 위해 마침표를 하나 더 붙이는 규칙을 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;4157&quot; data-start=&quot;4112&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트는 본문 줄이 마침표로 시작하면, 앞에 마침표를 하나 더 붙여서 보낸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;원래 보내고 싶은 줄: .
실제로 전송하는 줄: ..&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4243&quot; data-start=&quot;4202&quot; data-ke-size=&quot;size16&quot;&gt;서버는 DATA를 읽다가 줄이 마침표 하나만 있으면 본문 종료로 처리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4321&quot; data-start=&quot;4260&quot; data-ke-size=&quot;size16&quot;&gt;하지만 줄이 마침표로 시작하면서 그 뒤에 다른 문자가 있으면, 앞의 마침표 하나를 제거하고 본문으로 저장한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;수신한 줄: ..
저장할 줄: .

수신한 줄: ..hello
저장할 줄: .hello&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4424&quot; data-start=&quot;4385&quot; data-ke-size=&quot;size16&quot;&gt;이 규칙을 보통 dot-stuffing, 또는 투명성 처리라고 부른다고 한다.&lt;/p&gt;
&lt;p data-end=&quot;4447&quot; data-start=&quot;4426&quot; data-ke-size=&quot;size16&quot;&gt;이름은 조금 낯설지만 목적은 단순하다.&lt;/p&gt;
&lt;p data-end=&quot;4492&quot; data-start=&quot;4449&quot; data-ke-size=&quot;size16&quot;&gt;본문 안에 어떤 텍스트가 오더라도, 종료 신호와 충돌하지 않게 하려는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;4643&quot; data-start=&quot;4494&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5321도 메일 데이터 종료 표시로 인해 사용자의 텍스트가 방해받지 않도록, 클라이언트가 마침표로 시작하는 줄 앞에 마침표를 하나 더 붙이고 서버가 이를 다시 제거하는 투명성 절차를 설명한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;4671&quot; data-start=&quot;4645&quot; data-section-id=&quot;1kgtqpf&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 구현은 발송하지 않고 로그로 확인한다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;4705&quot; data-start=&quot;4673&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 메일을 실제로 다른 서버로 보내지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;4723&quot; data-start=&quot;4707&quot; data-ke-size=&quot;size16&quot;&gt;메일박스에 저장하지도 않는다.&lt;/p&gt;
&lt;p data-end=&quot;4746&quot; data-start=&quot;4725&quot; data-ke-size=&quot;size16&quot;&gt;파일로 남기는 것도 아직 하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;4771&quot; data-start=&quot;4748&quot; data-ke-size=&quot;size16&quot;&gt;대신 서버가 받은 내용을 로그에 찍어본다.&lt;/p&gt;
&lt;p data-end=&quot;4782&quot; data-start=&quot;4773&quot; data-ke-size=&quot;size16&quot;&gt;이유는 단순하다.&lt;/p&gt;
&lt;p data-end=&quot;4823&quot; data-start=&quot;4784&quot; data-ke-size=&quot;size16&quot;&gt;아직 우리가 확인하고 싶은 것은 &amp;ldquo;메일이 실제로 배달되는가&amp;rdquo;가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;4848&quot; data-start=&quot;4825&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서 확인하고 싶은 것은 이것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;DATA 명령을 받으면 354를 응답하는가
그 이후의 줄들을 본문으로 읽는가
마침표 하나만 있는 줄에서 본문을 끝내는가
본문이 끝난 뒤 250 OK를 반환하는가&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4980&quot; data-start=&quot;4953&quot; data-ke-size=&quot;size16&quot;&gt;즉, 이번 구현의 목표는 배달이 아니라 관찰이다.&lt;/p&gt;
&lt;p data-end=&quot;5171&quot; data-start=&quot;4982&quot; data-ke-size=&quot;size16&quot;&gt;작업 지침서에서도 Phase 1의 DATA 처리 요구사항을 RCPT TO 이후에만 사용 가능하도록 하고, 354 응답 후 본문을 수신하며, &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;로 종료를 감지하고, 마침표로 시작하는 줄에 대한 투명성 처리를 수행하는 흐름으로 정리하고 있다.&lt;/p&gt;
&lt;p data-end=&quot;5188&quot; data-start=&quot;5173&quot; data-ke-size=&quot;size16&quot;&gt;핵심 구조는 이런 모습이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;case &quot;DATA&quot;:
if state != StateRcpt {
    if err := writeLine(&quot;503 Bad sequence of commands&quot;); err != nil {
        return
    }
    continue
}
if err := writeLine(&quot;354 Start mail input; end with &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;&quot;); err != nil {
    return
}
// 메시지 수신 루프
// 250 OK를 받으면 메일 데이터 전송 모드로 전환됨
// 메일 데이터는 &quot;.&quot; 하나만 있는 줄을 만날 때까지 읽는다
msgLines := []string{}
for {
    // 한 줄씩 읽는다
    line, err := reader.ReadString('\n')
    if err != nil {
        log.Println(&quot;error reading data&quot;, err)
        if err := writeLine(&quot;554 Error on DATA read&quot;); err != nil {
            return
        }
        return
    }
    line = strings.TrimRight(line, &quot;\r\n&quot;)

    if line == &quot;.&quot; {
        break
    }
    line = strings.TrimPrefix(line, &quot;.&quot;)
    msgLines = append(msgLines, line)
    // 현재는 단순히 로그에 기록
    log.Printf(&quot;DATA: %s&quot;, line)

}
message = strings.Join(msgLines, &quot;\n&quot;)
log.Println(&quot;=== MESSAGE RECEIVED ===&quot;)
log.Printf(&quot;MAIL FROM: %s&quot;, from)
log.Printf(&quot;RCPT TO: %v&quot;, rcpt)
log.Printf(&quot;MESSAGE:\n%s&quot;, message)
log.Printf(&quot;메일 수신 완료:\n%s&quot;, message)

resetSession()
if err := writeLine(&quot;250 OK&quot;); err != nil {
    return
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6119&quot; data-start=&quot;6101&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 부분은 이 줄이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;if dataLine == &quot;.&quot; {
	break
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6179&quot; data-start=&quot;6162&quot; data-ke-size=&quot;size16&quot;&gt;본문 종료 신호를 만난 것이다.&lt;/p&gt;
&lt;p data-end=&quot;6206&quot; data-start=&quot;6181&quot; data-ke-size=&quot;size16&quot;&gt;이 줄을 만나기 전까지는 계속 본문을 읽는다.&lt;/p&gt;
&lt;p data-end=&quot;6230&quot; data-start=&quot;6208&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이 부분은 마침표 투명성 처리다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;line = strings.TrimPrefix(line, &quot;.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6329&quot; data-start=&quot;6308&quot; data-ke-size=&quot;size16&quot;&gt;마침표 하나만 있는 줄은 종료 신호다.&lt;/p&gt;
&lt;p data-end=&quot;6395&quot; data-start=&quot;6331&quot; data-ke-size=&quot;size16&quot;&gt;하지만 마침표로 시작하면서 다른 내용이 있는 줄은 본문이다.&lt;br /&gt;그래서 앞에 붙은 마침표 하나를 제거하고 저장한다.&lt;/p&gt;
&lt;p data-end=&quot;6411&quot; data-start=&quot;6397&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 로그에 찍는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;log.Printf(&quot;MAIL FROM: %s&quot;, from)
log.Printf(&quot;RCPT TO: %v&quot;, rcpt)
log.Printf(&quot;MESSAGE:\n%s&quot;, message)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6540&quot; data-start=&quot;6526&quot; data-ke-size=&quot;size16&quot;&gt;아직 배달은 하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;6585&quot; data-start=&quot;6542&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이제 서버는 메일 한 통의 봉투와 내용을 모두 받아볼 수 있게 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;6598&quot; data-start=&quot;6587&quot; data-section-id=&quot;1gxpqmp&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;직접 대화해보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;6609&quot; data-start=&quot;6600&quot; data-ke-size=&quot;size16&quot;&gt;서버를 실행한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;go run main.go&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6653&quot; data-start=&quot;6639&quot; data-ke-size=&quot;size16&quot;&gt;다른 터미널에서 접속한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;telnet localhost 2525&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6698&quot; data-start=&quot;6686&quot; data-ke-size=&quot;size16&quot;&gt;서버가 먼저 인사한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;S: 220 localhost Simple Mail Transfer Service Ready&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6777&quot; data-start=&quot;6765&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 인사한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: EHLO client.local
S: 250-localhost greets client.local
S: 250 HELP&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6870&quot; data-start=&quot;6862&quot; data-ke-size=&quot;size16&quot;&gt;봉투를 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: MAIL FROM:&amp;lt;alice@example.com&amp;gt;
S: 250 OK

C: RCPT TO:&amp;lt;bob@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6979&quot; data-start=&quot;6968&quot; data-ke-size=&quot;size16&quot;&gt;이제 본문을 보낸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: DATA
S: 354 Start mail input; end with &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;
C: From: Alice &amp;lt;alice@example.com&amp;gt;
C: To: Bob &amp;lt;bob@example.net&amp;gt;
C: Subject: Hello SMTP
C:
C: Hello Bob.
C: This message was received by my SMTP server.
C: .
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7232&quot; data-start=&quot;7217&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 연결을 종료한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: QUIT
S: 221 localhost Bye&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7290&quot; data-start=&quot;7276&quot; data-ke-size=&quot;size16&quot;&gt;전체 대화는 이렇게 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오후 1.50.25.png&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUfIP3/dJMcacpuzOT/aQcpfO9VS3aUaNVbpFvy5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUfIP3/dJMcacpuzOT/aQcpfO9VS3aUaNVbpFvy5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUfIP3/dJMcacpuzOT/aQcpfO9VS3aUaNVbpFvy5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUfIP3%2FdJMcacpuzOT%2FaQcpfO9VS3aUaNVbpFvy5k%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;814&quot; height=&quot;704&quot; data-filename=&quot;스크린샷 2026-05-07 오후 1.50.25.png&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7783&quot; data-start=&quot;7761&quot; data-ke-size=&quot;size16&quot;&gt;서버 로그에는 대략 이런 내용이 남는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오후 1.57.02.png&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU2d1Q/dJMcaakTrnS/XxSQkpMME7uulQGI7EuM40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU2d1Q/dJMcaakTrnS/XxSQkpMME7uulQGI7EuM40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU2d1Q/dJMcaakTrnS/XxSQkpMME7uulQGI7EuM40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU2d1Q%2FdJMcaakTrnS%2FXxSQkpMME7uulQGI7EuM40%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;1148&quot; height=&quot;318&quot; data-filename=&quot;스크린샷 2026-05-07 오후 1.57.02.png&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8061&quot; data-start=&quot;8027&quot; data-ke-size=&quot;size16&quot;&gt;이 로그를 보는 순간, SMTP가 조금 더 구체적으로 보인다.&lt;/p&gt;
&lt;p data-end=&quot;8101&quot; data-start=&quot;8063&quot; data-ke-size=&quot;size16&quot;&gt;메일은 어느 날 갑자기 완성된 객체로 서버에 도착하는 것이 아니었다.&lt;/p&gt;
&lt;p data-end=&quot;8115&quot; data-start=&quot;8103&quot; data-ke-size=&quot;size16&quot;&gt;먼저 서버와 인사한다.&lt;/p&gt;
&lt;p data-end=&quot;8129&quot; data-start=&quot;8117&quot; data-ke-size=&quot;size16&quot;&gt;그다음 봉투를 만든다.&lt;/p&gt;
&lt;p data-end=&quot;8147&quot; data-start=&quot;8131&quot; data-ke-size=&quot;size16&quot;&gt;그리고 본문 입력을 시작한다.&lt;/p&gt;
&lt;p data-end=&quot;8181&quot; data-start=&quot;8149&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 마침표 한 줄을 보내서 본문이 끝났다고 말한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;8199&quot; data-start=&quot;8183&quot; data-section-id=&quot;53e4sl&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마침표 규칙도 확인해 보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;8230&quot; data-start=&quot;8201&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 본문 안에 마침표로 시작하는 줄을 넣어보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: DATA
S: 354 Start mail input; end with &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;
C: From: Alice &amp;lt;alice@example.com&amp;gt;
C: To: Bob &amp;lt;bob@example.net&amp;gt;
C: Subject: Dot Test
C:
C: This line is normal.
C: ..This line starts with a dot.
C: .
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8480&quot; data-start=&quot;8462&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 보낸 줄은 이것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;..This line starts with a dot.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8550&quot; data-start=&quot;8526&quot; data-ke-size=&quot;size16&quot;&gt;하지만 서버가 저장해야 하는 줄은 이것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;.This line starts with a dot.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오후 2.00.34.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dabsEt/dJMcab5aVr5/bmrpRs1rg0XkQUsGqAYO9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dabsEt/dJMcab5aVr5/bmrpRs1rg0XkQUsGqAYO9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dabsEt/dJMcab5aVr5/bmrpRs1rg0XkQUsGqAYO9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdabsEt%2FdJMcab5aVr5%2FbmrpRs1rg0XkQUsGqAYO9k%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;668&quot; height=&quot;206&quot; data-filename=&quot;스크린샷 2026-05-07 오후 2.00.34.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8637&quot; data-start=&quot;8595&quot; data-ke-size=&quot;size16&quot;&gt;앞의 마침표 하나는 DATA 종료 신호와 충돌하지 않기 위해 추가된 것이다.&lt;/p&gt;
&lt;p data-end=&quot;8663&quot; data-start=&quot;8639&quot; data-ke-size=&quot;size16&quot;&gt;서버는 그 마침표 하나를 제거하고 저장한다.&lt;/p&gt;
&lt;p data-end=&quot;8710&quot; data-start=&quot;8665&quot; data-ke-size=&quot;size16&quot;&gt;이 규칙이 없으면 본문 안에 마침표 하나만 있는 줄을 안전하게 보낼 방법이 없다.&lt;/p&gt;
&lt;p data-end=&quot;8725&quot; data-start=&quot;8712&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 사소해 보인다.&lt;/p&gt;
&lt;p data-end=&quot;8764&quot; data-start=&quot;8727&quot; data-ke-size=&quot;size16&quot;&gt;하지만 프로토콜을 구현하다 보면 이런 사소한 약속들이 계속 나온다.&lt;/p&gt;
&lt;p data-end=&quot;8841&quot; data-start=&quot;8766&quot; data-ke-size=&quot;size16&quot;&gt;줄은 어떻게 끝나는가.&lt;br /&gt;명령은 언제 명령으로 해석되는가.&lt;br /&gt;본문은 어디서 끝나는가.&lt;br /&gt;본문 안의 특수한 줄은 어떻게 표현하는가.&lt;/p&gt;
&lt;p data-end=&quot;8877&quot; data-start=&quot;8843&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 구현은 이런 약속들을 하나씩 직접 확인하는 과정이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;8905&quot; data-start=&quot;8879&quot; data-section-id=&quot;az6ny8&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DATA를 너무 일찍 보내면 어떻게 될까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;8921&quot; data-start=&quot;8907&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 순서를 어겨보자.&lt;/p&gt;
&lt;p data-end=&quot;8971&quot; data-start=&quot;8923&quot; data-ke-size=&quot;size16&quot;&gt;MAIL FROM과 RCPT TO 없이 바로 DATA를 보내면 어떻게 될까?&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;S: 220 localhost Simple Mail Transfer Service Ready
C: EHLO client.local
S: 250-localhost greets client.local
S: 250 HELP
C: DATA
S: 503 Bad sequence of commands&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9164&quot; data-start=&quot;9148&quot; data-ke-size=&quot;size16&quot;&gt;서버는 503을 반환한다.&lt;/p&gt;
&lt;p data-end=&quot;9189&quot; data-start=&quot;9166&quot; data-ke-size=&quot;size16&quot;&gt;본문을 보내려면 먼저 봉투가 있어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;9225&quot; data-start=&quot;9191&quot; data-ke-size=&quot;size16&quot;&gt;서버 입장에서는 아직 이 메일이 누구에게 가야 하는지 모른다.&lt;/p&gt;
&lt;p data-end=&quot;9243&quot; data-start=&quot;9227&quot; data-ke-size=&quot;size16&quot;&gt;그러니 본문을 받을 수 없다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;MAIL FROM 없음
RCPT TO 없음
DATA 불가&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9311&quot; data-start=&quot;9290&quot; data-ke-size=&quot;size16&quot;&gt;이 흐름은 편지에 비유하면 자연스럽다.&lt;/p&gt;
&lt;p data-end=&quot;9354&quot; data-start=&quot;9313&quot; data-ke-size=&quot;size16&quot;&gt;편지지를 아무리 정성껏 써도, 봉투에 받을 사람이 없으면 배달할 수 없다.&lt;/p&gt;
&lt;p data-end=&quot;9370&quot; data-start=&quot;9356&quot; data-ke-size=&quot;size16&quot;&gt;SMTP에서도 마찬가지다.&lt;/p&gt;
&lt;p data-end=&quot;9389&quot; data-start=&quot;9372&quot; data-ke-size=&quot;size16&quot;&gt;본문보다 먼저 봉투가 필요하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;9404&quot; data-start=&quot;9391&quot; data-section-id=&quot;1r5lc0m&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;250 OK의 무게&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;9438&quot; data-start=&quot;9406&quot; data-ke-size=&quot;size16&quot;&gt;이번 구현에서는 DATA로 받은 메시지를 로그에만 찍는다.&lt;/p&gt;
&lt;p data-end=&quot;9470&quot; data-start=&quot;9440&quot; data-ke-size=&quot;size16&quot;&gt;그러니 지금의 250 OK는 실험용 응답에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;9527&quot; data-start=&quot;9472&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 SMTP 서버에서 DATA 종료 후 보내는 250 OK는 꽤 무거운 의미를 가진다.&lt;/p&gt;
&lt;p data-end=&quot;9594&quot; data-start=&quot;9529&quot; data-ke-size=&quot;size16&quot;&gt;서버가 본문 종료 표시를 받고, 메시지를 처리한 뒤 250 OK를 반환하면, 그 서버는 메시지를 받아들인 것이다.&lt;/p&gt;
&lt;p data-end=&quot;9731&quot; data-start=&quot;9596&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5321은 DATA 종료 후 긍정적인 완료 응답을 보내면 서버가 메시지에 대한 책임을 수락한다고 설명한다. 이후 배달에 실패하면 적절한 방식으로 실패를 보고해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;9755&quot; data-start=&quot;9733&quot; data-ke-size=&quot;size16&quot;&gt;지금은 이 책임을 전부 구현하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;9812&quot; data-start=&quot;9757&quot; data-ke-size=&quot;size16&quot;&gt;아직 우리는 메시지를 저장하지도 않고, 다른 서버로 전달하지도 않고, 실패 알림을 만들지도 않는다.&lt;/p&gt;
&lt;p data-end=&quot;9835&quot; data-start=&quot;9814&quot; data-ke-size=&quot;size16&quot;&gt;다만 이 사실은 기억해 둘 필요가 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;354는 본문을 보내라는 말
250은 본문을 받았다는 말&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9970&quot; data-start=&quot;9882&quot; data-ke-size=&quot;size16&quot;&gt;지금은 로그에 찍는 것으로 대신하지만, 언젠가 이 서버가 실제로 메시지를 저장하거나 전달하게 된다면 250 OK를 보내는 시점은 더 신중하게 다뤄야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;9988&quot; data-start=&quot;9972&quot; data-section-id=&quot;1qgzi9p&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;오늘 만든 것은 무엇인가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;10016&quot; data-start=&quot;9990&quot; data-ke-size=&quot;size16&quot;&gt;오늘 만든 서버는 아직 메일을 배달하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;10033&quot; data-start=&quot;10018&quot; data-ke-size=&quot;size16&quot;&gt;사용자의 받은 편지함도 없다.&lt;/p&gt;
&lt;p data-end=&quot;10049&quot; data-start=&quot;10035&quot; data-ke-size=&quot;size16&quot;&gt;파일로 저장하지도 않는다.&lt;/p&gt;
&lt;p data-end=&quot;10094&quot; data-start=&quot;10051&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이제 이 서버는 SMTP 메일 트랜잭션의 핵심 흐름을 따라갈 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;S: 220 Service Ready
C: EHLO client.local
S: 250 OK

C: MAIL FROM:&amp;lt;alice@example.com&amp;gt;
S: 250 OK

C: RCPT TO:&amp;lt;bob@example.net&amp;gt;
S: 250 OK

C: DATA
S: 354 Start mail input

C: From: Alice &amp;lt;alice@example.com&amp;gt;
C: To: Bob &amp;lt;bob@example.net&amp;gt;
C: Subject: Hello
C:
C: Hello.
C: .
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10412&quot; data-start=&quot;10389&quot; data-ke-size=&quot;size16&quot;&gt;이제 메일에는 봉투와 내용이 모두 생겼다.&lt;/p&gt;
&lt;p data-end=&quot;10447&quot; data-start=&quot;10414&quot; data-ke-size=&quot;size16&quot;&gt;봉투는 MAIL FROM과 RCPT TO로 만들었다.&lt;/p&gt;
&lt;p data-end=&quot;10468&quot; data-start=&quot;10449&quot; data-ke-size=&quot;size16&quot;&gt;내용은 DATA 이후에 보냈다.&lt;/p&gt;
&lt;p data-end=&quot;10494&quot; data-start=&quot;10470&quot; data-ke-size=&quot;size16&quot;&gt;그리고 마침표 한 줄로 본문의 끝을 알렸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;봉투
  ├─ MAIL FROM
  └─ RCPT TO

내용
  ├─ From:
  ├─ To:
  ├─ Subject:
  └─ Body

종료 신호
  └─ .&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10629&quot; data-start=&quot;10602&quot; data-ke-size=&quot;size16&quot;&gt;이전까지 이메일은 화면에서 보는 결과에 가까웠다.&lt;/p&gt;
&lt;p data-end=&quot;10661&quot; data-start=&quot;10631&quot; data-ke-size=&quot;size16&quot;&gt;이제는 그 결과가 만들어지는 과정을 조금씩 보고 있다.&lt;/p&gt;
&lt;p data-end=&quot;10675&quot; data-start=&quot;10663&quot; data-ke-size=&quot;size16&quot;&gt;서버가 먼저 인사했다.&lt;/p&gt;
&lt;p data-end=&quot;10693&quot; data-start=&quot;10677&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 자신을 소개했다.&lt;/p&gt;
&lt;p data-end=&quot;10703&quot; data-start=&quot;10695&quot; data-ke-size=&quot;size16&quot;&gt;봉투를 건넸다.&lt;/p&gt;
&lt;p data-end=&quot;10724&quot; data-start=&quot;10705&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이제 편지 내용까지 건넸다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;10744&quot; data-start=&quot;10726&quot; data-section-id=&quot;w6ps8s&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 글의 끝에서 남는 질문&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;10888&quot; data-start=&quot;10870&quot; data-ke-size=&quot;size16&quot;&gt;오늘 확인한 것은 본문의 경계다.&lt;/p&gt;
&lt;p data-end=&quot;10925&quot; data-start=&quot;10890&quot; data-ke-size=&quot;size16&quot;&gt;SMTP에서 본문은 그냥 연결이 끊길 때 끝나는 것이 아니었다.&lt;/p&gt;
&lt;p data-end=&quot;10947&quot; data-start=&quot;10927&quot; data-ke-size=&quot;size16&quot;&gt;길이를 미리 알려주는 것도 아니었다.&lt;/p&gt;
&lt;p data-end=&quot;10972&quot; data-start=&quot;10949&quot; data-ke-size=&quot;size16&quot;&gt;서버는 마침표 하나만 있는 줄을 기다린다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11021&quot; data-start=&quot;10989&quot; data-ke-size=&quot;size16&quot;&gt;그 줄을 만났을 때, 서버는 비로소 이렇게 말할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11057&quot; data-start=&quot;11043&quot; data-ke-size=&quot;size16&quot;&gt;이제 다음 질문이 남는다.&lt;/p&gt;
&lt;p data-end=&quot;11083&quot; data-start=&quot;11059&quot; data-ke-size=&quot;size16&quot;&gt;로그에 찍힌 이 메시지는 어디로 가야 할까?&lt;/p&gt;
&lt;p data-end=&quot;11115&quot; data-start=&quot;11085&quot; data-ke-size=&quot;size16&quot;&gt;메일을 받았다는 것은 단순히 화면에 출력했다는 뜻일까?&lt;/p&gt;
&lt;p data-end=&quot;11135&quot; data-start=&quot;11117&quot; data-ke-size=&quot;size16&quot;&gt;아니면 어딘가에 저장되어야 할까?&lt;/p&gt;
&lt;p data-end=&quot;11217&quot; data-start=&quot;11137&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 지금까지 받은 봉투와 본문을 하나의 메일 객체로 다뤄보고, 로그로만 흘려보내던 메시지를 조금 더 서버다운 방식으로 남겨보려 한다. 그전에! 나머지 명령어인 RSET NOOP를 구현할 것이다.&lt;/p&gt;</description>
      <category>Deep Dive/기타</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/238</guid>
      <comments>https://gowoong.tistory.com/238#entry238comment</comments>
      <pubDate>Thu, 7 May 2026 14:04:13 +0900</pubDate>
    </item>
    <item>
      <title>[SMTP] 4. 봉투와 편지는 다르다.</title>
      <link>https://gowoong.tistory.com/237</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서는 SMTP 서버와 처음으로 대화해봤다.&lt;/p&gt;
&lt;p data-end=&quot;229&quot; data-start=&quot;184&quot; data-ke-size=&quot;size16&quot;&gt;TCP 연결을 열면 클라이언트가 먼저 말하는 것이 아니라, 서버가 먼저 인사했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;S: 220 localhost Simple Mail Transfer Service Ready&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;320&quot; data-start=&quot;296&quot; data-ke-size=&quot;size16&quot;&gt;그다음 클라이언트는 자신이 누구인지 말했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: EHLO client.local
S: 250-localhost greets client.local
S: 250 HELP&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;444&quot; data-start=&quot;405&quot; data-ke-size=&quot;size16&quot;&gt;아주 짧은 대화였지만, 이때부터 이메일은 조금 다르게 보이기 시작했다.&lt;/p&gt;
&lt;p data-end=&quot;537&quot; data-start=&quot;446&quot; data-ke-size=&quot;size16&quot;&gt;이전까지 이메일은 화면에서 보는 것이 전부인 줄 알았다.&lt;br /&gt;보내는 사람, 받는 사람, 제목, 본문.&lt;br /&gt;메일 앱에서 보이는 그 정보들이 곧 이메일이라고 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;572&quot; data-start=&quot;539&quot; data-ke-size=&quot;size16&quot;&gt;하지만 SMTP 서버와 직접 대화해보니 순서가 조금 달랐다.&lt;/p&gt;
&lt;p data-end=&quot;616&quot; data-start=&quot;574&quot; data-ke-size=&quot;size16&quot;&gt;서버와 인사를 마친 뒤, 클라이언트는 곧바로 제목이나 본문을 보내지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;630&quot; data-start=&quot;618&quot; data-ke-size=&quot;size16&quot;&gt;먼저 이런 말을 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;MAIL FROM:&amp;lt;sender@example.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;693&quot; data-start=&quot;676&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이어서 이런 말을 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;RCPT TO:&amp;lt;user@example.net&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;753&quot; data-start=&quot;735&quot; data-ke-size=&quot;size16&quot;&gt;여기서 처음으로 헷갈림이 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;826&quot; data-start=&quot;755&quot; data-ke-size=&quot;size16&quot;&gt;메일에는 분명 From:과 To:가 있다.&lt;br /&gt;그런데 SMTP 명령에도 MAIL FROM과 RCPT TO가 있다.&lt;/p&gt;
&lt;p data-end=&quot;837&quot; data-start=&quot;828&quot; data-ke-size=&quot;size16&quot;&gt;둘은 같은 걸까?&lt;/p&gt;
&lt;p data-end=&quot;849&quot; data-start=&quot;839&quot; data-ke-size=&quot;size16&quot;&gt;아니면 다른 걸까?&lt;/p&gt;
&lt;p data-end=&quot;874&quot; data-start=&quot;851&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이 차이를 확인해보려 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;890&quot; data-start=&quot;876&quot; data-section-id=&quot;11dugsg&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;메일에는 봉투가 있다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;914&quot; data-start=&quot;892&quot; data-ke-size=&quot;size16&quot;&gt;우리가 종이 편지를 보낸다고 생각해보자.&lt;/p&gt;
&lt;p data-end=&quot;937&quot; data-start=&quot;916&quot; data-ke-size=&quot;size16&quot;&gt;편지를 쓸 때는 편지지에 내용을 쓴다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;안녕하세요.
오랜만에 연락드립니다.
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1007&quot; data-start=&quot;976&quot; data-ke-size=&quot;size16&quot;&gt;그리고 편지지 위쪽에 받는 사람 이름을 적을 수도 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;To. 홍길동
From. 김철수&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1072&quot; data-start=&quot;1040&quot; data-ke-size=&quot;size16&quot;&gt;하지만 우체국 직원은 편지지 안쪽을 보고 배달하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;1089&quot; data-start=&quot;1074&quot; data-ke-size=&quot;size16&quot;&gt;우체국이 보는 것은 봉투다.&lt;/p&gt;
&lt;p data-end=&quot;1180&quot; data-start=&quot;1091&quot; data-ke-size=&quot;size16&quot;&gt;봉투 겉면에는 받는 사람 주소가 있고, 경우에 따라 보내는 사람 주소도 있다.&lt;br /&gt;배달 과정에서 중요한 것은 편지지 안쪽의 문장이 아니라, 봉투에 적힌 주소다.&lt;/p&gt;
&lt;p data-end=&quot;1197&quot; data-start=&quot;1182&quot; data-ke-size=&quot;size16&quot;&gt;이메일도 비슷한 층이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;봉투
  ├─ 어디에서 온 메일인가
  └─ 어디로 배달해야 하는가

편지 내용
  ├─ From:
  ├─ To:
  ├─ Subject:
  └─ Body&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1319&quot; data-start=&quot;1301&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 이 구분이 조금 낯설다.&lt;/p&gt;
&lt;p data-end=&quot;1401&quot; data-start=&quot;1321&quot; data-ke-size=&quot;size16&quot;&gt;메일 화면에서 우리에게 보이는 것은 대부분 편지 내용 쪽이다.&lt;br /&gt;우리는 메일 앱에서 From, To, Subject, 본문을 본다.&lt;/p&gt;
&lt;p data-end=&quot;1447&quot; data-start=&quot;1403&quot; data-ke-size=&quot;size16&quot;&gt;하지만 SMTP 서버가 메일을 주고받을 때 먼저 다루는 것은 그 화면이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1460&quot; data-start=&quot;1449&quot; data-ke-size=&quot;size16&quot;&gt;먼저 봉투를 만든다.&lt;/p&gt;
&lt;p data-end=&quot;1637&quot; data-start=&quot;1462&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5321에서도 SMTP가 전송하는 메일 객체는 &lt;b&gt;봉투(envelope)&lt;/b&gt;와 &lt;b&gt;내용(content)&lt;/b&gt;으로 나뉜다고 설명한다. 봉투는 SMTP 명령을 통해 전달되고, 내용은 나중에 DATA 단계에서 헤더와 본문 형태로 전달된다.&lt;/p&gt;
&lt;p data-end=&quot;1677&quot; data-start=&quot;1639&quot; data-ke-size=&quot;size16&quot;&gt;이제 MAIL FROM과 RCPT TO가 조금 다르게 보인다.&lt;/p&gt;
&lt;p data-end=&quot;1718&quot; data-start=&quot;1679&quot; data-ke-size=&quot;size16&quot;&gt;이 둘은 메일 본문 안에 들어가는 From:과 To:가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1737&quot; data-start=&quot;1720&quot; data-ke-size=&quot;size16&quot;&gt;이 둘은 봉투에 적히는 정보다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1767&quot; data-start=&quot;1739&quot; data-section-id=&quot;1ntwfgq&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MAIL FROM은 편지의 From:이 아니다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1788&quot; data-start=&quot;1769&quot; data-ke-size=&quot;size16&quot;&gt;먼저 MAIL FROM을 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: MAIL FROM:&amp;lt;sender@example.com&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1871&quot; data-start=&quot;1847&quot; data-ke-size=&quot;size16&quot;&gt;이 명령은 새로운 메일 트랜잭션을 시작한다.&lt;/p&gt;
&lt;p data-end=&quot;1998&quot; data-start=&quot;1873&quot; data-ke-size=&quot;size16&quot;&gt;여기서 sender@example.com은 메일 내용에 표시될 작성자라기보다, SMTP 봉투의 발신자 주소에 가깝다. 조금 더 정확히 말하면, 배달 실패 같은 문제가 생겼을 때 오류 보고가 돌아갈 주소로 사용될 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2060&quot; data-start=&quot;2000&quot; data-ke-size=&quot;size16&quot;&gt;그래서 MAIL FROM을 단순히 &amp;ldquo;메일 화면에 보이는 보낸 사람&amp;rdquo;이라고 생각하면 나중에 헷갈리기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;2109&quot; data-start=&quot;2062&quot; data-ke-size=&quot;size16&quot;&gt;메일 화면에 보이는 보낸 사람은 보통 DATA 이후의 메시지 헤더 안에 들어간다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;From: Alice &amp;lt;alice@example.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2189&quot; data-start=&quot;2156&quot; data-ke-size=&quot;size16&quot;&gt;반면 SMTP 대화의 MAIL FROM은 이렇게 생겼다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;MAIL FROM:&amp;lt;bounce@example.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2247&quot; data-start=&quot;2235&quot; data-ke-size=&quot;size16&quot;&gt;둘은 같을 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;2271&quot; data-start=&quot;2249&quot; data-ke-size=&quot;size16&quot;&gt;하지만 반드시 같아야 하는 것은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;2345&quot; data-start=&quot;2273&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 어떤 서비스가 회원가입 인증 메일을 보낸다고 생각해보자.&lt;br /&gt;사용자에게 보이는 발신자는 이렇게 표시하고 싶을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;From: My Service &amp;lt;no-reply@myservice.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2435&quot; data-start=&quot;2402&quot; data-ke-size=&quot;size16&quot;&gt;하지만 배달 실패 알림은 별도의 주소로 받고 싶을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;MAIL FROM:&amp;lt;bounce@myservice.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2607&quot; data-start=&quot;2483&quot; data-ke-size=&quot;size16&quot;&gt;사용자 입장에서는 no-reply@myservice.com에서 메일이 온 것처럼 보인다.&lt;br /&gt;하지만 메일 시스템 입장에서는 배달 실패 같은 시스템 메시지를 bounce@myservice.com으로 돌려보낼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;2675&quot; data-start=&quot;2609&quot; data-ke-size=&quot;size16&quot;&gt;아직 이 연재에서 그런 기능까지 구현하지는 않는다.&lt;br /&gt;지금은 그저 둘이 같은 층에 있지 않다는 사실만 기억하면 된다.&lt;/p&gt;
&lt;p data-end=&quot;2697&quot; data-start=&quot;2677&quot; data-ke-size=&quot;size16&quot;&gt;MAIL FROM은 봉투 쪽이다.&lt;/p&gt;
&lt;p data-end=&quot;2718&quot; data-start=&quot;2699&quot; data-ke-size=&quot;size16&quot;&gt;From:은 편지 내용 쪽이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2744&quot; data-start=&quot;2720&quot; data-section-id=&quot;ug7yie&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;RCPT TO는 편지의 To:가 아니다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2765&quot; data-start=&quot;2746&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 RCPT TO를 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: RCPT TO:&amp;lt;user@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2850&quot; data-start=&quot;2820&quot; data-ke-size=&quot;size16&quot;&gt;RCPT는 recipient, 즉 수신자를 뜻한다.&lt;/p&gt;
&lt;p data-end=&quot;2950&quot; data-start=&quot;2852&quot; data-ke-size=&quot;size16&quot;&gt;이 명령은 이 메일을 실제로 어디로 배달할 것인지 서버에게 알려준다.&lt;br /&gt;그리고 하나의 메일에는 수신자가 여러 명일 수 있기 때문에 RCPT TO는 여러 번 나올 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: MAIL FROM:&amp;lt;sender@example.com&amp;gt;
S: 250 OK

C: RCPT TO:&amp;lt;alice@example.net&amp;gt;
S: 250 OK

C: RCPT TO:&amp;lt;bob@example.net&amp;gt;
S: 250 OK

C: RCPT TO:&amp;lt;carol@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3154&quot; data-start=&quot;3133&quot; data-ke-size=&quot;size16&quot;&gt;이 대화에서 봉투 수신자는 세 명이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;alice@example.net
bob@example.net
carol@example.net&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3265&quot; data-start=&quot;3221&quot; data-ke-size=&quot;size16&quot;&gt;그런데 나중에 DATA 안에 들어가는 To: 헤더는 이와 다를 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;To: team@example.net&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3376&quot; data-start=&quot;3301&quot; data-ke-size=&quot;size16&quot;&gt;또는 어떤 수신자는 To: 헤더에 보이지 않을 수도 있다.&lt;br /&gt;우리가 흔히 말하는 숨은참조, 즉 Bcc 같은 경우를 생각하면 된다.&lt;/p&gt;
&lt;p data-end=&quot;3455&quot; data-start=&quot;3378&quot; data-ke-size=&quot;size16&quot;&gt;메일을 받는 사람은 메일 화면에서 To: 헤더를 본다.&lt;br /&gt;하지만 SMTP 서버가 실제로 배달 대상으로 삼는 것은 RCPT TO다.&lt;/p&gt;
&lt;p data-end=&quot;3488&quot; data-start=&quot;3457&quot; data-ke-size=&quot;size16&quot;&gt;이 차이를 이해하면 이메일이 조금 더 입체적으로 보인다.&lt;/p&gt;
&lt;p data-end=&quot;3538&quot; data-start=&quot;3490&quot; data-ke-size=&quot;size16&quot;&gt;메일 앱에서 보이는 수신자와, SMTP 서버가 배달하려는 수신자는 같은 개념이 아니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;RCPT TO  &amp;rarr; 실제 배달 목적지
To:      &amp;rarr; 메일 내용에 표시되는 수신자 정보&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3776&quot; data-start=&quot;3604&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 트랜잭션은 보통 MAIL 명령으로 발신자 정보를 지정하고, 하나 이상의 RCPT 명령으로 수신자를 지정한 뒤, DATA 명령으로 메일 내용을 전송하는 흐름을 가진다. RFC 5321도 이 순서로 메일 트랜잭션을 설명한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3796&quot; data-start=&quot;3778&quot; data-section-id=&quot;14m0k3p&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이름이 비슷해서 더 헷갈린다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;3814&quot; data-start=&quot;3798&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 이렇게 볼 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;3986&quot; data-start=&quot;3816&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SMTP 봉투&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;메일 내용&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3881&quot; data-start=&quot;3846&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3866&quot; data-start=&quot;3846&quot;&gt;MAIL FROM:&amp;lt;...&amp;gt;&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3881&quot; data-start=&quot;3866&quot;&gt;From: ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3913&quot; data-start=&quot;3882&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3900&quot; data-start=&quot;3882&quot;&gt;RCPT TO:&amp;lt;...&amp;gt;&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3913&quot; data-start=&quot;3900&quot;&gt;To: ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3952&quot; data-start=&quot;3914&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3930&quot; data-start=&quot;3914&quot;&gt;배달 시스템이 보는 정보&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3952&quot; data-start=&quot;3930&quot;&gt;사용자가 메일 화면에서 보는 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;3986&quot; data-start=&quot;3953&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3969&quot; data-start=&quot;3953&quot;&gt;DATA 이전에 전달&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;3986&quot; data-start=&quot;3969&quot;&gt;DATA 이후에 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4013&quot; data-start=&quot;3988&quot; data-ke-size=&quot;size16&quot;&gt;이름이 비슷해서 처음에는 같은 것처럼 보인다.&lt;/p&gt;
&lt;p data-end=&quot;4056&quot; data-start=&quot;4015&quot; data-ke-size=&quot;size16&quot;&gt;MAIL FROM과 From:.&lt;br /&gt;RCPT TO와 To:.&lt;/p&gt;
&lt;p data-end=&quot;4093&quot; data-start=&quot;4058&quot; data-ke-size=&quot;size16&quot;&gt;하지만 SMTP 서버 입장에서 이 둘은 서로 다른 위치에 있다.&lt;/p&gt;
&lt;p data-end=&quot;4119&quot; data-start=&quot;4095&quot; data-ke-size=&quot;size16&quot;&gt;먼저 봉투가 오고,&lt;br /&gt;그다음 편지가 온다.&lt;/p&gt;
&lt;p data-end=&quot;4171&quot; data-start=&quot;4121&quot; data-ke-size=&quot;size16&quot;&gt;그래서 아직 우리는 메일 본문을 보내지 않았지만, 이미 메일의 중요한 일부를 만들고 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4192&quot; data-start=&quot;4173&quot; data-section-id=&quot;rdookk&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이제 코드에 상태가 필요해졌다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;4212&quot; data-start=&quot;4194&quot; data-ke-size=&quot;size16&quot;&gt;지난 글의 서버는 아주 단순했다.&lt;/p&gt;
&lt;p data-end=&quot;4270&quot; data-start=&quot;4214&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 접속하면 220을 보내고, EHLO나 HELO를 받으면 250을 반환했다.&lt;/p&gt;
&lt;p data-end=&quot;4290&quot; data-start=&quot;4272&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이제는 순서가 중요해졌다.&lt;/p&gt;
&lt;p data-end=&quot;4342&quot; data-start=&quot;4292&quot; data-ke-size=&quot;size16&quot;&gt;RCPT TO는 아무 때나 올 수 없다.&lt;br /&gt;먼저 MAIL FROM이 있어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;4405&quot; data-start=&quot;4344&quot; data-ke-size=&quot;size16&quot;&gt;MAIL FROM도 아무 때나 올 수 없다.&lt;br /&gt;먼저 EHLO나 HELO로 세션이 준비되어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;4423&quot; data-start=&quot;4407&quot; data-ke-size=&quot;size16&quot;&gt;그래서 코드에 상태를 두었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;const (
	StateGreeting = iota
	StateReady
	StateMail
	StateRcpt
	StateData
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4530&quot; data-start=&quot;4513&quot; data-ke-size=&quot;size16&quot;&gt;처음 연결되면 서버는 인사한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;state := StateGreeting&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4607&quot; data-start=&quot;4566&quot; data-ke-size=&quot;size16&quot;&gt;EHLO나 HELO를 정상적으로 받으면 세션을 준비 상태로 바꾼다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;resetSession := func() {
	state = StateReady
	from = &quot;&quot;
	rcpt = []string{}
	message = &quot;&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4740&quot; data-start=&quot;4711&quot; data-ke-size=&quot;size16&quot;&gt;여기서 from과 rcpt가 오늘의 핵심이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;from := &quot;&quot;
rcpt := []string{}
message := &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4866&quot; data-start=&quot;4797&quot; data-ke-size=&quot;size16&quot;&gt;from은 봉투 발신자를 담는다.&lt;br /&gt;rcpt는 봉투 수신자 목록을 담는다.&lt;br /&gt;message는 아직 비어 있다.&lt;/p&gt;
&lt;p data-end=&quot;4895&quot; data-start=&quot;4868&quot; data-ke-size=&quot;size16&quot;&gt;왜냐하면 편지 내용은 아직 받지 않았기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;4909&quot; data-start=&quot;4897&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 봉투만 만든다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4928&quot; data-start=&quot;4911&quot; data-section-id=&quot;jd4tcq&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MAIL FROM 구현하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;4958&quot; data-start=&quot;4930&quot; data-ke-size=&quot;size16&quot;&gt;이제 MAIL FROM을 처리하는 부분을 보자.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;case &quot;MAIL&quot;:
	if state != StateReady {
		if err := writeLine(&quot;503 Bad sequence of commands&quot;); err != nil {
			return
		}
		continue
	}

	resetSession()

	parts := strings.SplitN(arg, &quot;:&quot;, 2)
	if len(parts) != 2 {
		if err := writeLine(&quot;501 Syntax: MAIL FROM:&amp;lt;address&amp;gt;&quot;); err != nil {
			return
		}
		continue
	}

	mailCmd := parts[0]
	from = parts[1]

	if strings.ToUpper(mailCmd) != &quot;FROM&quot; || from == &quot;&quot; {
		if err := writeLine(&quot;501 Syntax: MAIL FROM:&amp;lt;address&amp;gt;&quot;); err != nil {
			return
		}
		continue
	}

	state = StateMail

	if err := writeLine(&quot;250 OK&quot;); err != nil {
		return
	}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5571&quot; data-start=&quot;5555&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 보는 것은 상태다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;if state != StateReady {
	writeLine(&quot;503 Bad sequence of commands&quot;)
	continue
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5716&quot; data-start=&quot;5664&quot; data-ke-size=&quot;size16&quot;&gt;아직 EHLO나 HELO를 하지 않았는데 MAIL FROM이 오면 받아주지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;5767&quot; data-start=&quot;5718&quot; data-ke-size=&quot;size16&quot;&gt;서버 입장에서는 아직 제대로 인사도 하지 않은 클라이언트가 갑자기 봉투를 내미는 셈이다.&lt;/p&gt;
&lt;p data-end=&quot;5791&quot; data-start=&quot;5769&quot; data-ke-size=&quot;size16&quot;&gt;그다음에는 이전 트랜잭션 정보를 지운다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;resetSession()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5899&quot; data-start=&quot;5819&quot; data-ke-size=&quot;size16&quot;&gt;MAIL FROM은 새로운 메일 트랜잭션의 시작이다.&lt;br /&gt;새 편지를 보내려면 이전 봉투에 적힌 발신자와 수신자를 계속 들고 있으면 안 된다.&lt;/p&gt;
&lt;p data-end=&quot;5917&quot; data-start=&quot;5901&quot; data-ke-size=&quot;size16&quot;&gt;그다음 명령 인자를 파싱한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;parts := strings.SplitN(arg, &quot;:&quot;, 2)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;5989&quot; data-start=&quot;5967&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 보낸 전체 명령이 이렇다면,&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;MAIL FROM:&amp;lt;sender@example.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6074&quot; data-start=&quot;6035&quot; data-ke-size=&quot;size16&quot;&gt;앞에서 MAIL은 이미 명령어로 분리되었고, 남은 인자는 이것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;FROM:&amp;lt;sender@example.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6141&quot; data-start=&quot;6115&quot; data-ke-size=&quot;size16&quot;&gt;이 문자열을 콜론 기준으로 나누면 이렇게 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;FROM
&amp;lt;sender@example.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6225&quot; data-start=&quot;6182&quot; data-ke-size=&quot;size16&quot;&gt;앞부분이 FROM인지 확인하고, 뒤쪽 주소를 from 변수에 저장한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;from = parts[1]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6268&quot; data-start=&quot;6254&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 상태를 바꾼다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;state = StateMail&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6318&quot; data-start=&quot;6299&quot; data-ke-size=&quot;size16&quot;&gt;이제 서버는 이렇게 말할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6381&quot; data-start=&quot;6343&quot; data-ke-size=&quot;size16&quot;&gt;아직 메일을 받은 것은 아니다.&lt;br /&gt;아직 본문도 없고, 제목도 없다.&lt;/p&gt;
&lt;p data-end=&quot;6401&quot; data-start=&quot;6383&quot; data-ke-size=&quot;size16&quot;&gt;다만 서버는 이렇게 말한 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;좋다. 이 봉투 발신자로 메일 트랜잭션을 시작하자.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;6460&quot; data-start=&quot;6445&quot; data-section-id=&quot;7n6sy3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;RCPT TO 구현하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;6474&quot; data-start=&quot;6462&quot; data-ke-size=&quot;size16&quot;&gt;이제 수신자를 받는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;case &quot;RCPT&quot;:
	if state != StateMail &amp;amp;&amp;amp; state != StateRcpt {
		if err := writeLine(&quot;503 Bad sequence of commands&quot;); err != nil {
			return
		}
		continue
	}

	parts := strings.SplitN(arg, &quot;:&quot;, 2)
	if len(parts) != 2 {
		if err := writeLine(&quot;501 Syntax: RCPT TO:&amp;lt;address&amp;gt;&quot;); err != nil {
			return
		}
		continue
	}

	rcptCmd := parts[0]
	rcptArg := parts[1]

	if strings.ToUpper(rcptCmd) != &quot;TO&quot; || rcptArg == &quot;&quot; {
		if err := writeLine(&quot;501 Syntax: RCPT TO:&amp;lt;address&amp;gt;&quot;); err != nil {
			return
		}
		continue
	}

	if len(rcpt) &amp;gt;= 100 {
		if err := writeLine(&quot;452 Error: exceeded max recipients&quot;); err != nil {
			return
		}
		continue
	}

	rcpt = append(rcpt, rcptArg)
	state = StateRcpt

	if err := writeLine(&quot;250 OK&quot;); err != nil {
		return
	}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7249&quot; data-start=&quot;7232&quot; data-ke-size=&quot;size16&quot;&gt;여기서도 먼저 상태를 확인한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;if state != StateMail &amp;amp;&amp;amp; state != StateRcpt {
	writeLine(&quot;503 Bad sequence of commands&quot;)
	continue
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7395&quot; data-start=&quot;7363&quot; data-ke-size=&quot;size16&quot;&gt;RCPT TO는 MAIL FROM 뒤에 와야 한다.&lt;/p&gt;
&lt;p data-end=&quot;7502&quot; data-start=&quot;7397&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이미 한 명의 수신자를 받은 뒤에도 또 다른 수신자를 받을 수 있어야 한다.&lt;br /&gt;그래서 StateMail뿐만 아니라 StateRcpt 상태에서도 RCPT TO를 허용한다.&lt;/p&gt;
&lt;p data-end=&quot;7515&quot; data-start=&quot;7504&quot; data-ke-size=&quot;size16&quot;&gt;이 차이가 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;7541&quot; data-start=&quot;7517&quot; data-ke-size=&quot;size16&quot;&gt;MAIL FROM은 한 번으로 충분하다.&lt;/p&gt;
&lt;p data-end=&quot;7570&quot; data-start=&quot;7543&quot; data-ke-size=&quot;size16&quot;&gt;하지만 RCPT TO는 여러 번 올 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: RCPT TO:&amp;lt;alice@example.net&amp;gt;
S: 250 OK

C: RCPT TO:&amp;lt;bob@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7688&quot; data-start=&quot;7666&quot; data-ke-size=&quot;size16&quot;&gt;코드에서는 수신자를 슬라이스에 추가한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;rcpt = append(rcpt, rcptArg)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7751&quot; data-start=&quot;7730&quot; data-ke-size=&quot;size16&quot;&gt;이제 봉투에는 수신자가 하나씩 쌓인다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;rcpt = []string{
	&quot;&amp;lt;alice@example.net&amp;gt;&quot;,
	&quot;&amp;lt;bob@example.net&amp;gt;&quot;,
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7905&quot; data-start=&quot;7829&quot; data-ke-size=&quot;size16&quot;&gt;지금 서버는 아직 이 주소가 실제로 존재하는지 확인하지 않는다.&lt;br /&gt;메일박스가 있는지도 모른다.&lt;br /&gt;도메인이 유효한지도 확인하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;7929&quot; data-start=&quot;7907&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계의 목표는 주소 검증이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;7986&quot; data-start=&quot;7931&quot; data-ke-size=&quot;size16&quot;&gt;이번 단계의 목표는 SMTP 대화 속에서 &lt;b&gt;봉투 수신자를 받을 수 있는 상태&lt;/b&gt;를 만드는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;8005&quot; data-start=&quot;7988&quot; data-section-id=&quot;1ct0785&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;수신자 수에도 제한을 둔다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;8022&quot; data-start=&quot;8007&quot; data-ke-size=&quot;size16&quot;&gt;코드에는 이런 부분도 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;if len(rcpt) &amp;gt;= 100 {
	if err := writeLine(&quot;452 Error: exceeded max recipients&quot;); err != nil {
		return
	}
	continue
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8197&quot; data-start=&quot;8154&quot; data-ke-size=&quot;size16&quot;&gt;처음 구현하는 입장에서는 &amp;ldquo;왜 벌써 100명 제한이 나오지?&amp;rdquo; 싶을 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;8274&quot; data-start=&quot;8199&quot; data-ke-size=&quot;size16&quot;&gt;하지만 SMTP에서는 하나의 메일에 여러 수신자가 붙을 수 있다.&lt;br /&gt;그래서 서버는 수신자 목록을 어느 정도 버퍼링할 수 있어야 한다.&lt;/p&gt;
&lt;p data-end=&quot;8449&quot; data-start=&quot;8276&quot; data-ke-size=&quot;size16&quot;&gt;구현 지침서에서도 RCPT TO는 여러 번 반복될 수 있고, 최소 100명의 수신자 버퍼를 지원해야 한다고 정리하고 있다. 또한 수신자가 너무 많을 때는 영구 실패가 아니라 452 같은 일시적 실패 응답을 사용하는 흐름으로 잡고 있다.&lt;/p&gt;
&lt;p data-end=&quot;8476&quot; data-start=&quot;8451&quot; data-ke-size=&quot;size16&quot;&gt;물론 지금 단계에서 이 제한이 핵심은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;8504&quot; data-start=&quot;8478&quot; data-ke-size=&quot;size16&quot;&gt;다만 이 코드를 보면서 알 수 있는 것이 있다.&lt;/p&gt;
&lt;p data-end=&quot;8541&quot; data-start=&quot;8506&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 서버는 단순히 한 줄을 받고 끝나는 프로그램이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;8568&quot; data-start=&quot;8543&quot; data-ke-size=&quot;size16&quot;&gt;서버는 현재 트랜잭션의 상태를 기억해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;8632&quot; data-start=&quot;8570&quot; data-ke-size=&quot;size16&quot;&gt;누가 보냈는지,&lt;br /&gt;누구에게 보낼 것인지,&lt;br /&gt;몇 명의 수신자를 받았는지,&lt;br /&gt;다음에 어떤 명령이 와야 하는지.&lt;/p&gt;
&lt;p data-end=&quot;8654&quot; data-start=&quot;8634&quot; data-ke-size=&quot;size16&quot;&gt;이런 것들을 계속 들고 있어야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;8667&quot; data-start=&quot;8656&quot; data-section-id=&quot;1gxpqmp&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;직접 대화해보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;8693&quot; data-start=&quot;8669&quot; data-ke-size=&quot;size16&quot;&gt;이제 서버를 실행하고 telnet로 접속해본다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;go run main.go&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8737&quot; data-start=&quot;8723&quot; data-ke-size=&quot;size16&quot;&gt;다른 터미널에서 접속한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;telnet localhost 2525&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8782&quot; data-start=&quot;8770&quot; data-ke-size=&quot;size16&quot;&gt;서버가 먼저 인사한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;S: 220 localhost Simple Mail Transfer Service Ready&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8861&quot; data-start=&quot;8849&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 인사한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: EHLO client.local
S: 250-localhost greets client.local
S: 250 HELP&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8961&quot; data-start=&quot;8946&quot; data-ke-size=&quot;size16&quot;&gt;이제 봉투 발신자를 보낸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: MAIL FROM:&amp;lt;alice@example.com&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9035&quot; data-start=&quot;9019&quot; data-ke-size=&quot;size16&quot;&gt;그리고 봉투 수신자를 보낸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: RCPT TO:&amp;lt;bob@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9109&quot; data-start=&quot;9089&quot; data-ke-size=&quot;size16&quot;&gt;수신자를 하나 더 추가할 수도 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;C: RCPT TO:&amp;lt;carol@example.net&amp;gt;
S: 250 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9179&quot; data-start=&quot;9165&quot; data-ke-size=&quot;size16&quot;&gt;전체 대화는 이렇게 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오후 12.08.34.png&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqgoTD/dJMb997mpqD/BI2E4exN4lHi6oNcMcMgR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqgoTD/dJMb997mpqD/BI2E4exN4lHi6oNcMcMgR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqgoTD/dJMb997mpqD/BI2E4exN4lHi6oNcMcMgR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqgoTD%2FdJMb997mpqD%2FBI2E4exN4lHi6oNcMcMgR1%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;740&quot; height=&quot;392&quot; data-filename=&quot;스크린샷 2026-05-07 오후 12.08.34.png&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9481&quot; data-start=&quot;9468&quot; data-ke-size=&quot;size16&quot;&gt;아직 메일 본문은 없다.&lt;/p&gt;
&lt;p data-end=&quot;9490&quot; data-start=&quot;9483&quot; data-ke-size=&quot;size16&quot;&gt;제목도 없다.&lt;/p&gt;
&lt;p data-end=&quot;9516&quot; data-start=&quot;9492&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 실제로 읽을 수 있는 내용도 없다.&lt;/p&gt;
&lt;p data-end=&quot;9532&quot; data-start=&quot;9518&quot; data-ke-size=&quot;size16&quot;&gt;하지만 봉투는 만들어졌다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;봉투 발신자: alice@example.com
봉투 수신자: bob@example.net
봉투 수신자: carol@example.net&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;9645&quot; data-start=&quot;9623&quot; data-ke-size=&quot;size16&quot;&gt;이전 글에서 우리는 서버와 인사만 했다.&lt;/p&gt;
&lt;p data-end=&quot;9699&quot; data-start=&quot;9647&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 처음으로 &amp;ldquo;이 메일은 어디에서 왔고, 어디로 가야 하는가&amp;rdquo;를 서버에게 알려줬다.&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-end=&quot;9719&quot; data-start=&quot;9701&quot; data-section-id=&quot;1m6vsgp&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;순서를 어기면 어떻게 될까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;9739&quot; data-start=&quot;9721&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 일부러 순서를 어겨본다.&lt;/p&gt;
&lt;p data-end=&quot;9789&quot; data-start=&quot;9741&quot; data-ke-size=&quot;size16&quot;&gt;EHLO만 보낸 뒤, MAIL FROM 없이 바로 RCPT TO를 보내본다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오후 12.09.55.png&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qTCjA/dJMcadWbJDq/wSu1aAj7CSRRzwE6N0kimK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qTCjA/dJMcadWbJDq/wSu1aAj7CSRRzwE6N0kimK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qTCjA/dJMcadWbJDq/wSu1aAj7CSRRzwE6N0kimK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqTCjA%2FdJMcadWbJDq%2FwSu1aAj7CSRRzwE6N0kimK%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;756&quot; height=&quot;282&quot; data-filename=&quot;스크린샷 2026-05-07 오후 12.09.55.png&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10003&quot; data-start=&quot;9987&quot; data-ke-size=&quot;size16&quot;&gt;서버는 503을 반환한다.&lt;/p&gt;
&lt;p data-end=&quot;10032&quot; data-start=&quot;10005&quot; data-ke-size=&quot;size16&quot;&gt;이 응답은 &amp;ldquo;명령의 순서가 잘못되었다&amp;rdquo;는 뜻이다.&lt;/p&gt;
&lt;p data-end=&quot;10076&quot; data-start=&quot;10034&quot; data-ke-size=&quot;size16&quot;&gt;이제 SMTP가 단순히 문자열 몇 줄을 주고받는 것이 아니라는 느낌이 든다.&lt;/p&gt;
&lt;p data-end=&quot;10100&quot; data-start=&quot;10078&quot; data-ke-size=&quot;size16&quot;&gt;같은 명령이라도 언제 오느냐가 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;10125&quot; data-start=&quot;10102&quot; data-ke-size=&quot;size16&quot;&gt;RCPT TO 자체는 올바른 명령이다.&lt;/p&gt;
&lt;p data-end=&quot;10164&quot; data-start=&quot;10127&quot; data-ke-size=&quot;size16&quot;&gt;하지만 아직 MAIL FROM이 없으니 지금 올 명령은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;10207&quot; data-start=&quot;10166&quot; data-ke-size=&quot;size16&quot;&gt;프로토콜을 구현한다는 것은 명령어 이름을 알아보는 것만으로 끝나지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;10246&quot; data-start=&quot;10209&quot; data-ke-size=&quot;size16&quot;&gt;그 명령이 &lt;b&gt;지금 이 상태에서 가능한 말인지&lt;/b&gt;도 판단해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;10271&quot; data-start=&quot;10248&quot; data-section-id=&quot;1ytggqj&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;아직 엄격한 주소 검사는 하지 않는다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;10317&quot; data-start=&quot;10273&quot; data-ke-size=&quot;size16&quot;&gt;현재 코드는 MAIL FROM과 RCPT TO를 아주 단순하게 파싱한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;parts := strings.SplitN(arg, &quot;:&quot;, 2)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10424&quot; data-start=&quot;10367&quot; data-ke-size=&quot;size16&quot;&gt;즉, 콜론을 기준으로 앞부분이 FROM인지, TO인지 확인하고 뒤쪽 문자열을 주소처럼 저장한다.&lt;/p&gt;
&lt;p data-end=&quot;10448&quot; data-start=&quot;10426&quot; data-ke-size=&quot;size16&quot;&gt;하지만 아직 엄격한 검사는 하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;10560&quot; data-start=&quot;10450&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 주소가 정말 &amp;lt;...&amp;gt; 형태인지,&lt;br /&gt;콜론 뒤에 공백이 들어갔는지,&lt;br /&gt;주소 안에 허용되지 않는 문자가 있는지,&lt;br /&gt;확장 매개변수가 붙었는지,&lt;br /&gt;도메인이 올바른지까지 보지는 않는다.&lt;/p&gt;
&lt;p data-end=&quot;10585&quot; data-start=&quot;10562&quot; data-ke-size=&quot;size16&quot;&gt;지금은 일부러 거기까지 가지 않으려 한다.&lt;/p&gt;
&lt;p data-end=&quot;10616&quot; data-start=&quot;10587&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서 중요한 것은 완전한 주소 파서가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;10663&quot; data-start=&quot;10618&quot; data-ke-size=&quot;size16&quot;&gt;중요한 것은 SMTP가 메일 본문을 받기 전에 봉투 정보를 먼저 받는다는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;10707&quot; data-start=&quot;10665&quot; data-ke-size=&quot;size16&quot;&gt;그리고 그 봉투 정보는 코드 안에서 별도의 상태로 관리되어야 한다는 점이다.&lt;/p&gt;
&lt;p data-end=&quot;10736&quot; data-start=&quot;10709&quot; data-ke-size=&quot;size16&quot;&gt;정확한 주소 문법은 나중에 천천히 다듬으면 된다.&lt;/p&gt;
&lt;p data-end=&quot;10752&quot; data-start=&quot;10738&quot; data-ke-size=&quot;size16&quot;&gt;지금은 먼저 흐름을 본다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;EHLO
  &amp;darr;
MAIL FROM
  &amp;darr;
RCPT TO
  &amp;darr;
DATA&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;10825&quot; data-start=&quot;10807&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 이 중에서 여기까지 왔다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;EHLO
  &amp;darr;
MAIL FROM
  &amp;darr;
RCPT TO&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;10892&quot; data-start=&quot;10871&quot; data-section-id=&quot;15mppdc&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;From과 To를 다시 보게 된다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;10939&quot; data-start=&quot;10894&quot; data-ke-size=&quot;size16&quot;&gt;이번 구현을 하고 나니 메일 화면의 From과 To가 조금 다르게 보인다.&lt;/p&gt;
&lt;p data-end=&quot;10955&quot; data-start=&quot;10941&quot; data-ke-size=&quot;size16&quot;&gt;이전에는 이렇게 생각했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;From은 보내는 사람
To는 받는 사람&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11003&quot; data-start=&quot;10993&quot; data-ke-size=&quot;size16&quot;&gt;틀린 말은 아니다.&lt;/p&gt;
&lt;p data-end=&quot;11039&quot; data-start=&quot;11005&quot; data-ke-size=&quot;size16&quot;&gt;하지만 SMTP 안쪽을 기준으로 보면 조금 더 나누어야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;MAIL FROM은 봉투의 발신자
From:은 편지 내용에 표시되는 작성자

RCPT TO는 봉투의 수신자
To:는 편지 내용에 표시되는 수신자&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11154&quot; data-start=&quot;11135&quot; data-ke-size=&quot;size16&quot;&gt;이 차이는 당장 눈에 띄지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;11190&quot; data-start=&quot;11156&quot; data-ke-size=&quot;size16&quot;&gt;메일 앱을 사용할 때는 보통 봉투를 직접 보지 않기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;11214&quot; data-start=&quot;11192&quot; data-ke-size=&quot;size16&quot;&gt;우리가 보는 것은 대부분 편지 내용이다.&lt;/p&gt;
&lt;p data-end=&quot;11241&quot; data-start=&quot;11216&quot; data-ke-size=&quot;size16&quot;&gt;하지만 서버들은 그 뒤에서 봉투를 주고받는다.&lt;/p&gt;
&lt;p data-end=&quot;11315&quot; data-start=&quot;11243&quot; data-ke-size=&quot;size16&quot;&gt;메일이 어디로 배달되어야 하는지,&lt;br /&gt;배달 실패는 어디로 돌아가야 하는지,&lt;br /&gt;한 번의 본문 전송으로 몇 명에게 전달해야 하는지.&lt;/p&gt;
&lt;p data-end=&quot;11343&quot; data-start=&quot;11317&quot; data-ke-size=&quot;size16&quot;&gt;이런 정보들은 편지 내용 밖에서 먼저 정해진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;11363&quot; data-start=&quot;11345&quot; data-section-id=&quot;w6ps8s&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 글의 끝에서 남는 질문&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;11512&quot; data-start=&quot;11494&quot; data-ke-size=&quot;size16&quot;&gt;오늘 확인한 것은 메일의 봉투다.&lt;/p&gt;
&lt;p data-end=&quot;11547&quot; data-start=&quot;11514&quot; data-ke-size=&quot;size16&quot;&gt;서버와 인사를 마친 클라이언트는 먼저 봉투 발신자를 말한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;MAIL FROM:&amp;lt;sender@example.com&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11609&quot; data-start=&quot;11593&quot; data-ke-size=&quot;size16&quot;&gt;그다음 봉투 수신자를 말한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;RCPT TO:&amp;lt;user@example.net&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11708&quot; data-start=&quot;11651&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이 정보는 나중에 메일 화면에서 보이는 From:이나 To: 헤더와 반드시 같지는 않다.&lt;/p&gt;
&lt;p data-end=&quot;11724&quot; data-start=&quot;11710&quot; data-ke-size=&quot;size16&quot;&gt;이제 다음 질문이 남는다.&lt;/p&gt;
&lt;p data-end=&quot;11752&quot; data-start=&quot;11726&quot; data-ke-size=&quot;size16&quot;&gt;봉투를 만들었으니, 편지지는 언제 건네야 할까?&lt;/p&gt;
&lt;p data-end=&quot;11776&quot; data-start=&quot;11754&quot; data-ke-size=&quot;size16&quot;&gt;메일의 제목과 본문은 어디에서 시작될까?&lt;/p&gt;
&lt;p data-end=&quot;11805&quot; data-start=&quot;11778&quot; data-ke-size=&quot;size16&quot;&gt;그리고 서버는 본문이 끝났다는 것을 어떻게 알까?&lt;/p&gt;
&lt;p data-end=&quot;11832&quot; data-start=&quot;11807&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 드디어 DATA로 들어간다.&lt;/p&gt;
&lt;p data-end=&quot;11856&quot; data-start=&quot;11834&quot; data-ke-size=&quot;size16&quot;&gt;그때부터는 봉투가 아니라 편지 내용이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;DATA
354 Start mail input; end with &amp;lt;CRLF&amp;gt;.&amp;lt;CRLF&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;11954&quot; data-start=&quot;11921&quot; data-ke-size=&quot;size16&quot;&gt;메일은 이제 처음으로 읽을 수 있는 모양을 갖추기 시작한다.&lt;/p&gt;</description>
      <category>Deep Dive/기타</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/237</guid>
      <comments>https://gowoong.tistory.com/237#entry237comment</comments>
      <pubDate>Thu, 7 May 2026 12:10:25 +0900</pubDate>
    </item>
    <item>
      <title>[SMTP] 3. SMTP 서버와 첫 대화: 서버가 먼저 인사한다</title>
      <link>https://gowoong.tistory.com/236</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서는 이메일이 어디에서 어디로 이동하는지 대략적인 지도를 그려봤다. 그리고 gmail과의 통신도 짧게나마 살펴보았다.&lt;/p&gt;
&lt;p data-end=&quot;204&quot; data-start=&quot;186&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 단순히 이렇게 생각했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;보내는 사람 &amp;rarr; 받는 사람&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;358&quot; data-start=&quot;234&quot; data-ke-size=&quot;size16&quot;&gt;하지만 조금만 안쪽으로 들어가보니 그 사이에는 여러 역할이 있었다. 메일을 작성하는 프로그램이 있고, 메일을 전달하는 서버가 있고, 중간에서 다시 전달하는 서버가 있고, 최종적으로 사용자의 메일함에 저장하는 서버가 있었다.&lt;/p&gt;
&lt;p data-end=&quot;386&quot; data-start=&quot;360&quot; data-ke-size=&quot;size16&quot;&gt;그리고 그 흐름 어딘가에서 SMTP가 등장했다.&lt;/p&gt;
&lt;p data-end=&quot;470&quot; data-start=&quot;388&quot; data-ke-size=&quot;size16&quot;&gt;SMTP는 메일을 읽는 기술도 아니고, 메일 화면을 보여주는 기술도 아니었다. SMTP는 메일을 다른 곳으로 &lt;b&gt;전송하기 위한 약속&lt;/b&gt;에 가까웠다.&lt;/p&gt;
&lt;p data-end=&quot;497&quot; data-start=&quot;472&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 이제 다음 질문으로 넘어갈 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;529&quot; data-start=&quot;499&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 서버와 실제로 대화하려면 무엇부터 해야 할까?&lt;/p&gt;
&lt;p data-end=&quot;559&quot; data-start=&quot;531&quot; data-ke-size=&quot;size16&quot;&gt;메일을 보내려면 먼저 발신자 주소를 알려줘야 할까?&lt;/p&gt;
&lt;p data-end=&quot;577&quot; data-start=&quot;561&quot; data-ke-size=&quot;size16&quot;&gt;수신자 주소를 알려줘야 할까?&lt;/p&gt;
&lt;p data-end=&quot;593&quot; data-start=&quot;579&quot; data-ke-size=&quot;size16&quot;&gt;본문을 먼저 보내야 할까?&lt;/p&gt;
&lt;p data-end=&quot;626&quot; data-start=&quot;595&quot; data-ke-size=&quot;size16&quot;&gt;아니면 클라이언트가 먼저 &amp;ldquo;안녕하세요&amp;rdquo;라고 말해야 할까?&lt;/p&gt;
&lt;p data-end=&quot;654&quot; data-start=&quot;628&quot; data-ke-size=&quot;size16&quot;&gt;의외로 첫 번째 말은 클라이언트가 하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;672&quot; data-start=&quot;656&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버가 먼저 인사한다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-section-id=&quot;s5qcrp&quot; data-end=&quot;691&quot; data-start=&quot;674&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문을 열면, 서버가 말한다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;734&quot; data-start=&quot;693&quot; data-ke-size=&quot;size16&quot;&gt;웹 개발에 익숙하면 보통 클라이언트가 먼저 요청을 보낸다고 생각하기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;759&quot; data-start=&quot;736&quot; data-ke-size=&quot;size16&quot;&gt;브라우저가 서버에 HTTP 요청을 보낸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;GET / HTTP/1.1
Host: example.com&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;820&quot; data-start=&quot;807&quot; data-ke-size=&quot;size16&quot;&gt;그러면 서버가 응답한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;HTTP/1.1 200 OK&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;907&quot; data-start=&quot;851&quot; data-ke-size=&quot;size16&quot;&gt;그래서 자연스럽게 네트워크 통신은 클라이언트가 먼저 말하고, 서버가 대답하는 구조라고 생각하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;932&quot; data-start=&quot;909&quot; data-ke-size=&quot;size16&quot;&gt;하지만 SMTP의 첫 장면은 조금 다르다.&lt;/p&gt;
&lt;p data-end=&quot;1124&quot; data-start=&quot;934&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 SMTP 서버에 TCP 연결을 열면, 서버가 먼저 자신이 준비되었다고 말한다. RFC 5321도 SMTP 세션이 클라이언트가 서버에 연결을 열고, 서버가 시작 메시지로 응답하면서 시작된다고 설명한다. 그다음 클라이언트는 보통 EHLO 명령으로 자신을 식별한다.&lt;/p&gt;
&lt;p data-end=&quot;1142&quot; data-start=&quot;1126&quot; data-ke-size=&quot;size16&quot;&gt;그 첫 인사는 이런 모양이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;S: 220 localhost Simple Mail Transfer Service Ready&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1778115066585&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;220 smtp.gmail.com ESMTP d2e1a72fcca58-83965645d74sm6572331b3a.1 - gsmtp&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1235&quot; data-start=&quot;1209&quot; data-ke-size=&quot;size16&quot;&gt;여기서 S:는 서버가 보낸 줄이라는 뜻이다.&lt;/p&gt;
&lt;p data-end=&quot;1305&quot; data-start=&quot;1237&quot; data-ke-size=&quot;size16&quot;&gt;아직 메일 주소도 나오지 않았다. 제목도 없고, 본문도 없고, 첨부파일도 없다.&lt;br /&gt;그저 서버가 먼저 이렇게 말한 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;나는 준비되어 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1364&quot; data-start=&quot;1332&quot; data-ke-size=&quot;size16&quot;&gt;메일 한 통의 긴 여행은 이 짧은 문장 하나에서 시작된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-section-id=&quot;q0llag&quot; data-end=&quot;1387&quot; data-start=&quot;1366&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SMTP는 TCP 위에서 대화한다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1433&quot; data-start=&quot;1389&quot; data-ke-size=&quot;size16&quot;&gt;SMTP를 구현한다고 하면 뭔가 특별한 메일 서버 라이브러리부터 떠올리기 쉽다.&lt;/p&gt;
&lt;p data-end=&quot;1461&quot; data-start=&quot;1435&quot; data-ke-size=&quot;size16&quot;&gt;하지만 가장 처음에 필요한 것은 훨씬 단순하다.&lt;/p&gt;
&lt;p data-end=&quot;1480&quot; data-start=&quot;1463&quot; data-ke-size=&quot;size16&quot;&gt;TCP 서버를 하나 열면 된다.&lt;/p&gt;
&lt;p data-end=&quot;1681&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size16&quot;&gt;SMTP는 기본적으로 클라이언트와 서버가 TCP 연결 위에서 한 줄씩 명령과 응답을 주고받는 구조다. RFC 5321은 SMTP가 특정 전송 하위 시스템에 종속되지 않고, 안정적으로 정렬된 데이터 스트림 채널을 필요로 한다고 설명한다. 실제 인터넷 환경에서는 TCP를 통한 전송을 다룬다.&lt;/p&gt;
&lt;p data-end=&quot;1715&quot; data-start=&quot;1683&quot; data-ke-size=&quot;size16&quot;&gt;그러니 우리가 처음 만들 것은 거창한 메일 서버가 아니다.&lt;/p&gt;
&lt;p data-end=&quot;1731&quot; data-start=&quot;1717&quot; data-ke-size=&quot;size16&quot;&gt;우선은 이것만 하면 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. TCP 포트를 연다.
2. 클라이언트가 접속한다.
3. 서버가 220 응답을 보낸다.
4. 클라이언트가 EHLO 또는 HELO를 보낸다.
5. 서버가 250 응답을 보낸다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1863&quot; data-start=&quot;1846&quot; data-ke-size=&quot;size16&quot;&gt;아직 메일을 저장하지도 않는다.&lt;/p&gt;
&lt;p data-end=&quot;1896&quot; data-start=&quot;1865&quot; data-ke-size=&quot;size16&quot;&gt;아직 Gmail이나 Naver로 메일을 보내지도 않는다.&lt;/p&gt;
&lt;p data-end=&quot;1912&quot; data-start=&quot;1898&quot; data-ke-size=&quot;size16&quot;&gt;오늘의 목표는 훨씬 작다.&lt;/p&gt;
&lt;p data-end=&quot;1941&quot; data-start=&quot;1914&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SMTP 서버와 첫 대화를 성공시키는 것.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-section-id=&quot;1uvoapc&quot; data-end=&quot;2136&quot; data-start=&quot;2120&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;220: 서버의 첫인사&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2180&quot; data-start=&quot;2138&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 서버가 연결 직후 보내는 220은 &amp;ldquo;서비스 준비됨&amp;rdquo;을 의미한다.&lt;/p&gt;
&lt;p data-end=&quot;2195&quot; data-start=&quot;2182&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 이런 식이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;220 localhost Simple Mail Transfer Service Ready&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2289&quot; data-start=&quot;2259&quot; data-ke-size=&quot;size16&quot;&gt;숫자 220은 사람이 읽기 위한 문장보다 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;2395&quot; data-start=&quot;2291&quot; data-ke-size=&quot;size16&quot;&gt;뒤의 문장은 서버마다 조금씩 달라질 수 있다. localhost라고 할 수도 있고, mail.example.com이라고 할 수도 있고, 서버 이름이나 버전 정보를 넣을 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;2423&quot; data-start=&quot;2397&quot; data-ke-size=&quot;size16&quot;&gt;하지만 클라이언트 입장에서 핵심은 앞의 숫자다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;220&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2478&quot; data-start=&quot;2442&quot; data-ke-size=&quot;size16&quot;&gt;이 숫자를 보고 클라이언트는 &amp;ldquo;서버가 준비되었구나&amp;rdquo;라고 판단한다.&lt;/p&gt;
&lt;p data-end=&quot;2522&quot; data-start=&quot;2480&quot; data-ke-size=&quot;size16&quot;&gt;여기서 재미있는 점은, SMTP 대화가 숫자와 문장이 섞인 형태라는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;2539&quot; data-start=&quot;2524&quot; data-ke-size=&quot;size16&quot;&gt;사람은 뒤의 문장을 읽는다.&lt;/p&gt;
&lt;p data-end=&quot;2557&quot; data-start=&quot;2541&quot; data-ke-size=&quot;size16&quot;&gt;프로그램은 앞의 숫자를 본다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;220 localhost Simple Mail Transfer Service Ready
│   └─ 사람이 읽는 설명
└───── 프로그램이 판단하는 코드&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2677&quot; data-start=&quot;2659&quot; data-ke-size=&quot;size16&quot;&gt;이 구조는 앞으로 계속 반복된다.&lt;/p&gt;
&lt;p data-end=&quot;2690&quot; data-start=&quot;2679&quot; data-ke-size=&quot;size16&quot;&gt;성공하면 250.&lt;/p&gt;
&lt;p data-end=&quot;2710&quot; data-start=&quot;2692&quot; data-ke-size=&quot;size16&quot;&gt;본문을 보내라고 하면 354.&lt;/p&gt;
&lt;p data-end=&quot;2728&quot; data-start=&quot;2712&quot; data-ke-size=&quot;size16&quot;&gt;종료하겠다고 하면 221.&lt;/p&gt;
&lt;p data-end=&quot;2748&quot; data-start=&quot;2730&quot; data-ke-size=&quot;size16&quot;&gt;실패하면 4xx나 5xx.&lt;/p&gt;
&lt;p data-end=&quot;2790&quot; data-start=&quot;2750&quot; data-ke-size=&quot;size16&quot;&gt;하지만 지금은 외우지 않아도 된다.&lt;br /&gt;오늘 필요한 숫자는 두 개뿐이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;220: 서버가 준비됨
250: 요청을 정상적으로 처리함&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2852&quot; data-start=&quot;2837&quot; data-ke-size=&quot;size16&quot;&gt;지금은 그 정도면 충분하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-section-id=&quot;1d4sg1x&quot; data-end=&quot;2874&quot; data-start=&quot;2854&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;EHLO: 클라이언트의 자기소개&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2907&quot; data-start=&quot;2876&quot; data-ke-size=&quot;size16&quot;&gt;서버가 먼저 인사하면, 이제 클라이언트가 대답할 차례다.&lt;/p&gt;
&lt;p data-end=&quot;2935&quot; data-start=&quot;2909&quot; data-ke-size=&quot;size16&quot;&gt;현대 SMTP에서는 보통 EHLO를 보낸다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;C: EHLO client.local&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3000&quot; data-start=&quot;2971&quot; data-ke-size=&quot;size16&quot;&gt;여기서 C:는 클라이언트가 보낸 줄이라는 뜻이다.&lt;/p&gt;
&lt;p data-end=&quot;3024&quot; data-start=&quot;3002&quot; data-ke-size=&quot;size16&quot;&gt;이 문장은 대략 이렇게 이해할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;mercury&quot;&gt;&lt;code&gt;안녕하세요. 저는 client.local입니다.
그리고 당신이 어떤 기능을 지원하는지도 알고 싶습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3189&quot; data-start=&quot;3097&quot; data-ke-size=&quot;size16&quot;&gt;EHLO는 Extended Hello라고 생각하면 된다. 단순히 인사만 하는 것이 아니라, 서버가 지원하는 확장 기능 목록을 받을 수 있는 출발점이기도 하다.&lt;/p&gt;
&lt;p data-end=&quot;3211&quot; data-start=&quot;3191&quot; data-ke-size=&quot;size16&quot;&gt;서버는 이런 식으로 응답할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;S: 250-localhost greets client.local
S: 250 HELP&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3295&quot; data-start=&quot;3275&quot; data-ke-size=&quot;size16&quot;&gt;여기서 처음 보는 모양이 하나 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;250-
250 &lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3354&quot; data-start=&quot;3320&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 줄은 250-처럼 숫자 뒤에 하이픈이 붙어 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;250-localhost greets client.local&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3425&quot; data-start=&quot;3403&quot; data-ke-size=&quot;size16&quot;&gt;이건 &amp;ldquo;아직 응답이 더 있다&amp;rdquo;는 뜻이다.&lt;/p&gt;
&lt;p data-end=&quot;3456&quot; data-start=&quot;3427&quot; data-ke-size=&quot;size16&quot;&gt;마지막 줄은 250처럼 숫자 뒤에 공백이 온다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;250 HELP&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3504&quot; data-start=&quot;3480&quot; data-ke-size=&quot;size16&quot;&gt;이건 &amp;ldquo;이 줄이 응답의 끝이다&amp;rdquo;라는 뜻이다.&lt;/p&gt;
&lt;p data-end=&quot;3530&quot; data-start=&quot;3506&quot; data-ke-size=&quot;size16&quot;&gt;즉, 여러 줄 응답은 이렇게 읽을 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;250-아직 더 있음
250-아직 더 있음
250 마지막 줄&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3631&quot; data-start=&quot;3579&quot; data-ke-size=&quot;size16&quot;&gt;지금은 서버가 특별한 기능을 많이 제공하지 않기 때문에 HELP 정도만 응답해도 충분하다.&lt;/p&gt;
&lt;p data-end=&quot;3732&quot; data-start=&quot;3633&quot; data-ke-size=&quot;size16&quot;&gt;나중에 기능이 늘어나면 여기에 SIZE, STARTTLS, AUTH, 8BITMIME 같은 것들이 붙을 수 있다. 하지만 아직은 그 단어들을 이해하지 않아도 된다.&lt;/p&gt;
&lt;p data-end=&quot;3762&quot; data-start=&quot;3734&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 그저 서버가 이렇게 말한다는 것만 확인한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;너의 인사를 받았다.
나는 이런 기능을 지원한다.
이제 다음 말을 해도 된다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-section-id=&quot;9js2uq&quot; data-end=&quot;3842&quot; data-start=&quot;3821&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HELO: 오래된 인사도 받아주기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;3872&quot; data-start=&quot;3844&quot; data-ke-size=&quot;size16&quot;&gt;EHLO와 비슷한 명령으로 HELO가 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;C: HELO client.local
S: 250 localhost&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3947&quot; data-start=&quot;3925&quot; data-ke-size=&quot;size16&quot;&gt;HELO는 더 오래된 방식의 인사다.&lt;/p&gt;
&lt;p data-end=&quot;4132&quot; data-start=&quot;3949&quot; data-ke-size=&quot;size16&quot;&gt;현대 SMTP에서는 보통 EHLO를 먼저 사용하지만, 예전 클라이언트와의 호환성을 위해 서버는 HELO도 처리할 수 있어야 한다. RFC 5321도 클라이언트는 가능하면 EHLO로 세션을 시작해야 하고, 서버는 HELO 명령도 지원해야 한다고 설명한다.&lt;/p&gt;
&lt;p data-end=&quot;4163&quot; data-start=&quot;4134&quot; data-ke-size=&quot;size16&quot;&gt;둘의 차이는 지금 단계에서는 이렇게만 생각하면 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;EHLO: 안녕하세요. 저는 누구이고, 확장 기능도 알고 싶습니다.
HELO: 안녕하세요. 저는 누구입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;4266&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;그래서 우리가 만들 서버는 둘 다 받아줄 것이다.&lt;/p&gt;
&lt;p data-end=&quot;4307&quot; data-start=&quot;4268&quot; data-ke-size=&quot;size16&quot;&gt;다만 앞으로의 구현에서는 EHLO를 기본 흐름으로 사용할 생각이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-section-id=&quot;1r0ef4b&quot; data-end=&quot;4329&quot; data-start=&quot;4309&quot; data-ke-size=&quot;size26&quot;&gt;가장 작은 SMTP 서버 만들기&lt;/h2&gt;
&lt;p data-end=&quot;4348&quot; data-start=&quot;4331&quot; data-ke-size=&quot;size16&quot;&gt;이제 실제로 서버를 만들어보자.&lt;/p&gt;
&lt;p data-end=&quot;4520&quot; data-start=&quot;4350&quot; data-ke-size=&quot;size16&quot;&gt;아직 완전한 SMTP 서버는 아니다. 엄밀히 말하면 RFC 5321의 최소 구현도 아니다. 최소 구현으로 가려면 MAIL, RCPT, DATA, RSET, NOOP, QUIT, VRFY 같은 명령도 더 필요하다.&lt;/p&gt;
&lt;p data-end=&quot;4539&quot; data-start=&quot;4522&quot; data-ke-size=&quot;size16&quot;&gt;하지만 오늘은 첫 대화만 본다.&lt;/p&gt;
&lt;p data-end=&quot;4625&quot; data-start=&quot;4541&quot; data-ke-size=&quot;size16&quot;&gt;서버가 먼저 220으로 인사하고, 클라이언트가 EHLO 또는 HELO로 자기소개를 하면, 서버가 250으로 대답하는 작은 서버를 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;나는 Go를 사용해 만들어 보려고 한다. 내가 익숙한 파이썬으로 할 수 도 있겠지만 몇몇 이유가 있다.&lt;/div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 네트워크 프로그래밍에 최적화된 표준 라이브러리&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Go의 net 패키지는 TCP 소켓을 다루는 데 필요한 것들이 표준 라이브러리에 다 들어있다. 외부 패키지 없이 지금처럼 바로 서버를 만들 수 있다. Python의 socket 모듈과 비슷하지만 훨씬 직관적이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. goroutine으로 동시 접속 처리가 단순함&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot; style=&quot;color: #14181f;&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;go handleConn(conn)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한 줄은 클라이언트가 100명 동시 접속해도 각각 독립된 goroutine으로 처리한다. Python에서 같은 걸 하려면 threading이나 asyncio를 써야 하는데 Go는 go 키워드 하나로 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 정적 타입으로 오류를 컴파일 시점에 잡음&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot; style=&quot;color: #14181f;&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;func writeLine(line string) error {&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환 타입이 명확하게 선언되어 있어서 오류 처리를 빠뜨리면 컴파일이 안 된다. 네트워크 프로그래밍은 오류 처리가 중요한데 Go가 강제로 오류를 처리하도록 유도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 와의 차이도 있다. C는 가비지 컬렉터가 없지만 Go는 카비지 컬렉터가 있어 SMTP 서버처럼 연결이 계속 생기고 끊기는 환경에서 C로 짜면 메모리 누수가 생기기 쉬운데 반면 Go는 신경을 쓰지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C로 동시성 처리를 하려면 락을 적용해야 하지만 Go는 goroutine을 사용해 처리한다. goroutine에 대해서는 나중에 따로 설명하는 것이 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;나만의 작은 SMTP 서버&lt;/b&gt;&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1778116450354&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package main

import (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;net&quot;
	&quot;strings&quot;
)

// 서버 도메인 이름 상수
// EHLO/HELO 응답, 220 인사말 등에서 서버를 식별하는 데 사용된다
// 실제 운영 환경에서는 실제 도메인명으로 변경해야 한다 (예: mail.example.com)
const serverName = &quot;localhost&quot;

func main() {
	// TCP 소켓을 2525번 포트에 바인딩하여 수신 대기
	// 표준 SMTP 포트는 25번이지만 개발 환경에서는 ISP 차단 우려로 2525 사용
	listener, err := net.Listen(&quot;tcp&quot;, &quot;:2525&quot;)
	if err != nil {
		log.Fatal(err)
	}
	// 함수 종료 시 리스너 자원 반환 (defer는 함수가 끝날 때 실행됨)
	defer listener.Close()

	log.Println(&quot;SMTP Server Started on port 2525&quot;)

	for {
		// 클라이언트 연결 요청을 수락
		// Accept()는 연결이 올 때까지 블로킹 상태로 대기한다
		conn, err := listener.Accept()
		if err != nil {
			log.Println(&quot;accept:&quot;, err)
			continue
		}
		// 새 연결마다 goroutine을 생성하여 동시에 여러 클라이언트를 처리
		// go 키워드가 없으면 한 번에 하나의 연결만 처리 가능
		go handleConn(conn)
	}
}

func handleConn(conn net.Conn) {
	// 함수 종료 시 TCP 연결 자원 반환
	defer conn.Close()

	// bufio.Reader: TCP 스트림을 줄 단위로 읽기 위한 버퍼
	// 네트워크 데이터는 바이트 스트림이므로 \n 기준으로 잘라 읽어야 한다
	reader := bufio.NewReader(conn)

	// bufio.Writer: 응답을 버퍼에 모았다가 Flush() 시점에 한 번에 전송
	// 매번 즉시 전송하는 것보다 네트워크 효율이 좋다
	writer := bufio.NewWriter(conn)

	// writeLine: 응답 전송을 위한 헬퍼 함수
	// RFC 5321에서 모든 줄은 반드시 \r\n(CRLF)으로 끝나야 한다
	// \n(LF)만 사용하면 일부 클라이언트에서 파싱 오류가 발생할 수 있다
	writeLine := func(line string) error {
		if _, err := fmt.Fprintf(writer, &quot;%s\r\n&quot;, line); err != nil {
			return err
		}
		// Flush()를 호출해야 버퍼에 쌓인 데이터가 실제로 전송된다
		return writer.Flush()
	}

	// RFC 5321 Section 3.1: 연결이 수립되면 서버가 먼저 220을 보내야 한다
	// 형식: 220 &amp;lt;도메인&amp;gt; &amp;lt;설명&amp;gt;
	if err := writeLine(&quot;220 &quot; + serverName + &quot; Simple Mail Transfer Service Ready&quot;); err != nil {
		return
	}

	// 클라이언트가 QUIT을 보내거나 연결이 끊길 때까지 명령을 반복 처리
	for {
		// \n이 나올 때까지 한 줄을 읽는다
		// TCP는 스트림 기반이라 한 번에 전체 명령이 오지 않을 수 있어 ReadString 사용
		line, err := reader.ReadString('\n')
		if err != nil {
			// 클라이언트가 연결을 끊었거나 네트워크 오류 발생
			log.Println(&quot;connection closed&quot;, err)
			return
		}

		// \r\n 또는 \n 제거하여 순수 명령 문자열만 남긴다
		line = strings.TrimRight(line, &quot;\r\n&quot;)

		// 수신한 명령을 서버 로그에 기록
		// TODO: log.Println &amp;rarr; log.Printf 로 수정 필요 (%s 포맷 지원)
		log.Println(&quot;C: %s&quot;, line)

		// 빈 줄 수신 시 오류 응답
		// RFC 5321에서 명령은 반드시 동사로 시작해야 한다
		if line == &quot;&quot; {
			if err := writeLine(&quot;500 Empty comamnd&quot;); err != nil {
				return
			}
			continue
		}

		// 명령어(cmd)와 인수(arg)를 첫 번째 공백 기준으로 분리
		// strings.Cut은 구분자가 없으면 전체를 cmd에, arg는 빈 문자열로 반환
		// 예) &quot;EHLO test.com&quot; &amp;rarr; cmd=&quot;EHLO&quot;, arg=&quot;test.com&quot;
		// 예) &quot;QUIT&quot;         &amp;rarr; cmd=&quot;QUIT&quot;,  arg=&quot;&quot;
		cmd, arg, _ := strings.Cut(line, &quot; &quot;)

		// RFC 5321 Section 2.4: 명령어는 대소문자 구분 없이 처리해야 한다 (MUST)
		// ehlo, Ehlo, EHLO 모두 동일하게 처리하기 위해 대문자로 통일
		cmd = strings.ToUpper(cmd)

		// 인수 앞뒤 공백 제거
		arg = strings.TrimSpace(arg)

		switch cmd {
		case &quot;EHLO&quot;:
			// RFC 5321 Section 4.1.1.1
			// EHLO는 클라이언트가 서버에 신원을 알리고 확장 기능을 협상하는 명령
			// 인수(도메인 또는 주소 리터럴)가 없으면 501 반환
			if arg == &quot;&quot; {
				if err := writeLine(&quot;501 Syntax: EHLO hostname&quot;); err != nil {
					return
				}
				continue
			}

			// TODO: EHLO 수신 시 진행 중인 트랜잭션 상태 초기화 필요
			// (MAIL FROM, RCPT TO, DATA 관련 변수들을 초기화해야 함)

			// 다중 응답 형식: 마지막 줄 제외는 &quot;250-&quot;, 마지막 줄은 &quot;250 &quot;
			// 클라이언트는 하이픈을 보면 다음 줄을 계속 읽고, 공백을 보면 응답 끝으로 판단
			if err := writeLine(&quot;250-&quot; + serverName + &quot; greets &quot; + arg); err != nil {
				return
			}
			// 현재는 HELP만 지원 목록에 포함
			// Phase 3에서 8BITMIME, SIZE, STARTTLS 등을 여기에 추가하게 된다
			if err := writeLine(&quot;250 HELP&quot;); err != nil {
				return
			}

		case &quot;HELO&quot;:
			// RFC 5321 Section 4.1.1.1
			// HELO는 확장 기능을 지원하지 않는 구형 클라이언트용 명령
			// EHLO와 달리 지원 기능 목록을 응답하지 않는다
			if arg == &quot;&quot; {
				if err := writeLine(&quot;501 Syntax: HELO hostname&quot;); err != nil {
					return
				}
				continue
			}
			// HELO 응답은 단순히 서버 도메인만 포함
			if err := writeLine(&quot;250 &quot; + serverName); err != nil {
				return
			}

		case &quot;HELP&quot;:
			// RFC 5321 Section 4.1.1.8
			// 서버가 지원하는 명령 목록을 사람이 읽을 수 있는 형태로 반환
			// 214는 Help message 응답 코드
			if err := writeLine(&quot;214 Commands supported: EHLO HELO HELP QUIT&quot;); err != nil {
				return
			}

		case &quot;QUIT&quot;:
			// RFC 5321 Section 4.1.1.10
			// 221 응답 후 연결 종료
			// 221을 보내기 전에 연결을 먼저 닫으면 안 된다 (MUST NOT)
			_ = writeLine(&quot;221 &quot; + serverName + &quot; Bye&quot;)
			return // defer conn.Close()가 실행되어 연결이 닫힌다

		default:
			// RFC 5321: 알 수 없는 명령에 대해 500 반환 후 연결 유지
			// 모르는 명령을 받았다고 연결을 끊으면 RFC 위반이다
			if err := writeLine(&quot;500 Command unrecognized&quot;); err != nil {
				return
			}
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트는 2525를 사용했다.&lt;/p&gt;
&lt;p data-end=&quot;6807&quot; data-start=&quot;6655&quot; data-ke-size=&quot;size16&quot;&gt;SMTP의 기본 포트는 25번이지만, 로컬에서 실험할 때는 관리자 권한이나 운영체제 설정 문제를 피하기 위해 2525 같은 포트를 사용하는 편이 편하다. 지금 우리가 만들고 있는 것은 실제 인터넷에 공개할 메일 서버가 아니라, SMTP 대화를 관찰하기 위한 실험용 서버다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-section-id=&quot;plgyjr&quot; data-end=&quot;6825&quot; data-start=&quot;6809&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;한 줄의 끝은 CRLF다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;6851&quot; data-start=&quot;6827&quot; data-ke-size=&quot;size16&quot;&gt;코드에서 응답을 보낼 때 이런 부분이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;fmt.Fprintf(writer, &quot;%s\r\n&quot;, line)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;6917&quot; data-start=&quot;6900&quot; data-ke-size=&quot;size16&quot;&gt;여기서 \r\n이 중요하다.&lt;/p&gt;
&lt;p data-end=&quot;7101&quot; data-start=&quot;6919&quot; data-ke-size=&quot;size16&quot;&gt;우리는 보통 줄 바꿈을 \n 정도로 생각한다. 하지만 SMTP에서 한 줄은 CRLF, 즉 \r\n으로 끝난다. RFC 5321도 SMTP의 라인은 &amp;lt;CRLF&amp;gt;로 끝난다고 설명하고, 줄 종료자로 다른 문자나 시퀀스를 생성해서는 안 된다고 설명한다.&lt;/p&gt;
&lt;p data-end=&quot;7116&quot; data-start=&quot;7103&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 사소해 보인다.&lt;/p&gt;
&lt;p data-end=&quot;7158&quot; data-start=&quot;7118&quot; data-ke-size=&quot;size16&quot;&gt;하지만 프로토콜을 직접 구현하다 보면 이런 작은 약속들이 계속 등장한다.&lt;/p&gt;
&lt;p data-end=&quot;7177&quot; data-start=&quot;7160&quot; data-ke-size=&quot;size16&quot;&gt;명령 한 줄은 어디서 끝나는가.&lt;/p&gt;
&lt;p data-end=&quot;7196&quot; data-start=&quot;7179&quot; data-ke-size=&quot;size16&quot;&gt;응답 한 줄은 어디서 끝나는가.&lt;/p&gt;
&lt;p data-end=&quot;7211&quot; data-start=&quot;7198&quot; data-ke-size=&quot;size16&quot;&gt;본문은 어디서 끝나는가.&lt;/p&gt;
&lt;p data-end=&quot;7262&quot; data-start=&quot;7213&quot; data-ke-size=&quot;size16&quot;&gt;우리는 아직 메일 본문까지 가지 않았다. 그런데도 벌써 &amp;ldquo;줄의 끝&amp;rdquo;이라는 약속을 만났다.&lt;/p&gt;
&lt;p data-end=&quot;7301&quot; data-start=&quot;7264&quot; data-ke-size=&quot;size16&quot;&gt;SMTP 구현은 이런 약속들을 하나씩 확인하는 과정이 될 것 같다.&lt;/p&gt;
&lt;p data-end=&quot;7301&quot; data-start=&quot;7264&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5321에는 안정성을 위해 크기 제한이나 타임아웃에 대한 내용도 포함되어 있다. 이러한 내용들을 모아 클로드와 작업 지침서를 만들었고 그걸 따라가며 구현을 진행하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/blV6Cj/dJMcadBPZ71/NCpc1PHqZ47UE5Hx4wxHkK/SMTP_%E1%84%80%E1%85%AE%E1%84%92%E1%85%A7%E1%86%AB_%E1%84%8C%E1%85%A1%E1%86%A8%E1%84%8B%E1%85%A5%E1%86%B8%E1%84%8C%E1%85%B5%E1%84%8E%E1%85%B5%E1%86%B7%E1%84%89%E1%85%A5.md?attach=1&amp;amp;knm=tfile.md&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;SMTP_구현_작업지침서.md&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.02MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-section-id=&quot;1gxpqmp&quot; data-end=&quot;7314&quot; data-start=&quot;7303&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;직접 대화해 보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;7325&quot; data-start=&quot;7316&quot; data-ke-size=&quot;size16&quot;&gt;서버를 실행한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;go run main.go&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7369&quot; data-start=&quot;7355&quot; data-ke-size=&quot;size16&quot;&gt;다른 터미널에서 접속한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;telnet localhost 2525&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7417&quot; data-start=&quot;7402&quot; data-ke-size=&quot;size16&quot;&gt;그러면 서버가 먼저 말한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;220 localhost Simple Mail Transfer Service Ready&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7506&quot; data-start=&quot;7481&quot; data-ke-size=&quot;size16&quot;&gt;여기서 클라이언트가 EHLO를 입력해 본다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;EHLO client.local&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7551&quot; data-start=&quot;7539&quot; data-ke-size=&quot;size16&quot;&gt;서버는 이렇게 답한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;250-localhost greets client.local
250 HELP&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7624&quot; data-start=&quot;7609&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 연결을 종료해 본다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;cos&quot;&gt;&lt;code&gt;QUIT&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7659&quot; data-start=&quot;7644&quot; data-ke-size=&quot;size16&quot;&gt;서버가 마지막 인사를 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;221 localhost Bye&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7731&quot; data-start=&quot;7692&quot; data-ke-size=&quot;size16&quot;&gt;읽기 좋게 C:와 S:를 붙여서 전체 대화를 다시 보면 이렇다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;S: 220 localhost Simple Mail Transfer Service Ready
C: EHLO client.local
S: 250-localhost greets client.local
S: 250 HELP
C: QUIT
S: 221 localhost Bye&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 10.23.37.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnGsLR/dJMcacC2eeF/27JoLpUcClUuh5ZiiVEsEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnGsLR/dJMcacC2eeF/27JoLpUcClUuh5ZiiVEsEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnGsLR/dJMcacC2eeF/27JoLpUcClUuh5ZiiVEsEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnGsLR%2FdJMcacC2eeF%2F27JoLpUcClUuh5ZiiVEsEk%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;776&quot; height=&quot;316&quot; data-filename=&quot;스크린샷 2026-05-07 오전 10.23.37.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;7903&quot; data-start=&quot;7897&quot; data-ke-size=&quot;size16&quot;&gt;아주 짧다.&lt;/p&gt;
&lt;p data-end=&quot;7918&quot; data-start=&quot;7905&quot; data-ke-size=&quot;size16&quot;&gt;메일을 보내지도 않았다.&lt;/p&gt;
&lt;p data-end=&quot;7935&quot; data-start=&quot;7920&quot; data-ke-size=&quot;size16&quot;&gt;수신자를 지정하지도 않았다.&lt;/p&gt;
&lt;p data-end=&quot;7944&quot; data-start=&quot;7937&quot; data-ke-size=&quot;size16&quot;&gt;본문도 없다.&lt;/p&gt;
&lt;p data-end=&quot;7973&quot; data-start=&quot;7946&quot; data-ke-size=&quot;size16&quot;&gt;하지만 우리는 처음으로 SMTP 서버와 대화했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-section-id=&quot;s1y817&quot; data-end=&quot;8000&quot; data-start=&quot;7975&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;오늘 만든 것은 아직 메일 서버가 아니다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;8033&quot; data-start=&quot;8002&quot; data-ke-size=&quot;size16&quot;&gt;오늘 만든 프로그램은 아직 메일 서버라고 부르기 어렵다.&lt;/p&gt;
&lt;p data-end=&quot;8046&quot; data-start=&quot;8035&quot; data-ke-size=&quot;size16&quot;&gt;메일을 받지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;8061&quot; data-start=&quot;8048&quot; data-ke-size=&quot;size16&quot;&gt;메일을 저장하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;8079&quot; data-start=&quot;8063&quot; data-ke-size=&quot;size16&quot;&gt;다른 서버로 전달하지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;8096&quot; data-start=&quot;8081&quot; data-ke-size=&quot;size16&quot;&gt;사용자의 받은 편지함도 없다.&lt;/p&gt;
&lt;p data-end=&quot;8137&quot; data-start=&quot;8098&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 이 작은 프로그램은 SMTP 서버의 가장 첫 장면을 재현한다.&lt;/p&gt;
&lt;p data-end=&quot;8154&quot; data-start=&quot;8139&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 문을 두드린다.&lt;/p&gt;
&lt;p data-end=&quot;8171&quot; data-start=&quot;8156&quot; data-ke-size=&quot;size16&quot;&gt;서버가 준비되었다고 말한다.&lt;/p&gt;
&lt;p data-end=&quot;8189&quot; data-start=&quot;8173&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 자신을 소개한다.&lt;/p&gt;
&lt;p data-end=&quot;8206&quot; data-start=&quot;8191&quot; data-ke-size=&quot;size16&quot;&gt;서버가 그 인사를 받아준다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;연결
  &amp;darr;
220
  &amp;darr;
EHLO
  &amp;darr;
250&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;8281&quot; data-start=&quot;8249&quot; data-ke-size=&quot;size16&quot;&gt;이 네 줄만으로도 SMTP가 조금 다르게 보이기 시작한다.&lt;/p&gt;
&lt;p data-end=&quot;8320&quot; data-start=&quot;8283&quot; data-ke-size=&quot;size16&quot;&gt;이전까지 이메일은 &amp;ldquo;보내기 버튼을 누르면 어딘가로 가는 것&amp;rdquo;이었다.&lt;/p&gt;
&lt;p data-end=&quot;8358&quot; data-start=&quot;8322&quot; data-ke-size=&quot;size16&quot;&gt;이제는 그 안쪽에 아주 구체적인 대화가 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;8385&quot; data-start=&quot;8360&quot; data-ke-size=&quot;size16&quot;&gt;서버는 아무 말 없이 기다리고만 있지 않았다.&lt;/p&gt;
&lt;p data-end=&quot;8402&quot; data-start=&quot;8387&quot; data-ke-size=&quot;size16&quot;&gt;오히려 서버가 먼저 말했다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;220 Service Ready&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-section-id=&quot;w6ps8s&quot; data-end=&quot;8453&quot; data-start=&quot;8435&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이번 글의 끝에서 남는 질문&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;8632&quot; data-start=&quot;8610&quot; data-ke-size=&quot;size16&quot;&gt;서버는 먼저 자신이 준비되었다고 말한다.&lt;/p&gt;
&lt;p data-end=&quot;8654&quot; data-start=&quot;8634&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트는 자신이 누구인지 말한다.&lt;/p&gt;
&lt;p data-end=&quot;8671&quot; data-start=&quot;8656&quot; data-ke-size=&quot;size16&quot;&gt;서버는 그 인사를 받아준다.&lt;/p&gt;
&lt;p data-end=&quot;8687&quot; data-start=&quot;8673&quot; data-ke-size=&quot;size16&quot;&gt;이제 다음 질문이 남는다.&lt;/p&gt;
&lt;p data-end=&quot;8717&quot; data-start=&quot;8689&quot; data-ke-size=&quot;size16&quot;&gt;인사를 마친 뒤, 클라이언트는 무엇을 말해야 할까?&lt;/p&gt;
&lt;p data-end=&quot;8746&quot; data-start=&quot;8719&quot; data-ke-size=&quot;size16&quot;&gt;메일을 보내려면 가장 먼저 무엇을 알려줘야 할까?&lt;/p&gt;
&lt;p data-end=&quot;8779&quot; data-start=&quot;8748&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서는 드디어 메일의 &amp;ldquo;봉투&amp;rdquo;를 만들기 시작한다.&lt;/p&gt;
&lt;p data-end=&quot;8820&quot; data-start=&quot;8781&quot; data-ke-size=&quot;size16&quot;&gt;본문보다 먼저, 제목보다 먼저, 서버는 발신자와 수신자를 알아야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;MAIL FROM
RCPT TO&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;8889&quot; data-start=&quot;8853&quot; data-ke-size=&quot;size16&quot;&gt;이 두 줄에서 이메일은 조금 더 메일다운 모양을 갖추기 시작한다.&lt;/p&gt;</description>
      <category>Deep Dive/기타</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/236</guid>
      <comments>https://gowoong.tistory.com/236#entry236comment</comments>
      <pubDate>Thu, 7 May 2026 10:25:57 +0900</pubDate>
    </item>
    <item>
      <title>[SMTP] 2. SMTP 지도 그리기: 메일은 어디에서 어디로 가는가</title>
      <link>https://gowoong.tistory.com/235</link>
      <description>&lt;h2 data-end=&quot;1695&quot; data-start=&quot;1677&quot; data-section-id=&quot;13c7jma&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 지난 글에서 남은 질문&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1721&quot; data-start=&quot;1697&quot; data-ke-size=&quot;size16&quot;&gt;이전글에서 우리는 이런 질문으로 출발했다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1749&quot; data-start=&quot;1723&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;p data-end=&quot;1749&quot; data-start=&quot;1725&quot; data-ke-size=&quot;size16&quot;&gt;우리 서버가 직접 이메일을 보낼 수 있을까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1781&quot; data-start=&quot;1751&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 질문에 바로 답하려고 하면 조금 막막하다.&lt;/p&gt;
&lt;p data-end=&quot;1913&quot; data-start=&quot;1783&quot; data-ke-size=&quot;size16&quot;&gt;이메일을 &amp;ldquo;보낸다&amp;rdquo;는 말이 생각보다 넓기 때문이다. 사용자가 메일을 작성하는 것도 보내는 과정처럼 보이고, 우리 서버가 다른 서버에 전달하는 것도 보내는 과정처럼 보이고, 수신자가 메일함에서 읽는 것도 전체 흐름 안에 들어있다.&lt;/p&gt;
&lt;p data-end=&quot;1948&quot; data-start=&quot;1915&quot; data-ke-size=&quot;size16&quot;&gt;그래서 구현을 시작하기 전에 먼저 지도를 그려보려고 한다.&lt;/p&gt;
&lt;p data-end=&quot;1979&quot; data-start=&quot;1950&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메일은 어디에서 출발해서 어디까지 가는 걸까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1979&quot; data-start=&quot;1950&quot; data-ke-size=&quot;size16&quot;&gt;메일은 어디에서 출발해서 어디까지 가는 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그중에서 SMTP는 정확히 어느 구간을 담당하는 걸까?&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2002&quot; data-start=&quot;1981&quot; data-section-id=&quot;1obl4gk&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 이메일에는 여러 역할이 있다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2028&quot; data-start=&quot;2004&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 이메일 시스템이 단순하게 보인다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;보내는 사람 &amp;rarr; 받는 사람&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2085&quot; data-start=&quot;2058&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제로는 그 사이에 여러 역할이 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-07 오전 8.44.35.png&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;1278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yieeI/dJMcafmdCwU/KX9kbkVSi6XlYTXrMKhkG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yieeI/dJMcafmdCwU/KX9kbkVSi6XlYTXrMKhkG0/img.png&quot; data-alt=&quot;RFC 5321에서의 SMTP 설명&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yieeI/dJMcafmdCwU/KX9kbkVSi6XlYTXrMKhkG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyieeI%2FdJMcafmdCwU%2FKX9kbkVSi6XlYTXrMKhkG0%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;2232&quot; height=&quot;1278&quot; data-filename=&quot;스크린샷 2026-05-07 오전 8.44.35.png&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;1278&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RFC 5321에서의 SMTP 설명&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2223&quot; data-start=&quot;2167&quot; data-ke-size=&quot;size16&quot;&gt;여기서 우리가 구현하려는 SMTP는 이 전체 과정 중에서도 &lt;b&gt;메일을 전송하는 구간&lt;/b&gt;에 관여한다.&lt;/p&gt;
&lt;p data-end=&quot;2421&quot; data-start=&quot;2225&quot; data-ke-size=&quot;size16&quot;&gt;메일을 작성하는 화면도 아니고, 받은 메일을 읽는 기능도 아니다. SMTP는 메일을 어느 서버에서 다른 서버로 넘기는 쪽에 가깝다. RFC 5321도 SMTP를 메일 전송과 전달을 위한 프로토콜로 설명하고, 메일 읽기와 메시지 형식은 다른 문서와 프로토콜의 영역으로 구분한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2449&quot; data-start=&quot;2423&quot; data-section-id=&quot;1gculym&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. SMTP에는 클라이언트와 서버가 있다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2499&quot; data-start=&quot;2451&quot; data-ke-size=&quot;size16&quot;&gt;웹 개발을 하다 보면 보통 브라우저가 클라이언트고, API 서버가 서버라고 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;2628&quot; data-start=&quot;2501&quot; data-ke-size=&quot;size16&quot;&gt;SMTP에서도 비슷하게 클라이언트와 서버가 있다. 다만 여기서 클라이언트는 꼭 사람이 사용하는 앱이 아닐 수 있다. 메일을 다른 서버로 보내려는 쪽이 SMTP 클라이언트가 되고, 메일을 받는 쪽이 SMTP 서버가 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;SMTP 클라이언트  ── 명령 ──&amp;gt;  SMTP 서버
SMTP 클라이언트  &amp;lt;─ 응답 ───  SMTP 서버&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2730&quot; data-start=&quot;2705&quot; data-ke-size=&quot;size16&quot;&gt;즉, SMTP는 기본적으로 &lt;b&gt;대화&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;2791&quot; data-start=&quot;2732&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 명령을 보내고, 서버가 응답한다. 클라이언트가 다시 명령을 보내고, 서버가 다시 응답한다.&lt;/p&gt;
&lt;p data-end=&quot;2906&quot; data-start=&quot;2793&quot; data-ke-size=&quot;size16&quot;&gt;이걸 조금 우체국처럼 생각해 보면, 보내는 쪽은 &amp;ldquo;이 편지를 이 사람에게 보내고 싶습니다&amp;rdquo;라고 말하고, 받는 쪽은 &amp;ldquo;좋습니다&amp;rdquo;, &amp;ldquo;그 주소는 모릅니다&amp;rdquo;, &amp;ldquo;본문을 보내세요&amp;rdquo; 같은 식으로 답하는 구조다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2935&quot; data-start=&quot;2908&quot; data-section-id=&quot;qvkc3k&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 메일 서버는 하나의 역할만 하지 않는다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2958&quot; data-start=&quot;2937&quot; data-ke-size=&quot;size16&quot;&gt;여기서 조금 헷갈리는 부분이 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;3040&quot; data-start=&quot;2960&quot; data-ke-size=&quot;size16&quot;&gt;어떤 서버는 메일을 받을 때는 SMTP 서버다. 하지만 그 메일을 다시 다른 서버로 전달해야 한다면, 이번에는 SMTP 클라이언트가 된다.&lt;/p&gt;
&lt;p data-end=&quot;3074&quot; data-start=&quot;3042&quot; data-ke-size=&quot;size16&quot;&gt;즉, 하나의 서버가 상황에 따라 역할을 바꿀 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A 서버 ──&amp;gt; B 서버 ──&amp;gt; C 서버&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3168&quot; data-start=&quot;3112&quot; data-ke-size=&quot;size16&quot;&gt;이 흐름에서 B 서버는 A에게 메일을 받을 때는 서버이고, C에게 메일을 보낼 때는 클라이언트다.&lt;/p&gt;
&lt;p data-end=&quot;3312&quot; data-start=&quot;3170&quot; data-ke-size=&quot;size16&quot;&gt;이런 중간 전달자를 릴레이라고 부를 수 있다. RFC 5321의 SMTP 모델도 SMTP 서버가 최종 목적지일 수도 있고, 중간 릴레이나 게이트웨이 역할을 할 수도 있다고 설명한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3390&quot; data-start=&quot;3366&quot; data-section-id=&quot;zdqvjm&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 그러면 우리는 무엇을 만들 것인가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;3484&quot; data-start=&quot;3416&quot; data-ke-size=&quot;size16&quot;&gt;우리는 우선 가장 작은 SMTP 실험실을 만들 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;SMTP 클라이언트가 접속한다
서버가 인사한다
클라이언트가 명령을 보낸다
서버가 응답한다
메일 데이터를 받아서 저장한다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3827&quot; data-start=&quot;3625&quot; data-ke-size=&quot;size16&quot;&gt;구현 후 곧바로 구글이나 네이버로 메일을 보내고 싶을 수 있지만 그렇게 하지 못할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1778112567479&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;telnet smtp.gmail.com 25&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 gmail과 통신을 진행해보면 알 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1778112631729&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Trying 172.217.221.109...
Connected to smtp.gmail.com.
Escape character is '^]'.
220 smtp.gmail.com ESMTP d2e1a72fcca58-83965645d74sm6572331b3a.1 - gsmtp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 gmail이 우리에게 보낸 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1778112667312&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EHLO test.com
250-smtp.gmail.com at your service, [121.152.99.9]
250-SIZE 35882577
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 EHLO 라는 문구로 내가 누구인지 보내면 이후 250으로 시작하는 여러 줄을 통해 gmail 서버가 자신의 소개와 자신이 지원하는 기능들을 소개한다. 이러한 것은 추후 언급하겠지만 중요한 것은 gmail의 경우 STARTTLS라고 TLS 환경에서만 동작을 진행한다. 즉 우리가 SMTP를 만들었지만 gmail은 우리가 보내는 것을 거부할 가능성이 높다 그렇기 때문에 우리는 일단 작은 서버부터 만들고 이후에 생각을 해볼 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. RFC는 어디에 쓰일까&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SMTP는 오래된 프로토콜이다. 오래되었다는 말은 낡았다는 뜻이기도 하지만, 동시에 많은 시스템이 오랫동안 같은 약속을 지켜왔다는 뜻이기도 하다.&lt;/p&gt;
&lt;p data-end=&quot;4126&quot; data-start=&quot;4063&quot; data-ke-size=&quot;size16&quot;&gt;우리가 SMTP 서버를 직접 구현하려면 그 약속을 어느 정도 따라야 한다. 그 약속이 정리된 문서가 RFC다.&lt;/p&gt;
&lt;p data-end=&quot;4231&quot; data-start=&quot;4134&quot; data-ke-size=&quot;size16&quot;&gt;앞으로 구현을 하다가 &amp;ldquo;서버가 먼저 말해야 하나?&amp;rdquo;, &amp;ldquo;응답은 어떤 형식이어야 하나?&amp;rdquo;, &amp;ldquo;메일 본문은 어디서 끝나나?&amp;rdquo; 같은 질문이 생길 때마다 RFC를 펼쳐볼 것이다.&lt;/p&gt;
&lt;p data-end=&quot;4300&quot; data-start=&quot;4239&quot; data-ke-size=&quot;size16&quot;&gt;지금은 RFC를 처음부터 끝까지 읽지 않는다. 먼저 지도를 그리고, 길을 잃을 때마다 필요한 부분을 찾아본다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc5321&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://datatracker.ietf.org/doc/html/rfc5321&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778113040147&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;RFC 5321: Simple Mail Transfer Protocol&quot; data-og-description=&quot;This document is a specification of the basic protocol for Internet electronic mail transport. It consolidates, updates, and clarifies several previous documents, making all or parts of most of them obsolete. It covers the SMTP extension mechanisms and bes&quot; data-og-host=&quot;datatracker.ietf.org&quot; data-og-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc5321&quot; data-og-url=&quot;https://datatracker.ietf.org/doc/html/rfc5321&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/j0RMc/dJMb81G1pay/sUP0ndgQg2AjeIOKuetNsk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc5321&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://datatracker.ietf.org/doc/html/rfc5321&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/j0RMc/dJMb81G1pay/sUP0ndgQg2AjeIOKuetNsk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RFC 5321: Simple Mail Transfer Protocol&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This document is a specification of the basic protocol for Internet electronic mail transport. It consolidates, updates, and clarifies several previous documents, making all or parts of most of them obsolete. It covers the SMTP extension mechanisms and bes&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;datatracker.ietf.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Dive/기타</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/235</guid>
      <comments>https://gowoong.tistory.com/235#entry235comment</comments>
      <pubDate>Thu, 7 May 2026 09:17:54 +0900</pubDate>
    </item>
    <item>
      <title>[SMTP] 1. SMTP를 직접 구현해보기로 했다.</title>
      <link>https://gowoong.tistory.com/234</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 꽤 오랜 시간 이메일을 사용해왔다.&lt;/p&gt;
&lt;p data-end=&quot;574&quot; data-start=&quot;410&quot; data-ke-size=&quot;size16&quot;&gt;요즘에는 다른 서비스를 가입할 때도 이메일을 입력한다. 회원가입을 마치기 위해 인증 메일을 확인한다. 비밀번호를 잊어버렸을 때도 이메일로 재설정 링크를 받는다. 결제 영수증도 이메일로 오고, 서비스 공지도 이메일로 오고, 가끔은 내가 가입한 기억도 희미한 서비스에서 오랜만에 메일이 오기도 한다.&lt;/p&gt;
&lt;p data-end=&quot;589&quot; data-start=&quot;576&quot; data-ke-size=&quot;size16&quot;&gt;이메일은 너무 익숙하다.&lt;/p&gt;
&lt;p data-end=&quot;616&quot; data-start=&quot;591&quot; data-ke-size=&quot;size16&quot;&gt;너무 익숙해서 오히려 잘 생각하지 않게 된다.&lt;/p&gt;
&lt;p data-end=&quot;635&quot; data-start=&quot;618&quot; data-ke-size=&quot;size16&quot;&gt;메일을 보낸다는 것은 무엇일까?&lt;/p&gt;
&lt;p data-end=&quot;685&quot; data-start=&quot;637&quot; data-ke-size=&quot;size16&quot;&gt;서비스에서 &amp;ldquo;인증 메일을 보냈습니다&amp;rdquo;라고 말할 때, 실제로는 무슨 일이 일어나는 걸까?&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-end=&quot;708&quot; data-start=&quot;687&quot; data-section-id=&quot;1fkq1e5&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예전에 만들었던 이메일 인증 기능&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;738&quot; data-start=&quot;710&quot; data-ke-size=&quot;size16&quot;&gt;예전에 회사에서 회원가입 기능을 구현한 적이 있다.&lt;/p&gt;
&lt;p data-end=&quot;882&quot; data-start=&quot;740&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 이메일 주소를 입력하면 인증 메일을 보내고, 사용자는 그 메일 안에 있는 링크나 코드를 통해 본인의 이메일임을 증명하는 방식이었다. 비밀번호를 잊어버렸을 때도 비슷했다. 사용자가 이메일을 입력하면 비밀번호를 재설정할 수 있는 링크를 보내야 했다.&lt;/p&gt;
&lt;p data-end=&quot;917&quot; data-start=&quot;884&quot; data-ke-size=&quot;size16&quot;&gt;당시의 나는 이 기능을 &amp;ldquo;이메일 발송 기능&amp;rdquo;이라고 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;940&quot; data-start=&quot;919&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실제 구현은 생각보다 단순했다.&lt;/p&gt;
&lt;p data-end=&quot;1016&quot; data-start=&quot;942&quot; data-ke-size=&quot;size16&quot;&gt;외부 메일 발송 서비스를 선택하고, 계정을 만들고, API 키를 발급받고, 문서에 나온 대로 코드를 작성했다. 대략 이런 느낌이었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;SendEmail(to, subject, body)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1067&quot; data-start=&quot;1058&quot; data-ke-size=&quot;size16&quot;&gt;메일이 보내졌다.&lt;/p&gt;
&lt;p data-end=&quot;1190&quot; data-start=&quot;1069&quot; data-ke-size=&quot;size16&quot;&gt;기능은 동작했고, 사용자는 인증 메일을 받을 수 있었다. 서비스 입장에서도 문제가 없었다. 오히려 그게 가장 현실적인 선택이었다. 내가 직접 메일 서버를 만들 필요도 없었고, 메일 전송에 대해 깊게 알 필요도 없었다.&lt;/p&gt;
&lt;p data-end=&quot;1215&quot; data-start=&quot;1192&quot; data-ke-size=&quot;size16&quot;&gt;그때의 나는 &amp;ldquo;메일을 보냈다&amp;rdquo;고 생각했다.&lt;/p&gt;
&lt;p data-end=&quot;1253&quot; data-start=&quot;1217&quot; data-ke-size=&quot;size16&quot;&gt;그런데 지금 와서 다시 생각해보면, 나는 정말 메일을 보낸 걸까?&lt;/p&gt;
&lt;p data-end=&quot;1301&quot; data-start=&quot;1255&quot; data-ke-size=&quot;size16&quot;&gt;아니면 메일을 보내주는 다른 서비스에게 &amp;ldquo;이 메일 좀 보내주세요&amp;rdquo;라고 요청한 걸까?&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-end=&quot;1318&quot; data-start=&quot;1303&quot; data-section-id=&quot;oicpus&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;나중에 생긴 작은 의문&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1354&quot; data-start=&quot;1320&quot; data-ke-size=&quot;size16&quot;&gt;서비스를 실제로 운영하려고 하니 비용 문제가 보이기 시작했다.&lt;/p&gt;
&lt;p data-end=&quot;1489&quot; data-start=&quot;1356&quot; data-ke-size=&quot;size16&quot;&gt;메일 발송 서비스는 편리했지만, 계속 사용하려면 정기적으로 비용을 지불해야 했다. 물론 당연한 일이다. 누군가 서버를 운영하고, 메일을 안정적으로 보내고, 실패를 처리하고, 여러 문제를 대신 감당해주고 있으니 비용이 드는 것은 자연스럽다.&lt;/p&gt;
&lt;p data-end=&quot;1512&quot; data-start=&quot;1491&quot; data-ke-size=&quot;size16&quot;&gt;그런데 그때 문득 이런 생각이 들었다.&lt;/p&gt;
&lt;p data-end=&quot;1531&quot; data-start=&quot;1514&quot; data-ke-size=&quot;size16&quot;&gt;우리가 직접 할 수는 없었을까?&lt;/p&gt;
&lt;p data-end=&quot;1561&quot; data-start=&quot;1533&quot; data-ke-size=&quot;size16&quot;&gt;우리 서버가 직접 이메일을 보내는 것도 가능했을까?&lt;/p&gt;
&lt;p data-end=&quot;1621&quot; data-start=&quot;1563&quot; data-ke-size=&quot;size16&quot;&gt;외부 서비스를 쓰지 않고, 우리가 만든 서버에서 사용자에게 인증 메일을 보내는 구조를 만들 수 있었을까?&lt;/p&gt;
&lt;p data-end=&quot;1661&quot; data-start=&quot;1623&quot; data-ke-size=&quot;size16&quot;&gt;그게 가능하다면 왜 많은 서비스는 외부 메일 발송 서비스를 사용할까?&lt;/p&gt;
&lt;p data-end=&quot;1690&quot; data-start=&quot;1663&quot; data-ke-size=&quot;size16&quot;&gt;반대로 가능하지 않다면 무엇 때문에 어려운 걸까?&lt;/p&gt;
&lt;p data-end=&quot;1706&quot; data-start=&quot;1692&quot; data-ke-size=&quot;size16&quot;&gt;이 질문들이 계속 남았다.&lt;/p&gt;
&lt;p data-end=&quot;1804&quot; data-start=&quot;1708&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 단순한 비용 문제처럼 보였다. &amp;ldquo;메일 발송 서비스 비용을 아낄 수 있지 않을까?&amp;rdquo; 같은 생각이었다. 하지만 조금 더 생각해보니 단순히 돈의 문제가 아닌 것 같았다.&lt;/p&gt;
&lt;p data-end=&quot;1834&quot; data-start=&quot;1806&quot; data-ke-size=&quot;size16&quot;&gt;나는 이메일이 어떻게 보내지는지 잘 모르고 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-end=&quot;1855&quot; data-start=&quot;1836&quot; data-section-id=&quot;1qbrvpu&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;너무 익숙하지만 잘 모르는 것&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1871&quot; data-start=&quot;1857&quot; data-ke-size=&quot;size16&quot;&gt;이메일은 오래된 기술이다.&lt;/p&gt;
&lt;p data-end=&quot;2003&quot; data-start=&quot;1965&quot; data-ke-size=&quot;size16&quot;&gt;하지만 아직 이메일에 대해 잘 모르는 상태에서는 모르는 것들 투성이다.&lt;/p&gt;
&lt;p data-end=&quot;2020&quot; data-start=&quot;2005&quot; data-ke-size=&quot;size16&quot;&gt;메일은 어디로 보내야 할까?&lt;/p&gt;
&lt;p data-end=&quot;2042&quot; data-start=&quot;2022&quot; data-ke-size=&quot;size16&quot;&gt;상대방의 메일 서버는 어떻게 찾을까?&lt;/p&gt;
&lt;p data-end=&quot;2074&quot; data-start=&quot;2044&quot; data-ke-size=&quot;size16&quot;&gt;메일을 보냈다는 것은 정확히 어느 시점을 말하는 걸까?&lt;/p&gt;
&lt;p data-end=&quot;2096&quot; data-start=&quot;2076&quot; data-ke-size=&quot;size16&quot;&gt;내 서버에서 메일을 내보낸 순간일까?&lt;/p&gt;
&lt;p data-end=&quot;2117&quot; data-start=&quot;2098&quot; data-ke-size=&quot;size16&quot;&gt;상대 서버가 메일을 받은 순간일까?&lt;/p&gt;
&lt;p data-end=&quot;2140&quot; data-start=&quot;2119&quot; data-ke-size=&quot;size16&quot;&gt;사용자의 받은편지함에 도착한 순간일까?&lt;/p&gt;
&lt;p data-end=&quot;2166&quot; data-start=&quot;2142&quot; data-ke-size=&quot;size16&quot;&gt;스팸함에 들어가면 보낸 걸까, 실패한 걸까?&lt;/p&gt;
&lt;p data-end=&quot;2196&quot; data-start=&quot;2168&quot; data-ke-size=&quot;size16&quot;&gt;메일 주소는 정말 존재하는지 어떻게 알 수 있을까?&lt;/p&gt;
&lt;p data-end=&quot;2217&quot; data-start=&quot;2198&quot; data-ke-size=&quot;size16&quot;&gt;메일 본문은 그냥 문자열이면 될까?&lt;/p&gt;
&lt;p data-end=&quot;2236&quot; data-start=&quot;2219&quot; data-ke-size=&quot;size16&quot;&gt;파일 첨부는 어떻게 되는 걸까?&lt;/p&gt;
&lt;p data-end=&quot;2256&quot; data-start=&quot;2238&quot; data-ke-size=&quot;size16&quot;&gt;한글 제목은 어떻게 보내는 걸까?&lt;/p&gt;
&lt;p data-end=&quot;2274&quot; data-start=&quot;2258&quot; data-ke-size=&quot;size16&quot;&gt;생각해보면 질문은 끝이 없다.&lt;/p&gt;
&lt;p data-end=&quot;2353&quot; data-start=&quot;2276&quot; data-ke-size=&quot;size16&quot;&gt;그런데 나는 이 질문들에 대해 제대로 답하지 못한다. 이메일 인증 기능을 구현해본 적은 있지만, 이메일 자체를 이해했다고 말하기는 어렵다.&lt;/p&gt;
&lt;p data-end=&quot;2388&quot; data-start=&quot;2355&quot; data-ke-size=&quot;size16&quot;&gt;외부 서비스를 사용했기 때문에 몰라도 됐던 부분들이 있었다.&lt;/p&gt;
&lt;p data-end=&quot;2421&quot; data-start=&quot;2390&quot; data-ke-size=&quot;size16&quot;&gt;이제는 그 몰라도 됐던 부분을 직접 들여다보고 싶어졌다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-end=&quot;2441&quot; data-start=&quot;2423&quot; data-section-id=&quot;1o6sqsd&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그래서 직접 구현해보려 한다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2480&quot; data-start=&quot;2443&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 &amp;ldquo;이제부터 완벽한 메일 서버를 만들겠다&amp;rdquo;는 선언이 아니다.&lt;/p&gt;
&lt;p data-end=&quot;2513&quot; data-start=&quot;2482&quot; data-ke-size=&quot;size16&quot;&gt;운영 가능한 이메일 인프라를 만들겠다는 이야기도 아니다.&lt;/p&gt;
&lt;p data-end=&quot;2621&quot; data-start=&quot;2515&quot; data-ke-size=&quot;size16&quot;&gt;아직 나는 무엇이 중요한지 모른다. 무엇이 어려운지도 모른다. 어디서 막힐지도 모른다. 어쩌면 아주 단순해 보이는 부분에서 막힐 수도 있고, 전혀 예상하지 못한 개념을 만나게 될 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;2861&quot; data-start=&quot;2834&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이메일을 직접 보내려면 무엇을 해야 할까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2896&quot; data-start=&quot;2863&quot; data-ke-size=&quot;size16&quot;&gt;이 질문에 답하기 위해 SMTP라는 것을 구현해볼 생각이다.&lt;/p&gt;
&lt;p data-end=&quot;2986&quot; data-start=&quot;2898&quot; data-ke-size=&quot;size16&quot;&gt;SMTP는 정보처리기사 공부할 때 포트번호 관련해서 스쳐지나가듯이 봤던 기억이 있다.&lt;/p&gt;
&lt;p data-end=&quot;3013&quot; data-start=&quot;2988&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 로컬에서 작은 서버를 띄워볼 것이다.&lt;/p&gt;
&lt;p data-end=&quot;3043&quot; data-start=&quot;3015&quot; data-ke-size=&quot;size16&quot;&gt;그 서버가 메일을 받을 수 있는지 확인해볼 것이다.&lt;/p&gt;
&lt;p data-end=&quot;3090&quot; data-start=&quot;3045&quot; data-ke-size=&quot;size16&quot;&gt;그다음에는 메일을 보낸다는 행위가 실제로 어떤 순서로 이루어지는지 살펴볼 것이다.&lt;/p&gt;
&lt;p data-end=&quot;3119&quot; data-start=&quot;3092&quot; data-ke-size=&quot;size16&quot;&gt;그리고 언젠가는 이런 질문에 다시 돌아오고 싶다.&lt;/p&gt;
&lt;p data-end=&quot;3145&quot; data-start=&quot;3121&quot; data-ke-size=&quot;size16&quot;&gt;예전에 회사에서 만들었던 이메일 인증 기능.&lt;/p&gt;
&lt;p data-end=&quot;3176&quot; data-start=&quot;3147&quot; data-ke-size=&quot;size16&quot;&gt;그 기능을 외부 서비스 없이 직접 만들 수 있었을까?&lt;/p&gt;
&lt;p data-end=&quot;3195&quot; data-start=&quot;3178&quot; data-ke-size=&quot;size16&quot;&gt;가능했다면 어디까지 가능했을까?&lt;/p&gt;
&lt;p data-end=&quot;3217&quot; data-start=&quot;3197&quot; data-ke-size=&quot;size16&quot;&gt;가능하지 않았다면 무엇 때문이었을까?&lt;/p&gt;
&lt;p data-end=&quot;3255&quot; data-start=&quot;3219&quot; data-ke-size=&quot;size16&quot;&gt;그때는 몰랐던 것들을, 이번에는 직접 구현하면서 알아가보려 한다.&lt;/p&gt;</description>
      <category>Deep Dive/기타</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/234</guid>
      <comments>https://gowoong.tistory.com/234#entry234comment</comments>
      <pubDate>Thu, 7 May 2026 08:27:13 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 23주차 암호화 &amp;amp; 분산 시스템 보안</title>
      <link>https://gowoong.tistory.com/233</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 암호화를 통한 정보 보호&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0,0&quot;&gt;OS의 한계:&lt;/b&gt; OS는 자신이 통제하는 하드웨어 내에서는 강력하지만, 데이터가 네트워크로 나가거나 하드디스크가 물리적으로 탈취되면 무력해진다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,1,0&quot;&gt;해결책:&lt;/b&gt; 자원을 잃을 것을 가정하고, 적대자가 데이터를 가져가더라도 &lt;b data-index-in-node=&quot;38&quot; data-path-to-node=&quot;4,1,0&quot;&gt;내용을 읽을 수 없게(기밀성)&lt;/b&gt; 하거나 &lt;b data-index-in-node=&quot;59&quot; data-path-to-node=&quot;4,1,0&quot;&gt;수정 여부를 알 수 있게(무결성)&lt;/b&gt; 미리 준비하는 것, 즉 &lt;b data-index-in-node=&quot;91&quot; data-path-to-node=&quot;4,1,0&quot;&gt;암호화&lt;/b&gt;가 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Basic Concepts&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&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;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;평문(Plaintext):&lt;/b&gt; 우리가 읽을 수 있는 원래 메시지(&lt;span data-index-in-node=&quot;34&quot; data-math=&quot;P&quot;&gt;$P$&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;암호문(Ciphertext):&lt;/b&gt; 암호화 알고리즘을 거쳐 읽을 수 없게 된 메시지(&lt;span data-index-in-node=&quot;44&quot; data-math=&quot;C&quot;&gt;$C$&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;키(Key):&lt;/b&gt; 암호화와 복호화 과정을 조절하는 비밀 값(&lt;span data-index-in-node=&quot;31&quot; data-math=&quot;K&quot;&gt;$K$&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,3,0&quot;&gt;수식:&lt;/b&gt; &lt;span data-index-in-node=&quot;4&quot; data-math=&quot;C = E(P, K)&quot;&gt;$C = E(P, K)$&lt;/span&gt; (암호화), &lt;span data-index-in-node=&quot;23&quot; data-math=&quot;P = D(C, K)&quot;&gt;$P = D(C, K)$&lt;/span&gt; (복호화).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,4,0&quot;&gt;설계 원칙:&lt;/b&gt; 암호 알고리즘(&lt;span data-index-in-node=&quot;15&quot; data-math=&quot;E, D&quot;&gt;$E, D$&lt;/span&gt;) 자체는 공개되어도 안전해야 하며, 오직 &lt;b data-index-in-node=&quot;43&quot; data-path-to-node=&quot;6,4,0&quot;&gt;키(&lt;span data-index-in-node=&quot;45&quot; data-math=&quot;K&quot;&gt;$K$&lt;/span&gt;)의 비밀성&lt;/b&gt;에만 보안을 의존해야 한다 (Kerckhoffs의 원칙).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Symmetric Cryptography (대칭키 암호화)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;정의:&lt;/b&gt; 암호화와 복호화에 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;8,0,0&quot;&gt;동일한 키&lt;/b&gt;를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;특징:&lt;/b&gt; 계산 속도가 매우 빨라 대량의 데이터를 처리하기에 적합하다 (예: AES).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;난제 (Key Distribution Problem):&lt;/b&gt; 통신하는 양측이 어떻게 안전하게 키를 공유할 것인가가 가장 큰 문제다. 네트워크를 통해 키를 보내다가 탈취되면 모든 보안이 무너진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Asymmetric Cryptography (비대칭키/공개키 암호화)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;정의:&lt;/b&gt; 서로 수학적으로 연결된 &lt;b&gt;공개키(Public Key)&lt;/b&gt;와 &lt;b data-index-in-node=&quot;38&quot; data-path-to-node=&quot;10,0,0&quot;&gt;개인키(Private Key)&lt;/b&gt; 쌍을 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;작동 방식:&lt;/b&gt; 누구나 알 수 있는 공개키로 암호화하면, 오직 주인만 가진 개인키로만 복호화할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;장점:&lt;/b&gt; 키 전달 문제가 해결된다. (공개키는 누구에게나 그냥 보내도 되기 때문)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,3,0&quot;&gt;단점:&lt;/b&gt; 대칭키 방식보다 훨씬 느리다. 따라서 실무에서는 비대칭키로 '대칭키'를 안전하게 전달한 뒤, 실제 데이터는 그 대칭키로 주고받는 하이브리드 방식을 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cryptographic Hash Functions (암호학적 해시 함수)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&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;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;정의:&lt;/b&gt; 임의의 길이를 가진 데이터를 고정된 길이의 짧은 값(지문과 같음)으로 변환한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;필수 특성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1. &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;12,1,0&quot;&gt;단방향성:&lt;/b&gt; 해시값에서 원래 메시지를 알아내는 것이 불가능해야 한다.&lt;/li&gt;
&lt;li&gt;2. &lt;b data-index-in-node=&quot;54&quot; data-path-to-node=&quot;12,1,0&quot;&gt;충돌 저항성:&lt;/b&gt; 서로 다른 두 메시지가 같은 해시값을 가질 확률이 극도로 낮아야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;용도:&lt;/b&gt; 데이터의 &lt;b data-index-in-node=&quot;9&quot; data-path-to-node=&quot;12,2,0&quot;&gt;무결성(Integrity)&lt;/b&gt; 확인. 파일이 1비트라도 바뀌면 해시값이 완전히 달라지기 때문에 변조 여부를 즉시 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style2&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Digital Signatures (디지털 서명)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;개념:&lt;/b&gt; 비대칭키 암호화와 해시 함수를 결합하여 만든 '전자 인장'다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;과정:&lt;/b&gt; 송신자가 메시지의 해시값을 자신의 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;14,1,0&quot;&gt;개인키&lt;/b&gt;로 암호화한다. 수신자는 송신자의 &lt;b data-index-in-node=&quot;46&quot; data-path-to-node=&quot;14,1,0&quot;&gt;공개키&lt;/b&gt;로 이를 풀어 해시값을 비교한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,0&quot;&gt;효과:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1. &lt;b data-index-in-node=&quot;8&quot; data-path-to-node=&quot;14,2,0&quot;&gt;인증:&lt;/b&gt; 송신자의 개인키로만 만들 수 있으므로 진짜 그 사람이 보냈음을 증명한다.&lt;/li&gt;
&lt;li&gt;2. &lt;b data-index-in-node=&quot;58&quot; data-path-to-node=&quot;14,2,0&quot;&gt;부인 방지:&lt;/b&gt; 보낸 사람이 나중에 &quot;내가 안 보냈다&quot;고 발뺌할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Distributed System Security (분산 시스템 보안)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 자신의 기계 내 자원만 통제할 수 있다. 분산 시스템에서는 두 가지 큰 문제에 직면한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;상대방을 믿을 수 있는가?&lt;/b&gt;: 원격 시스템이 우리의 보안 정책을 제대로 지키고 있는지 알 수 없으며, 심지어 신뢰할 수 있는 파트너를 사칭하는 공격자일 수도 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;네트워크를 믿을 수 있는가?&lt;/b&gt;: 통신 경로는 공격자가 메시지를 가로채거나, 조작하거나, 재전송(Replay)할 수 있는 '불신의 공간'이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;The Adversary and the Network (공격자와 네트워크)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 상의 공격자는 다음과 같은 일을 할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;메시지 가로채기(Eavesdropping):&lt;/b&gt; 기밀성 침해.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;메시지 변조(Alteration):&lt;/b&gt; 무결성 침해.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;재전송 공격(Replay Attack):&lt;/b&gt; 예전에 보낸 유효한 메시지(예: 송금 요청)를 복사해서 나중에 다시 보내기.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0&quot;&gt;서비스 거부(DoS):&lt;/b&gt; 메시지를 삭제하거나 지연시켜 가용성 침해.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Authentication in Distributed Systems (분산 인증)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 떨어져 있는 기계들이 &quot;너 정말 그 사람 맞아?&quot;를 확인하는 방법이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;공개키 인증서 (Digital Certificates):&lt;/b&gt; 상대방의 공개키가 진짜인지 확인하기 위해 신뢰할 수 있는 제3자(CA, Certificate Authority)가 서명한 보증서이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;X.509 표준:&lt;/b&gt; 우리가 브라우저에서 HTTPS를 쓸 때 확인하는 인증서의 표준 규격이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kerberos (커버로스)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 시스템 인증의 고전이자 표준이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;중앙 집중형 인증:&lt;/b&gt; KDC(Key Distribution Center)라는 신뢰할 수 있는 서버가 '티켓(Ticket)'을 발행한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;작동 원리:&lt;/b&gt; 사용자가 한 번 로그인하면 KDC로부터 티켓을 받고, 이 티켓을 사용해 다른 여러 서비스(파일 서버, 프린터 등)에 매번 비밀번호를 입력하지 않고도 안전하게 접근한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;Secure Channels: SSL/TLS&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 상에 암호화된 '안전한 터널'을 만드는 기술이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;과정:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1. &lt;b data-index-in-node=&quot;7&quot; data-path-to-node=&quot;18,0,0&quot;&gt;Handshake:&lt;/b&gt; 사용할 암호 알고리즘을 합의하고 인증서를 확인한다.&lt;/li&gt;
&lt;li&gt;2. &lt;b data-index-in-node=&quot;51&quot; data-path-to-node=&quot;18,0,0&quot;&gt;Key Exchange:&lt;/b&gt; 비대칭키를 이용해 대칭키(세션키)를 안전하게 교환한다.&lt;/li&gt;
&lt;li&gt;3. &lt;b data-index-in-node=&quot;100&quot; data-path-to-node=&quot;18,0,0&quot;&gt;Data Transfer:&lt;/b&gt; 이후 실제 데이터는 빠른 대칭키로 암호화하여 주고받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/233</guid>
      <comments>https://gowoong.tistory.com/233#entry233comment</comments>
      <pubDate>Tue, 10 Feb 2026 13:27:50 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 22주차 Security Access</title>
      <link>https://gowoong.tistory.com/232</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 도입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 우리의 보안 목표가 무엇인지 알고 있고, 우리가 강제하고자 하는 보안 정책에 대한 최소한의 일반적인 감을 잡았으며, 우리의 정책을 위반할 수도(혹은 위반하지 않을 수도) 있는 다양한 시스템 서비스를 요청하는 주체가 누구인지에 대한 증거를 가지고 있다. 이제 우리는 그 정보를 가져와서 소프트웨어가 우리를 대신해 수행할 수 있는 실질적으로 실행 가능한 무언가로 전환해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 두 가지 중요한 단계가 있다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;해당 요청이 우리의 보안 정책 내에 적합한지 판단한다.&lt;/li&gt;
&lt;li&gt;적합하다면 작업을 수행하고, 그렇지 않다면 작업이 수행되지 않도록 확실히 보장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 단계를 일반적으로 &lt;b&gt;접근 제어(Access Control)&lt;/b&gt;라고 부른다. 우리는 어떤 시스템 자원이나 서비스가 어떤 주체에 의해, 어떤 방식으로, 어떤 상황에서 접근될 수 있는지 결정할 것이다. 기본적으로 이것은 컴퓨팅 패러다임에 잘 맞는 이진 결정(Yes 또는 No)으로 귀결된다.&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;6&quot; data-ke-style=&quot;style3&quot;&gt;이 섹션은 접근 제어가 단순히 &quot;허용하느냐 마느냐&quot;의 결정임을 강조한다. 예시로 &lt;br /&gt;open(&quot;/var/foo&quot;, O_RDWR)&lt;br /&gt;시스템 콜을 호출했을 때, OS는 내부적으로 이 요청을 승인할지 거부할지 결정해야 한다. 이때 기준이 되는 것이 바로 &lt;b&gt;접근 제어 결정(Access Control Decision)&lt;/b&gt;이다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;2 The Access Control Matrix (접근 제어 행렬)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 결정을 내리는 가장 간단하고 개념적인 방법은 &lt;b data-index-in-node=&quot;32&quot; data-path-to-node=&quot;10&quot;&gt;접근 제어 행렬&lt;/b&gt;을 사용하는 것이다. 이 행렬의 행(Row)은 접근을 요청하는 주체(사용자 등)를 나타내고, 열(Column)은 접근 대상이 되는 객체(파일 등)를 나타낸다. 각 칸(Entry)에는 해당 주체가 해당 객체에 대해 수행할 수 있는 권한이 나열된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;구조:&lt;/b&gt; 행(User 1, User 2...) &amp;times; 열(File A, File B...).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;권한:&lt;/b&gt; 칸 안에 'Read', 'Write', 'Execute' 등이 들어간다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;한계:&lt;/b&gt; 개념적으로는 완벽하지만 실무적으로는 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;12,2,0&quot;&gt;희소성(Sparsity)&lt;/b&gt; 문제가 발생한다. 수만 명의 사용자와 수백만 개의 파일이 있는 시스템에서 대부분의 칸은 비어 있게 되어 메모리 낭비가 엄청나다. 따라서 OS는 이를 효율적으로 저장하기 위한 다른 방식을 택한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3 Access Control Lists (ACL, 접근 제어 리스트)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬을 효율적으로 저장하는 첫 번째 방법은 &lt;b&gt;열(Column)&lt;/b&gt;을 따라 쪼개는 것이다. 각 객체(파일)와 연관된 리스트를 저장하는데, 이를 접근 제어 리스트(ACL)라고 한다. 각 리스트의 항목은 주체와 해당 주체가 그 객체에 대해 가진 권한을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;저장 방식:&lt;/b&gt; 파일 자체에 &quot;이 파일을 읽을 수 있는 사람들의 명단&quot;을 붙여둔다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;UNIX의 예:&lt;/b&gt; ls -l 명령어로 보는 권한(-rwxr-xr-x)은 아주 단순화된 형태의 ACL이다. 현대적인 시스템은 특정 유저나 그룹을 지정할 수 있는 더 정교한 ACL을 지원한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,2,0&quot;&gt;장점:&lt;/b&gt; 특정 파일의 보안 상태를 확인하거나 변경하기 매우 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4 Capabilities (권한 기반 시스템)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬을 저장하는 두 번째 방법은 &lt;b&gt;행(Row)&lt;/b&gt;을 따라 쪼개는 것이다. 각 주체는 자신이 접근할 수 있는 객체와 그에 대한 권한 목록을 가진다. 이를 권한(Capability) 또는 케이퍼빌리티 리스트라고 부른다. 이것은 마치 사용자가 가지고 다니는 '티켓'이나 '열쇠'와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,0,0&quot;&gt;저장 방식:&lt;/b&gt; 사용자가 자산에 대한 입장권을 주머니에 넣고 다니는 방식이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,1,0&quot;&gt;장점:&lt;/b&gt; &quot;이 유저가 무엇을 할 수 있는가?&quot;를 파악하기 매우 빠르다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22,2,0&quot;&gt;단점 (중요):&lt;/b&gt; &lt;b&gt;권한 취소(Revocation)&lt;/b&gt;가 매우 어렵다. 특정 파일에 대한 접근권을 뺏으려면, 그 파일의 티켓을 가진 모든 유저의 주머니를 뒤져서 티켓을 회수해야 하기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5 Roles and Groups (역할과 그룹)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수천 명의 사용자를 개별적으로 관리하는 것은 비효율적이다. 그래서 우리는 &lt;b&gt;그룹(Groups)&lt;/b&gt;이나 &lt;b data-index-in-node=&quot;64&quot; data-path-to-node=&quot;25&quot;&gt;역할(Roles)&lt;/b&gt; 개념을 사용한다. 접근 제어 결정은 이제 특정 개인(Alice)이 아니라 특정 역할(Engineer)에 대해 내려진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0,0&quot;&gt;RBAC (Role-Based Access Control):&lt;/b&gt; 현대 기업용 소프트웨어의 표준이다. 유저를 역할에 할당하고, 역할에 권한을 부여함으로써 관리 포인트를 획기적으로 줄인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6 Multilevel Security (다단계 보안)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로는 단순히 &quot;누구인가&quot;를 넘어 더 엄격한 보안 모델이 필요하다. 군대나 정보 기관에서 쓰이는 &lt;b&gt;다단계 보안(MLS)&lt;/b&gt;이 그 예다. 이는 데이터에 비밀 등급(Clearance)을 매긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,0,0&quot;&gt;No Read Up (Simple Security Property):&lt;/b&gt; 낮은 등급의 유저는 높은 등급의 데이터를 읽을 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;31,1,0&quot;&gt;No Write Down (&lt;span data-index-in-node=&quot;15&quot; data-math=&quot;\star&quot;&gt;$\star$&lt;/span&gt;-Property):&lt;/b&gt; 높은 등급의 유저는 정보를 보호하기 위해 낮은 등급의 객체에 글을 쓸 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7 Design Principles (설계 원칙)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 제어를 구현할 때 반드시 지켜야 할 원칙들이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;35&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,0,0&quot;&gt;완전한 중재 (Complete Mediation):&lt;/b&gt; 어떤 자원에 접근하든 예외 없이 반드시 접근 제어 검사를 거쳐야 한다. 캐싱된 권한 정보가 보안 사고의 원인이 되기도 하다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,1,0&quot;&gt;최소 권한 (Least Privilege):&lt;/b&gt; 사용자나 프로세스는 업무를 수행하는 데 필요한 최소한의 권한만 가져야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/232</guid>
      <comments>https://gowoong.tistory.com/232#entry232comment</comments>
      <pubDate>Tue, 3 Feb 2026 12:32:34 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 21주차 Security</title>
      <link>https://gowoong.tistory.com/231</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;OSTEP의 최신? 버전에 포함된 Security를 학습하기로 스터디에서 결정했다. 내가 가진 책에는 포함된 내용이 아니라 &lt;a href=&quot;https://pages.cs.wisc.edu/~remzi/OSTEP/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pages.cs.wisc.edu/~remzi/OSTEP/&lt;/a&gt; PDF에서 제공하는 자료로 학습을 진행한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 복숭아로 이해하는 보안의 3대 요소 (CIA)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OSTEP에서는&amp;nbsp;&lt;b&gt;보안의 3대 요소&lt;/b&gt;를 복숭아에 비유해 설명한다. 우리가 가진 소중한 자원을 복숭아라고 생각한다면 보안은 다음 세가지를 지키는 것이라고 한다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size20&quot;&gt;1. 기밀성 (Confidentiality)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;비유:&lt;/b&gt; 내가 잠시 고개를 돌린 사이, 누군가 내 복숭아를 훔쳐가는 것을 원치 않는 것.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,0&quot;&gt;정의:&lt;/b&gt; 오직 인가된 사용자나 시스템만이 자산에 접근할 수 있도록 보장하는 것.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,2,0&quot;&gt;컴퓨터에서:&lt;/b&gt; 개인의 비밀번호, 신용카드 정보, 민감한 개인 파일 등을 허락받지 않은 사람이 읽지 못하게 보호하는 기능이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size20&quot;&gt;2. 무결성 (Integrity)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;비유:&lt;/b&gt; 내가 잠시 고개를 돌린 사이, 누군가 내 싱싱한 복숭아를 맛없는 '순무'로 바꿔치기해 놓는 것을 원치 않는 것.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;정의:&lt;/b&gt; 자산이 인가되지 않은 방식으로 수정되거나 파괴되지 않도록 데이터의 정확성과 완전성을 보장하는 것.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;컴퓨터에서:&lt;/b&gt; 누군가 몰래 파일의 내용을 고치거나, 전송 중인 데이터를 변조하지 못하게 막는 것을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size20&quot;&gt;3. 가용성 (Availability)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;비유:&lt;/b&gt; 내가 복숭아를 먹으려고 손을 뻗을 때마다, 누군가 내 손을 때려서 방해하는 것을 원치 않는 것.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;정의:&lt;/b&gt; 인가된 사용자가 자산을 필요로 할 때 언제든지 지연 없이 사용할 수 있도록 보장하는 것.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;컴퓨터에서:&lt;/b&gt; 서비스 거부 공격(DoS)처럼 시스템을 마비시켜 정당한 사용자가 컴퓨터 자원을 쓰지 못하게 만드는 상황을 방지하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;보안(Security) vs 신뢰성(Reliability)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;많은 학생이 이 보안의 개념을 이전 장에서 배운 &lt;b&gt;'신뢰성(Reliability)'&lt;/b&gt;과 혼동하곤 한다. 하지만 교수는 이 둘 사이에 결정적인 차이가 있다고 강조한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;신뢰성:&lt;/b&gt; &quot;사고나 실수&quot;에 관한 것다. 계획이 부족하거나 우연히 발생한 오류로부터 시스템을 보호하는 일.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;보안:&lt;/b&gt; &lt;b&gt;&quot;누군가 의도적으로 당신의 복숭아를 노리고 있다!&quot;&lt;/b&gt;는 점이 다르다. 보안의 세계에는 단순한 오류가 아니라, 당신의 시스템을 망가뜨리려는 &lt;b&gt;영리하고, 악의적이며, 끈질기고, 교활한 '인간 공격자(Adversary)'&lt;/b&gt;가 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;적대적 공격자(Adversary)의 특징&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;보안 설계가 어려운 이유는 우리가 상대해야 할 적이 다음과 같은 특성을 가졌기 때문이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;지능적(Clever):&lt;/b&gt; 시스템의 허점을 찾아내는 데 매우 영리하다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;악의적(Malevolent):&lt;/b&gt; 분명히 해를 끼칠 의도를 가지고 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,2,0&quot;&gt;끈질김(Persistent):&lt;/b&gt; 한 번 실패해도 포기하지 않고 계속 시도한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,3,0&quot;&gt;유연함(Flexible):&lt;/b&gt; 방어책이 생기면 새로운 공격 경로를 찾아낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 운영체제 보안 입문(Introduction to Operating System Security)&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운영체제 보안이 왜 중요한가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&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;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;기초가 흔들리면 다 무너진다:&lt;/b&gt; 아무리 훌륭하고 안전한 애플리케이션을 만들었더라도, 그 밑바탕인 운영체제가 취약하면 공격자는 운영체제를 통해 정보를 훔치거나 프로그램을 중단시킬 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;모든 것은 OS 위에서 실행된다:&lt;/b&gt; 파일 시스템, 메모리 관리, 네트워크 통신 등 모든 자원을 OS가 제어하기 때문에, OS 보안이 뚫리면 시스템 전체의 통제권을 잃게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;보안의 3대 핵심 목표 (The Goals of Security)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;기밀성(Confidentiality):&lt;/b&gt; 인가되지 않은 사용자에게 정보가 노출되지 않도록 하는 것 (예: 파일 접근 권한 설정).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;무결성(Integrity):&lt;/b&gt; 인가되지 않은 사용자가 데이터를 수정하거나 파괴하지 못하도록 보장하는 것 (예: 시스템 파일 변조 방지).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;가용성(Availability):&lt;/b&gt; 정당한 사용자가 필요할 때 시스템 자원을 즉시 사용할 수 있도록 보장하는 것 (예: DoS 공격 방어).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;보안 정책(Policy) vs 보안 메커니즘(Mechanism)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;보안 정책(Policy):&lt;/b&gt; &quot;무엇을 보호해야 하는가?&quot;에 대한 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;12,0,0&quot;&gt;결정&lt;/b&gt;&amp;nbsp;(예: &quot;관리자만 이 파일을 수정할 수 있다.&quot;)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;보안 메커니즘(Mechanism):&lt;/b&gt; 정책을 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;12,1,0&quot;&gt;실행&lt;/b&gt;하기 위한 구체적인 방법 (예: 액세스 제어 리스트(ACL), 암호화 등)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,2,0&quot;&gt;결론:&lt;/b&gt; 훌륭한 운영체제는 다양한 정책을 유연하게 수용할 수 있는 강력한 메커니즘을 제공해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;보안 설계 원칙 (Design Principles for Security)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;Saltzer와 Schroeder가 제안한 고전적이지만 여전히 유효한 원칙들을 소개한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;최소 권한의 원칙 (Least Privilege):&lt;/b&gt; 사용자나 프로세스는 업무 수행에 꼭 필요한 최소한의 권한만 가져야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;경제적 설계 (Economy of Mechanism):&lt;/b&gt; 보안 메커니즘은 단순하고 작아야 검증하기 쉽다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,2,0&quot;&gt;개방적 설계 (Open Design):&lt;/b&gt; 시스템의 보안이 설계의 비밀에 의존해서는 안 된다 (알고리즘은 공개되어도 안전해야 함).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,3,0&quot;&gt;완전한 중재 (Complete Mediation):&lt;/b&gt; 모든 자원 접근은 반드시 보안 검사를 거쳐야 하며, 예외가 있어서는 안 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;적대적 환경 (The Adversarial Environment)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;보안이 일반적인 소프트웨어 공학(Reliability)과 다른 점은 바로 &lt;b&gt;'인간 공격자(Adversary)'&lt;/b&gt;의 존재다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공격자는 시스템의 허점을 찾아내기 위해 수단과 방법을 가리지 않으며, 방어자가 생각하지 못한 창의적인 방식으로 접근한다.&lt;/li&gt;
&lt;li&gt;따라서 보안 설계는 &quot;실수로 발생할 오류&quot;뿐만 아니라 &quot;악의적인 의도를 가진 공격&quot;을 가정한 상태에서 이루어져야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 인증(Authentication): 시스템의 첫 번째 관문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제가 보안 정책을 실행하기 위해 가장 먼저 알아야 할 것은 &lt;b&gt;&quot;요청을 보낸 주체가 누구인가?&quot;&lt;/b&gt;다. 관리자가 보낸 요청은 허용해야 하지만, 이름 모를 해커의 요청은 거부해야 하기 때문이다. 이 확인 과정을 &lt;b&gt;인증(Authentication)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;인증의 핵심: 맥락(Context)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;운영체제는 요청 그 자체보다 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;5&quot;&gt;'누가'&lt;/b&gt; 요청했는지를 중요하게 여긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;예시:&lt;/b&gt; 가족이 우유를 사 오라고 하면 사 오지만, 길 가던 모르는 사람이 시키면 거절하는 것과 같다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;OS에서의 적용:&lt;/b&gt; 시스템 관리자가 프로그램을 설치하면 허용하지만, 출처 불명의 스크립트가 설치를 시도하면 차단해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;무엇을 통해 증명하는가? (Three Factors)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;인증은 보통 다음 세 가지 중 하나 이상의 요소를 통해 이루어진다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;지식(Something you know):&lt;/b&gt; 비밀번호, PIN 번호 등.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;소유(Something you have):&lt;/b&gt; 스마트카드, 보안 키(Yubikey), OTP 생성기 등.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;존재(Something you are):&lt;/b&gt; 지문, 홍채, 얼굴 인식 등 생체 인식.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;비밀번호(Password) 방식의 명암&lt;/h3&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;가장 보편적이지만 가장 취약한 방식이기도 하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;취약점:&lt;/b&gt; 사용자는 외우기 쉬운 짧은 비번을 선호하고, 여러 사이트에서 돌려쓰는 경향이 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;공격 방식:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;9&quot; data-path-to-node=&quot;12,1,0&quot;&gt;사전 공격(Dictionary Attack):&lt;/b&gt; 흔히 쓰이는 단어를 대입.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,1,0,0&quot;&gt;무차별 대입(Brute-force):&lt;/b&gt; 모든 조합을 시도.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,1,1,0&quot;&gt;사회 공학(Social Engineering):&lt;/b&gt; 속임수를 써서 비번을 알아냄.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;운영체제의 비밀번호 보호: 해싱(Hashing)과 솔팅(Salting)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;운영체제는 비밀번호를 그대로 저장하지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;해싱(Hashing):&lt;/b&gt; 비번을 복구 불가능한 암호문으로 변환하여 저장한다. 서버가 뚫려도 원래 비번은 알 수 없게 하기 위함이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;솔팅(Salting):&lt;/b&gt; 같은 비번이라도 사용자마다 고유의 '소금(Salt)' 값을 추가해 해싱한다. 이는 미리 계산된 해시 테이블(Rainbow Table)을 이용한 공격을 무력화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;다중 요소 인증 (MFA / 2FA)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;비밀번호(지식)가 뚫리더라도 다른 요소(소유 또는 존재)가 필요하게 설계하여 보안성을 획기적으로 높이는 방식이다. 현대의 모든 보안 시스템이 지향하는 표준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/231</guid>
      <comments>https://gowoong.tistory.com/231#entry231comment</comments>
      <pubDate>Tue, 20 Jan 2026 12:37:38 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 20주차 Andrew File System (AFS)</title>
      <link>https://gowoong.tistory.com/224</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;분산 파일 시스템의 고전인 NFS가 '단순함'을 택했다면, 카네기 멜론 대학교(CMU)에서 개발한 &lt;b data-index-in-node=&quot;55&quot; data-path-to-node=&quot;3&quot;&gt;AFS&lt;/b&gt;는 수천 대의 클라이언트를 수용할 수 있는 &lt;b&gt;'확장성(Scalability)'&lt;/b&gt;에 모든 사활을 걸었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;1. AFS의 핵심 설계 철학: 확장성 (Scalability)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;NFSv2는 클라이언트가 늘어날수록 서버에 &quot;이 파일 최신이야?&quot;라고 묻는 요청(GETATTR)이 폭증하여 서버가 마비되는 문제가 있었다. AFS 연구진은 다음 두 가지 질문에 집중했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;&quot;어떻게 하면 서버 한 대가 더 많은 클라이언트를 감당할 수 있을까?&quot;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;&quot;클라이언트가 최대한 스스로 일을 처리하게 할 순 없을까?&quot;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;2. 전체 파일 캐싱 (Whole-file Caching)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;AFS의 가장 큰 특징은 데이터 접근 방식입니다. 블록 단위로 가져오는 NFS와 달리 AFS는 파일 단위로 움직인다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;Open 시:&lt;/b&gt; 클라이언트가 파일을 열면, 서버로부터 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;11,0,0&quot;&gt;파일 전체&lt;/b&gt;를 가져와 클라이언트의 &lt;b data-index-in-node=&quot;47&quot; data-path-to-node=&quot;11,0,0&quot;&gt;로컬 디스크&lt;/b&gt;에 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;사용 중:&lt;/b&gt; 이후의 모든 read(), write() 작업은 네트워크를 타지 않고 &lt;b data-index-in-node=&quot;45&quot; data-path-to-node=&quot;11,1,0&quot;&gt;로컬 디스크&lt;/b&gt;에서만 일어난다. (매우 빠름!)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;Close 시:&lt;/b&gt; 파일이 수정되었다면 그제야 서버로 파일을 다시 업로드(Flush)한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size26&quot;&gt;3. 혁신적인 캐시 일관성: 콜백 (Callbacks)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;NFS 클라이언트가 의심이 많아 계속 서버에 물어보는 스타일이라면, AFS는 &lt;b&gt;'서버의 약속'&lt;/b&gt;을 믿는 스타일이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;Callback의 원리:&lt;/b&gt; 서버는 클라이언트에게 파일을 줄 때 &lt;b&gt;&quot;이 파일이 수정되면 내가 반드시 너한테 먼저 연락할게&quot;&lt;/b&gt;라는 약속(Callback Promise)을 함께 준다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;장점:&lt;/b&gt; 클라이언트는 서버가 연락(Callback Break)을 주기 전까지는 서버에 물어볼 필요 없이 자신의 캐시가 무조건 최신이라고 간주한다. 이로 인해 서버의 응답 부하가 거의 사라진 다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;4. AFS 버전별 발전 (v1 vs v2)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;AFS v1:&lt;/b&gt; 여전히 파일 경로를 해석(Pathname lookup)할 때 서버에 너무 많이 의존했고, 콜백이 없어 성능 한계가 있었다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;AFS v2:&lt;/b&gt; &lt;b&gt;콜백(Callback)&lt;/b&gt;과 &lt;b&gt;파일 식별자(FID)&lt;/b&gt;를 도입하여 진정한 확장성을 완성했다. 파일 이름 대신 고유한 FID를 사용하여 경로 해석 부하를 줄였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size26&quot;&gt;5. 일관성 모델: Last Writer Wins&lt;/h2&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;AFS는 파일이 닫힐(close) 때만 서버에 반영된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시에 여러 클라이언트가 파일을 수정하면, &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;22,0,0&quot;&gt;가장 마지막에 파일을 닫은 사람의 버전&lt;/b&gt;이 최종적으로 서버에 저장된다.&lt;/li&gt;
&lt;li&gt;이는 엄격한 실시간 일관성은 아니지만, 일반적인 문서 작업이나 프로그래밍 환경에서는 충분히 효율적인 방식이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AFS는 &lt;b&gt;&quot;네트워크 비용은 비싸고 로컬 디스크는 싸다&quot;&lt;/b&gt;는 가정을 바탕으로, 클라이언트에게 권한을 위임하여 서버의 자유를 찾아준 시스템이다. 오늘날 현대적인 클라우드 스토리지 동기화 방식의 모태가 되었다는 점에서 시스템 엔지니어라면 반드시 이해해야 할 설계 모델이다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/224</guid>
      <comments>https://gowoong.tistory.com/224#entry224comment</comments>
      <pubDate>Tue, 13 Jan 2026 14:15:39 +0900</pubDate>
    </item>
    <item>
      <title>application.yaml 속성 (자주 사용되는 것만 정리)</title>
      <link>https://gowoong.tistory.com/222</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트에서 데이터 베이스 설정이나 여러 설정을 하는 applicaion.yaml 에는 수천 개의 속성이 존재하는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/appendix/application-properties/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-boot/appendix/application-properties/index.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767857337954&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Common Application Properties :: Spring Boot&quot; data-og-description=&quot;&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/appendix/application-properties/index.html&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/appendix/application-properties/index.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/appendix/application-properties/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/appendix/application-properties/index.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Common Application Properties :: Spring Boot&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많아도 너무 많다 그런데 그 걸 다 알고 있는 것은 말도 안 되는 일이다. 실제 현업 프로젝트에서도 자주 사용되는 속성은 몇 개 안 될 것 같다. 그렇다면 일단 자주 사용되는 속성을 몇 개 정리해 두고 필요할 때 다시 꺼내 보는 용도로 정리를 하려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-start-index=&quot;232&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span data-start-index=&quot;232&quot;&gt;1. 기본 서버 설정&lt;/span&gt;&lt;/h2&gt;
&lt;div data-start-index=&quot;257&quot;&gt;&lt;span data-start-index=&quot;257&quot;&gt;가장 기본이 되는 애플리케이션 식별 정보와, 최신 Java 버전을 사용할 때 성능을 극대화하기 위한 설정&lt;/span&gt;&lt;/div&gt;
&lt;pre id=&quot;code_1767857531169&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  port: 8080                 # 기본 포트 설정 (충돌 시 변경)
  shutdown: graceful         # 배포 중단 시 처리 중인 요청을 완료하고 종료 (우아한 종료)

spring:
  application:
    name: my-api-service     # 로깅이나 모니터링 시 식별을 위한 앱 이름
  threads:
    virtual:
      enabled: true          # Java 21+ 사용 시 필수. 가상 스레드를 활성화하여 동시성 처리 성능 극대화 [4-6]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;graceful shutdown은 프로세스 종료 시 처리하고 있는 요청을 끝까지 처리할 수 있는 기능이라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-start-index=&quot;723&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span data-start-index=&quot;723&quot;&gt;2. 데이터베이스 및 JPA (가장 많이 쓰이는 조합)&lt;/span&gt;&lt;/h2&gt;
&lt;div data-start-index=&quot;753&quot;&gt;&lt;span data-start-index=&quot;753&quot;&gt;DB 연결은 &lt;b&gt;로컬(개발)&lt;/b&gt;과 &lt;/span&gt;&lt;b data-start-index=&quot;772&quot;&gt;운영(Prod)&lt;/b&gt;&lt;span data-start-index=&quot;780&quot;&gt; 환경에서 설정이 달라지는 대표적인 부분이다.&lt;/span&gt;&lt;/div&gt;
&lt;h4 data-start-index=&quot;806&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span data-start-index=&quot;806&quot;&gt;A. 로컬 개발용 (H2 인메모리 DB)&lt;/span&gt;&lt;/h4&gt;
&lt;div data-start-index=&quot;828&quot;&gt;&lt;span data-start-index=&quot;828&quot;&gt;빠른 개발과 테스트를 위해 콘솔을 활성화하고 SQL을 눈으로 확인하는 설정이다.&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring:
  datasource:
    url: jdbc:h2:mem:testdb  # 인메모리 DB 주소
    username: sa
    password:                # 보통 공란
    driver-class-name: org.h2.Driver
  h2:
    console:
      enabled: true          # 브라우저에서 H2 콘솔 접근 허용 (/h2-console)
  jpa:
    hibernate:
      ddl-auto: create-drop  # 시작 시 테이블 생성, 종료 시 삭제
    show-sql: true           # 실행되는 SQL을 콘솔에 출력
    properties:
      hibernate:
        format_sql: true     # SQL을 보기 좋게 줄바꿈하여 출력&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-start-index=&quot;1338&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span data-start-index=&quot;1338&quot;&gt;B. 운영 환경용 (MySQL/PostgreSQL)&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring:
  datasource:
    url: jdbc:postgresql://prod-db-url:5432/mydb
    driver-class-name: org.postgresql.Driver
    username: myuser
    password: mypassword
  jpa:
    hibernate:
      ddl-auto: validate     # 운영에서는 절대 create/update를 쓰지 않고 스키마 검증만 수행
    open-in-view: false      # 트랜잭션 범위 밖에서 DB 커넥션을 점유하지 않도록 설정 (성능 최적화)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-start-index=&quot;1906&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span data-start-index=&quot;1906&quot;&gt;3. 로깅 (Logging) 및 모니터링&lt;/span&gt;&lt;/h2&gt;
&lt;div data-start-index=&quot;1928&quot;&gt;&lt;span data-start-index=&quot;1928&quot;&gt;최신 Spring Boot 3.4부터는 &lt;b&gt;구조화된 로깅(Structured Logging)&lt;/b&gt;을 지원하여 로그 분석 시스템(ELK 등)과 연동하기 쉬워졌다.&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;logging:
  level:
    root: INFO               # 기본 로깅 레벨
    com.myapp: DEBUG         # 내 패키지는 디버그 레벨로 상세하게
    org.springframework.web: INFO
    org.hibernate.SQL: DEBUG # 쿼리 확인용 (운영에서는 INFO 권장)
  
  # Spring Boot 3.4+ 기능: JSON 로그 출력 (운영 환경 추천)
  structured:
    format:
      console: ecs           # Elastic Common Schema 포맷의 JSON 로그 출력&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-start-index=&quot;2461&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span data-start-index=&quot;2461&quot;&gt;4. API 에러 처리 및 문서화 (Swagger)&lt;/span&gt;&lt;/h2&gt;
&lt;div data-start-index=&quot;2489&quot;&gt;&lt;span data-start-index=&quot;2489&quot;&gt;REST API 개발 시 클라이언트에게 명확한 에러를 전달하고 문서를 자동화하는 설정이다.&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring:
  mvc:
    problemdetails:
      enabled: true          # RFC 7807 표준 에러 응답(ProblemDetail) 자동 활성화

springdoc:                   # Swagger/OpenAPI 설정
  api-docs:
    path: /api-docs          # OpenAPI JSON 문서 경로
  swagger-ui:
    path: /swagger-ui.html   # Swagger UI 접속 경로
    enabled: true            # 운영 환경에서는 false로 꺼두기도 함&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-start-index=&quot;2976&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span data-start-index=&quot;2976&quot;&gt;5. Actuator (운영 모니터링)&lt;/span&gt;&lt;/h2&gt;
&lt;div data-start-index=&quot;2997&quot;&gt;&lt;span data-start-index=&quot;2997&quot;&gt;애플리케이션의 상태를 체크하기 위해 &lt;/span&gt;Actuator&lt;span data-start-index=&quot;3025&quot;&gt;를 사용할 때 주로 쓰는 설정이다. 보안상 필요한 것만 노출해야 한다.&lt;/span&gt;&lt;/div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;management:
  endpoints:
    web:
      exposure:
        include: &quot;health, info, metrics, prometheus&quot; # 노출할 엔드포인트 목록
  endpoint:
    health:
      show-details: always # ⚠️ 주의: 운영 환경에서는 보안을 위해 'never' 또는 'when_authorized' 권장&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;6. 환경별 설정 분리 (Profiles)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot 2.4 버전부터는 spring.config.activate.on-profile 속성을 사용하여 하나의 파일(application.yml) 안에서 논리적으로 설정을 분리하거나, 파일 자체를 나누어 관리하는 것을 권장한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;설정 방법: Multi-Document (--- 구분자 사용)&lt;/b&gt; 하나의 YAML 파일 안에서 --- (하이픈 3개)를 사용하여 문서를 나누고, 각 블록이 어떤 환경(Profile)에서 동작할지 지정하는 방식이다. 한눈에 모든 설정을 볼 수 있어 관리가 편리하다.&lt;/p&gt;
&lt;pre id=&quot;code_1767859169586&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# --------------------------------------------------------
# 1. 공통 설정 (Common)
# 모든 환경에 기본적으로 적용되는 설정
# --------------------------------------------------------
spring:
  application:
    name: my-api-service
  profiles:
    active: local            # 기본적으로 활성화할 프로필 (default: local)

---
# --------------------------------------------------------
# 2. 로컬 개발 환경 (Local)
# 'local' 프로필이 활성화되었을 때만 적용
# --------------------------------------------------------
spring:
  config:
    activate:
      on-profile: local      # [핵심 속성] 이 설정이 적용될 프로필 지정
  datasource:
    url: jdbc:h2:mem:testdb
  jpa:
    hibernate:
      ddl-auto: create-drop

---
# --------------------------------------------------------
# 3. 운영 환경 (Prod)
# 'prod' 프로필이 활성화되었을 때만 적용
# --------------------------------------------------------
spring:
  config:
    activate:
      on-profile: prod       # [핵심 속성] prod 프로필일 때만 동작
  datasource:
    url: jdbc:postgresql://prod-db:5432/mydb
  jpa:
    hibernate:
      ddl-auto: validate
    open-in-view: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;실행 방법 (운영 배포 시)&lt;/b&gt; JAR 파일을 실행할 때, 환경 변수나 인자값으로 활성 프로필을 바꿔주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1767859215885&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 운영 환경 프로필(prod)로 실행
java -jar my-app.jar --spring.profiles.active=prod&lt;/code&gt;&lt;/pre&gt;</description>
      <category>백엔드 개발/자바 스프링</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/222</guid>
      <comments>https://gowoong.tistory.com/222#entry222comment</comments>
      <pubDate>Thu, 8 Jan 2026 16:47:20 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 19주차 Part.2 : 네트워크 파일 시스템(NFS)</title>
      <link>https://gowoong.tistory.com/221</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;분산 파일 시스템은 여러 대의 머신이 &lt;b&gt;하나의 파일 시스템을 함께 쓰는 것&lt;/b&gt;을 목표로 한다. 한 머신에서 보던 파일과 디렉터리를 다른 머신에서도 같은 방식으로 접근하게 만들어 &amp;ldquo;어디서 접속하든 같은 파일을 쓰는 환경&amp;rdquo;을 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오후 1.18.27.png&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/plU4Z/dJMcabQpvG3/eDS65Vj9T0JPO5xaQyZz0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/plU4Z/dJMcabQpvG3/eDS65Vj9T0JPO5xaQyZz0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/plU4Z/dJMcabQpvG3/eDS65Vj9T0JPO5xaQyZz0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FplU4Z%2FdJMcabQpvG3%2FeDS65Vj9T0JPO5xaQyZz0k%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;670&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2026-01-06 오후 1.18.27.png&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;309&quot; data-start=&quot;159&quot; data-ke-size=&quot;size16&quot;&gt;이 방식의 가장 큰 가치는 &lt;b&gt;공유&lt;/b&gt;에 있다. 파일이 특정 클라이언트의 로컬 디스크에 갇히지 않고 서버 쪽에 놓이므로, 여러 클라이언트가 같은 데이터를 기준으로 작업하게 된다. 결과적으로 팀 단위 작업이나 여러 머신을 오가며 일하는 환경에서 데이터가 흩어지지 않는다.&lt;/p&gt;
&lt;p data-end=&quot;410&quot; data-start=&quot;311&quot; data-ke-size=&quot;size16&quot;&gt;운영 측면에서도 이점이 생긴다. 데이터가 서버에 모이므로 관리가 중앙집중화된다. 백업 같은 작업을 모든 클라이언트에서 각각 처리하지 않고, 소수의 서버를 대상으로 수행하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;493&quot; data-start=&quot;412&quot; data-ke-size=&quot;size16&quot;&gt;보안 측면의 동기도 존재한다. 데이터가 저장된 서버를 물리적으로 통제된 장소에 두면, 물리적 접근을 통한 위험을 줄이는 방향의 운영이 가능해진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;46&quot; data-start=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;1. 기본적인 분산 파일 시스템&lt;/h2&gt;
&lt;p data-end=&quot;436&quot; data-start=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;분산 파일 시스템은 여러 클라이언트와 하나(또는 소수)의 서버로 구성되며 서버가 디스크에 데이터를 저장하고 클라이언트가 잘 정의된 프로토콜 메시지로 파일과 디렉터리에 접근한다.&lt;br /&gt;이 구조를 쓰는 가장 큰 이유는 클라이언트들 사이에서 데이터 공유를 쉽게 하기 위해서이며 한 머신에서 보던 파일 시스템의 &amp;ldquo;같은 관점&amp;rdquo;을 다른 머신에서도 그대로 보게 한다.&lt;br /&gt;추가로 서버 쪽에 데이터를 모아 두면 백업 같은 관리 작업을 중앙에서 수행하기 쉬워지고 서버를 물리적으로 보호하면 보안상 이점도 생긴다.&lt;/p&gt;
&lt;p data-end=&quot;721&quot; data-start=&quot;438&quot; data-ke-size=&quot;size16&quot;&gt;단순한 클라이언트/서버 분산 파일 시스템에서 클라이언트 애플리케이션은 클라이언트 측 파일 시스템을 통해 open(), read(), write(), close(), mkdir() 같은 시스템콜로 서버에 있는 파일을 접근한다.&lt;br /&gt;이때 애플리케이션 입장에서는 성능 차이를 제외하면 로컬 디스크 파일 시스템과 다르게 보이지 않게 &amp;ldquo;투명한 접근&amp;rdquo;을 제공하는 것이 목표가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-06 오후 1.21.17.png&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MIncF/dJMcafZzI4j/kV5wpkK7B76WRttfE3Xe50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MIncF/dJMcafZzI4j/kV5wpkK7B76WRttfE3Xe50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MIncF/dJMcafZzI4j/kV5wpkK7B76WRttfE3Xe50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMIncF%2FdJMcafZzI4j%2FkV5wpkK7B76WRttfE3Xe50%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;928&quot; height=&quot;236&quot; data-filename=&quot;스크린샷 2026-01-06 오후 1.21.17.png&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1235&quot; data-start=&quot;723&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트 측 파일 시스템의 역할은 시스템콜을 처리하기 위해 필요한 동작을 수행하는 것이며 예를 들어 read() 요청이 오면 서버(파일 서버)로 &amp;ldquo;특정 블록을 읽어 달라&amp;rdquo;는 메시지를 보낼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;1235&quot; data-start=&quot;723&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;파일 서버는 디스크(또는 서버의 메모리 캐시)에서 데이터를 읽어 클라이언트에 응답하고 클라이언트 측 파일 시스템은 그 데이터를 사용자 버퍼에 복사해 read()를 완료한다. 같은 블록을 다시 읽는 경우 클라이언트 메모리나 클라이언트 디스크에 캐시해 두면 네트워크 트래픽 없이 처리될 수도 있다.&lt;/p&gt;
&lt;p data-end=&quot;1235&quot; data-start=&quot;723&quot; data-ke-size=&quot;size16&quot;&gt;결국 분산 파일 시스템의 동작은 &amp;ldquo;클라이언트 측 파일 시스템&amp;rdquo;과 &amp;ldquo;파일 서버&amp;rdquo;라는 두 소프트웨어 구성요소의 설계와 상호작용이 좌우한다.&lt;/p&gt;
&lt;h4 data-end=&quot;1235&quot; data-start=&quot;723&quot; data-ke-size=&quot;size20&quot;&gt;서버는 왜 크래시 되는가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 크래시하는 이유로는 전원 장애처럼 물리적인 원인도 있고 거대한 코드베이스가 갖는 버그 때문에 언젠가 크래시를 유발할 수도 있다. 또한 작은 메모리 누수도 결국 메모리 고갈로 이어져 시스템 크래시를 만들 수 있다. 분산 환경에서는 네트워크가 분할되는 등 이상 동작을 하면 실제로는 원격 서버가 살아 있어도 통신이 안 되어 &amp;ldquo;크래시처럼 보이는&amp;rdquo; 상황이 생긴다고 본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. NFS에 대하여&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-5-2-thinking&quot; data-message-id=&quot;f2b7bce2-82e1-45e4-a92e-068edea1f4b4&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;2091&quot; data-start=&quot;1627&quot; data-ke-size=&quot;size16&quot;&gt;초기의 성공적인 분산 시스템 중 하나로 Sun Microsystems가 만든 Sun Network File System(NFS)이 있으며 NFS를 정의할 때 Sun은 폐쇄적이고 독점적인 시스템 대신 &amp;ldquo;오픈 프로토콜&amp;rdquo;을 만드는 방식을 택한다.&lt;br /&gt;이 프로토콜은 클라이언트와 서버가 통신할 때 쓰는 메시지 형식을 정확히 규정하며 서로 다른 집단이 각자 NFS 서버를 구현해도 상호운용성을 유지한 채 시장에서 경쟁할 수 있게 한다.&lt;br /&gt;이 &amp;ldquo;오픈 마켓&amp;rdquo; 접근은 실제로 성공했고 Oracle/Sun, NetApp, EMC, IBM 등 많은 회사가 NFS 서버를 판매하게 되었으며 NFS의 확산은 이러한 접근 덕분이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h2 data-end=&quot;166&quot; data-start=&quot;114&quot; data-ke-size=&quot;size26&quot;&gt;3. 핵심 단순하고 빠른 서버 크래시 복구&lt;/h2&gt;
&lt;p data-end=&quot;357&quot; data-start=&quot;168&quot; data-ke-size=&quot;size16&quot;&gt;NFSv2는 &lt;b&gt;서버가 크래시했을 때 빠르게 복구하는 것&lt;/b&gt;을 최우선 목표로 삼는다&lt;br /&gt;여러 클라이언트가 한 서버를 공유하는 환경에서는 서버가 멈춘 시간이 곧 모든 사용자의 생산성 손실로 이어지기 때문에, &amp;ldquo;서버가 빨리 다시 살아나는가&amp;rdquo;가 시스템 전체의 품질을 좌우한다&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;413&quot; data-start=&quot;364&quot; data-ke-size=&quot;size26&quot;&gt;4. 빠른 크래시 복구의 열쇠 : 상태를 유지하지 않음&lt;/h2&gt;
&lt;p data-end=&quot;636&quot; data-start=&quot;415&quot; data-ke-size=&quot;size16&quot;&gt;이 목표를 위해 NFSv2는 &lt;b&gt;stateless(무상태) 프로토콜&lt;/b&gt;로 설계한다&lt;br /&gt;서버는 &amp;ldquo;어떤 클라이언트가 어떤 블록을 캐시하는지&amp;rdquo;, &amp;ldquo;어떤 파일이 열려 있는지&amp;rdquo;, &amp;ldquo;파일 포인터가 어디인지&amp;rdquo; 같은 정보를 &lt;b&gt;추적하지 않는다&lt;/b&gt;&lt;br /&gt;대신 &lt;b&gt;각 요청 메시지 자체에 작업을 완료하는 데 필요한 정보를 모두 담도록&lt;/b&gt; 설계한다&lt;/p&gt;
&lt;p data-end=&quot;1028&quot; data-start=&quot;638&quot; data-ke-size=&quot;size16&quot;&gt;왜 open() 같은 stateful 방식이 문제가 되는지도 예로 든다&lt;br /&gt;만약 서버가 open()을 받아서 파일 디스크립터를 만들고 클라이언트에 돌려주는 방식이라면, 그 디스크립터는 클라이언트와 서버가 공유하는 &lt;b&gt;분산 상태(distributed state)&lt;/b&gt;가 된다 이때 서버가 크래시하면 &amp;ldquo;그 fd가 어떤 파일을 가리키는지&amp;rdquo; 같은 정보가 메모리에서 사라져 복구가 복잡해진다 또한 클라이언트가 open() 후 크래시하면 close()가 오지 않으므로 서버가 &amp;ldquo;언제 파일을 닫아도 되는지&amp;rdquo; 판단하려면 결국 클라이언트 생존을 추적해야 하는 문제가 생긴다&lt;/p&gt;
&lt;p data-end=&quot;1134&quot; data-start=&quot;1030&quot; data-ke-size=&quot;size16&quot;&gt;그래서 NFS는 각 요청이 완결적으로 동작하도록 만들고, 서버는 재시작 후에도 그냥 요청을 다시 처리하면 되게 만든다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1167&quot; data-start=&quot;1141&quot; data-ke-size=&quot;size26&quot;&gt;5. NFSv2 Protocol&lt;/h2&gt;
&lt;p data-end=&quot;1448&quot; data-start=&quot;1169&quot; data-ke-size=&quot;size16&quot;&gt;stateless하게 만들려면 &amp;ldquo;서버가 상태를 들고 있어야만 가능한 open()&amp;rdquo; 같은 호출을 프로토콜에서 직접 제공하기 어렵다&lt;br /&gt;그 대신 NFS는 &lt;b&gt;file handle&lt;/b&gt;을 핵심 식별자로 사용한다.&lt;/p&gt;
&lt;p data-end=&quot;1448&quot; data-start=&quot;1169&quot; data-ke-size=&quot;size16&quot;&gt;file handle은 보통 &lt;b&gt;(volume id, inode number, generation number)&lt;/b&gt;로 구성되며, 서버가 &amp;ldquo;어느 파일시스템의 어느 inode를 대상으로 하는 요청인지&amp;rdquo;를 즉시 알 수 있게 한다&lt;/p&gt;
&lt;p data-end=&quot;1520&quot; data-start=&quot;1450&quot; data-ke-size=&quot;size16&quot;&gt;프로토콜은 대략 다음처럼 &amp;ldquo;핸들 기반 연산&amp;rdquo;들로 구성한다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1664&quot; data-start=&quot;1521&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1564&quot; data-start=&quot;1521&quot;&gt;LOOKUP: (디렉터리 핸들, 이름) &amp;rarr; (대상 파일/디렉터리 핸들)&lt;/li&gt;
&lt;li data-end=&quot;1618&quot; data-start=&quot;1565&quot;&gt;READ/WRITE: (파일 핸들, offset, count, 데이터) 중심으로 동작한다&lt;/li&gt;
&lt;li data-end=&quot;1664&quot; data-start=&quot;1619&quot;&gt;GETATTR/SETATTR: 파일 속성(메타데이터) 조회/설정에 사용한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1955&quot; data-start=&quot;1666&quot; data-ke-size=&quot;size16&quot;&gt;LOOKUP은 경로명을 단계적으로 해석해 file handle을 얻는 데 쓰인다&lt;br /&gt;예를 들어 클라이언트가 루트(/) 디렉터리의 핸들을 이미 갖고 있다면, /foo.txt를 열기 위해 루트 핸들과 &amp;ldquo;foo.txt&amp;rdquo;를 넘겨 LOOKUP을 하고, 성공 시 foo.txt의 핸들과 속성을 받는다&lt;br /&gt;(루트 핸들은 실제로는 NFS mount 프로토콜로 얻지만 여기서는 생략한다)&lt;/p&gt;
&lt;p data-end=&quot;2223&quot; data-start=&quot;1957&quot; data-ke-size=&quot;size16&quot;&gt;READ/WRITE는 &lt;b&gt;반드시 offset과 count를 포함&lt;/b&gt;한다&lt;br /&gt;이 덕분에 서버는 &amp;ldquo;현재 파일 포인터가 어디인지&amp;rdquo; 같은 상태를 기억하지 않아도, 요청만 보고 정확히 어느 바이트를 읽고/써야 하는지 처리할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2278&quot; data-start=&quot;2230&quot; data-ke-size=&quot;size26&quot;&gt;6. 프로토콜에서 분산 파일 시스템으로&lt;/h2&gt;
&lt;p data-end=&quot;2502&quot; data-start=&quot;2280&quot; data-ke-size=&quot;size16&quot;&gt;NFS는 &amp;ldquo;프로토콜 호출&amp;rdquo;만으로 끝나지 않고, 클라이언트 쪽에서 POSIX API(open/read/write/close)를 자연스럽게 보이도록 &lt;b&gt;클라이언트 측 파일시스템이 번역 계층 역할&lt;/b&gt;을 한다.&lt;br /&gt;클라이언트 측 파일시스템은 &lt;b&gt;열린 파일 목록을 추적&lt;/b&gt;하고, 응용프로그램의 시스템콜을 적절한 NFS 요청들로 변환한다.&lt;/p&gt;
&lt;p data-end=&quot;2915&quot; data-start=&quot;2504&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 포인트는 &amp;ldquo;상태는 서버가 아니라 클라이언트가 가진다&amp;rdquo;는 점이다.&lt;br /&gt;예를 들어 read(fd, &amp;hellip;)는 시스템콜 자체로는 offset을 넘기지 않지만, 클라이언트는 &lt;b&gt;fd &amp;rarr; file handle 매핑&lt;/b&gt;과 &lt;b&gt;현재 파일 포인터(current file pointer)&lt;/b&gt;를 로컬에서 들고 있으므로, 이를 바탕으로 (핸들, offset, count) 형태의 READ 요청을 만들어 서버에 보낸다.&lt;br /&gt;경로가 길면 LOOKUP도 여러 번 나가며(/home/remzi/foo.txt면 home, remzi, foo.txt 순서), 각 요청은 서버가 처리하는 데 필요한 정보를 모두 담는다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2980&quot; data-start=&quot;2922&quot; data-ke-size=&quot;size26&quot;&gt;7. 서버의 고장을 멱등연산으로 처리하기&lt;/h2&gt;
&lt;p data-end=&quot;3216&quot; data-start=&quot;2982&quot; data-ke-size=&quot;size16&quot;&gt;여기서부터는 &amp;ldquo;서버가 크래시하거나 네트워크가 이상해져도 시스템이 계속 굴러가게 만드는 법&amp;rdquo;을 다룬다. 핵심은 &lt;b&gt;타임아웃 후 재전송&lt;/b&gt;을 기본 동작으로 두면, 서버 입장에서는 같은 요청이 &lt;b&gt;중복 도착&lt;/b&gt;할 수 있다는 점을 받아들여야 한다는 점이다 따라서 NFS는 가능한 연산을 &lt;b&gt;멱등적(idempotent)&lt;/b&gt;으로 만들거나, 멱등적으로 다루기 쉬운 방식(예: 명시적 offset 기반 WRITE)을 선호한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3273&quot; data-start=&quot;3223&quot; data-ke-size=&quot;size26&quot;&gt;8. 성능 개선하기: 클라이언트 측 캐싱&lt;/h2&gt;
&lt;p data-end=&quot;3403&quot; data-start=&quot;3275&quot; data-ke-size=&quot;size16&quot;&gt;네트워크 왕복이 많아지면 성능이 급격히 나빠지므로, NFS는 성능 개선을 위해 &lt;b&gt;클라이언트 캐싱&lt;/b&gt;을 사용한다.&lt;br /&gt;하지만 서버가 무상태이므로 &amp;ldquo;누가 무엇을 캐시 중인지&amp;rdquo;를 서버가 모르며, 이는 곧 캐시 일관성 문제로 이어진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3447&quot; data-start=&quot;3410&quot; data-ke-size=&quot;size26&quot;&gt;9. 캐시 일관성 문제&lt;/h2&gt;
&lt;p data-end=&quot;3571&quot; data-start=&quot;3449&quot; data-ke-size=&quot;size16&quot;&gt;캐싱이 들어오면 &amp;ldquo;여러 클라이언트가 동시에 접근할 때 같은 내용을 보장하는가&amp;rdquo;가 문제가 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3835&quot; data-start=&quot;3573&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3708&quot; data-start=&quot;3573&quot;&gt;&lt;b&gt;Update Visibility 문제&lt;/b&gt;: 한 클라이언트가 파일을 수정했을 때, 다른 클라이언트가 &amp;ldquo;언제부터&amp;rdquo; 그 변경을 볼 수 있는지 불명확해지는 문제를 말한다&lt;/li&gt;
&lt;li data-end=&quot;3835&quot; data-start=&quot;3709&quot;&gt;&lt;b&gt;Stale Cache 문제&lt;/b&gt;: 다른 클라이언트가 이미 변경한 뒤에도, 내가 들고 있던 캐시를 계속 쓰면 오래된 데이터를 읽게 되는 문제를 말한다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;3882&quot; data-start=&quot;3842&quot; data-ke-size=&quot;size26&quot;&gt;10. NFS의 캐시 일관성 기법에 대한 평가&lt;/h2&gt;
&lt;p data-end=&quot;4121&quot; data-start=&quot;3884&quot; data-ke-size=&quot;size16&quot;&gt;NFS는 원래 &amp;ldquo;접근 전에 검사(validate-before-access)&amp;rdquo; 같은 방식으로 일관성을 맞추려 하면 &lt;b&gt;GETATTR 요청이 과도하게 늘어나는&lt;/b&gt; 문제가 생기며, 이를 완화하려고 &lt;b&gt;attribute cache&lt;/b&gt;를 도입했다. 이 attribute cache는 timeout을 두며, 또한 &lt;b&gt;flush-on-close&lt;/b&gt; 같은 동작이 일관성 의미에 영향을 준다.&lt;/p&gt;
&lt;p data-end=&quot;4290&quot; data-start=&quot;4123&quot; data-ke-size=&quot;size16&quot;&gt;그 결과 NFS가 제공하는 가장 대표적인 보장은 &lt;b&gt;close-to-open consistency&lt;/b&gt;로 알려져 있다 즉 한 클라이언트가 파일을 닫을 때(close) 변경 내용을 밀어 넣고, 다른 클라이언트가 파일을 열 때(open) 최신을 보게 되는 수준의 일관성을 기대하게 만든다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;4349&quot; data-start=&quot;4297&quot; data-ke-size=&quot;size26&quot;&gt;11. 서버 측 쓰기 버퍼링의 의미&lt;/h2&gt;
&lt;p data-end=&quot;4572&quot; data-start=&quot;4351&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 타임아웃 후 재시도하는 모델에서는, 서버가 WRITE에 대해 &amp;ldquo;성공&amp;rdquo;을 응답한 뒤 크래시하면 데이터 유실이 생길 수 있다. 그래서 서버는 WRITE 성공 응답을 하기 전에 &lt;b&gt;안정 저장장치에 반영(stable storage에 커밋)해야 하는 압박&lt;/b&gt;을 받으며, 이 때문에 서버 측 write buffering(메모리에만 쌓아두는 최적화)을 마음대로 하기 어렵다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/221</guid>
      <comments>https://gowoong.tistory.com/221#entry221comment</comments>
      <pubDate>Tue, 6 Jan 2026 13:45:11 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 19주차 Part.1 : 분산 시스템</title>
      <link>https://gowoong.tistory.com/220</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;분산 시스템은 전 세계의 구조를 바꿨다. 웹 브라우저가 지구상 어딘가에 있는 웹 서버에 접속하면&amp;nbsp;&lt;b&gt;클라이언트/서버&lt;/b&gt; 분산 시스템이라는 구조에 한 구성원이 된다. Google이나 Facebook의 웹 서비스를 사용한다는 것은 하나의 기계를 사용하는 것이 아니다. 수천대로 이루어진 기계들이 사이트의 특정 서비스를 제공하기 위해서 서로 협력하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;3&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3&quot;&gt;1. 분산 시스템의 등장 배경 (Introduction)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;분산 시스템은 여러 대의 독립된 컴퓨터가 네트워크로 연결되어, 사용자에게는 마치 하나의 시스템처럼 보이게 하는 소프트웨어 모음이다. 핵심 과제는 &lt;b&gt;'어떻게 하면 신뢰할 수 없는 부품(메시지 손실, 기기 장애 등)을 가지고 신뢰할 수 있는 시스템을 구축할 것인가?'&lt;/b&gt;에 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot; data-path-to-node=&quot;4,7&quot;&gt;분산 시스템의 핵심 사안은 실패와 고장의 극복이다. 개별 구성 요소들은 자주 고장 나지만 기계들을 고장 없는 시스템처럼 보이도록 만들 수가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;2. 통신의 기초 (Communication Basics)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;컴퓨터 간의 통신은 기본적으로 &lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;6,1&quot;&gt;메시지 교환&lt;/b&gt;을 통해 이루어진다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;7,0,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,1,0&quot;&gt;비신뢰성:&lt;/b&gt; 네트워크 하드웨어(카드, 스위치, 라우터 등)는 완벽하지 않으며 패킷의 손실, 지연, 순서 바뀜 등이 빈번하게 발생한다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;7,1,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,1,0&quot;&gt;패킷 손실의 원인:&lt;/b&gt; 비트 손상, 하드웨어 손상 외에도 라우터나 수신 호스트의 메모리 버퍼가 부족할 때 패킷을 버리게 되는 경우가 가장 근본적인 원인이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot; data-path-to-node=&quot;7,1,1,2&quot;&gt;패킷 손실은 네트워킹에서 근본적인 문제이다. 그렇다면 어떻게 대처해야 할까?&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;3. 신뢰할 수 없는 통신 계층 (Unreliable Communication Layers)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;간단한 방법은 아무런 조치도 취하지 않는 것이다. 어떤 응용 프로그램들은 패킷 손실 시 대응 방법을 가지고 있기 때문에 메시지 계층과 직접 통신하도록 하는 것이 이로울 때도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;가장 대표적인 예시로 &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;9,1&quot;&gt;UDP/IP&lt;/b&gt;가 있다. UDP를 사용하기 위해서는&amp;nbsp;&lt;b&gt;소켓 API&lt;/b&gt;를 이용하여&amp;nbsp;&lt;b&gt;통신지정(Communication end point)&lt;/b&gt;을 생성한다. 다른 편 기계의 프로세스들은&amp;nbsp;&lt;b&gt;UDP 데이터그램&lt;/b&gt;을 원래의 프로세스로 전송한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;10,0,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,1,0&quot;&gt;특징:&lt;/b&gt; 데이터를 보낼 뿐 도착 여부를 보장하지 않는 비연결형 프로토콜이다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;10,1,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,1,0&quot;&gt;체크섬(Checksum):&lt;/b&gt; UDP는 데이터의 변조 여부를 확인하기 위해 체크섬 기능을 포함하지만, 패킷 손실 자체를 막지는 못한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;4. 신뢰할 수 있는 통신 계층 (Reliable Communication Layers)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;신뢰할 수 없는 네트워크 위에서 안정적인 통신을 보장하기 위해 다음과 같은 기법을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;13,0,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,1,0&quot;&gt;확인 응답(Acknowledgment, ACK):&lt;/b&gt; 메시지를 정상 수신했음을 송신자에게 알리는 짧은 메시지이다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;13,1,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,1,0&quot;&gt;타임아웃 및 재전송(Timeout and Retry):&lt;/b&gt; 일정 시간 내에 ACK가 오지 않으면 메시지가 유실된 것으로 판단하고 다시 보낸다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;13,2,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,1,0&quot;&gt;시퀀스 카운터(Sequence Counter):&lt;/b&gt; 메시지에 번호를 매겨 중복 수신된 메시지를 식별하고 폐기함으로써 '정확히 한 번' 전달되는 효과를 낸다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;13,3,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,3,1,0&quot;&gt;TCP/IP:&lt;/b&gt; 이러한 복잡한 처리를 대신 해주는 가장 널리 쓰이는 신뢰성 프로토콜이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt;5. 통신 추상화 (Communication Abstractions)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;분산 시스템 개발의 복잡도를 낮추기 위해 통신 과정을 추상화한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;16,0,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,1,0&quot;&gt;분산 공유 메모리(DSM):&lt;/b&gt; 여러 머신의 메모리를 하나의 거대한 가상 주소 공간으로 보이게 하지만, 장애 대응과 성능 문제로 인해 현재는 거의 사용되지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;16,1,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,1,0&quot;&gt;원격 프로시저 호출(RPC):&lt;/b&gt; 원격지에 있는 함수를 마치 로컬 함수처럼 호출하게 해주는 방식이다. 클라이언트는 프로시저 호출을 하고 잠시 후에 결과를 리턴 받는다. 서버는 공지할(export) 루틴을 정의한다. 나머지는 RPC 시스템이 두 부분으로 나누어 담당한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;16,1,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot; data-path-to-node=&quot;16,1,1,2&quot;&gt;&lt;b&gt;스텁 생성기(stub generator)&lt;/b&gt; 또는 &lt;b&gt;프로토콜 컴파일러(protocol compiler)&lt;/b&gt;라고 함&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;16,1,1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b&gt;런타임 라이브러리(run-time library)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17&quot;&gt;5.1 &lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot; data-path-to-node=&quot;19,0,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,1,0&quot;&gt;스텁 생성기(Stub Generator)&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot; data-path-to-node=&quot;19,0,1,0&quot;&gt;인터페이스를 정의하면 통신에 필요한 코드를 자동 생성한다. 스텁 생성기의 장점으로는&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot; data-path-to-node=&quot;19,0,1,1&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;17&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;수작업으로 코드를 작성할 경우 발생할 수 있는 작은 실수를 막아준다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;17&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;스텁 컴파일러가 코드를 최적화 할 수 있기 때문에 성능을 개선할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;스텁 컴파일러에서 고려해야 할 몇 가지 중요한 문제들이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;복잡한 구조의 인자나 다수의 인자를 전달하는 문제 : 복잡한 자료 구조를 어떻게 패키지화해서 전송할 것인가? 예를 들어 write() 시스템 콜을 사용할 때는 세 개의 인자를 전달해야 한다. int 형의 파일 디스크립터와 버퍼의 포인터 그리고 써야 할 바이트를 나타내는 크기를 전달해야 한다. 만약 RPC 패키지가 포인터를 전달했다면 그 포인터를 어떻게 해석해야 하는지 알아야 정확한 동작을 할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;해결 방안 : 일반적으로 잘 알려진 데이터 형을 사용하거나 자료 구조에 해석하는 방법에 대한 내용을 추가하여 컴파일러가 바이트의 어떤 부분을 직렬화할지 알 수 있도록 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;병행성을 고려하며 서버를 구성해야 한다. : 단순한 서버는 간단한 반복문에서 요청을 대기하며 한 번에 한 요청씩 처리한다. 하지만, 엄청나게 비효율 적일 것이다. 만약 RPC 호출이 차단되면(예, I/O를 기다리며) 서버의 자원이 낭비된다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;해결 방안 : 흔한 구성 방식은 쓰레드 풀을 사용하는 것이다. 이 구성 방식은 서버를 시작할 때 정해진 수의 쓰레드들을 생성한다. RPC 호출이 도착하면 메인 쓰레드가 워커 쓰레드로 보낸다. 메인 쓰레드는 계속 RPC 호출을 받기 위해 대기한다. 또 다른 요청이 도착하면 다시 다른 워커 쓰레드에게 전달한다. 이 방식은 RPC 호출의 올바른 동작을 위해 락이나 기타 동기화 기법들을 써야 하기 때문에 프로그래밍 복잡도가 늘어난다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-path-to-node=&quot;17&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-path-to-node=&quot;17&quot; data-index-in-node=&quot;0&quot;&gt;5.2 &lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot; data-path-to-node=&quot;19,0,1,0&quot;&gt;&lt;b data-path-to-node=&quot;19,0,1,0&quot; data-index-in-node=&quot;0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,1,0&quot;&gt;런타임 라이브러리&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot; data-path-to-node=&quot;19,0,1,0&quot;&gt;런타임 라이브러리는 RPC 시스템에서 대부분의 중요한 일을 책임진다. 성능과 신뢰성에 관한 대부분의 문제들을 처리하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;원격 서비스의 위치를 찾는 문제 : 가장 간단한 방법은 기존의 시스템을 활용하는 것이다. 현재의 인터넷 프로토콜이 사용하는 호스트의 이름과 포트 번호를 활용하는 것이다. 그런 시스템에서 클라이언트는 RPC 서비스를 실행하기를 원하는 기계의 호스트 이름 또는 IP 주소 그리고 포트 번호를 반드시 기록한다. 그다음, 패킷이 특정 주소로부터 시스템에 있는 임의의 다른 기계로 전달될 수 있는 메커니즘을 제공해야 한다. - DNS와 이름 해석 방법&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;RPC를 어떤 전송 계층 프로토콜 위에 만들지를 결정해야 한다 : TCP보다는 UDP에 RPC 패키지들은 구현을 한다. 그러면 효율적으로 RPC 계층을 만들 수 있지만 RPC 시스템이 신뢰성 담보를 책임져야 한다. RPC 계층은 앞서 설명했던 타임아웃/재시도 그리고 ack방식을 충분히 활용하여 원하는 수준의 신뢰도를 달성한다. 통신 계층은 순서 번호 같은 것을 사용하여 각 RPC가 단 한 번만 또는 많아야 한 번만 발생하도록 보장한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2&quot;&gt;5.3 RPC 구현 시 고려해야 할 기타 문제들&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;RPC가 단순히 함수를 호출하는 것처럼 보이게 하려면, 보이지 않는 곳에서 다음과 같은 복잡한 문제들을 해결해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;1. 데이터 조립 및 분해 (Fragmentation and Reassembly)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;네트워크 패킷은 한 번에 보낼 수 있는 크기(MTU)가 정해져 있다. 만약 RPC를 통해 전달하려는 인자나 결과 값이 이 크기보다 크다면 시스템은 이를 여러 개로 쪼개서 보내야 하며(&lt;b data-index-in-node=&quot;102&quot; data-path-to-node=&quot;5&quot;&gt;Fragmentation&lt;/b&gt;), 받는 쪽에서는 이를 다시 순서대로 합쳐야 한다(&lt;b data-index-in-node=&quot;144&quot; data-path-to-node=&quot;5&quot;&gt;Reassembly&lt;/b&gt;). 이 과정에서 패킷이 하나라도 유실되면 전체 메시지를 처리할 수 없으므로 신뢰성 있는 관리가 필수적이다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;2. 바이트 순서 표시 (Byte Ordering / Endianness)&lt;/span&gt;&lt;/h4&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;서로 다른 아키텍처를 가진 컴퓨터끼리 통신할 때 가장 흔히 발생하는 문제다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;빅 엔디안 (Big Endian):&lt;/b&gt; 상위 바이트부터 메모리에 저장하는 방식 (예: 0x1234를 12, 34 순으로 저장).&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;리틀 엔디안 (Little Endian):&lt;/b&gt; 하위 바이트부터 메모리에 저장하는 방식 (예: 0x1234를 34, 12 순으로 저장).&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,2,0&quot;&gt;해결책:&lt;/b&gt; 서로 다른 엔디안 방식을 사용하는 컴퓨터가 통신하면 숫자가 완전히 다르게 해석될 수 있다. 따라서 RPC 시스템은 데이터를 보낼 때 표준화된 네트워크 바이트 순서를 따르거나, 마샬링 과정에서 이를 변환하는 작업을 수행한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;3. 클라이언트에게 비동기적 실행을 허가할 것인가?&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;어떤 RPC는 동기적으로 동작한다. 즉, 클라이언트가 프로시저 호출을 요청하면 그 결과가 리턴될 때까지 대기한다. 대기 시간이 길어질 수도 있다. 클라이언트가 다른 일을 처리할 수 있도록 어떤 RPC 패키지는 RPC를 비동기적으로 호출하기도 한다. 비동기 RPC가 호출이 되면 RPC 패키지는 요청을 보내고 즉시 리턴한다. 클라이언트는 그 후에 다른 RPC를 호출한다거나 유용한 연산을 하는 식의 다른 작업을 자유롭게 진행할 수 있다. 그때 RPC를 호출하면, 현재 진행 중인 RPC를 대기토록 한다. 완료되면 리턴된 인자들을 접근할 수 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/220</guid>
      <comments>https://gowoong.tistory.com/220#entry220comment</comments>
      <pubDate>Tue, 6 Jan 2026 13:00:18 +0900</pubDate>
    </item>
    <item>
      <title>클린아키텍처 2편: &amp;lsquo;안쪽은 바깥을 몰라야 한다&amp;rsquo;</title>
      <link>https://gowoong.tistory.com/219</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발에서 변경은 피할 수 없다. 비즈니스가 바뀌면 도메인과 유스케이스가 함께 바뀌는 건 자연스럽다. 문제는 그 반대다. &lt;b&gt;비즈니스 규칙은 그대로인데&lt;/b&gt;, DB/프레임워크/외부 SDK 같은 &amp;lsquo;디테일&amp;rsquo; 변화가 핵심 로직까지 밀고 들어오면서 수정 범위가 폭발하는 순간이 있다. 이 글은 그 폭발의 원인을 &amp;ldquo;레이어&amp;rdquo;가 아니라 &lt;b&gt;의존성 방향&lt;/b&gt;으로 설명하는 원칙, 즉 &lt;b&gt;Dependency Rule(의존성은 안쪽으로만 흐른다)&lt;/b&gt; 하나에만 집중한다. 그럼 여기서 말하는 &amp;lsquo;의존성&amp;rsquo;은 정확히 무엇일까? 자바에서는 특히 import/타입 참조로 이 규칙이 깨지기 쉽다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Dependency Rule이 말하는 &amp;lsquo;의존성&amp;rsquo;의 정확한 의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처에서 말하는 &amp;ldquo;의존성&amp;rdquo;은 추상적인 분위기 용어가 아니다. 특히 자바에서는 의존성이 꽤 &lt;b&gt;구체적이고 기계적으로&lt;/b&gt; 드러난다. 가장 중요한 구분은 다음이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;418&quot; data-start=&quot;277&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;359&quot; data-start=&quot;277&quot;&gt;&lt;b&gt;컴파일 타임 의존(Compile-time dependency)&lt;/b&gt;: 소스 코드가 특정 타입/패키지/모듈을 알고 있어야 컴파일되는 의존&lt;/li&gt;
&lt;li data-end=&quot;418&quot; data-start=&quot;360&quot;&gt;&lt;b&gt;런타임 연결(Runtime wiring)&lt;/b&gt;: DI 컨테이너가 실행 시점에 객체를 연결해 주는 행위&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;535&quot; data-start=&quot;420&quot; data-ke-size=&quot;size16&quot;&gt;클린 아키텍처의 Dependency Rule이 통제하려는 대상은 대부분 &lt;b&gt;컴파일 타임 의존&lt;/b&gt;이다.&lt;br /&gt;즉 &amp;ldquo;실행할 때 주입받는다&amp;rdquo;가 아니라, &amp;ldquo;코드가 그 타입을 import/참조하고 있느냐&amp;rdquo;가 핵심이다.&lt;/p&gt;
&lt;h3 data-end=&quot;569&quot; data-start=&quot;537&quot; data-ke-size=&quot;size23&quot;&gt;1) 자바에서 &amp;lsquo;컴파일 타임 의존&amp;rsquo;이 생기는 순간들&lt;/h3&gt;
&lt;p data-end=&quot;595&quot; data-start=&quot;571&quot; data-ke-size=&quot;size16&quot;&gt;아래 중 하나라도 해당하면 의존성이 생긴다.&lt;/p&gt;
&lt;h4 data-end=&quot;620&quot; data-start=&quot;597&quot; data-ke-size=&quot;size20&quot;&gt;(1) import / 타입 참조&lt;/h4&gt;
&lt;pre id=&quot;code_1767404548622&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.transaction.annotation.Transactional;

public class CreateUserUseCase {
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;804&quot; data-start=&quot;743&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;804&quot; data-start=&quot;743&quot;&gt;import 자체가 없어도, 코드 어디선가 Transactional 타입을 참조하면 이미 의존이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;822&quot; data-start=&quot;806&quot; data-ke-size=&quot;size20&quot;&gt;(2) 상속 / 구현&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767404575222&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface UserRepository extends JpaRepository&amp;lt;UserEntity, Long&amp;gt; { }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;995&quot; data-start=&quot;911&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;954&quot; data-start=&quot;911&quot;&gt;extends/implements는 가장 강한 형태의 결합이다.&lt;/li&gt;
&lt;li data-end=&quot;995&quot; data-start=&quot;955&quot;&gt;이 한 줄로 &amp;ldquo;안쪽 코드가 바깥 프레임워크 타입을 안다&amp;rdquo;가 확정된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;1018&quot; data-start=&quot;997&quot; data-ke-size=&quot;size20&quot;&gt;(3) 애너테이션(어노테이션)&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767404587405&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class CreateUserUseCase { }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1162&quot; data-start=&quot;1075&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1103&quot; data-start=&quot;1075&quot;&gt;애너테이션도 결국 &amp;ldquo;특정 타입에 대한 참조&amp;rdquo;다.&lt;/li&gt;
&lt;li data-end=&quot;1162&quot; data-start=&quot;1104&quot;&gt;그래서 도메인/유스케이스에 프레임워크 애너테이션이 들어오면, 그 순간 안쪽이 바깥을 아는 셈이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;1193&quot; data-start=&quot;1164&quot; data-ke-size=&quot;size20&quot;&gt;(4) 제네릭 타입/반환 타입/파라미터 타입&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767404600455&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public ResponseEntity&amp;lt;UserResponse&amp;gt; execute(CreateUserRequest req) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1412&quot; data-start=&quot;1281&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1327&quot; data-start=&quot;1281&quot;&gt;반환/입력 타입에 웹/프레임워크 타입이 섞이는 순간, 경계가 흐려지기 시작한다.&lt;/li&gt;
&lt;li data-end=&quot;1412&quot; data-start=&quot;1328&quot;&gt;특히 ResponseEntity, HttpServletRequest, JpaRepository 같은 타입이 대표적인 &amp;ldquo;디테일 타입&amp;rdquo;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;1469&quot; data-start=&quot;1414&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1469&quot; data-start=&quot;1416&quot; data-ke-size=&quot;size16&quot;&gt;핵심: &lt;b&gt;의존성은 &amp;lsquo;객체가 누구를 new 하느냐&amp;rsquo;보다 &amp;lsquo;타입을 누구를 아느냐&amp;rsquo;가 더 크다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1524&quot; data-start=&quot;1476&quot; data-ke-size=&quot;size23&quot;&gt;2) &amp;ldquo;런타임 DI는 괜찮은데, 컴파일 타임 의존은 조심해야 한다&amp;rdquo;는 말의 의미&lt;/h3&gt;
&lt;p data-end=&quot;1647&quot; data-start=&quot;1526&quot; data-ke-size=&quot;size16&quot;&gt;스프링 DI를 예로 들면, 유스케이스는 실행 시점에 UserRepository 구현체를 주입받아도 된다.&lt;br /&gt;하지만 유스케이스 코드가 JpaRepository 같은 구체 타입을 알기 시작하면 교체가 어려워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dependency Rule은 &amp;ldquo;런타임에 뭘 주입받으면 안 된다&amp;rdquo;가 아니라,&lt;br /&gt;&lt;b&gt;&amp;ldquo;안쪽 코드가 바깥 타입을 import/참조하지 않게 하라&amp;rdquo;&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;1971&quot; data-start=&quot;1944&quot; data-ke-size=&quot;size16&quot;&gt;클린 아키텍처에서 &amp;ldquo;바깥&amp;rdquo;은 보통 이런 것들이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2200&quot; data-start=&quot;1973&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2050&quot; data-start=&quot;1973&quot;&gt;Web: Controller, Request/Response DTO, ResponseEntity, HttpServlet*&lt;/li&gt;
&lt;li data-end=&quot;2114&quot; data-start=&quot;2051&quot;&gt;DB/ORM: JpaRepository, EntityManager, @Entity, @Table&lt;/li&gt;
&lt;li data-end=&quot;2157&quot; data-start=&quot;2115&quot;&gt;Infra: 외부 API SDK, 메시지 큐 클라이언트, 클라우드 SDK&lt;/li&gt;
&lt;li data-end=&quot;2200&quot; data-start=&quot;2158&quot;&gt;Framework: 스프링 애너테이션, AOP, Transaction 등&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;133&quot; data-start=&quot;100&quot; data-ke-size=&quot;size26&quot;&gt;Dependency Rule이 깨지는 대표 패턴 3가지&lt;/h2&gt;
&lt;p data-end=&quot;164&quot; data-start=&quot;135&quot; data-ke-size=&quot;size16&quot;&gt;Dependency Rule은 한 문장으로 요약된다.&lt;/p&gt;
&lt;blockquote data-end=&quot;228&quot; data-start=&quot;166&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;228&quot; data-start=&quot;168&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;안쪽은 바깥을 몰라야 한다.&lt;/b&gt;&lt;br /&gt;(정책/핵심 로직이 디테일(DB&amp;middot;웹&amp;middot;프레임워크)에 끌려가지 않게)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;362&quot; data-start=&quot;230&quot; data-ke-size=&quot;size16&quot;&gt;그런데 실제 코드에서는 &amp;ldquo;레이어를 나눴는데도&amp;rdquo; 이 규칙이 쉽게 깨진다. 이유는 대부분 &lt;b&gt;컴파일 타임 의존&lt;/b&gt; 때문이다. 즉, 코드가 특정 타입을 import 하거나, 상속하거나, 애너테이션을 붙이는 순간 안쪽이 바깥을 &amp;ldquo;알게&amp;rdquo; 된다.&lt;/p&gt;
&lt;p data-end=&quot;432&quot; data-start=&quot;364&quot; data-ke-size=&quot;size16&quot;&gt;아래 3가지는 내가 봤을 때(그리고 대부분의 프로젝트에서) Dependency Rule을 가장 흔하게 무너뜨리는 패턴이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;489&quot; data-start=&quot;439&quot; data-ke-size=&quot;size23&quot;&gt;패턴 1) 안쪽(UseCase/Domain)에 프레임워크 타입/애너테이션이 들어온다&lt;/h3&gt;
&lt;p data-end=&quot;503&quot; data-start=&quot;491&quot; data-ke-size=&quot;size16&quot;&gt;대표적으로 이런 것들:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;635&quot; data-start=&quot;504&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;547&quot; data-start=&quot;504&quot;&gt;@Service, @Transactional 같은 스프링 애너테이션&lt;/li&gt;
&lt;li data-end=&quot;596&quot; data-start=&quot;548&quot;&gt;ResponseEntity, HttpServletRequest 같은 웹 타입&lt;/li&gt;
&lt;li data-end=&quot;635&quot; data-start=&quot;597&quot;&gt;@Entity, EntityManager 같은 ORM 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;703&quot; data-start=&quot;637&quot; data-ke-size=&quot;size16&quot;&gt;문제는 &amp;ldquo;스프링을 쓴다&amp;rdquo;가 아니라, &lt;b&gt;안쪽 코드가 프레임워크 타입을 참조하는 순간&lt;/b&gt; 의존성 방향이 바뀐다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;875&quot; data-start=&quot;801&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;820&quot; data-start=&quot;801&quot;&gt;프레임워크를 교체하기 어려워지고&lt;/li&gt;
&lt;li data-end=&quot;849&quot; data-start=&quot;821&quot;&gt;단위 테스트가 프레임워크 컨텍스트에 묶이기 쉽고&lt;/li&gt;
&lt;li data-end=&quot;875&quot; data-start=&quot;850&quot;&gt;무엇보다 &amp;ldquo;정책&amp;rdquo;이 &amp;ldquo;디테일&amp;rdquo;에 끌려다닌다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1033&quot; data-start=&quot;999&quot; data-ke-size=&quot;size23&quot;&gt;패턴 2) Port(인터페이스)가 디테일을 &amp;ldquo;노출&amp;rdquo;한다&lt;/h3&gt;
&lt;p data-end=&quot;1101&quot; data-start=&quot;1035&quot; data-ke-size=&quot;size16&quot;&gt;클린 아키텍처에서 Port는 안쪽에 존재한다. 그런데 Port가 바깥 타입을 노출하는 순간, 안쪽이 바깥을 알아버린다.&lt;/p&gt;
&lt;p data-end=&quot;1116&quot; data-start=&quot;1103&quot; data-ke-size=&quot;size16&quot;&gt;가장 흔한 예가 이거다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1162&quot; data-start=&quot;1117&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1162&quot; data-start=&quot;1117&quot;&gt;UserRepository extends JpaRepository&amp;lt;...&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1257&quot; data-start=&quot;1164&quot; data-ke-size=&quot;size16&quot;&gt;이 한 줄은 사실상 &amp;ldquo;포트가 아니라 JPA 전용 인터페이스&amp;rdquo;가 되어버린다. 즉, 유스케이스는 결국 JPA의 존재를 전제로 돌아가게 되고, 저장 기술 교체가 어려워진다.&lt;/p&gt;
&lt;p data-end=&quot;1271&quot; data-start=&quot;1259&quot; data-ke-size=&quot;size16&quot;&gt;또 다른 형태도 있다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1354&quot; data-start=&quot;1272&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1315&quot; data-start=&quot;1272&quot;&gt;유스케이스/포트의 반환 타입이 ResponseEntity 같은 웹 타입&lt;/li&gt;
&lt;li data-end=&quot;1354&quot; data-start=&quot;1316&quot;&gt;포트 메서드 시그니처에 특정 ORM 엔티티 타입이 박혀 있는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-end=&quot;1700&quot; data-start=&quot;1641&quot; data-ke-size=&quot;size23&quot;&gt;패턴 3) UseCase 경계가 웹 DTO로 오염된다 (Input/Output이 바깥 모델에 고정)&lt;/h3&gt;
&lt;p data-end=&quot;1729&quot; data-start=&quot;1702&quot; data-ke-size=&quot;size16&quot;&gt;이 패턴은 특히 개발을 빨리 할 때 자주 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1832&quot; data-start=&quot;1731&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1795&quot; data-start=&quot;1731&quot;&gt;Controller의 Request DTO를 그대로 useCase.execute(requestDto)로 넘김&lt;/li&gt;
&lt;li data-end=&quot;1832&quot; data-start=&quot;1796&quot;&gt;UseCase 결과도 Response DTO 형태로 바로 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1857&quot; data-start=&quot;1834&quot; data-ke-size=&quot;size16&quot;&gt;처음엔 편하지만, 곧 이런 문제가 생긴다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1952&quot; data-start=&quot;1858&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1907&quot; data-start=&quot;1858&quot;&gt;API 버전/필드명/직렬화 규칙 같은 &amp;ldquo;표현의 변화&amp;rdquo;가 유스케이스에 직접 영향을 준다&lt;/li&gt;
&lt;li data-end=&quot;1952&quot; data-start=&quot;1908&quot;&gt;결과적으로 &amp;ldquo;디테일 변화 &amp;rarr; 정책 변화&amp;rdquo;처럼 보이는 거대한 도미노가 발생한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심은 경계 모델을 분리하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2110&quot; data-start=&quot;2045&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2058&quot; data-start=&quot;2045&quot;&gt;Web DTO는 바깥&lt;/li&gt;
&lt;li data-end=&quot;2110&quot; data-start=&quot;2059&quot;&gt;UseCase는 자신만의 입력 모델(Command) / 출력 모델(Result)을 가진다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2131&quot; data-start=&quot;2112&quot; data-ke-size=&quot;size16&quot;&gt;이게 &amp;ldquo;변경을 없애는&amp;rdquo; 게 아니라,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2181&quot; data-start=&quot;2132&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2149&quot; data-start=&quot;2132&quot;&gt;&lt;b&gt;표현 변화(디테일)&lt;/b&gt;가&lt;/li&gt;
&lt;li data-end=&quot;2181&quot; data-start=&quot;2150&quot;&gt;&lt;b&gt;유스케이스(정책)&lt;/b&gt;를 흔드는 것을 막는 장치다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Deep Dive/아키텍처</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/219</guid>
      <comments>https://gowoong.tistory.com/219#entry219comment</comments>
      <pubDate>Sat, 3 Jan 2026 11:03:12 +0900</pubDate>
    </item>
    <item>
      <title>클린아키텍처 1편: 왜 클린 아키텍처가 필요해졌나?</title>
      <link>https://gowoong.tistory.com/218</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;과거 스타트업에서 아무것도 모른 채 기초만 막 떼고 개발을 시작했었다. 그러다 보니 일단 기능 위주로 돌아가게만 구현을 했었는데 나중에 요구사항의 변경으로 기능을 변경하거나 기술 스택이 변경되는 경우 매우 오랜 시간 수정을 하거나 많은 작업을 수행했어야 했다. 그렇기에 아키텍처가 중요하다는 것을 느끼게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 딥다이브 주제는 클린아키텍처이다. 단순하게 &quot;어떻게 구현하나?&quot;가 아니라 &lt;b&gt;왜 이런 아키텍처가 등장했는지 어떤 것을 추구하는지 이해&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;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddb3m7/dJMcagYsq0l/N2bhztoGAmDlxHw6UEHv11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddb3m7/dJMcagYsq0l/N2bhztoGAmDlxHw6UEHv11/img.png&quot; data-alt=&quot;AI가 만들어준 클린 아키텍처 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddb3m7/dJMcagYsq0l/N2bhztoGAmDlxHw6UEHv11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fddb3m7%2FdJMcagYsq0l%2FN2bhztoGAmDlxHw6UEHv11%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;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AI가 만들어준 클린 아키텍처 이미지&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 불편했던 경험담&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 스프링이 아니더라도 개발을 하면서 경험했던 것이 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;ldquo;Repository는 JPA를 쓰니까 JpaRepository를 상속하면 끝.&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이런 질문을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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 data-end=&quot;775&quot; data-start=&quot;758&quot;&gt;DB를 NoSQL로 바꾸면?&lt;/li&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;776&quot;&gt;테스트에서 메모리 저장소로 바꾸고 싶으면?&lt;/li&gt;
&lt;li data-end=&quot;832&quot; data-start=&quot;802&quot;&gt;특정 저장 기술을 걷어내고 다른 방식으로 교체하면?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장 기술을 바꾸려면 사용하는 곳 전체가 영향을 받거나, 구조 자체를 갈아엎어야 했다. 결국 선택한 기술에 끌려다니는 것 같았다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 문제는 스프링이 &amp;ldquo;나쁘기&amp;rdquo; 때문이 아니라, &lt;b&gt;핵심(정책)과 디테일(기술)이 섞이는 구조&lt;/b&gt;가 반복되기 때문이다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.&amp;nbsp; 객체지향에서 반복되는 '정책 vs 디테일' 충돌&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처는 특정 프레임워크의 회피법이 아니다. 더 근본적으로는 객체지향에서 늘 등장하는 질문을 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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 data-end=&quot;1239&quot; data-start=&quot;1191&quot;&gt;&lt;b&gt;정책(Policy)&lt;/b&gt;: 비즈니스 규칙, 유스케이스, &amp;ldquo;무슨 일을 해야 하는가&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;1293&quot; data-start=&quot;1240&quot;&gt;&lt;b&gt;디테일(Detail)&lt;/b&gt;: DB, ORM, 외부 API, 웹 프레임워크, 메시지큐, UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 시간이 지날수록 디테일이 코드를 지배하기 쉽다는 점이다.&lt;/p&gt;
&lt;pre id=&quot;code_1767330671829&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;원래 기대: 정책(핵심)이 디테일(기술)을 바꿔 끼운다
   Policy  -&amp;gt;  Detail

현실에서 자주 발생: 디테일이 정책을 침식한다
   Policy  &amp;lt;-  Detail&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;디테일이 정책을 침식한다&quot;는 것은 이런 의미다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1610&quot; data-start=&quot;1496&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1527&quot; data-start=&quot;1496&quot;&gt;핵심 로직이 프레임워크 타입/ORM 타입을 직접 참조&lt;/li&gt;
&lt;li data-end=&quot;1580&quot; data-start=&quot;1528&quot;&gt;저장 기술의 제약(@Entity, lazy loading, 트랜잭션)에 맞춰 도메인이 변형&lt;/li&gt;
&lt;li data-end=&quot;1610&quot; data-start=&quot;1581&quot;&gt;테스트는 스프링/DB 없이는 못 돌리는 형태가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1637&quot; data-start=&quot;1612&quot; data-ke-size=&quot;size16&quot;&gt;이 지점부터 변경 비용이 눈덩이처럼 불어난다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1637&quot; data-start=&quot;1612&quot; data-ke-size=&quot;size26&quot;&gt;3. 왜 변경 비용이 폭발할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겉으로는 &quot;DB 교체가 어렵다&quot;, &quot;테스트가 무겁다&quot; 같은 현상으로 보이지만, 더 근본 원인은&amp;nbsp;&lt;b&gt;의존성 방향&lt;/b&gt;이 잘못 잡혀 있기 때문이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;정책(고수준)&lt;/b&gt;이&amp;nbsp;&lt;b&gt;디테일(저수준)&lt;/b&gt;에 의존하는 순간, 디테일의 변화가 정책까지 전염된다.&lt;/blockquote&gt;
&lt;h4 data-end=&quot;1850&quot; data-start=&quot;1839&quot; data-ke-size=&quot;size20&quot;&gt;(좋은 상태)&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767330794942&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[정책/핵심] ---&amp;gt; [디테일/기술]
핵심은 기술을 갈아끼울 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-end=&quot;1912&quot; data-start=&quot;1901&quot; data-ke-size=&quot;size20&quot;&gt;(나쁜 상태)&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1767330805625&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[정책/핵심] &amp;lt;--- [디테일/기술]
기술을 바꾸면 핵심이 흔들린다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1988&quot; data-start=&quot;1962&quot; data-ke-size=&quot;size16&quot;&gt;클린 아키텍처는 여기서 한 문장 규칙을 꺼낸다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;1988&quot; data-start=&quot;1962&quot; data-ke-size=&quot;size26&quot;&gt;4. 클린 아키텍처의 출발점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처의 핵심은 &quot;레이어 이름&quot;이 아니라, 다음의 규칙이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;의존성은 항상 안쪽(핵심)을 향해야 한다.&amp;nbsp;&lt;/b&gt;즉,&amp;nbsp;&lt;b&gt;안쪽은 바깥을 몰라야 한다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &quot;안쪽&quot;은 도메인/유스케이스 같은 핵심 규칙이고, &quot;바깥&quot;은 DB/프레임워크/UI/외부 API 같은 구현 세부 사항이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dA1DgV/dJMcafZyl81/0tilhIY8WeXEESwu2A2jo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dA1DgV/dJMcafZyl81/0tilhIY8WeXEESwu2A2jo1/img.png&quot; data-alt=&quot;이미지 출처 : https://velog.io/@inseo24/clean-architecture-01-03&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dA1DgV/dJMcafZyl81/0tilhIY8WeXEESwu2A2jo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdA1DgV%2FdJMcafZyl81%2F0tilhIY8WeXEESwu2A2jo1%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;940&quot; height=&quot;722&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 : https://velog.io/@inseo24/clean-architecture-01-03&lt;/figcaption&gt;
&lt;/figure&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 규칙이 지켜지면 기대 효과는 명확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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 data-end=&quot;2402&quot; data-start=&quot;2378&quot;&gt;저장 기술이 바뀌어도 핵심 로직은 그대로&lt;/li&gt;
&lt;li data-end=&quot;2435&quot; data-start=&quot;2403&quot;&gt;단위 테스트가 쉬워짐(핵심은 순수 자바로 테스트 가능)&lt;/li&gt;
&lt;li data-end=&quot;2474&quot; data-start=&quot;2436&quot;&gt;프레임워크 교체가 &amp;ldquo;전체 리라이트&amp;rdquo;가 아니라 &amp;ldquo;바깥 교체&amp;rdquo;로 축소&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 이 규칙을 지키기 어려운 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 같은 프레임워크가 문제를 &quot;만드는&quot;것은 아니지만, 편의 기능이 많을수록 &lt;b&gt;바깥의 코드가 안쪽으로 새어 들어오기 쉬운 환경&lt;/b&gt;이 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2703&quot; data-start=&quot;2610&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2633&quot; data-start=&quot;2610&quot;&gt;상속한 줄(extends ...)&lt;/li&gt;
&lt;li data-end=&quot;2686&quot; data-start=&quot;2634&quot;&gt;어노테이션 몇 개(@Entity, @Service, @Transactional)&lt;/li&gt;
&lt;li data-end=&quot;2703&quot; data-start=&quot;2687&quot;&gt;프레임워크 객체 직접 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2753&quot; data-start=&quot;2705&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 생산성이지만, 규모가 커질수록 &amp;ldquo;핵심이 디테일에 예속&amp;rdquo;되는 비용으로 돌아온다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;2753&quot; data-start=&quot;2705&quot; data-ke-size=&quot;size26&quot;&gt;6. 은탄환은 없다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발업계에서 매우 유명한 말이다. 완벽한 해결방법은 없다는 것이다. 지금까지 클린아키텍처가 등장한 배경을 봤는데 그렇다고 클린아키텍처가 모든 문제를 완벽하게 해결하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린아키텍처는 장점이 명확하지만, 비용도 분명히 존재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일/계층/매핑이 늘어난다 (초기 생산성 하락)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Port/Adapter, 도메인 모델 vs JPA 엔티티 분리 같은 구조를 잡으면 클래스 수가 늘고, 매핑 코드도 생긴다. 작은 프로젝트에서는 &amp;ldquo;과하다&amp;rdquo;는 느낌이 들 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;잘못하면 &quot;추상화만 늘고&quot; 오히려 복잡해진다.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;913&quot; data-start=&quot;835&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;870&quot; data-start=&quot;835&quot;&gt;유스케이스가 사실상 CRUD인데도 포트를 과하게 쪼개거나&lt;/li&gt;
&lt;li data-end=&quot;913&quot; data-start=&quot;871&quot;&gt;의미 없는 인터페이스를 남발하면&lt;br /&gt;코드는 길어지는데 얻는 이득이 적다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;946&quot; data-start=&quot;915&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;변경 가능성이 높은 축에만 추상화&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;h3 data-end=&quot;946&quot; data-start=&quot;915&quot; data-ke-size=&quot;size23&quot;&gt;팀 합의가 없으면 일관성이 무너진다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안쪽에 스프링 어노테이션이 들어오거나, DTO/엔티티가 경계를 넘나들기 시작하면 구조는 &amp;ldquo;있는데 없는&amp;rdquo; 상태가 된다. 클린 아키텍처는 &amp;ldquo;원칙을 지키는 습관/규칙&amp;rdquo;이 같이 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발 속도가 중요한 단계에서는 부담이 될 수 있다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토타입/POC 단계에서는 &amp;ldquo;빠르게 검증&amp;rdquo;이 우선이라 단순한 레이어드 + 적절한 테스트로도 충분할 수 있다. 즉, &lt;b&gt;언제 도입할지 타이밍&lt;/b&gt;이 중요하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 그래서 언제 가치가 클까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 아키텍처가 빛나는 순간은 보통 이런 때다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1377&quot; data-start=&quot;1274&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1300&quot; data-start=&quot;1274&quot;&gt;기능이 늘고 변경이 잦다(정책 변화가 빈번)&lt;/li&gt;
&lt;li data-end=&quot;1333&quot; data-start=&quot;1301&quot;&gt;DB/외부 연동/메시지큐 등 디테일 교체 가능성이 있다&lt;/li&gt;
&lt;li data-end=&quot;1355&quot; data-start=&quot;1334&quot;&gt;테스트 속도가 개발 속도를 좌우한다&lt;/li&gt;
&lt;li data-end=&quot;1377&quot; data-start=&quot;1356&quot;&gt;팀이 커지고 코드 소유권이 분산된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1396&quot; data-start=&quot;1379&quot; data-ke-size=&quot;size16&quot;&gt;반대로 아래라면 과할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1468&quot; data-start=&quot;1397&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1416&quot; data-start=&quot;1397&quot;&gt;단기 POC / 일회성 프로젝트&lt;/li&gt;
&lt;li data-end=&quot;1442&quot; data-start=&quot;1417&quot;&gt;변경 가능성이 낮고 단순 CRUD에 가까움&lt;/li&gt;
&lt;li data-end=&quot;1468&quot; data-start=&quot;1443&quot;&gt;팀/조직이 아직 규칙을 유지할 여력이 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 넘어가기 전에 다음과 같은 것을 생각해 보자&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;545&quot; data-start=&quot;453&quot;&gt;&lt;b&gt;지금 내 구조에서 의존성 방향은 어디로 흐르고 있나?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;545&quot; data-start=&quot;495&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;545&quot; data-start=&quot;495&quot;&gt;&amp;ldquo;서비스(핵심)&amp;rdquo;가 &amp;ldquo;DB 라이브러리 타입&amp;rdquo;을 직접 참조하면 방향이 바깥으로 새는 신호&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;634&quot; data-start=&quot;547&quot;&gt;&lt;b&gt;내 테스트가 무거운 이유는 &amp;lsquo;DB가 필요해서&amp;rsquo;인가, &amp;lsquo;경계가 없어서&amp;rsquo;인가?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;634&quot; data-start=&quot;601&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;634&quot; data-start=&quot;601&quot;&gt;단위 테스트를 만들려면 스프링 컨텍스트/DB가 필수인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는 &amp;lsquo;안쪽은 바깥을 몰라야 한다&amp;rsquo;를 알아보겠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출처&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@inseo24/clean-architecture-01-03&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@inseo24/clean-architecture-01-03&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767332077329&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;만들면서 배우는 클린 아키텍처 01-03&quot; data-og-description=&quot;책을 읽자&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@inseo24/clean-architecture-01-03&quot; data-og-url=&quot;https://velog.io/@inseo24/clean-architecture-01-03&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgxIdS/hyZQ8QEQAE/EK3AAIQo5TjsGfLFZ4q2XK/img.png?width=940&amp;amp;height=722&amp;amp;face=0_0_940_722,https://scrap.kakaocdn.net/dn/J1OxD/hyZQE4Ibnd/jcPHbffsQpLuoBXckca7L1/img.png?width=940&amp;amp;height=722&amp;amp;face=0_0_940_722,https://scrap.kakaocdn.net/dn/gpSp0/hyZQCFMWwX/he6jrygmJtznuk4f6QliN1/img.png?width=940&amp;amp;height=722&amp;amp;face=0_0_940_722&quot;&gt;&lt;a href=&quot;https://velog.io/@inseo24/clean-architecture-01-03&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@inseo24/clean-architecture-01-03&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgxIdS/hyZQ8QEQAE/EK3AAIQo5TjsGfLFZ4q2XK/img.png?width=940&amp;amp;height=722&amp;amp;face=0_0_940_722,https://scrap.kakaocdn.net/dn/J1OxD/hyZQE4Ibnd/jcPHbffsQpLuoBXckca7L1/img.png?width=940&amp;amp;height=722&amp;amp;face=0_0_940_722,https://scrap.kakaocdn.net/dn/gpSp0/hyZQCFMWwX/he6jrygmJtznuk4f6QliN1/img.png?width=940&amp;amp;height=722&amp;amp;face=0_0_940_722');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;만들면서 배우는 클린 아키텍처 01-03&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;책을 읽자&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Dive/아키텍처</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/218</guid>
      <comments>https://gowoong.tistory.com/218#entry218comment</comments>
      <pubDate>Fri, 2 Jan 2026 14:39:08 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 18주차 Flash 기반 SSD</title>
      <link>https://gowoong.tistory.com/217</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아마 2025년 마지막 포스팅이자 OSTEP 스터디 정리가 될 것이다. 2026년에도 계속해서 OSTEP 내용들과 개발 관련 포스팅을 올릴 수 있도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 스토리지 기술의 중심인 &lt;b&gt;SSD(Solid State Drive)&lt;/b&gt;는 HDD와 달리 기계적 장치가 없으며, 반도체를 이용해 데이터를 저장한다. OSTEP(Operating Systems: Three Easy Pieces)에서 다루는 Flash 기반 SSD의 핵심 구조와 작동 원리, 그리고 운영체제가 직면한 과제들을 정리한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;1. 하드웨어 구조 (NAND Flash Structure)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;NAND 플래시 메모리는 데이터를 저장하기 위해 계층적인 구조를 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;뱅크(Bank) 및 다이(Die):&lt;/b&gt; 병렬 처리를 가능하게 하는 상위 단위이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;블록(Block):&lt;/b&gt; 여러 개의 페이지가 모여 하나의 블록을 이룬다. (예: 128KB, 256KB 등)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,2,0&quot;&gt;페이지(Page):&lt;/b&gt; 데이터를 읽고 쓰는 최소 단위이다. (예: 4KB)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&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;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PA5hk/dJMcadHnHu0/FoQo4h4f9A2NyeMfGkQakK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PA5hk/dJMcadHnHu0/FoQo4h4f9A2NyeMfGkQakK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PA5hk/dJMcadHnHu0/FoQo4h4f9A2NyeMfGkQakK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPA5hk%2FdJMcadHnHu0%2FFoQo4h4f9A2NyeMfGkQakK%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;1024&quot; height=&quot;1024&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;2. 플래시 메모리의 핵심 제약 사항&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;SSD를 이해하기 위해 반드시 알아야 할 물리적 특성이 존재한다. 이러한 제약들 때문에 SSD 컨트롤러는 복잡한 내부 로직을 수행해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;Read (읽기):&lt;/b&gt; 페이지 단위로 매우 빠르게 수행된다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;Program (쓰기):&lt;/b&gt; 페이지 단위로 수행되지만, 반드시 &lt;b data-index-in-node=&quot;33&quot; data-path-to-node=&quot;13,1,0&quot;&gt;빈(Erase된) 상태&lt;/b&gt;의 페이지에만 쓸 수 있다. 이미 데이터가 있는 페이지에는 덮어쓰기가 불가능하다. 이는 HDD와 가장 큰 차이점 중 하나이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;Erase (삭제):&lt;/b&gt; 페이지 단위가 아닌 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;13,2,0&quot;&gt;블록 단위&lt;/b&gt;로만 가능하다. 이 'Erase-before-write' 특성은 SSD 관리의 가장 큰 난제이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,3,0&quot;&gt;수명 (Wear-out):&lt;/b&gt; 각 블록은 삭제 및 프로그램 횟수(P/E Cycle)에 한계가 있어, 과도하게 사용하면 데이터를 저장할 수 없게 된다. 이는 플래시 메모리 셀의 물리적 특성에서 기인한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;3. FTL(Flash Translation Layer)의 설계 전략&lt;/h2&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;OS는 SSD를 '연속된 섹터의 배열'로 인식하고자 하지만, 실제 플래시는 '덮어쓰기 불가'라는 제약이 있다. 이를 해결하기 위해 SSD 컨트롤러 내부의 &lt;b data-index-in-node=&quot;86&quot; data-path-to-node=&quot;10&quot;&gt;FTL&lt;/b&gt;은 논리적 주소(LBA)를 물리적 주소(PBA)로 동적으로 매핑한다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;A. 로그 구조 매핑 (Log-structured Mapping)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;FTL은 HDD처럼 제자리에 덮어쓰지(Update-in-place) 않는다. 대신 새로운 데이터를 항상 &lt;b&gt;비어 있는 페이지에 추가(Append)&lt;/b&gt;하고, 매핑 테이블을 업데이트한다. 기존 데이터가 저장되었던 페이지는 '무효(Invalid)' 상태가 된다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;B. 매핑의 단위 (Granularity)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;페이지 수준 매핑(Page-level Mapping):&lt;/b&gt; 모든 페이지를 개별적으로 매핑한다. 유연성이 극대화되지만, 매핑 테이블의 크기가 너무 커져서 SSD의 메모리(DRAM)에 다 담기 힘들다는 단점이 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;블록 수준 매핑(Block-level Mapping):&lt;/b&gt; 블록 단위로만 매핑한다. 테이블 크기는 줄어들지만, 블록 내의 작은 데이터 하나만 수정해도 전체 블록을 새로 써야 하는 비효율이 발생한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,0&quot;&gt;혼합 매핑(Hybrid Mapping):&lt;/b&gt; OSTEP에서 중요하게 다루는 개념이다. 최근 쓰기는 페이지 단위로(Log Block), 오래된 데이터는 블록 단위로(Data Block) 관리하여 효율성과 공간 절약을 동시에 꾀한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size26&quot;&gt;4. 가비지 컬렉션(Garbage Collection)과 쓰기 증폭&lt;/h2&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;로그 구조로 데이터를 계속 쌓다 보면 결국 빈 공간이 부족해진다. 이때 FTL은 &lt;b data-index-in-node=&quot;45&quot; data-path-to-node=&quot;17&quot;&gt;가비지 컬렉션&lt;/b&gt;을 수행한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;Victim 블록 선정:&lt;/b&gt; 무효(Invalid) 페이지가 많은 블록을 선택한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;데이터 이동:&lt;/b&gt; 해당 블록에서 아직 살아있는(Valid) 페이지들만 골라 새로운 블록으로 옮긴다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,0&quot;&gt;블록 삭제:&lt;/b&gt; 이제 유효한 데이터가 없는 기존 블록을 통째로 Erase 하여 빈 공간으로 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 사용자가 요청한 1개 페이지 쓰기를 처리하기 위해, 내부적으로 여러 페이지를 읽고 쓰는 과정이 발생하는데 이를 &lt;b&gt;쓰기 증폭(Write Amplification)&lt;/b&gt;이라 한다. 이는 SSD 성능 저하의 주범이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;5. 수명 관리: 웨어 레벨링(Wear Leveling)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;플래시 셀은 지우기 횟수에 한계가 있다. 만약 특정 블록에만 쓰기 작업이 집중된다면 그 블록은 금방 고장 나게 된다. FTL은 모든 물리 블록에 대해 삭제/쓰기 횟수를 모니터링하며, 작업 부하를 전체 블록에 고르게 분산시키는 &lt;b data-index-in-node=&quot;126&quot; data-path-to-node=&quot;22&quot;&gt;웨어 레벨링&lt;/b&gt; 알고리즘을 수행하여 SSD 전체의 수명을 보장한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size26&quot;&gt;6. OS와의 협력: TRIM 명령&lt;/h2&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;OS 수준에서 파일이 삭제되어도 SSD는 이를 즉시 알 수 없다(그저 무효화된 데이터일 뿐이다). 이를 방치하면 가비지 컬렉션 시 불필요한 데이터 이동이 발생한다. &lt;b data-index-in-node=&quot;92&quot; data-path-to-node=&quot;25&quot;&gt;TRIM 명령&lt;/b&gt;은 OS가 특정 섹터의 데이터가 더 이상 필요 없음을 SSD에 명시적으로 알려주어, 가비지 컬렉션 효율을 높이고 쓰기 증폭을 줄이는 역할을 수행한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u6HrO/dJMcahwhn94/KrEImFxLQ2wWd02ZaZN6y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u6HrO/dJMcahwhn94/KrEImFxLQ2wWd02ZaZN6y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u6HrO/dJMcahwhn94/KrEImFxLQ2wWd02ZaZN6y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu6HrO%2FdJMcahwhn94%2FKrEImFxLQ2wWd02ZaZN6y1%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;1024&quot; height=&quot;1024&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/217</guid>
      <comments>https://gowoong.tistory.com/217#entry217comment</comments>
      <pubDate>Mon, 29 Dec 2025 12:08:31 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 17주차 Part.2 Data Integrity and Protection</title>
      <link>https://gowoong.tistory.com/216</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;파일 시스템이 저널링을 통해 크래시 일관성을 완벽하게 보장한다고 해도, 저장 장치(디스크) 자체가 데이터를 망가뜨린다면 아무 소용이 없다. 현대의 파일 시스템과 스토리지 시스템은 이러한 하드웨어 레벨의 데이터 손상을 탐지하고 복구하기 위해 다양한 기법을 사용한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;1. 디스크 실패의 유형 (Disk Failure Models)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;디스크는 완벽하지 않으며 다양한 방식으로 실패한다. 가장 기본적인 모델은 디스크가 완전히 고장 나는 &lt;b data-index-in-node=&quot;56&quot; data-path-to-node=&quot;5&quot;&gt;fail-stop&lt;/b&gt;이지만, 실제로는 더 까다로운 부분적 실패가 빈번하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;잠재적 섹터 오류 (Latent Sector Errors, LSEs)&lt;/b&gt;: 디스크 표면의 물리적 손상이나 헤드 문제 등으로 인해 특정 섹터를 읽거나 쓸 수 없는 상태가 된다. 디스크 컨트롤러가 오류를 감지하고 운영체제에 보고한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;조용한 데이터 부패 (Silent Data Corruption)&lt;/b&gt;: 가장 위험한 형태의 실패다. 펌웨어 버그, 전자기적 간섭 등으로 인해 디스크에 저장된 데이터의 비트가 바뀌지만, 디스크 컨트롤러가 이를 감지하지 못하고 정상적인 읽기 작업인 것처럼 손상된 데이터를 반환한다.&lt;/li&gt;
&lt;/ul&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;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OAGZw/dJMcac9uWUn/od9Kokkkis08Sej8T7rjPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OAGZw/dJMcac9uWUn/od9Kokkkis08Sej8T7rjPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OAGZw/dJMcac9uWUn/od9Kokkkis08Sej8T7rjPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOAGZw%2FdJMcac9uWUn%2Fod9Kokkkis08Sej8T7rjPk%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;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은 정상적인 읽기(왼쪽)와 두 가지 주요 디스크 실패 유형을 보여준다. 가운데 LSE는 물리적 손상으로 명시적인 에러를 반환하지만, 오른쪽의 조용한 데이터 부패는 에러 신호 없이 변질된 데이터가 그대로 전달된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size26&quot;&gt;2. 해결책: 체크섬 (Checksums)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;데이터 무결성을 보장하기 위한 핵심 기법은 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;11&quot;&gt;체크섬&lt;/b&gt;을 사용하는 것이다. 체크섬은 데이터 블록의 내용을 요약한 작은 크기의 해시 값이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;체크섬의 작동 원리:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;쓰기 시점&lt;/b&gt;: 데이터를 디스크에 쓸 때, 해당 데이터에 대한 체크섬을 계산하여 데이터와 함께 (또는 별도의 위치에) 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;읽기 시점&lt;/b&gt;: 디스크에서 데이터를 읽을 때, 함께 저장된 체크섬도 읽어온다. 동시에, 읽어온 데이터를 바탕으로 새로운 체크섬을 계산한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;검증&lt;/b&gt;: 저장되어 있던 체크섬과 새로 계산한 체크섬을 비교한다. 두 값이 일치하면 데이터가 안전하다고 판단하고, 일치하지 않으면 데이터가 손상된 것으로 간주한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size26&quot;&gt;3. 체크섬 알고리즘 (Checksum Algorithms)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;체크섬을 계산하는 방식은 다양하며, 계산 속도와 오류 감지 능력 사이의 트레이드오프가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;XOR&lt;/b&gt;: 가장 단순하고 빠른 방식이지만, 두 비트가 동시에 반전되는 등의 특정 오류 패턴을 감지하지 못하는 약점이 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;덧셈 (Addition)&lt;/b&gt;: 데이터를 바이트 단위로 더하고 2의 보수를 취하는 방식이다. XOR보다 약간 더 낫지만 여전히 충돌 가능성이 있다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,2,0&quot;&gt;플레처 체크섬 (Fletcher's Checksum)&lt;/b&gt;: 위치에 따른 가중치를 부여하여 단순 합계 방식의 약점을 보완했다. 계산이 비교적 간단하면서도 오류 감지 능력이 뛰어나 널리 사용된다 (예: ZFS).&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,3,0&quot;&gt;순환 중복 검사 (CRC)&lt;/b&gt;: 네트워크 통신 등에서 검증된 강력한 방식이다. 이진 다항식 나눗셈을 기반으로 하며, 일반적인 버스트 에러(연속적인 비트 손상)를 매우 효과적으로 감지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size26&quot;&gt;4. 실전 적용과 데이터 복구 (Practical Use and Recovery)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;체크섬만으로는 데이터 손상을 &lt;i data-index-in-node=&quot;16&quot; data-path-to-node=&quot;20&quot;&gt;탐지&lt;/i&gt;할 뿐 &lt;i data-index-in-node=&quot;22&quot; data-path-to-node=&quot;20&quot;&gt;복구&lt;/i&gt;할 수는 없다. 복구를 위해서는 &lt;b&gt;중복성(Redundancy)&lt;/b&gt;이 필수적이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21&quot;&gt;체크섬의 저장 위치:&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;체크섬을 데이터 블록 내부에 저장하는 방식은 구현이 간단하지만 공간 효율이 떨어진다. ZFS와 같은 현대적인 시스템은 &lt;b data-index-in-node=&quot;66&quot; data-path-to-node=&quot;22&quot;&gt;부모 포인터&lt;/b&gt;에 체크섬을 저장한다. 즉, 어떤 블록을 가리키는 간접 블록(indirect block)이나 아이노드(inode)에 그 블록의 주소와 함께 체크섬을 저장하는 방식이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23&quot;&gt;복구 메커니즘:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터를 읽을 때 체크섬 불일치가 감지되면, 시스템은 해당 데이터가 손상되었음을 알게 된다.&lt;/li&gt;
&lt;li&gt;시스템은 미러링된 다른 디스크나 RAID의 패리티 블록을 사용하여 손상된 데이터의 정상적인 복사본을 가져온다.&lt;/li&gt;
&lt;li&gt;가져온 정상 데이터로 손상된 블록을 덮어써서 복구한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;25&quot;&gt;스크러빙 (Scrubbing):&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;데이터 손상은 읽기 작업이 수행될 때만 감지된다. 잘 사용되지 않는 '차가운(cold)' 데이터는 오랫동안 손상된 채 방치될 수 있다. 이를 방지하기 위해 시스템은 백그라운드에서 주기적으로 모든 데이터를 읽고 체크섬을 검증하는 &lt;b data-index-in-node=&quot;127&quot; data-path-to-node=&quot;26&quot;&gt;디스크 스크러빙&lt;/b&gt; 작업을 수행하여 잠재적인 오류를 선제적으로 찾아내고 복구한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYMTup/dJMcaiu6Ry7/RmudeorREHnmqx663DMCK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYMTup/dJMcaiu6Ry7/RmudeorREHnmqx663DMCK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYMTup/dJMcaiu6Ry7/RmudeorREHnmqx663DMCK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYMTup%2FdJMcaiu6Ry7%2FRmudeorREHnmqx663DMCK0%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;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;위 순서도는 데이터 읽기 과정에서의 체크섬 검증 로직을 보여준다. 계산된 체크섬과 저장된 체크섬이 일치하지 않으면 데이터 손상으로 판단하고 복구 절차를 시작한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;파일 시스템은 단순히 데이터를 위치시키는 것을 넘어, 하드웨어의 불완전성을 극복하고 데이터의 무결성을 보장해야 하는 중요한 책임을 가진다. &lt;b data-index-in-node=&quot;78&quot; data-path-to-node=&quot;31&quot;&gt;저널링&lt;/b&gt;이 논리적인 일관성을 책임진다면, &lt;b data-index-in-node=&quot;100&quot; data-path-to-node=&quot;31&quot;&gt;체크섬&lt;/b&gt;과 &lt;b data-index-in-node=&quot;105&quot; data-path-to-node=&quot;31&quot;&gt;스크러빙&lt;/b&gt;은 물리적인 데이터 부패로부터 우리의 소중한 정보를 지키는 최후의 보루 역할을 수행한다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/216</guid>
      <comments>https://gowoong.tistory.com/216#entry216comment</comments>
      <pubDate>Tue, 23 Dec 2025 13:05:37 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 17주차 Part.1 Crash Consistency: FSCK and Journaling</title>
      <link>https://gowoong.tistory.com/215</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;파일 시스템은 데이터를 디스크에 영구적으로 저장해야 하지만, 시스템 충돌(Crash)이나 전원 차단은 예고 없이 발생한다. 이 장에서는 파일 시스템이 어떻게 이러한 위기 속에서 데이터의 일관성을 유지하는지 깊이 있게 다룬다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;1. 크래시 일관성 문제의 핵심 (The Problem)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;파일 시스템 업데이트는 단일 작업이 아니다. 책의 예시인 &lt;b&gt;'파일에 하나의 데이터 블록을 추가하는 상황(Append)'&lt;/b&gt;을 가정해 본다. 이 작업을 완수하려면 디스크 상의 세 가지 구조체를 수정해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;data bitmap&lt;/b&gt;: 새로운 블록이 할당되었음을 표시한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;inode&lt;/b&gt;: 파일 크기를 갱신하고 새 블록을 가리키는 포인터를 추가한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;data block (Db)&lt;/b&gt;: 실제 사용자 데이터를 쓴다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;문제는 디스크가 이 세 가지 쓰기 작업을 동시에 '원자적(Atomic)'으로 처리할 수 없다는 점이다. 만약 작업 도중 충돌이 발생하여 일부만 기록된다면, 파일 시스템은 일관성이 깨진 상태가 된다.&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;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VdwVr/dJMcabJzFxJ/6NAWwOEl2c5xf3YWypOC31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VdwVr/dJMcabJzFxJ/6NAWwOEl2c5xf3YWypOC31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VdwVr/dJMcabJzFxJ/6NAWwOEl2c5xf3YWypOC31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVdwVr%2FdJMcabJzFxJ%2F6NAWwOEl2c5xf3YWypOC31%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;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지는 Inode와 Data Bitmap은 업데이트되었지만, 실제 Data Block 기록 중에 충돌이 발생한 상황을 보여준다. 이 경우 파일 시스템은 존재하지 않는 쓰레기 데이터를 가리키게 된다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;발생 가능한 시나리오&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;데이터 블록만 쓰인 경우&lt;/b&gt;: 파일 시스템 입장에서는 아무 일도 일어나지 않은 것과 같으나, 데이터는 디스크 어딘가에 유령처럼 남는다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;메타데이터(Inode/Bitmap)만 쓰인 경우&lt;/b&gt;: 파일 시스템은 데이터가 있다고 믿지만, 실제 블록에는 과거의 쓰레기 데이터가 들어있어 &lt;b&gt;메타데이터 부패(Metadata Corruption)&lt;/b&gt;가 발생한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;비트맵만 업데이트된 경우&lt;/b&gt;: 해당 블록은 사용 중으로 표시되지만 어떤 파일도 이를 소유하지 않는 &lt;b&gt;스페이스 리크(Space Leak)&lt;/b&gt;가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;2. 고전적 해결책: FSCK (File System Checker)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;초기 시스템은 충돌을 막으려 하지 않고, 문제가 생긴 후 고치는 방식을 택했다. 그것이 바로 FSCK다. FSCK는 시스템 부팅 시 실행되며 디스크의 모든 메타데이터를 전수 조사한다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;FSCK의 상세 점검 단계&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;슈퍼블록 점검&lt;/b&gt;: 파일 시스템의 크기가 합리적인지 등 기본 정보를 확인한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;프리 블록 비트맵 점검&lt;/b&gt;: 모든 inode를 스캔하여 어떤 블록이 할당되었는지 확인하고, 이를 바탕으로 비트맵을 강제로 재구축하여 일치시킨다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,0&quot;&gt;Inode 상태 및 링크 카운트&lt;/b&gt;: 파일과 디렉토리 연결 구조를 전부 확인하여 inode에 기록된 링크 수와 실제 참조 수가 맞는지 교정한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,3,0&quot;&gt;중복 할당 확인&lt;/b&gt;: 두 개의 파일이 동일한 블록을 가리키는 경우를 찾아 해결한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,4,0&quot;&gt;디렉토리 체크&lt;/b&gt;: 디렉토리 구조 내의 .과 .. 포인터가 올바른지 확인한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15&quot;&gt;한계점&lt;/b&gt;: 디스크가 수 테라바이트(TB) 규모로 커지면서 모든 블록을 스캔하는 데 수 시간이 걸리게 되었다. 이는 가용성 면에서 치명적인 결함이 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;3. 현대적 해결책: 저널링 (Journaling/WAL)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;저널링은 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;18&quot;&gt;&quot;Write-Ahead Logging&quot;&lt;/b&gt; 개념을 파일 시스템에 도입한 것이다. 실제 데이터를 수정하기 전, 수행할 작업에 대한 노트를 먼저 남기는 방식이다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;데이터 저널링의 5단계 과정 (Data Journaling)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;책에서는 이를 아래와 같은 단계로 상세히 설명한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;21&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,0,0&quot;&gt;Journal Write&lt;/b&gt;: 트랜잭션의 시작을 알리는 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;21,0,0&quot;&gt;TxB(Transaction Begin)&lt;/b&gt;, 수정될 비트맵, inode, 그리고 실제 데이터(Db)를 저널 영역에 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,1,0&quot;&gt;Journal Commit&lt;/b&gt;: 트랜잭션의 끝을 알리는 &lt;b data-index-in-node=&quot;29&quot; data-path-to-node=&quot;21,1,0&quot;&gt;TxE(Transaction End)&lt;/b&gt; 블록을 쓴다. 이 블록이 디스크에 안전하게 기록되어야 트랜잭션이 완료된 것으로 간주한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,2,0&quot;&gt;Checkpoint&lt;/b&gt;: 이제 안심하고 실제 파일 시스템의 위치(고정 위치)에 데이터와 메타데이터를 업데이트한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,3,0&quot;&gt;Free&lt;/b&gt;: 체크포인트가 끝나면 저널 내의 해당 트랜잭션을 해제하여 공간을 재사용한다.&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;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q8aDG/dJMcai2Wb90/okp6Kifko6NwqEaRAZmE80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q8aDG/dJMcai2Wb90/okp6Kifko6NwqEaRAZmE80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q8aDG/dJMcai2Wb90/okp6Kifko6NwqEaRAZmE80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq8aDG%2FdJMcai2Wb90%2Fokp6Kifko6NwqEaRAZmE80%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;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지는 저널링 로그의 구조를 보여준다. 트랜잭션 시작(TxB)부터 끝(TxE)까지의 기록이 순차적으로 저장되며, 이 기록을 바탕으로 복구가 이루어진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size26&quot;&gt;4. 성능 최적화: 메타데이터 저널링 (Ordered Journaling)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 매번 두 번(저널에 한 번, 실제 위치에 한 번) 쓰는 것은 너무 느리다. 그래서 현대의 &lt;b data-index-in-node=&quot;55&quot; data-path-to-node=&quot;25&quot;&gt;ext3, ext4&lt;/b&gt;는 '메타데이터 저널링' 방식을 주로 사용한다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;26&quot; data-ke-size=&quot;size23&quot;&gt;Ordered Journaling의 작동 원리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,0,0&quot;&gt;데이터 쓰기&lt;/b&gt;: 사용자 데이터(Db)를 실제 위치에 먼저 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,1,0&quot;&gt;메타데이터 저널링&lt;/b&gt;: 메타데이터(Inode, Bitmap)만 저널에 기록하고 커밋한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;27,2,0&quot;&gt;체크포인트&lt;/b&gt;: 메타데이터를 실제 위치에 업데이트한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;28&quot;&gt;왜 이 순서인가?&lt;/b&gt; 데이터를 먼저 쓰고 메타데이터를 나중에 저널링함으로써, 메타데이터가 업데이트되었을 때 해당 포인터가 이미 유효한 데이터를 가리키고 있음을 보장한다. 만약 데이터 쓰기 도중 충돌하면 메타데이터는 저널에 기록되지 않으므로 시스템 일관성은 완벽하게 유지된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;5. 요약: 복구 메커니즘&lt;/h2&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;충돌 후 재부팅 시 시스템은 저널을 살핀다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;32&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,0,0&quot;&gt;TxB는 있는데 TxE가 없다면?&lt;/b&gt;: 트랜잭션이 불완전하므로 해당 로그는 무시한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;32,1,0&quot;&gt;TxE까지 완벽하게 기록되어 있다면?&lt;/b&gt;: 체크포인트 도중 충돌한 것이므로 저널의 내용을 다시 실제 위치에 덮어쓴다(&lt;b data-index-in-node=&quot;63&quot; data-path-to-node=&quot;32,1,0&quot;&gt;Redo Log&lt;/b&gt;). 이 과정은 전체 디스크 스캔이 필요 없으므로 수 초 내에 끝난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;이러한 저널링 기법은 파일 시스템의 신뢰성을 획기적으로 높였으며, 오늘날 거의 모든 범용 파일 시스템의 표준이 되었다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/215</guid>
      <comments>https://gowoong.tistory.com/215#entry215comment</comments>
      <pubDate>Tue, 23 Dec 2025 12:49:28 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 16주차 Fast File System (FFS) 와 로그 구조화 파일 시스템 (LFS)</title>
      <link>https://gowoong.tistory.com/214</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Fast File System (FFS)&lt;/b&gt;은 1984년 Berkeley의 Kirk McKusick이 기존 UNIX 파일 시스템(UFS, Old Unix File System)의 성능 문제점을 해결하기 위해 설계한 파일 시스템이다. FFS의 핵심은 &lt;b&gt;디스크의 지역성(Locality)&lt;/b&gt;을 활용하여 I/O 효율을 극대화하는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Fast&amp;nbsp;File&amp;nbsp;System&amp;nbsp;(FFS)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;1. FFS 탄생 배경: 기존 UNIX FS의 문제점&lt;/h3&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;기존 UNIX 파일 시스템(UFS)은 단순하고 깔끔했지만, 다음 두 가지 주요 문제 때문에 성능이 매우 나빴다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;작은 블록 크기 (Small Block Size):&lt;/b&gt; UFS는 512 바이트의 작은 블록 크기를 사용했다. 이는 하나의 파일을 읽을 때 디스크 I/O 횟수가 많아져 효율성이 떨어지고, 파일 전체를 읽는 데 많은 시간이 걸렸다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;데이터 지역성 부재 (Lack of Data Locality):&lt;/b&gt; UFS는 Inode(파일 메타데이터)와 데이터 블록을 디스크 전체에 무작위로 할당하는 경향이 있었다. 파일의 Inode를 읽고 해당 데이터 블록을 읽기 위해 디스크 헤드가 먼 거리를 이동해야 했으므로 &lt;b&gt;탐색 시간(Seek Time)&lt;/b&gt;이 길어지고 회전 지연(Rotational Latency)이 증가하여 성능이 크게 저하되었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;2. FFS의 주요 개선 사항 (핵심 메커니즘)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;FFS는 하드웨어, 특히 &lt;b&gt;디스크의 기하학적 구조(Disk Geometry)&lt;/b&gt;를 인지하는 설계를 도입하여 이러한 문제를 해결했다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size20&quot;&gt;① 큰 블록 크기 도입&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;개선:&lt;/b&gt; FFS는 블록 크기를 4KB 또는 8KB로 늘렸다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;효과:&lt;/b&gt; 한 번의 디스크 I/O로 더 많은 데이터를 전송할 수 있게 되어, 파일당 필요한 I/O 횟수가 줄고, &lt;b&gt;전송 효율(Transfer Efficiency)&lt;/b&gt;이 극대화되었다. (단, 작은 파일의 내부 단편화(Internal Fragmentation) 문제가 생길 수 있어, 이를 해결하기 위해 &lt;b data-index-in-node=&quot;167&quot; data-path-to-node=&quot;11,1,0&quot;&gt;프래그먼트(Fragment)&lt;/b&gt; 개념을 도입했다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size20&quot;&gt;② 실린더 그룹 (Cylinder Group, CG)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;개념:&lt;/b&gt; 디스크를 논리적인 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;13,0,0&quot;&gt;실린더 그룹&lt;/b&gt; 단위로 나눈다. 각 실린더 그룹은 디스크의 연속된 트랙(Cylinder) 묶음으로 구성된다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;구조:&lt;/b&gt; 각 CG는 자체적인 &lt;b data-index-in-node=&quot;15&quot; data-path-to-node=&quot;13,1,0&quot;&gt;슈퍼블록 복사본&lt;/b&gt;, &lt;b data-index-in-node=&quot;25&quot; data-path-to-node=&quot;13,1,0&quot;&gt;자유 공간 비트맵&lt;/b&gt;, &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;13,1,0&quot;&gt;Inode 목록&lt;/b&gt;, 그리고 &lt;b data-index-in-node=&quot;50&quot; data-path-to-node=&quot;13,1,0&quot;&gt;데이터 블록&lt;/b&gt; 영역을 가진다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;효과:&lt;/b&gt; 파일의 Inode와 데이터 블록을 같은 실린더 그룹 내에 할당하려고 시도한다. Inode와 데이터가 지리적으로 가까워지면서 디스크 헤드의 이동 거리(Seek Time)가 최소화되고 성능이 크게 향상되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size20&quot;&gt;③ 지역성을 위한 할당 정책&lt;/h4&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;FFS는 지역성을 높이기 위해 다음과 같은 할당 정책을 따른다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,0,0&quot;&gt;새로운 파일 할당:&lt;/b&gt; 새로운 디렉토리에 파일을 만들 때, FFS는 &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;16,0,0&quot;&gt;현재 디렉토리와 다른 실린더 그룹&lt;/b&gt;을 선택하려고 노력한다. 이는 여러 디렉토리에 걸쳐 I/O를 분산시키기 위함이다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,1,0&quot;&gt;동일 파일 블록 할당:&lt;/b&gt; 파일의 첫 번째 데이터 블록을 할당한 후, 나머지 데이터 블록들은 &lt;b data-index-in-node=&quot;50&quot; data-path-to-node=&quot;16,1,0&quot;&gt;가급적 동일한 실린더 그룹 내의 연속된 영역&lt;/b&gt;에 할당하려고 시도한다. 이는 파일을 순차적으로 읽을 때 탐색 시간을 최소화한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16,2,0&quot;&gt;데이터와 메타데이터의 근접성:&lt;/b&gt; Inode와 해당 파일의 데이터 블록을 &lt;b data-index-in-node=&quot;39&quot; data-path-to-node=&quot;16,2,0&quot;&gt;같은 실린더 그룹 내&lt;/b&gt;에 배치하여 메타데이터 접근 후 데이터 접근 시의 헤드 이동을 최소화한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;FFS는 &quot;디스크의 구조를 인지하고 데이터를 할당하자&quot;는 철학을 바탕으로 &lt;b&gt;지역성(Locality)&lt;/b&gt;을 극대화하여 기존 UFS의 고질적인 성능 문제를 해결했다. FFS의 설계는 이후 모든 파일 시스템 설계에 큰 영향을 미쳤다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;LFS(Log-structured File System)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;LFS(Log-structured File System)는 1990년대 초 버클리 대학에서 제안된 혁신적인 파일 시스템이다. FFS가 디스크의 지역성을 개선했다면, LFS는 &lt;b&gt;&quot;모든 쓰기를 순차적(Sequential)으로 처리하여 디스크 성능을 극대화하자&quot;&lt;/b&gt;는 아이디어에서 출발했다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;1. LFS의 탄생 배경 (Why LFS?)&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;메모리 크기의 증가:&lt;/b&gt; 시스템 메모리(RAM)가 커지면서 읽기 요청은 대부분 캐시에서 처리된다. 따라서 파일 시스템의 성능은 결국 &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;5,0,0&quot;&gt;쓰기 성능&lt;/b&gt;에 의해 결정되게 되었다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,0&quot;&gt;임의 쓰기(Random Write)의 한계:&lt;/b&gt; 기존 FFS는 메타데이터와 데이터를 업데이트할 때 디스크의 여러 위치를 오가며 쓰기 때문에 탐색 시간(Seek Time)이 많이 발생한다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,2,0&quot;&gt;순차 쓰기의 압도적 속도:&lt;/b&gt; 하드 디스크는 임의 쓰기보다 순차 쓰기가 수십 배 이상 빠르다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;2. 핵심 메커니즘: 쓰기 버퍼링과 로그 (The Write Buffer)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;LFS는 데이터를 바로 디스크에 쓰지 않는다. 대신 메모리에 &lt;b&gt;'세그먼트(Segment)'&lt;/b&gt;라고 불리는 큰 버퍼를 두고 쓰기 요청을 모은다. 버퍼가 가득 차면, 이를 디스크의 비어 있는 연속된 공간에 &lt;b data-index-in-node=&quot;116&quot; data-path-to-node=&quot;7&quot;&gt;단 한 번의 순차 쓰기&lt;/b&gt;로 기록한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,0,0&quot;&gt;로그(Log) 구조:&lt;/b&gt; 디스크는 하나의 거대한 로그처럼 취급된다. 새로운 데이터, 수정된 데이터, 심지어 아이노드(Inode)까지도 항상 로그의 끝에 추가(Append)된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;3. 아이노드 맵 (Inode Map, imap)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;데이터가 항상 다른 위치에 써진다면, 아이노드(Inode)의 위치도 계속 변하게 된다. 이를 해결하기 위해 LFS는 &lt;b data-index-in-node=&quot;66&quot; data-path-to-node=&quot;10&quot;&gt;아이노드 맵&lt;/b&gt;이라는 간접 층을 도입했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;imap:&lt;/b&gt; 아이노드 번호를 입력하면 현재 그 아이노드가 디스크 어디에 있는지(주소)를 알려주는 테이블.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;체크포인트 지역(CR):&lt;/b&gt; imap 자체의 위치를 알기 위해 디스크의 고정된 장소에 CR을 둔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size26&quot;&gt;4. 가비지 컬렉션 (Garbage Collection / Cleaning)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 수정할 때 기존 데이터를 덮어쓰지 않고 새로운 곳에 쓰기 때문에, 디스크에는 &lt;b&gt;'오래된 데이터(Garbage)'&lt;/b&gt;가 남게 된다. 이를 정리하여 빈 공간을 만드는 과정이 필요하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;청소기(Cleaner) 동작:&lt;/b&gt; 가득 찬 세그먼트들을 읽어 들여 현재 유효한(Live) 데이터만 골라낸다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;새로운 세그먼트 생성:&lt;/b&gt; 살아남은 데이터들만 모아서 다시 새로운 세그먼트에 순차적으로 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,0&quot;&gt;공간 확보:&lt;/b&gt; 데이터가 모두 옮겨진 옛날 세그먼트들은 이제 빈 공간으로 재사용된다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/214</guid>
      <comments>https://gowoong.tistory.com/214#entry214comment</comments>
      <pubDate>Tue, 23 Dec 2025 12:21:09 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 15주차 Part.2 파일 시스템 구현</title>
      <link>https://gowoong.tistory.com/213</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;디스크는 그저 거대한 &lt;b&gt;블록(Block)&lt;/b&gt;들의 배열일 뿐이다. 파일 시스템의 목표는 이 수많은 블록들을 관리하여 사용자가 편리하게 파일과 디렉터리를 쓸 수 있게 만드는 것이다. 어떻게 맨땅(Raw Disk)에 파일 시스템을 구축하는지, 그 구조부터 살펴보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size26&quot;&gt;1. 디스크 구조 잡기 (On-Disk Structures)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;먼저 디스크를 &lt;b&gt;블록 단위&lt;/b&gt;로 나눈다. OSTEP에서는 블록 크기를 &lt;b&gt;4KB&lt;/b&gt;로 가정한다. 총 64개의 블록이 있다고 칠 때, VSFS는 이를 용도별로 구획한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beTjBx/dJMcadN0Bt5/xPb0NjcgNKySpYvdX3w7YK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beTjBx/dJMcadN0Bt5/xPb0NjcgNKySpYvdX3w7YK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beTjBx/dJMcadN0Bt5/xPb0NjcgNKySpYvdX3w7YK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeTjBx%2FdJMcadN0Bt5%2FxPb0NjcgNKySpYvdX3w7YK%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;272&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 영역 (Data Region):&lt;/b&gt; 파일의 실제 내용이 저장되는 곳이다. 디스크의 대부분을 차지한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아이노드 테이블 (Inode Table):&lt;/b&gt; 파일들의 메타데이터(크기, 권한, 소유자 등)를 저장하는 &lt;b&gt;아이노드(inode)&lt;/b&gt;들이 모여 있는 곳이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비트맵 (Bitmaps):&lt;/b&gt; 빈 공간 관리를 위한 지도다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Data Bitmap:&lt;/b&gt; 어떤 데이터 블록이 사용 중인지(1) 또는 비어있는지(0) 표시.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Inode Bitmap:&lt;/b&gt; 어떤 아이노드가 사용 중인지 표시.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;슈퍼블록 (Superblock):&lt;/b&gt; 파일 시스템 전체의 정보(총 블록 수, 아이노드 테이블 시작 위치, 매직 넘버 등)를 담고 있다. 마운트 할 때 가장 먼저 읽는 곳이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;2. 핵심 자료구조: 아이노드 (The Inode)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아이노드(Index Node)&lt;/b&gt;는 파일 시스템의 심장이다. 파일 하나당 하나의 아이노드가 할당된다. (이름은 없고 번호로 식별된다.)&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;아이노드에는 무엇이 들어있나?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메타데이터:&lt;/b&gt; 파일 종류(일반/디렉터리), 크기, 권한(rwx), 소유자, 시간 정보(mtime 등), 링크 카운트 등.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 위치 포인터:&lt;/b&gt; 파일의 실제 내용이 담긴 &lt;b&gt;데이터 블록의 위치&lt;/b&gt;를 가리키는 주소들.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;큰 파일은 어떻게 처리하나? (The Multi-Level Index)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;아이노드의 크기는 보통 128바이트나 256바이트로 작다. 여기에 수 기가바이트짜리 파일의 모든 블록 주소를 다 넣을 수는 없다. 그래서 &lt;b&gt;다단계 인덱스(Multi-Level Index)&lt;/b&gt; 방식을 쓴다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqo2w/dJMcajm9M6U/t2rX3LgyPfskyXzDvQQVi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqo2w/dJMcajm9M6U/t2rX3LgyPfskyXzDvQQVi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqo2w/dJMcajm9M6U/t2rX3LgyPfskyXzDvQQVi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnqo2w%2FdJMcajm9M6U%2Ft2rX3LgyPfskyXzDvQQVi0%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;272&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;직접 포인터 (Direct Pointers):&lt;/b&gt; 처음 12개 정도의 포인터는 실제 데이터 블록을 직접 가리킨다. 작은 파일은 이것만으로 충분하며 매우 빠르다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;간접 포인터 (Indirect Pointer):&lt;/b&gt; 파일이 커지면 쓰인다. 이 포인터가 가리키는 블록에는 데이터가 아니라 &lt;b&gt;&quot;데이터 블록들의 주소 목록&quot;&lt;/b&gt;이 들어있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이중 간접 포인터 (Double Indirect Pointer):&lt;/b&gt; 더 큰 파일을 위해, &quot;포인터를 가리키는 포인터 블록&quot;을 가리킨다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;삼중 간접 포인터 (Triple Indirect Pointer):&lt;/b&gt; 정말 거대한 파일(TB 단위 등)을 지원한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 작은 파일은 빠르게, 큰 파일은 (약간의 오버헤드를 감수하고) 유연하게 지원할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size26&quot;&gt;3. 디렉터리 구조 (Directory Organization)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;앞서 말했듯 디렉터리도 파일이다. 디렉터리의 데이터 블록에는 무엇이 들어있을까? 단순하게 &lt;b&gt;(파일 이름, 아이노드 번호, 레코드 길이)&lt;/b&gt;의 리스트가 저장된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdYlBH/dJMcaaRlnTw/tIgkBLSrNB3cxANdKE1CZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdYlBH/dJMcaaRlnTw/tIgkBLSrNB3cxANdKE1CZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdYlBH/dJMcaaRlnTw/tIgkBLSrNB3cxANdKE1CZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdYlBH%2FdJMcaaRlnTw%2FtIgkBLSrNB3cxANdKE1CZ0%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;1024&quot; height=&quot;559&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiP4-Leya-RAxUAAAAAHQAAAAAQuAU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct dir_entry {
    char name[256]; // 파일 이름
    int  inum;      // 아이노드 번호
    int  reclen;    // 레코드 길이 (삭제된 공간 관리용)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;파일 찾기:&lt;/b&gt; ls를 치면 이 리스트를 순차적으로 읽어서 보여준다. (선형 탐색)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파일 삭제:&lt;/b&gt; 파일을 지우면 리스트 중간에 빈 공간이 생긴다. 이를 효율적으로 재사용하기 위해 reclen 같은 필드를 둔다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최적화:&lt;/b&gt; 파일이 아주 많은 디렉터리의 경우 선형 탐색이 느리므로, B-Tree나 해시 테이블을 사용하기도 한다(XFS, ext4 등).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size26&quot;&gt;4. 빈 공간 관리 (Free Space Management)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;새 파일을 만들거나 내용을 추가하려면 빈 아이노드와 빈 데이터 블록을 찾아야 한다. VSFS는 &lt;b&gt;비트맵(Bitmap)&lt;/b&gt;을 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0: 비어있음 (Free)&lt;/li&gt;
&lt;li&gt;1: 사용 중 (Allocated)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;비트맵은 공간을 적게 차지하고(4KB 블록 하나로 32,000개의 블록 상태 표현 가능), 연속된 빈 공간을 찾기 쉽다는 장점이 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size26&quot;&gt;5. 접근 경로 추적 (Access Paths: Reading and Writing)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;파일 시스템 구현을 이해했는지 확인하는 가장 좋은 방법은, 파일 I/O 시 디스크에서 어떤 일이 일어나는지 추적해 보는 것이다. (이 과정의 비효율성을 느끼는 것이 포인트다.)&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;32&quot; data-ke-size=&quot;size23&quot;&gt;시나리오 1: 파일 읽기 (/foo/bar 읽기)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;33&quot; data-ke-size=&quot;size16&quot;&gt;파일을 하나 읽으려는데 디스크 헤드는 엄청나게 바쁘게 움직인다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;34&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;루트 디렉터리 읽기:&lt;/b&gt; 루트 inode(보통 2번) 읽기 -&amp;gt; 루트 데이터 읽기 (foo 찾음 -&amp;gt; inode 번호 획득)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;foo 디렉터리 읽기:&lt;/b&gt; foo inode 읽기 -&amp;gt; foo 데이터 읽기 (bar 찾음 -&amp;gt; inode 번호 획득)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;bar 파일 열기:&lt;/b&gt; bar inode 읽기 -&amp;gt; open() 완료 (fd 반환)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;bar 파일 읽기:&lt;/b&gt; bar inode 읽기(블록 위치 확인) -&amp;gt; bar 데이터 읽기 -&amp;gt; bar inode 쓰기(atime 업데이트)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-path-to-node=&quot;35&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;35,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점:&lt;/b&gt; 파일 하나 읽는데 I/O가 너무 많이 발생한다! 특히 디렉터리 계층을 타고 내려가는 비용이 크다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-path-to-node=&quot;36&quot; data-ke-size=&quot;size23&quot;&gt;시나리오 2: 파일 생성 및 쓰기 (/foo/bar 생성)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;쓰기는 더 복잡하다. 메타데이터 업데이트(비트맵, 아이노드)가 추가되기 때문이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;38&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;foo 디렉터리 읽기:&lt;/b&gt; (이름 중복 확인)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아이노드 할당:&lt;/b&gt; Inode Bitmap 읽기 -&amp;gt; Inode Bitmap 쓰기(1로 변경) -&amp;gt; 새 Inode 쓰기(초기화)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디렉터리 갱신:&lt;/b&gt; foo 데이터 읽기 -&amp;gt; foo 데이터 쓰기(새 파일 이름 추가) -&amp;gt; foo Inode 쓰기(크기/시간 갱신)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실제 데이터 쓰기:&lt;/b&gt; Data Bitmap 읽기 -&amp;gt; Data Bitmap 쓰기 -&amp;gt; 데이터 블록 쓰기 -&amp;gt; Inode 쓰기(블록 포인터 추가)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-path-to-node=&quot;39&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;39,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점:&lt;/b&gt; 쓰기 작업 한번 할 때마다 비트맵, 아이노드, 데이터 블록을 오가며 디스크 I/O가 폭발한다. 이를 &lt;b&gt;&quot;Write Traffic&quot;&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;41&quot; data-ke-size=&quot;size26&quot;&gt;6. 캐싱과 버퍼링 (Caching and Buffering)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;위의 끔찍한 I/O 비용을 해결하기 위해 현대 OS는 적극적인 캐싱을 도입한다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;43&quot; data-ke-size=&quot;size23&quot;&gt;읽기 성능 향상: 통합 페이지 캐시 (Unified Page Cache)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;44&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 시스템은 파일 시스템용 고정 메모리 영역을 뒀지만 낭비가 심했다.&lt;/li&gt;
&lt;li&gt;현대 시스템은 &lt;b&gt;남는 메모리(RAM)를 전부 페이지 캐시로 활용&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;자주 읽는 디렉터리나 파일은 메모리에 올라와 있으므로, 위의 시나리오에서 &quot;루트 읽기&quot;, &quot;foo 읽기&quot; 등은 대부분 디스크 접근 없이 메모리에서 즉시 처리된다. (Cache Hit)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;45&quot; data-ke-size=&quot;size23&quot;&gt;쓰기 성능 향상: 쓰기 버퍼링 (Write Buffering)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;46&quot; data-ke-size=&quot;size16&quot;&gt;write() 호출 시 디스크에 바로 쓰지 않고 메모리에 모아뒀다가 나중에 쓴다(Delayed Write). 보통 5~30초 뒤에 쓴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;47&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점 1 (Batching):&lt;/b&gt; 작은 I/O들을 모아서 한 번의 큰 I/O로 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점 2 (Scheduling):&lt;/b&gt; 디스크 헤드 움직임을 최소화하는 순서로 정렬해서 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점 3 (Avoidance):&lt;/b&gt; 파일을 만들고 바로 지우면, 아예 디스크에 쓸 필요가 없어진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; 전원이 나가면 버퍼에 있던 데이터는 사라진다. (그래서 fsync가 필요하다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;49&quot; data-ke-size=&quot;size26&quot;&gt;요약 (Summary)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;50&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;아이노드(Inode)&lt;/b&gt;는 파일의 모든 정보(메타데이터, 데이터 위치)를 담고 있는 핵심 자료구조다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다단계 인덱스(Multi-Level Index)&lt;/b&gt; 구조를 통해 작은 파일의 효율성과 큰 파일의 확장성을 모두 잡았다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디렉터리&lt;/b&gt;는 그저 (이름, 아이노드 번호) 목록을 담은 파일일 뿐이다.&lt;/li&gt;
&lt;li&gt;파일 시스템 작업(읽기/쓰기)은 수많은 &lt;b&gt;메타데이터 I/O&lt;/b&gt;를 유발한다.&lt;/li&gt;
&lt;li&gt;이 성능 저하를 막기 위해 OS는 &lt;b&gt;시스템 메모리(RAM)를 적극적으로 캐시&lt;/b&gt;로 사용하여 디스크 접근을 최소화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;51&quot; data-ke-size=&quot;size16&quot;&gt;이제 우리는 디스크 위에 파일 시스템이 어떻게 &quot;구현&quot;되어 있는지 알게 되었다. 하지만 여기서 끝이 아니다. &lt;b&gt;&quot;쓰기 도중에 전원이 나가면 파일 시스템이 깨지는 문제(Crash Consistency)&quot;&lt;/b&gt;는 어떻게 해결할까? 이는 다음 장에서 다루게 된다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/213</guid>
      <comments>https://gowoong.tistory.com/213#entry213comment</comments>
      <pubDate>Tue, 9 Dec 2025 12:55:02 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 15주차 Part.1 파일과 디렉터리</title>
      <link>https://gowoong.tistory.com/212</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;운영체제의 가상화(Virtualization) 파트를 지나,&lt;/span&gt;&lt;span&gt; 이제 &lt;b&gt;영속성(Persistence)&lt;/b&gt;의 세계로 들어왔다.&lt;/span&gt;&lt;span&gt; 영속성의 핵심은 전원이 꺼져도 데이터가 날아가지 않게 하는 것이다.&lt;/span&gt;&lt;span&gt; 이를 위해 운영체제는 &lt;b&gt;파일 시스템(File System)&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;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size26&quot;&gt;1. 파일과 디렉터리: 기본 개념 (Files and Directories)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;파일 시스템은 데이터를 저장하기 위해 두 가지 핵심 추상화를 제공한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;파일 (File):&lt;/b&gt; 운영체제 입장에서 파일은 그저 &lt;b&gt;바이트의 선형 배열(Linear Array of Bytes)&lt;/b&gt;일 뿐이다. 파일의 내용(이미지인지, 텍스트인지)은 OS가 알 바 아니다. 각 파일은 &lt;b&gt;inode number(아이노드 번호)&lt;/b&gt;라는 저수준의 이름으로 식별된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디렉터리 (Directory):&lt;/b&gt; 디렉터리도 파일이다. 다만 그 내용이 &lt;b&gt;(사용자가 읽을 수 있는 이름, inode number)&lt;/b&gt;의 매핑 정보 리스트라는 점이 특별하다. 디렉터리 구조는 트리(Tree) 형태를 띤다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;2. 파일 생성과 읽기/쓰기 (open, read, write)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;파일을 다루는 가장 기본적인 작업이다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;파일 생성 (open)&lt;/h3&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7k-Drva-RAxUAAAAAHQAAAAAQhgQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;int fd = open(&quot;foo&quot;, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;플래그(Flags):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;O_CREAT: 파일이 없으면 생성한다.&lt;/li&gt;
&lt;li&gt;O_WRONLY: 쓰기 전용으로 연다.&lt;/li&gt;
&lt;li&gt;O_TRUNC: 파일이 이미 있으면 내용을 싹 비우고(0바이트로 만듦) 연다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값 (File Descriptor):&lt;/b&gt; 성공 시 정수(fd)를 반환한다. 이 fd는 프로세스마다 개인적으로 관리하는 배열의 인덱스다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;읽기와 쓰기 (read, write)&lt;/h3&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7k-Drva-RAxUAAAAAHQAAAAAQhwQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 파일을 읽는 명령어 cat의 동작 원리
while ((bytes_read = read(fd_in, buffer, sizeof(buffer))) &amp;gt; 0) {
    write(fd_out, buffer, bytes_read);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;read()와 write()는 현재 파일의 &lt;b&gt;오프셋(Offset)&lt;/b&gt; 위치에서 작업을 수행하고, 작업한 바이트 수만큼 오프셋을 이동시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size26&quot;&gt;3. 임의 접근: lseek (Reading and Writing, but Not Sequentially)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;파일을 항상 순차적으로만 읽을 필요는 없다. 데이터베이스 같은 프로그램은 파일의 중간 내용을 빈번하게 읽어야 한다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7k-Drva-RAxUAAAAAHQAAAAAQiAQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;off_t lseek(int fd, off_t offset, int whence);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동작:&lt;/b&gt; 디스크 헤드를 물리적으로 이동시키는 게 아니라, 커널 메모리 내의 &lt;b&gt;오프셋 변수 값만 수정&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;whence 옵션:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SEEK_SET: 파일의 시작점 기준 (절대 위치)&lt;/li&gt;
&lt;li&gt;SEEK_CUR: 현재 오프셋 기준 (상대 위치)&lt;/li&gt;
&lt;li&gt;SEEK_END: 파일의 끝 기준 (주로 파일 크기를 알거나 뒤에 덧붙일 때 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size26&quot;&gt;4. [핵심] 오픈 파일 테이블과 공유 (Shared File Entries)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;이 챕터에서 가장 중요하고 헷갈리기 쉬운 부분이다. 운영체제는 열린 파일을 어떻게 관리할까? 세 가지 자료구조가 관여한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;프로세스별 파일 디스크립터 테이블 (Per-process Descriptor Table):&lt;/b&gt; 각 프로세스가 가진 fd 배열.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시스템 전체 오픈 파일 테이블 (System-wide Open File Table):&lt;/b&gt; 모든 프로세스가 공유하는 테이블. 여기에 &lt;b&gt;현재 오프셋(Current Offset)&lt;/b&gt;, 접근 모드, 참조 횟수 등이 저장된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;inode (메타데이터):&lt;/b&gt; 실제 파일의 정보.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size23&quot;&gt;케이스 A: fork()를 했을 때 (공유 O)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;부모 프로세스가 파일을 열고(fd=3), fork()를 호출해 자식을 낳으면?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;30&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식도 똑같은 fd=3을 가진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중요:&lt;/b&gt; 둘 다 &lt;b&gt;같은 오픈 파일 테이블 엔트리&lt;/b&gt;를 가리킨다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과:&lt;/b&gt; 부모가 읽어서 오프셋을 이동시키면, &lt;b&gt;자식의 오프셋도 같이 이동&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;31&quot; data-ke-size=&quot;size23&quot;&gt;케이스 B: open()을 따로 두 번 했을 때 (공유 X)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;같은 파일을 프로세스 A가 열고, 프로세스 B가 따로 열면?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각 &lt;b&gt;다른 오픈 파일 테이블 엔트리&lt;/b&gt;가 생성된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과:&lt;/b&gt; 서로 &lt;b&gt;오프셋이 독립적&lt;/b&gt;이다. A가 읽어도 B의 오프셋은 변하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;35&quot; data-ke-size=&quot;size26&quot;&gt;5. 영속성 보장: fsync() (Writing Immediately)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;write()를 호출했다고 해서 데이터가 즉시 디스크에 기록되는 것은 아니다. 성능을 위해 OS는 데이터를 메모리(Buffer Cache)에 잠시 담아둔다. 하지만 이 상태에서 전원이 꺼지면? 데이터는 날아간다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스(DBMS)처럼 데이터 유실이 치명적인 프로그램은 fsync(fd)를 사용해야 한다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7k-Drva-RAxUAAAAAHQAAAAAQiQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;write(fd, buffer, size); // 메모리 버퍼에만 씀
fsync(fd);               // 디스크에 강제로 내려 씀 (Dirty Page Flush)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;39&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주의:&lt;/b&gt; 파일의 내용뿐만 아니라, 파일을 포함하는 디렉터리 정보까지 안전하게 쓰려면 &lt;b&gt;디렉터리에 대해서도 fsync()&lt;/b&gt;를 해줘야 완벽하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;41&quot; data-ke-size=&quot;size26&quot;&gt;6. 파일 이름 변경: rename()&lt;/h2&gt;
&lt;p data-path-to-node=&quot;42&quot; data-ke-size=&quot;size16&quot;&gt;파일 이름을 바꿀 때 mv 명령어를 쓰는데, 내부적으로는 rename() 시스템 콜을 사용한다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7k-Drva-RAxUAAAAAHQAAAAAQigQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;rename(&quot;old_name&quot;, &quot;new_name&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;44&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;원자성 (Atomicity):&lt;/b&gt; rename의 가장 큰 특징은 &lt;b&gt;원자적(Atomic)&lt;/b&gt;이라는 것이다. 이름이 바뀌는 도중에 시스템이 셧다운 되어도, 파일은 old_name으로 남아있거나 new_name으로 바뀌거나 둘 중 하나다. 어중간한 상태(파일 소실)는 없다.&lt;/li&gt;
&lt;li&gt;이 특징을 이용해 에디터들은 임시 파일에 저장을 완료한 후, 원본 파일로 rename 하는 방식으로 안전한 저장을 구현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;46&quot; data-ke-size=&quot;size26&quot;&gt;7. 파일 정보 얻기: stat() (Metadata)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;47&quot; data-ke-size=&quot;size16&quot;&gt;파일 시스템은 파일의 내용(Content) 외에도 파일에 대한 정보(Metadata)를 inode에 저장한다. 이를 확인하는 함수가 stat()이다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7k-Drva-RAxUAAAAAHQAAAAAQiwQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;struct stat {
    dev_t     st_dev;     // 디바이스 ID
    ino_t     st_ino;     // Inode 번호 (중요!)
    mode_t    st_mode;    // 권한 및 파일 타입
    nlink_t   st_nlink;   // 하드 링크 수 (Reference Count)
    uid_t     st_uid;     // 소유자 ID
    off_t     st_size;    // 파일 크기 (바이트)
    time_t    st_mtime;   // 마지막 수정 시간
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;49&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일의 이름은 inode에 없다. 이름은 &lt;b&gt;디렉터리&lt;/b&gt;가 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;51&quot; data-ke-size=&quot;size26&quot;&gt;8. 파일 삭제: unlink() (Removing Files)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;왜 파일을 지우는 함수 이름이 remove나 delete가 아니라 &lt;b&gt;unlink&lt;/b&gt;일까?&lt;/p&gt;
&lt;p data-path-to-node=&quot;53&quot; data-ke-size=&quot;size16&quot;&gt;파일 삭제의 메커니즘은 &lt;b&gt;참조 횟수(Reference Count)&lt;/b&gt;에 기반한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;54&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;파일(inode)은 link count라는 숫자를 가진다. (이 파일을 가리키는 이름이 몇 개인가?)&lt;/li&gt;
&lt;li&gt;unlink(&quot;foo&quot;)를 호출하면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;54,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;foo&quot;라는 이름을 디렉터리에서 지운다.&lt;/li&gt;
&lt;li&gt;해당 inode의 link count를 1 감소시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;만약 link count가 &lt;b&gt;0이 되면&lt;/b&gt;, 그때 비로소 inode와 데이터 블록을 해제(Free)하여 파일이 진짜 삭제된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;56&quot; data-ke-size=&quot;size26&quot;&gt;9. 디렉터리 다루기 (mkdir, readdir, rmdir)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;57&quot; data-ke-size=&quot;size16&quot;&gt;디렉터리는 일반 파일과 달리 직접 write 할 수 없다. (그랬다간 파일 시스템 구조가 망가진다.)&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;58&quot; data-ke-size=&quot;size23&quot;&gt;디렉터리 읽기&lt;/h3&gt;
&lt;p data-path-to-node=&quot;59&quot; data-ke-size=&quot;size16&quot;&gt;디렉터리는 단순히 read()로 읽는 대신 전용 라이브러리를 쓴다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7k-Drva-RAxUAAAAAHQAAAAAQjAQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;DIR *dp = opendir(&quot;.&quot;); // 디렉터리 열기
struct dirent *d;
while ((d = readdir(dp)) != NULL) { // 엔트리 하나씩 읽기
    printf(&quot;inode: %d, name: %s\n&quot;, d-&amp;gt;d_ino, d-&amp;gt;d_name);
}
closedir(dp);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;61&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ls 명령어가 내부적으로 이렇게 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;63&quot; data-ke-size=&quot;size26&quot;&gt;10. 링크: 하드 링크 vs 심볼릭 링크&lt;/h2&gt;
&lt;p data-path-to-node=&quot;64&quot; data-ke-size=&quot;size16&quot;&gt;두 가지 링크 방식의 차이를 이해하는 것은 매우 중요하다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;65&quot; data-ke-size=&quot;size23&quot;&gt;(1) 하드 링크 (Hard Link)&lt;/h3&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7k-Drva-RAxUAAAAAHQAAAAAQjQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;ln file target  # link() 시스템 콜
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;67&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동작:&lt;/b&gt; 디렉터리에 (target, 기존 inode 번호) 쌍을 추가한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;67,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 파일과 &lt;b&gt;똑같은 inode 번호&lt;/b&gt;를 가진다.&lt;/li&gt;
&lt;li&gt;즉, 별도의 파일이 아니라 &lt;b&gt;같은 파일에 이름만 하나 더 생긴 것&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;원본을 지워도(unlink), 링크가 남아있으면(ref count &amp;gt; 0) 파일은 사라지지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;제약:&lt;/b&gt; 디렉터리에는 하드 링크를 걸 수 없다(순환 참조 방지). 다른 파티션(파일 시스템) 간에는 걸 수 없다(inode 번호는 파티션 내에서만 유일하므로).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;68&quot; data-ke-size=&quot;size23&quot;&gt;(2) 심볼릭 링크 (Symbolic Link / Soft Link)&lt;/h3&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwj7k-Drva-RAxUAAAAAHQAAAAAQjgQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;ln -s file target # symlink() 시스템 콜
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;70&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동작:&lt;/b&gt; &lt;b&gt;완전히 새로운 파일(새로운 inode)&lt;/b&gt;을 만든다. 이 파일의 내용(Content)은 원본 파일의 &lt;b&gt;경로 문자열&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;70,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본과 &lt;b&gt;다른 inode 번호&lt;/b&gt;를 가진다.&lt;/li&gt;
&lt;li&gt;원본 파일을 지우면, 심볼릭 링크는 가리킬 곳을 잃어버린 상태(&lt;b&gt;Dangling Reference&lt;/b&gt;)가 되어 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;디렉터리나 다른 파티션에 대해서도 자유롭게 걸 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;72&quot; data-ke-size=&quot;size26&quot;&gt;11. 권한과 접근 제어 (Permission Bits and ACLs)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;73&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Permission Bits:&lt;/b&gt; 유닉스 파일 시스템은 rwxr-xr-x와 같은 9비트 정보를 통해 소유자(User), 그룹(Group), 기타(Other)에 대한 읽기/쓰기/실행 권한을 관리한다. (chmod로 변경)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ACL (Access Control List):&lt;/b&gt; 9비트만으로는 &quot;철수한테만 쓰기 권한을 주고 싶다&quot; 같은 정교한 제어가 어렵다. 이를 위해 ACL을 사용하여 사용자별로 구체적인 권한을 설정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;75&quot; data-ke-size=&quot;size26&quot;&gt;12. 파일 시스템 만들기 및 마운트 (mkfs, mount)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;76&quot; data-ke-size=&quot;size16&quot;&gt;파일 시스템을 사용하려면 먼저 초기화하고 트리에 붙여야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;77&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;mkfs (Make Filesystem):&lt;/b&gt; 디스크 파티션에 빈 파일 시스템 구조(루트 inode, 프리 리스트 등)를 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;mount:&lt;/b&gt; 물리적인 장치(예: /dev/sda1)를 현재 파일 시스템 트리의 특정 위치(예: /home)에 갖다 붙인다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;77,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마운트 하기 전까지 /home은 그냥 빈 디렉터리지만, 마운트 후에는 해당 디스크의 루트 디렉터리가 /home 위치에 오버레이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;79&quot; data-ke-size=&quot;size26&quot;&gt;요약 (Summary)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;80&quot; data-ke-size=&quot;size16&quot;&gt;OSTEP 39장은 파일 시스템의 인터페이스를 폭넓게 다뤘다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;81&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;파일&lt;/b&gt;은 바이트 배열, &lt;b&gt;디렉터리&lt;/b&gt;는 이름 목록이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;open&lt;/b&gt;을 통해 &lt;b&gt;Open File Table&lt;/b&gt; 엔트리를 만들고 &lt;b&gt;Offset&lt;/b&gt;을 관리한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;fork&lt;/b&gt; 시 오프셋이 공유되는 원리를 이해해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;fsync&lt;/b&gt;로 영속성을, &lt;b&gt;rename&lt;/b&gt;으로 원자성을 보장한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;unlink&lt;/b&gt;는 링크 카운트를 줄이며, 0이 될 때 파일이 삭제된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하드 링크&lt;/b&gt;는 이름 추가, &lt;b&gt;심볼릭 링크&lt;/b&gt;는 바로가기 파일 생성이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;82&quot; data-ke-size=&quot;size16&quot;&gt;이 API들은 유닉스 철학의 정수이며, 오늘날 대부분의 시스템(Linux, macOS 등)이 이 표준을 따르고 있다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/212</guid>
      <comments>https://gowoong.tistory.com/212#entry212comment</comments>
      <pubDate>Tue, 9 Dec 2025 12:34:11 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 15주차 - 영속성 Redundant Array of Inexpensive Disk (RAID)</title>
      <link>https://gowoong.tistory.com/211</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;단일 디스크는 느리고, 작고, 고장이 나기 쉽다. 그렇다면 &lt;b&gt;여러 개의 디스크를 묶어서&lt;/b&gt; 마치 &lt;b&gt;하나의 크고, 빠르고, 안전한 디스크&lt;/b&gt;처럼 보이게 만들면 어떨까? 이것이 바로 &lt;b&gt;RAID(Redundant Array of Inexpensive Disks)&lt;/b&gt;의 기본 아이디어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAID는 운영체제(파일 시스템)에게 투명(Transparent)하다. 즉, OS는 뒤에 디스크가 몇 개가 있는지, 어떻게 묶여 있는지 신경 쓰지 않고 그냥 하나의 논리적인 블록 장치로 인식한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;RAID를 평가하는 기준은 크게 세 가지다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;용량 (Capacity):&lt;/b&gt; N개의 디스크를 썼을 때 실제로 사용할 수 있는 공간은 얼마인가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;신뢰성 (Reliability):&lt;/b&gt; 디스크가 몇 개 고장 나도 데이터를 잃지 않는가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 (Performance):&lt;/b&gt; 읽기/쓰기 속도가 얼마나 빨라지는가? (대기 시간 및 처리량)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;OSTEP에서 중요하게 다루는 RAID 레벨 0, 1, 4, 5를 중심으로 이 세 가지 기준을 분석해 본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size26&quot;&gt;1. RAID Level 0: 스트라이핑 (Striping)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;RAID 0은 데이터를 여러 디스크에 쪼개서 분산 저장하는 방식이다. 엄밀히 말하면 중복(Redundancy)이 없으므로 RAID의 'R'에는 부합하지 않지만, 성능과 용량을 극대화한 방식이다.&lt;/p&gt;
&lt;div&gt;
&lt;div data-full-size-image-uri=&quot;https://encrypted-tbn3.gstatic.com/licensed-image?q=tbn:ANd9GcTCDhZcBZtd_yxewiybUN5ghKpe4OSGjBN3oFwdq5ggQ1IhXASLRaCaoG0fG7K_YkJvzL2eGuCWJ0DhigayHLsz0boQWPpyh29fvbqbZ8nqRZ9zsss&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2661&quot; data-origin-height=&quot;2800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7eJJF/dJMcaaw2rw3/kjJcste5xrBEuojxFydvhK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7eJJF/dJMcaaw2rw3/kjJcste5xrBEuojxFydvhK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7eJJF/dJMcaaw2rw3/kjJcste5xrBEuojxFydvhK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7eJJF%2FdJMcaaw2rw3%2FkjJcste5xrBEuojxFydvhK%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;620&quot; height=&quot;652&quot; data-origin-width=&quot;2661&quot; data-origin-height=&quot;2800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방식:&lt;/b&gt; 데이터를 &lt;b&gt;청크(Chunk)&lt;/b&gt; 단위로 잘라서 디스크 0, 디스크 1, ... 디스크 N에 순서대로 라운드 로빈 방식으로 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용량:&lt;/b&gt; &lt;span data-math=&quot;N&quot;&gt;$N$&lt;/span&gt;개의 디스크가 있다면 &lt;span data-math=&quot;N \times B&quot;&gt;$N \times B$&lt;/span&gt; (B는 디스크 용량). 즉, &lt;b&gt;100% 활용&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;신뢰성:&lt;/b&gt; &lt;b&gt;0&lt;/b&gt;. 디스크가 하나만 고장 나도 전체 데이터가 손실된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능:&lt;/b&gt; 가장 뛰어나다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순차 읽기/쓰기: 모든 디스크를 병렬로 사용하므로 &lt;span data-math=&quot;N \times S&quot;&gt;$N \times S$&lt;/span&gt; (S는 단일 디스크 속도).&lt;/li&gt;
&lt;li&gt;랜덤 읽기/쓰기: 데이터가 골고루 퍼져 있다면 역시 병렬 처리가 가능하여 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size26&quot;&gt;2. RAID Level 1: 미러링 (Mirroring)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;RAID 1은 데이터를 있는 그대로 복사해서 다른 디스크에 똑같이 저장하는 방식이다. 보통 RAID 10(1+0) 형태로 많이 쓰인다.&lt;/p&gt;
&lt;div&gt;
&lt;div data-full-size-image-uri=&quot;https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcSfcvLUBzojXyXHDy_nKFdqv2HgtQUuyeDorH_u5WA23Tx8RG80b1rPCuVIwknlLGpO06b0P0-Vlp8ubpEoj0UJene76ZvgXBG6kXzxBD9OYJ0CqJI&quot;&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;2048&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czxp9O/dJMcafLSzsK/M9vQhBKAo4KdlvlyWoC5bK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czxp9O/dJMcafLSzsK/M9vQhBKAo4KdlvlyWoC5bK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czxp9O/dJMcafLSzsK/M9vQhBKAo4KdlvlyWoC5bK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fczxp9O%2FdJMcafLSzsK%2FM9vQhBKAo4KdlvlyWoC5bK%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;620&quot; height=&quot;650&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;2048&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방식:&lt;/b&gt; 데이터 블록을 쓸 때, 원본 디스크와 미러(사본) 디스크에 동시에 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용량:&lt;/b&gt; &lt;span data-math=&quot;N / 2&quot;&gt;$N / 2$&lt;/span&gt;. 디스크를 100개 사도 50개 분량밖에 못 쓴다. &lt;b&gt;비용이 비싸다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;신뢰성:&lt;/b&gt; 좋다. 미러링된 쌍 중 하나가 살아있으면 데이터는 안전하다. (최대 N/2개 고장 허용)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;읽기:&lt;/b&gt; 임의의 디스크에서 읽으면 되므로 처리량이 좋다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쓰기:&lt;/b&gt; 두 디스크에 모두 써야 완료되므로, 둘 중 느린 디스크의 속도에 맞춰진다. (하지만 논리적으로 크게 느리진 않다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size26&quot;&gt;3. RAID Level 4: 패리티 (Parity)와 패리티 디스크&lt;/h2&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;RAID 1의 용량 낭비 문제(50% 손실)를 해결하기 위해 등장했다. 오류 검출 및 복구를 위해 &lt;b&gt;패리티(Parity)&lt;/b&gt; 비트를 사용한다. 주로 XOR 연산(&lt;span data-math=&quot;\oplus&quot;&gt;$\oplus$&lt;/span&gt;)을 이용한다.&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;24&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;24,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;XOR의 마법:&lt;/b&gt; &lt;span data-math=&quot;C = A \oplus B&quot;&gt;$C = A \oplus B$&lt;/span&gt; 일 때, C와 B만 알면 A를 복구할 수 있다. (&lt;span data-math=&quot;A = C \oplus B&quot;&gt;$A = C \oplus B$&lt;/span&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방식:&lt;/b&gt; N-1개의 디스크에는 데이터를 저장하고, &lt;b&gt;마지막 1개의 디스크를 전용 패리티 디스크&lt;/b&gt;로 쓴다. 각 행(Stripe)의 데이터들을 XOR 한 값을 패리티 디스크에 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용량:&lt;/b&gt; &lt;span data-math=&quot;N - 1&quot;&gt;$N - 1$&lt;/span&gt;. 디스크 하나 분량만 손해 보면 되므로 RAID 1보다 훨씬 효율적이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;신뢰성:&lt;/b&gt; 디스크 1개가 고장 나면, 나머지 디스크들과 패리티 디스크를 XOR 연산하여 데이터를 복구할 수 있다. (1개 고장 허용)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 (중요!):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순차 읽기/쓰기: 빠르다. (디스크 N-1개 병렬 사용)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;랜덤 쓰기 (Small Write Problem):&lt;/b&gt; 여기가 문제다. 데이터 블록 하나만 수정하려고 해도, 패리티를 다시 계산해야 한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;26,3,1,1,1&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기존 데이터 읽기&lt;/li&gt;
&lt;li&gt;기존 패리티 읽기&lt;/li&gt;
&lt;li&gt;새 패리티 계산 (&lt;span data-math=&quot;P_{new} = (D_{old} \oplus D_{new}) \oplus P_{old}&quot;&gt;$P_{new} = (D_{old} \oplus D_{new}) \oplus P_{old}$&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;새 데이터 쓰기&lt;/li&gt;
&lt;li&gt;새 패리티 쓰기&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;결국 &lt;b&gt;모든 쓰기 작업이 패리티 디스크 하나에 집중&lt;/b&gt;된다. 병목현상(Bottleneck)이 발생하여 성능이 저하된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size26&quot;&gt;4. RAID Level 5: 회전 패리티 (Rotating Parity)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;RAID 4의 &lt;b&gt;&quot;패리티 디스크 병목&quot;&lt;/b&gt; 문제를 해결하기 위해 고안되었다. 현재 가장 널리 쓰이는 방식 중 하나다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;30&quot; data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방식:&lt;/b&gt; RAID 4와 같지만, 패리티 블록을 한 디스크에 몰아두지 않고 &lt;b&gt;모든 디스크에 번갈아 가며(Rotate) 분산 저장&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용량:&lt;/b&gt; &lt;span data-math=&quot;N - 1&quot;&gt;$N - 1$&lt;/span&gt;. (RAID 4와 동일)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;신뢰성:&lt;/b&gt; 1개 고장 허용. (RAID 4와 동일)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;31,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패리티 업데이트 작업이 모든 디스크에 분산되므로, RAID 4의 병목 현상이 사라진다.&lt;/li&gt;
&lt;li&gt;랜덤 쓰기 성능이 RAID 4보다 월등히 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/211</guid>
      <comments>https://gowoong.tistory.com/211#entry211comment</comments>
      <pubDate>Tue, 9 Dec 2025 12:11:17 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 14주차 - 영속성 1 Part.2</title>
      <link>https://gowoong.tistory.com/210</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;운영체제는 저장 장치(디스크)를 관리하기 위해 두 가지 핵심 추상화 개념을 제공한다.&lt;/span&gt;&lt;span&gt; 바로 &lt;b&gt;파일(File)&lt;/b&gt;과 &lt;b&gt;디렉터리(Directory)&lt;/b&gt;다.&lt;/span&gt;&lt;span&gt; 이들은 사용자가 데이터를 쉽게 저장하고,&lt;/span&gt;&lt;span&gt; 이름을 붙이고,&lt;/span&gt;&lt;span&gt; 정리할 수 있게 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;1. 파일 (Files)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;1.1. 파일의 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정의:&lt;/b&gt;&lt;span&gt; 파일은 단순히 &lt;b&gt;바이트들의 선형 배열(Linear array of bytes)&lt;/b&gt;이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징:&lt;/b&gt;&lt;span&gt; 운영체제는 파일의 내용(텍스트인지,&lt;/span&gt;&lt;span&gt; 이미지인지 등)을 알지 못하며,&lt;/span&gt;&lt;span&gt; 단지 데이터를 영구적으로 저장하고 요청 시 돌려주는 역할만 수행한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;저수준 이름 (Low-level Name):&lt;/b&gt;&lt;span&gt; 각 파일은 &lt;b&gt;아이노드 번호(inode number)&lt;/b&gt;라는 고유한 번호를 가진다.&lt;/span&gt;&lt;span&gt; 사용자는 보통 파일 이름(예:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;foo.txt&lt;span&gt;)을 사용하지만,&lt;/span&gt;&lt;span&gt; OS 내부적으로는 이 번호를 통해 파일을 식별한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;1.2. 파일 관련 시스템 콜 (API)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;파일을 다루기 위해 OS는 다음과 같은 인터페이스를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;open():&lt;/b&gt;&lt;span&gt; 파일을 생성하거나 연다.&lt;/span&gt;&lt;span&gt; 성공 시 &lt;b&gt;파일 디스크립터(File Descriptor)&lt;/b&gt;라는 정수를 반환한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;파일 디스크립터:&lt;/b&gt;&lt;span&gt; 프로세스마다 비공개로 가지는 정수로,&lt;/span&gt;&lt;span&gt; 파일을 조작할 수 있는 권한(Capability)을 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;read() / write():&lt;/b&gt;&lt;span&gt; 파일 디스크립터를 이용해 데이터를 읽거나 쓴다.&lt;/span&gt;&lt;span&gt; 읽고 쓴 만큼 현재 오프셋(Offset)이 자동으로 업데이트되어 순차적인 접근이 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;lseek():&lt;/b&gt;&lt;span&gt; 파일 내의 현재 오프셋을 특정 위치로 이동시킨다.&lt;/span&gt;&lt;span&gt; 이를 통해 파일의 임의 위치에 접근(Random Access)할 수 있다.&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;b&gt;주의할 점은 이 함수가 디스크 헤드를 물리적으로 움직이는 것이 아니라, OS 메모리 내의 변수 값만 변경한다는 점이다&lt;/b&gt;&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;fsync():&lt;/b&gt;&lt;span&gt; 성능을 위해 메모리에 버퍼링된 쓰기 작업(Dirty Data)을 즉시 디스크로 강제 전송하여 지속성을 보장한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;2. 디렉터리 (Directories)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;2.1. 디렉터리의 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정의:&lt;/b&gt; 디렉터리 또한 하나의 &lt;b&gt;파일&lt;/b&gt;이다. 하지만 그 내용은 &lt;b&gt;(사용자가 읽을 수 있는 이름, 아이노드 번호)&lt;/b&gt; 쌍의 리스트로 구성된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계층 구조 (Directory Tree):&lt;/b&gt; 디렉터리는 다른 디렉터리를 포함할 수 있어, 루트 디렉터리(/)로부터 시작하는 트리 구조를 형성한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경로 (Path):&lt;/b&gt; 파일의 위치는 절대 경로(예: /foo/bar.txt)로 표현된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;2.2. 디렉터리 관련 연산&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성:&lt;/b&gt; mkdir() 시스템 콜을 사용한다. 디렉터리는 생성될 때 기본적으로 자기 자신을 가리키는 .과 부모를 가리키는 .. 두 개의 항목을 가진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;읽기:&lt;/b&gt; 디렉터리는 일반 파일처럼 열 수 없고, opendir(), readdir(), closedir()이라는 별도의 라이브러리 함수를 사용해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;삭제:&lt;/b&gt; rmdir()을 사용하지만, 디렉터리가 비어 있을 때( .과 ..만 있을 때)만 삭제할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;3. 링크 (Links): 파일에 이름 붙이기&lt;/h2&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;파일 시스템에서는 하나의 파일(아이노드)에 여러 개의 이름을 붙일 수 있다. 이를 &lt;b&gt;링크&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;3.1. 하드 링크 (Hard Link)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;명령어:&lt;/b&gt; ln file file2&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원리:&lt;/b&gt; 새로운 파일 이름을 생성하고, 원본 파일과 &lt;b&gt;동일한 아이노드 번호&lt;/b&gt;를 가리키게 한다. 즉, file과 file2는 이름만 다를 뿐 완전히 같은 파일이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;삭제 (unlink):&lt;/b&gt; 파일을 삭제할 때 rm 명령어를 쓰지만, 실제 시스템 콜은 unlink()이다. 이는 디렉터리에서 이름과 아이노드 연결을 끊고 &lt;b&gt;참조 카운트(Reference Count)&lt;/b&gt;를 1 줄인다. 참조 카운트가 0이 되어야만 실제 파일 데이터가 디스크에서 해제된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;3.2. 심볼릭 링크 (Symbolic Link / Soft Link)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;명령어:&lt;/b&gt; ln -s file file2&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원리:&lt;/b&gt; 하드 링크와 달리, &lt;b&gt;완전히 새로운 파일(새로운 아이노드)&lt;/b&gt;을 생성한다. 이 파일의 내용물은 원본 파일의 &lt;b&gt;경로(Pathname)&lt;/b&gt;다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;22,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 파일이 삭제되면 심볼릭 링크는 가리키는 대상이 없는 '댕글링 포인터(Dangling Reference)'가 된다.&lt;/li&gt;
&lt;li&gt;디렉터리에 대해서도 링크를 걸 수 있으며, 다른 파일 시스템에 있는 파일도 가리킬 수 있다 (하드 링크는 불가능).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size26&quot;&gt;4. 메타데이터 (Metadata)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;파일의 내용(데이터) 외에 파일 자체에 대한 정보도 중요하다. 이를 &lt;b&gt;메타데이터&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정보 확인:&lt;/b&gt; stat() 시스템 콜을 사용하면 파일의 크기, 소유자, 권한, 수정 시간, 아이노드 번호 등의 정보를 볼 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;저장 위치:&lt;/b&gt; 이러한 정보는 파일 시스템의 &lt;b&gt;아이노드(inode)&lt;/b&gt; 구조체 안에 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;div id=&quot;model-response-message-contentr_1014fb48b27b9215&quot;&gt;
&lt;h2 data-path-to-node=&quot;28&quot; data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;파일&lt;/b&gt;은 바이트의 배열이며 &lt;b&gt;아이노드 번호&lt;/b&gt;로 식별된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디렉터리&lt;/b&gt;는 (이름, 아이노드 번호) 쌍을 저장하는 파일이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하드 링크&lt;/b&gt;는 같은 아이노드를 가리키는 다른 이름이고, &lt;b&gt;심볼릭 링크&lt;/b&gt;는 경로를 저장한 별도의 파일이다.&lt;/li&gt;
&lt;li&gt;OS는 open, read, write 등의 시스템 콜을 통해 이러한 추상화된 객체들에 접근할 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/210</guid>
      <comments>https://gowoong.tistory.com/210#entry210comment</comments>
      <pubDate>Tue, 2 Dec 2025 15:32:02 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 14주차 - 영속성 1 Part.1</title>
      <link>https://gowoong.tistory.com/209</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제의 세 번째 핵심 주제인 &lt;b&gt;영속성(Persistence)&lt;/b&gt;은 시스템 전원이 꺼지거나 충돌이 발생해도 데이터가 영구적으로 보존되도록 보장하는 개념이다. 이 영속성은 하드웨어(I/O 장치, 디스크)와 소프트웨어(파일 시스템, 디렉터리)의 유기적인 상호작용으로 완성된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;1. 시스템 아키텍처 (System Architecture)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;I/O 장치를 이해하려면 먼저 이들이 시스템 전체에서 어디에 위치하는지 봐야 한다.&lt;/span&gt;&lt;span&gt; 컴퓨터 시스템은 성능 비용에 따라 계층적인 버스(Bus) 구조를 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;1.1. 계층적 버스 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 계층적인 구조가 필요할까? 이는 &lt;b&gt;물리학적 제약&lt;/b&gt;과 &lt;b&gt;비용&lt;/b&gt; 때문이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버스는 고속일수록 길이가 짧아져야 한다. (신호 무결성 문제)&lt;/li&gt;
&lt;li&gt;고속 버스는 제작 비용이 비싸기 때문에 모든 장치를 고속 버스에 연결할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvpcYi/dJMcaiofTho/KiZL21ef2EJ0YEZP4c1o91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvpcYi/dJMcaiofTho/KiZL21ef2EJ0YEZP4c1o91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvpcYi/dJMcaiofTho/KiZL21ef2EJ0YEZP4c1o91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvpcYi%2FdJMcaiofTho%2FKiZL21ef2EJ0YEZP4c1o91%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;550&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;일반적인 시스템은 다음과 같은 계층 구조를 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 버스 (Memory Bus):&lt;/b&gt;&lt;span&gt; CPU와 메인 메모리를 연결하는 버스다.&lt;/span&gt;&lt;span&gt; 가장 빠르고(대역폭이 높고) 지연 시간이 짧지만,&lt;/span&gt;&lt;span&gt; 가격이 비싸고 길이를 늘리기 어렵다.&lt;/span&gt;&lt;span&gt; 주로 그래픽 카드 같은 고성능 장치들이 이곳에 가깝게 연결된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;범용 I/O 버스 (General I/O Bus):&lt;/b&gt;&lt;span&gt; PCI(Peripheral Component Interconnect) 등이 여기에 해당한다.&lt;/span&gt;&lt;span&gt; 메모리 버스보다는 느리지만,&lt;/span&gt;&lt;span&gt; 여전히 꽤 빠른 속도를 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주변 장치 버스 (Peripheral Bus):&lt;/b&gt;&lt;span&gt; SCSI,&lt;/span&gt;&lt;span&gt; SATA,&lt;/span&gt;&lt;span&gt; USB 등이 여기에 속한다.&lt;/span&gt;&lt;span&gt; 가장 느리지만,&lt;/span&gt;&lt;span&gt; 길이를 길게 늘릴 수 있고 많은 장치를 연결할 수 있다.&lt;/span&gt;&lt;span&gt; 하드 디스크,&lt;/span&gt;&lt;span&gt; 마우스,&lt;/span&gt;&lt;span&gt; 키보드 등이 이곳에 연결된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;ldquo;PCI&amp;rdquo;는 &amp;ldquo;Peripheral Component Interconnect&amp;rdquo;의 약자로, 1990년대 중반부터 2000년대 초반까지 컴퓨터에서 주로 사용되던 IO Bus 표준 중 하나다. PCI에 대한 몇 가지 주요 특징은 다음과 같다.&lt;br /&gt;&lt;br /&gt;플러그 앤 플레이: 장치를 컴퓨터에 연결하면 운영 체제가 자동으로 장치를 감지하고 설정한다.&lt;br /&gt;32비트와 64비트 버전: 초기 PCI 버스는 32비트 데이터 경로를 사용했지만, 나중에 64비트 버전도 출시되었다.&lt;br /&gt;높은 데이터 전송 속도: PCI 버스는 그 당시 다른 버스 표준에 비해 높은 데이터 전송 속도를 제공했다.&lt;br /&gt;범용성: 여러 종류의 장치 (그래픽 카드, 네트워크 카드, 사운드 카드 등)와 호환된다.&lt;br /&gt;&lt;br /&gt;그러나 PCI는 오래된 기술이기 때문에 현대의 PC나 서버에서는 더 빠른 성능과 확장성을 제공하는 PCI Express (PCIe)와 같은 새로운 표준에 의해 대체되었다. PCIe는 PCI의 후속 표준으로, 데이터 전송 속도와 확장성, 그리고 세밀한 전력 관리 기능 등 여러 가지 향상된 기능을 제공한다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size26&quot;&gt;2. 표준 장치 (A Canonical Device)와 동작 방식&lt;/h2&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;운영체제가 실제 장치를 제어하는 방법을 이해하기 위해, &lt;b&gt;표준 장치(Canonical Device)&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;1082&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl6fC6/dJMcagRvFFl/xc19N096MKuBA7xUkbtLX0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl6fC6/dJMcagRvFFl/xc19N096MKuBA7xUkbtLX0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl6fC6/dJMcagRvFFl/xc19N096MKuBA7xUkbtLX0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl6fC6%2FdJMcagRvFFl%2Fxc19N096MKuBA7xUkbtLX0%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;1082&quot; height=&quot;404&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;2.1. 장치의 내부 구조&lt;/h3&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;모든 장치는 크게 두 부분으로 나뉜다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;하드웨어 인터페이스 (Interface):&lt;/b&gt; OS가 장치를 제어하기 위해 들여다보는 '창문'이다. 주로 3개의 레지스터(저장소)로 구성된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상태(Status) 레지스터:&lt;/b&gt; 장치가 바쁜지(BUSY), 준비됐는지(READY), 에러가 났는지 알려준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명령(Command) 레지스터:&lt;/b&gt; OS가 &quot;데이터를 읽어라/써라&quot; 같은 명령을 내리는 곳이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터(Data) 레지스터:&lt;/b&gt; 데이터를 주고받는 통로다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내부 구조 (Internals):&lt;/b&gt; 실제 장치의 기능을 수행하는 부분이다. (예: 디스크의 모터와 헤드, 펌웨어 칩 등)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;2.2. 장치와 소통하는 기본 방법: 폴링(Polling)과 PIO&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;가장 원시적인 통신 방법은 &lt;b&gt;폴링(Polling)&lt;/b&gt;과 &lt;b&gt;PIO(Programmed I/O)&lt;/b&gt;다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상황:&lt;/b&gt; OS가 장치에게 데이터를 쓰라고 명령하고 싶다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단계 1 (폴링):&lt;/b&gt; OS는 Status 레지스터를 &lt;b&gt;무한 반복문&lt;/b&gt;으로 계속 읽는다. (&quot;지금 바빠? 바빠? 안 바빠?&quot;) 장치가 READY 상태가 될 때까지 CPU는 다른 일을 못 하고 기다린다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단계 2 (데이터 전송 - PIO):&lt;/b&gt; 준비가 되면 CPU가 메모리에서 데이터를 가져와 Data 레지스터에 직접 쓴다. CPU가 데이터 이동 노동을 직접 하므로 이를 &lt;b&gt;PIO&lt;/b&gt;라고 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단계 3 (명령 전달):&lt;/b&gt; Command 레지스터에 '쓰기' 명령을 기록한다. 장치는 이제 작업을 시작하고 상태를 BUSY로 바꾼다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단계 4 (완료 대기):&lt;/b&gt; OS는 다시 Status를 계속 확인하며 작업이 끝날 때까지 기다린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-path-to-node=&quot;18&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;18,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점:&lt;/b&gt; 장치가 느리다면(예: 디스크), CPU는 그 긴 시간 동안 아무것도 못 하고 Status 레지스터만 쳐다보고 있어야 한다. 이는 엄청난 &lt;b&gt;CPU 자원 낭비&lt;/b&gt;다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size26&quot;&gt;3. 효율성을 위한 핵심 기술: 인터럽트와 DMA&lt;/h2&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;위의 비효율을 해결하기 위해 운영체제는 두 가지 핵심 기술을 도입했다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size23&quot;&gt;3.1. 인터럽트 (Interrupt): &quot;다 되면 알려줘&quot;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;OS가 장치를 계속 감시(폴링)하는 대신, 작업을 시켜놓고 &lt;b&gt;CPU를 다른 프로세스에 넘겨버리는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;OS는 장치에 명령을 내린 후, 해당 프로세스를 &lt;b&gt;'대기(Blocked)'&lt;/b&gt; 상태로 만들고 &lt;b&gt;다른 프로세스를 실행&lt;/b&gt;한다(Context Switch).&lt;/li&gt;
&lt;li&gt;장치가 작업을 마치면 CPU에 전기 신호(&lt;b&gt;하드웨어 인터럽트&lt;/b&gt;)를 보낸다.&lt;/li&gt;
&lt;li&gt;CPU는 하던 일을 잠시 멈추고, OS의 &lt;b&gt;인터럽트 핸들러(Interrupt Handler)&lt;/b&gt;를 실행하여 &quot;아, 디스크 작업 끝났구나&quot;라고 인지하고, 대기하던 프로세스를 다시 깨운다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;24,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; I/O 작업 중에도 CPU가 놀지 않고 연산(Overlap)을 할 수 있어 효율이 극대화된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주의:&lt;/b&gt; 아주 빠른 장치라면 인터럽트 처리 비용(문맥 교환 비용)이 폴링보다 더 비쌀 수 있으므로, 처음엔 잠깐 폴링하다가 안 끝나면 인터럽트를 쓰는 &lt;b&gt;하이브리드 방식&lt;/b&gt;을 쓰기도 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size23&quot;&gt;3.2. DMA (Direct Memory Access): &quot;짐 옮기는 건 비서가 해&quot;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;인터럽트를 써도 여전히 &lt;b&gt;데이터 복사(PIO)&lt;/b&gt;는 CPU가 직접 해야 했다. (예: 디스크에서 온 데이터를 메모리로 1바이트씩 옮기는 일). 이를 해결하는 것이 &lt;b&gt;DMA&lt;/b&gt;다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;27&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;동작:&lt;/b&gt; OS는 CPU 대신 &lt;b&gt;DMA 엔진&lt;/b&gt;이라는 특수 하드웨어에게 명령한다. &quot;디스크(장치)에 있는 데이터 4KB를 메모리 주소 X로 옮겨줘.&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해방:&lt;/b&gt; CPU는 데이터 복사 노동에서 해방되어 그 시간에 다른 프로세스를 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;완료:&lt;/b&gt; DMA 엔진이 데이터 복사를 다 마치면, 그때 CPU에게 &lt;b&gt;인터럽트&lt;/b&gt;를 걸어 보고한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;27,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;결과:&lt;/b&gt; CPU 점유율을 획기적으로 낮출 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-path-to-node=&quot;20&quot;&gt;4. 운영체제에 연결하기 : 디바이스 드라이버&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 다룰 문제는 서로 다른 인터페이스를 가지는 수많은 장치들과 운영체제를 연결시키는 일반화된 방법을 찾는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SCSI 디스크, IDE 디스크, USB 등과 같은 기기 위에서 동작하는 파일 시스템을 만들고자 하는데, 각 장치들의 구체적인 입출력 명령어 형식에 종속되게 만들고 싶지 않다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추상화(abstraction)라는 고전적 방법을 사용하여 이 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctnIna/dJMcabbBups/B520ZaEBcWeOyQPArYRdx1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctnIna/dJMcabbBups/B520ZaEBcWeOyQPArYRdx1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctnIna/dJMcabbBups/B520ZaEBcWeOyQPArYRdx1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctnIna%2FdJMcabbBups%2FB520ZaEBcWeOyQPArYRdx1%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;1070&quot; height=&quot;580&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 시스템은 어떤 디스크 종류를 사용하는지 전혀 알지 못한다. 파일 시스템은 범용 블록 계층에 블럭&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;read&lt;/span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;를 요청할 뿐이다. 범용 블럭 계층은 적절한 디바이스 드라이버로 받은 요청을 전달하며, 디바이스 드라이버는 특정 요청을 장치에 내리기 위해 필요한 일들을 처리한다. 이 그림을 통해 장치에 대한 구체적인 동작이 어떻게 운영체제의 대부분에게 숨겨지는지 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 캡슐화는 단점도 있다. 커널이 범용적인 인터페이스만을 제공하는 경우, 특수 기능을 많이 가진 장치는 사용하기 힘들 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 장치를 시스템에 연결하든 디바이스 드라이버가 필요하기 때문에,&lt;span&gt;&amp;nbsp;&lt;/span&gt;시간이 지나면서 디바이스 드라이버 코드가 커널 코드의 대부분을 차지하게 되었다. 드라이버들이 대부분 전업 커널 개발자가 아닌 개발자들에 의해 만들어지기 때문에 상당한 버그를 포함하고 있고, 커널 크래시의 주범이 되고 있다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/209</guid>
      <comments>https://gowoong.tistory.com/209#entry209comment</comments>
      <pubDate>Tue, 2 Dec 2025 15:24:06 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 13주차 - 병행성 3 Part.2</title>
      <link>https://gowoong.tistory.com/208</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 학습에서 락(Lock), 컨디션 변수(Condition Variable), 세마포어(Semaphore) 같은 도구를 배웠다. 하지만 도구를 안다고 해서 결함 없는 프로그램을 짤 수 있는 것은 아니다. 이번 포스팅에서는 실제 상용 소프트웨어에서 빈번하게 발생하는 &lt;b&gt;동시성 버그의 유형&lt;/b&gt;과, 쓰레드(Thread)의 대안으로 제시된 &lt;b&gt;이벤트(Event) 기반 동시성 모델이 가진 현실적인 어려움&lt;/b&gt;에 대해 OSTEP 책 내용을 바탕으로 깊이 있게 파고든다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 흔한 동시성 문제들 (Common Concurrency Problems)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;6,0&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;6,1&quot;&gt;&lt;span&gt;Lu 등의 연구진이 MySQL, Apache, Mozilla, OpenOffice와 같은 대형 오픈소스 소프트웨어의 버그를 분석한 결과, 동시성 버그는 크게 &lt;/span&gt;&lt;b&gt;&lt;span&gt;비교착 상태(Non-Deadlock) 버그&lt;/span&gt;&lt;/b&gt;&lt;span&gt;와 &lt;/span&gt;&lt;b&gt;&lt;span&gt;교착 상태(Deadlock) 버그&lt;/span&gt;&lt;/b&gt;&lt;span&gt;로 나뉜다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;6,2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;6,3&quot;&gt;. &lt;/span&gt;&lt;span data-path-to-node=&quot;6,4&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;6,5&quot;&gt;&lt;span&gt;놀랍게도 전체 동시성 버그 중 &lt;/span&gt;&lt;b&gt;&lt;span&gt;약 70% 이상이 Deadlock이 아닌 Non-Deadlock 버그&lt;/span&gt;&lt;/b&gt;&lt;span&gt;였다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;6,6&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;6,7&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1. 비 교착 상태 버그 (Non-Deadlock Bugs)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;8,0&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;8,1&quot;&gt;&lt;span&gt;이 유형의 버그는 주로 &lt;b&gt;원자성 위반(Atomicity Violation)&lt;/b&gt;과 &lt;/span&gt;&lt;b&gt;&lt;span&gt;순서 위반(Order Violation)&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 두 가지가 대부분을 차지한다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;8,2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;8,3&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 원자성 위반 (Atomicity Violation)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;10,0,1&quot;&gt;&lt;span data-path-to-node=&quot;10,0,1,0&quot;&gt;&lt;b&gt;&lt;span&gt;정의:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 더 이상 나눌 수 없는 작업 단위(Atomic Region)가 중간에 끼어든 다른 쓰레드에 의해 방해받아, 의도했던 원자성이 깨지는 현상이다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;10,0,1,1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;10,0,1,2&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사례 (MySQL):&lt;br /&gt;&lt;/b&gt;&lt;span data-path-to-node=&quot;10,1,2,1&quot;&gt;&lt;span&gt; 코드에서 Thread 1이 &lt;/span&gt;&lt;span&gt;proc_info&lt;/span&gt;&lt;span&gt;가 &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;이 아님을 확인하고 &lt;/span&gt;&lt;span&gt;fputs&lt;/span&gt;&lt;span&gt;를 호출하기 직전에, Thread 2가 끼어들어 &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt;로 바꿔버리면 &lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;10,1,2,1&quot;&gt;&lt;span&gt;Thread 1은 충돌(Crash)하게 된다.&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;10,1,2,2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;10,1,2,1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1764029930749&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Thread 1
if (thd-&amp;gt;proc_info) {
    // (이 시점에 인터럽트 발생하여 Thread 2가 실행된다면?)
    fputs(thd-&amp;gt;proc_info, ...);
}

// Thread 2
thd-&amp;gt;proc_info = NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-path-to-node=&quot;10,2,1,0&quot;&gt;&lt;b&gt;&lt;span&gt;해결:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 공유 변수를 참조하는 &lt;/span&gt;&lt;b&gt;&lt;span&gt;모든 구간에 락(Lock)을 걸어&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 검사(Check)와 사용(Use)이 끊기지 않고 원자적으로 실행되도록 보장해야 한다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;10,2,1,1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;10,2,1,2&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 순서 위반 (Order Violation)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-path-to-node=&quot;12,0,1&quot;&gt;&lt;span data-path-to-node=&quot;12,0,1,0&quot;&gt;&lt;b&gt;&lt;span&gt;정의:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 두 쓰레드 간의 메모리 접근 순서가 의도한 순서(A가 항상 B보다 먼저 실행)대로 지켜지지 않는 현상이다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;12,0,1,1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;12,0,1,2&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사례: &lt;/b&gt;&lt;span data-path-to-node=&quot;12,1,2,0&quot;&gt;Thread 2는 Thread 1이 mThread를 초기화했다고 가정하고 실행된다. &lt;/span&gt;&lt;span data-path-to-node=&quot;12,1,2,2&quot;&gt;&lt;span&gt;만약 Thread 2가 먼저 실행된다면 &lt;/span&gt;&lt;span&gt;NULL&lt;/span&gt;&lt;span&gt; 포인터 참조 등으로 오류가 발생한다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;12,1,2,4&quot;&gt;.&lt;/span&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1764030049534&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Thread 1: 초기화 수행
void init() {
    mThread = PR_CreateThread(mMain, ...);
    mThreadState = initialized;
}

// Thread 2: 사용
void mMain(...) {
    mState = mThread-&amp;gt;State; // mThread가 초기화되지 않았다면?
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-path-to-node=&quot;12,2,0,0&quot;&gt;&lt;b&gt;해결:&lt;/b&gt; 강제적인 순서 부여가 필요하다. &lt;/span&gt;&lt;span data-path-to-node=&quot;12,2,0,2&quot;&gt;&lt;span&gt;&lt;b&gt;컨디션 변수(Condition Variable)&lt;/b&gt;나 &lt;b&gt;세마포어(Semaphore)&lt;/b&gt;를 사용하여, Thread 2가 Thread 1의 작업 완료 신호를 기다리게 만들어야 한다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;12,2,0,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;12,2,0,4&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2. 교착 상태 버그 (Deadlock Bugs)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;15,0&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;15,1&quot;&gt;&lt;span&gt;교착 상태는 쓰레드들이 서로가 가진 자원(Lock)을 놓기만을 기다리며 영원히 멈춰버리는 현상이다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;15,2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;15,3&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 데드락 발생의 4가지 필수 조건 (Coffman Conditions)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;17,0&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;17,1&quot;&gt;&lt;span&gt;다음 네 가지 조건이 모두 만족되어야 데드락이 발생한다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;17,2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;17,3&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;상호 배제 (Mutual Exclusion):&lt;/b&gt; 자원을 한 번에 하나의 쓰레드만 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;점유 및 대기 (Hold-and-wait):&lt;/b&gt; 자원을 가진 상태에서 다른 자원을 기다린다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비선점 (No Preemption):&lt;/b&gt; 다른 쓰레드가 가진 자원을 강제로 뺏을 수 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;환형 대기 (Circular Wait):&lt;/b&gt; 자원 대기 관계가 꼬리를 물어 순환 고리를 형성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 해결 전략&lt;/h4&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;데드락을 해결하는 방법은 위 4가지 조건 중 하나를 깨트리는 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;21&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;순환 대기(Circular Wait) 제거 (가장 실용적):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-path-to-node=&quot;21,0,1,0,0,0&quot;&gt;모든 락에 &lt;b&gt;전역적인 순서(Total Ordering)&lt;/b&gt;를 부여한다. &lt;/span&gt;&lt;span data-path-to-node=&quot;21,0,1,0,0,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,0,1,0,0,2&quot;&gt;&lt;span&gt;예를 들어, 항상 &lt;/span&gt;&lt;span&gt;L1&lt;/span&gt;&lt;span&gt;을 획득한 후에만 &lt;/span&gt;&lt;span&gt;L2&lt;/span&gt;&lt;span&gt;를 획득하도록 코드를 작성하면 순환 대기가 발생하지 않는다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,0,1,0,0,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,0,1,0,0,4&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;점유 및 대기(Hold-and-wait) 제거:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-path-to-node=&quot;21,1,1,0,0,0&quot;&gt;필요한 모든 락을 처음에 &lt;b&gt;원자적으로 한꺼번에 획득&lt;/b&gt;한다. &lt;/span&gt;&lt;span data-path-to-node=&quot;21,1,1,0,0,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,1,1,0,0,2&quot;&gt;&lt;span&gt;이를 위해 전역적인 &lt;/span&gt;&lt;span&gt;prevention&lt;/span&gt;&lt;span&gt; 락을 사용할 수 있지만, 병행성이 저하되는 단점이 있다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,1,1,0,0,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,1,1,0,0,4&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비선점(No Preemption) 제거:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-path-to-node=&quot;21,2,1,0,0,0&quot;&gt;pthread_mutex_trylock()과 같은 인터페이스를 사용한다. 락 획득에 실패하면, 가지고 있던 락을 모두 놓고 다시 시도한다. &lt;/span&gt;&lt;span data-path-to-node=&quot;21,2,1,0,0,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,2,1,0,0,2&quot;&gt;&lt;span&gt;단, 무한히 시도만 반복하는 &lt;b&gt;라이브락(Livelock)&lt;/b&gt;에 빠질 위험이 있다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,2,1,0,0,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,2,1,0,0,4&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상호 배제(Mutual Exclusion) 제거:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-path-to-node=&quot;21,3,1,0,0,0&quot;&gt;락을 사용하지 않는 &lt;b&gt;Wait-free 자료구조&lt;/b&gt;를 사용한다. 하드웨어 명령인 Compare-And-Swap (CAS) 등을 이용해 락 없이 원자적 연산을 수행한다. &lt;/span&gt;&lt;span data-path-to-node=&quot;21,3,1,0,0,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,3,1,0,0,2&quot;&gt;&lt;span&gt;구현이 매우 복잡하다는 단점이 있다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,3,1,0,0,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;21,3,1,0,0,4&quot;&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 이벤트 기반 동시성의 어려움 (Difficulty of Using Events)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;24,0&quot;&gt;쓰레드 기반 프로그래밍의 대안으로, &lt;b&gt;이벤트 기반 동시성(Event-based Concurrency)&lt;/b&gt;이 있다. 이는 select()나 poll() 같은 시스템 콜을 이용해 이벤트 루프(Loop)를 돌며 작업을 처리하는 방식이다. &lt;/span&gt;&lt;span data-path-to-node=&quot;24,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;24,2&quot;&gt;&lt;span&gt;락(Lock)이 필요 없고 스케줄링을 개발자가 제어할 수 있다는 장점이 있지만, 치명적인 단점이 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 멀티 코어 활용의 어려움 (Multiple CPUs)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;26,0&quot;&gt;이벤트 루프는 기본적으로 &lt;b&gt;싱글 쓰레드&lt;/b&gt;에서 돌아간다. 따라서 현대적인 멀티 코어 CPU의 성능을 활용하지 못한다. 이를 해결하려면 여러 개의 이벤트 핸들러를 병렬로 실행해야 하는데, 이러면 결국 공유 데이터 보호를 위해 다시 &lt;b&gt;락(Lock)&lt;/b&gt;을 써야 한다. &lt;/span&gt;&lt;span data-path-to-node=&quot;26,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;26,2&quot;&gt;&lt;span&gt;즉, 이벤트 방식의 최대 장점(락이 필요 없음, 데드락 없음)이 사라지게 된다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;26,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;26,4&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 블로킹 시스템 콜의 문제 (Blocking System Calls)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;28,0&quot;&gt;이벤트 핸들러 내에서 디스크 I/O 같은 &lt;b&gt;블로킹(Blocking) 작업&lt;/b&gt;을 수행하면 전체 시스템이 멈춘다. 쓰레드 방식에서는 한 쓰레드가 멈춰도 OS가 다른 쓰레드를 실행시키면 되지만, 싱글 쓰레드 이벤트 루프에서는 서버 전체가 멈춰버린다. &lt;/span&gt;&lt;span data-path-to-node=&quot;28,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;28,2&quot;&gt;&lt;span&gt;따라서 모든 I/O를 비동기(Asynchronous) 방식으로 처리해야 하는데, 이는 코드 복잡도를 크게 높인다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;28,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;28,4&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 암시적 블로킹 (Implicit Blocking)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;30&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;30,0&quot;&gt;비동기 I/O를 사용하더라도 피할 수 없는 블로킹이 있다. 바로 &lt;b&gt;페이지 폴트(Page Fault)&lt;/b&gt;다. 이벤트 핸들러가 접근하려는 메모리가 디스크(Swap)에 있다면, OS는 해당 페이지를 메모리로 로드할 때까지 프로세스를 멈춘다(Block). &lt;/span&gt;&lt;span data-path-to-node=&quot;30,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;30,2&quot;&gt;&lt;span&gt;이는 프로그래머가 제어할 수 없는 영역이며, 서버 성능에 큰 타격을 줄 수 있다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;30,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;30,4&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 상태 관리의 복잡성 (State Management)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;32,0&quot;&gt;쓰레드 기반 코드에서는 함수의 지역 변수나 실행 흐름 정보가 &lt;b&gt;스택(Stack)&lt;/b&gt;에 자동으로 저장된다. 하지만 이벤트 방식에서는 I/O 작업을 요청하고 리턴해버리므로, 다음 이벤트(I/O 완료)가 발생했을 때 이어서 작업하기 위해 이전 상태를 &lt;b&gt;수동으로 저장(Manual Stack Management)&lt;/b&gt;해야 한다. &lt;/span&gt;&lt;span data-path-to-node=&quot;32,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;32,2&quot;&gt;&lt;span&gt;이를 위해 &lt;/span&gt;&lt;b&gt;&lt;span&gt;Continuation&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 같은 복잡한 기법을 사용해야 한다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;32,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;32,4&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5) API 변화에 대한 취약성&lt;/h3&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-path-to-node=&quot;34,0&quot;&gt;코드 내의 어떤 라이브러리 함수가 내부적으로 블로킹 방식으로 변경되거나 동작이 바뀌면, 이벤트 루프 전체가 영향을 받는다. &lt;/span&gt;&lt;span data-path-to-node=&quot;34,1&quot;&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;34,2&quot;&gt;&lt;span&gt;쓰레드 방식보다 라이브러리나 시스템 API의 변화에 훨씬 민감하게 반응하며, 유지보수가 어렵다&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;34,3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span data-path-to-node=&quot;34,4&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약 (Summary)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;37&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동시성 버그&lt;/b&gt;는 데드락보다 &lt;b&gt;원자성 위반&lt;/b&gt;이나 &lt;b&gt;순서 위반&lt;/b&gt; 같은 Non-Deadlock 버그가 더 흔하다. 이를 막기 위해 락과 컨디션 변수를 적재적소에 활용해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데드락&lt;/b&gt;을 막기 위한 가장 실용적인 방법은 락 획득에 &lt;b&gt;전역적인 순서(Ordering)&lt;/b&gt;를 정하는 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 기반 모델&lt;/b&gt;은 락이 필요 없다는 장점이 있지만, &lt;b&gt;블로킹 문제&lt;/b&gt;, &lt;b&gt;멀티 코어 활용의 한계&lt;/b&gt;, &lt;b&gt;수동 상태 관리의 복잡함&lt;/b&gt; 때문에 만능 해결책은 아니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;결국 동시성 프로그래밍에서 &quot;은탄환(Silver Bullet)&quot;은 없다. 상황에 따라 쓰레드와 락을 신중하게 사용하거나, 이벤트 방식을 사용할 때의 제약 사항을 명확히 이해하고 접근하는 것이 중요하다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/208</guid>
      <comments>https://gowoong.tistory.com/208#entry208comment</comments>
      <pubDate>Tue, 25 Nov 2025 09:26:05 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 12주차 - 병행성 3 Part.1</title>
      <link>https://gowoong.tistory.com/207</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제에서 쓰레드를 다룰 때 우리가 해결해야 할 문제는 크게 두 가지다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;상호 배제(Mutual Exclusion):&lt;/b&gt; 한 번에 하나의 쓰레드만 임계 영역(Critical Section)에 들어가게 하는 것. (주로 Lock으로 해결)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순서 정렬(Ordering):&lt;/b&gt; 쓰레드 A가 작업을 마친 후에 쓰레드 B가 실행되어야 하는 것처럼, 작업의 순서를 조율하는 것.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;Lock만으로는 두 번째 문제인 '순서 정렬'을 효율적으로 해결하기 어렵다. 부모 쓰레드가 자식 쓰레드가 끝날 때까지 기다려야 하는 경우를 생각해보자. 공유 변수를 계속 확인하며 무한 루프를 도는 방식(Spinning)은 CPU 자원을 심각하게 낭비한다. 이때 필요한 것이 &lt;b&gt;컨디션 변수&lt;/b&gt;와 &lt;b&gt;세마포어&lt;/b&gt;다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 컨디션 변수 (Condition Variables)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;컨디션 변수는 쓰레드가 특정 조건(Condition)이 참이 될 때까지 &lt;b&gt;기다리게(Sleep)&lt;/b&gt; 하는 장치다. OSTEP에서는 이를 &quot;쓰레드들이 실행을 멈추고, 조건이 충족되었다는 신호를 받을 때까지 대기하는 큐(Queue)&quot;라고 정의한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 동작 (API)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;컨디션 변수는 항상 &lt;b&gt;Lock(Mutex)과 함께 사용&lt;/b&gt;되어야 한다. 주요 함수는 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;wait(condition, lock):
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호출한 쓰레드를 대기 상태로 만든다(Sleep).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핵심 동작:&lt;/b&gt; 이 함수는 호출 시 &lt;b&gt;가지고 있던 Lock을 원자적(Atomically)으로 해제&lt;/b&gt;하고 잠에 든다. 그리고 나중에 깨어날 때 &lt;b&gt;다시 Lock을 획득&lt;/b&gt;하고 리턴한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;signal(condition):
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대기 큐에서 자고 있는 쓰레드 중 하나를 깨운다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;broadcast(condition):
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대기 큐에 있는 모든 쓰레드를 깨운다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;올바른 사용 패턴 (The Canonical Pattern)&lt;/h3&gt;
&lt;pre id=&quot;code_1764028933149&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// [대기하는 쓰레드]
pthread_mutex_lock(&amp;amp;lock);
while (ready == 0) {         // 조건이 만족되지 않았다면
    pthread_cond_wait(&amp;amp;cond, &amp;amp;lock); // 잠든다 (Lock 해제 -&amp;gt; Sleep -&amp;gt; 깨어나면 Lock 획득)
}
// (작업 수행)
pthread_mutex_unlock(&amp;amp;lock);

// [신호 보내는 쓰레드]
pthread_mutex_lock(&amp;amp;lock);
ready = 1;                   // 조건을 변경하고
pthread_cond_signal(&amp;amp;cond);  // 자고 있는 쓰레드를 깨운다
pthread_mutex_unlock(&amp;amp;lock);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 if가 아니라 while인가? (Mesa Semantics)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;많은 입문자가 실수하는 부분이 while 대신 if를 사용하는 것이다. 하지만 반드시 while을 써야 한다. 그 이유는 현대 운영체제 대부분이 &lt;b&gt;Mesa Semantics&lt;/b&gt;를 따르기 때문이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;Mesa Semantics에서는 signal을 보내 쓰레드를 깨웠더라도, &lt;b&gt;그 쓰레드가 즉시 실행된다는 보장이 없다.&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;깨어난 쓰레드가 락을 다시 획득하기 전에 다른 쓰레드가 끼어들어 상태(ready)를 다시 0으로 바꿔버릴 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;허위 기상(Spurious Wakeup):&lt;/b&gt; 드물게 조건이 충족되지 않았는데도 쓰레드가 깨어나는 현상이 발생할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;따라서 wait에서 리턴되었더라도, &lt;b&gt;조건이 정말로 만족되었는지 다시 한번 확인(Re-check)&lt;/b&gt;해야 하므로 while 루프가 필수적이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 세마포어 (Semaphores)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;세마포어는 에츠허르 데이크스트라(Edsger Dijkstra)가 제안한 개념으로, Lock과 컨디션 변수의 기능을 모두 수행할 수 있는 강력하고 유연한 도구다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의와 동작&lt;/h3&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;세마포어는 내부적으로 &lt;b&gt;정수 값(Integer Value)&lt;/b&gt;을 하나 가진 객체다. 이 값은 오직 두 가지 원자적 연산으로만 조작할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;sem_wait() (P 연산):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세마포어 값을 1 감소시킨다.&lt;/li&gt;
&lt;li&gt;감소 후 값이 0 이상이면 즉시 리턴한다(진행).&lt;/li&gt;
&lt;li&gt;감소 후 값이 음수이면, 호출한 쓰레드는 대기 상태(Sleep)로 들어간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sem_post() (V 연산):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;25,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세마포어 값을 1 증가시킨다.&lt;/li&gt;
&lt;li&gt;대기 중인 쓰레드가 있다면(값이 음수였다면) 하나를 깨운다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-path-to-node=&quot;26&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;26,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고:&lt;/b&gt; 세마포어의 값이 음수일 때, 그 절대값은 현재 대기 중인 쓰레드의 개수를 의미한다. (예: -3이면 3개의 쓰레드가 자고 있음)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;세마포어의 활용법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 락(Lock)으로 사용하기 (Binary Semaphore)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;29&quot; data-ke-size=&quot;size16&quot;&gt;세마포어의 &lt;b&gt;초기값을 1&lt;/b&gt;로 설정하면 Mutex Lock처럼 동작한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;30&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;초기값 1:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;30,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 쓰레드 sem_wait() 호출 -&amp;gt; 값은 0이 되고 A는 진입(Lock 획득).&lt;/li&gt;
&lt;li&gt;B 쓰레드 sem_wait() 호출 -&amp;gt; 값은 -1이 되고 B는 대기(Sleep).&lt;/li&gt;
&lt;li&gt;A 쓰레드 sem_post() 호출 -&amp;gt; 값은 0이 되고 B를 깨움. B가 락을 획득하게 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 순서 정렬(Ordering)로 사용하기&lt;/h3&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;세마포어의 &lt;b&gt;초기값을 0&lt;/b&gt;으로 설정하면, join()과 같은 기능을 구현할 수 있다. (부모가 자식을 기다림)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;초기값 0:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;33,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모: sem_wait() 호출 -&amp;gt; 값은 -1이 되고 부모는 즉시 잠든다.&lt;/li&gt;
&lt;li&gt;자식: 작업을 마치고 sem_post() 호출 -&amp;gt; 값은 0이 되고 부모를 깨운다.&lt;/li&gt;
&lt;li&gt;결과적으로 부모는 자식이 끝날 때까지 기다리게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 생산자/소비자 문제 (Bounded Buffer Problem)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;35&quot; data-ke-size=&quot;size16&quot;&gt;유한한 버퍼를 두고 데이터를 넣는 생산자와 데이터를 꺼내는 소비자가 있을 때, 세마포어 3개를 조합하여 해결한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;36&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;empty: 버퍼의 빈 공간 개수 (초기값 = 버퍼 크기 MAX)&lt;/li&gt;
&lt;li&gt;full: 버퍼에 차 있는 데이터 개수 (초기값 = 0)&lt;/li&gt;
&lt;li&gt;mutex: 버퍼에 접근할 때 상호 배제를 위한 락 (초기값 = 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[생산자 코드]&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1764029086050&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void producer() {
    sem_wait(&amp;amp;empty); // 빈 공간이 생길 때까지 대기 (빈 공간 1 감소)
    sem_wait(&amp;amp;mutex); // 임계 영역 진입 (Lock 획득)
    put_data();       // 데이터 넣기
    sem_post(&amp;amp;mutex); // Lock 해제
    sem_post(&amp;amp;full);  // 찬 공간 1 증가 (소비자 깨움)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[주의할 점 - 데드락]&lt;/b&gt; 위 코드에서 sem_wait(&amp;amp;empty)와 sem_wait(&amp;amp;mutex)의 순서를 바꾸면 &lt;b&gt;데드락(Deadlock)&lt;/b&gt;이 발생할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;40&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생산자가 mutex를 잡았는데 버퍼가 꽉 차서(empty 대기) 잠들면, 소비자는 mutex를 얻지 못해 데이터를 꺼내지 못하고, 결국 영원히 서로 기다리게 된다. &lt;b&gt;반드시 락을 얻기 전에 자원(빈 공간)의 가용 여부부터 확인해야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/207</guid>
      <comments>https://gowoong.tistory.com/207#entry207comment</comments>
      <pubDate>Tue, 25 Nov 2025 09:08:30 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 11주차 - 병행성 2 Part.2</title>
      <link>https://gowoong.tistory.com/206</link>
      <description>&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;락 기반의 병행 자료 구조&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;병행 자료 구조(Concurrent Data Structures)란 다수의 스레드가 동시에 접근할 수 있는 자료 구조를 말한다. 이러한 자료 구조는 병행성(Concurrency) 문제를 해결하기 위해 락(Lock)을 사용하여 데이터의 일관성(Consistency)을 유지한다. 락 기반의 병행 자료 구조는 스레드 간의 데이터 접근 충돌을 방지하고, 데이터의 정확성을 보장하는 데 매우 중요한 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;병행성이란 여러 작업이 동시에 진행되는 것을 말하며, 멀티스레드 환경에서 흔히 발생한다. 이때 공유 자원에 대한 접근을 적절히 제어하지 않으면 데이터 불일치, 데이터 손상 등의 문제가 발생할 수 있다. 락은 이러한 문제를 해결하기 위해 사용되는 동기화 메커니즘으로, 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 제어한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래에서는 대표적인 락 기반의 병행 자료 구조인 병행 카운터, 병행 연결 리스트, 병행 큐, 병행 해시 테이블에 대해 자세히 설명하겠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병행 카운터(Concurrent Counter)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병행 카운터는 다수의 스레드가 동시에 값을 증가시키거나 감소시킬 수 있는 카운터다. 이때 가장 중요한 점은 여러 스레드가 동시에 카운터 값을 변경할 때도 그 값이 정확하게 유지되어야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병행 카운터는 일반적으로 락을 사용하여 구현된다. 카운터의 값을 읽거나 쓸 때 락을 획득하여 다른 스레드의 접근을 막고, 연산이 끝나면 락을 해제한다. 이를 통해 카운터 값의 일관성을 보장할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell0&quot; class=&quot;cs&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;pthread_mutex_t lock;
int counter = 0;

void increment() {
    pthread_mutex_lock(&amp;amp;lock);  // 락 획득
    counter++;                  // 카운터 증가
    pthread_mutex_unlock(&amp;amp;lock);  // 락 해제
}

void decrement() {
    pthread_mutex_lock(&amp;amp;lock);  // 락 획득
    counter--;                  // 카운터 감소
    pthread_mutex_unlock(&amp;amp;lock);  // 락 해제
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;pthread_mutex_t&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;타입의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;lock&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수가 락의 역할을 한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;increment()&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;decrement()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수에서는 락을 획득한 후 카운터 값을 변경하고, 마지막에 락을 해제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 락을 이용한 구현은 비교적 간단하고 데이터의 일관성을 보장할 수 있다는 장점이 있다. 하지만 모든 연산에 락을 사용하므로 성능 저하가 발생할 수 있고, 특히 다수의 스레드가 동시에 접근할 때는 락 경합(Lock Contention)으로 인해 효율이 크게 떨어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병행 카운터는 웹 서버에서 활성 사용자 수를 추적하거나, 데이터베이스에서 특정 이벤트 발생 횟수를 기록하는 등 다양한 응용 프로그램에서 사용된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병행 연결 리스트(Concurrent Linked List)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;병행 연결 리스트는 다수의 스레드가 동시에 노드를 추가하거나 삭제할 수 있는 연결 리스트다. 이때 락을 사용하여 스레드 간의 노드 접근을 안전하게 제어한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;병행 연결 리스트는 노드별로 락을 사용하거나, 전체 리스트에 대한 하나의 락을 사용할 수 있다. 노드별 락을 사용하면 리스트의 다른 부분을 동시에 수정할 수 있어 성능이 향상될 수 있지만, 구현이 복잡해지고 데드락(Deadlock)이나 라이브락(Livelock)과 같은 문제가 발생할 수 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell1&quot; class=&quot;crmsh&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;typedef struct Node {
    int data;
    struct Node* next;
    pthread_mutex_t lock;
} Node;

Node* head = NULL;
pthread_mutex_t list_lock = PTHREAD_MUTEX_INITIALIZER;

void add_node(int value) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node-&amp;gt;data = value;
    pthread_mutex_init(&amp;amp;new_node-&amp;gt;lock, NULL);

    pthread_mutex_lock(&amp;amp;list_lock);  // 리스트 락 획득
    new_node-&amp;gt;next = head;
    head = new_node;
    pthread_mutex_unlock(&amp;amp;list_lock);  // 리스트 락 해제
}

void remove_node(int value) {
    pthread_mutex_lock(&amp;amp;list_lock);  // 리스트 락 획득
    Node* current = head;
    Node* prev = NULL;

    while (current != NULL) {
        if (current-&amp;gt;data == value) {
            if (prev == NULL) {
                head = current-&amp;gt;next;
            } else {
                prev-&amp;gt;next = current-&amp;gt;next;
            }
            pthread_mutex_destroy(&amp;amp;current-&amp;gt;lock);
            free(current);
            break;
        }
        prev = current;
        current = current-&amp;gt;next;
    }
    pthread_mutex_unlock(&amp;amp;list_lock);  // 리스트 락 해제
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드에서는 연결 리스트 전체에 대한 하나의 락(&lt;span&gt;list_lock&lt;/span&gt;)을 사용하여 노드 추가와 삭제 연산을 제어한다. 노드 추가 시에는 새로운 노드를 생성하고 락을 초기화한 후, 리스트 락을 획득하여 새 노드를 리스트의 head에 추가한다. 노드 삭제 시에는 리스트 락을 획득한 후 리스트를 탐색하여 해당 노드를 찾아 제거한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;병행 연결 리스트는 운영체제의 스케줄러, 메모리 할당기, 네트워크 프로토콜 스택 등 다양한 시스템 소프트웨어에서 사용된다. 예를 들어, 스케줄러는 실행 중인 프로세스나 스레드의 목록을 관리하기 위해 병행 연결 리스트를 활용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병행 큐(Concurrent Queue)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병행 큐는 다수의 스레드가 동시에 요소를 삽입하거나 제거할 수 있는 큐다. 병행 큐는 생산자-소비자 문제(Producer-Consumer Problem)를 해결하기 위해 자주 사용되며, 락을 통해 데이터의 일관성을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병행 큐는 삽입 연산과 제거 연산에 대해 별도의 락을 사용할 수 있다. 이를 통해 삽입과 제거가 동시에 이루어질 수 있어 성능이 향상된다. 하지만 락 사용으로 인한 성능 저하가 발생할 수 있으며, 특히 큐의 길이가 길어질수록 락 경합이 심해질 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell2&quot; class=&quot;xl&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;typedef struct QueueNode {
    int data;
    struct QueueNode* next;
} QueueNode;

typedef struct {
    QueueNode* front;
    QueueNode* rear;
    pthread_mutex_t lock;
    pthread_cond_t cond;
} Queue;

void enqueue(Queue* q, int value) {
    QueueNode* new_node = (QueueNode*)malloc(sizeof(QueueNode));
    new_node-&amp;gt;data = value;
    new_node-&amp;gt;next = NULL;

    pthread_mutex_lock(&amp;amp;q-&amp;gt;lock);  // 큐 락 획득
    if (q-&amp;gt;rear == NULL) {
        q-&amp;gt;front = new_node;
        q-&amp;gt;rear = new_node;
    } else {
        q-&amp;gt;rear-&amp;gt;next = new_node;
        q-&amp;gt;rear = new_node;
    }
    pthread_cond_signal(&amp;amp;q-&amp;gt;cond);  // 대기 중인 스레드에 시그널 전송
    pthread_mutex_unlock(&amp;amp;q-&amp;gt;lock);  // 큐 락 해제
}

int dequeue(Queue* q) {
    pthread_mutex_lock(&amp;amp;q-&amp;gt;lock);  // 큐 락 획득
    while (q-&amp;gt;front == NULL) {
        pthread_cond_wait(&amp;amp;q-&amp;gt;cond, &amp;amp;q-&amp;gt;lock);  // 조건 변수를 사용하여 대기
    }
    QueueNode* temp = q-&amp;gt;front;
    int value = temp-&amp;gt;data;
    q-&amp;gt;front = q-&amp;gt;front-&amp;gt;next;
    if (q-&amp;gt;front == NULL) {
        q-&amp;gt;rear = NULL;
    }
    free(temp);
    pthread_mutex_unlock(&amp;amp;q-&amp;gt;lock);  // 큐 락 해제
    return value;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 큐 전체에 대한 하나의 락(&lt;span&gt;lock&lt;/span&gt;)과 조건 변수(&lt;span&gt;cond&lt;/span&gt;)를 사용한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;enqueue()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수에서는 큐 락을 획득한 후 새로운 노드를 큐의 rear에 추가하고, 대기 중인 스레드에 시그널을 보낸다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;dequeue()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수에서는 큐 락을 획득하고, 큐가 비어 있는 경우 조건 변수를 사용하여 대기한다. 큐에 요소가 있으면 front 노드를 제거하고 해당 값을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병행 큐는 로그 시스템, 작업 스케줄러, 메시지 전달 시스템 등 생산자-소비자 패턴이 필요한 다양한 응용 프로그램에서 사용된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병행 해시 테이블(Concurrent Hash Table)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;병행 해시 테이블은 다수의 스레드가 동시에 키-값 쌍을 삽입, 삭제 또는 조회할 수 있는 해시 테이블이다. 병행 해시 테이블은 성능 향상을 위해 버킷(Bucket) 단위로 락을 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 버킷에 대해 개별적인 락을 사용하면 서로 다른 버킷에 대한 연산이 동시에 이루어질 수 있어 성능이 크게 향상된다. 하지만 락의 개수가 많아질수록 메모리 오버헤드가 증가하고 구현이 복잡해질 수 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell3&quot; class=&quot;pgsql&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;#define HASH_SIZE 101

typedef struct HashNode {
    int key;
    int value;
    struct HashNode* next;
    pthread_mutex_t lock;
} HashNode;

typedef struct {
    HashNode* buckets[HASH_SIZE];
    pthread_mutex_t table_lock;
} HashTable;

unsigned int hash(int key) {
    return key % HASH_SIZE;
}

void insert(HashTable* table, int key, int value) {
    unsigned int index = hash(key);
    pthread_mutex_lock(&amp;amp;table-&amp;gt;table_lock);  // 테이블 락 획득

    HashNode* new_node = (HashNode*)malloc(sizeof(HashNode));
    new_node-&amp;gt;key = key;
    new_node-&amp;gt;value = value;
    pthread_mutex_init(&amp;amp;new_node-&amp;gt;lock, NULL);

    new_node-&amp;gt;next = table-&amp;gt;buckets[index];
    table-&amp;gt;buckets[index] = new_node;

    pthread_mutex_unlock(&amp;amp;table-&amp;gt;table_lock);  // 테이블 락 해제
}

int lookup(HashTable* table, int key) {
    unsigned int index = hash(key);
    pthread_mutex_lock(&amp;amp;table-&amp;gt;table_lock);  // 테이블 락 획득

    HashNode* current = table-&amp;gt;buckets[index];
    while (current != NULL) {
        if (current-&amp;gt;key == key) {
            pthread_mutex_unlock(&amp;amp;table-&amp;gt;table_lock);  // 테이블 락 해제
            return current-&amp;gt;value;
        }
        current = current-&amp;gt;next;
    }

    pthread_mutex_unlock(&amp;amp;table-&amp;gt;table_lock);  // 테이블 락 해제
    return -1; // 키를 찾지 못한 경우
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드에서는 해시 테이블 전체에 대한 하나의 락(&lt;span&gt;table_lock&lt;/span&gt;)을 사용한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;insert()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수에서는 테이블 락을 획득한 후 새로운 노드를 해당 버킷에 추가한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;lookup()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수에서는 테이블 락을 획득하고 해당 버킷을 탐색하여 키에 해당하는 값을 반환한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;병행 해시 테이블은 데이터베이스 시스템, 캐시 시스템, 네트워크 라우팅 테이블 등 다양한 응용 프로그램에서 사용된다. 예를 들어, 웹 서버의 세션 관리 시스템에서는 세션 정보를 병행 해시 테이블에 저장하여 여러 스레드가 동시에 세션 정보를 효율적으로 조회하고 갱신할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;락 기반 병행 자료 구조의 장단점&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;락 기반의 병행 자료 구조는 다음과 같은 장단점을 가지고 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;장점:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구현이 비교적 간단하고 직관적이다.&lt;/li&gt;
&lt;li&gt;데이터의 일관성을 보장할 수 있다.&lt;/li&gt;
&lt;li&gt;다양한 자료 구조에 적용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단점:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락 사용으로 인한 성능 저하가 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;락 경합이 발생하면 효율이 크게 떨어질 수 있다.&lt;/li&gt;
&lt;li&gt;데드락이나 라이브락 등의 문제가 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;락의 개수가 많아질수록 메모리 오버헤드가 증가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 락 기반의 병행 자료 구조를 사용할 때는 락의 granularity를 적절히 조절하고, 불필요한 락 사용을 최소화하며, 데드락 등의 문제를 예방하기 위한 주의가 필요하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;락 없는 병행 자료 구조&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락 기반의 병행 자료 구조가 가진 단점을 해결하기 위해 락 없는(Lock-Free) 병행 자료 구조가 제안되었다. 락 없는 병행 자료 구조는 락을 사용하지 않고 아토믹 연산(Atomic Operation)을 활용하여 병행성을 제어한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 락 없는 병행 자료 구조로는 다음과 같은 것들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락 없는 스택(Lock-Free Stack)&lt;/li&gt;
&lt;li&gt;락 없는 큐(Lock-Free Queue)&lt;/li&gt;
&lt;li&gt;락 없는 해시 테이블(Lock-Free Hash Table)&lt;/li&gt;
&lt;li&gt;락 없는 연결 리스트(Lock-Free Linked List)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락 없는 병행 자료 구조는 락 사용에 따른 성능 저하를 방지하고, 데드락 등의 문제를 해결할 수 있다는 장점이 있다. 하지만 구현이 복잡하고 아토믹 연산의 사용으로 인한 오버헤드가 발생할 수 있다는 단점도 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락 기반의 병행 자료 구조는 멀티스레드 환경에서 데이터의 일관성을 유지하기 위해 널리 사용되는 방법이다. 병행 카운터, 병행 연결 리스트, 병행 큐, 병행 해시 테이블 등 다양한 자료 구조에 락을 적용하여 스레드 간의 동기화를 제어할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 락 사용에 따른 성능 저하와 락 경합, 데드락 등의 문제를 인지하고 적절히 대처해야 한다. 이를 위해 락의 granularity를 조절하고, 불필요한 락 사용을 최소화하며, 데드락 예방 기법을 활용하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 락 없는 병행 자료 구조와 같은 대안적인 방법을 고려하여 상황에 맞는 최적의 솔루션을 선택해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병행 프로그래밍에서 자료 구조의 선택과 구현은 매우 중요한 문제다. 락 기반의 병행 자료 구조와 락 없는 병행 자료 구조의 특성을 잘 이해하고, 적재적소에 활용함으로써 효율적이고 안정적인 병행 프로그램을 작성할 수 있을 것이다.&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/206</guid>
      <comments>https://gowoong.tistory.com/206#entry206comment</comments>
      <pubDate>Tue, 11 Nov 2025 18:00:53 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 11주차 - 병행성 2 Part.1</title>
      <link>https://gowoong.tistory.com/205</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;락(Lock)은 코드의 특정 영역을 감싸서 한 순간에 오로지 한 스레드만 이 영역에 접근할 수 있도록 해주는 동기화 메커니즘이다. 즉, 락은 여러 스레드가 공유 자원에 동시에 접근하는 것을 제어하여 상호 배제(Mutual Exclusion)를 보장한다. 이 락이 없으면 멀티 스레드 프로그램에서 경쟁 조건(Race Condition)이 발생하여 예상치 못한 결과를 초래할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;락의 기본 개념&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;예를 들어, 다음과 같은 공유 자원에 대한 연산이 있다고 가정해본다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;codecell0&quot; class=&quot;ini&quot; style=&quot;background-color: #f3f4f5; color: #222832; text-align: start;&quot;&gt;&lt;code&gt;balance = balance + 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;이 코드를 임계 영역(Critical Section)이라고 하며, 락을 사용하여 다음과 같이 보호할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762850143232&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;lock_t mutex; // 전역 변수로 선언된 락
...
lock(&amp;amp;mutex);
balance = balance + 1;
unlock(&amp;amp;mutex);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락은 하나의 변수로 표현되며, 사용하기 전에 먼저 선언해야 한다. 이 락 변수는 두 가지 상태를 가질 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용 가능 상태: 어느 스레드도 락을 가지고 있지 않은 상태&lt;/li&gt;
&lt;li&gt;사용 중 상태: 임계 영역에서 정확히 하나의 스레드가 락을 획득한 상태&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;lock()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는 락을 획득하려고 시도하고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;unlock()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는 획득한 락을 해제한다. 어떤 스레드가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;lock()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를 호출하여 락을 획득하면, 해당 스레드를 락의 소유자(Owner)라고 부른다. 락이 이미 다른 스레드에 의해 사용 중인 경우,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;lock()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는 해당 락이 해제될 때까지 대기한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락의 소유자가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;unlock()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를 호출하면 락은 다시 사용 가능한 상태가 된다. 만약 어떤 스레드도 해당 락을 기다리고 있지 않다면, 락은 사용 가능한 상태로 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락은 프로그래머에게 스케줄링에 대한 제어권을 제공하여 특정 코드 영역 내에서 한 번에 하나의 스레드만 실행되도록 보장한다. 이를 통해 스레드 간의 잘못된 실행 순서로 인한 문제를 예방할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;POSIX 스레드 락&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POSIX 스레드 라이브러리에서는 락을 뮤텍스(Mutex)라고 부른다. 뮤텍스는 &amp;ldquo;Mutual Exclusion&amp;rdquo;의 줄임말로, 스레드 간의 상호 배제를 의미한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;POSIX 스레드에서 뮤텍스를 사용하는 예제 코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1762850261364&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

Pthread_mutex_lock(&amp;amp;lock); // pthread_mutex_lock()을 위한 래퍼 함수
balance = balance + 1;
Pthread_mutex_unlock(&amp;amp;lock);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;POSIX 스레드에서는 뮤텍스 변수를 선언하고, 이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;pthread_mutex_lock()&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;pthread_mutex_unlock()&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;락의 세분성에 따른 전략&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락의 세분성(Granularity)에 따라 두 가지 전략으로 나눌 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;거친 락(Coarse-Grained Locking)
&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세밀한 락(Fine-Grained Locking)
&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;병렬성이 향상되지만, 데드락(Deadlock) 등의 복잡한 문제가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;락의 구현&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락은 다양한 방식으로 구현될 수 있다. 대표적인 방법으로는 인터럽트 비활성화, 테스트 앤 셋(Test-and-Set), 컴페어 앤 스왑(Compare-and-Swap), 페치 앤 애드(Fetch-and-Add) 등이 있다. 각각의 방법은 하드웨어 지원 여부와 특성에 따라 선택될 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;락의 평가 기준&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락의 성능과 효율성을 평가하기 위해 다음과 같은 기준을 고려할 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;상호 배제(Mutual Exclusion)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락의 가장 기본적인 역할로, 한 번에 하나의 스레드만 임계 영역에 접근할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공정성(Fairness)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 스레드가 락을 공평하게 획득할 수 있어야 한다. 특정 스레드가 락을 독점하거나 기아 상태에 빠지지 않아야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;성능(Performance)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;락의 사용으로 인한 오버헤드를 최소화해야 한다. 락의 획득과 해제에 소요되는 시간이 짧아야 하며, 불필요한 대기 시간을 줄여야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;인터럽트 비활성화를 통한 락 구현&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기의 단일 프로세서 시스템에서는 인터럽트를 비활성화하여 상호 배제를 구현했다.&lt;/p&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell3&quot; class=&quot;cpp&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;void lock() {
    DisableInterrupts();
}

void unlock() {
    EnableInterrupts();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 구현이 간단하지만, 몇 가지 단점이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인터럽트 활성화/비활성화는 커널 모드 권한이 필요하므로, 사용자 코드에서는 사용이 제한된다.&lt;/li&gt;
&lt;li&gt;멀티프로세서 환경에서는 적용할 수 없다. 한 프로세서에서 인터럽트를 비활성화해도 다른 프로세서에서는 여전히 임계 영역에 접근할 수 있기 때문.&lt;/li&gt;
&lt;li&gt;장시간 인터럽트를 비활성화하면 중요한 이벤트를 놓칠 수 있다.&lt;/li&gt;
&lt;li&gt;인터럽트 활성화/비활성화 명령어는 일반 명령어에 비해 실행 시간이 길어 효율성이 떨어진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 인터럽트 비활성화는 제한된 상황에서만 사용되어야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;테스트 앤 셋(Test-and-Set)을 통한 락 구현&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 앤 셋(Test-and-Set) 또는 아토믹 익스체인지(Atomic Exchange)는 멀티프로세서 환경에서 락을 구현하기 위한 하드웨어 명령어다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell4&quot; class=&quot;pgsql&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;int TestAndSet(int *old_ptr, int new) {
    int old = *old_ptr;
    *old_ptr = new;
    return old;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;TestAndSet&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;old_ptr&lt;/span&gt;이 가리키는 메모리 위치의 값을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;로 설정하고, 이전 값을 반환한다. 이 과정은 원자적(Atomic)으로 수행되어 경쟁 조건을 방지한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;TestAndSet&lt;/span&gt;을 사용하여 스핀락(Spinlock)을 구현할 수 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell5&quot; class=&quot;cpp&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;typedef struct __lock_t {
    int flag;
} lock_t;

void init(lock_t *lock) {
    lock-&amp;gt;flag = 0;  // 0: 사용 가능, 1: 사용 중
}

void lock(lock_t *lock) {
    while (TestAndSet(&amp;amp;lock-&amp;gt;flag, 1) == 1)
        ; // 스핀 대기
}

void unlock(lock_t *lock) {
    lock-&amp;gt;flag = 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;lock()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;TestAndSet&lt;/span&gt;을 사용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;flag&lt;/span&gt;가 0일 때 1로 변경하고 락을 획득한다. 만약&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;flag&lt;/span&gt;가 이미 1이라면 0이 될 때까지 스핀 대기한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;unlock()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;flag&lt;/span&gt;를 0으로 변경하여 락을 해제한다.&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;컴페어 앤 스왑(Compare-and-Swap)을 통한 락 구현&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;컴페어 앤 스왑(Compare-and-Swap)은 또 다른 하드웨어 명령어로, 메모리 위치의 값을 예상 값과 비교하고, 일치하면 새로운 값으로 교체한다.&lt;/p&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell6&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;int CompareAndSwap(int *ptr, int expected, int new) {
    int actual = *ptr;
    if (actual == expected)
        *ptr = new;
    return actual;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;CompareAndSwap&lt;/span&gt;을 사용하여 스핀락을 구현할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell7&quot; class=&quot;cpp&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;typedef struct __lock_t {
    int flag;
} lock_t;

void init(lock_t *lock) {
    lock-&amp;gt;flag = 0;  // 0: 사용 가능, 1: 사용 중
}

void lock(lock_t *lock) {
    while (CompareAndSwap(&amp;amp;lock-&amp;gt;flag, 0, 1) == 1)
        ; // 스핀 대기
}

void unlock(lock_t *lock) {
    lock-&amp;gt;flag = 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;CompareAndSwap&lt;/span&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;TestAndSet&lt;/span&gt;과 유사하게 동작하지만, 예상 값과 실제 값을 비교한다는 점에서 더 강력한 기능을 제공한다.&lt;/p&gt;
&lt;h2 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페치 앤 애드(Fetch-and-Add)를 통한 락 구현&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페치 앤 애드(Fetch-and-Add)는 메모리 위치의 값을 원자적으로 증가시키고, 이전 값을 반환하는 하드웨어 명령어다.&lt;/p&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell8&quot; class=&quot;autoit&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;int FetchAndAdd(int *ptr) {
    int old = *ptr;
    *ptr = old + 1;
    return old;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;FetchAndAdd&lt;/span&gt;를 사용하여 티켓 락(Ticket Lock)을 구현할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell9&quot; class=&quot;arduino&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;typedef struct __lock_t {
    int ticket;
    int turn;
} lock_t;

void lock_init(lock_t *lock) {
    lock-&amp;gt;ticket = 0;
    lock-&amp;gt;turn = 0;
}

void lock(lock_t *lock) {
    int myturn = FetchAndAdd(&amp;amp;lock-&amp;gt;ticket);
    while (lock-&amp;gt;turn != myturn)
        ; // 스핀 대기
}

void unlock(lock_t *lock) {
    FetchAndAdd(&amp;amp;lock-&amp;gt;turn);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;티켓 락은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;ticket&lt;/span&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;turn&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수를 사용하여 락을 요청한 순서대로 스레드에게 락을 할당한다. 이를 통해 공정성을 보장할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;과도한 스핀에 대한 주의사항&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스핀락을 사용할 때 주의해야 할 점은 과도한 스핀(Excessive Spinning)이다. 이는 락을 획득하기 위해 스레드가 무한히 반복하여 CPU 자원을 낭비하는 상황을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과도한 스핀은 다음과 같은 경우에 발생할 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;락의 소유자가 락을 장시간 보유하는 경우&lt;/li&gt;
&lt;li&gt;많은 스레드가 락을 경쟁적으로 획득하려는 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과도한 스핀을 방지하기 위해서는 락의 보유 시간을 최소화하고, 필요한 경우 스레드 간의 락 양보 메커니즘을 구현해야 한다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/205</guid>
      <comments>https://gowoong.tistory.com/205#entry205comment</comments>
      <pubDate>Tue, 11 Nov 2025 17:49:50 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 10주차 - 병행성 1</title>
      <link>https://gowoong.tistory.com/204</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 하나의 물리적 CPU를 다수의 가상 CPU로 확장하여 마치 여러 개의 프로그램이 동시에 실행되는 것처럼 보이게 만든다. 이를 통해 사용자는 컴퓨터가 동시에 여러 작업을 수행하는 것처럼 느끼게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 또한 각 프로세스가 독립적으로 많은 가상 메모리를 가지고 있는 것처럼 보이게 만든다. 이는 주소공간이라는 개념을 통해 구현된다. 주소공간은 각 프로그램이 메모리의 어느 부분을 사용할 수 있는지를 나타내는 가상의 메모리 공간이다. 운영체제는 물리 메모리를 여러 개의 주소 공간이 번갈아 가며 사용할 수 있도록 관리한다. 이를 통해 각 프로그램은 마치 자신만의 메모리를 가지고 있는 것 같은 독립성을 가진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병행성: 개요&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;쓰레드의 개념&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 프로그램은 한순간에 하나의 명령만을 실행한다. 이를 단일 쓰레드 프로그램이라고 한다. 하지만 멀티 쓰레드 프로그램은 하나 이상의 실행 지점을 가지고 있다. 즉, 멀티 쓰레드 프로그램은 여러 개의 쓰레드를 가지고 있으며, 각 쓰레드는 독립적으로 명령어를 불러와 실행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드는 프로세스와 매우 유사하다 다만 쓰레드들은 주소 공간을 공유하기 때문에 같은 데이터에 접근할 수 있다는 차이가 존재한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;쓰레드의 상태와 문맥 교환&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 쓰레드의 상태는 프로세스의 상태와 매우 유사하다. 각 쓰레드는 프로그램 카운터와 레지스터를 가지고 있다. 프로그램 카운터는 다음에 실행할 명령어의 주소를 저장하고, 레지스터는 연산을 위해 데이터를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 두 개의 쓰레드가 하나의 프로세서에서 실행 중이라면, 실행하고자 하는 쓰레드는 반드시 문맥 교환을 통해 현재 실행 중인 쓰레드와 교체되어야 한다. 문맥 교환 과정에서는 교체되려는 쓰레드가 사용하던 레지스터들을 저장하고 교체되어 실행되는 쓰레드가 이전에 사용하던 레지스터의 내용을 복원한다. 이는 프로세스 간의 문맥 교환과 유사한 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스의 상태를 저장하기 위해 &amp;lsquo;프로세스 제어 블록(Process Control Block, PCB)&amp;rsquo;이 사용되듯이, 쓰레드의 상태를 저장하기 위해서는 &amp;lsquo;쓰레드 제어 블록(Thread Control Block, TCB)&amp;rsquo;이 사용된다. 다만 프로세스 간 문맥 교환과 달리, 쓰레드 간 문맥 교환에서는 주소 공간을 그대로 사용한다는 점이 다르다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;쓰레드와 스택&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;쓰레드와 프로세스의 또 다른 차이점은 &amp;lsquo;스택&amp;rsquo;에 있다. 단일 쓰레드 프로세스에서는 주소 공간 내에 하나의 스택만 존재한다. 스택은 주로 주소 공간의 하부에 위치한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;반면에 멀티 쓰레드 프로세스에서는 각 쓰레드가 독립적으로 실행되며, 실행에 필요한 여러 함수를 호출할 수 있다. 따라서 멀티쓰레드 프로세스의 주소 공간에는 각 쓰레드마다 별도의 스택이 할당된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-06 오전 8.39.18.png&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcs6Da/dJMcaaXT7Kp/2KMyNIrnvK6K5igAURwVL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcs6Da/dJMcaaXT7Kp/2KMyNIrnvK6K5igAURwVL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcs6Da/dJMcaaXT7Kp/2KMyNIrnvK6K5igAURwVL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcs6Da%2FdJMcaaXT7Kp%2F2KMyNIrnvK6K5igAURwVL0%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;968&quot; height=&quot;596&quot; data-filename=&quot;스크린샷 2025-11-06 오전 8.39.18.png&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 쓰레드의 스택에 저장되는 변수, 매개변수, 리턴 값 등은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;쓰레드-로컬 저장소라고&lt;/b&gt;&lt;/span&gt; 불린다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;쓰레드-로컬 저장소&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;예제: 쓰레드 생성&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1762386183913&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;assert.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;

void *mythread(void *arg) {
    printf(&quot;%s\n&quot;, (char *) arg);
    return NULL;
}

int main(int argc, char *argv[]) {
    pthread_t p1, p2;
    int rc;

    printf(&quot;main: begin\n&quot;);

    rc = pthread_create(&amp;amp;p1, NULL, mythread, &quot;A&quot;);
    assert(rc == 0);

    rc = pthread_create(&amp;amp;p2, NULL, mythread, &quot;B&quot;);
    assert(rc == 0);

    // 쓰레드가 종료될 때까지 대기하기 위해 join 사용
    rc = pthread_join(p1, NULL); assert(rc == 0);
    rc = pthread_join(p2, NULL); assert(rc == 0);

    printf(&quot;main: end\n&quot;);
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 메인 프로그램은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mythread()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를 실행할 두 개의 쓰레드를 생성한다. 각 쓰레드는 서로 다른 인자를 전달받는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;pthread_create()&lt;/span&gt;: 새로운 쓰레드를 생성하는 함수. 첫 번째 인자는 쓰레드 식별자, 두 번째 인자는 쓰레드 속성 (여기서는 NULL 사용), 세 번째 인자는 쓰레드가 실행할 함수, 네 번째 인자는 해당 함수에 전달할 인자.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;pthread_join()&lt;/span&gt;: 특정 쓰레드가 종료되기를 기다리는 함수. 첫 번째 인자는 기다릴 쓰레드의 식별자, 두 번째 인자는 쓰레드의 반환값을 받을 포인터 (여기서는 NULL 사용).&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쓰레드가 생성되면, 스케줄러의 동작에 따라 즉시 실행되거나 대기(Ready) 상태에 머무를 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 프로그램의 가능한 실행 순서는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-06 오전 8.44.28.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W6pZk/dJMcahvXtKO/p99w9dCzcDDd9Fg9dZ6IJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W6pZk/dJMcahvXtKO/p99w9dCzcDDd9Fg9dZ6IJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W6pZk/dJMcahvXtKO/p99w9dCzcDDd9Fg9dZ6IJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW6pZk%2FdJMcahvXtKO%2Fp99w9dCzcDDd9Fg9dZ6IJK%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;726&quot; height=&quot;513&quot; data-filename=&quot;스크린샷 2025-11-06 오전 8.44.28.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;513&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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;스크린샷 2025-11-06 오전 8.44.33.png&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/erRvia/dJMcaiIoZvM/v0rtRULk40hu1yKXZBG04k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/erRvia/dJMcaiIoZvM/v0rtRULk40hu1yKXZBG04k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/erRvia/dJMcaiIoZvM/v0rtRULk40hu1yKXZBG04k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FerRvia%2FdJMcaiIoZvM%2Fv0rtRULk40hu1yKXZBG04k%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;722&quot; height=&quot;578&quot; data-filename=&quot;스크린샷 2025-11-06 오전 8.44.33.png&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-06 오전 8.44.38.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EYtRV/dJMcaiIoZvO/yTc4kRcgK1AZPVYNfC5pMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EYtRV/dJMcaiIoZvO/yTc4kRcgK1AZPVYNfC5pMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EYtRV/dJMcaiIoZvO/yTc4kRcgK1AZPVYNfC5pMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEYtRV%2FdJMcaiIoZvO%2FyTc4kRcgK1AZPVYNfC5pMK%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;754&quot; height=&quot;573&quot; data-filename=&quot;스크린샷 2025-11-06 오전 8.44.38.png&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;573&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄러가 특정 시점에 어떤 쓰레드를 실행하느냐에 따라 다양한 순서가 나올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 쓰레드 1이 쓰레드 2보다 먼저 생성되었더라도 스케줄러가 쓰레드 2를 먼저 실행한다면 &amp;ldquo;B&amp;rdquo;가 &amp;ldquo;A&amp;rdquo;보다 먼저 출력될 수 있다. 먼저 생성되었다고 해서 반드시 먼저 실행된다는 보장은 없다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쓰레드의 생성은 함수 호출과 유사해 보이지만, 함수 호출에서는 함수가 실행을 마치면 호출자(caller)에게 리턴하는 반면, 쓰레드 생성에서는 새로운 쓰레드가 생성되어 호출자와는 독립적으로 실행된다. 쓰레드는 생성 함수가 리턴되기 전이나 후에 실행될 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 예제에서 볼 수 있듯이, 쓰레드는 프로그램의 실행 흐름을 복잡하게 만든다. 어떤 쓰레드가 언제 실행될지 정확히 예측하기 어려워지기 때문이다. 이러한 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병행성(concurrency) 문제&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;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;훨씬 더 어려운 이유: 데이터의 공유&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞의 간단한 쓰레드 예제를 통해 쓰레드의 생성 방법과 실행 순서가 스케줄러의 동작에 따라 달라질 수 있음을 살펴보았다. 하지만 예제에서는 쓰레드 간의 상호작용, 특히 공유 데이터에 대한 접근은 다루지 않았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 코드는 전역 공유 변수를 갱신하는 두 개의 쓰레드를 사용한 간단한 예제이다.&lt;/p&gt;
&lt;pre id=&quot;code_1762386481264&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &quot;mythreads.h&quot;

static volatile int counter = 0;

// mythread()
// 인자로 전달된 문자열을 출력하고
// 10000000번 반복하며 counter에 1을 더하는 함수
// 끝나면 다시 인자 문자열을 출력
void *mythread(void *arg) {
    printf(&quot;%s: begin\n&quot;, (char *) arg);

    for (int i = 0; i &amp;lt; 1e7; i++) {
        counter = counter + 1;
    }

    printf(&quot;%s: done\n&quot;, (char *) arg);
    return NULL;
}

// main()
// 두 개의 쓰레드를 생성하고 (pthread_create)
// 기다린다 (pthread_join)
int main(int argc, char *argv[]) {
    pthread_t p1, p2;

    printf(&quot;main: begin (counter = %d)\n&quot;, counter);

    Pthread_create(&amp;amp;p1, NULL, mythread, &quot;A&quot;);
    Pthread_create(&amp;amp;p2, NULL, mythread, &quot;B&quot;);

    // 쓰레드가 종료될 때까지 기다리기 위해 join 사용
    Pthread_join(p1, NULL);
    Pthread_join(p2, NULL);

    printf(&quot;main: done with both (counter = %d)\n&quot;, counter);
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 주목할 점은 다음과 같다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;에러 처리를 간단히 하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;Pthread_create()&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;Pthread_join()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;래퍼 함수를 사용했다. 이들 함수는 실패 시 에러 메시지를 출력하고 프로그램을 종료한다.&lt;/li&gt;
&lt;li&gt;작업자 쓰레드로 두 개의 독립된 함수를 만드는 대신, 하나의 함수(&lt;span&gt;mythread()&lt;/span&gt;)를 사용하고 인자를 통해 출력할 문자열을 전달했다.&lt;/li&gt;
&lt;li&gt;각 작업자 쓰레드는 공유 변수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;에 1,000만 번(1e7) 1을 더한다. 따라서 최종 결과는 20,000,000이 되어야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 프로그램을 컴파일하고 실행해 보겠다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell2&quot; class=&quot;yaml&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;prompt&amp;gt; gcc -o main main.c -Wall -pthread
prompt&amp;gt; ./main
main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 20000000)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이 코드를 실행하면 단일 프로세서에서도 항상 기대한 결과가 나오지는 않는다. 때로는 아래와 같은 결과가 나올 수 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell3&quot; class=&quot;yaml&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;prompt&amp;gt; ./main
main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 19345221)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이것이 정말 잘못된 것인지 확인하기 위해 프로그램을 다시 실행해 보겠다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell4&quot; class=&quot;yaml&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;prompt&amp;gt; ./main
main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 19221041)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;팁: 도구를 제대로 알고 사용하자&lt;br /&gt;&lt;br /&gt;프로그램을 작성하고 디버깅하며 컴퓨터 시스템을 이해하는 데 도움이 되는 새로운 도구들을 항상 배워야 한다. 역어셈블러(disassembler)는 유용한 도구 중 하나다. 실행 파일에 역어셈블러를 실행하면 해당 프로그램이 어떤 어셈블리 명령어들로 구성되었는지 알 수 있다.&lt;br /&gt;&lt;br /&gt;예를 들어, 예제에서 사용한 카운터를 갱신하는 저수준 코드를 이해하고 싶다면 Linux의 objdump 명령어를 사용하여 어셈블리 코드를 볼 수 있다.&lt;br /&gt;&lt;br /&gt;prompt&amp;gt; objdump -d main &lt;br /&gt;&lt;br /&gt;이 명령어를 수행하면 프로그램의 모든 명령어가 출력된다. 컴파일 시 -g 옵션을 사용했다면 프로그램의 심벌 정보를 포함하는 식별자와 함께 정리되어 나열된다.&lt;br /&gt;&lt;br /&gt;objdump는 반드시 사용법을 익혀야 할 많은 도구 중 하나다. gdb와 같은 디버거, valgrind나 purify와 같은 메모리 프로파일러, 그리고 컴파일러 역시 시간을 들여 사용법을 익혀야 할 도구들이다. 도구를 잘 사용할수록 더 좋은 시스템을 개발할 수 있다&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;각 실행 결과가 잘못되었을 뿐만 아니라 실행마다 결과가 다르다 이는 매우 큰 의문을 불러일으킨다. 왜 이런 일이 일어나는 것일까?&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;제어 없는 스케줄링&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 현상이 발생하는 이유를 이해하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;갱신을 위해 컴파일러가 생성한 코드의 실행 순서를 파악해야 한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;에 단순히 1을 더하려고 한다. x86에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;를 증가시키는 코드의 순서는 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell6&quot; class=&quot;x86asm&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;mov 0x8049a1c, %eax
add $0x1, %eax
mov %eax, 0x8049a1c
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 예제에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수의 주소는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;0x8049a1c&lt;/span&gt;라고 가정한다. 이 세 줄의 명령어에서는 먼저 x86의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어가 지정된 메모리 주소의 값을 읽어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;레지스터에 저장한다. 그런 다음&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;레지스터의 값에 1(&lt;span&gt;0x1&lt;/span&gt;)을 더한다. 마지막으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;에 저장된 값을 메모리의 원래 주소에 다시 저장한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 개의 쓰레드 중 쓰레드 1이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;를 증가시키는 코드 영역에 진입하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;의 값을 1 증가시키려는 상황을 가정해 보겠다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;의 값이 50이라면 50을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;레지스터에 저장한다. 쓰레드 1에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;는 50이 된다. 그 후 레지스터의 값에 1을 더해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;는 51이 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 상황이 안 좋아진다. 타이머 인터럽트가 발생하여 운영체제가 실행 중인 쓰레드의 PC 값과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;를 포함한 레지스터들의 현재 상태를 쓰레드의 TCB(Thread Control Block)에 저장한다. 그리고 상황은 더 악화된다. 쓰레드 2가 선택되고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을 증가시키는 동일한 코드 영역에 진입한다. 첫 번째 명령어를 실행하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을 읽어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;에 저장한다. 각 쓰레드는 개별적인 쓰레드 전용 레지스터를 가지고 있다. 사용 중이던 레지스터들을 저장하고 복구하는 문맥 교환 코드에 의해 이 레지스터들은 가상화된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값은 아직 50이므로 쓰레드 2의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값은 50이다. 쓰레드 2가 다음 두 문장을 실행하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을 1 증가시켜&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;는 51이 되고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;(주소&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;0x8049a1c&lt;/span&gt;)에 저장한다. 전역 변수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;는 이제 51이 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 또 한 번의 문맥 교환이 발생하면 쓰레드 1이 다시 실행된다. 이전 상황을 떠올려 보면 쓰레드 1은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;동작을 실행했고 이제 마지막&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어를 수행하려는 중이다. 그리고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;eax&lt;/span&gt;는 51이다. 따라서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어가 실행되면 레지스터의 값을 메모리에 저장하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;의 값은 다시 51이 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;무슨 일이 일어난 것일까? 간단히 말하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;의 값을 증가시키는 코드가 두 번 수행되었지만, 50에서 시작한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;의 값은 1만 증가하여 51이 되었다. &amp;ldquo;정확하게&amp;rdquo; 동작하도록 작성된 프로그램이라면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;의 값은 52가 되어야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 더 잘 이해하기 위해 실행 흐름을 구체적으로 살펴보겠다. 이 예제에서는 다음과 같이 코드가 메모리 주소 100에 저장되어 있다고 가정한다(RISC 유사 명령어 집합에 익숙하다면 x86은 명령어 길이가 가변적이라는 점과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mov&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어는 5바이트,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어는 3바이트를 사용한다는 점을 참고해야 한다).&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell7&quot; class=&quot;basic&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;100 mov 0x8049a1c, %eax
105 add $0x1, %eax
108 mov %eax, 0x8049a1c
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 가정 하에 일어나는 동작을 아래 표에 나타내었다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;counter&lt;/span&gt;의 초기값은 50이라고 가정하고 예제의 내용을 상기하며 흐름을 따라가 보겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-06 오전 9.00.40.png&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwE0Xc/dJMcaboYrCi/02D0yBsRswt7LeeGj86KR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwE0Xc/dJMcaboYrCi/02D0yBsRswt7LeeGj86KR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwE0Xc/dJMcaboYrCi/02D0yBsRswt7LeeGj86KR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwE0Xc%2FdJMcaboYrCi%2F02D0yBsRswt7LeeGj86KR1%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;1023&quot; height=&quot;524&quot; data-filename=&quot;스크린샷 2025-11-06 오전 9.00.40.png&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시에서처럼 명령어의 실행 순서에 따라 결과가 달라지는 상황을 &amp;lsquo;경쟁 조건(race condition)&amp;rsquo;이라고 한다. 문맥 교환이 부적절한 시점에 실행되면 잘못된 결과를 얻게 된다. 실제로 경쟁 조건에 처한 경우 실행할 때마다 다른 결과를 얻는다. 컴퓨터의 일반적인 동작에서 발생하는 결정적 결과와 달리, 결과를 예측할 수 없거나 실행할 때마다 결과가 다른 경우를 &amp;lsquo;비결정적(indeterminate)&amp;rsquo;인 결과라고 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여러 쓰레드가 동일한 코드를 실행할 때 경쟁 조건이 발생하기 때문에 이러한 코드 부분을 &amp;lsquo;임계 영역(critical section)&amp;rsquo;이라고 한다. 공유 변수(또는 더 일반적으로는 공유 자원)에 접근하고 하나 이상의 쓰레드에서 동시에 실행되면 안 되는 코드를 임계 영역이라고 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 코드에서 필요한 것은 &amp;lsquo;상호 배제(mutual exclusion)&amp;rsquo;이다. 이 속성은 하나의 쓰레드가 임계 영역 내의 코드를 실행 중일 때는 다른 쓰레드가 실행할 수 없도록 보장해 준다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;원자성에 대한 바람&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;임계 영역 문제를 해결하는 방법 중 하나는 강력한 단일 명령어로 의도한 동작을 수행하여 인터럽트 발생 가능성을 원천적으로 차단하는 것이다. 예를 들어, 다음과 같은 강력한 명령어가 있다고 가정해 보겠다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell8&quot; class=&quot;dockerfile&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;memory-add 0x8049a1c, $0x1
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 명령어는 메모리의 특정 위치에 어떤 값을 더하는 명령어다. 하드웨어가 이 명령어의 원자적 실행을 보장한다고 가정한다. 이 명령어가 실행되면 원하는 대로 값이 갱신될 것이다. 하드웨어가 원자성을 보장하기 때문에 명령어 수행 도중에 인터럽트가 발생하지 않는다. 인터럽트가 발생하더라도 명령어가 실행되지 않았거나 실행이 완료된 후라는 것을 의미하며, 중간 상태는 존재하지 않는다. 멋진 하드웨어, 아닌가?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문맥 상 &amp;lsquo;원자적&amp;rsquo;이라는 말은 &amp;ldquo;하나의 단위&amp;rdquo;를 뜻하며, 때로는 &amp;ldquo;전부 아니면 전무&amp;rdquo;로 이해될 수도 있다. 다음 세 개의 명령어가 원자적으로 실행되기를 원한다고 가정해 보겠다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell9&quot; class=&quot;x86asm&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;mov 0x8049a1c, %eax
add $0x1, %eax
mov %eax, 0x8049a1c&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여담: 주요 병행성 관련 용어&lt;br /&gt;Critical Section, Race Condition, Indeterminate, Mutual Exclusion&lt;br /&gt;사용된 네 개의 용어는 병행 코드의 핵심 개념이므로 명시적으로 정의하고 넘어가겠다. 더 자세한 정보를 원한다면 Dijkstra의 초기 연구 [Dij65; Dij68]를 살펴보시기 바란다.&lt;br /&gt;&lt;br /&gt;- 임계 영역(critical section)은 일반적으로 변수나 자료 구조와 같은 공유 자원에 접근하는 코드의 일부분을 말한다.&lt;br /&gt;- 경쟁 조건(race condition)은 여러 쓰레드가 거의 동시에 임계 영역을 실행하려고 할 때 발생하며, 공유 자료 구조를 모두가 갱신하려고 시도한다면 의도하지 않은 놀라운 결과를 만들어낸다.&lt;br /&gt;- 비결정적(indeterminate) 프로그램은 하나 이상의 경쟁 조건을 포함하여 실행 결과가 각 쓰레드의 실행 시점에 의존하기 때문에 프로그램의 결과가 실행할 때마다 달라진다. 결과는 컴퓨터 시스템에서 일반적으로 기대하는 것과 달리 결정적이지 않다.&lt;br /&gt;- 이러한 문제를 피하려면 쓰레드는 상호 배제(mutual exclusion)라는 기법을 사용하여 하나의 쓰레드만 임계 영역에 진입할 수 있도록 보장해야 한다. 그 결과 경쟁을 피할 수 있고 프로그램 실행 결과를 결정론적으로 얻을 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 언급했듯이, 위의 명령어들을 하나의 명령어로 대체할 수 있다면 해당 명령어를 사용하면 된다. 하지만 일반적인 상황에서는 그러한 명령어가 없다고 봐야 한다. 병행성을 가지는 B-tree를 만들면서 값을 갱신한다고 할 때, B-tree를 원자적으로 갱신하는 어셈블리 명령어를 원할까? 글쎄, 아마도 제정신이라면 그렇지 않을 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하드웨어적으로는 동기화 함수(synchronization primitives) 구현에 필요한 기본적인 명령어 몇 개만 필요하다. 결과적으로 병행 실행이라는 어려운 상황에서 하드웨어 동기화 명령어와 운영체제의 지원을 통해 한 번에 하나의 쓰레드만 임계 영역에서 실행하도록 구성된 &amp;ldquo;제대로 잘 작동하는&amp;rdquo; 멀티 쓰레드 프로그램을 작성할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;또 다른 문제: 상대 기다리기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 병행성 문제는 주로 공유 변수에 접근할 때 발생하는 쓰레드 간의 상호 작용 문제로 정의되었다. 하지만 실제 상황에서는 한 쓰레드가 다른 쓰레드의 작업이 완료될 때까지 기다려야 하는 경우도 자주 발생한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 프로세스가 디스크에 입출력(I/O) 요청을 보내고 응답이 올 때까지 대기 상태로 들어가는 상황이 있다. 이 경우, 프로세스는 I/O 작업이 끝날 때까지 잠들어 있다가(sleep), I/O가 완료되면 다시 깨어나(wake up) 작업을 계속 진행하게 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잠자기(sleep): 프로세스나 쓰레드가 특정 이벤트(예: I/O 완료)가 발생할 때까지 대기 상태로 들어가는 것을 의미한다.&lt;/li&gt;
&lt;li&gt;깨우기(wake up): 잠들어 있던 프로세스나 쓰레드가 특정 이벤트가 발생하면 다시 실행 가능한 상태로 전환되는 것을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 내용에서는 원자적 연산을 지원하기 위한 동기화 함수들의 구현 방법과 멀티 쓰레드 프로그램에서 흔히 사용되는 잠자기/깨우기 동작을 지원하는 기술에 대해 다룰 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/204</guid>
      <comments>https://gowoong.tistory.com/204#entry204comment</comments>
      <pubDate>Thu, 6 Nov 2025 09:08:42 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 9주차 - 메모리 가상화 4</title>
      <link>https://gowoong.tistory.com/203</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 전 시간에는 세그멘테이션에 대해 알아봤다. 이 방법은 공간 관리 문제를 해결하는 방법 중 하나이지만 공간 자체가 단편화될 수 있고, 할당이 점점 더 어려워진다는 문제를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번에 배울 것은 공간을 동일한 크기의 조각으로 분할하는 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;페이징&lt;/span&gt;&lt;/b&gt;에 대해 알아보겠다. 이 방식에서는 프로세스의 주소 공간을 고정 크기의 단위인 페이지로 나눈고 이에 상응하여 물리 메모리도 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페이지 프레임&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;간단한 예제 및 개요&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;191&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF8XOL/dJMb9d8hy3p/UzjNBHSJEECH9hUO2cIb8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF8XOL/dJMb9d8hy3p/UzjNBHSJEECH9hUO2cIb8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF8XOL/dJMb9d8hy3p/UzjNBHSJEECH9hUO2cIb8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF8XOL%2FdJMb9d8hy3p%2FUzjNBHSJEECH9hUO2cIb8K%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;366&quot; height=&quot;191&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;191&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림은 총 64바이트이며 4개의 16바이트 페이지로 구성된 주소 공간의 예를 보여준다. 물리 메모리는 고정 크기의 슬롯들로 구성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tezIC/dJMb9iuY3T6/r1oNtpiB2Mb3ESPjQj2RZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tezIC/dJMb9iuY3T6/r1oNtpiB2Mb3ESPjQj2RZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tezIC/dJMb9iuY3T6/r1oNtpiB2Mb3ESPjQj2RZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtezIC%2FdJMb9iuY3T6%2Fr1oNtpiB2Mb3ESPjQj2RZ1%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;454&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시는 8개의 페이지 프레임으로 이루어진 128바이트의 작은 물리 메모리를 가정한다. 페이징의 가장 큰 장점은 유연성이다. 페이징을 사용하면 프로세스의 주소 공간 사용 방식과 상관없이 효율적으로 주소 공간 개념을 지원할 수 있다. 예를 들어, 힙과 스택이 어느 방향으로 커지는지, 어떻게 사용되는지 고민할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 장점은 단순함이다. 운영체제가 64바이트의 가상 주소 공간을 8페이지 물리 메모리에 배치하려면, 단순히 비어있는 4개의 페이지만 찾으면 된다.(이건 위에 16바이트를 1페이지로 가정했기 때문) 이를 위해 운영체제는 빈 페이지 리스트를 유지하고, 리스트의 첫 네 개 페이지를 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 주소 공간의 각 가상 페이지에 대한 물리 메모리 위치를 기록하기 위해 프로세스마다 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페이지 테이블&lt;/b&gt;&lt;/span&gt;이 라는 자료구조를 유지한다. 페이지 테이블의 역할은 가상 주소 공간의 각 페이지에 대한 주소 변환 정보(물리 페이지 프레임의 위치)를 저장하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;예시에서는 가상 페이지(VP) 0은 물리 페이지 프레임(PF) 3에, VP 1은 PF 7에, VP 2는 PF 5에, VP 3은 PF 2에 매핑되어 있다. 페이지 테이블은 프로세스마다 존재한다는 점을 기억해야 한다. 다른 프로세스를 실행한다면, 그 프로세스를 위한 별도의 페이지 테이블이 필요할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 변환 과정을 살펴보겠다. 64바이트의 작은 주소 공간을 가진 프로세스가 메모리 접근을 수행한다고 가정해 본다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;code_1761529726062&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;movl &amp;lt;virtual address&amp;gt;, %eax&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령어는 가상 주소의 데이터를 eax 레지스터에 로드한다. 프로세스가 생성한 가상 주소를 변환하기 위해, 먼저 가상 주소를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;가상 페이지 번호&lt;/b&gt;&lt;/span&gt;와 페이지 내의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;오프셋&lt;/b&gt;&lt;/span&gt;이라는 두 개의 요소로 분할한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 2 비트가 페이지를 선택하는 역할을 하고 나머지 비트는 페이지 내에서 원하는 바이트의 위치를 나타내기에 6비트가 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1761530295642&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;movl 21, %eax&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;21을 이진수로 변환하면 010101이므로, VPN은 1, Offset은 5다. 앞서 예시에서 VPN 1은 PF 7과 매핑되었으므로, 주소 변환 과정은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nERe2/dJMb9d8hzdm/zCZuzPP6HeXFslXNz7fak0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nERe2/dJMb9d8hzdm/zCZuzPP6HeXFslXNz7fak0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nERe2/dJMb9d8hzdm/zCZuzPP6HeXFslXNz7fak0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnERe2%2FdJMb9d8hzdm%2FzCZuzPP6HeXFslXNz7fak0%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;400&quot; height=&quot;320&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;Offset은 동일하게 적용되고, VPN만 PFN으로 변환되어 최종적으로 1110101이라는 물리 주소를 얻게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페이지 테이블 저장 위치?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;페이지 테이블은 매우 커질 수 있다. 예를 들어, 4KB 크기의 페이지를 가지는 전형적인 32비트 주소 공간을 생각해 보겠다. 이 가상 주소는 20비트 VPN과 12비트 Offset으로 구성된다(2^12 = 4KB, 나머지는 VPN).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;만약 100개의 프로세스가 실행 중이라면 400MB의 메모리가 필요하다는 것을 의미한다. 현재와 같이 GB 단위의 메모리를 갖고 있는 상황에서도 주소 변환을 위해 이렇게 많은 메모리가 필요하다는 것은 다소 비정상적이다.&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;571&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qFuqa/dJMb9PTWQRO/XF6CWcrccMjqs1xoApQ99k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qFuqa/dJMb9PTWQRO/XF6CWcrccMjqs1xoApQ99k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qFuqa/dJMb9PTWQRO/XF6CWcrccMjqs1xoApQ99k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqFuqa%2FdJMb9PTWQRO%2FXF6CWcrccMjqs1xoApQ99k%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;571&quot; height=&quot;464&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;위 그림에서 PF0에 페이지 테이블이 존재하는 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페이지 테이블에는 실제로 무엇이 있는가?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;페이지 테이블은 가상 주소(페이지 번호)를 물리 주소(물리 페이지 프레임 번호)로 매핑하는 데 사용하는 자료구조이다. 가장 간단한 형태는 선형 페이지 테이블로, 단순한 배열 형태다. 원하는 물리 프레임 번호(PFN)를 찾기 위해 가상 페이지 번호(VPN)로 배열의 항목에 접근하고, 그 항목의 페이지 테이블 항목(PTE)을 검색한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 PTE에는 다음과 같은 여러 비트들이 포함된다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Valid Bit: 특정 변환의 유효 여부를 나타내기 위해 사용. 예를 들어, 프로그램이 실행을 시작할 때 코드와 힙이 주소 공간의 한쪽에 있고 반대쪽은 스택이 차지하고 있을 것이다. 그 사이의 모든 미사용 공간은 invalid로 표시되며, 프로세스가 그런 메모리에 접근하려고 하면 트랩(Trap)이 발생한다.&lt;/li&gt;
&lt;li&gt;Protection Bit: 페이지가 읽기 가능한지, 쓰기 가능한지, 실행 가능한지를 표시하기 위해 사용. 허용되지 않은 방식으로 접근할 경우 트랩이 발생한다.&lt;/li&gt;
&lt;li&gt;Present Bit: 해당 페이지가 물리 메모리에 존재하는지, 아니면 디스크로 스왑 아웃되었는지를 나타내는 데 사용된다.&lt;/li&gt;
&lt;li&gt;Reference Bit: 페이지가 접근되었는지를 추적하는 데 사용. 이는 어떤 페이지가 인기 있는지 결정하여 메모리에 유지되어야 할 페이지를 판단하는 데 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페이징: 너무 느림&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;페이징을 사용할 때 페이지 테이블의 크기가 메모리 상에서 매우 크게 증가할 가능성이 있다. 페이징의 문제 중 하나는 모든 메모리 참조마다 페이지 테이블에서 주소 변환 정보를 읽어와야 한다는 점이다. 이로 인해 메모리 접근이 최소 한 번 더 발생하게 되는데, 메모리 접근은 비용이 높은 연산이므로 프로그램의 실행 속도가 크게 저하될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;이 문제를 해결하기 위해, 하드웨어의 도움을 받을 수 있다. 바로 &lt;b&gt;Translation Lookaside Buffer (TLB)&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페이징: 더 빠른 변환 (TLB)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TLB는 CPU의 Memory Management Unit (MMU) 내에 위치한 작고 빠른 하드웨어 캐시다. TLB의 목적은 최근에 사용된 가상 주소와 물리 주소 간의 매핑 정보를 저장하여, 주소 변환 속도를 향상시키는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로세스가 가상 주소를 사용하여 메모리에 접근하면, 하드웨어는 먼저 TLB에서 해당 가상 주소의 변환 정보를 찾아본다. 만약 TLB에 정보가 있다면 (TLB Hit), 곧바로 물리 주소를 얻어 메모리에 접근할 수 있다. 이 경우, 페이지 테이블을 읽는 추가 작업 없이 주소 변환을 완료할 수 있으므로 매우 빠르다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면에, 원하는 정보가 TLB에 없는 경우 (TLB Miss)에는 기존대로 페이지 테이블을 참조하여 주소 변환을 진행해야 한다. 이 과정에서 얻은 변환 정보는 향후 재사용을 위해 TLB에 저장된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;적절한 크기의 TLB를 사용하면, 대부분의 메모리 접근이 TLB Hit로 처리되므로 전체적인 주소 변환 속도가 크게 향상된다. 이렇게 TLB를 사용함으로써 페이징의 성능 문제를 완화하고, 가상 메모리를 실질적으로 사용 가능하게 만들 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 용어 정리&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;페이징 (Paging)&lt;/b&gt;: 가상 메모리를 일정한 크기의 페이지 단위로 나누어 관리하는 기법.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이지 (Page)&lt;/b&gt;: 가상 메모리를 일정한 크기로 나눈 블록. 주소 변환의 기본 단위가 됨.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이지 테이블 (Page Table)&lt;/b&gt;: 각 페이지의 가상 주소와 실제 물리 주소 간의 매핑 정보를 저장하는 자료구조.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주소 변환 (Address Translation)&lt;/b&gt;: 가상 주소를 물리 주소로 변환하는 과정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Translation Lookaside Buffer (TLB)&lt;/b&gt;: 주소 변환을 가속하기 위한 하드웨어 캐시. 최근 사용된 가상-물리 주소 매핑 정보를 저장함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TLB Hit&lt;/b&gt;: 원하는 주소 변환 정보가 TLB에 존재하는 경우.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TLB Miss&lt;/b&gt;: 원하는 주소 변환 정보가 TLB에 없는 경우. 페이지 테이블을 참조하여 변환을 진행해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;TLB의 기본 알고리즘&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TLB 검색 과정 가상 주소가 제공되면, 해당 주소의 가상 페이지 번호(VPN)를 추출하고, TLB에서 이 VPN에 해당하는 엔트리를 병렬적으로 검색한다. 일치하는 엔트리가 있는 경우, 그 엔트리에 포함된 물리 페이지 번호(PPN)로 물리 주소를 계산한다.&lt;/li&gt;
&lt;li&gt;TLB 히트 유효한 엔트리가 존재하면, 즉시 물리 주소를 생성하고 메모리에 액세스한다. 이 과정을 TLB 히트라고 한다.&lt;/li&gt;
&lt;li&gt;TLB 미스 일치하는 엔트리가 없을 경우, TLB 미스가 발생하며, 페이지 테이블을 참조하여 가상 주소를 물리 주소로 변환한다. 변환된 주소는 TLB에 추가되어 다음 접근 시 빠르게 찾을 수 있도록 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;예제: 배열 접근&lt;/h3&gt;
&lt;pre id=&quot;code_1761531735609&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main() {
    int array[100];
    array[50] = 10;  // 배열의 특정 위치에 데이터 쓰기
    return 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 array[50]에 값을 할당하려면 다음과 같은 과정이 필요하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가상 주소 0x1400 (50 * 4) 생성&lt;/li&gt;
&lt;li&gt;TLB 검색: 0x1400에 대한 엔트리가 존재하는지 확인
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TLB 히트: 엔트리가 존재하면 엔트리에 포함된 물리 페이지 번호를 사용하여 물리 주소를 생성하고 메모리에 액세스&lt;/li&gt;
&lt;li&gt;TLB 미스: 엔트리가 존재하지 않으면 페이지 테이블 워크를 통해 변환 정보를 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;페이지 테이블 워크: 페이지 테이블에서 가상 페이지 번호 0x3C에 대한 엔트리를 찾아 물리 페이지 번호를 얻음&lt;/li&gt;
&lt;li&gt;TLB에 엔트리 추가: 0x1400 (VPN)과 0x0A00 (PPN)으로 구성된 엔트리를 TLB에 추가&lt;/li&gt;
&lt;li&gt;물리 주소 생성: 물리 페이지 번호 0x0A00과 오프셋 0x00을 사용하여 물리 주소 0xA000 생성&lt;/li&gt;
&lt;li&gt;메모리 액세스: 0xA000 주소에 값 10 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;TLB 미스는 누가 처리할까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TLB 미스는 Translation Lookaside Buffer(번역 조회 버퍼)에서 원하는 주소 변환 정보를 찾지 못할 때 발생하는 상황이다. TLB 미스를 처리하는 방법은 두 가지가 있다: 하드웨어 관리와 소프트웨어(운영 체제) 관리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어 관리 방식은 주로 복잡한 명령어 집합인 CISC(complex instruction set computers) 구조에서 사용된다. 하드웨어가 TLB 미스를 처리하려면 페이지 테이블에 대한 정보를 갖고 있어야 한다. TLB 미스가 발생하면 하드웨어가 페이지 테이블에서 필요한 정보를 추출하고 TLB를 갱신한 후 명령어를 재실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 소프트웨어 관리 방식은 최근에 등장한 RISC(reduced instruction set computing) 구조에서 주로 사용된다. TLB 미스가 발생하면 하드웨어가 예외 시그널을 발생시키고, 이를 받은 운영 체제가 커널 모드로 전환하여 트랩 핸들러를 실행한다. 트랩 핸들러는 페이지 테이블을 검색하여 변환 정보를 찾고 TLB를 갱신한 후에 명령어를 재실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지 방법의 중요한 차이점은 하드웨어 관리 방식은 하드웨어가 직접 TLB 미스를 처리하는 데 반해, 소프트웨어 관리 방식은 운영 체제의 코드가 TLB 미스를 처리한다는 것이다. 소프트웨어 관리 방식은 유연성이 높고 하드웨어 변경에 영향을 받지 않지만, 성능 측면에서는 하드웨어 관리 방식에 비해 부가적인 오버헤드가 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TLB 미스가 발생하면 운영 체제의 페이지 폴트 핸들러가 실행되어 페이지 테이블을 참조하여 필요한 가상-물리 주소 변환 정보를 찾는다. 이 정보는 TLB에 로드되고, 이후 메모리 액세스는 TLB 히트로 처리된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;TLB의 구성: 무엇이 있나?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;가상 페이지 번호(VPN)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;가상 주소에서 추출된 페이지 번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;물리 페이지 프레임 번호(PPFN)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;물리 메모리에서 해당 페이지를 저장하는 프레임 번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;엑세스 권한&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;해당 페이지에 대한 액세스 권한(읽기, 쓰기, 실행)을 나타낸다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;유효 비트&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;해당 엔트리가 유효한지 여부를 나타낸다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;ASID (Address Space Identifier)&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;프로세스 간 TLB 엔트리 충돌을 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;TLB의 문제: 문맥 교환&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 프로세스는 다른 가상 주소 공간을 사용하기 때문에 문맥 교환이 발생하면 현재 프로세스의 TLB 엔트리가 유효하지 않게 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 다음과 같은 방법을 사용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TLB를 비우기: 문맥 교환이 발생할 때마다 TLB를 비운다.&lt;/li&gt;
&lt;li&gt;ASID(Address Space Identifier) 사용: TLB 엔트리에 ASID를 추가하여 프로세스별 TLB 엔트리를 구분한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;이슈: 교체 정책&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;TLB Miss가 발생하면 TLB에 새 엔트리를 추가해야 한다. 하지만 TLB는 제한된 크기의 캐시이기 때문에 어떤 엔트리를 교체해야 할지 결정하는 정책이 필요하다.&lt;br /&gt;TLB의 교체 정책은 제한된 공간과 성능 요구 사항을 충족하기 위해 매우 중요하다. 각 정책은 특정 시나리오에서의 성능 최적화를 목표로 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 교체 정책으로는 다음과 같은 것들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LRU (Least Recently Used): 최근에 사용되지 않은 항목을 먼저 교체한다.&lt;/li&gt;
&lt;li&gt;FIFO (First In, First Out): 먼저 추가된 항목을 먼저 교체한다.&lt;/li&gt;
&lt;li&gt;Random Replacement: 무작위 선택을 통한 교체&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;물리 메모리 크기의 극복: 메커니즘&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스왑 공간&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개념&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;: 물리 메모리가 부족하면 사용하지 않는 페이지를 하드 디스크의 특정 영역(스왑 공간)에 백업하고, 필요하면 다시 메모리로 불러오는 기술&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&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;/ul&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;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baoxt0/dJMb9OAJDzO/r9QkBOLJB2JMKyHE2WcU2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baoxt0/dJMb9OAJDzO/r9QkBOLJB2JMKyHE2WcU2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baoxt0/dJMb9OAJDzO/r9QkBOLJB2JMKyHE2WcU2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbaoxt0%2FdJMb9OAJDzO%2Fr9QkBOLJB2JMKyHE2WcU2k%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;1280&quot; height=&quot;438&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스왑 공간 사용 방식 - 가상 메모리 공간이 부족하면 사용하지 않는 페이지를 스왑 공간으로 백업하고, 필요하면 다시 메모리로 불러온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&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;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Present Bit&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개념&lt;/b&gt;: 페이지 테이블에 존재하는 각 엔트리에 설정되는 플래그 비트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기능&lt;/b&gt;: 해당 페이지가 현재 물리 메모리에 존재하는지 여부를 나타냄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;활용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Present bit가 1이면 페이지가 메모리에 존재하므로 바로 접근 가능&lt;/li&gt;
&lt;li&gt;Present bit가 0이면 페이지가 스왑 공간에 존재하므로 페이지 폴트 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;페이지 폴트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개념&lt;/b&gt;: 프로세스가 메모리에 존재하지 않는 페이지에 접근하려는 경우 발생하는 예외 상황&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;처리 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;운영체제는 해당 페이지를 스왑 공간에서 메모리로 로드&lt;/li&gt;
&lt;li&gt;페이지 테이블을 업데이트하여 Present bit를 1로 설정&lt;/li&gt;
&lt;li&gt;프로세스가 다시 해당 페이지에 접근하도록 허용&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;영향&lt;/b&gt;: 페이지 폴트는 메모리 접근 지연을 야기하여 성능 저하를 초래한다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/203</guid>
      <comments>https://gowoong.tistory.com/203#entry203comment</comments>
      <pubDate>Mon, 27 Oct 2025 11:32:07 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 7주차 - 메모리 가상화 3 - 숙제</title>
      <link>https://gowoong.tistory.com/202</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.먼저&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-H&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;BEST&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;플래그로 실행하여 몇 개의 무작위 할당과 해제를 생성하세요. alloc()/free()가 무엇을 반환할지 예측할 수 있나요? 각 요청 후에 프리 리스트의 상태를 추측할 수 있나요? 시간이 지남에 따라 프리 리스트에서 무엇을 알 수 있나요?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-21 오전 9.39.08.png&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF0mVA/dJMb81UirkE/O9h1jYmCDeKwnlkZ5ywGj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF0mVA/dJMb81UirkE/O9h1jYmCDeKwnlkZ5ywGj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF0mVA/dJMb81UirkE/O9h1jYmCDeKwnlkZ5ywGj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF0mVA%2FdJMb81UirkE%2FO9h1jYmCDeKwnlkZ5ywGj0%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;467&quot; height=&quot;686&quot; data-filename=&quot;스크린샷 2025-10-21 오전 9.39.08.png&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A : 풀이 방법&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1761007754620&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[1000..............................................................1099]
Free List: [ addr:1000 sz:100 ]Free(ptr[0]) : 병합 없음, 반환 값 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;508&quot; data-start=&quot;495&quot; data-ke-size=&quot;size18&quot;&gt;1) Alloc(3)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;631&quot; data-start=&quot;510&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;578&quot; data-start=&quot;510&quot;&gt;프리 리스트에서 &amp;ldquo;3B를 담을 수 있는 가장 작은&amp;rdquo; 블록 찾기&lt;br /&gt;&amp;rarr; 후보는 [1000,100] 하나뿐 &amp;rarr; 선택&lt;/li&gt;
&lt;li data-end=&quot;631&quot; data-start=&quot;580&quot;&gt;선택한 블록을 &lt;b&gt;앞에서 3B 잘라서&lt;/b&gt; 주고, &lt;b&gt;나머지(97B)&lt;/b&gt; 를 프리로 남김&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;677&quot; data-start=&quot;633&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;653&quot; data-start=&quot;633&quot;&gt;반환 포인터: &lt;b&gt;1000&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;677&quot; data-start=&quot;654&quot;&gt;새 프리 리스트: [1003,97]&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1761007833942&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[1000 1001 1002][1003.................................1099]
  ^^^ alloc 3B      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ free 97B
Free List: [ addr:1003 sz:97 ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;873&quot; data-start=&quot;850&quot; data-ke-size=&quot;size18&quot;&gt;2) Free(1000, size=3)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;932&quot; data-start=&quot;875&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;923&quot; data-start=&quot;875&quot;&gt;병합이 꺼져 있어서, 그냥 &amp;ldquo;주소 순(ADDRSORT)&amp;rdquo;으로 &lt;b&gt;끼워 넣기만&lt;/b&gt; 함&lt;/li&gt;
&lt;li data-end=&quot;932&quot; data-start=&quot;924&quot;&gt;반환값: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;946&quot; data-start=&quot;934&quot; data-ke-size=&quot;size16&quot;&gt;프리 리스트(주소순):&lt;/p&gt;
&lt;pre id=&quot;code_1761007862909&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[1000,3] [1003,97]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림:&lt;/p&gt;
&lt;pre id=&quot;code_1761007872260&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[1000 1001 1002][1003.................................1099]
  ^^^ free 3B        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ free 97B
Free List: [1000,3] [1003,97]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ptr[0]&amp;nbsp;=&amp;nbsp;Alloc(3)&amp;nbsp;returned&amp;nbsp;1000&amp;nbsp;(searched&amp;nbsp;1&amp;nbsp;elements)&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;1&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1003&amp;nbsp;sz:97&amp;nbsp;]&lt;br /&gt;&lt;br /&gt;Free(ptr[0])&lt;br /&gt;returned&amp;nbsp;0&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;2&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1000&amp;nbsp;sz:3&amp;nbsp;][&amp;nbsp;addr:1003&amp;nbsp;sz:97&amp;nbsp;]&lt;br /&gt;&lt;br /&gt;ptr[1]&amp;nbsp;=&amp;nbsp;Alloc(5)&amp;nbsp;returned&amp;nbsp;1003&amp;nbsp;(searched&amp;nbsp;2&amp;nbsp;elements)&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;2&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1000&amp;nbsp;sz:3&amp;nbsp;][&amp;nbsp;addr:1008&amp;nbsp;sz:92&amp;nbsp;]&lt;br /&gt;&lt;br /&gt;Free(ptr[1])&lt;br /&gt;returned&amp;nbsp;0&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;3&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1000&amp;nbsp;sz:3&amp;nbsp;][&amp;nbsp;addr:1003&amp;nbsp;sz:5&amp;nbsp;][&amp;nbsp;addr:1008&amp;nbsp;sz:92&amp;nbsp;]&lt;br /&gt;&lt;br /&gt;ptr[2]&amp;nbsp;=&amp;nbsp;Alloc(8)&amp;nbsp;returned&amp;nbsp;1008&amp;nbsp;(searched&amp;nbsp;3&amp;nbsp;elements)&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;3&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1000&amp;nbsp;sz:3&amp;nbsp;][&amp;nbsp;addr:1003&amp;nbsp;sz:5&amp;nbsp;][&amp;nbsp;addr:1016&amp;nbsp;sz:84&amp;nbsp;]&lt;br /&gt;&lt;br /&gt;Free(ptr[2])&lt;br /&gt;returned&amp;nbsp;0&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;4&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1000&amp;nbsp;sz:3&amp;nbsp;][&amp;nbsp;addr:1003&amp;nbsp;sz:5&amp;nbsp;][&amp;nbsp;addr:1008&amp;nbsp;sz:8&amp;nbsp;][&amp;nbsp;addr:1016&amp;nbsp;sz:84&amp;nbsp;]&lt;br /&gt;&lt;br /&gt;ptr[3]&amp;nbsp;=&amp;nbsp;Alloc(8)&amp;nbsp;returned&amp;nbsp;1008&amp;nbsp;(searched&amp;nbsp;4&amp;nbsp;elements)&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;3&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1000&amp;nbsp;sz:3&amp;nbsp;][&amp;nbsp;addr:1003&amp;nbsp;sz:5&amp;nbsp;][&amp;nbsp;addr:1016&amp;nbsp;sz:84&amp;nbsp;]&lt;br /&gt;&lt;br /&gt;Free(ptr[3])&lt;br /&gt;returned&amp;nbsp;0&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;4&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1000&amp;nbsp;sz:3&amp;nbsp;][&amp;nbsp;addr:1003&amp;nbsp;sz:5&amp;nbsp;][&amp;nbsp;addr:1008&amp;nbsp;sz:8&amp;nbsp;][&amp;nbsp;addr:1016&amp;nbsp;sz:84&amp;nbsp;]&lt;br /&gt;&lt;br /&gt;ptr[4]&amp;nbsp;=&amp;nbsp;Alloc(2)&amp;nbsp;returned&amp;nbsp;1000&amp;nbsp;(searched&amp;nbsp;4&amp;nbsp;elements)&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;4&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1002&amp;nbsp;sz:1&amp;nbsp;][&amp;nbsp;addr:1003&amp;nbsp;sz:5&amp;nbsp;][&amp;nbsp;addr:1008&amp;nbsp;sz:8&amp;nbsp;][&amp;nbsp;addr:1016&amp;nbsp;sz:84&amp;nbsp;]&lt;br /&gt;&lt;br /&gt;ptr[5]&amp;nbsp;=&amp;nbsp;Alloc(7)&amp;nbsp;returned&amp;nbsp;1008&amp;nbsp;(searched&amp;nbsp;4&amp;nbsp;elements)&lt;br /&gt;Free&amp;nbsp;List&amp;nbsp;[&amp;nbsp;Size&amp;nbsp;4&amp;nbsp;]:&amp;nbsp;[&amp;nbsp;addr:1002&amp;nbsp;sz:1&amp;nbsp;][&amp;nbsp;addr:1003&amp;nbsp;sz:5&amp;nbsp;][&amp;nbsp;addr:1015&amp;nbsp;sz:1&amp;nbsp;][&amp;nbsp;addr:1016&amp;nbsp;sz:84&amp;nbsp;]&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 프리 리스트를 검색할 때 최악 적합(Worst-fit) 정책(&lt;span&gt;-p&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;WORST&lt;/span&gt;)을 사용하면 결과가 어떻게 달라지나요? 무엇이 바뀌나요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A : Best Fit에서는 필요한 크기에 가장 맞는 블록을 고른다. 그래서 작은 블록을 먼저 소모한다. Worst Fit에서는 가장 큰 블록을 고른다. 그래서 작은 블록은 그대로 남기고 큰 블록에서 잘라간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 최선 적합(First-fit) 정책(&lt;span&gt;-p&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;FIRST&lt;/span&gt;)은 어떤가요? 최선 적합을 사용하면 무엇이 빨라지나요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A : 평균 탐색 길이가 감소한다. 왜냐하면 가장 처음 맞는 곳에서 바로 블록을 사용하기 때문이다. 구현이 단순하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 위의 질문들에서, 리스트를 어떤 순서로 유지하느냐에 따라 일부 정책의 자유 블록 탐색 시간이 달라질 수 있습니다. 다양한 프리 리스트 순서(&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;ADDRSORT,-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;SIZESORT+,-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;SIZESORT-&lt;/span&gt;)를 사용하여 정책과 리스트 순서가 어떻게 상호작용하는지 확인하세요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A :&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;리스트 순서 (-l) \ 정책 (-p)&lt;/td&gt;
&lt;td&gt;FIRST (처음 맞는 곳)&lt;/td&gt;
&lt;td&gt;BEST (가장 작은 적합)&lt;/td&gt;
&lt;td&gt;WORST (가장 큰 적합)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ADDRSORT&lt;/b&gt; (주소 오름차순)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;짧음&lt;/b&gt;: 앞쪽에서 종종 멈춤&lt;/td&gt;
&lt;td&gt;&lt;b&gt;김&lt;/b&gt;: 최솟값 찾으려면 보통 &lt;b&gt;전수 스캔&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;김&lt;/b&gt;: 최댓값 찾으려면 보통 &lt;b&gt;전수 스캔&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SIZESORT+&lt;/b&gt; (크기 오름차순)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;짧음&lt;/b&gt;: 작은 것부터 보니 첫 적합이 빠름 &amp;rarr; &lt;b&gt;거의 BEST처럼&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;짧음&lt;/b&gt;: &lt;b&gt;처음 만나는 적합 = 베스트핏&lt;/b&gt; &amp;rarr; &lt;b&gt;초기 정지&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;김&lt;/b&gt;: 끝쪽(큰 블록)을 찾아가야 해서 &lt;b&gt;전수에 가깝게&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SIZESORT-&lt;/b&gt; (크기 내림차순)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;짧음&lt;/b&gt;: 큰 것부터 보니 첫 적합이 빠름 &amp;rarr; &lt;b&gt;거의 WORST처럼&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;김&lt;/b&gt;: 끝쪽(작은 적합)을 찾아야 해서 &lt;b&gt;전수에 가깝게&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;짧음&lt;/b&gt;: &lt;b&gt;처음 만나는 적합 = 워스트핏&lt;/b&gt; &amp;rarr; &lt;b&gt;초기 정지&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 프리 리스트의 병합(Coalescing)은 매우 중요할 수 있습니다. 무작위 할당 수를 늘려보세요(예: -n 1000). 시간이 지남에 따라 큰 할당 요청에는 어떤 일이 일어나나요? 병합 없이(즉, -C 플래그 없이) 그리고 병합과 함께 실행해 보세요. 결과에 어떤 차이가 있나요? 각 경우에 시간에 따른 프리 리스트의 크기는 어떻게 되나요? 이 경우 리스트의 순서가 중요한가요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A : &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;큰 할당을 오래 성공시키려면 &lt;/span&gt;&lt;b&gt;병합(-C ON)&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 이 필수이고, 탐색 속도는 &lt;/span&gt;&lt;b&gt;정책&amp;times;리스트 정렬(BEST&amp;harr;SIZESORT+, WORST&amp;harr;SIZESORT-, FIRST는 대체로 짧음)&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 로 최적화하되, &lt;/span&gt;&lt;b&gt;병합이 꺼지면&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 시간이 갈수록 조각화로 &lt;/span&gt;&lt;b&gt;큰 요청 실패&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;가 급증한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 할당된 블록의 비율을 나타내는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-P&lt;/span&gt;를 50 이상으로 변경하면 어떻게 되나요? 이 비율이 100에 가까워지면 할당은 어떻게 되나요? 0에 가까워지면요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;224&quot; data-start=&quot;43&quot;&gt;&lt;b&gt;P &amp;gt; 50 (할당 위주)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;224&quot; data-start=&quot;68&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;118&quot; data-start=&quot;68&quot;&gt;&lt;b&gt;라이브 블록이 계속 늘어남&lt;/b&gt; &amp;rarr; 프리 리스트가 줄고, 큰 덩어리부터 잘려나감.&lt;/li&gt;
&lt;li data-end=&quot;166&quot; data-start=&quot;121&quot;&gt;시간이 갈수록 &lt;b&gt;큰 요청 실패&lt;/b&gt;가 늘어남(특히 -C OFF일 때).&lt;/li&gt;
&lt;li data-end=&quot;224&quot; data-start=&quot;169&quot;&gt;searched k는 초반엔 짧다가, 남은 자유공간이 잘게 쪼개질수록 점점 길어질 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;356&quot; data-start=&quot;226&quot;&gt;&lt;b&gt;P &amp;rarr; 100 (거의 전부 할당)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;356&quot; data-start=&quot;255&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;304&quot; data-start=&quot;255&quot;&gt;해제가 거의 없어 &lt;b&gt;언젠가 힙 고갈&lt;/b&gt; &amp;rarr; 이후 Alloc은 &lt;b&gt;연속 실패&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;356&quot; data-start=&quot;307&quot;&gt;-C 유무나 정책/정렬의 차이가 &lt;b&gt;거의 영향 없음&lt;/b&gt;(풀릴 메모리가 없으니까).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;562&quot; data-start=&quot;358&quot;&gt;&lt;b&gt;P &amp;lt; 50 (해제 위주)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;562&quot; data-start=&quot;383&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;413&quot; data-start=&quot;383&quot;&gt;라이브 블록이 줄어 &lt;b&gt;프리 리스트가 커짐&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;473&quot; data-start=&quot;416&quot;&gt;-C ON이면 큰 블록으로 &lt;b&gt;재결합&lt;/b&gt;되어 &lt;b&gt;큰 요청 성공률&amp;uarr; / searched&amp;darr;&lt;/b&gt;.&lt;/li&gt;
&lt;li data-end=&quot;562&quot; data-start=&quot;476&quot;&gt;-C OFF면 프리 리스트 길이만 &lt;b&gt;길어지고&lt;/b&gt;(작은 구멍 증가) &amp;rarr; &lt;b&gt;searched&amp;uarr;&lt;/b&gt;, 큰 요청은 &lt;b&gt;조각화 때문에 실패&lt;/b&gt;할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;7. 고도로 단편화된 프리 공간을 생성하려면 어떤 종류의 특정 요청을 만들 수 있나요? -A 플래그를 사용하여 단편화된 프리 리스트를 생성하고, 다양한 정책과 옵션이 프리 리스트의 구성을 어떻게 변경하는지 확인하세요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A: -A로 &lt;b&gt;교차 해제 + 살짝 작은 재할당&lt;/b&gt;을 반복하면 &lt;b&gt;초미세 조각(1B)들이 누적&lt;/b&gt;되어 고단편화가 잘 생기고, 이때 &lt;b&gt;BEST/FIRST+ADDRSORT&lt;/b&gt;는 슬리버를 양산, &lt;b&gt;WORST+SIZESORT-&lt;/b&gt;는 큰 덩어리를 깎아 &lt;b&gt;중형 조각&lt;/b&gt;을 확산시킵니다; &lt;b&gt;코얼레싱(-C ON)&lt;/b&gt; 을 켜면 인접 해제들이 즉시 합쳐져 &lt;b&gt;큰 요청 생존성&lt;/b&gt;이 크게 개선됩니다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/202</guid>
      <comments>https://gowoong.tistory.com/202#entry202comment</comments>
      <pubDate>Tue, 21 Oct 2025 10:16:16 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 7주차 - 메모리 가상화  3 - 빈 공간 관리</title>
      <link>https://gowoong.tistory.com/201</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;가정&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 챕터의 논의의 대부분은 사용자 수준 메모리 할당 라이브러리에 존재하는 메모리 할당기의 발전 역사에 초첨을 맞춘다....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;malloc()&lt;/code&gt;과 &lt;code&gt;free()&lt;/code&gt;에서 제공하는 것과 같은 기본 인터페이스를 가정한다. 구체적으로 &lt;code&gt;void *malloc (size_t size)&lt;/code&gt;는 응용 프로그램이 요청한 바이트 수를 나타내는 변수 &lt;code&gt;size&lt;/code&gt;를 받아들인다. 이 함수는 요청된 크기와 같거나 큰 영역을 가리키는, 타입이 없는 또는 C 언어의 용어로 &lt;code&gt;void&lt;/code&gt; 포인터를 반환한다. 대응되는 루틴 &lt;code&gt;void free(void *ptr)&lt;/code&gt;는 포인터를 인자로 전달받고 해당 영역을 해제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스의 의미에 주의하라 공간을 해제할 때 사용자는 라이브러리에게 크기 정보를 전달하지 않는다. 라이브러리는 포인터만으로 해제하고자 하는 메모리 영역의 크기를 파악해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 라이브러리가 관리하는 공간은 &lt;b&gt;힙(heap)&lt;/b&gt;이다.&amp;nbsp; 힙의 빈 공간을 관리하는 데는 일반적으로 &lt;b&gt;링크드 리스트&lt;/b&gt;가 사용된다. 물론 반드시 리스트일 필요는 없고 빈 공간을 표현할 수 있는 자료 구조면 충분하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 단편화 방지에 특히 중점을 둘 것이다. 논의를. 단순화하기 위하여, 외부 단편화에 초점을 맞출 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에게 할당된 메모리는 다른 위치로 재배치될 수 없다고 가정한다. &lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;예를 들어, 프로그램이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;malloc()&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;을 호출하여 힙의 일부 영역에 대한 포인터를 받으면, 그 메모리 영역은 대응하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;free()&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;를 통하여 반환될 때까지 프로그램이 소유하게 되고 라이브러리에 의해 다른 위치로 옮겨질 수 없다. 단편화 해결에 유용하게 사용되는 빈 공간의 압축은 이 경우에는 사용이 불가능하다. 운영체제가 세그먼트를 구현할 때는 단편화를 해결하기 위하여 압축을 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;저수준 기법들&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할당기에서 사용되는 일반적인 기법에 대해 논의 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째,&lt;b&gt; 분할(splitting)&lt;/b&gt;과 &lt;b&gt;병합(coalescing)&lt;/b&gt;의 개념에 대해 알아본다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;분할(Splitting)&lt;/b&gt;: 큰 메모리 블록을 여러 개의 작은 블록으로 나누는 작업을 말한다. 메모리 할당 요청이 들어왔을 때, 요청된 크기보다 큰 블록을 찾아 그 블록을 분할하여 요청된 크기만큼 할당하고 남은 부분은 빈 공간으로 유지한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;병합(Coalescing)&lt;/b&gt;: 인접한 빈 메모리 블록들을 하나의 큰 블록으로 합치는 작업을 말한다. 메모리 블록이 해제되었을 때, 해제된 블록과 인접한 빈 블록들을 병합하여 더 큰 빈 블록을 만들 수 있다. 이를 통해 외부 단편화를 줄일 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째, 할당된 영역의 크기를 빠르고 상대적으로 쉽게 파악할 수 있는 방법을 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 빈 공간과 사용 중인 공간을 추적하기 위해 빈 공간 내에 간단한 리스트를 구현하는 방법에 대해 설명한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;기본 전략&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;이상적인 할당기는 속도가 빠르고 단편화를 최소화해야 한다. 할당과 해제 요청 스트림은 무작위로, 결국 프로그래머에 의해 결정되기 때문에, 어느 특정 전략도 잘 맞지 않는 입력을 만나면 성능이 매우 좋지 않을 수 있다. 최선의 정책을 설명하는 것이 아니라 몇 가지 기본 정책에 대해 이야기하고 각각의 장단점을 논의한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;최적 적함(Best Fit)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;가장 작은 남는 공간에 할당한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;빈 공간 리스트를 검색하여 요청한 크기와 같거나 더 큰 빈 메모리 청크를 찾는다. 그 후, 후보자 그룹 중에서 가장 작은 크기의 청크를 반환한다. 이 청크는 최적 청크라고 불린다. 최소 적합이라고도 불린다. 빈 공간 리스트를 한 번만 순회하면 반환할 정확한 블록을 찾을 수 있다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&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;823&quot; data-origin-height=&quot;677&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7Vwq4/dJMb9LjGIzu/2oKmn1MOqrez6wo7EnYmxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7Vwq4/dJMb9LjGIzu/2oKmn1MOqrez6wo7EnYmxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7Vwq4/dJMb9LjGIzu/2oKmn1MOqrez6wo7EnYmxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7Vwq4%2FdJMb9LjGIzu%2F2oKmn1MOqrez6wo7EnYmxK%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;823&quot; height=&quot;677&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;677&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최악 적합(Worst Fit)&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;가장 큰 남는 공간에 할당한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&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;816&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcg7JO/dJMb9NV6rRi/UZuRmI8GKdyUug8tUifhUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcg7JO/dJMb9NV6rRi/UZuRmI8GKdyUug8tUifhUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcg7JO/dJMb9NV6rRi/UZuRmI8GKdyUug8tUifhUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcg7JO%2FdJMb9NV6rRi%2FUZuRmI8GKdyUug8tUifhUk%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;816&quot; height=&quot;668&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최초 적합(First Fit)&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;첫 번째로 충분히 큰 공간에 할당한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 요청보다 큰 첫 번째 블록을 찾아서 요청만큼 반환한다. 남은 빈 공간은 후속 요청을 위해 계속 유지된다. 속도가 빠르다는 것이 장점이다. 원하는 블록을 찾기 위해 항상 빈 공간 리스트 전체를 탐색할 필요가 없다. 그러나 리스트의 시작에 크기가 작은 객체가 많이 생길 수 있다. 따라서 할당기가 빈 공간 리스트의 순서를 관리하는 방법이 쟁점이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;주소 기반 정렬(address-based ordering)을 사용하면 리스트를 주소로 정렬하여 병합을 쉽게 하고, 단편화를 감소시킨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4g1x0/dJMb9MCTiKw/Y8S7d7kepzQP8xRStYRYd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4g1x0/dJMb9MCTiKw/Y8S7d7kepzQP8xRStYRYd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4g1x0/dJMb9MCTiKw/Y8S7d7kepzQP8xRStYRYd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4g1x0%2FdJMb9MCTiKw%2FY8S7d7kepzQP8xRStYRYd0%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;824&quot; height=&quot;674&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;다음 적합(Next Fit)&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 할당된 공간의 다음부터 충분히 큰 공간을 찾아 할당합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&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;704&quot; data-origin-height=&quot;819&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvs5Rq/dJMb9QywP03/KD1JrNvXVFcP9kgNkSkil0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvs5Rq/dJMb9QywP03/KD1JrNvXVFcP9kgNkSkil0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvs5Rq/dJMb9QywP03/KD1JrNvXVFcP9kgNkSkil0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcvs5Rq%2FdJMb9QywP03%2FKD1JrNvXVFcP9kgNkSkil0%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;704&quot; height=&quot;819&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;819&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;다른 접근법&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 메모리 할당을 향상시키기 위한 기술과 알고리즘이 있다.&lt;/p&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개별 리스트(Segregated List)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 응용 프로그램이 자주 요청하는 크기의 객체를 관리하기 위해 별도의 리스트를 유지하는 것을 개별 리스트라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법의 장점은 특정 크기의 요청을 위한 메모리 청크를 유지하기 때문에 단편화 가능성을 상당히 줄일 수 있다는 것이다. 요청된 크기의 청크만이 존재하기 때문에 복잡한 리스트 검색이 필요하지 않으므로 할당과 해제 요청을 신속히 처리할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제점&amp;nbsp;지정된 크기의 메모리 풀과 일반적인 풀에 얼마만큼의 메모리를 할당해야 하는가?&lt;/p&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;슬랩 할당기(Slab Allocator)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 문제는 특수 목적 할당기인 슬랩 할당기가 더 나은 방법으로 해결했다. 커널이 부팅될 때 커널 객체를 위한 여러 객체 캐시(object cache)를 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 커널 객체란 락, 파일 시스템 아이노드 등 자주 요청되는 자료 구조들을 일컫는다. 객체 캐시는 지정된 크기의 객체들로 구성된 빈 공간 리스트이고, 메모리 할당 및 해제 요청을 빠르게 하기 위해 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 할당된 캐시 공간이 부족하면 상위 메모리 할당기에게 추가 슬랩을 요청한다. 슬랩 내 객체들에 대한 참조 횟수가 0이 되면 상위 메모리 할당기는 이 슬랩을 회수할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬랩 할당 방식은 빈 객체들을 사전에 초기화된 상태로 유지한다는 점에서 개별 리스트 방식보다 우수하다. 반납된 객체들을 초기화된 상태로 리스트에 유지하여 슬랩 할당기는 객체당 잦은 초기화와 반납의 작업을 피할 수 있어서 오버헤드를 현저히 감소시킨다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;슬랩(Slab): 커널 객체를 저장하기 위해 할당된 연속적인 메모리 블록이다. 슬랩은 동일한 크기의 객체들로 구성되며, 각 객체는 미리 초기화된 상태로 유지된다.&lt;/li&gt;
&lt;li&gt;객체 캐시(Object Cache): 동일한 크기와 타입의 객체들을 관리하는 캐시. 각 객체 캐시는 해당 객체의 할당과 해제 요청을 처리한다. 객체 캐시는 여러 개의 슬랩으로 구성될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;버디 할당(Buddy Allocation)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;빈 공간의 합병을 간단히 하는 방법을 버디 할당이라고 한다.&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이진 버디 할당기(Binary Buddy Allocator)&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;538&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLFl7O/dJMb88MEwDW/rEKVEH1tA3W0HOuqhNwClk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLFl7O/dJMb88MEwDW/rEKVEH1tA3W0HOuqhNwClk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLFl7O/dJMb88MEwDW/rEKVEH1tA3W0HOuqhNwClk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLFl7O%2FdJMb88MEwDW%2FrEKVEH1tA3W0HOuqhNwClk%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;538&quot; height=&quot;302&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 메모리는 처음에 개념적으로 2ⁿ인 하나의 큰 공간으로 생각하면, 메모리 요청이 발생해 그 요청을 충족시키기에 충분한 공간이 발견될 때까지(그리고 더 분할하면 공간이 너무 작아져서 요청을 만족시킬 수 없을 때까지) 빈 공간을 2개로 계속 분할한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 사진의 예에서 7KB 크기의 요청이 들어와서 가장 왼쪽 8KB 블록이 될 때까지 분할하고 사용자에게 반환된다. 이 방식은 2의 거듭제곱 크기의 블록만 할당할 수 있기 때문에 내부 단편화가 발생할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;블록 해제 시 해제한 블록 옆에 있는 &lt;b&gt;버디&lt;/b&gt; 블록을 확인한다. 만약 비어있다면 두 블록을 병합하고, 이 재귀 합병 과정은 트리를 따라 전체 빈 공간이 복원되거나 버디가 사용 중이라는 것이 밝혀질 때까지 계속 올라간다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;버디 할당이 잘 작동하는 이유는 특정 블록의 버디를 결정하기 쉽기 때문이다. 특정 블록과 그 블록의 버디의 주소는 오직 한 비트만 다르다. 어느 위치의 비트가 다른가는 버디 트리의 수준에 따라 달라진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버디(Buddy): 버디 할당에서 인접한 두 개의 블록을 버디라고 한다. 버디는 항상 동일한 크기를 가지며, 주소가 인접해 있다. 버디 블록은 병합과 분할 과정에서 함께 처리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기타 아이디어&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에 설명한 접근 방식들의 한 가지 문제점은 확장성이다. 빈 공간들의 개수가 늘어남에 따라 리스트 검색이 매우 느려질 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;균형 이진트리(balanced binary tree), 스플레이 트리(splay tree), 부분 정렬 트리(partially ordered tree)&lt;/b&gt;와 같은 복잡한 자료구조를 할당기에 적용하여 성능을 높일 수 있다. 단순함과 성능은 반비례한다는 것이 문제다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 장에서는 가장 기본적인 형태의 메모리 할당기에 대해 논의해 봤다. 이러한 할당기는 많은 소프트웨어 및 운영체제에서 사용되고 있다. 할당기를 구축할 때는 다양한 선택사항이 있으며, 워크로드를 이해하고 그에 맞게 조정하는 것이 중요하다. 다양한 워크로드에 대해 빠르고 효율적이며 확장 가능한 할당기를 만드는 것은 현대 컴퓨터 시스템의 숙제라 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;할당기를 설계할 때는 다양한 전략과 기법들을 고려해야 한다. 최적 적합, 최악 적합, 최초 적합, 다음 적합과 같은 기본 전략들은 각각 장단점을 가지고 있다. 이러한 전략들을 적절히 조합하고, 개별 리스트, 슬랩 할당, 버디 할당과 같은 발전된 기법들을 활용하여 할당기의 성능과 효율성을 향상시킬 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, 할당기의 성능은 사용되는 자료구조에 크게 영향을 받는다. 단순한 링크드 리스트부터 균형 이진트리, 스플레이 트리, 부분 정렬 트리 등의 복잡한 자료구조까지 다양한 옵션이 있다. 적절한 자료구조를 선택하여 빠른 검색과 삽입, 삭제 연산을 지원하는 것이 중요하다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메모리 할당기를 설계하고 구현할 때는 항상 트레이드오프(trade-off)를 고려해야 한다. 단편화를 최소화하면서도 할당과 해제의 속도를 높이는 것, 메모리 오버헤드를 줄이면서도 효율적인 관리가 가능하도록 하는 것 등이 중요한 과제다. 이를 위해서는 다양한 전략과 기법들을 적절히 활용하고, 시스템의 특성과 요구사항에 맞게 할당기를 튜닝해야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;효과적인 메모리 할당기를 개발하는 것은 시스템의 전반적인 성능과 안정성에 큰 영향을 미친다. 따라서 운영체제 개발자와 시스템 프로그래머는 메모리 할당 문제에 대해 깊이 이해하고, 최적의 솔루션을 설계하고 구현할 수 있어야 한다. 이를 통해 시스템 자원을 효율적으로 활용하고, 사용자에게 더 나은 경험을 제공할 수 있을 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/201</guid>
      <comments>https://gowoong.tistory.com/201#entry201comment</comments>
      <pubDate>Tue, 21 Oct 2025 09:07:26 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 6주차 - 메모리 가상화 2 정리 및 숙제</title>
      <link>https://gowoong.tistory.com/200</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;주소 변환의 원리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문제의식&lt;/b&gt; : 각 프로세스가 &quot;자기만의 주소 0부터&quot; 쓰게 하면서도 서로 간섭하지 않게 하고, 물리 메모리에 유연하게 배치하려면? --&amp;gt; &lt;b&gt;가상 주소 --&amp;gt; 물리 주소&lt;/b&gt;로 바꿔주는 &lt;b&gt;MMU&lt;/b&gt;가 필요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핵심 기술(초기형)&lt;/b&gt;: &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;Base + Bound&lt;/b&gt;&lt;/span&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;운영체제의 역할(개입 시점)&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로세스 생성: 빈 슬롯 찾아 할당&lt;/li&gt;
&lt;li&gt;종료: 회수&lt;/li&gt;
&lt;li&gt;문맥 교환: 각 프로세스의 Base/Bound를 저장&amp;middot;복원&lt;/li&gt;
&lt;li data-end=&quot;628&quot; data-start=&quot;574&quot;&gt;보호 위반: 예외 처리&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핵심 메시지&lt;/b&gt;: 단순&amp;middot;빠르지만 주소공간이 크거나 드문드문 쓰일 때 &lt;b&gt;낭비/유연성 한계&lt;/b&gt;가 생김. 그래서 더 발전된 기법(세그멘테이션/페이징)으로 발전&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;세그멘테이션&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;아이디어&lt;/b&gt;: Base/Bound를 &quot;세그먼트별&quot;로 일반화(코드/힙/스택 등 각자 &lt;b&gt;Base/Bound&lt;/b&gt;를 가짐). 덕분에 &lt;b&gt;쓰는 부분만 &lt;/b&gt;물리메모리에 배치 --&amp;gt; 드문드문(sparse) 주소 공간을 효율적으로 수용..&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주소 변환&lt;/b&gt;: (세그먼트 선택) + (세그먼트 내부 오프셋) &amp;rarr; 해당 세그먼트의 Base에 오프셋을 더함. 스택은 성장 방향이 반대라 방향 비트로 처리.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공유/보호&lt;/b&gt;:세그먼트 단위 &lt;b&gt;보호 비트(읽기/쓰기/실행)&lt;/b&gt; 로 &lt;b&gt;코드 공유&lt;/b&gt; 가능(여러 프로세스가 같은 코드 세그먼트를 읽기 전용으로 매핑).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;현실의 문제:&lt;/b&gt; 세그먼트가 &lt;b&gt;가변 크기&lt;/b&gt;라서 &lt;b&gt;외부 단편화&lt;/b&gt;가 생김(틈이 쪼개져 큰 연속 공간을 못 만들 때). 압축(compaction)이나 적합-할당 알고리즘을 써도 근본 해결은 어려움. 그래서 오늘날은 보통 &lt;b&gt;페이징&lt;/b&gt;(고정 크기)으로 해결하거나 혼합. &lt;span data-state=&quot;delayed-open&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;숙제&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;질문&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 시드 1, 2, 3으로 실행하고 프로세스에서 생성된 각 가상 주소가 범위 내에 있는지 또는 범위를 벗어났는지 계산하세요. 범위 내라면 변환을 계산하세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.18.04.png&quot; data-origin-width=&quot;705&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ed7kMo/btsQ7vWb2Pu/mxOrAQvUvF2m1MnnCpXTtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ed7kMo/btsQ7vWb2Pu/mxOrAQvUvF2m1MnnCpXTtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ed7kMo/btsQ7vWb2Pu/mxOrAQvUvF2m1MnnCpXTtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fed7kMo%2FbtsQ7vWb2Pu%2FmxOrAQvUvF2m1MnnCpXTtk%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;705&quot; height=&quot;302&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.18.04.png&quot; data-origin-width=&quot;705&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A1 : Limit 가 290 이다. VA 0, 1, 2, 3, 4를 비교했을 때 VA1 만 290 이하에 있으니 범위 내에 있다고 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.21.03.png&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmdAau/btsQ8x6BRna/rbMQ2aLLwWN9K8Oh8wsbcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmdAau/btsQ8x6BRna/rbMQ2aLLwWN9K8Oh8wsbcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmdAau/btsQ8x6BRna/rbMQ2aLLwWN9K8Oh8wsbcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmdAau%2FbtsQ8x6BRna%2FrbMQ2aLLwWN9K8Oh8wsbcK%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;677&quot; height=&quot;300&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.21.03.png&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A2 : Limit 가 500 일때 범위 내에 있는 것은 VA0, VA1이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.21.11.png&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vjgE9/btsQ7NB92lj/YvYcGSgKsrSS7EWC7mJp5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vjgE9/btsQ7NB92lj/YvYcGSgKsrSS7EWC7mJp5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vjgE9/btsQ7NB92lj/YvYcGSgKsrSS7EWC7mJp5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvjgE9%2FbtsQ7NB92lj%2FYvYcGSgKsrSS7EWC7mJp5k%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;644&quot; height=&quot;303&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.21.11.png&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A3: Limit 가 316 일 때 범위 내에 있는 것은 VA3, VA4이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 다음 플래그로 실행하세요:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;. 생성된 모든 가상 주소가 범위 내에 있도록 하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;(한계 레지스터)을 어떤 값으로 설정해야 할까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.28.03.png&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SmwOe/btsQ9hWGtnJ/oaKbFhhkyeYykQbCDcdEy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SmwOe/btsQ9hWGtnJ/oaKbFhhkyeYykQbCDcdEy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SmwOe/btsQ9hWGtnJ/oaKbFhhkyeYykQbCDcdEy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSmwOe%2FbtsQ9hWGtnJ%2FoaKbFhhkyeYykQbCDcdEy1%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;618&quot; height=&quot;374&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.28.03.png&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A: 최소 929 보다는 커야 한다 그러면 930이 가장 최소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 다음 플래그로 실행하세요:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;. 주소 공간이 여전히 물리 메모리에 전체적으로 맞도록 기준을 설정할 수 있는 최댓값은 얼마인가요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.31.31.png&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o9JcK/btsQ7yrQDR3/6d6OkZppvOWRvt57TNkPok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o9JcK/btsQ7yrQDR3/6d6OkZppvOWRvt57TNkPok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o9JcK/btsQ7yrQDR3/6d6OkZppvOWRvt57TNkPok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo9JcK%2FbtsQ7yrQDR3%2F6d6OkZppvOWRvt57TNkPok%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;649&quot; height=&quot;372&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.31.31.png&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A: 물리 메모리 범위는 16K - 100으로 16384 - 100 = 16284이다. 즉 Base 가 16284 위치여도 가능하다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 위와 동일한 문제 중 일부를 실행해 보세요. 단, 더 큰 주소 공간(&lt;span&gt;-a&lt;/span&gt;)과 물리 메모리(&lt;span&gt;-p&lt;/span&gt;)를 사용하세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.37.17.png&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/db3Vbm/btsQ8reubP7/cHGw7i7NcUsNKXngiSQ3Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/db3Vbm/btsQ8reubP7/cHGw7i7NcUsNKXngiSQ3Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/db3Vbm/btsQ8reubP7/cHGw7i7NcUsNKXngiSQ3Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdb3Vbm%2FbtsQ8reubP7%2FcHGw7i7NcUsNKXngiSQ3Nk%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;641&quot; height=&quot;363&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.37.17.png&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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 data-end=&quot;728&quot; data-start=&quot;706&quot;&gt;VA는 0~65535 범위에서 생성.&lt;/li&gt;
&lt;li data-end=&quot;754&quot; data-start=&quot;729&quot;&gt;유효성: VA &amp;lt; 4096 이어야 함.&lt;/li&gt;
&lt;li data-end=&quot;833&quot; data-start=&quot;755&quot;&gt;적재 가능 조건: Base + 4096 &amp;le; 1,048,576 &amp;rarr; &lt;b&gt;Base_max = 1,044,480 (&amp;asymp;0xFF000)&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.38.29.png&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IC4Cc/btsQ69MGbAi/tgAcGUwxucFPdA2JoIPJZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IC4Cc/btsQ69MGbAi/tgAcGUwxucFPdA2JoIPJZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IC4Cc/btsQ69MGbAi/tgAcGUwxucFPdA2JoIPJZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIC4Cc%2FbtsQ69MGbAi%2FtgAcGUwxucFPdA2JoIPJZk%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;678&quot; height=&quot;376&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.38.29.png&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/figure&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;limit 가 21689가 되었다. 현재 VA들을 모두 범위 안으로 들어오게 만들기 위해 Limit를 42660으로 조정하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.40.04.png&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM017C/btsQ5YkpjsQ/it8PQ6ZMwULkFQQ20puwL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM017C/btsQ5YkpjsQ/it8PQ6ZMwULkFQQ20puwL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM017C/btsQ5YkpjsQ/it8PQ6ZMwULkFQQ20puwL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM017C%2FbtsQ5YkpjsQ%2Fit8PQ6ZMwULkFQQ20puwL1%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;626&quot; height=&quot;313&quot; data-filename=&quot;스크린샷 2025-10-14 오후 12.40.04.png&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/200</guid>
      <comments>https://gowoong.tistory.com/200#entry200comment</comments>
      <pubDate>Tue, 14 Oct 2025 12:41:48 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 6주차 - 메모리 가상화 2 Part.1</title>
      <link>https://gowoong.tistory.com/199</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;주소 변환의 원리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 가상화는 가상화를 제공하는 동시에 효율성과 제어 모두를 추구한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;효율성: 레지스터, TLB(Translation Lookaside Buffer) 등의 하드웨어 지원을 활용해 주소 변환의 효율을 높인다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레지스터: CPU 내부에 있는 고속의 작은 메모리로, 자주 사용되는 데이터나 명령어를 저장하여 빠른 접근을 가능하게 한다.&lt;/li&gt;
&lt;li&gt;TLB: 최근에 사용된 가상 주소와 물리 주소의 매핑 정보를 캐시로 저장하여, 주소 변환 속도를 향상시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;제어: 운영체제는 각 프로세스의 주소 공간을 분리하여 관리함으로써, 한 프로세스가 다른 프로세스의 메모리에 무단으로 접근하는 것을 방지한다. 이를 통해 시스템의 안정성과 보안을 높인다.&lt;/li&gt;
&lt;li&gt;유연성: 가상 메모리 시스템은 프로그래머가 물리 메모리의 제약에 구애받지 않고 편리하게 프로그래밍할 수 있는 환경을 제공한다. 프로세스는 자신만의 독립된 주소 공간을 가지므로, 메모리 할당과 관리가 용이해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 변환(Address translation)은 프로그램이 사용하는 가상 주소를 실제 물리 메모리 상의 주소로 매핑하는 과정이다. 주소 변환은 하드웨어적으로 이루어지며, 프로세서가 메모리 참조 명령어(데이터 읽기, 쓰기, 명령어 가져오기 등)를 수행할 때마다 발생한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;운영체제는 메모리 관리자 역할을 수행하며, 물리 메모리의 할당 및 회수, 가상 주소 공간과 물리 주소 공간의 매핑 등을 담당한다. 이를 위해 운영체제는 메모리의 사용 현황을 파악하고 있어야 하며, 프로세스 간의 메모리 보호와 공유를 적절히 제어해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;동적 (하드웨어 기반) 재배치&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 재배치(Dynamic Relocation)는 프로세스의 주소 공간을 실행 시간에 동적으로 물리 메모리 상의 다른 위치로 이동할 수 있게 해주는 기술이다. 이를 통해 운영체제는 메모리 관리를 보다 유연하게 할 수 있으며, 메모리 단편화 문제를 완화할 수 있다. 동적 재배치를 구현하기 위해 CPU에는 베이스 레지스터(Base Register)와 바운드 레지스터(Bound Register)라는 두 개의 하드웨어 레지스터가 사용된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;베이스 레지스터 (Base Register)&lt;/b&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;이를 통해 프로세스는 가상 주소 0번지에서 시작하는 것처럼 인식하지만, 실제로는 물리 메모리의 다른 영역에 위치할 수 있다.&lt;/li&gt;
&lt;li&gt;베이스 레지스터 값을 변경함으로써 프로세스의 주소 공간을 물리 메모리 상의 다른 위치로 이동시킬 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;바운드 레지스터 (Bound Register)&lt;/b&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;만약 가상 주소가 바운드 레지스터에 저장된 범위를 벗어나면 하드웨어는 예외(Exception)를 발생시켜 운영체제에 알린다.&lt;/li&gt;
&lt;li&gt;이를 통해 프로세스가 자신의 주소 공간 외부의 메모리 영역에 접근하는 것을 방지하여 메모리 보호를 강화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주소 변환 과정&lt;/b&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;물리 주소가 유효한 범위 내에 있다면 해당 주소의 메모리에 접근할 수 있도록 허용한다.&lt;/li&gt;
&lt;li&gt;만약 물리 주소가 바운드 레지스터 값의 범위를 벗어난다면 하드웨어는 예외를 발생시켜 운영체제에 알린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동적 재배치 과정&lt;/b&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;또한 바운드 레지스터 값도 새로운 주소 공간 크기에 맞게 조정한다.&lt;/li&gt;
&lt;li&gt;프로세스의 상태 정보를 저장하는 자료구조인 프로세스 제어 블록(PCB, Process Control Block)에 변경된 베이스와 바운드 레지스터 값을 저장한다.&lt;/li&gt;
&lt;li&gt;프로세스가 다시 실행될 때, PCB에 저장된 베이스와 바운드 레지스터 값이 CPU의 레지스터에 로드되어 프로세스는 새로운 물리 메모리 영역에서 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 관리 장치 (MMU, Memory Management Unit)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주소 변환과 메모리 보호 기능은 CPU 내부의 하드웨어 컴포넌트인 메모리 관리 장치(MMU)에 의해 수행된다.&lt;/li&gt;
&lt;li&gt;MMU는 베이스 레지스터와 바운드 레지스터를 활용하여 가상 주소를 물리 주소로 변환하고, 변환된 주소가 유효한 범위 내에 있는지 검사한다.&lt;/li&gt;
&lt;li&gt;이 과정은 하드웨어적으로 이루어지므로 소프트웨어 오버헤드 없이 빠르게 처리할 수 있다.&lt;/li&gt;
&lt;li&gt;MMU는 주소 변환 테이블(Address Translation Table)을 관리하여 가상 주소와 물리 주소 간의 매핑 정보를 저장한다.&lt;/li&gt;
&lt;li&gt;주소 변환 테이블은 운영체제에 의해 설정되며, MMU는 이를 참조하여 주소 변환을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 재배치 기술을 사용하면 운영체제는 프로세스의 주소 공간을 물리 메모리 상의 임의의 위치로 이동시킬 수 있으므로, 메모리 관리의 유연성이 향상된다. 이를 통해 메모리 단편화 문제를 완화하고, 메모리 사용 효율을 높일 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 베이스 레지스터와 바운드 레지스터를 활용한 주소 변환 및 메모리 보호 기능은 프로세스 간의 메모리 침범을 방지하여 시스템의 안정성과 보안성을 강화한다. 각 프로세스는 자신의 주소 공간 내에서만 메모리에 접근할 수 있으며, 다른 프로세스의 메모리 영역에는 접근할 수 없다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동적 재배치는 초기의 메모리 관리 기법 중 하나로, 현대의 가상 메모리 시스템에서는 페이징(Paging)과 세그멘테이션(Segmentation) 등의 보다 발전된 기술이 사용된다. 하지만 동적 재배치의 기본 개념인 베이스와 바운드 레지스터를 활용한 주소 변환 및 메모리 보호 기능은 현대의 메모리 관리 기법에서도 중요한 역할을 담당하고 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;예제: 베이스와 바운드를 이용한 주소 변환&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;가상 주소 공간의 크기가 4KB인 프로세스가 있다고 가정해 보겠다. (현실에서는 매우 작은 크기이지만, 이해를 돕기 위한 예시) 이 프로세스는 물리 메모리의 16KB 지점에 적재되어 있다. 이 경우 주소 변환은 다음과 같이 이루어진다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt;가상 주소&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt;물리주소&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;16KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;1KB&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;17KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;3000&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;19384&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;4400&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;폴트 (바운드 초과)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 알 수 있듯이, 물리 주소를 얻으려면 가상 주소에 베이스 레지스터의 값(여기서는 16KB)을 더해주면 된다. 가상 주소는 프로세스의 주소 공간 내에서의 오프셋(offset)으로 생각할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 가상 주소가 주소 공간의 크기(여기서는 4KB)를 초과하거나 음수라면, 즉 바운드 레지스터로 정의된 범위를 벗어나면 폴트(fault)가 발생하고 예외(exception)가 처리된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;하드웨어 지원 요약&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널, 유저 모드 구분: 운영체제는 커널 모드에서 핵심 기능을 수행하고, 유저 모드에서는 응용 프로그램이 실행된다. 이는 보안과 안정성을 유지하기 위해 중요하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널 모드(kernel mode): 운영체제의 핵심 기능이 실행되는 모드로, 모든 시스템 자원에 접근할 수 있는 권한을 가진다. 커널 모드에서는 중요한 시스템 작업과 하드웨어 제어가 이루어진다.&lt;/li&gt;
&lt;li&gt;유저 모드(user mode): 응용 프로그램이 실행되는 모드로, 제한된 권한을 가진다. 유저 모드에서는 시스템 자원에 직접 접근할 수 없고, 운영체제가 제공하는 API를 통해 자원을 요청하고 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;베이스, 바운드 레지스터: 각 프로세스가 사용하는 메모리 공간의 시작과 크기를 지정하는 레지스터다. 베이스 레지스터는 시작 위치를 가리키고, 바운드 레지스터는 공간의 크기를 제한한다.&lt;/li&gt;
&lt;li&gt;주소 변환 능력: 하드웨어는 가상 주소를 물리 주소로 변환한다. 이때 베이스 레지스터 값을 더하고, 바운드 레지스터를 사용하여 유효성을 확인한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가상 주소(virtual address): 프로세스가 사용하는 논리적인 주소로, 프로세스의 주소 공간 내에서 유효하다. 가상 주소는 프로세스마다 독립적으로 할당되며, 0부터 시작한다.&lt;/li&gt;
&lt;li&gt;물리 주소(physical address): 실제 물리 메모리 상의 주소로, 가상 주소를 변환한 결과다. 물리 주소는 시스템 전체에서 유일하며, 실제 메모리 위치를 가리킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;잘못된 주소 검출: 하드웨어는 가상 주소가 유효한 범위 내에 있는지 확인하여 보안과 안정성을 유지한다.&lt;/li&gt;
&lt;li&gt;오류 처리 능력: 잘못된 주소 접근이나 범위 초과와 같은 오류가 발생할 때, 하드웨어는 이를 신속하게 감지하고 적절히 처리한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예외(exception): 프로그램 실행 중 발생하는 비정상적인 상황으로, 하드웨어나 소프트웨어에 의해 감지된다. 예외가 발생하면 현재 실행 흐름이 중단되고 예외 처리기(exception handler)로 제어가 전달된다.&lt;/li&gt;
&lt;li&gt;예외 처리기(exception handler): 예외가 발생했을 때 호출되는 코드 루틴으로, 예외 상황을 처리하고 프로그램의 실행을 정상화하거나 안전하게 종료시키는 역할을 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;운영체제 이슈&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;베이스와 바운드 방식의 가상 메모리 구현을 위해서 운영체제가 반드시 개입되어야 하는 중요한 세 개의 시점이 존재한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&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;운영체제는 물리 메모리를 슬롯(slot)의 배열로 관리하며, 각 슬롯의 사용 여부를 추적한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;슬롯(slot): 물리 메모리를 일정한 크기의 블록으로 나눈 것으로, 프로세스에 할당되는 메모리의 기본 단위다. 운영체제는 슬롯의 사용 여부를 비트맵이나 링크드 리스트 등의 자료구조로 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;운영체제는 빈 슬롯을 검색하여 해당 영역을 프로세스의 주소 공간으로 할당하고, 사용 중으로 표시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문맥 교환 시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU에는 한 쌍의 베이스-바운드 레지스터만 존재하므로, 실행 중인 프로세스마다 다른 값을 가져야 한다.&lt;/li&gt;
&lt;li&gt;운영체제는 프로세스 전환 시 현재 프로세스의 베이스와 바운드 레지스터 값을 저장하고, 새로운 프로세스의 값으로 설정해야 한다.&lt;/li&gt;
&lt;li&gt;이 값들은 프로세스 제어 블록(PCB)에 저장되며, 운영체제는 PCB에서 읽어와 CPU 레지스터에 로드한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스 제어 블록(Process Control Block, PCB): 프로세스의 메타데이터를 저장하는 운영체제의 자료구조로, 프로세스 상태, 레지스터 값, 메모리 할당 정보 등을 포함한다. 운영체제는 PCB를 통해 프로세스를 관리하고 문맥 교환을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;프로세스가 할당된 주소 공간 밖의 메모리에 접근하려 할 경우, CPU는 예외를 발생시킨다.&lt;/li&gt;
&lt;li&gt;운영체제는 이 예외를 처리하여 해당 프로세스를 종료하거나 적절한 조치를 취한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;세그멘테이션&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;세그멘테이션: 베이스/바운드의 일반화&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위한 아이디어 중 하나가 세그멘테이션(segmentation)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세그멘테이션은 주소 공간을 논리적으로 분할하여 각각의 세그먼트에 대해 별도의 베이스(base)와 바운드(bound) 쌍을 할당하여 메모리 관리 장치(MMU)에 저장하는 방식이다. 이러한 세그먼트는 특정 길이를 가지는 연속적인 주소 공간을 나타내며, 일반적으로 코드, 스택, 및 힙 등과 같이 프로그램이나 데이터의 논리적인 부분을 나타낸다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세그먼트(segment): 프로그램의 논리적인 구성 요소로, 연속적인 주소 공간을 차지하는 코드, 데이터, 스택 등을 나타냅니다. 각 세그먼트는 고유한 이름, 크기, 보호 속성 등을 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&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;426&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rDzRN/btsQ5AiVFhb/IoxfuQob5sjhyhkaMkyqY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rDzRN/btsQ5AiVFhb/IoxfuQob5sjhyhkaMkyqY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rDzRN/btsQ5AiVFhb/IoxfuQob5sjhyhkaMkyqY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrDzRN%2FbtsQ5AiVFhb%2FIoxfuQob5sjhyhkaMkyqY1%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;426&quot; height=&quot;401&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 보면 64 KB의 물리 메모리에 3개의 세그먼트와 운영체제용으로 예약된 16 KB 영역이 존재한다. 그림에서 볼 수 있듯이, 사용 중인 메모리에만 물리 공간이 할당된다. 이러한 구조는 사용되지 않은 영역이 많은 대형 주소 공간(드문드문 사용되는 주소 공간(sparse address space)이라고도 부름)을 수용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;드문드문 사용되는 주소 공간(sparse address space): 프로그램의 주소 공간 중 실제로 사용되는 부분이 드문드문 흩어져 있는 상태를 말한다. 이는 주소 공간의 상당 부분이 사용되지 않고 비어 있음을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;세그먼트 지원을 위한 MMU 하드웨어 구조는 예상한 것과 같다. 이 예의 경우 3쌍의 베이스와 바운드 레지스터 집합이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf0Mrp/btsQ6c9Fe7t/zyC839GqvLZLFYQzyszqw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf0Mrp/btsQ6c9Fe7t/zyC839GqvLZLFYQzyszqw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf0Mrp/btsQ6c9Fe7t/zyC839GqvLZLFYQzyszqw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf0Mrp%2FbtsQ6c9Fe7t%2FzyC839GqvLZLFYQzyszqw0%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;369&quot; height=&quot;169&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 바운드 레지스터는 세그먼트의 크기를 저장한다. 그림에서 코드 세그먼트가 물리 주소 32 KB에 배치되고 크기는 2 KB이며, 힙 세그먼트가 34 KB에 배치되고 역시 크기는 2 KB라는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1QDTO/btsQ5iJYLF0/KKOP4AMEY9RBveSD7ojE4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1QDTO/btsQ5iJYLF0/KKOP4AMEY9RBveSD7ojE4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1QDTO/btsQ5iJYLF0/KKOP4AMEY9RBveSD7ojE4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1QDTO%2FbtsQ5iJYLF0%2FKKOP4AMEY9RBveSD7ojE4K%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;245&quot; height=&quot;698&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 공간을 사용하여 주소 변환을 살펴보겠다. 먼저, 가상 주소 100번지가 참조된다고 가정해 본다. 이 주소는 코드 세그먼트에 속한다. 참조가 발생하면, 하드웨어는 해당 세그먼트의 베이스 값에 가상 주소의 오프셋을 더한다. 이 경우, 100을 더하여 물리 주소는 100 + 32 KB로 계산되어 32868이 된다. 그 후, 하드웨어는 이 주소가 범위 내에 있는지 확인하고 (100은 2 KB보다 작으므로), 범위 내에 있다면 물리 메모리 주소 32868을 읽는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오프셋(offset): 세그먼트의 시작 주소로부터 특정 주소까지의 거리를 나타내는 값이다. 오프셋은 세그멘트 내에서의 상대적인 위치를 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음으로 가상 주소 4200번지를 힙에서 살펴보면, 힙의 베이스인 34 KB에 이를 더하면 물리 주소 39016을 얻게 된다. 그러나 이 주소는 올바른 물리 주소가 아니다. 먼저 힙 내에서의 오프셋, 즉 주소가 세그멘트의 시작으로부터 몇 번째 바이트인지를 확인해야 한다. 힙은 가상 주소 4 KB(4096)에서 시작하기 때문에 오프셋은 4200 - 4096으로 계산되어 104가 된다. 이 오프셋(104)을 힙의 베이스 레지스터의 물리 주소(34 KB)에 더하면 원하는 결과인 34920을 얻을 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;그러나 만약 잘못된 주소인 힙의 마지막을 벗어난 7 KB와 같은 주소에 접근하려고 한다면 어떻게 될까? 하드웨어는 이 주소가 범위를 벗어났다는 것을 감지하고 운영체제에 트랩을 발생시킨다. 운영체제는 문제의 프로세스를 종료시킬 가능성이 크다. 이렇게 잘못된 주소 접근으로 인해 발생하는 문제를 C 프로그래머들이 많이 겪는 유명한 용어의 기원을 알 수 있다: 세그멘트 위반(segment violation) 또는 세그멘트 폴트(segment fault).&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;세그멘트 위반(segment violation) 또는 세그멘트 폴트(segment fault)&lt;/b&gt;&lt;br /&gt;세그먼트 위반(segment violation) 또는 세그먼트 폴트(segment fault)는 프로그래밍에서 주소 접근 오류가 발생했을 때 나타나는 용어로 기원적으로는 주소 접근 오류가 발생했을 때, 해당 프로세스가 접근한 메모리 주소가 메모리 세그멘트의 범위를 벗어났을 때 발생한 것이다. 이러한 오류는 프로그램이 메모리를 잘못 사용하거나 액세스하려고 할 때 발생한다.&lt;br /&gt;세그멘트 위반 또는 세그멘트 폴트가 나타나면, 보통 운영체제는 프로그램이 메모리를 잘못 사용하는 오류를 신속하게 감지하여 프로세스나 시스템의 안정성을 유지하기 위해 해당 프로세스를 중단하거나 종료시킨다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랩(trap): 프로세스가 잘못된 메모리 접근이나 불법적인 명령어 실행 등의 예외 상황을 발생시켰을 때, CPU가 현재 실행 중인 프로세스를 중단하고 운영체제에게 제어권을 넘기는 것을 말한다. 트랩이 발생하면 운영체제는 적절한 예외 처리기를 실행하여 상황을 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세그멘테이션은 프로그램의 논리적 구조에 기반하여 메모리를 관리하므로, 프로그래머에게 직관적이고 유연한 메모리 모델을 제공한다. 또한 세그먼트 단위로 메모리 보호와 공유가 가능하므로 시스템의 안정성과 보안을 향상시킬 수 있다. 그러나 세그멘테이션은 외부 단편화(external fragmentation) 문제를 야기할 수 있으며, 세그먼트 테이블 관리에 오버헤드가 발생할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 단편화(external fragmentation): 메모리에 할당되지 않은 빈 공간들이 여러 곳에 산재해 있어, 실제로는 충분한 메모리가 있음에도 불구하고 연속적인 메모리 공간을 할당하지 못하는 현상을 말한다. 세그멘테이션에서는 가변 크기의 세그먼트를 할당하므로 외부 단편화가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현대의 대부분의 운영체제는 세그멘테이션보다는 페이징(paging) 기법을 사용하여 메모리를 관리한다. 페이징은 고정된 크기의 페이지 단위로 메모리를 나누어 관리하므로, 외부 단편화 문제를 해결할 수 있다. 그러나 일부 운영체제에서는 세그멘테이션과 페이징을 혼합하여 사용하기도 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;세그멘트 종류의 파악&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;하드웨어는 주소 변환을 위해 세그멘트 레지스터를 사용한다.&lt;span&gt; &lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;한 가지 일반적인 접근 방법은 가상 주소의 최상위 몇 비트를 사용하여 주소 공간을 여러 세그먼트로 나누는 것이다. 이 방법은 예를 들어 VAX/VMS 시스템에서 사용되었다. 위의 예에서는 3개의 세그먼트가 있으므로 주소 공간을 세그먼트로 나누기 위해서는 2비트가 필요하다. 따라서 세그먼트를 표시하기 위해 가상 주소의 최상위 2비트를 사용하는 경우, 가상 주소의 형태는 다음과 같을 것이다.&lt;/span&gt;&lt;/span&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;336&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QTxQC/btsQ8ThoKic/RAMq5O0FcJCtyKvFJbw351/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QTxQC/btsQ8ThoKic/RAMq5O0FcJCtyKvFJbw351/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QTxQC/btsQ8ThoKic/RAMq5O0FcJCtyKvFJbw351/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQTxQC%2FbtsQ8ThoKic%2FRAMq5O0FcJCtyKvFJbw351%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;336&quot; height=&quot;107&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;예를 들어, 최상위 2비트가 00이면 하드웨어는 가상 주소가 코드 세그먼트를 가리킨다는 것을 인식하고, 이에 따라 코드 세그먼트의 베이스와 바운드 쌍을 활용하여 주소를 정확한 물리 메모리 위치로 재배치한다. 최상위 2비트가 01이면, 하드웨어는 주소가 힙 세그멘트를 가리킨다는 것을 인지하고, 힙의 베이스와 바운드를 사용하여 주소를 변환한다. 이해를 돕기 위해 이전에 언급한 힙에 해당하는 가상 주소인 4200을 변환해 보면 가상 주소 4200에 대한 이진 표현은 다음과 같다.&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;317&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPlRyV/btsQ65JMb0N/z1ayHYq0W7E3DbKgIvqHLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPlRyV/btsQ65JMb0N/z1ayHYq0W7E3DbKgIvqHLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPlRyV/btsQ65JMb0N/z1ayHYq0W7E3DbKgIvqHLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPlRyV%2FbtsQ65JMb0N%2Fz1ayHYq0W7E3DbKgIvqHLK%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;317&quot; height=&quot;81&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;그림에서 볼 수 있듯이, 최상위 2비트 (01)는 하드웨어에게 참조하는 세그먼트의 종류를 알려준다. 그리고 하위 12비트는 해당 세그먼트 내의 오프셋을 나타낸다. 예를 들어, 이진 형식으로 표현된 주소 0000 0110 1000은 16진수로는 0x068 또는 10진수로는 104입니다. 하드웨어는 세그멘트 레지스터를 이해하기 위해 처음 2비트를 사용하고, 그 다음 12비트를 세그멘트 오프셋으로 취한다. 이 오프셋에 베이스 레지스터 값을 더하여 하드웨어는 최종적인 물리 주소를 계산한다. 또한, 오프셋을 사용하면 바운드 검사도 쉽게 수행할 수 있습니다. 바운드를 넘어선 오프셋인지를 검사하기만 하면 된다. 그렇지 않으면 주소가 잘못된 것이다. 만약 베이스와 바운드 쌍을 배열 형태로 저장한다면 (세그멘트당 하나의 항목), 원하는 물리 주소를 얻기 위해 다음과 같은 과정을 수행하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1760403338876&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// get top 2 bits of 14-bit VA
Segment = (VirtualAddress &amp;amp; SEG_MASK) &amp;gt;&amp;gt; SEG_SHIFT
// now get offset
Offset = VirtualAddress &amp;amp; OFFSET_MASK
if (Offset &amp;gt;= Bounds[Segment])
	RaiseException(PROTECTION_FAULT)
else
	PhysAddr = Base[Segment] + Offset
	Register = AccessMemory(PhysAddr)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SEG_MASK: 가상 주소에서 세그멘트 번호를 추출하기 위한 비트 마스크다. 이 마스크를 가상 주소와 AND 연산하면 세그멘트 번호만 남게 된다.&lt;/li&gt;
&lt;li&gt;SEG_SHIFT: 세그먼트 번호를 오른쪽으로 시프트 하여 배열 인덱스로 사용하기 위한 시프트 양이다. 이 값은 세그먼트 번호를 나타내는 비트 수와 같다.&lt;/li&gt;
&lt;li&gt;OFFSET_MASK: 가상 주소에서 세그멘트 내 오프셋을 추출하기 위한 비트 마스크다. 이 마스크를 가상 주소와 AND 연산하면 오프셋만 남게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 현재 예를 기준으로 위 코드에서 사용된 상수 값들을 설정할 수 있다. SEG_MASK는 0x3000, SEG_SHIFT는 12, 그리고 OFFSET_MASK는 0 xFFF로 지정된다. 세그먼트 종류를 나타내는 데 최상위 2비트를 사용하고, 주소 공간에는 코드, 힙, 스택 세그먼트만 존재하기 때문에 지정 가능한 세그멘트 하나가 미사용으로 남게 된다. 즉, 전체 주소 공간의 1/4은 사용이 불가능하다. 이 문제를 해결하기 위해 일부 시스템은 코드와 힙을 하나의 세그멘트에 저장하고 세그멘트 선택을 위해 1비트만 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 주소의 세그먼트를 하드웨어적으로 파악하는 다른 방법들도 있다. 묵시적(implicit) 접근 방식에서는 주소가 어떻게 형성되는지를 관찰하여 세그먼트를 결정한다. 예를 들어, 주소가 프로그램 카운터에서 생성된다면 해당 주소는 코드 세그멘트 내에 있을 것이다. 주소가 스택 또는 베이스 포인터에 의해 생성된다면 주소는 스택 세그멘트 내에 있을 것이다. 다른 주소는 모두 힙에 위치하고 있어야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;스택&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택은 다른 세그먼트들과는 다르게 확장 방향이 반대라는 중요한 차이가 있다. &lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;이에 따라 다른 방식의 주소 변환이 필요하다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;첫 번째, 간단한 하드웨어가 추가로 필요하다. 베이스와 바운드 값뿐만 아니라 하드웨어는 세그먼트가 어느 방향으로 확장하는지도 알아야 한다. 예를 들어, 하나의 비트를 사용하여 주소가 양의 방향으로 확장되는 경우에는 1로 설정하고, 음의 방향으로 확장되는 경우에는 0으로 설정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방향 비트(Direction Bit): 세그먼트가 양의 방향으로 확장되는지 음의 방향으로 확장되는지를 나타내는 비트, 이 비트를 통해 스택과 같이 음의 방향으로 확장되는 세그멘트를 지원할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;하드웨어는 세그먼트가 반대 방향으로 확장될 수 있다는 것을 알기 때문에, 이러한 가상 주소에 대해서는 다른 방식으로 변환한다.&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 예에서 가상 주소 15 KB에 접근하려고 한다고 가정할 때, 이 주소는 물리 주소 27 KB에 매핑되어야 한다. 이 가상 주소를 이진 형태로 변환하면 11 1100 0000 0000 (16진수 0x3 C00)이 된다. 하드웨어는 상위 2비트 (11)를 사용하여 세그먼트를 지정한다. 이를 고려하면 3 KB의 오프셋이 남는다. 올바른 음수 오프셋을 얻기 위해 3 KB에서 세그먼트 최대 크기를 빼야 한다. 이 예에서는 세그먼트의 최대 크기가 4 KB이므로 올바른 오프셋은 3 KB에서 4 KB를 뺀 -1 KB다. 이 음수 오프셋 (-1 KB)을 베이스 (28 KB)에 더하면 올바른 물리 주소 27 KB를 얻게 된다. 바운드 검사는 음수 오프셋의 절댓값이 세그먼트의 크기보다 작다는 것을 확인하여 계산할 수 있다.&lt;/span&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;552&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UAfkr/btsQ7v9EeAr/LEGYtkvklp6gvziyLcwEQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UAfkr/btsQ7v9EeAr/LEGYtkvklp6gvziyLcwEQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UAfkr/btsQ7v9EeAr/LEGYtkvklp6gvziyLcwEQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUAfkr%2FbtsQ7v9EeAr%2FLEGYtkvklp6gvziyLcwEQk%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;552&quot; height=&quot;193&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;193&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;공유 지원&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;세그멘테이션 기법이 발전함에 따라 시스템 설계자들은 간단한 하드웨어 지원으로 새로운 종류의 효율성을 성취할 수 있다는 것을 깨달았다. 구체적으로, 메모리를 절약하기 위해 때로는 주소 공간들 간에 특정 메모리 세그먼트를 공유하는 것이 유용하다. 특히, 코드 공유가 일반적이며, 현재 시스템에서도 널리 사용되고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;이러한 공유를 지원하기 위해, 하드웨어에 보호 비트(protection bit)의 추가가 필요하다. 각 세그먼트에 보호 비트를 추가하여 세그먼트를 읽거나 쓸 수 있는지, 혹은 세그멘트의 코드를 실행할 수 있는지를 나타낸다. 코드 세그멘트를 읽기 전용으로 설정하면 주소 공간의 독립성을 유지하면서도, 여러 프로세스가 주소 공간의 일부를 공유할 수 있다. 각 프로세스는 여전히 자신의 전용 메모리를 사용한다고 생각하지만, 운영체제는 이러한 변경이 불가능하도록 설정된 메모리 영역을 비밀리에 공유하여 그러한 환상을 유지한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보호 비트(Protection Bit): 각 세그먼트에 할당된 비트로, 세그멘트에 대한 접근 권한을 나타낸다. 일반적으로 읽기, 쓰기, 실행 권한을 나타내는 비트들로 구성된다. 보호 비트를 통해 세그멘트 단위로 메모리 보호를 구현할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEbCiS/btsQ87zMNQ2/Rbiu0bZh7kEB3k69NaOPk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEbCiS/btsQ87zMNQ2/Rbiu0bZh7kEB3k69NaOPk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEbCiS/btsQ87zMNQ2/Rbiu0bZh7kEB3k69NaOPk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEbCiS%2FbtsQ87zMNQ2%2FRbiu0bZh7kEB3k69NaOPk0%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;535&quot; height=&quot;193&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;193&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어 (및 운영체제)가 유지하는 부가 정보의 예시가 그림에 있다. 코드 세그먼트는 읽기 및 실행으로 설정되어, 같은 물리 세그먼트가 여러 가상 주소 공간에 매핑될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보호 비트를 사용하면 앞서 언급한 하드웨어 알고리즘이 수정되어야 한다. 가상 주소가 범위 내에 있는지 확인하는 것뿐만 아니라 특정 액세스가 허용되는지를 확인해야 한다. 사용자 프로세스가 읽기 전용 페이지에 쓰기를 시도하거나 실행 불가능한 페이지에서 실행하려고 할 때 하드웨어는 예외를 발생시켜서 운영체제가 위반 프로세스를 처리할 수 있도록 해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;소단위 대 대단위 세그멘테이션&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 예제의 대부분은 지금까지 소수의 세그멘트 (예: 코드, 스택, 힙)만을 지원하는 시스템에 주로 초점을 맞추고 있다. 이러한 세그멘테이션은 대단위(coarse-grained)로 간주할 수 있다. 이는 주소 공간을 비교적 큰 단위의 공간으로 분할하기 때문이다. 그러나 일부 초기 시스템 (예: Multics)은 주소 공간을 작은 크기의 공간으로 잘게 나누는 것이 허용되어 소단위(fine-grained) 세그멘테이션이라고도 한다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;많은 수의 세그먼트를 지원하기 위해서는 여러 세그먼트의 정보를 메모리에 저장할 수 있는 세그먼트 테이블과 같은 하드웨어가 필요하다. 세그멘트 테이블을 이용하면 매우 많은 세그먼트를 손쉽게 생성하고 융통성 있게 세그멘트를 사용할 수 있다. 예를 들어, Burroughs B5000과 같은 초창기 시스템은 수천 개의 세그멘트를 지원했고, 컴파일러가 코드나 데이터를 여러 세그멘트로 분할할 경우 운영체제와 하드웨어가 이를 지원했다. 당시의 생각은 소단위 세그멘트로 관리하는 것이 운영체제가 사용 중인 세그멘트와 미사용인 세그멘트를 구분하여 메인 메모리를 더 효율적으로 활용할 수 있다는 것이었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세그먼트 테이블(Segment Table): 각 세그멘트의 정보를 저장하는 자료구조다. 세그멘트 테이블은 세그멘트의 베이스 주소, 크기, 보호 비트 등을 포함한다. 세그멘트 테이블을 통해 많은 수의 세그멘트를 효율적으로 관리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;운영체제의 지원&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템이 각 주소 공간 구성 요소를 별도로 물리 메모리에 재배치하기 때문에 전체 주소 공간이 하나의 베이스-바운드 쌍을 가지는 간단한 방식에 비해 물리 메모리를 효율적으로 절약할 수 있다. 특히, 스택과 힙 사이의 사용하지 않는 공간에 물리 메모리를 할당할 필요가 없어져서 같은 크기의 물리 메모리에 더 많은 주소 공간을 탑재할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 세그멘테이션은 새로운 많은 문제를 제기한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 문제는 오래된 문제다. 문맥 교환 시 운영체제가 수행해야 하는 작업은 무엇일까? 바로 세그멘트 레지스터의 저장과 복원이다. 각 프로세스는 자신의 가상 주소 공간을 가지며, 운영체제는 프로세스가 다시 실행하기 전에 레지스터들을 올바르게 설정해야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 번째로, 더욱 중요한 문제는 미사용 중인 물리 메모리 공간의 관리다. 새로운 주소 공간이 생성되면 운영체제는 이 공간의 세그멘트를 위한 비어있는 물리 메모리 영역을 찾을 수 있어야 한다. 이전에는 각 주소 공간의 크기가 동일하다고 가정했지만, 이제는 프로세스가 많은 세그멘트를 가질 수 있고, 각 세그멘트의 크기도 다를 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 발생할 수 있는 문제는 물리 메모리가 빠르게 작은 크기의 빈 공간들로 채워진다는 것이다. 이 작은 빈 공간들은 새로이 생겨나는 세그먼트에 할당하기도 힘들 뿐만 아니라 기존 세그멘트를 확장하는 데에도 도움이 되지 않는다. 이러한 문제를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;외부 단편화(external fragmentation)&lt;/b&gt;&lt;/span&gt;라고 부른다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 단편화(External Fragmentation): 메모리에 할당되지 않은 작은 빈 공간들이 여러 곳에 산재해 있는 현상을 말한다. 이러한 작은 빈 공간들은 새로운 메모리 할당 요청을 만족시키기에는 충분히 크지 않아서 메모리 낭비를 초래한다. 세그멘테이션에서는 가변 크기의 세그멘트를 할당하므로 외부 단편화가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ytb9h/btsQ8jVffMr/rg6CmoRErTsuoYvoplgKY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ytb9h/btsQ8jVffMr/rg6CmoRErTsuoYvoplgKY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ytb9h/btsQ8jVffMr/rg6CmoRErTsuoYvoplgKY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fytb9h%2FbtsQ8jVffMr%2Frg6CmoRErTsuoYvoplgKY1%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;506&quot; height=&quot;412&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예에서 새로운 프로세스가 생성되어 20 KB를 할당하려고 한다. 위 예에서 24 KB의 빈 공간이 존재하지만 하나의 연속된 공간이 아닌 세 개의 청크(chunk)로 나누어져 있다. 운영체제는 20 KB의 요청을 충족시킬 수 없다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제의 해결책 중 하나는 기존의 세그멘트를 정리하여 물리 메모리를 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;압축(compact)&lt;/span&gt;&lt;/b&gt;하는 것이다. 예를 들어, 운영체제는 현재 실행 중인 프로세스를 중단하고, 그들의 데이터를 하나의 연속된 공간에 복사한 후, 세그멘트 레지스터가 새로운 물리 메모리 위치를 가리키게 하여, 자신이 작업할 큰 빈 공간을 확보한다. 이렇게 함으로써 운영체제는 새로운 할당 요청을 충족시킬 수 있다. 그러나 세그멘트 복사는 메모리에 부하가 큰 연산이고 일반적으로 상당량의 프로세서 시간을 사용하기 때문에 압축은 비용이 많이 든다. 압축 작업 후의 물리 메모리는 그림 19.6(오른쪽)에 나와 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;압축(Compaction): 메모리에 할당된 세그먼트들을 한쪽으로 모아서 연속적인 빈 공간을 만드는 작업이다. 압축을 통해 외부 단편화를 해소할 수 있지만, 모든 세그먼트를 이동시켜야 하므로 오버헤드가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;간단한 방법은 빈 공간 리스트를 관리하는 알고리즘을 사용하는 것이다. 빈 공간 관리 알고리즘은 할당 가능한 메모리 영역을 리스트 형태로 유지한다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;최적 적합(best-fit), 최악 적합(worst-fit), 최초 적합(first-fit), 버디 알고리즘(buddy algorithm)&lt;/b&gt; &lt;/span&gt;등 여러 가지 방식이 있다. 이 중 최적 적합은 빈 공간 리스트에서 요청된 크기와 가장 비슷한 크기의 공간을 할당한다. 알고리즘이 아무리 정교하게 동작한다 해도 외부 단편화는 여전히 존재하며, 좋은 알고리즘은 외부 단편화를 최소화하는 것이 목표다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/199</guid>
      <comments>https://gowoong.tistory.com/199#entry199comment</comments>
      <pubDate>Tue, 14 Oct 2025 10:12:30 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 5주차 - 메모리 가상화 1</title>
      <link>https://gowoong.tistory.com/198</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;초기 컴퓨터 시스템의 운영체제는 사용자에게 그다지 많은 기능을 제공하지 않았다. 운영체제는 단지 물리주소 0번지부터 메모리에 위치했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L8gaW/btsQVFKbp0q/WaQUypPyrHXl08HhOMXJek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L8gaW/btsQVFKbp0q/WaQUypPyrHXl08HhOMXJek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L8gaW/btsQVFKbp0q/WaQUypPyrHXl08HhOMXJek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL8gaW%2FbtsQVFKbp0q%2FWaQUypPyrHXl08HhOMXJek%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;400&quot; height=&quot;355&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 프로세스가 물리 메모리의 특정 영역을 독점적으로 사용하고, 나머지 공간은 다른 용도로 사용했다. 초기 시스템에서는 특별한 가상화 기술이 거의 없었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;멀티프로그래밍과 시분할&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 실제로 하나지만 , 여러 프로세스가 마치 자신만의 CPU를 가진 것처럼 느끼게 하는 것이 목표다. 이를 통해 다수의 프로세스가 동시에 실행되는 것 같은 환상을 만들어 냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시분할 시스템은 컴퓨터 가격이 높았던 시절 더 많은 사람이 동시에 사용할 수 있게 하고 자원 활용도를 극대화할 방법으로 개발되었다. 이는 여러 사용자가 컴퓨터를 동시에 쓸 수 있게 함으로써, 기존의 일괄 처리 방식에 비해 결과를 더 빨리 받아볼 수 있게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시분할을 구현하는 한 가지 방법은 프로세스를 아주 짧은 시간 동안만 실행시키되, 그 순간에는 메모리 전체에 대한 접근 권한을 부여하는 것이었다. 그 후 프로세스를 중단하고 그때의 모든 상태를 디스크 같은 곳에 저장한 뒤, 다른 프로세스의 정보를 불러와 또 잠깐 실행하는 식이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이러한 방식의 문제가 있었는데, 레지스터 값을 저장하고 복원하는 것은 빨랐지만, 메모리 전체를 디스크에 옮기는 것은 너무 느렸다. 이러한 문제를 해결하기 위해, 프로세스를 전환할 때에도 메모리에 그대로 둔 채로 시분할을 구현하는 것이 메모리 가상화의 주된 목표가 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsaiI4/btsQSG5lOte/cd5eUnE3IDwcHyzGoKgrmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsaiI4/btsQSG5lOte/cd5eUnE3IDwcHyzGoKgrmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsaiI4/btsQSG5lOte/cd5eUnE3IDwcHyzGoKgrmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsaiI4%2FbtsQSG5lOte%2Fcd5eUnE3IDwcHyzGoKgrmK%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;400&quot; height=&quot;450&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 보면 프로세스 A, B, C 세 개가 있다. 512KB짜리 물리 메모리에서 각자의 영역을 할당받은 것이다. A는 현재 실행 중이고, B와 C는 실행 대기 중이다. 중요한 건 프로세스들이 메모리를 공유하지 않고 각자의 공간을 가진다는 점이다. 이는 프로세스 간 메모리 침범을 방지하기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시분할 시스템에서는 여러 프로세스가 동시에 돌아가므로, 이런 메모리 보호가 매우 중요하다. 각 프로세스가 서로의 데이터나 코드를 함부로 건드리지 못하게 막아야 하는 것이다. 따라서 운영체제는 프로세스별로 할당된 메모리 영역을 엄격히 관리하고 지켜줘야 한다. 그래야 시분할 시스템이 안전하고 안정적으로 작동할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;주소 공간&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 공간(address space)는 실행 중인 프로그램이 메모리가 어떻게 구성되어 있다고 가정하는지를 나타낸다. 운영체제의 메모리 가상화 방식을 이해하는 데 있어 핵심적인 개념이다. 주소 공간은 프로그램이 사용하는 모든 메모리 영역을 포함하며, 운영체제는 이를 효율적이고 안전하게 관리할 책임이 있다. 따라서 주소 공간에 대한 이해는 운영체제가 메모리를 어떻게 다루는지 파악하는 데 있어 필수다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;423&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rXMGX/btsQVJlsiBT/r8nrf7zLivJ3Zp93Nc8kYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rXMGX/btsQVJlsiBT/r8nrf7zLivJ3Zp93Nc8kYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rXMGX/btsQVJlsiBT/r8nrf7zLivJ3Zp93Nc8kYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrXMGX%2FbtsQVJlsiBT%2Fr8nrf7zLivJ3Zp93Nc8kYk%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;400&quot; height=&quot;423&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;423&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 공간에는 코드, 스택, 힙 등 프로그램을 실행하는 데 필요한 모든 상태 정보가 담겨 있다.&lt;/p&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;스택(Stack) 영역:&lt;/h3&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;메모리의 높은 주소에서 낮은 주소 방향으로 할당된다.&lt;/li&gt;
&lt;li&gt;재귀 호출이 너무 깊어지거나 지역 변수가 너무 많으면 스택 오버플로우 오류가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;힙(Heap) 영역:&lt;/h3&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;주로 참조형 데이터(예: 객체)가 저장된다.&lt;/li&gt;
&lt;li&gt;메모리의 낮은 주소에서 높은 주소 방향으로 할당된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;데이터(Data) 영역:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전역 변수나 정적(static) 변수 등 프로그램에서 사용하는 데이터가 저장되는 영역.&lt;/li&gt;
&lt;li&gt;전역/정적 변수를 참조하는 코드가 있다면, 컴파일 후 이 영역을 참조하게 된다.&lt;/li&gt;
&lt;li&gt;프로그램이 시작할 때 할당되고, 종료되면 해제된다.&lt;/li&gt;
&lt;li&gt;초기화되지 않은 변수는 그림에는 없지만 BSS 영역에 따로 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;BSS (Block Started by Symbol) 영역&lt;br /&gt;- 초기화되지 않은 전역 변수와 정적 변수가 저장되는 메모리 영역이다. BSS 영역의 변수들은 프로그램 시작 시 자동으로 0이나 NULL로 초기화된다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;텍스트(Text) 또는 코드(Code) 영역:&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU가 실행할 수 있는 기계어 코드가 저장된 영역.&lt;/li&gt;
&lt;li&gt;프로그램 코드는 변경되면 안 되므로 읽기 전용(read-only)으로 보호된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 정적이므로 실행 중에 추가 메모리를 요구하지 않는다. 따라서 메모리에 쉽게 적재할 수 있어 주로 주소 공간의 상단에 위치한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 아래로는 프로그램 실행에 따라 크기가 변할 수 있는 힙과 스택이 자리한다. 힙은 위쪽에, 스택은 아래쪽에 배치되는데, 서로 반대 방향으로 확장될 수 있어야 하기 때문이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터 영역이 0~16KB 주소를 사용하는 것처럼 보이지만, 실제 물리 메모리에서 해당 주소를 쓰는 건 아니다. 메모리 가상화 기법을 통해 임의의 물리 주소에 맵핑되기 때문이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 운영체제가 가상 주소와 물리 주소의 매핑을 관리하는 것을 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;메모리 가상화&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;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;가상 메모리 시스템의 목표&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상 메모리의 세가지 주요 목표는 다음과 같다.&lt;/p&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;투명성(Transparency)&lt;/h3&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;운영체제는 이를 위해 각 프로세스에게 독립적인 가상 주소 공간을 제공한다. 프로세스는 이 가상공간 내에서 자유롭게 메모리를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;효율성(Efficiency)&lt;/h3&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;이를 위해 페이지 테이블(page table)이나 TLB(Translation Lookaside Buffer)와 같은 하드웨어 지원 메커니즘이 필수적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;TLB(Translation Lookaside Buffer)&lt;br /&gt;- TLB는 가장 최근에 사용된 가상-물리 주소 매핑 정보를 캐시로 저장하는 하드웨어 장치로, 주소 변환이 필요할 때 페이지 테이블을 탐색하기 전에 먼저 TLB를 확인함으로써, 주소 변환 속도를 크게 향상시킬 수 있다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;보호(Protection)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 가상화는 각 프로세스를 다른 프로세스와 운영체제로부터 보호하는 데 있어 필수적인 역할을 한다.&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가상 메모리 시스템은 각 페이지나 세그먼트 별로 읽기(read), 쓰기(write), 실행(execute) 권한을 설정할 수 있다. 이를 통해 잘못된 메모리 접근이나 악의적인 행위를 차단할 수 있다.&lt;/li&gt;
&lt;li&gt;이런 보호 기능을 활용하면 프로세스를 서로 완벽히 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;격리(isolation)&lt;/b&gt;&lt;/span&gt;시킬 수 있다. 한 프로세스의 오류가 다른 프로세스나 시스템 전체에 영향을 미치지 않도록 하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Unix 시스템에서 메모리를 관리하기 위한 API에 대해 알아보겠다.&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;메모리 공간의 종류&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 프로그램이 실행될 때에는 크게 두가지 유형의 메모리 공간이 할당된다.&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;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스택 메모리 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1759128664555&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void func() {
    int x;  // 스택에 정수형 변수 선언
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;힙 메모리 예시&lt;/h4&gt;
&lt;pre id=&quot;code_1759128673551&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void func() {
    int *x = (int*) malloc(sizeof(int));
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;malloc이 반환하는 것은 새로 할당된 공간의 주소이다. 성공 시에는 해당 주소를, 실패 시에는 NULL을 돌려준다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;malloc() 함수&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;malloc()은 힙에서 메모리를 동적으로 할당하는 함수다. 사용법은 매우 간단하다, 필요한 메모리의 크기(바이트 단위)를 인자로 전달하면 된다. 할당에 성공하면 새로 할당된 메모리 블록의 시작 주소를 가리키는 포인터를 반환하고, 실패하면 NULL을 반환한다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell2&quot; class=&quot;arduino&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;void* malloc(size_t size);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;는 할당받고자 하는 메모리의 크기를 바이트 단위로 나타낸 값이다. 이 크기를 정확히 지정하기 위해 보통&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sizeof()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;연산자를 사용한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;C에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sizeof()&lt;/span&gt;는 컴파일 시점에 평가되는 연산자로, 인자로 전달된 데이터 타입이나 변수의 크기를 바이트 단위로 반환한다. 따라서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sizeof()&lt;/span&gt;는 함수 호출이 아니라 연산자로 취급된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc(sizeof(double))&lt;/span&gt;과 같이 호출하면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sizeof(double)&lt;/span&gt;은 컴파일 시점에 상수 값 8(64비트 시스템 기준)으로 대체되어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc()&lt;/span&gt;에 전달된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sizeof()&lt;/span&gt;를 변수에 적용할 때는 주의해야 한다. 다음 코드를 보겠다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell3&quot; class=&quot;cpp&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;int *x = malloc(10 * sizeof(int));
printf(&quot;%d\n&quot;, sizeof(x));
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 줄에서는 정수 10개를 저장할 수 있는 배열을 위한 메모리를 할당했다. 그런데 두 번째 줄에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sizeof(x)&lt;/span&gt;를 출력해 보면, 4(32비트 시스템)나 8(64비트 시스템)이 나온다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왜 그럴까? 여기서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sizeof()&lt;/span&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;가 가리키는 메모리 블록의 크기가 아니라, 포인터 변수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;자체의 크기를 반환하기 때문이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면 다음과 같이 정적 배열에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sizeof()&lt;/span&gt;를 적용하면 기대한 대로 동작합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell4&quot; class=&quot;cpp&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;int x[10];
printf(&quot;%d\n&quot;, sizeof(x));  // 40 출력 (32비트 시스템 기준)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문자열을 다룰 때도 조심해야 한다. 문자열을 저장할 공간을 동적 할당할 때는 보통&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc(strlen(s)&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;1)&lt;/span&gt;과 같은 코드를 사용한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;strlen()&lt;/span&gt;으로 문자열의 길이를 구한 뒤, 마지막 NULL 문자를 저장할 공간까지 확보하는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한 가지 더 눈여겨볼 점은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc()&lt;/span&gt;의 반환 타입이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;void*&lt;/b&gt;&lt;/span&gt;라는 것이다. 이는 C 언어의 특징을 잘 보여주는데,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc()&lt;/span&gt;은 그저 메모리 블록의 주소만 반환할 뿐, 그 공간을 어떤 타입의 데이터를 저장하는 데 사용할지는 전적으로 프로그래머에게 맡기는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc()&lt;/span&gt;이 반환한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;void*&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;값은 적절한 타입의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;포인터로 캐스팅&lt;/b&gt;&lt;/span&gt;해서 사용해야 한다. 앞선 예제에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;(int*)&lt;/span&gt;로 캐스팅한 것처럼 말이다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;free() 함수&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메모리 할당보다 어려운 문제는 할당된 메모리를 언제, 어떻게 해제할 것인가 하는 점이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;힙에서 동적 할당한 메모리가 더 이상 필요 없어졌을 때, 이를 시스템에 반환하는 작업은 전적으로 프로그래머의 책임이다. 이를 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;free()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를 사용한다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #fefefe; color: #080808;&quot;&gt;
&lt;pre id=&quot;codecell5&quot; class=&quot;cpp&quot; style=&quot;background-color: #f3f4f5; color: #222832;&quot;&gt;&lt;code&gt;int *x = malloc(10 * sizeof(int));
// ...
free(x);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;free()&lt;/span&gt;는 인자로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc()&lt;/span&gt;이 반환한 포인터 값 하나만 받는다. 해제할 메모리 블록의 크기는 전달하지 않는데, 메모리 할당 라이브러리가 내부적으로 관리하고 있기 때문이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메모리 누수(memory leak)를 방지하려면 불필요해진 메모리를 빠짐없이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;free()&lt;/span&gt;로 해제해야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반대로 이미 해제된 메모리를 또 다시 해제하려 하면 언디파인드 비헤이비어(undefined behavior)를 초래할 수 있으니, 이 점도 주의해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;운영체제의 지원&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;C 표준 라이브러리에서 제공하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc()&lt;/span&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;free()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수는 각각 메모리 할당과 해제를 담당한다. 이들은 시스템 콜이 아닌 라이브러리 함수로, 프로세스의 가상 주소 공간 내에서 힙 메모리를 관리하는 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 라이브러리 자체는 운영체제에게 메모리를 요청하고 반환하는 시스템 콜을 기반으로 동작한다. 대표적인 예가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;brk&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;시스템 콜인데, 이는 프로그램의 &amp;lsquo;브레이크(break)&amp;rsquo; 위치를 조정하여 힙의 크기를 늘리거나 줄이는 기능을 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 브레이크란 힙의 끝을 가리키는 포인터를 말한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;brk&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;시스템 콜은 새로운 브레이크 주소값을 인자로 받아, 그에 맞춰 힙 영역을 조정하는 것이다. 비슷한 역할을 하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sbrk&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수도 있는데, 이는 브레이크 위치를 얼마나 옮길지 그 증감량을 인자로 받는다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 중요한 점은 프로그래머가 직접&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;brk&lt;/span&gt;나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;sbrk&lt;/span&gt;를 호출해서는 안 된다는 것이다. 이들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc()&lt;/span&gt;이나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;free()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 메모리 할당 라이브러리 내부에서만 사용되어야 한다. 직접 호출할 경우 예측 불가능한 문제가 발생할 수 있으므로, 반드시 표준 라이브러리 함수를 통해 메모리를 다뤄야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또 다른 방법으로는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mmap()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수를 사용하여 운영체제로부터 메모리를 받아올 수도 있다. 적절한 인자를 전달하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;mmap()&lt;/span&gt;은 파일과 연결되지 않은 익명(anonymous) 메모리 영역을 할당해 주는데, 이 공간은 힙과 유사하게 다룰 수 있다. 특히 대용량 메모리가 필요할 때 유용하게 활용할 수 있는 방법이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;malloc(size) 호출 라이브러리 내부 장부에서 남는 블록이 있나 확인 &amp;rarr; 있으면 바로 반환 없으면 OS에 추가 요청 작은/중간 크기: brk/sbrk로 힙을 늘림 큰 크기: mmap으로 별도 구역을 받음 free(p) &amp;rarr; 라이브러리가 장부에 &amp;ldquo;빈 블록&amp;rdquo;으로 표시 아주 큰 블록이면 munmap으로 OS에 바로 돌려주기도 함&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;기타 함수들&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메모리 관리와 관련하여 알아두면 좋은 추가 함수들이 몇 가지 더 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;calloc()&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;malloc()&lt;/span&gt;과 비슷하지만, 할당된 메모리 블록을 모두 0으로 초기화한 뒤 반환한다.&lt;/li&gt;
&lt;li&gt;초기화를 빠뜨리는 실수를 방지하고자 할 때 유용하다.&lt;/li&gt;
&lt;li&gt;특히 동적 할당한 배열을 0으로 초기화할 때 많이 쓰이며, &amp;lsquo;초기화되지 않은 읽기&amp;rsquo;와 같은 미묘한 버그를 예방하는 데 도움이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;realloc()&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;malloc()&lt;/span&gt;이나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;calloc()&lt;/span&gt;으로 할당한 메모리 블록의 크기를 조정할 때 사용한다.&lt;/li&gt;
&lt;li&gt;동적 할당한 배열의 크기를 늘리거나 줄일 때 유용하다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;realloc()&lt;/span&gt;을 호출하면 기존 블록보다 큰 새 메모리 블록을 할당하고, 이전 블록의 내용을 모두 복사해 온다. 그리고 새 블록의 주소를 반환한.&lt;/li&gt;
&lt;li&gt;이를 통해 메모리를 재사용하면서도 블록 크기를 유연하게 변경할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/198</guid>
      <comments>https://gowoong.tistory.com/198#entry198comment</comments>
      <pubDate>Mon, 29 Sep 2025 16:10:26 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 4주차 - 스케줄링 2 : Part.2 - 비례 배분</title>
      <link>https://gowoong.tistory.com/197</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;비례 배분(Proportional Share) &lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;혹은&lt;/span&gt;&lt;b&gt; 공정 배분(Fair Share)&lt;/b&gt;&lt;/span&gt; 스케줄링은 기존의 스케줄링 알고리즘과는 다른 목적을 가지고 있다. 이전의 알고리즘들이 반환 시간(turnaround time)이나 응답 시간(response time)을 최적화하는 데 중점을 뒀다면, 비례 배분 스케줄링은 각 작업(job)이나 프로세스에게 CPU 시간의 일정 비율을 보장하는 것을 목표로 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비례 배분 스케줄링의 대표적인 예시로는 Waldspurger와 Weihl이 제안한 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;추첨 스케줄링(Lottery Scheduling)&lt;/b&gt;&lt;/span&gt;이 있다. 이 아이디어 자체는 상당히 오래되었는데, 그 기본 개념은 매우 간단하다. 다음에 실행할 프로세스를 추첨을 통해 랜덤 하게 선택하되, CPU 시간을 더 많이 할당받아야 할 프로세스에게는 추첨에 당첨될 기회를 더 많이 준다는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 두 개의 프로세스 A와 B가 시스템에 존재하고 A는 CPU 시간의 80%를, B는 20%를 받기로 했다고 가정해 본다. 추첨 스케줄링에서는 전체 추첨권(ticket) 100장 중 A에게 80장을, B에게 20장을 부여할 것이다. 그리고 매 스케줄링 시점마다 이 추첨권 중 하나를 무작위로 뽑아, 뽑힌 추첨권을 가진 프로세스를 다음에 실행한다. 이렇게 하면 장기적으로 봤을 때 A와 B가 각각 CPU 시간의 80%, 20%를 할당받을 수 있게 되는 것이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;기본 개념 : 추첨권이 당신의 지분이다.&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추첨권(티켓)이라는 기본적인 개념이 추첨 스케줄링의 근간을 이룬다. 추첨권은 경품권의 개념과 유사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;예를 들어, 두 개의 프로세스 A와 B가 시스템에 존재하고 A는 CPU 시간의 75%를, B는 25%를 받기로 했다고 가정해 본다. 추첨 스케줄링에서는 전체 추첨권(ticket) 100장 중 A에게 75장을, B에게 25장을 부여할 것이다. 그리고 매 스케줄링 시점마다 이 추첨권 중 하나를 무작위로 뽑아, 뽑힌 추첨권을 가진 프로세스를 다음에 실행한다. 이렇게 하면 장기적으로 봤을 때 A와 B가 각각 CPU 시간의 75%, 25%를 할당받을 수 있게 되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;팁 : 무작위성의 이용&lt;/b&gt;&lt;br /&gt;무작위성비례 배분 스케줄링에서 사용되는 추첨권 기반의 무작위 선택 방식은 여러 가지 장점을 가지고 있다. 결정을 내려야 할 때, 이러한 무작위성은 강력하면서도 간단한 해법이 될 수 있다. 전통적인 결정 방식과 비교했을 때, 무작위 방식은 다음과 같은 세 가지 이점을 제공한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;특수한 상황에 대한 적응력:&lt;/b&gt; 무작위 선택은 특이 케이스에 잘 대응한다. 예를 들어, 가상 메모리 시스템에서 많이 사용되는 LRU(Least Recently Used) 페이지 교체 알고리즘은 반복되는 순차 접근 패턴에 대해 최악의 성능을 보이는 경우가 있다. 하지만 무작위로 페이지를 선택하면 이런 최악의 시나리오를 피할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;가벼운 구현:&lt;/b&gt; 무작위 추첨 방식은 관리해야 할 상태 정보가 매우 적기 때문에 구현이 간단하다. 다른 전통적인 스케줄링 알고리즘들에 비해 알고리즘 자체의 복잡도가 낮은 편이다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;빠른 의사 결정:&lt;/b&gt; 난수 생성이 빠르게 이루어진다면, 무작위 추첨을 통한 의사 결정도 신속하게 내려질 수 있다. 이는 스케줄링 결정이 자주 필요한 상황에서 특히 유용하다. &lt;br /&gt;&lt;br /&gt;물론 실제 구현에서는 속도 향상을 위해 순수한 무작위성을 어느 정도 포기하고 의사 난수(pseudo-random number)를 사용하기도 한다. 이처럼 무작위성은 비례 배분 스케줄링에 있어 단순한 해법이면서도 강력한 적응력과 효율성을 제공하는 핵심 요소라 할 수 있다. 특히 시스템의 동작을 예측하기 어려운 환경이나, 빠른 의사 결정이 필요한 상황에서 큰 힘을 발휘한다. 물론 무작위 방식이 항상 최선의 선택을 보장하는 것은 아니다. 때로는 특정 프로세스에게 지나치게 불리한 결과를 초래할 수도 있다. 하지만 장기적으로 볼 때, 무작위성은 각 프로세스에게 공정한 기회를 제공하고 특이 상황에 대한 대응력을 높여주는 효과적인 도구임에 틀림없다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작위성은 원하는 비율을 정확히 보장하지는 않는다. 하지만 두 작업이 장시간 실행될수록 , 원하는 비율을 달성할 가능성이 높아진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;추첨 기법&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;추첨권을 다루는 다양한 기법 중 하나는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;추첨권 화폐(ticket currency)&lt;/b&gt;&lt;/span&gt;의 개념이다. 이 기법은 사용자가 자신의 화폐 가치로 추첨권을 할당하고 이를 자유롭게 변환할 수 있도록 허용한다. 예를 들어, 사용자 A와 B가 각각 100장의 추첨권을 받았다고 가정하면, 사용자 A는 A1과 A2의 두 개의 작업을 실행 중이고 자신이 정한 화폐로 각각 500장의 추첨권을 할당하였다.(전체 1000장의 추첨권 중에). 사용자 B는 하나의 작업을 실행 중이고 자신의 기준 화폐 10장 중에 10장을 할당하였다. 시스템은 A1과 A2의 몫을 A의 기준 화폐 500장에서 전체의 기준이 되는 화폐 각각 50장으로 변환한다. 마찬가지로, B1의 추첨권 10장은 전체 기준이 되는 추첨권 100장으로 변환된다. 전체 기준이 되는 추첨권의 양 (총 200 장을 기준으로 추첨한다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;다른 유용한 기법은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;추첨권 양도(ticket transfer)&lt;/b&gt;&lt;/span&gt;이다. 이를 통해 프로세스는 추첨권을 일시적으로 다른 프로세스에게 양도할 수 있다. 이는 클라이언트/서버 환경에서 특히 유용하며, 클라이언트 프로세스가 서버에게 작업을 요청할 때 서버의 성능을 최대화하기 위해 추첨권을 전달할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;마지막으로, &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;추첨권 팽창(ticket inflation)&lt;/b&gt;&lt;/span&gt; 기법은 프로세스가 일시적으로 자신이 소유한 추첨권의 수를 조절할 수 있게 한다. 이는 서로 신뢰하는 프로세스들 간의 상호 작용에서 유용하며, 어떤 프로세스가 더 많은 CPU 시간을 필요로 할 때 다른 프로세스들과 통신하지 않고도 자체적으로 추첨권의 가치를 조절할 수 있다.&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;구현&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;추첨 스케줄링의 가장 큰 장점은 구현이 단순하다는 점이다. 필요한 것은 난수 발생기와 프로세스들의 집합을 표현하는 자료 구조(예, 리스트), 추첨권의 전체 개수뿐이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758588041116&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// counter: used to track if we&amp;rsquo;ve found the winner yet
int counter = 0;

// winner: use some call to a random number generator to
// 		   get a value, between 0 and the total # of tickets
int winner = getrandom(0, totaltickets);

// current: use this to walk through the list of jobs
node_t *current = head;
// loop until the sum of ticket values is &amp;gt; the winner
while (current) {
	counter = counter + current-&amp;gt;tickets;
	if (counter &amp;gt; winner)
		break; // found the winner
	current = current-&amp;gt;next;
}
// &amp;rsquo;current&amp;rsquo; is the winner: schedule it...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트를 사용하여 프로세스를 관리한다고 가정하자. A, B 및 C 세 개의 프로세스로 구성되고 각자 몇 장의 추첨권을 가진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오전 9.41.00.png&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b26zau/btsQK2sfWyl/a5BFFPk8YNkkWSMo6h6Xc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b26zau/btsQK2sfWyl/a5BFFPk8YNkkWSMo6h6Xc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b26zau/btsQK2sfWyl/a5BFFPk8YNkkWSMo6h6Xc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb26zau%2FbtsQK2sfWyl%2Fa5BFFPk8YNkkWSMo6h6Xc0%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;786&quot; height=&quot;148&quot; data-filename=&quot;스크린샷 2025-09-23 오전 9.41.00.png&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄을 결정하기 위해 먼저 총 추첨권의 개수 $ (400)^{2} $ 중에서 한 숫자를 선택해야 한다. 300이 선택되었다고 하자. 리스트를 순회하며 카운터 값을 이용하여 당첨자를 찾아낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 리스트를 순회하면서 counter의 값이&amp;nbsp; winner의 값을 초과할 때까지 각 추첨권 개수를 counter에 더한다. 값이 초과하게 되면 리스트의 현재 원소가 당첨자가 된다. 당첨 번호가 300인 우리의 예에서는 다음과 같이 진행된다. 먼저 A의 추첨권 개수가 더해져서 counter 값이 100으로 증가한다. 100은 300보다 작기 때문에 계속 루프를 실행한다. 다음 counter는 150(B의 추첨권 개수)으로 갱신되고 아직 300보다 작기 때문에 다시 순회를 계속한다. 마침내 counter는 400(확실히 300 보다 큼)으로 갱신되고 루프를 빠져나오게 되고 current는 프로세스 C(당첨자)를 가리키게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 리스트를 내림차순으로 정렬하면 이 과정이 가장 효율적이 된다. 정렬 순서는 알고리즘의 정확성에 영향을 주지는 않는다. 그러나 리스트를 정렬해 놓으면 검색 횟수가 최소화되는 것을 보장한다. 특히, 적은 수의 프로세스가 대부분의 추첨권을 소유하고 있는 경우 효과적이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;예제&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추첨 스케줄링의 동작 원리를 보다 쉽게 이해하기 위해, 두 개의 프로세스가 CPU를 공유하는 상황을 살펴보겠다. 각 프로세스는 동일한 수의 추첨권(예: 100장씩)을 가지고 있고, 수행해야 할 작업량도 같다고 가정한다. 이상적으로는 두 프로세스가 거의 동시에 종료되는 것이 좋겠지만, 추첨 방식의 무작위성 때문에 한 프로세스가 다른 프로세스보다 일찍 끝날 가능성이 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 불균형 정도를 측정하기 위해, 간단한 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;불공정 지표(unfairness metric)&lt;/b&gt;&lt;/span&gt;인 U를 정의해 보겠다. U는 먼저 종료된 프로세스의 완료 시간을 나중에 종료된 프로세스의 완료 시간으로 나눈 값이다. 예를 들어 첫 번째 프로세스가 시간 10에 끝나고 두 번째 프로세스가 시간 20에 끝났다면, U = 10/20 = 0.5가 된다. 두 프로세스가 거의 동시에 종료될수록 U는 1에 가까워진다. 완벽하게 공정한 스케줄러라면 U = 1을 달성할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;402&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by2aQN/btsQJ6u9PHe/ZlI460xyKglyAOJLndbKa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by2aQN/btsQJ6u9PHe/ZlI460xyKglyAOJLndbKa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by2aQN/btsQJ6u9PHe/ZlI460xyKglyAOJLndbKa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby2aQN%2FbtsQJ6u9PHe%2FZlI460xyKglyAOJLndbKa1%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;402&quot; height=&quot;317&quot; data-origin-width=&quot;402&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;위 그래프는 프로세스의 실행 시간에 따른 평균 불공정도를 보여준다. 프로세스 실행 시간을 1부터 1000까지 다양하게 변화시키면서 각 경우를 30번씩 시뮬레이션했다. 그래프에서 볼 수 있듯이, 프로세스의 실행 시간이 짧을수록 불공정도가 커지는 경향이 있다. 추첨 스케줄링이 의도한 결과에 근접하려면 프로세스들이 충분히 오랫동안 실행되어야 한다는 것을 알 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;추첨권 배분 방식&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;추첨 스케줄링에서 아직 다루지 않은 문제는 추첨권을 작업에게 어떻게 분배할지이다. 작업들에게 몇 개의 추첨권을 주어야 하는지 결정하는 것은 시스템 동작에 큰 영향을 미치므로 상당히 복잡한 문제다. 한 가지 접근 방식은 사용자가 자신의 상황을 가장 잘 이해한다고 가정하는 것이다. 각 사용자에게 추첨권을 할당한 후, 사용자가 자신이 실행하고자 하는 작업에 따라 추첨권을 분배할 수 있도록 하는 것이다. 그러나 이 방법은 문제의 본질을 해결하지 않다. 어떤 작업을 수행해야 하는지 전혀 제시하지 않는다. 주어진 작업 집합에 대한 &lt;b&gt;추첨권 할당 문제&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;왜 결정론적(Deterministic) 방법을 사용하지 않는가&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;추첨 스케줄링은 무작위성을 활용하여 스케줄러를 단순하면서도 어느 정도 공정하게 만들 수 있지만, 완벽한 비례 배분을 보장하기는 어렵다. 특히 프로세스의 실행 시간이 짧을 때는 이런 한계가 더욱 두드러진다. 이런 문제를 해결하기 위해 Waldspurger는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;보폭 스케줄링(stride scheduling)&lt;/b&gt;&lt;/span&gt;이라는 결정론적 공정 배분 스케줄러를 고안했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보폭 스케줄링에서는 각 프로세스마다 고유한&lt;span style=&quot;color: #006dd7;&quot;&gt; &lt;b&gt;보폭(stride)&lt;/b&gt;&lt;/span&gt; 값을 할당한다. 보폭은 자신이 가지고 있는 추첨권 수에 반비례하는 값이다. 앞의 예에서 작업 A, B, C는 각각 100, 50, 250의 추첨권을 가지고 있으며 임의의 큰 값을 각자의 추첨권 개수로 나누어 보폭을 계산할 수 있다. 예를 들어 10000을 각자의 추첨권 개수로 나누면 각 작업의 보폭은 100, 200 및 40이 된다. 이 값을 보폭이라고 부르며 프로세스가 CPU를 사용할 때마다 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;pass&lt;/b&gt;&lt;/span&gt; 변수를 보폭만큼 증가시켜 CPU 사용량을 추적한다. 스케줄러는 이 pass 값과 보폭을 기준으로 다음에 실행할 프로세스를 결정하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 아이디어는 간단하다. 가장 작은 pass 값을 가진 프로세스를 선택한다. 프로세스를 실행시킬 때마다 pass 값을 보폭만큼씩 증가시킨다. 아래는 Waldspurger가 제시한 보폭 스케줄링의 의사코드다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오전 10.00.02.png&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A1gMy/btsQI4EEiky/Q49EYc62K1HwOD4Hw6qbTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A1gMy/btsQI4EEiky/Q49EYc62K1HwOD4Hw6qbTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A1gMy/btsQI4EEiky/Q49EYc62K1HwOD4Hw6qbTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA1gMy%2FbtsQI4EEiky%2FQ49EYc62K1HwOD4Hw6qbTk%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;1244&quot; height=&quot;168&quot; data-filename=&quot;스크린샷 2025-09-23 오전 10.00.02.png&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;168&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;399&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rD3jq/btsQK3rav8E/fJHgLbabXqkApwGlza0NsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rD3jq/btsQK3rav8E/fJHgLbabXqkApwGlza0NsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rD3jq/btsQK3rav8E/fJHgLbabXqkApwGlza0NsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrD3jq%2FbtsQK3rav8E%2FfJHgLbabXqkApwGlza0NsK%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;399&quot; height=&quot;300&quot; data-origin-width=&quot;399&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보폭 스케줄링은 이런 방식으로 CPU 사용 시간을 프로세스 간에 공정하게 분배하여, 모든 프로세스가 자신의 중요도에 비례하여 CPU를 사용할 수 있도록 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데도 많은 경우 추첨 스케줄링이 선호되는 이유는 무엇일까? 그것은 추첨 스케줄링이 보폭 스케줄링에 비해 가지는 고유한 장점 때문이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보폭 스케줄링은 새로운 작업이 도착할 때마다 해당 작업의 상태 정보를 고려하여 스케줄링을 수행한다. 예를 들어 보폭 스케줄링에서 새 작업이 도착하면 해당 작업의 초기 pass 값을 신중히 결정해야 한다. 만약 pass 값을 0으로 설정하면 이 작업이 CPU를 독점할 수도 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면 추첨 스케줄링에서는 각 작업의 추첨권 수만 관리하면 된다. 새 작업이 추가되면 해당 작업의 추첨권 수를 전체 추첨권 수에 반영하는 것으로 충분하다. 즉, 새 작업을 시스템에 통합하는 과정이 매우 간단해지는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 추첨 스케줄링은 무작위성으로 인해 특정 패턴의 작업 부하에 대해 보폭 스케줄링보다 더 나은 성능을 보일 수 있다. 물론 정확한 비례 배분이 필요하다면 보폭 스케줄링이 더 적합하겠지만, 많은 실제 시스템에서는 근사적인 공정성으로도 충분한 경우가 많다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결국 추첨 스케줄링은 구현이 간단하고 새 작업 추가가 용이하며, 무작위성이 주는 이점까지 누릴 수 있어서 여전히 매력적인 선택지로 남아 있는 것이다. 물론 정확성과 공정성 측면에서는 보폭 스케줄링에 뒤질 수밖에 없지만, 그 단순함과 유연함은 실제 시스템을 설계할 때 큰 강점으로 작용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비례 배분 스케줄링은 시스템 내의 여러 프로세스나 작업들 사이에서 CPU 시간을 공정하게 나누어주는 방식 중 하나다. 이 방식에서는 각 프로세스의 상대적 중요도에 따라 일정 수의&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; 추첨권(ticket)&lt;/b&gt;&lt;/span&gt;을 부여하는데, 이는 마치 추첨에 응모하는 것과 비슷하다. 추첨권의 수가 많을수록 해당 프로세스가 CPU 시간을 얻을 확률이 높아진다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스케줄러는 각 프로세스가 가진 추첨권의 수에 비례하여 CPU 시간을 할당한다. 즉, 추첨권을 많이 가진 프로세스일수록 더 자주, 더 오랫동안 CPU를 사용할 수 있게 되는 것이다. 이를 통해 프로세스의 상대적 중요도를 CPU 시간 할당에 공정하게 반영할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비례 배분 스케줄링의 주요 특징은 다음과 같다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상대적 중요도의 반영: 각 프로세스에 할당된 추첨권의 수는 그 프로세스의 중요도를 나타낸다. CPU 시간은 이 추첨권 수에 비례하여 분배되므로, 중요도가 높은 프로세스는 자연스럽게 더 많은 CPU 시간을 받게 된다.&lt;/li&gt;
&lt;li&gt;유연한 우선순위 조정: 프로세스의 중요도는 추첨권 수를 조정하는 것만으로 쉽게 변경할 수 있다. 새로운 프로세스가 추가되거나 기존 프로세스의 우선순위를 바꿔야 할 때 매우 유용하다.&lt;/li&gt;
&lt;li&gt;장기적 공정성 보장: 충분히 긴 시간 동안 스케줄링을 수행하면, 각 프로세스는 자신의 추첨권 비율만큼의 CPU 시간을 받게 된다. 이는 시스템 자원이 프로세스 간에 공정하게 분배됨을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 비례 배분 스케줄링에도 몇 가지 단점이 있다. 우선 무작위 추첨 방식 때문에 단기적으로는 CPU 할당이 불균형해 보일 수 있다. 또한 추첨을 수행하고 당첨 프로세스를 확인하는 과정에서 어느 정도의 오버헤드가 발생할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고 비례 배분 스케줄링은 프로세스의 상대적 중요도를 유연하게 반영하면서 시스템 자원을 효율적이고 공정하게 관리할 수 있는 강력한 방법이다. 시스템의 요구사항과 우선순위가 다양한 환경에서 적응력을 발휘할 수 있기에, 여러 분야에서 폭넓게 활용되고 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;운영체제의 CPU 스케줄링 알고리즘으로서 뿐만 아니라, 네트워크 대역폭 할당, 디스크 I/O 스케줄링 등 컴퓨터 시스템의 다양한 자원 관리 문제에 비례 배분 스케줄링의 아이디어를 적용할 수 있다. 그만큼 개념이 직관적이고 구현이 간단하면서도 효과적이라고 할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;숙제&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 3개의 작업과 난수 시드 1, 2, 3으로 시뮬레이션한 솔루션을 계산하세요. (계산 과정은 GPT로 정리함)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오전 10.30.00.png&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djEdV0/btsQJbjpWJo/BTXDT491c7GcejBmEDI9bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djEdV0/btsQJbjpWJo/BTXDT491c7GcejBmEDI9bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djEdV0/btsQJbjpWJo/BTXDT491c7GcejBmEDI9bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjEdV0%2FbtsQJbjpWJo%2FBTXDT491c7GcejBmEDI9bk%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;437&quot; height=&quot;386&quot; data-filename=&quot;스크린샷 2025-09-23 오전 10.30.00.png&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-end=&quot;197&quot; data-start=&quot;187&quot; data-ke-size=&quot;size18&quot;&gt;0) 초기 상태&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;459&quot; data-start=&quot;198&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;319&quot; data-start=&quot;198&quot;&gt;작업 목록
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;319&quot; data-start=&quot;210&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;245&quot; data-start=&quot;210&quot;&gt;Job 0: length=1, tickets=84&lt;/li&gt;
&lt;li data-end=&quot;283&quot; data-start=&quot;248&quot;&gt;Job 1: length=7, tickets=25&lt;/li&gt;
&lt;li data-end=&quot;319&quot; data-start=&quot;286&quot;&gt;Job 2: length=4, tickets=44&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;351&quot; data-start=&quot;320&quot;&gt;&lt;b&gt;총 티켓 = 84 + 25 + 44 = 153&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;459&quot; data-start=&quot;352&quot;&gt;(초기) 티켓 구간
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;459&quot; data-start=&quot;369&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;396&quot; data-start=&quot;369&quot;&gt;Job 0: &lt;b&gt;0 ~ 83&lt;/b&gt; (84장)&lt;/li&gt;
&lt;li data-end=&quot;428&quot; data-start=&quot;399&quot;&gt;Job 1: &lt;b&gt;84 ~ 108&lt;/b&gt; (25장)&lt;/li&gt;
&lt;li data-end=&quot;459&quot; data-start=&quot;431&quot;&gt;Job 2: &lt;b&gt;109 ~ 152&lt;/b&gt; (44장)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;464&quot; data-start=&quot;461&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;477&quot; data-start=&quot;466&quot; data-ke-size=&quot;size18&quot;&gt;1) 스텝별 계산&lt;/p&gt;
&lt;p data-end=&quot;499&quot; data-start=&quot;479&quot; data-ke-size=&quot;size18&quot;&gt;[시간 1] R=651593&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;621&quot; data-start=&quot;500&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;537&quot; data-start=&quot;500&quot;&gt;총 티켓=153 &amp;rarr; W = 651593 % 153 = 119&lt;/li&gt;
&lt;li data-end=&quot;574&quot; data-start=&quot;538&quot;&gt;119는 &lt;b&gt;109~152&lt;/b&gt; 구간 &amp;rarr; &lt;b&gt;Job 2&lt;/b&gt; 선택&lt;/li&gt;
&lt;li data-end=&quot;621&quot; data-start=&quot;575&quot;&gt;실행 후 남은 시간: Job2 4 &amp;rarr; 3&lt;br /&gt;(아직 아무도 끝나지 않음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;643&quot; data-start=&quot;623&quot; data-ke-size=&quot;size18&quot;&gt;[시간 2] R=788724&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;823&quot; data-start=&quot;644&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;674&quot; data-start=&quot;644&quot;&gt;여전히 총 티켓=153 (아직 아무도 끝나지 않음)&lt;/li&gt;
&lt;li data-end=&quot;699&quot; data-start=&quot;675&quot;&gt;W = 788724 % 153 = 9&lt;/li&gt;
&lt;li data-end=&quot;731&quot; data-start=&quot;700&quot;&gt;9는 &lt;b&gt;0~83&lt;/b&gt; 구간 &amp;rarr; &lt;b&gt;Job 0&lt;/b&gt; 선택&lt;/li&gt;
&lt;li data-end=&quot;823&quot; data-start=&quot;732&quot;&gt;실행 후 남은 시간: Job0 1 &amp;rarr; 0 &amp;rarr; &lt;b&gt;Job 0 완료 (시간 2)&lt;/b&gt;&lt;br /&gt;&amp;rarr; 큐에서 제거, 이제 남은 작업은 Job1(25), Job2(44)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;845&quot; data-start=&quot;825&quot; data-ke-size=&quot;size18&quot;&gt;(재계산) 현재 총 티켓/구간&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;937&quot; data-start=&quot;846&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;871&quot; data-start=&quot;846&quot;&gt;&lt;b&gt;총 티켓 = 25 + 44 = 69&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;937&quot; data-start=&quot;872&quot;&gt;구간
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;937&quot; data-start=&quot;881&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;908&quot; data-start=&quot;881&quot;&gt;Job 1: &lt;b&gt;0 ~ 24&lt;/b&gt; (25장)&lt;/li&gt;
&lt;li data-end=&quot;937&quot; data-start=&quot;911&quot;&gt;Job 2: &lt;b&gt;25 ~ 68&lt;/b&gt; (44장)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;958&quot; data-start=&quot;939&quot; data-ke-size=&quot;size18&quot;&gt;[시간 3] R=93859&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1040&quot; data-start=&quot;959&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;992&quot; data-start=&quot;959&quot;&gt;총 티켓=69 &amp;rarr; W = 93859 % 69 = 19&lt;/li&gt;
&lt;li data-end=&quot;1025&quot; data-start=&quot;993&quot;&gt;19는 &lt;b&gt;0~24&lt;/b&gt; 구간 &amp;rarr; &lt;b&gt;Job 1&lt;/b&gt; 선택&lt;/li&gt;
&lt;li data-end=&quot;1040&quot; data-start=&quot;1026&quot;&gt;Job1 7 &amp;rarr; 6&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1061&quot; data-start=&quot;1042&quot; data-ke-size=&quot;size18&quot;&gt;[시간 4] R=28347&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1144&quot; data-start=&quot;1062&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1095&quot; data-start=&quot;1062&quot;&gt;총 티켓=69 &amp;rarr; W = 28347 % 69 = 57&lt;/li&gt;
&lt;li data-end=&quot;1129&quot; data-start=&quot;1096&quot;&gt;57은 &lt;b&gt;25~68&lt;/b&gt; 구간 &amp;rarr; &lt;b&gt;Job 2&lt;/b&gt; 선택&lt;/li&gt;
&lt;li data-end=&quot;1144&quot; data-start=&quot;1130&quot;&gt;Job2 3 &amp;rarr; 2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1166&quot; data-start=&quot;1146&quot; data-ke-size=&quot;size18&quot;&gt;[시간 5] R=835765&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1250&quot; data-start=&quot;1167&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1201&quot; data-start=&quot;1167&quot;&gt;총 티켓=69 &amp;rarr; W = 835765 % 69 = 37&lt;/li&gt;
&lt;li data-end=&quot;1235&quot; data-start=&quot;1202&quot;&gt;37은 &lt;b&gt;25~68&lt;/b&gt; 구간 &amp;rarr; &lt;b&gt;Job 2&lt;/b&gt; 선택&lt;/li&gt;
&lt;li data-end=&quot;1250&quot; data-start=&quot;1236&quot;&gt;Job2 2 &amp;rarr; 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1272&quot; data-start=&quot;1252&quot; data-ke-size=&quot;size18&quot;&gt;[시간 6] R=432767&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1401&quot; data-start=&quot;1273&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1307&quot; data-start=&quot;1273&quot;&gt;총 티켓=69 &amp;rarr; W = 432767 % 69 = 68&lt;/li&gt;
&lt;li data-end=&quot;1341&quot; data-start=&quot;1308&quot;&gt;68은 &lt;b&gt;25~68&lt;/b&gt; 구간 &amp;rarr; &lt;b&gt;Job 2&lt;/b&gt; 선택&lt;/li&gt;
&lt;li data-end=&quot;1401&quot; data-start=&quot;1342&quot;&gt;Job2 1 &amp;rarr; 0 &amp;rarr; &lt;b&gt;Job 2 완료 (시간 6)&lt;/b&gt;&lt;br /&gt;&amp;rarr; 남은 작업은 Job1(25)뿐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1423&quot; data-start=&quot;1403&quot; data-ke-size=&quot;size18&quot;&gt;(재계산) 현재 총 티켓/구간&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1482&quot; data-start=&quot;1424&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1439&quot; data-start=&quot;1424&quot;&gt;&lt;b&gt;총 티켓 = 25&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1482&quot; data-start=&quot;1440&quot;&gt;구간
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1482&quot; data-start=&quot;1449&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1482&quot; data-start=&quot;1449&quot;&gt;Job 1: &lt;b&gt;0 ~ 24&lt;/b&gt; (25장) (혼자 남음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1504&quot; data-start=&quot;1484&quot; data-ke-size=&quot;size18&quot;&gt;[시간 7] R=762280&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1576&quot; data-start=&quot;1505&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1538&quot; data-start=&quot;1505&quot;&gt;총 티켓=25 &amp;rarr; W = 762280 % 25 = 5&lt;/li&gt;
&lt;li data-end=&quot;1561&quot; data-start=&quot;1539&quot;&gt;유일 후보 &amp;rarr; &lt;b&gt;Job 1&lt;/b&gt; 선택&lt;/li&gt;
&lt;li data-end=&quot;1576&quot; data-start=&quot;1562&quot;&gt;Job1 6 &amp;rarr; 5&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1596&quot; data-start=&quot;1578&quot; data-ke-size=&quot;size18&quot;&gt;[시간 8] R=2106&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1658&quot; data-start=&quot;1597&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1628&quot; data-start=&quot;1597&quot;&gt;총 티켓=25 &amp;rarr; W = 2106 % 25 = 6&lt;/li&gt;
&lt;li data-end=&quot;1658&quot; data-start=&quot;1629&quot;&gt;&lt;b&gt;Job 1&lt;/b&gt; 선택 &amp;rarr; Job1 5 &amp;rarr; 4&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1680&quot; data-start=&quot;1660&quot; data-ke-size=&quot;size18&quot;&gt;[시간 9] R=445387&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1745&quot; data-start=&quot;1681&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1715&quot; data-start=&quot;1681&quot;&gt;총 티켓=25 &amp;rarr; W = 445387 % 25 = 12&lt;/li&gt;
&lt;li data-end=&quot;1745&quot; data-start=&quot;1716&quot;&gt;&lt;b&gt;Job 1&lt;/b&gt; 선택 &amp;rarr; Job1 4 &amp;rarr; 3&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1768&quot; data-start=&quot;1747&quot; data-ke-size=&quot;size18&quot;&gt;[시간 10] R=721540&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1833&quot; data-start=&quot;1769&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1803&quot; data-start=&quot;1769&quot;&gt;총 티켓=25 &amp;rarr; W = 721540 % 25 = 15&lt;/li&gt;
&lt;li data-end=&quot;1833&quot; data-start=&quot;1804&quot;&gt;&lt;b&gt;Job 1&lt;/b&gt; 선택 &amp;rarr; Job1 3 &amp;rarr; 2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1856&quot; data-start=&quot;1835&quot; data-ke-size=&quot;size18&quot;&gt;[시간 11] R=228762&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1921&quot; data-start=&quot;1857&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1891&quot; data-start=&quot;1857&quot;&gt;총 티켓=25 &amp;rarr; W = 228762 % 25 = 12&lt;/li&gt;
&lt;li data-end=&quot;1921&quot; data-start=&quot;1892&quot;&gt;&lt;b&gt;Job 1&lt;/b&gt; 선택 &amp;rarr; Job1 2 &amp;rarr; 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1944&quot; data-start=&quot;1923&quot; data-ke-size=&quot;size18&quot;&gt;[시간 12] R=945271&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2032&quot; data-start=&quot;1945&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1979&quot; data-start=&quot;1945&quot;&gt;총 티켓=25 &amp;rarr; W = 945271 % 25 = 21&lt;/li&gt;
&lt;li data-end=&quot;2032&quot; data-start=&quot;1980&quot;&gt;&lt;b&gt;Job 1&lt;/b&gt; 선택 &amp;rarr; Job1 1 &amp;rarr; 0 &amp;rarr; &lt;b&gt;Job 1 완료 (시간 12)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2037&quot; data-start=&quot;2034&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;2057&quot; data-start=&quot;2039&quot; data-ke-size=&quot;size18&quot;&gt;2) 최종 요약 (완료 시각)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2198&quot; data-start=&quot;2058&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2085&quot; data-start=&quot;2058&quot;&gt;&lt;b&gt;Job 0&lt;/b&gt;: 시간 &lt;b&gt;2&lt;/b&gt;에 종료&lt;/li&gt;
&lt;li data-end=&quot;2113&quot; data-start=&quot;2086&quot;&gt;&lt;b&gt;Job 2&lt;/b&gt;: 시간 &lt;b&gt;6&lt;/b&gt;에 종료&lt;/li&gt;
&lt;li data-end=&quot;2142&quot; data-start=&quot;2114&quot;&gt;&lt;b&gt;Job 1&lt;/b&gt;: 시간 &lt;b&gt;12&lt;/b&gt;에 종료&lt;/li&gt;
&lt;li data-end=&quot;2198&quot; data-start=&quot;2143&quot;&gt;총 실행 시간 = 1 + 4 + 7 = 12 (퀀텀 12번) &amp;mdash; 난수도 정확히 12개 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오전 10.30.16.png&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qzffu/btsQILrMhRz/BCeBkuC3W5FC4rHiD4xaVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qzffu/btsQILrMhRz/BCeBkuC3W5FC4rHiD4xaVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qzffu/btsQILrMhRz/BCeBkuC3W5FC4rHiD4xaVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqzffu%2FbtsQILrMhRz%2FBCeBkuC3W5FC4rHiD4xaVK%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;506&quot; height=&quot;543&quot; data-filename=&quot;스크린샷 2025-09-23 오전 10.30.16.png&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-end=&quot;239&quot; data-start=&quot;232&quot; data-ke-size=&quot;size18&quot;&gt;초기 상태&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;485&quot; data-start=&quot;240&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;271&quot; data-start=&quot;240&quot;&gt;Job 0: length=9, tickets=94&lt;/li&gt;
&lt;li data-end=&quot;303&quot; data-start=&quot;272&quot;&gt;Job 1: length=8, tickets=73&lt;/li&gt;
&lt;li data-end=&quot;335&quot; data-start=&quot;304&quot;&gt;Job 2: length=6, tickets=30&lt;/li&gt;
&lt;li data-end=&quot;365&quot; data-start=&quot;336&quot;&gt;&lt;b&gt;초기 Tot = 94+73+30 = 197&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;485&quot; data-start=&quot;366&quot;&gt;&lt;b&gt;초기 티켓 구간(0-index, 포함 범위)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;485&quot; data-start=&quot;401&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;426&quot; data-start=&quot;401&quot;&gt;Job 0: &lt;b&gt;0&amp;ndash;93&lt;/b&gt; (94장)&lt;/li&gt;
&lt;li data-end=&quot;456&quot; data-start=&quot;429&quot;&gt;Job 1: &lt;b&gt;94&amp;ndash;166&lt;/b&gt; (73장)&lt;/li&gt;
&lt;li data-end=&quot;485&quot; data-start=&quot;459&quot;&gt;Job 2: &lt;b&gt;167&amp;ndash;196&lt;/b&gt; (30장)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;490&quot; data-start=&quot;487&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;527&quot; data-start=&quot;492&quot; data-ke-size=&quot;size18&quot;&gt;스텝별 계산 (t는 1부터 시작; 실행 후 남은 길이 표시)&lt;/p&gt;
&lt;p data-end=&quot;591&quot; data-start=&quot;528&quot; data-ke-size=&quot;size16&quot;&gt;표기: R(난수), W=R%Tot(당첨 티켓), Run(실행된 잡), left(실행 후 남은 길이)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1767&quot; data-start=&quot;593&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;684&quot; data-start=&quot;593&quot;&gt;t=1: Tot=197, R=605944 &amp;rarr; &lt;b&gt;W=169&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt; (169&amp;isin;167&amp;ndash;196)&lt;br /&gt;left: j0=9, j1=8, j2=5&lt;/li&gt;
&lt;li data-end=&quot;772&quot; data-start=&quot;685&quot;&gt;t=2: Tot=197, R=606802 &amp;rarr; &lt;b&gt;W=42&lt;/b&gt; &amp;rarr; &lt;b&gt;Job0&lt;/b&gt; (42&amp;isin;0&amp;ndash;93)&lt;br /&gt;left: j0=8, j1=8, j2=5&lt;/li&gt;
&lt;li data-end=&quot;850&quot; data-start=&quot;773&quot;&gt;t=3: Tot=197, R=581204 &amp;rarr; &lt;b&gt;W=54&lt;/b&gt; &amp;rarr; &lt;b&gt;Job0&lt;/b&gt;&lt;br /&gt;left: j0=7, j1=8, j2=5&lt;/li&gt;
&lt;li data-end=&quot;928&quot; data-start=&quot;851&quot;&gt;t=4: Tot=197, R=158383 &amp;rarr; &lt;b&gt;W=192&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt;&lt;br /&gt;left: j0=7, j1=8, j2=4&lt;/li&gt;
&lt;li data-end=&quot;1006&quot; data-start=&quot;929&quot;&gt;t=5: Tot=197, R=430670 &amp;rarr; &lt;b&gt;W=28&lt;/b&gt; &amp;rarr; &lt;b&gt;Job0&lt;/b&gt;&lt;br /&gt;left: j0=6, j1=8, j2=4&lt;/li&gt;
&lt;li data-end=&quot;1097&quot; data-start=&quot;1007&quot;&gt;t=6: Tot=197, R=393532 &amp;rarr; &lt;b&gt;W=123&lt;/b&gt; &amp;rarr; &lt;b&gt;Job1&lt;/b&gt; (123&amp;isin;94&amp;ndash;166)&lt;br /&gt;left: j0=6, j1=7, j2=4&lt;/li&gt;
&lt;li data-end=&quot;1175&quot; data-start=&quot;1098&quot;&gt;t=7: Tot=197, R=723012 &amp;rarr; &lt;b&gt;W=22&lt;/b&gt; &amp;rarr; &lt;b&gt;Job0&lt;/b&gt;&lt;br /&gt;left: j0=5, j1=7, j2=4&lt;/li&gt;
&lt;li data-end=&quot;1267&quot; data-start=&quot;1176&quot;&gt;t=8: Tot=197, R=994820 &amp;rarr; &lt;b&gt;W=167&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt; (167&amp;isin;167&amp;ndash;196)&lt;br /&gt;left: j0=5, j1=7, j2=3&lt;/li&gt;
&lt;li data-end=&quot;1345&quot; data-start=&quot;1268&quot;&gt;t=9: Tot=197, R=949396 &amp;rarr; &lt;b&gt;W=53&lt;/b&gt; &amp;rarr; &lt;b&gt;Job0&lt;/b&gt;&lt;br /&gt;left: j0=4, j1=7, j2=3&lt;/li&gt;
&lt;li data-end=&quot;1425&quot; data-start=&quot;1346&quot;&gt;t=10: Tot=197, R=544177 &amp;rarr; &lt;b&gt;W=63&lt;/b&gt; &amp;rarr; &lt;b&gt;Job0&lt;/b&gt;&lt;br /&gt;left: j0=3, j1=7, j2=3&lt;/li&gt;
&lt;li data-end=&quot;1505&quot; data-start=&quot;1426&quot;&gt;t=11: Tot=197, R=444854 &amp;rarr; &lt;b&gt;W=28&lt;/b&gt; &amp;rarr; &lt;b&gt;Job0&lt;/b&gt;&lt;br /&gt;left: j0=2, j1=7, j2=3&lt;/li&gt;
&lt;li data-end=&quot;1585&quot; data-start=&quot;1506&quot;&gt;t=12: Tot=197, R=268241 &amp;rarr; &lt;b&gt;W=124&lt;/b&gt; &amp;rarr; &lt;b&gt;Job1&lt;/b&gt;&lt;br /&gt;left: j0=2, j1=6, j2=3&lt;/li&gt;
&lt;li data-end=&quot;1665&quot; data-start=&quot;1586&quot;&gt;t=13: Tot=197, R=35924 &amp;rarr; &lt;b&gt;W=70&lt;/b&gt; &amp;rarr; &lt;b&gt;Job0&lt;/b&gt;&lt;br /&gt;left: j0=1, j1=6, j2=3&lt;/li&gt;
&lt;li data-end=&quot;1767&quot; data-start=&quot;1666&quot;&gt;t=14: Tot=197, R=27444 &amp;rarr; &lt;b&gt;W=61&lt;/b&gt; &amp;rarr; &lt;b&gt;Job0&lt;/b&gt;&lt;br /&gt;left: j0=0, j1=6, j2=3 &amp;rarr; &lt;b&gt;JOB 0 종료 (t=14)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1787&quot; data-start=&quot;1769&quot; data-ke-size=&quot;size18&quot;&gt;잡0 종료 후 티켓 재계산&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1873&quot; data-start=&quot;1788&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1834&quot; data-start=&quot;1788&quot;&gt;남은 작업/티켓: Job1(73), Job2(30) &amp;rarr; &lt;b&gt;Tot=103&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1873&quot; data-start=&quot;1835&quot;&gt;새 구간: Job1 &lt;b&gt;0&amp;ndash;72&lt;/b&gt;, Job2 &lt;b&gt;73&amp;ndash;102&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2540&quot; data-start=&quot;1875&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1953&quot; data-start=&quot;1875&quot;&gt;t=15: Tot=103, R=464894 &amp;rarr; &lt;b&gt;W=55&lt;/b&gt; &amp;rarr; &lt;b&gt;Job1&lt;/b&gt;&lt;br /&gt;left: j0=0, j1=5, j2=3&lt;/li&gt;
&lt;li data-end=&quot;2044&quot; data-start=&quot;1954&quot;&gt;t=16: Tot=103, R=318465 &amp;rarr; &lt;b&gt;W=92&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt; (92&amp;isin;73&amp;ndash;102)&lt;br /&gt;left: j0=0, j1=5, j2=2&lt;/li&gt;
&lt;li data-end=&quot;2123&quot; data-start=&quot;2045&quot;&gt;t=17: Tot=103, R=380015 &amp;rarr; &lt;b&gt;W=48&lt;/b&gt; &amp;rarr; &lt;b&gt;Job1&lt;/b&gt;&lt;br /&gt;left: j0=0, j1=4, j2=2&lt;/li&gt;
&lt;li data-end=&quot;2202&quot; data-start=&quot;2124&quot;&gt;t=18: Tot=103, R=891790 &amp;rarr; &lt;b&gt;W=16&lt;/b&gt; &amp;rarr; &lt;b&gt;Job1&lt;/b&gt;&lt;br /&gt;left: j0=0, j1=3, j2=2&lt;/li&gt;
&lt;li data-end=&quot;2281&quot; data-start=&quot;2203&quot;&gt;t=19: Tot=103, R=525753 &amp;rarr; &lt;b&gt;W=41&lt;/b&gt; &amp;rarr; &lt;b&gt;Job1&lt;/b&gt;&lt;br /&gt;left: j0=0, j1=2, j2=2&lt;/li&gt;
&lt;li data-end=&quot;2360&quot; data-start=&quot;2282&quot;&gt;t=20: Tot=103, R=560510 &amp;rarr; &lt;b&gt;W=87&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt;&lt;br /&gt;left: j0=0, j1=2, j2=1&lt;/li&gt;
&lt;li data-end=&quot;2439&quot; data-start=&quot;2361&quot;&gt;t=21: Tot=103, R=236123 &amp;rarr; &lt;b&gt;W=47&lt;/b&gt; &amp;rarr; &lt;b&gt;Job1&lt;/b&gt;&lt;br /&gt;left: j0=0, j1=1, j2=1&lt;/li&gt;
&lt;li data-end=&quot;2540&quot; data-start=&quot;2440&quot;&gt;t=22: Tot=103, R=23858 &amp;rarr; &lt;b&gt;W=65&lt;/b&gt; &amp;rarr; &lt;b&gt;Job1&lt;/b&gt;&lt;br /&gt;left: j0=0, j1=0, j2=1 &amp;rarr; &lt;b&gt;JOB 1 종료 (t=22)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;2560&quot; data-start=&quot;2542&quot; data-ke-size=&quot;size18&quot;&gt;잡1 종료 후 티켓 재계산&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2609&quot; data-start=&quot;2561&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2609&quot; data-start=&quot;2561&quot;&gt;남은 작업: Job2(30) &amp;rarr; &lt;b&gt;Tot=30&lt;/b&gt; (혼자 남음이니 매번 Job2)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2709&quot; data-start=&quot;2611&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2709&quot; data-start=&quot;2611&quot;&gt;t=23: Tot=30, R=325143 &amp;rarr; &lt;b&gt;W=3&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt;&lt;br /&gt;left: j0=0, j1=0, j2=0 &amp;rarr; &lt;b&gt;JOB 2 종료 (t=23)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-end=&quot;2714&quot; data-start=&quot;2711&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;2724&quot; data-start=&quot;2716&quot; data-ke-size=&quot;size18&quot;&gt;최종 요약&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2831&quot; data-start=&quot;2725&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2789&quot; data-start=&quot;2725&quot;&gt;&lt;b&gt;완료 시각&lt;/b&gt;: Job0 = &lt;b&gt;t=14&lt;/b&gt;, Job1 = &lt;b&gt;t=22&lt;/b&gt;, Job2 = &lt;b&gt;t=23&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;2831&quot; data-start=&quot;2790&quot;&gt;총 실행 퀀텀 = 9+8+6 = &lt;b&gt;23&lt;/b&gt; (난수 23개 모두 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오전 10.50.24.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NjE02/btsQLyLalmS/fUNSKypmqd3Vzn8RsHEtT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NjE02/btsQLyLalmS/fUNSKypmqd3Vzn8RsHEtT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NjE02/btsQLyLalmS/fUNSKypmqd3Vzn8RsHEtT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNjE02%2FbtsQLyLalmS%2FfUNSKypmqd3Vzn8RsHEtT0%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;436&quot; height=&quot;365&quot; data-filename=&quot;스크린샷 2025-09-23 오전 10.50.24.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-end=&quot;208&quot; data-start=&quot;201&quot; data-ke-size=&quot;size18&quot;&gt;초기 상태&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;371&quot; data-start=&quot;209&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;252&quot; data-start=&quot;209&quot;&gt;Job 0: length=2, tickets=54 &amp;rarr; 구간 &lt;b&gt;0&amp;ndash;53&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;298&quot; data-start=&quot;253&quot;&gt;Job 1: length=3, tickets=60 &amp;rarr; 구간 &lt;b&gt;54&amp;ndash;113&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;345&quot; data-start=&quot;299&quot;&gt;Job 2: length=6, tickets=6 &amp;rarr; 구간 &lt;b&gt;114&amp;ndash;119&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;371&quot; data-start=&quot;346&quot;&gt;&lt;b&gt;Tot = 54+60+6 = 120&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;376&quot; data-start=&quot;373&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;386&quot; data-start=&quot;378&quot; data-ke-size=&quot;size18&quot;&gt;스텝별 계산&lt;/p&gt;
&lt;p data-end=&quot;393&quot; data-start=&quot;388&quot; data-ke-size=&quot;size16&quot;&gt;t=1&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;480&quot; data-start=&quot;394&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;457&quot; data-start=&quot;394&quot;&gt;R=13168, &lt;b&gt;W=13168 % 120 = 88&lt;/b&gt; &amp;rarr; 88 &amp;isin; 54&amp;ndash;113 &amp;rarr; &lt;b&gt;Job1 실행&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;480&quot; data-start=&quot;458&quot;&gt;남은: J0=2, J1=2, J2=6&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;487&quot; data-start=&quot;482&quot; data-ke-size=&quot;size16&quot;&gt;t=2&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;572&quot; data-start=&quot;488&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;549&quot; data-start=&quot;488&quot;&gt;Tot=120, R=837469, &lt;b&gt;W=109&lt;/b&gt; &amp;rarr; 109 &amp;isin; 54&amp;ndash;113 &amp;rarr; &lt;b&gt;Job1 실행&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;572&quot; data-start=&quot;550&quot;&gt;남은: J0=2, J1=1, J2=6&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;579&quot; data-start=&quot;574&quot; data-ke-size=&quot;size16&quot;&gt;t=3&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;660&quot; data-start=&quot;580&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;637&quot; data-start=&quot;580&quot;&gt;Tot=120, R=259354, &lt;b&gt;W=34&lt;/b&gt; &amp;rarr; 34 &amp;isin; 0&amp;ndash;53 &amp;rarr; &lt;b&gt;Job0 실행&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;660&quot; data-start=&quot;638&quot;&gt;남은: J0=1, J1=1, J2=6&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;667&quot; data-start=&quot;662&quot; data-ke-size=&quot;size16&quot;&gt;t=4&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;755&quot; data-start=&quot;668&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;732&quot; data-start=&quot;668&quot;&gt;Tot=120, R=234331, &lt;b&gt;W=91&lt;/b&gt; &amp;rarr; 91 &amp;isin; 54&amp;ndash;113 &amp;rarr; &lt;b&gt;Job1 실행 &amp;rarr; 완료&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;755&quot; data-start=&quot;733&quot;&gt;남은: J0=1, J1=0, J2=6&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;777&quot; data-start=&quot;757&quot; data-ke-size=&quot;size16&quot;&gt;▶ 작업1 완료 후 구간/합 갱신&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;829&quot; data-start=&quot;778&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;829&quot; data-start=&quot;778&quot;&gt;남은 티켓: J0=54(구간 0&amp;ndash;53), J2=6(구간 54&amp;ndash;59), &lt;b&gt;Tot=60&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;836&quot; data-start=&quot;831&quot; data-ke-size=&quot;size16&quot;&gt;t=5&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;913&quot; data-start=&quot;837&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;896&quot; data-start=&quot;837&quot;&gt;Tot=60, R=995645, &lt;b&gt;W=5&lt;/b&gt; &amp;rarr; 5 &amp;isin; 0&amp;ndash;53 &amp;rarr; &lt;b&gt;Job0 실행 &amp;rarr; 완료&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;913&quot; data-start=&quot;897&quot;&gt;남은: J0=0, J2=6&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;930&quot; data-start=&quot;915&quot; data-ke-size=&quot;size16&quot;&gt;▶ 작업0 완료 후 갱신&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;959&quot; data-start=&quot;931&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;959&quot; data-start=&quot;931&quot;&gt;남은 티켓: J2=6(혼자), &lt;b&gt;Tot=6&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;966&quot; data-start=&quot;961&quot; data-ke-size=&quot;size16&quot;&gt;t=6&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1012&quot; data-start=&quot;967&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1012&quot; data-start=&quot;967&quot;&gt;R=470263, &lt;b&gt;W=1&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt; (혼자) &amp;rarr; 남은 J2=5&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1019&quot; data-start=&quot;1014&quot; data-ke-size=&quot;size16&quot;&gt;t=7&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1060&quot; data-start=&quot;1020&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1060&quot; data-start=&quot;1020&quot;&gt;R=836462, &lt;b&gt;W=2&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt; &amp;rarr; 남은 J2=4&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1067&quot; data-start=&quot;1062&quot; data-ke-size=&quot;size16&quot;&gt;t=8&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1108&quot; data-start=&quot;1068&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1108&quot; data-start=&quot;1068&quot;&gt;R=476353, &lt;b&gt;W=1&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt; &amp;rarr; 남은 J2=3&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1115&quot; data-start=&quot;1110&quot; data-ke-size=&quot;size16&quot;&gt;t=9&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1156&quot; data-start=&quot;1116&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1156&quot; data-start=&quot;1116&quot;&gt;R=639068, &lt;b&gt;W=2&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt; &amp;rarr; 남은 J2=2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1164&quot; data-start=&quot;1158&quot; data-ke-size=&quot;size16&quot;&gt;t=10&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1205&quot; data-start=&quot;1165&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1205&quot; data-start=&quot;1165&quot;&gt;R=150616, &lt;b&gt;W=4&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2&lt;/b&gt; &amp;rarr; 남은 J2=1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1213&quot; data-start=&quot;1207&quot; data-ke-size=&quot;size16&quot;&gt;t=11&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1252&quot; data-start=&quot;1214&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1252&quot; data-start=&quot;1214&quot;&gt;R=634861, &lt;b&gt;W=1&lt;/b&gt; &amp;rarr; &lt;b&gt;Job2 실행 &amp;rarr; 완료&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1257&quot; data-start=&quot;1254&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;1266&quot; data-start=&quot;1259&quot; data-ke-size=&quot;size18&quot;&gt;최종 정리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1440&quot; data-start=&quot;1267&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1320&quot; data-start=&quot;1267&quot;&gt;실행 순서(시간 1&amp;rarr;11): &lt;b&gt;1, 1, 0, 1, 0, 2, 2, 2, 2, 2, 2&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1396&quot; data-start=&quot;1321&quot;&gt;&lt;b&gt;완료 시각&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1396&quot; data-start=&quot;1337&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1355&quot; data-start=&quot;1337&quot;&gt;Job 1: &lt;b&gt;t=4&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1376&quot; data-start=&quot;1358&quot;&gt;Job 0: &lt;b&gt;t=5&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1396&quot; data-start=&quot;1379&quot;&gt;Job 2: &lt;b&gt;t=11&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1440&quot; data-start=&quot;1397&quot;&gt;총 퀀텀: 2 + 3 + 6 = &lt;b&gt;11&lt;/b&gt; (난수 11개를 정확히 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PWbj2/btsQJWfaWGY/ATuSrC6lj81zRWJyqDWZFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PWbj2/btsQJWfaWGY/ATuSrC6lj81zRWJyqDWZFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PWbj2/btsQJWfaWGY/ATuSrC6lj81zRWJyqDWZFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPWbj2%2FbtsQJWfaWGY%2FATuSrC6lj81zRWJyqDWZFK%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;400&quot; height=&quot;533&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이제 두 개의 특정 작업으로 실행해 보세요: 각각 길이는 10이지만 하나(작업 0)는 티켓이 1장이고 다른 하나(작업 1)는 100장입니다(예:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;10:1,10:100&lt;/span&gt;). 티켓의 수가 이렇게 불균형할 때 어떤 일이 일어날까요? 작업 1이 완료되기 전에 작업 0이 실행될 수 있을까요? 얼마나 자주 그럴까요? 일반적으로 이러한 티켓 불균형이 복권 스케줄링의 동작에 어떤 영향을 미칠까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A : 작업 1 이 완료되기 전에 작업 0이 실행될 수는 있으나 그 확률이 매우 낮을 것 같다. 대략 10번 중 1 번은 작업 0이 실행될 수 있을 것 같다. 낮은 확률로 실행은 되겠으나 마치 기아현상처럼 보일 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오전 11.05.55.png&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6RJid/btsQIFZspD7/F6Fyeoarn9cGQWAd4rTbSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6RJid/btsQIFZspD7/F6Fyeoarn9cGQWAd4rTbSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6RJid/btsQIFZspD7/F6Fyeoarn9cGQWAd4rTbSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6RJid%2FbtsQIFZspD7%2FF6Fyeoarn9cGQWAd4rTbSk%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;476&quot; height=&quot;902&quot; data-filename=&quot;스크린샷 2025-09-23 오전 11.05.55.png&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 길이가 100이고 티켓 할당이 100으로 동일한 두 개의 작업(&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;100:100,100:100&lt;/span&gt;)으로 실행할 때 스케줄러는 얼마나 불공정할까요? 몇 가지 다른 난수 시드로 실행하여 (확률적) 답을 결정하세요; 한 작업이 다른 작업보다 얼마나 일찍 끝나는지에 따라 불공정성이 결정됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-23 오전 11.15.45.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxMgGK/btsQK26UKTo/8Xctys2SDHB6BqHkbD2wYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxMgGK/btsQK26UKTo/8Xctys2SDHB6BqHkbD2wYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxMgGK/btsQK26UKTo/8Xctys2SDHB6BqHkbD2wYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxMgGK%2FbtsQK26UKTo%2F8Xctys2SDHB6BqHkbD2wYK%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;1574&quot; height=&quot;512&quot; data-filename=&quot;스크린샷 2025-09-23 오전 11.15.45.png&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난수로 인하여 끝나는 시간이 시드마다 다르다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/197</guid>
      <comments>https://gowoong.tistory.com/197#entry197comment</comments>
      <pubDate>Tue, 23 Sep 2025 11:18:20 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 4주차 - 스케줄링 2 : Part.1 - MLFQ</title>
      <link>https://gowoong.tistory.com/196</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;멀티 레벨 피드백 큐&lt;/b&gt;&lt;/span&gt;(Multi-Level Feedback Queue, MLFQ) 스케줄러는 1962년 Corbato 등에 의해 CTSS(Compatible Time-Sharing System)에 처음 도입되었다. Corbato는 이 연구와 Multics에 대한 후속 연구로 최고 권위의 튜링상을 수상했다. 이후 MLFQ는 수년에 걸쳐 발전을 거듭하며 오늘날의 몇몇 현대 시스템에까지 이르게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MLFQ가 해결하고자 하는 핵심 문제는 두 가지다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;짧은 작업을 우선 처리하여 반환 시간(turnaround time)을 최적화하는 것이다. SJF나 STCF 같은 알고리즘은 이를 위해 각 작업의 실행 시간 정보를 필요로 하지만, 안타깝게도 운영체제는 이 정보를 사전에 알 수 없다.&lt;/li&gt;
&lt;li&gt;사용자와 상호작용하는 프로세스, 즉 사용자가 화면 앞에서 응답을 기다리는 프로세스의 응답 시간(response time)을 최소화하는 것이다. RR 같은 알고리즘은 응답 시간을 어느 정도 단축시키지만, 반환 시간 측면에서는 거의 최악에 가까운 성능을 보인다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;MLFQ : 기본 규칙&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MLFQ는 여러 개의 큐로 이루어져 있으며, 각 큐는 서로 다른 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;우선순위(priority level)&lt;/b&gt;&lt;/span&gt;를 갖는다. 실행 가능한 모든 프로세스는 이. 큐들 중 하나에 위치하게 된다. MLFQ는 우선순위에 따라 다음에 실행할 프로세스를 선택하는데, 보다 높은 우선순위의 큐에 있는 프로세스가 우선적으로 선택된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 큐에는 여러 프로세스가 존재할 수 있는데 이들은 모두 동일한 우선순위를 갖는다. 같은 큐 내의 프로세스들에 대해서는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;라운드 로빈(Round-Robin) 스케줄링&lt;/b&gt;&lt;/span&gt;이 적용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MLFQ의 핵심은 우선순위를 정하는 방식이다. MLFQ는 각 프로세스에 고정된 우선순위를 부여하지 않고, 프로세스의 실행 특성에 따라 동적으로 우선순위를 조정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 어떤 작업이 키보드 입력을 기다리며 반복적으로 CPU를 양보하면 MLFQ는 해당 작업의 우선순위를 높게 유지한다. 이러한 패턴은 대화형 프로세스가 나타내는 패턴이다. 대신에 한 작업이 긴 시간 동안 CPU를 집중적으로 사용하면 MLFQ는 해당 작업의 우선순위를 낮춘다. 이렇게 MLFQ는 작업이 진행되는 동안 해당 작업이 정보를 얻고, 이 정보를 이용하여 미래 행동을 예측한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MLFQ의 두 가지 기본 규칙은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;규칙 1: &lt;b&gt;Priority(A) &amp;gt; Priority(B) 이면, B는 실행되지 않고 A가 실행된다.&lt;/b&gt;&lt;br /&gt;규칙 2: &lt;b&gt;Priority(A) = Priority(B) 이면, A와 B는 RR 방식으로 실행된다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;시도 1: 우선순위의 변경&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MLFQ는 프로세스의 우선순위를 어떻게 조정할 것인지 결정해야 한다. 프로세스의 우선순위를 변경한다는 것은 곧 그 프로세스가 위치할 큐를 결정하는 것과 같다. 이를 위해서는 시스템의 워크로드 특성을 반영해야 한다. 일반적으로 워크로드는 짧은 CPU 버스트를 가지며 자주 I/O를 수행하는 대화형 작업과, CPU 시간을 많이 필요로 하지만 응답 시간은 상대적으로 덜 중요한 긴 CPU 버스트의 계산 집약적 작업이 섞여 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&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;332&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6blSQ/btsQIxM2ZSR/nkx2kg9XGKNUSBr2GObYH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6blSQ/btsQIxM2ZSR/nkx2kg9XGKNUSBr2GObYH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6blSQ/btsQIxM2ZSR/nkx2kg9XGKNUSBr2GObYH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6blSQ%2FbtsQIxM2ZSR%2Fnkx2kg9XGKNUSBr2GObYH1%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;332&quot; height=&quot;292&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 A와 B는 가장 높은 우선순위 큐에, C는 중간 큐에, D는 가장 낮은 우선순위 큐에 있다. 우리가 알고 있는 MLFQ의 동작 방식에 따르면, 스케줄러는 최상위 큐에 있는 A와 B를 RR방식으로 번갈아 실행할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위 조정 알고리즘을 위한 첫 번째 시도는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;규칙 3: 새로운 프로세스가 시스템에 진입하면, 가장 높은 우선순위, 즉 맨 위의 큐에 배치된다.&lt;/li&gt;
&lt;li&gt;규칙 4a: 할당된 타임 슬라이스를 모두 소진하면 해당 프로세스의 우선순위가 한 단계 낮아진다. 즉, 바로 아래 큐로 이동한다.&lt;/li&gt;
&lt;li&gt;규칙 4b: 타임 슬라이스가 끝나기 전에 스스로 CPU를 양보(yield)하면 같은 우선순위를 유지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예 1: 한 개의 긴 실행 시간을 가진 작업&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 예시를 통해 알아보자. 먼저 CPU를 오랫동안 사용하는 프로세스가 시스템에 들어왔을 때 어떤 일이 벌어지는지 알아보겠다.&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;471&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clOvM3/btsQHetVfUn/xyvyDeEoIz5D8wQ7HbbqHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clOvM3/btsQHetVfUn/xyvyDeEoIz5D8wQ7HbbqHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clOvM3/btsQHetVfUn/xyvyDeEoIz5D8wQ7HbbqHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclOvM3%2FbtsQHetVfUn%2FxyvyDeEoIz5D8wQ7HbbqHK%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;471&quot; height=&quot;255&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 처음에 최상위 큐인 Q2에 위치한다.(규칙 3) 10ms의 타임 슬라이스를 모두 소진하면, 스케줄러는 이 프로세스의 우선순위를 한 단계 낮춰 Q1으로 이동시킨다.(규칙 4a).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q1에서도 마찬가지로 타임 슬라이스를 다. 쓰면 프로세스는 최하위 큐인 Q0 로 내려간다. 이 후로는 계속 Q0에 남아있게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예 2: 짧은 작업과 함께&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 복잡한 예를 살펴보겠다. 이를 통해 MLFQ가 어떻게 SJF 와 유사하게 동작할. 수 있는지 이해할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 A는 CPU를 오랫동안 사용하는 계산 집약적인 작업이고, 프로세스 B는 실행 시간이 짧은 대화형 작업이다. A는 이미 어느 정도 실행된 상태이고, B는 방금 시스템에 도착했다고 가정하겠다. 이때 MLFQ는 어떻게 동작할까? B를 우선적으로 처리함으로써 SJF와 비슷한 결과를 낼 수 있는가?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;370&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs4wox/btsQG1n6m2R/iVqEKliWo07ruwZbNQLfzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs4wox/btsQG1n6m2R/iVqEKliWo07ruwZbNQLfzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs4wox/btsQG1n6m2R/iVqEKliWo07ruwZbNQLfzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs4wox%2FbtsQG1n6m2R%2FiVqEKliWo07ruwZbNQLfzk%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;370&quot; height=&quot;254&quot; data-origin-width=&quot;370&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 CPU 집약적 프로세스와 마찬가지로 A는 최하위 큐에서 실행 중이다. B는 시간 100에 시스템에 진입하면서 최상위 큐에 배치된다(규칙 3). B의 실행 시간은 20ms로 매우 짧기 때문에, 두번의 타입 슬라이스 안에 실행을 마친다. 즉 최하위 큐까지 내려가기 전에 종료된다. B가 끝난 후에 A가 하위 큐에서 실행을 재개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제는 MLFQ 알고리즘의 주된 목표를 잘 보여준다. 스케줄러는 각 프로세스의 실행 시간을 미리 알 수 없으므로, 일단 모든 프로세스를 &amp;ldquo;짧은 작업&amp;rdquo;으로 간주하고 높은 우선순위를 부여하는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 정말 짧은 작업이라면 금방 실행을 마치고 종료될 것이다. 그렇지 않고 긴 작업이라면 점차 아래 큐로 내려가면서 스스로 CPU 집약적 작업임을 드러내게 된다. 이런 방식으로 MLFQ는 SJF와 유사한 효과를 달성할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예 2: 입출력 작업에 대해서는 어떻게?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 입출력 작업을 수행하는 예를 살펴보겠다. 앞서 언급한 규칙 4b에 따르면, 프로세스가 할당된 타임 슬라이스를 다 쓰기 전에 CPU를 양보하면 현재의 우선순위를 유지하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 큐칙의 의도는 다음과 같다. 대화형 프로세스는 사용자의 키보드나 마우스 입력을 기다리면서 빈번히 입출력 작업을 수행하게 된다. 이 때문에 타임 슬라이스가 끝나기 전에 CPU를 양보하는 일이 잦을 거라고 예상할 수 있다. 이런 경유에는 프로세스의 우선순위를 그대로 유지하여 대화형 작업의 응답성을 높이고자 하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rkG7x/btsQJ5hQQuz/PWqskfwk0ivPbxKReS0Agk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rkG7x/btsQJ5hQQuz/PWqskfwk0ivPbxKReS0Agk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rkG7x/btsQJ5hQQuz/PWqskfwk0ivPbxKReS0Agk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrkG7x%2FbtsQJ5hQQuz%2FPWqskfwk0ivPbxKReS0Agk%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;570&quot; height=&quot;258&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 B(회색) 대회형 프로세스로 입출력 요청을 하기 전에 CPU를 1ms 동안만 사용한다. 반면 프로세스 A는 CPU를 오래 점유하는 배치 프로세스이다. 이 둘은 CPU 사용을 두고 경쟁한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B는 입출력 작업 때문에 반복적으로 CPU를 양보하게 된다. 따라서 MLFQ는 규칙 4b에 의해 B를 가장 높은 우선순위에 머물게 한다. 만약 B가 정말 대화형 프로세스라면, 이는 MLFQ가 대화형 작업의 응답 시간을 최소화하려는 목표에 부합하는 것이라 할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;현재 MLFQ의 문제점&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 MLFQ는 비교적 단순한 알고리즘이다. CPU를 긴 작업과 짧은 작업 사이에 적절히 분배하고, I/O 집약적인 대화형 작업의 응답 시간을 최소화하는 등 나름 잘 동작하는 것처럼 보인다. 그러나 이 기법에는 몇 가지 심각한 결함이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8U0qb/btsQHsyMJGn/WePp5UG5tSuilKqfLT4FG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8U0qb/btsQHsyMJGn/WePp5UG5tSuilKqfLT4FG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8U0qb/btsQHsyMJGn/WePp5UG5tSuilKqfLT4FG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8U0qb%2FbtsQHsyMJGn%2FWePp5UG5tSuilKqfLT4FG0%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;678&quot; height=&quot;253&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;기아 상태(starvation)의 가능성&lt;/b&gt;: 만약 시스템에 대화형 작업이 너무 많이 존재한다면, 이들이 대부분의 CPU 시간을 차지하게 될 것이다. 그 결과 CPU를 오래 사용하는 배치 작업들은 제대로 된 CPU 시간을 할당받지 못하게 된다. 이런 시나리오에서도 긴 작업들이 어느 정도 진행될 수 있도록 보장하는 것이 바람직할 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스케줄러 악용의 위험&lt;/b&gt;: 영리한 사용자라면 스케줄러를 자신에게 유리하게 작동하도록 프로그램을 조작할 수 있다. 일반적으로 이는 스케줄러를 속여 정해진 것보다 더 많은 CPU 시간을 할당받는 것을 의미한다. 현재의 MLFQ 알고리즘은 이런 공격에 취약하다. 예를 들어, 프로세스가 타임 슬라이스가 끝나기 직전에 불필요한 I/O 요청을 내려 CPU를 양보한다면, 계속해서 높은 우선순위 큐에 머물 수 있다. 이 트릭을 잘 활용하면, 예컨대 타임 슬라이스의 99%를 쓰고 CPU를 양보하는 식으로, 사실상 CPU를 독점할 수도 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시간에 따른 프로세스 특성 변화&lt;/b&gt;: 프로그램의 행동 양식은 시간이 지남에 따라 달라질 수 있다. CPU 집약적 작업이 어느 순간 대화형 작업으로 바뀔 수도 있다. 그러나 현재의 MLFQ로는 이런 작업이 다른 대화형 작업과 동등한 취급을 받기 어렵다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;시도 2: 우선순위의 상향 조정&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 MLFQ의 규칙을 보완하여 기아 문제를 해결할 수 있는 방법을 살펴보겠다. CPU 집약적인 작업도 어느 정도 진행될 수 있도록 보장하려면 어떻게 해야 하는가?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한 가지 간단한 아이디어는 주기적으로 모든 작업의 우선순위를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;상향 조정(boost)&lt;/b&gt;&lt;/span&gt;하는 것이다. 여러 방법이 있겠지만, 가장 단순한 방식은 모든 프로세스를 최상위 큐로 이동시키는 것이다. 이를 반영한 새로운 규칙은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;규칙 5: 일정 시간 S가 경과할 때마다, 시스템의 모든 프로세스를 최상위 큐로 이동시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 새 규칙은 두 가지 문제를 동시에 해결한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 프로세스가 기아 상태에 빠지지 않음을 보장한다. 최상위 큐에 있는 동안에는 다른 우선순위 높은 프로세스들과 라운드 로빈 방식으로 CPU를 나눠 쓰게 되므로, 어느 정도의 CPU 시간을 할당받을 수 있다.&lt;/li&gt;
&lt;li&gt;CPU 집약적 작업이 대화형 작업으로 특성이 바뀌더라도, 우선순위 상향을 통해 스케줄러가 이 변화를 감지하고 적절히 대응할 수 있게 된다.&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;678&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8U0qb/btsQHsyMJGn/WePp5UG5tSuilKqfLT4FG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8U0qb/btsQHsyMJGn/WePp5UG5tSuilKqfLT4FG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8U0qb/btsQHsyMJGn/WePp5UG5tSuilKqfLT4FG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8U0qb%2FbtsQHsyMJGn%2FWePp5UG5tSuilKqfLT4FG0%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;678&quot; height=&quot;253&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 이미지는 CPU를 오래 사용하는 한 프로세스와 짧은 대화형 프로세스 두 개가 경쟁하는 시나리오다. 왼쪽 그래프는 우선순위 상향이 없는 경우인데, 긴 작업이 짧은 작업들이 도착한 이후로는 계속 기아 상태에 빠져 있다. 반면 오른쪽 그래프는 50ms 마다 우선순위 상향이 일어나는 모습니다. 이 경우 긴 작업도 주기적으로 실행 기회를 얻어 꾸준히 진행됨을 알 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물론 주기 S를 얼마로 정할 것인지가 중요한 문제다. 존경받는 시스템 연구자 John Ousterhout는 이런 종류의 값을 &amp;ldquo;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;부두(voodoo) 상수&lt;/b&gt;&lt;/span&gt;&amp;rdquo;라고 불렀는데, 마치 이 값을 정하려면 흑마술이라도 써야 할 것 같다는 뜻이다. 안타깝게도 S가 바로 그런 변수다. S를 너무 크게 잡으면 긴 작업이 여전히 기아 상태에 빠질 수 있고, 너무 작게 잡으면 대화형 작업이 충분한 CPU 시간을 받지 못할 수 있다. 적절한 균형점을 찾는 것이 중요한 과제다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;시도 3: 더 나은 시간 측정&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MLFQ에는 해결해야 할 또 다른 문제가 있다. 바로 사용자가 스케줄러를 조작하여 자신에게 유리하게 만드는 것을 막는 것이다. 이런 일이 가능했던 것은 규칙 4a와 4b 때문이다. 이 규칙들은 프로세스가 타임 슬라이스를 다 쓰기 전에 CPU를 양보함으로써 현재 우선순위를 유지할 수 있도록 허용했다. 이를 막기 위해서는 어떻게 해야 하는가?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해법은 MLFQ의 각 단계에서 프로세스가 실제로 CPU를 사용한 누적 시간을 측정하는 것이다. 스케줄러는 현재 단계에서 각 프로세스의 CPU 사용 시간을 기록한다. 프로세스가 해당 단계의 타임 슬라이스를 모두 소진하면, 그 횟수에 관계없이 다음 우선순위 큐로 내려가게 된다. 타임 슬라이스를 한 번에 다 쓰든, 짧게 여러 번 나눠 쓰든 상관없이 누적 사용 시간만 고려하는 것이다. 이를 반영하여 규칙 4a와 4b를 하나의 규칙으로 통합해 보겠다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;규칙 4: 프로세스가 현재 단계에서 CPU를 양보한 횟수와 무관하게, 정해진 시간 할당량을 모두 소진하면 우선순위가 낮아진다. (즉, 한 단계 아래 큐로 이동한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gxeb0/btsQJbpjp7g/IVqQDx1OQJDaK44KQXBojk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gxeb0/btsQJbpjp7g/IVqQDx1OQJDaK44KQXBojk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gxeb0/btsQJbpjp7g/IVqQDx1OQJDaK44KQXBojk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGxeb0%2FbtsQJbpjp7g%2FIVqQDx1OQJDaK44KQXBojk%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;631&quot; height=&quot;260&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽은 프로세스가 규칙 4a와 4b를 악용하여 스케줄러를 조작하는 모습인데, 타임 슬라이스가 거의 끝나갈 때마다 I/O 요청을 내려 계속 높은 우선순위에 머물러 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면 오른쪽은 새로운 규칙 4를 적용한 모습이다. 이제는 프로세스의 I/O 행동과 무관하게 정해진 CPU 시간을 다 쓰면 하위 큐로 내려가게 되므로, 더 이상 CPU를 불공정하게 많이 쓸 수 없게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 MLFQ에서 프로세스의 CPU 사용 시간을 정확히 측정하고 누적하는 것은 스케줄러 조작을 방지하고 공정성을 높이는 데 있어 매우 중요한 아이디어라고 할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;MLFQ 조정과 다른 쟁점들&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;MLFQ 스케줄링에는 여러 가지 추가적인 고려 사항이 있다. 스케줄러가 사용하는 각종 변수들을 어떻게 설정할 것인가도 중요한 문제다. 예를 들어, 몇 개의 큐를 사용할 것인지, 각 큐의 타임 슬라이스는 얼마로 할 것인지, 기아 상태를 방지하고 프로세스의 행동 변화를 반영하기 위해 얼마나 자주 우선순위를 조정할 것인지 등이 그런 문제들이다. 이에 대한 명확한 답을 찾기는 쉽지 않다. 실제 워크로드를 관찰하고 경험을 쌓아가며 최적점을 찾아야 할 것이다.&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;384&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMQ7DW/btsQGUo1pZo/yK3vCs4KKJlizCVKU0aOL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMQ7DW/btsQGUo1pZo/yK3vCs4KKJlizCVKU0aOL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMQ7DW/btsQGUo1pZo/yK3vCs4KKJlizCVKU0aOL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMQ7DW%2FbtsQGUo1pZo%2FyK3vCs4KKJlizCVKU0aOL1%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;384&quot; height=&quot;258&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 많은 MLFQ 구현체들은 각 큐마다 서로 다른 타임 슬라이스를 사용한다. 일반적으로 높은 우선순위 큐는 짧은 타임 슬라이스를 할당받는다. 이 큐에는 주로 대화형 프로세스들이 위치하므로, 이들을 빠르게 교체하는 것이 응답성 향상에 도움이 되기 때문이다(예: 10ms 이하). 반대로 낮은 우선순위 큐는 주로 CPU 집약적인 장기 실행 프로세스들을 포함하므로, 상대적으로 긴 타임 슬라이스를 부여받는다(예: 수백 ms).&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그림 11.7은 최상위 큐는 10ms, 중간 큐는 20ms, 최하위 큐는 40ms의 타임 슬라이스를 갖는 스케줄러에서 두 프로세스가 실행되는 모습을 보여준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;팁: 부두 상수를 피하라 (Ousterhout&amp;rsquo;s Law)&lt;/b&gt;&lt;br /&gt;가능하다면 &lt;b&gt;부두(voodoo) 상수&lt;/b&gt;의 사용을 피하는 것이 좋다. 하지만 현실적으로는 앞서 본 것처럼 이를 완전히 배제하기 어려운 경우가 많다. 최적값을 찾는 일도 쉽지 않다.&lt;br /&gt;흔히 이런 상황에서는 온갖 기본값들로 가득 찬 설정 파일이 등장한다. 뭔가 잘 동작하지 않을 때 경험 많은 관리자가 이 값들을 수정할 수 있도록 하는 것이다. 하지만 대부분의 경우 이 기본값들이 그대로 사용되며, 현장에서 잘 먹혀들기를 바랄 뿐이다.&lt;br /&gt;이런 통찰을 널리 알린 운영체제 교수 John Ousterhout의 이름을 따 &amp;lsquo;Ousterhout&amp;rsquo;s Law&amp;rsquo;라고도 부른다.&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 MLFQ는 매우 유연하고 강력한 스케줄링 기법이지만, 최적으로 운용하기 위해서는 여러 변수들에 대한 세심한 조정이 필요하다. 실제 시스템의 특성과 요구 사항을 잘 파악하여 적절한 균형점을 찾아가는 것이 중요한 과제다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Solaris 운영체제의 MLFQ 구현체인 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;시분할 스케줄링 클래스(Time Sharing Scheduling Class, TS)&lt;/b&gt;&lt;/span&gt;는 설정이 특히 간편하다. Solaris는 프로세스의 수명 주기 동안 우선순위의 변화 패턴, 각 큐의 타임 슬라이스 길이, 그리고 우선순위 상향 조정(priority boost)의 주기 등을 결정하는 테이블을 제공한다. 시스템 관리자는 이 테이블을 수정함으로써 스케줄러의 동작을 조정할 수 있다. 기본 설정은 60개의 큐, 최상위 큐의 타임 슬라이스는 20ms에서 시작해 최하위 큐에서는 수백 ms까지 점진적으로 증가, 그리고 약 1초마다 우선순위 상향 조정이 일어나는 식이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 모든 MLFQ 스케줄러가 이런 테이블이나 이 장에서 언급한 규칙들을 그대로 사용하는 것은 아니다. 어떤 경우에는 수학 공식을 활용해 우선순위를 조절하기도 한다. 가령 FreeBSD 4.3 버전의 스케줄러는 프로세스의 누적 CPU 사용량을 기준으로 현재 우선순위를 계산하는 공식을 사용한다. 이때 CPU 사용량은 시간이 지나면서 서서히 감쇄(decay)되는데, 이를 통해 우선순위 상향 조정을 간접적으로 구현하는 셈이다. 이런 유형의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;감쇄 사용량(decay-usage)&lt;/b&gt;&lt;/span&gt; 알고리즘과 그 특성에 대해서는 Epema의 논문에 잘 정리되어 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 외에도 스케줄러들은 다양한 부가 기능을 제공하곤 한다. 예컨대 일부 스케줄러는 최상위 우선순위 큐를 운영체제 프로세스를 위해 예약해 두기도 한다. 이 경우 일반 사용자 프로세스는 절대 최고 우선순위를 얻지 못하게 된다. 또 어떤 시스템들은 사용자가 직접 우선순위를 조정할 수 있는 방법을 제공하기도 한다. 리눅스의 &lt;b&gt;nice&lt;/b&gt; 명령어가 대표적인데, 이를 통해 프로세스의 우선순위를 높이거나 낮출 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;멀티 레벨 피드백 큐(Multi-Level Feedback Queue, MLFQ)&lt;/b&gt;&lt;/span&gt;라고 불리는 스케줄링 기법에 대해 알아보았다. 이제 왜 이런 이름이 붙었는지 이해할 수 있을 것이다. MLFQ는 여러 개의 우선순위 큐를 사용하며, 각 프로세스의 우선순위를 결정하기 위해 과거 실행 이력을 피드백으로 활용한다. 즉, 프로세스가 지금까지 어떻게 행동했는지가 앞으로의 우선순위를 정하는 기준이 되는 것이다. 따라서 MLFQ에서는 프로세스의 시간에 따른 행동 변화와 그에 대한 적절한 대응이 매우 중요하다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이해를 돕기 위해 이 장에서 다룬 MLFQ의 주요 규칙들을 다시 정리해 보겠다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;규칙 1: 프로세스 A의 우선순위가 B보다 높으면, A는 실행되고 B는 실행되지 않는다.&lt;/li&gt;
&lt;li&gt;규칙 2: 프로세스 A와 B의 우선순위가 같으면, A와 B는 라운드 로빈 방식으로 실행된다.&lt;/li&gt;
&lt;li&gt;규칙 3: 새로운 프로세스가 시스템에 진입하면 최상위 큐에 배치된다.&lt;/li&gt;
&lt;li&gt;규칙 4: 프로세스가 현재 단계에서 받은 시간을 모두 소진하면, CPU를 양보한 횟수와 관계없이 한 단계 아래 큐로 이동한다.&lt;/li&gt;
&lt;li&gt;규칙 5: 주기 S마다 시스템의 모든 프로세스를 최상위 큐로 이동시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MLFQ는 여러모로 흥미로운 스케줄러다. 프로세스의 특성을 사전에 알지 못해도, 실행 과정을 관찰하여 그에 맞게 우선순위를 부여할 수 있다. 또한 반환 시간(turnaround time)과 응답 시간(response time)을 동시에 최적화하려 노력한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MLFQ는 실행 시간이 짧은 대화형 프로세스에게는 SJF/STCF와 유사한 수준의 성능을 제공하며, 실행 시간이 긴 CPU 집약적 프로세스에 대해서도 어느 정도 공정성을 보장하여 조금씩이라도 진전을 이룰 수 있게 해준다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 장점 때문에 BSD 유닉스 및 그 파생 운영체제들 [Lef+89; Bac86], Solaris [McD06], 윈도우 NT 및 후속 윈도우 시리즈[CS97] 등 많은 시스템들이 MLFQ를 기본 스케줄러로 채택하고 있다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/196</guid>
      <comments>https://gowoong.tistory.com/196#entry196comment</comments>
      <pubDate>Mon, 22 Sep 2025 10:01:57 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 3주차 - 스케줄링 1</title>
      <link>https://gowoong.tistory.com/195</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주차에는 다양한 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;스케줄링 정책(Scheduling Policy)&lt;/span&gt;&lt;/b&gt;을 이해하는데 집중한다. 이러한 정책은 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;스케줄링 기법(Scheduling Discipline)&lt;/span&gt;&lt;/b&gt;이라고 불린다. 초기의 스케줄링 방법들은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;생산 관리 분야&lt;/b&gt;&lt;/span&gt;에서 개발되었고, 이후 컴퓨터 시스템에 적용되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 이번에 배울 스케줄링에 대한 핵심 질문은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 스케줄링 정책은 어떻게 개발할 수 있을까? &lt;br /&gt;2. 스케줄링 정책을 설계하기 위한 기본적인 프레임워크는 무엇일까? &lt;br /&gt;3. 정책 개발에 필요한 핵심 가정은 무엇일까? &lt;br /&gt;4. 어떤 평가 기준이 중요할까? &lt;br /&gt;5. 컴퓨터 시스템 초창기에 사용된 기본 접근 방식은 무엇일까?&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;워크로드에 대한 가정&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄링 정책을 알아보기 전에 몇 가지 가정을 하겠다. 이 가정을 통해 시스템에서 실행되는 프로세스들의 집합인 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;워크로드(Workload)&lt;/b&gt;&lt;/span&gt;를 정의하는데 도움이 될 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;1. 모든 작업의 실행 시간은 동일하다.&lt;/b&gt; - 예를 들어, 모든 작업이 정확히 10초 동안 실행됨. &lt;br /&gt;&lt;b&gt;2. 모든 작업은 동시에 시스템에 도착한다.&lt;/b&gt; - 즉, 모든 작업이 시작 시점에 한꺼번에 도착한다. &lt;br /&gt;&lt;b&gt;3. 각 작업은 한 번 시작되면 중간에 중단되지 않고 완료될 때까지 실행됨.&lt;/b&gt; - 작업이 실행되는 동안 다른 작업으로 전환되지 않음. &lt;br /&gt;&lt;b&gt;4. 모든 작업은 오직 CPU 자원만 사용한다.&lt;/b&gt; - 작업 실행 중에는 입출력(I/O) 작업이 발생하지 않는다고 가정. &lt;br /&gt;&lt;b&gt;5. 각 작업의 실행 시간은 스케줄러가 미리 알고 있다.&lt;/b&gt; - 스케줄러는 작업의 실행 시간을 사전에 파악하고 있다고 가정.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 작업의 실행 시간을 미리 알고 있다는 가정은 비현실적이다. 이러한 가정은 스케줄링 정책을 단순화하여 이해하기 쉽게 하기 위함이다. 실제 시스템에서는 현실적인 제약 사항들을 고려해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;스케줄링 평가 항목&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄링 알고리즘을 평가하기 위해 워크로드에 대한 가정외에도 평가 기준을 정해야 한다. 다양한 평가 기준이 사용되지만 일단 반환 시간(Turnaround Time)이라는 하나의 척도만 살펴보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$T_{turnaround} = T_{completion} - T_{arrival}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 $T_{turnaround}$ 은 반환 시간, $T_{completion}$은 작업 완료 시각, $T_{arrival}$은 작업 도착 시간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 모든 작업이 동시에 도착한다고 가정하기 때문에 $T_{arrival} = 0$이라 할 수 있다. 따라서 반환 시간은 곧 작업 완료 시각과 같아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환 시간은 시스템의 성능 측면에서 본 척도라는 점에 주목해야 한다. 또 중요한 기준으로 공정성(fairness)이 있는데 이는 Jain's Fairness Index 와 같은 지표로 측정 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄링에서 성능과 공정성은 종종 상충되는 목표가 된다. 예를 들어 스케줄러가 시스템 성능을 극대화 하기 위해 일부 작업을 오랫동안 미루게 되면, 그만큼 공정성은 나빠지게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;선입선출(FCFS)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;선입선출(First-Come, First-Served)&lt;/b&gt;&lt;/span&gt;은 가장 간단한 스케줄링 알고리즘 중 하나다. 이 알고리즘은 작업이 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;도착한 순서&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-filename=&quot;스크린샷 2025-09-15 오전 8.50.52.png&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BgE4Y/btsQxCORHRC/tVboUbtry51TTE6fs0D3QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BgE4Y/btsQxCORHRC/tVboUbtry51TTE6fs0D3QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BgE4Y/btsQxCORHRC/tVboUbtry51TTE6fs0D3QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBgE4Y%2FbtsQxCORHRC%2FtVboUbtry51TTE6fs0D3QK%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;366&quot; data-filename=&quot;스크린샷 2025-09-15 오전 8.50.52.png&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선입선출 스케줄링은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;비선점형 스케줄링&lt;/b&gt;&lt;/span&gt;이므로 프로세스가 자발적으로 CPU를 반납할 때 까지 CPU를 빼앗지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 프로세스 A,B,C가 각각 3, 5, 2초의 서비스 시간을 가지고 있고, A, B, C 순으로 도착하면 다음과 같은 프로세스를 실행한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;프로세스&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;서비스 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;대기 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;반환 시간&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;3초&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;0초&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;0초&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;3초&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;B&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;5초&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;3초&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;3초&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;8초&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;C&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;2초&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;8초&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;8초&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;&lt;b&gt;10초&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;이렇게 되면 평균 대기 시간은 (0 + 3 + 8) / 3 = 3.67초, 평균 응답 시간은 (0 + 3 + 8) / 3 = 3.67초, 평균 반환 시간은 (3 + 8 + 10) / 3 = 7초가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&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;/ul&gt;
&lt;h3 style=&quot;color: #29313d;&quot; data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;짧은 작업이 긴 작업보다 늦게 도착하면 긴 작업을 기다려야 하므로 평균 대기 시간이 증가할 수 있다. 이를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;호위 효과(convoy effect)&lt;/b&gt;&lt;/span&gt;라고 한다.&lt;/li&gt;
&lt;li&gt;FCFS 스케줄링은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;비선점형(nonpreemptive)&lt;/b&gt;&lt;/span&gt; 방식이므로 한 프로세스가 CPU를 점유하고 있으면 다른 프로세스는 CPU를 빼앗을 수 없다. 이는 CPU 사용률을 낮추고 I/O 바운드 프로세스의 대기 시간을 증가시킬 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;최단 작업 우선(SJF)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최단 작업 우선(Shortest Job First)는 실행 시간이 가장 짧은 작업을 가장 먼저 처리하는 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-15 오전 9.05.03.png&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dq4qZD/btsQzdgpLlR/GfkNitwWCTnbg2kFudK2V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dq4qZD/btsQzdgpLlR/GfkNitwWCTnbg2kFudK2V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dq4qZD/btsQzdgpLlR/GfkNitwWCTnbg2kFudK2V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdq4qZD%2FbtsQzdgpLlR%2FGfkNitwWCTnbg2kFudK2V1%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;736&quot; height=&quot;362&quot; data-filename=&quot;스크린샷 2025-09-15 오전 9.05.03.png&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지와 같은 예를 SJF로 스케줄 해보면 SJF의 평균 반환 시간은 50초이다. $ \frac{(10 + 20 + 30)}{3} = 50$ SJF는 장점이 많은 스케쥴링 기법이지만, 여전히 비현실적인 가정을 하고 있다. 만약 가정 2를 바꿔 모든 작업은 동시에 도착하는 것이 아니라 랜덤 하게 도착한다고 하면 어떻게 될까? 만약 A 작업이 t = 0에 도착하고 이후에 B, C 작업이 t = 10에 도착한다고 하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-15 오전 9.12.58.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8HjXL/btsQzjnBi79/skGkhFuM1ls2cK3J9MRTwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8HjXL/btsQzjnBi79/skGkhFuM1ls2cK3J9MRTwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8HjXL/btsQzjnBi79/skGkhFuM1ls2cK3J9MRTwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8HjXL%2FbtsQzjnBi79%2FskGkhFuM1ls2cK3J9MRTwK%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;846&quot; height=&quot;380&quot; data-filename=&quot;스크린샷 2025-09-15 오전 9.12.58.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 평균 반환 시간은 $\frac{100 + (110 - 10) + (120 - 10)}{3} = 103.33$이 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;최소 잔여시간 우선(STCF)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 문제를 해결하기 위해 최소 잔여시간 우선(Shortest Time-to completion First) 스케줄링 기법을 적용해 보겠다. 이 기법은 SJF를 조금 변형한 것으로 작업의 총 실행 시간이 아니라 남아있는 실행시간, 즉 잔여 실행 시간을 기준으로 삼는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-15 오전 9.16.29.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba6odK/btsQxrgaqfi/iH3kDUJDvBXuk9p7YkZx41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba6odK/btsQxrgaqfi/iH3kDUJDvBXuk9p7YkZx41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba6odK/btsQxrgaqfi/iH3kDUJDvBXuk9p7YkZx41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba6odK%2FbtsQxrgaqfi%2FiH3kDUJDvBXuk9p7YkZx41%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;792&quot; height=&quot;374&quot; data-filename=&quot;스크린샷 2025-09-15 오전 9.16.29.png&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 예에서 A의 경우 120초 라는 실행시간을 가지는데 A가 t=0에서 B, C가 t=10에 도착할 때 A를 실행하고 있다가 B, C가 도착하는 순간 잔여 실행 시간을 비교해 더 빨리 끝이 나는 B, C를 실행하도록 하는 것이다. 이후 B, C 작업이 끝이 나면 A작업을 다시 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 결과 평균 반환 시간이 단축되어 50초가 $\frac{(120 - 0) + (20 - 10) + (30-10)}{3}$ 된다. 모든 작업이 동시에 도착하는 특별한 경우에는 SJF 가 최적의 스케줄을 보장한다면 이렇게 작업이 서로 다른 시간에 도착하는 일반적인 상황에서는 STCF가 최적이 될 것이라는 것을 이해할 수 있다. STCF는 작업이 도착할 때마다 잔여 작업량을 재평가하여 가장 적절한 프로세스를 선택하기 때문이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;새로운 평가 기준 : 응답 시간&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;STCF는 작업의 실행 시간을 미리 알고, CPU만을 사용하며, 반환 시간이라는 단일 평가 기준을 가정할 때는 매우 효과적인 스케줄링 정책이다. 초기의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;일괄 처리(Batch Processing)&lt;/b&gt;&lt;/span&gt;시스템에서는 이런 알고리즘이 적절했으나 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;시분할(Time-Sharing) 시스템의&lt;/span&gt;&lt;/b&gt; 등장으로 상황이 달라졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자들은 터미널을 통해 컴퓨터와 상호작용을 하게 되었고 이에 따라 시스템의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;응답성(responsiveness)&lt;/b&gt;&lt;/span&gt;이 중요한 성능 지표로 부상했다. 이를 반영하기 위해 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;응답 시간(response time)&lt;/b&gt;&lt;/span&gt;이라는 새로운 평가 기준이 도입되었는데, 이는 작업이 도착한 시점부터 실제로 처음 실행되기 시작할 때까지 걸린 시간을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 세 개의 작업 A, B, C가 동시에 도착했다고 한다. STCF 스케줄링을 적용하면 실행 시간이 가장 긴 작업, 가령 C는 나머지 두 작업이 모두 끝날 때까지 기다려야 비로소 CPU를 할당받을 수 있다. 반환 시간 측면에서는 최적일 수 있지만, 응답 시간 측면에서 보면 형편없는 결과라 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자와의 상호작용이 중요해진 상황에서 STCF는 더 이상 적절한 스케줄링 기법이 아니다. 응답 시간을 개선하면서도 시스템의 전반적인 성능을 높일 수 있는 새로운 스케줄링 알고리즘이 필요해진 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;라운드 로빈(RR)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;응답 시간 문제를 해결하기 위해 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;라운드 로빈(Round Robin)&lt;/b&gt;&lt;/span&gt; 스케줄링이라는 새로운 알고리즘이 도입되었다. RR에서는 각 작업이 완료될 때까지 기다리지 않고, 대신 정해진 시간 동안만 실행한 수 다음 작업으로 전환한다. 이때 각 작업에 할당되는 실행 시간을 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;타임 슬라이스(time slice)&lt;/b&gt;&lt;/span&gt; 또는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;스케줄링 퀀텀(scheduling quantum)&lt;/b&gt;&lt;/span&gt;이라고 부른다. 이 과정은 모든 작업이 끝날 때까지 계속 반복되는데 이런 특성 때문에 RR을 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;타임 슬라이싱(time slicing)&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-filename=&quot;스크린샷 2025-09-15 오전 9.28.33.png&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnaAMj/btsQx3Z575l/69cLqhjGnT0r0Epur7KsCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnaAMj/btsQx3Z575l/69cLqhjGnT0r0Epur7KsCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnaAMj/btsQx3Z575l/69cLqhjGnT0r0Epur7KsCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnaAMj%2FbtsQx3Z575l%2F69cLqhjGnT0r0Epur7KsCk%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;956&quot; height=&quot;780&quot; data-filename=&quot;스크린샷 2025-09-15 오전 9.28.33.png&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임 슬라이스의 길이는 타이머 인터럽트 주기의 배수로 설정해야 한다. 예를 들어 타이머 인터럽트가 10밀리초마다 발생한다면, 타임 슬라이스는 10, 20, 30 등 10의 배수로 정해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RR스케줄링에서 타임 슬라이스의 길이는 매우 중요하다. 일반적으로 &lt;b&gt;타임 슬라이스가 짧을 수록 응답 시간 측면에서의 성능은 좋아진다.&lt;/b&gt; 그러나 &lt;b&gt;너무 짧게 설정하면 문맥 교환 오버헤드가 전체 성능에 부정적인 영향&lt;/b&gt;을 미칠 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;따라서 시스템 설계자는 문맥 교환에 드는 비용과 응답 시간 개선 효과를 고려하여 최적의 타임 슬라이스 길이를 결정해야 한다. 이는 해당 시스템의 특성과 워크로드에 따라 달라질 수 있는 중요한 튜닝 포인트 중 하나다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;입출력 연산의 고려&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 CPU 작업만 고려했지만, 실제 시스템에서는 입출력(I/O) 작업도 빈번하게 발생한다. 이는 스케줄링에 또 다른 도전 과제를 제기한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 프로세스가 I/O 작업을 요청하면, 그 프로세스는 I/O가 완료될 때까지 CPU를 사용하지 않는다. 대신 I/O 작업이 끝나기를 기다리면서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;대기(blocked)&lt;/b&gt;&lt;/span&gt; 상태로 전환된다. 특히 하드 디스크 드라이브 같은 장치에 I/O 요청을 보냈다면, 현재의 I/O 부하에 따라 몇 초 또는 그 이상 블록 상태로 남아있게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때 스케줄러의 역할은 블록된 프로세스 대신 실행할 다른 프로세스를 선택하는 것이다. 이를 통해 CPU가 쉬지 않고 일할 수 있도록 해야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반대로, I/O 작업이 완료되면 스케줄러는 또 다른 결정을 내려야 한다. I/O 완료는 인터럽트를 발생시키고, 이를 처리하기 위해 운영체제가 실행된다. 운영체제는 I/O를 요청했던 프로세스를 대기 상태에서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;준비(ready)&lt;/b&gt;&lt;/span&gt; 상태로 전환시킨다. 여기서 스케줄러는 해당 프로세스를 바로 실행할 것인지, 아니면 다른 프로세스에게 CPU를 할당할 것인지 결정한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇듯 I/O 작업은 스케줄링 문제를 더욱 복잡하게 만든다. 스케줄러는 CPU 사용 패턴뿐만 아니라 I/O 특성까지 고려하여 적절한 프로세스를 선택해야 한다. 이를 통해 전체 시스템의 CPU 활용도와 응답성을 높이는 것이 목표다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;만병통치약은 없다.(No More Oracle)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 입출력을 고려한 기본적인 스케줄링 접근 방식에 대해 살펴보았으니, 마지막으로 남은 가정에 대해 생각해 보겠다. 바로 스케줄러가 각 작업의 실행 시간을 미리 알고 있다는 가정인데, 앞서 언급했듯이 이는 아마도 우리가 할 수 있는 가정 중 가장 비현실적인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 범용 운영체제에서는 개별 작업의 실행 시간을 정확히 예측하는 것이 거의 불가능하다. 프로세스의 행동은 입력 데이터, 사용자 상호작용, 외부 이벤트 등 다양한 요인에 따라 달라지기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 작업의 실행 시간에 대한 사전 지식 없이도 SJF나 STCF처럼 동작하는 스케줄링 알고리즘을 만들 수 있을까? 나아가 이런 알고리즘에 라운드 로빈에서 봤던 것처럼 응답 시간을 개선하는 아이디어도 접목시킬 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안타깝게도 이 모든 조건을 완벽히 만족시키는 만병통치약 같은 솔루션은 존재하지 않다. 스케줄러가 완벽한 정보를 가질 수 없는 상황에서는 어떤 알고리즘을 사용하더라도 트레이드오프가 발생할 수밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현대의 스케줄러들은 과거의 실행 이력, 휴리스틱, 피드백 등을 활용하여 이 문제에 대한 나름의 해법을 모색하고 있다. 비록 최적해는 아닐지라도, 주어진 환경에서 최선의 성능을 끌어내기 위해 노력하고 있는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;3주 차 숙제&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Q1. SJF와 FIFO 스케줄러로 길이가 200인 세 개의 작업을 실행할 때의 응답 시간과 반환 시간을 계산하시오.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIFO&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 20px;&quot;&gt;&lt;b&gt;작업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 20px;&quot;&gt;&lt;b&gt;시작 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 20px;&quot;&gt;&lt;b&gt;종료 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 20px;&quot;&gt;&lt;b&gt;응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 20px;&quot;&gt;&lt;b&gt;반환 시간&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;200&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;B&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;400&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;400&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;C&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;400&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;600&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;400&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 18px;&quot;&gt;&lt;b&gt;600&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SJF&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;b&gt;작업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;b&gt;시작 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;b&gt;종료 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;b&gt;응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;b&gt;반환 시간&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;200&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;B&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;400&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;200&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;400&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;C&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;400&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;600&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;400&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; text-align: center;&quot;&gt;&lt;b&gt;600&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Q2. 이제 작업의 길이를 다르게 하여 동일한 작업을 수행해 보시오: 100, 200, 300.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIFO&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;작업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;시작 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;종료 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;반환 시간&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;B&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;C&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;600&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;600&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SJF&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;작업&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;시작 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;종료 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;반환 시간&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;0&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;B&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;C&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;600&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;300&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;600&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Q3. 이제 RR 스케줄러와 시간 할당량(time-slice)을 1로 하여 동일한 작업을 수행해 보시오.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업은 순서대로 A &amp;rarr; B &amp;rarr; C &amp;rarr; A &amp;rarr; B &amp;rarr; C... 형태로 반복 수행된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;558&quot; data-start=&quot;502&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;511&quot; data-start=&quot;502&quot;&gt;A는 100번&lt;/li&gt;
&lt;li data-end=&quot;521&quot; data-start=&quot;512&quot;&gt;B는 200번&lt;/li&gt;
&lt;li data-end=&quot;537&quot; data-start=&quot;522&quot;&gt;C는 300번&lt;/li&gt;
&lt;li data-end=&quot;558&quot; data-start=&quot;538&quot;&gt;전체 수행 시간은: &lt;b&gt;600&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Q4. 어떤 유형의 워크로드에 대해 SJF가 FIFO와 동일한 반환 시간을 제공하는가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A : 모든 작업의 실행 시간이 동일할 때, 모든 작업의 실행 시간이 다르더라도 도착 순서가 실행 시간 순서와 같을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Q5. 어떤 유형의 워크로드와 시간 할당량에 대해 SJF가 RR과 동일한 응답 시간을 제공하는가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A : 작업 개수가 1개인 경우, 모든 작업의 실행 시간이 동일 + 동시에 도착, 짧은 작업이 먼저 도착&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Q6. 작업 길이가 증가함에 따라 SJF에서 응답 시간은 어떻게 되는가? 시뮬레이터를 사용하여 이 추세를 보여줄 수 있는가?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cpu-scheduling-sim.netlify.app/?utm_source=chatgpt.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cpu-scheduling-sim.netlify.app/?utm_source=chatgpt.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-15 오전 10.12.50.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eg54RZ/btsQwe2mmJI/aRPObbh9lDgqMFxpskvfS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eg54RZ/btsQwe2mmJI/aRPObbh9lDgqMFxpskvfS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eg54RZ/btsQwe2mmJI/aRPObbh9lDgqMFxpskvfS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feg54RZ%2FbtsQwe2mmJI%2FaRPObbh9lDgqMFxpskvfS0%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;836&quot; height=&quot;558&quot; data-filename=&quot;스크린샷 2025-09-15 오전 10.12.50.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;558&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 시간이 &lt;b&gt;선형적으로 증가&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Q7. 시간 할당량이 증가함에 따라 RR에서 응답 시간은 어떻게 되는가? N개의 작업이 주어졌을 때 최악의 응답 시간을 제공하는 수식을 작성할 수 있는가?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A : 시간 할당량 Q가 커질수록, RR의 최악 응답 시간은 선형적으로 증가 RR에서 각 작업은 &lt;b&gt;한 번씩 순서대로 CPU를 받기 때문에&lt;/b&gt;,&lt;br /&gt;&lt;b&gt;마지막 작업의 응답 시간이 가장 늦고, 그것이 최악의 응답 시간&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$(N - 1) &amp;times; Q$$&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N = 작업 수 Q = 시간 할당량&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) N = 5, Q = 20&lt;br /&gt;&amp;rarr; 최악 응답 시간 = (5 - 1) &amp;times; 20 = &lt;b&gt;80&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Q (time-slice)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;최악 응답 시간 (N=5)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;40&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;20&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;80&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;50&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;200&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;100&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;400&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/195</guid>
      <comments>https://gowoong.tistory.com/195#entry195comment</comments>
      <pubDate>Mon, 15 Sep 2025 10:22:31 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 2주차 - 가상화의 세계 - 숙제</title>
      <link>https://gowoong.tistory.com/194</link>
      <description>&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;소개&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 프로그램&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;process-run.py&lt;/span&gt;를 통해 프로그램이 실행되고 CPU를 사용하거나(예: 명령어 추가 수행) I/O를 수행할 때(예: 디스크에 요청을 보내고 완료될 때까지 기다림) 프로세스 상태가 어떻게 변하는지 볼 수 있다. 자세한 내용은 README를 참조하라.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;질문&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q1. 다음 플래그와 함께&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;process-run.py&lt;/span&gt;를 실행하세요:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;5:100,5:100&lt;/span&gt;. CPU 활용률(예: CPU가 사용 중인 시간의 백분율)은 어떻게 되어야 할까요? 왜 그렇게 알 수 있나요?&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;플래그를 사용해 여러분의 생각이 맞는지 확인해 보세요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;A : 5ms 실행하고 100ms 기다리는 것을 2번 하기 때문에 10% 정도로 생각했다. (5:100)의 의미를 잘 몰랐다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-09 오전 9.52.05.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx99SA/btsQobqXIuM/vzVzIPOkKJuRUvVliOr2Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx99SA/btsQobqXIuM/vzVzIPOkKJuRUvVliOr2Ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx99SA/btsQobqXIuM/vzVzIPOkKJuRUvVliOr2Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx99SA%2FbtsQobqXIuM%2FvzVzIPOkKJuRUvVliOr2Ek%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;436&quot; height=&quot;233&quot; data-filename=&quot;스크린샷 2025-09-09 오전 9.52.05.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;233&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;하지만 100%가 나왔다. 보니 I/O 대기가 없다. 그러니 100 가 나오는 것이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q2. 이제 다음 플래그로 실행해 보세요:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;./process-run.py&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;4:100,1:0&lt;/span&gt;. 이 플래그는 4개의 명령어(모두 CPU 사용)를 가진 하나의 프로세스와 I/O를 실행하고 완료될 때까지 기다리는 하나의 프로세스를 지정합니다. 두 프로세스를 모두 완료하는 데 얼마나 걸리나요?&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;를 사용해 여러분의 생각이 맞는지 확인해 보세요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;A : 4:100 은 4개의 명령어를 100%의 확률?로 CPU 작업을 하는 프로세스와 1개의 명령어를 I/O 작업만 하는 프로세스로 생성한다는 것 같다. 그럼 time? 5가 나올 것 같다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.12.17.png&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCq7yK/btsQqHa1SbK/RpbzhrKrrQTkKgC0OF5xf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCq7yK/btsQqHa1SbK/RpbzhrKrrQTkKgC0OF5xf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCq7yK/btsQqHa1SbK/RpbzhrKrrQTkKgC0OF5xf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCq7yK%2FbtsQqHa1SbK%2FRpbzhrKrrQTkKgC0OF5xf1%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;487&quot; height=&quot;246&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.12.17.png&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;11이 나왔다... 왜 그럴까? self.io_finish_times[self.curr_proc].append(clock_tick + self.io_length + 1) 이 코드 때문에 4 + 1 + 1 해서 6만큼 추가로 걸린 것 같다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q3. 프로세스의 순서를 바꿔 보세요:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;1:0,4:100&lt;/span&gt;. 이제 어떻게 되나요? 순서를 바꾸는 것이 중요한가요? 왜 그런가요? (항상 그렇듯이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;를 사용해 여러분의 생각이 맞는지 확인해 보세요)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;7이 나올 것 같다. PID 0의 I/O 요청은 tick 1에 실행되었고, I/O 길이는 기본 5 이기 때문에 I/O 실행은 7초에 다시 진행 그리고 그 사이 CPU 작업이 모두 끝나서 7에 끝난다. Q2에서는 CPU가 4 tick을 먼저 사용하고 I/O 대기가 5 tick부터 시작했다. 그 상태에서 5 + 1 tick을 추가로 기다리니 11 tick에서 종료되었던 것이다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.15.25.png&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp69h0/btsQooKtCxO/kCu1MbIZBsf0kJHJGkUfP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp69h0/btsQooKtCxO/kCu1MbIZBsf0kJHJGkUfP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp69h0/btsQooKtCxO/kCu1MbIZBsf0kJHJGkUfP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp69h0%2FbtsQooKtCxO%2FkCu1MbIZBsf0kJHJGkUfP1%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;476&quot; height=&quot;187&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.15.25.png&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q4. 이제 다른 플래그들을 살펴보겠습니다. 중요한 플래그 중 하나는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;인데, 이는 프로세스가 I/O를 실행할 때 시스템이 어떻게 반응하는지 결정합니다. 플래그를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;SWITCH_ON_END&lt;/span&gt;로 설정하면 한 프로세스가 I/O를 수행하는 동안 시스템은 다른 프로세스로 전환하지 않고 해당 프로세스가 완전히 끝날 때까지 기다립니다. 다음 두 프로세스를 실행할 때 어떤 일이 일어나나요(&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;1:0,4:100&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;SWITCH_ON_END&lt;/span&gt;), 하나는 I/O를 수행하고 다른 하나는 CPU 작업을 수행합니다?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프로세스 전환이 없다고 하면 처음 I/O 작업에서 1 + 5 + 1초 기다릴 것이고 그럼 7 tick에 끝난다. 이후 CPU 작업 4 tick을 하면 11 tick에 끝날 것 같다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.26.25.png&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lB5Q8/btsQrrr1lts/KR2C83ODohWFZxWV0XEtq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lB5Q8/btsQrrr1lts/KR2C83ODohWFZxWV0XEtq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lB5Q8/btsQrrr1lts/KR2C83ODohWFZxWV0XEtq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlB5Q8%2FbtsQrrr1lts%2FKR2C83ODohWFZxWV0XEtq1%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;489&quot; height=&quot;245&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.26.25.png&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q5. 이제 같은 프로세스를 실행하되, 한 프로세스가 I/O를 기다리는(&lt;span&gt;WAITING&lt;/span&gt;) 동안 다른 프로세스로 전환되도록 switching 동작을 설정해 봅시다(&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;1:0,4:100&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;SWITCH_ON_IO&lt;/span&gt;). 이제 어떤 일이 일어나나요?&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;를 사용해 여러분의 생각이 맞는지 확인해 보세요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Q3처럼 7 tick에 끝날 것이다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.27.20.png&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqyfjT/btsQoMYDl6q/llTk5tN05UzTOqbRJFg3v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqyfjT/btsQoMYDl6q/llTk5tN05UzTOqbRJFg3v1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqyfjT/btsQoMYDl6q/llTk5tN05UzTOqbRJFg3v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqyfjT%2FbtsQoMYDl6q%2FllTk5tN05UzTOqbRJFg3v1%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;473&quot; height=&quot;188&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.27.20.png&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q6. 또 다른 중요한 동작은 I/O가 완료될 때 수행할 작업입니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-I&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;IO_RUN_LATER&lt;/span&gt;를 사용하면 I/O가 완료될 때 그것을 실행한 프로세스가 반드시 즉시 실행되는 것은 아닙니다. 오히려 그 시점에 실행 중이던 프로세스가 계속 실행됩니다. 이러한 프로세스 조합을 실행하면 어떤 일이 일어나나요? (&lt;span&gt;./process-run.py&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;3:0,5:100,5:100,5:100&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;SWITCH_ON_IO&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-I&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;IO_RUN_LATER&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-c&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-p&lt;/span&gt;) 시스템 자원이 효율적으로 활용되고 있나요?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;실행 중이던 프로세스가 계속 실행된다? 그럼 I/O 작업 중에 CPU 작업이 일어나지 않을 것 같고 각 작업을 별도로 실행해서 31 tick에 끝난다?&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.28.39.png&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be4WhI/btsQoeVtAZ7/7PjiaHVsY8kK1BXSjggd80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be4WhI/btsQoeVtAZ7/7PjiaHVsY8kK1BXSjggd80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be4WhI/btsQoeVtAZ7/7PjiaHVsY8kK1BXSjggd80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe4WhI%2FbtsQoeVtAZ7%2F7PjiaHVsY8kK1BXSjggd80%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;667&quot; height=&quot;532&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.28.39.png&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q7. 이제 같은 프로세스를 실행하되,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-I&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;IO_RUN_IMMEDIATE&lt;/span&gt;를 설정해 I/O를 실행한 프로세스를 즉시 실행하도록 해 봅시다. 이 동작은 어떻게 다른가요? 방금 I/O를 완료한 프로세스를 다시 실행하는 것이 왜 좋은 생각일 수 있을까요?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;7 tick 작업을 하는 것이 3번 실행돼서 21 tick이 나올 것 같다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.33.06.png&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VsNu3/btsQqVfPFdT/SkwTKsrq3bSnkSD6H3jZ11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VsNu3/btsQqVfPFdT/SkwTKsrq3bSnkSD6H3jZ11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VsNu3/btsQqVfPFdT/SkwTKsrq3bSnkSD6H3jZ11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVsNu3%2FbtsQqVfPFdT%2FSkwTKsrq3bSnkSD6H3jZ11%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;699&quot; height=&quot;387&quot; data-filename=&quot;스크린샷 2025-09-09 오전 10.33.06.png&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;IO_RUN_LATER일 경우&lt;/b&gt;&lt;/span&gt;: Tick 7에 I/O 완료돼도 바로 실행 안 될 수 있음 다른 프로세스가 READY 상태면 걔가 먼저 실행됨&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;IO_RUN_IMMEDIATE일 경우&lt;/b&gt;&lt;/span&gt;: I/O 끝난 순간 바로 그 프로세스를 실행시킴 불필요한 컨텍스트 스위칭 줄이고, 응답 시간 짧아짐&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q8. 이제 무작위로 생성된 프로세스로 실행해 봅시다:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;3:50,3:50&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;3:50,3:50&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-l&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;3:50,3:50&lt;/span&gt;. 추적 결과가 어떻게 될지 예측해 보세요.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-I&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;IO_RUN_IMMEDIATE&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;플래그와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-I&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;IO_RUN_LATER&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;플래그를 사용할 때 어떤 일이 일어나나요?&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;SWITCH_ON_IO&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-S&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;SWITCH_ON_END&lt;/span&gt;를 사용할 때 어떤 일이 일어나나요?&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Seed&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;-S&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-I&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Total Time&lt;/td&gt;
&lt;td&gt;CPU Busy&lt;/td&gt;
&lt;td&gt;CPU %&lt;/td&gt;
&lt;td&gt;IO Busy&lt;/td&gt;
&lt;td&gt;IO %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;IO&lt;/td&gt;
&lt;td&gt;IMMEDIATE&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;53.33%&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;66.67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;END&lt;/td&gt;
&lt;td&gt;IMMEDIATE&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;44.44%&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;55.56%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;END&lt;/td&gt;
&lt;td&gt;LATER&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;44.44%&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;55.56%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;IO&lt;/td&gt;
&lt;td&gt;LATER&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;53.33%&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;66.67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;IO&lt;/td&gt;
&lt;td&gt;IMMEDIATE&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;62.50%&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;87.50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;END&lt;/td&gt;
&lt;td&gt;IMMEDIATE&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;33.33%&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;66.67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;END&lt;/td&gt;
&lt;td&gt;LATER&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;33.33%&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;66.67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;IO&lt;/td&gt;
&lt;td&gt;LATER&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;62.50%&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;87.50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;IO&lt;/td&gt;
&lt;td&gt;IMMEDIATE&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;52.94%&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;64.71%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;END&lt;/td&gt;
&lt;td&gt;IMMEDIATE&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;37.50%&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;62.50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;END&lt;/td&gt;
&lt;td&gt;LATER&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;37.50%&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;62.50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;IO&lt;/td&gt;
&lt;td&gt;LATER&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;50.00%&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;61.11%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-end=&quot;1897&quot; data-start=&quot;1858&quot; data-ke-size=&quot;size20&quot;&gt;SWITCH_ON_IO vs SWITCH_ON_END&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2051&quot; data-start=&quot;1898&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1982&quot; data-start=&quot;1898&quot;&gt;&lt;b&gt;SWITCH_ON_IO&lt;/b&gt;가 항상 &lt;b&gt;Total Time을 단축&lt;/b&gt;시킴
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1982&quot; data-start=&quot;1946&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1982&quot; data-start=&quot;1946&quot;&gt;I/O 요청 즉시 다른 프로세스로 전환하여 CPU 낭비가 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2051&quot; data-start=&quot;1983&quot;&gt;&lt;b&gt;SWITCH_ON_END&lt;/b&gt;는 프로세스가 끝날 때까지 CPU를 잡고 있어서 &lt;b&gt;CPU Idle Time 증가&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;2095&quot; data-start=&quot;2053&quot; data-ke-size=&quot;size20&quot;&gt;IO_RUN_IMMEDIATE vs IO_RUN_LATER&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2223&quot; data-start=&quot;2096&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2186&quot; data-start=&quot;2096&quot;&gt;IO_RUN_IMMEDIATE는 &lt;b&gt;I/O 완료 즉시 실행&lt;/b&gt;하여 응답성 향상
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2186&quot; data-start=&quot;2146&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2186&quot; data-start=&quot;2146&quot;&gt;특히 I/O 뒤에 짧은 작업(io_done)이 이어질 경우 매우 유리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;2223&quot; data-start=&quot;2187&quot;&gt;IO_RUN_LATER는 효율 낮음, I/O 완료 후 대기&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;2248&quot; data-start=&quot;2225&quot; data-ke-size=&quot;size20&quot;&gt;시드 값(-s)이 주는 영향&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2322&quot; data-start=&quot;2249&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2322&quot; data-start=&quot;2249&quot;&gt;명령 순서가 다르기 때문에:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2322&quot; data-start=&quot;2269&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2302&quot; data-start=&quot;2269&quot;&gt;I/O가 겹치면 둘 다 BLOCKED &amp;rarr; 전체 시간 증가&lt;/li&gt;
&lt;li data-end=&quot;2322&quot; data-start=&quot;2305&quot;&gt;I/O가 분산되면 효율 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;설정&lt;/td&gt;
&lt;td&gt;효과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SWITCH_ON_IO&lt;/td&gt;
&lt;td&gt;CPU 낭비 최소화, Total Time 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IO_RUN_IMMEDIATE&lt;/td&gt;
&lt;td&gt;응답 시간 향상, 효율적인 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SWITCH_ON_END&lt;/td&gt;
&lt;td&gt;CPU 독점 시간 증가, 비효율&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IO_RUN_LATER&lt;/td&gt;
&lt;td&gt;I/O 후 실행 지연, 시스템 idle 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-s 변경&lt;/td&gt;
&lt;td&gt;명령 순서 달라짐 &amp;rarr; 결과 변화 유발&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/194</guid>
      <comments>https://gowoong.tistory.com/194#entry194comment</comments>
      <pubDate>Tue, 9 Sep 2025 10:45:26 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 2주차 - 가상화의 세계 part.3</title>
      <link>https://gowoong.tistory.com/193</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 CPU 가상화를 위해 제한적 직접 실행이라는 기법을 사용한다. 이 기법의 기본 아이디어는 프로그램을 CPU에서 직접 실행시키되, 운영체제가 CPU 제어권을 잃지 않도록 프로세스의 행동에 제한을 두는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;기본 원리 : 제한적 직접 실행&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 실행할 때 운영체제는 다음과 같은 절차를 따른다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로세스를 위한 메모리를 할당하고 프로그램을 메모리에 적재한다.&lt;/li&gt;
&lt;li&gt;CPU를 사용자 모드로 전환하고 프로그램의 main() 함수로 이동한다.&lt;/li&gt;
&lt;li&gt;프로그램이 실행되면서 시스템 콜이 호출되면 커널 모드로 전환되고 운영체제가 해당 요청을 처리한다.&lt;/li&gt;
&lt;li&gt;요청 처리가 완료되면 다시 사용자 모드로 돌아가 프로그램 실행을 계속한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 직접 실행 방식은 CPU 가상화를 구현하는 데 있어 몇 가지 문제를 야기한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫째&lt;/b&gt;, 프로그램을 그대로 실행시킨다면 운영체제가 원하지 않는 동작을 프로그램이 수행하지 않는다는 것을 어떻게 보장할 수 있을까? 프로그램에 아무런 제한을 두지 않는다면, 운영체제의 통제를 벗어나는 일이 발생할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;둘째&lt;/b&gt;, 프로세스를 실행할 때 운영체제가 CPU 사용을 적절히 분배하려면 프로그램 실행을 중단하고 다른 프로세스로 전환할 수 있어야 한다. 이를 시분할(time sharing)이라고 하는데, 직접 실행 방식으로는 이를 구현하기 어렵다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;시분할(time sharing)&lt;/b&gt;이란 여러 프로세스가 CPU를 돌아가며 사용하도록 하여, 마치 동시에 실행되는 것처럼 보이게 하는 기술을 말하는데, 이는 CPU 가상화를 위해 필수적인 개념이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제들에 대한 해답을 찾아가는 과정에서 우리는 CPU 가상화에 필요한 사항들을 더 잘 이해하게 될 것이다. 그리고 &amp;ldquo;제한적 직접 실행&amp;rdquo;이라는 이름에 담긴 의미, 즉 프로그램 실행에 일정한 제한을 가하는 것의 중요성도 깨닫게 될 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 프로그램 실행을 제한하지 않는다면 운영체제는 아무것도 통제할 수 없게 되고, 그저 단순한 라이브러리에 지나지 않게 된다. 이는 운영체제가 제 역할을 하기 위해서는 매우 바람직하지 않은 상황이 될 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;문제점 1: 제한된 연산&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;그러나 프로세스가 모든 연산을 수행하도록 허용하면 시스템의 안정성과 보안에 문제가 생길 수 있다. 따라서 사용자 모드에서 실행되는 코드는 특정 연산에 제한을 받는다. 제한된 연산을 수행하려면 시스템 콜을 통해 운영체제에 요청해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어는 두 가지 실행 모드를 제공하여 운영체제를 지원한다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;사용자 모드(user mode)&lt;/b&gt;&lt;/span&gt;: 응용 프로그램이 실행되는 모드로, 하드웨어 자원에 대한 접근이 제한된다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;커널 모드(kernel mode)&lt;/b&gt;&lt;/span&gt;: 운영체제가 실행되는 모드로, 모든 하드웨어 자원에 접근할 수 있는 권한을 가진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 제한된 연산을 수행하려면 사용자 모드에서 커널 모드로 전환되어야 한다. 이를 위해 하드웨어는 다음과 같은 특수 명령어를 제공한다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;trap&lt;/span&gt;&lt;/b&gt;: 사용자 모드에서 커널 모드로 전환하는 명령어&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;return-from-trap&lt;/span&gt;&lt;/b&gt;: 커널 모드에서 사용자 모드로 돌아가는 명령어&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 운영체제는 트랩이 발생했을 때 실행할 코드의 주소를 담고 있는 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;트랩 테이블(trap table)&lt;/span&gt;&lt;/b&gt;을 하드웨어에 알려주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 하드웨어의 지원을 바탕으로, 프로세스는 제한된 연산이 필요할 때 시스템 콜을 호출하여 &lt;b&gt;trap&lt;/b&gt;을 발생시키고, 운영체제는 요청받은 연산을 대신 수행한 뒤 &lt;b&gt;return-from-trap&lt;/b&gt;을 통해 다시 프로세스에게 제어를 넘겨준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;왜 시스템 콜은 일반적인 프로시져 콜처럼 생겼을까?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;open()&lt;/b&gt;이나 &lt;b&gt;read()&lt;/b&gt;같은 시스템 콜을 호출하는 코드를 보면, C언어의 일반적인 함수 호출 코드와 매우 유사하다. 그렇다면 시스템 콜과 함수 호출이 동일한 형태라면, 운영체제는 어떻게 이 둘을 구분하여 시스템 콜에 맞는 동작을 수행할 수 있겠는가?&lt;br /&gt;답은 간단하다. 시스템 콜도 사실 함수 호출이지만, 그 안에는 trap이라는 특수 명령어가 숨어있다.&lt;br /&gt;예를 들어 open() 시스템 콜을 호출하는 코드를 생각해 보겠다. 이 코드가 실행되면 사실 C 라이브러리 내부의 open() 함수가 호출된다. 이 라이브러리 함수는 운영체제 커널과 미리 약속된 규칙에 따라, open()에 전달된 인자들과 시스템 콜 번호를 정해진 위치(스택이나 특정 레지스터)에 저장한다. 그러고 나서 trap 명령어를 실행하여 운영체제로 제어를 넘긴다.&lt;br /&gt;운영체제는 trap 핸들러를 실행하여 요청받은 시스템 콜을 처리하고, 그 결과를 다시 약속된 위치에 저장한다. 이제 trap 명령어에 의해 실행이 중단되었던 라이브러리 함수로 다시 제어가 돌아오면, 라이브러리 함수는 이 결과값을 받아 시스템 콜을 호출한 원래 프로그램에 반환한다.&lt;br /&gt;여기서 주목할 점은, 시스템 콜을 호출하는 라이브러리 함수의 코드는 대부분 어셈블리어로 작성되어 있다는 것이다. 이는 함수의 인자와 반환값을 처리하고 trap을 실행하는 과정이 하드웨어마다 조금씩 다르기 때문에, 이 부분은 어셈블리어로 구현되어야 한다.&lt;br /&gt;다행히 프로그램 개발자가 직접 어셈블리 코드를 작성할 필요는 없다. 시스템 콜을 C 함수처럼 호출하기만 하면, 나머지는 라이브러리가 알아서 처리해 주기 때문이다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;문제점 2: 프로세스 간 전환&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;운영체제는 여러 프로세스를 번갈아 가며 실행해야 하므로, CPU 제어권을 다시 얻어 문맥 교환을 수행할 수 있어야 한다. 이를 위해 하드웨어 타이머를 사용하여 주기적으로 인터럽트를 발생시킨다. 타이머 인터럽트가 발생하면 운영체제는 CPU 제어권을 되찾아 현재 프로세스의 상태를 저장하고 다음에 실행할 프로세스의 정보를 복원하여 실행을 계속한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #29313d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;협조 방식: 시스템 콜 호출시 까지 대기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기의 몇몇 운영체제들은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;협조적(cooperative)&lt;/b&gt; &lt;/span&gt;스케줄링 방식을 사용했다. 대표적인 예로는 초기 버전의 매킨토시 운영체제나 제록스 알토 시스템이 있다. 이 방식에서 운영체제는 프로세스들이 공정하게 CPU를 양보할 것이라고 가정한다. 만약 어떤 프로세스가 너무 오랫동안 CPU를 독점할 것 같으면, 그 프로세스는 정기적으로 CPU 사용권을 운영체제에게 넘겨줘서 다른 프로세스들이 실행될 수 있게 해야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;자연스럽게 다음과 같은 질문을 할 수 있다. &amp;ldquo;이 이상적인 상황에서 프로세스는 어떻게 자발적으로 CPU를 양보할까?&amp;rdquo; 대부분의 프로세스는 파일 입출력, 네트워크 통신, 새 프로세스 생성 등의 작업을 위해 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;시스템 콜&lt;/span&gt;&lt;/b&gt;을 자주 호출하게 된다. 프로세스가 시스템 콜을 호출하면 자연스럽게 CPU 제어권이 운영체제로 넘어가게 된다. 심지어 어떤 운영체제들은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;yield&lt;/b&gt;&lt;/span&gt;라는 시스템 콜을 제공하기도 하는데, 이 호출은 특별히 할 일이 없어도 자발적으로 CPU를 양보하는 역할을 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;응용 프로그램의 오작동 처리하기&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;운영체제는 종종 비정상적으로 동작하는 프로세스를 적절히 처리해야 한다. 비정상적으로 동작하는 프로세스란 오작동 하도록 의도적으로 설계되었거나 의도치 않은 버그로 인해 해서는 안 될 행위를 하려는 프로세스를 말한다. 현재 시스템에서 운영체제가 이러한 프로세스들을 처리하는 방법은 간단하다. 해당 프로세스를 종료시킨다. 원 스트라이크에 아웃! 과도하게 엄격하다고 볼 수 있을지는 모르겠으나. 프로세스가 허락된 영역 외의 메모리에 대한 접근을 시도하거나. 불법적인 명령어를 실행할 때 해당 프로세스를 종료시키는 것 외에. 이를 처리하는 다른 방법이 특별히 존재하지 않는다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 프로세스가 잘못된 행동을 하면 대개 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;트랩(trap)&lt;/b&gt;&lt;/span&gt;이 발생하고 CPU 제어권이 운영체제에게 넘어간다. 예를 들어 0으로 나누기를 시도한다거나 접근 권한이 없는 메모리 영역에 접근하려 하면 트랩이 발생하는 식이다. 트랩이 발생하면 운영체제는 문제를 일으킨 프로세스를 강제 종료시킬 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러니까 협조적 스케줄링 방식에서는 운영체제가 시스템 콜이나 트랩(불법 연산)이 발생하기를 기다렸다가 CPU를 다시 획득하는 방식이다. 하지만 이런 수동적인 방법에는 문제가 있다. 만약 어떤 프로세스가 악의적으로든, 실수로든 무한 루프에 빠져서 전혀 시스템 콜을 호출하지 않는다면 문제가 발생할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비협조 방식: 운영체제가 제어권 확보&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;이제, 프로세스들이 자발적으로 CPU를 양보하지 않고 계속 실행을 독점하려 할 때는 어떻게 해야 할까? 안타깝게도 하드웨어의 추가적인 지원 없이는 운영체제가 할 수 있는 일이 거의 없다. 사실 협조적 스케줄링에서 프로세스가 무한 루프에 빠졌을 때 운영체제가 할 수 있는 유일한 방법은 컴퓨터를 재부팅하는 것뿐이었다. 이는 오랫동안 모든 컴퓨터 문제를 해결하는 만능키 같은 역할을 해왔다. 하지만 CPU 제어권을 되찾기 위해 매번 재부팅을 한다는 건 그리 현명한 방법은 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;핵심 질문 : 협조 없이 제어를 얻는 방법&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;프로세스가 비협조적인 상황에서도 CPU의 할당을 위한 제어권을 어떻게 하면 획득 할 수 있을까? 어떻게 하면 악의적인 프로세스가 컴퓨터를 장악하는 것을 방지할 수 있을까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;이 문제에 대한 해결책은 수십 년 전 초기 컴퓨터 시스템을 설계했던 엔지니어들에 의해 고안되었다. 그 해결책은 바로 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;타이머 인터럽트(timer interrupt)&lt;/b&gt;&lt;/span&gt;를 이용하는 것이다. 타이머 장치를 프로그래밍하여 일정 시간(보통 몇 밀리초)마다 인터럽트를 발생시킨다. 타이머 인터럽트가 발생하면, 현재 실행 중이던 프로세스는 즉시 중단되고 미리 정해진 &lt;b&gt;인터럽트 핸들러(interrupt handler)&lt;/b&gt;로 제어가 넘어간다. 이 핸들러는 운영체제의 코드다. 따라서 이 시점에서 운영체제는 CPU를 다시 장악하게 되고, 원하는 작업(현재 프로세스를 중단하고 다른 프로세스로 전환하는 등)을 수행할 수 있게 되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;팁 : 타이머 인터럽트를 이용한 제어권 확보&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;타이머 인터럽트 &lt;/b&gt;기능을 사용하면 프로세스가 비협조적으로 행동하는 상황에서도 운영체제가 실행될 수 있다. 타이머 인터럽트는 운영체제가 컴퓨터를 제어하는 데 있어 근간이 되는 핵심기능이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 운영체제는 타이머 인터럽트가 발생했을 때 어떤 코드를 실행해야 하는지 하드웨어에 알려주어야 한다. 이 작업은 시스템이 부팅되는 동안 이루어진다. 부팅 과정에서 운영체제는 타이머를 시작시키고, 타이머 인터럽트가 발생하면 자신에게 제어가 넘어올 것이라는 사실을 알기에 안심하고 사용자 프로그램을 실행할 수 있게 되는 것이다. 물론 운영체제는 필요할 때 타이머를 멈출 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;타이머 인터럽트가 발생했을 때 하드웨어도 해야 할 일이 있다. 현재 실행 중이던 프로그램의 상태(레지스터 값들)를 저장해서, 추후 인터럽트 처리가 끝나고 해당 프로그램으로 복귀할 때 그대로 실행을 재개할 수 있도록 해야 한다. 이 과정은 시스템 콜이 호출되었을 때 일어나는 일과 매우 유사하다. 다양한 레지스터 값들이 커널 스택에 저장되었다가, &amp;lsquo;return-from-trap&amp;rsquo; 명령어에 의해 복원된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문맥의 저장과 복원&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제가 시스템 콜이나 타이머 인터럽트를 통해 CPU 제어권을 되찾았다면, 그 다음에는 중요한 결정을 내려야 한다. 바로 &amp;ldquo;현재 실행 중이던 프로세스를 계속 실행할 것인가, 아니면 다른 프로세스로 전환할 것인가&amp;rdquo;를 결정해야 하는 것이다. 이 결정은 운영체제 내의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;스케줄러(scheduler)&lt;/b&gt;&lt;/span&gt;라는 모듈이 담당한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 스케줄러가 다른 프로세스로 전환하기로 결정했다면, 운영체제는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;문맥 교환(context switch)&lt;/b&gt;&lt;/span&gt;이라 불리는 작업을 수행한다. 문맥 교환의 기본 개념은 간단하다. 현재 실행 중인 프로세스의 레지스터 값들을 모두 저장하고(주로 커널 스택에), 다음에 실행할 프로세스의 레지스터 값들을 복원하는 것이다. 이렇게 하면 &amp;lsquo;return-from-trap&amp;rsquo; 명령어가 실행될 때, 원래 실행 중이던 프로세스로 돌아가는 것이 아니라 새로운 프로세스로 가서 실행을 시작하게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문맥 교환을 위해 운영체제는 저수준 어셈블리어를 사용한다. 현재 프로세스의 범용 레지스터, 프로그램 카운터(PC), 커널 스택 포인터 등을 모두 저장하고, 다음 프로세스의 값들을 복원한다. 이렇게 하면 인터럽트가 발생했을 때의 프로세스에서 문맥 교환 코드를 호출하고, 새 프로세스의 문맥으로 복귀하게 되는 것이다. &amp;lsquo;return-from-trap&amp;rsquo;이 실행되면, 새 프로세스가 실행 중인 프로세스가 되고, 문맥 교환이 완료된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문맥 교환 과정에서는 레지스터 값의 저장과 복원이 두 번 일어난다는 점에 주목해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;첫 번째는 타이머 인터럽트 등에 의해 인터럽트가 발생했을 때다. 이때는 하드웨어가 자동으로 현재 프로세스의 사용자 레지스터를 커널 스택에 저장한다.&lt;/li&gt;
&lt;li&gt;두 번째는 운영체제가 프로세스 전환을 결정했을 때다. 이때는 운영체제 커널이 현재 프로세스의 나머지 레지스터(커널 레지스터)를 프로세스의 PCB(Process Control Block)에 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 마치 원래 실행 중이던 프로세스 A가 아니라, 새 프로세스 B에서 인터럽트가 발생한 것처럼 보이게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병생실행으로 인한 문제&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 인터럽트나 시스템 콜 처리 도중에 다른 인터럽트가 발생하면 까다로운 상황이 연출될 수 있다. 이를 방지하기 위해 운영체제는 인터럽트 처리 중에는 추가 인터럽트를 불가능하게 하거나, 내부 자료구조에 대한 동시 접근을 막기 위한 잠금 기법을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;운영체제는 인터럽트나 트랩을 처리하는 도중에 또 다른 인터럽트가 발생할 경우를 매우 신중하게 다뤄야 한다. &lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;운영체제가 취할 수 있는 가장 간단한 방법은 인터럽트를 처리하는 동안 추가 인터럽트를 막는 것이다. 이렇게 하면 인터럽트 처리 루틴이 실행되는 동안에는 다른 인터럽트가 CPU로 전달되지 않는다. 물론 운영체제는 이런 인터럽트 차단을 매우 신중하게 사용해야 한다. 인터럽트를 너무 오랫동안 비활성화하면 중요한 이벤트를 놓칠 수 있고, 시스템 성능에도 안 좋은 영향을 줄 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;운영체제는 또한 &amp;lsquo;락(lock)&amp;rsquo;이라고 불리는 정교한 기법들을 사용하여, 커널 내부의 자료구조에 동시에 접근하는 것을 방지한다. 이런 락 메커니즘은 커널 내에서 여러 활동이 동시에 일어날 수 있도록 허용하면서도, 자료의 일관성을 유지할 수 있게 해 준다. 하지만 병행성을 다루는 장에서 보게 될 것처럼, 이런 락 기법은 상당히 복잡해질 수 있고, 때로는 찾기 힘든 버그를 만들어내기도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 우리는 CPU 가상화를 구현하기 위한 중요한 저수준 기법인 &amp;lsquo;제한적 직접 실행&amp;rsquo;에 대해 알아보았다. 이 기법의 핵심 아이디어는 간단하다. 바로 실행하고자 하는 프로그램을 CPU에서 직접 실행시키되, 운영체제가 CPU 제어권을 잃지 않도록 프로세스의 행동에 제한을 걸어두는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 접근 방식은 우리 일상생활에서도 찾아볼 수 있다. 아기가 있는 집이라면 아기 보호 장치를 설치하는 것에 익숙할 것이다. 위험한 물건이 있는 서랍을 잠그고, 전기 콘센트에 안전 덮개를 씌우는 식이다. 이렇게 안전 장치를 마련해 두면 대부분의 위험 요인은 차단되므로, 아기가 자유롭고 안전하게 돌아다닐 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;운영체제도 이와 비슷한 방식으로 CPU에 안전 메커니즘을 마련한다. 시스템이 부팅될 때 트랩 핸들러를 설정하고, 타이머 인터럽트를 시작시킨 다음, 프로세스들이 제한된 모드에서만 실행되도록 한다. 이렇게 하면 운영체제는 프로세스를 효율적으로 실행시키면서도, 특별한 작업(프로세스의 CPU 독점, 다른 프로세스로의 전환 등)이 필요할 때만 개입할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이로써 우리는 CPU 가상화의 기본 개념을 배웠습니다. 그러나 아직 중요한 질문이 남아 있다. &amp;ldquo;특정 시점에 어떤 프로세스를 실행해야 할까?&amp;rdquo; 이는 운영체제의 스케줄러가 답해야 할 질문이며, 우리가 다음에 공부할 주제이기도 하다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/193</guid>
      <comments>https://gowoong.tistory.com/193#entry193comment</comments>
      <pubDate>Tue, 9 Sep 2025 09:46:18 +0900</pubDate>
    </item>
    <item>
      <title>[AI &amp;amp; LLM] Amazon Q Developer 헤커톤 회고</title>
      <link>https://gowoong.tistory.com/192</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 9월 5~6일 2일간 진행했던 Amazon Q Developer 헤커톤의 후기와 해당 경험을 바탕으로 느낀 점과 앞으로의 발전 방향에 대해 작성해보려고 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;헤커톤 후기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS와 메가존 클라우드의 후원으로 진행된 이번 헤커톤은 AWS의 Q Developer를 이용한 바이브 코딩으로 서비스를 개발하는 과정이었다. 9월 5일은 온라인으로 9월 6일은 센터필드 AWS에서 진행했던 행사로 나는 크래프톤 정글 8기 동기들과 함께 5인 팀을 구성했다. 현재 다들 취업 준비를 열심히 하고 있고 나 또한 면접이나 코테 준비 등으로 바쁜 와중에 진행되었던 헤커톤이어서 그런지 우리 팀은 사전에 주제도 심도 있게 논의하지 않았다. 금요일 시작하자 말자 생각나는 주제를 선정했다. 우리는 일단 이 헤커톤이 그렇게 많은 사람과 팀이 참가할지 몰랐고 이렇게 수준이 높은 헤커톤일 줄 몰랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q Developer를 비롯한 바이브 코딩 툴 역시 많이 다뤄보지 못했고 어떻게 사용해야 더 좋은 퍼포먼스를 낼 수 있을지 고민해본 적이 없었던 것 같다. 그랬기 때문에 단순히 구현하는 용도로 주로 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 힘들었던 점은 5명이 협업을 바이브코딩으로 진행해야 한다는 점이었다. 일단 우리는 next.js를 이용해 백엔드와 프론트를 전부 하나의 레포에서 진행했다. 그리고 팀원 간의 역할 분리도 좀 부족했다고 생각한다. 그 이유가 5명이 전부 비슷한 부분을 수정하고 있으니 당연히 계속해서 충돌이 나고 이를 수정하는데 많은 시간을 사용해야 했다. 가뜩이나 시간이 없는데 충돌을 해결하고 서로 다르게 생성된 코드를 수정하다 보니 기존의 코드에서 계속 변경이 일어나 일관성이 보장되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 계속해서 문제가 있는 상태로 헤커톤을 마무리했다. 우리는 배포에서 특히 문제가 많아 완성을 하지 못했다. 이 점이 매우 아쉬웠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;반성과 성찰&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 단지 Q Developer가 아직 개발에 그렇게 좋지 않다고 생각했는데 1차 심사를 통과한 4개의 팀의 발표와 Q Developer 사용 사례를 보고 나서는 내 생각이 잘못됐는구나 우리가 제대로 이해하지 않고 그냥 코드만 작성하게 시키고 있었다는 것을 느꼈다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q Developer를 잘 사용하기 위해 페르소나를 만들기도 하고 MCP를 사용하거나 Q Developer를 병렬로 연결하는 등 나로서는 생각치도 못한 방법이 나왔다. 그런 방법들을 보면서 많이 반성했다. 단지 내가 제대로 사용하지 않고 도구 탓을 하고 있었다. 듣기로는 2차 헤커톤이 예정되어 있다고 하는데 나도 다시 도전을 하고 싶다는 생각이 들었다. 그리고 이번에는 정말 탄탄히 준비해야겠다고 생각했다. 생성형 AI를 제대로 사용하기 위한 방법들을 조사하고 실습하고 나에게 딱 맞는 도구로 튜닝하도록 연습한 뒤 리벤지 매치를 준비해야겠다고 생각했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;여담&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 여담이지만 협업을 바이브 코딩으로 진행할 때 팀원간의 충돌이 매우 많이 일어났는데 과연 이런 문제가 우리 팀만 있었는지 알고 싶기는 했다. 만약 모든 팀들의 공통적인 문제였다면 바이브 코딩으로 협업을 할 때 이러한 문제를 해결하거나 완화하거나 사전에 예방할 수 있는 그런 서비스를 만들어 보는 것도 좋지 않을까? 그리고 그런 서비스를 기획하여 다음 헤커톤에 참여할 수 있지 않을까? 생각한다. 마지막으로 2일간 고생한 우리 팀원들에게 감사를 표한다.&lt;/p&gt;</description>
      <category>AI &amp;amp; LLM</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/192</guid>
      <comments>https://gowoong.tistory.com/192#entry192comment</comments>
      <pubDate>Mon, 8 Sep 2025 10:54:27 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 2주차 - 가상화의 세계 part.2</title>
      <link>https://gowoong.tistory.com/191</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 API는 운영체제(OS)가 애플리케이션에 제공하는 인터페이스로, 사용자 프로그램이 운영체제의 다양한 기능을 사용할 수 있도록 해주는 시스템 호출이다. 이는 프로세스의 생성, 종료, 정지, 제개와 같은 기본적인 관리 작업뿐만 아니라, 프로세스 상태 정보 제공, 메모리 할당, 파일 접근 등 가상 머신 관련 기능을 요청하는데 필수적이다. 또한, 프로세스 API는 프로세스뿐만 아니라 모듈(실행 파일 또는 DLL)과 드라이버에 대한 정보 조회 및 메모리 사용량 데이터 수집과 같은 고급 기능도 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 fork(), wait(), exec()는 프로세스의 생성, 실행, 대기 및 종료를 다루는 데 필수적인 API 이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;fork() 시스템 콜&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork() 시스템 콜은 현재 실행 중인 프로세스(부모 프로세스)와 쪽 같은 복사본인 새로운 프로세스(자식 프로세스)를 생성하는 기능을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;용어 설명:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프로세스&lt;/b&gt;: 실행 중인 프로그램의 인스턴스로, 자체적인 메모리 공간과 시스템 자원을 가짐.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시스템 콜&lt;/b&gt;: 운영 체제에게 특정 작업을 요청하기 위해 프로그램이 사용하는 인터페이스.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PID (프로세스 식별자)&lt;/b&gt;: 각 프로세스를 고유하게 식별하기 위해 운영 체제가 할당하는 번호.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;fork()로 생성된 자식 프로세스는 부모 프로세스의 메모리 공간을 복사하여 가지게 된다. 여기에는 다음과 같은 영역이 포함된다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터&lt;/b&gt;: 전역 변수와 정적 변수가 저장되는 영역&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드&lt;/b&gt;: 프로그램의 실행 가능한 기계어 코드가 저장되는 영역&lt;/li&gt;
&lt;li&gt;&lt;b&gt;힙&lt;/b&gt;: 동적으로 할당되는 메모리 영역&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스택&lt;/b&gt;: 함수 호출과 지역 변수 등을 관리하는 영역&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 부모 프로세스와 자식 프로세스는 서로 다른 PID를 가지며, 독립적으로 실행된다. 이를 통해 동시에 여러 작업을 수행할 수 있게 되어 시스템의 효율성과 응답성을 높일 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1757294297609&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

int main(int argc, char *argv[]){
    printf(&quot;hello world (pid:%d)\n&quot;, (int) getpid());
    int rc = fork();
    if (rc &amp;lt; 0) { // fork failed; exit
        fprintf(stderr, &quot;fork failed\n&quot;);
        exit(1);
    } else if (rc == 0) { // child (new process)
        printf(&quot;hello, I am child (pid:%d)\n&quot;, (int) getpid());
    } else { // parent goes down this path (main)
        printf(&quot;hello, I am parent of %d (pid:%d)\n&quot;, rc, (int) getpid());
    }
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-08 오전 10.17.20.png&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tBNka/btsQmQtqnhU/1yXAQ41ahRMJnT0t87J4fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tBNka/btsQmQtqnhU/1yXAQ41ahRMJnT0t87J4fK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tBNka/btsQmQtqnhU/1yXAQ41ahRMJnT0t87J4fK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtBNka%2FbtsQmQtqnhU%2F1yXAQ41ahRMJnT0t87J4fK%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;56&quot; data-filename=&quot;스크린샷 2025-09-08 오전 10.17.20.png&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;wait() 시스템 콜&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;wait() 시스템 콜은 부모 프로세스가 자식 프로세스의 종료를 대기하는 기능을 한다. 자식 프로세스가 종료될 때까지 부모 프로세스의 실행을 잠시 멈춘다. 이를 통해 자원의 회수 및 자식 프로세스의 종료 상태 값을 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757294578725&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;sys/wait.h&amp;gt;

int main(int argc, char *argv[]){
    printf(&quot;hello world (pid:%d)\n&quot;, (int) getpid());
    int rc = fork();
    if (rc &amp;lt; 0) { // fork failed; exit
        fprintf(stderr, &quot;fork failed\n&quot;);
        exit(1);
    } else if (rc == 0) { // child (new process)
        printf(&quot;hello, I am child (pid:%d)\n&quot;, (int) getpid());
    } else { // parent goes down this path (main)
        int wc = wait(NULL);
        printf(&quot;hello, I am parent of %d (wc:%d) (pid:%d)\n&quot;,rc, wc, (int) getpid());
    }
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-08 오전 10.22.43.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csrIB9/btsQpDTyGdm/YpBNCUkw9xBznMNhHIl9b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csrIB9/btsQpDTyGdm/YpBNCUkw9xBznMNhHIl9b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csrIB9/btsQpDTyGdm/YpBNCUkw9xBznMNhHIl9b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsrIB9%2FbtsQpDTyGdm%2FYpBNCUkw9xBznMNhHIl9b0%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;362&quot; height=&quot;61&quot; data-filename=&quot;스크린샷 2025-09-08 오전 10.22.43.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;마지막으로, exec() 시스템 콜&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exec() 계열의&lt;span style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt; 시스템 콜은 프로세스가 새로운 프로그램을 실행하게 해 준다. exec() 호출은 현재 프로세스의 이미지를 새로운 프로그램의 이미지로 교체한다. 이는 프로세스의 메모리 내용을 완전히 새로운 프로그램으로 바꾸는 것을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757294773587&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include &amp;lt;sys/wait.h&amp;gt;

int main(int argc, char *argv[]){
    printf(&quot;hello world (pid:%d)\n&quot;, (int) getpid());
    int rc = fork();
    if (rc &amp;lt; 0) { // fork failed; exit
        fprintf(stderr, &quot;fork failed\n&quot;);
        exit(1);
    } else if (rc == 0) { // child (new process)
        printf(&quot;hello, I am child (pid:%d)\n&quot;, (int) getpid());
        char *myargs[3];
        myargs[0] = strdup(&quot;wc&quot;); // program: &quot;wc&quot; (word count)
        myargs[1] = strdup(&quot;p3.c&quot;); // argument: file to count
        myargs[2] = NULL; // marks end of array
        execvp(myargs[0], myargs); // runs word count
        printf(&quot;this shouldn&amp;rsquo;t print out&quot;);
    } else { // parent goes down this path (main)
        int wc = wait(NULL);
        printf(&quot;hello, I am parent of %d (wc:%d) (pid:%d)\n&quot;, rc, wc, (int) getpid());
    }
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-08 오전 10.25.48.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FLSz5/btsQoKlnITc/qKuuHRXEQgxrv5Iluh0XIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FLSz5/btsQoKlnITc/qKuuHRXEQgxrv5Iluh0XIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FLSz5/btsQoKlnITc/qKuuHRXEQgxrv5Iluh0XIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFLSz5%2FbtsQoKlnITc%2FqKuuHRXEQgxrv5Iluh0XIK%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;362&quot; height=&quot;73&quot; data-filename=&quot;스크린샷 2025-09-08 오전 10.25.48.png&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;왜, 이런 API를?&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 체제의 핵심 기능 중 하나는 프로세스 관리이다. 프로세스는 운영 체제 상에서 실행 중인 프로그램의 인스턴스로, 각각 고유한 주소 공간과 시스템 자원을 할당받는다. 이러한 프로세스들을 효율적으로 생성, 실행, 관리, 종료하는 것은 시스템의 안정성과 성능에 직접적인 영향을 미친다. 이를 위해 운영 체제는 fork(), wait(), exec()와 같은 프로세스 관리 API를 제공한다.&lt;/p&gt;
&lt;b&gt;복잡한 시스템의 단순화&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fork(), wait(), exec()와 같은 API들은 복잡한 멀티프로세싱 시스템을 구축할 때 필수적이다. 이들 API를 사용함으로써 개발자는 새로운 프로세스를 생성하고(fork()), 프로세스의 실행을 조정(wait()), 그리고 새로운 프로그램을 실행(exec())할 수 있다. 이 과정에서, 각 API는 복잡한 내부 작업을 추상화하고 개발자에게 단순화된 인터페이스를 제공한다. 결과적으로, 개발자는 운영 체제의 복잡한 내부 메커니즘을 자세히 알지 못해도 프로세스 관리 기능을 쉽게 구현할 수 있다.&lt;/p&gt;
&lt;b&gt;멀티태스킹과 병렬 처리 지원&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨팅 환경은 멀티태스킹과 병렬 처리를 필요로 한다. fork()를 사용하여 프로세스를 복제하고, exec()로 새로운 작업을 실행시키며, wait()으로 자식 프로세스의 실행 완료를 동기화함으로써, 개발자는 여러 작업을 동시에 처리할 수 있는 애플리케이션을 만들 수 있다. 이는 웹 서버와 같이 동시에 여러 요청을 처리해야 하는 애플리케이션에서 특히 중요하다.&lt;/p&gt;
&lt;b&gt;자원 관리 및 장애 격리&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 API를 사용하면 프로세스 간의 자원 공유와 통신을 정교하게 관리할 수 있다. 예를 들어, fork() 후 exec()를 사용하면, 자식 프로세스는 부모 프로세스로부터 독립된 메모리 공간을 할당받게 되며, 이는 장애 격리(fault isolation)를 가능하게 한다. 하나의 프로세스에서 발생한 문제가 다른 프로세스에 영향을 미치지 않도록 하는 것이다. 이러한 장애 격리 메커니즘은 시스템의 안정성과 보안을 향상한다.&lt;/p&gt;
&lt;b&gt;효율적인 프로세스 관리&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wait() 시스템 콜은 부모 프로세스가 자식 프로세스의 종료를 기다리게 함으로써, 자식 프로세스가 시스템 자원을 반환하고 종료 상태를 부모에게 알리는 과정을 관리한다. 이는 프로세스가 시스템 자원을 낭비하지 않고 효율적으로 활용하도록 보장한다. 프로세스의 정상 종료 및 비정상 종료를 관리하는 것은 시스템의 성능과 안정성을 유지하는 데 중요하다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/191</guid>
      <comments>https://gowoong.tistory.com/191#entry191comment</comments>
      <pubDate>Mon, 8 Sep 2025 10:30:47 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 2주차 - 가상화의 세계 part.1</title>
      <link>https://gowoong.tistory.com/190</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;프로세스(Process)&lt;/b&gt;&lt;/span&gt;는 운영체제의 핵심 개념 중 하나이다. 프로세스는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;실행 중인 프로그램&lt;/b&gt;&lt;/span&gt;을 의미하며, 프로그램 자체는 디스크에 저장된 명령어와 데이터의 집합이다. 운영체제는 이 명령어와 데이터를 실행하여 프로그램을 작동시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 여러 프로그램을 동시에 실행하기를 원한다. 예를 들어, 웹 브라우저, 이메일 ,게임, 음악 플레이어 등을 동시에 실행하는 것이다. 운영체제는 실제로 한정된 CPU를 가지고 있음에도 불구하고, 여러 개의 프로세스가 동시에 실행되는 것처럼 만드는 기술, 즉 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;CPU 가상화&lt;/span&gt;&lt;/b&gt;를 통해 이를 가능케 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 환상을 만들기 위해, 운영체제는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;시분할(time sharing)&lt;/b&gt;&amp;nbsp;&lt;/span&gt;방식을 사용하여 한 프로세스를 잠시 실행한 후 다른 프로세스로 전환하는 작업을 반복한다. 이 과정을 통해, 여러 프로세스가 동시에 실행되는 것처럼 보이게 한다. 하지만 이 방식은 프로세스마다 성능이 다소 저하될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제가 이런 작업을 잘 처리하기 위해서는 저수준의 기술적 방법, 즉 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;메커니즘(mechanism)&lt;/span&gt;&lt;/b&gt;과, 어떤 프로세스를 언제 실행시킬지 결정하는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;정책(policy)&lt;/b&gt;&lt;/span&gt;이 필요하다. 메커니즘은 운영체제가 필요한 기능을 구현하는 방법을 말하며, 정책은 운영체제가 어떤 결정을 내리기 위한 규칙이나 알고리즘이다. 예를 들어, 여러 프로그램이 실행 가능할 때 어느 것을 먼저 실행할지 결정하는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;스케줄링 정책(scheduling policy)&lt;/b&gt;&lt;/span&gt;이 이에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 운영체제는 복잡한 작업을 처리하면서도 사용자에게 편리하고 효율적인 환경을 제공하기 위해 설계되었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;시분할과 공간분할&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;시분할&lt;/b&gt;&lt;/span&gt;은 운영체제가 자원을 효율적으로 공유하는 중요한 방법 중 하나이다. 여러 사용자나 프로그램이 동시에 자원을 사용하는 것처럼 보이게 하기 위해, 운영체제는 짧은 시간 동안 한 사용자(또는 프로그램)에게 자원을 할당하고, 그 다음 사용자에게 순차적으로 할당하는 방식으로 자원(CPU나 네트워크 링크 등)을 관리한다. 이와 반대되는 개념으로 공간 분할이 있다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;공간 분할(space sharing)&lt;/b&gt;&lt;/span&gt;에서는 자원의 '공간'을 여러 사용자나 프로그램에게 나누어 준다. 디스크가 좋은 예인데, 디스크의 경우 특정 공간(블록)이 한 파일에 할당되면, 그 파일이 삭제되기 전까지 다른 파일이 그 공간을 사용할 가능성이 낮다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;프로세스의 개념&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;운영체제는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;실행 중인 프로그램&lt;/b&gt;&lt;/span&gt;을 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;프로세스&lt;/b&gt;&lt;/span&gt;라는 개념을 제공한다. 프로세스를 간단하게 표현하면, 실행 중에 접근하거나 영향을 받은 자원의 목록이라고 할 수 있다.&lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;프로세스를 이해하려면 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;하드웨어 상태(machine state)&lt;/b&gt;&lt;/span&gt;를 이해해야 한다. 프로그램은 실행 중에 하드웨어 상태를 읽거나 변경한다. 이떄 가장 중요한 하드웨어 구성 요소는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;메모리&lt;/b&gt;&lt;/span&gt;이다. 메모리는 명령어와 데이터를 저장한다. 프로세스가 접근할 수 있는 메모리는 프로세스의 구성 요소이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;레지스터도 프로세스의 하드웨어 상태를 구성하는 중요한 요소이다. 많은 명령어가 레지스터를 직접 읽거나 변경한다. 따라서 프로세스를 실행하려면 레지스터도 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;프로세스의 하드웨어 상태를 구성하는 레지스터 중 특별한 레지스터가 있다. &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;프로그램 카운터(PC)&lt;/span&gt;&lt;/b&gt;는 실행 중인 명령어를 알려준다. PC는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;명령어 포인터(IP)&lt;/b&gt;&lt;/span&gt;라고도 불린다. &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;스택 포인터(SP)&lt;/span&gt;&lt;/b&gt;와 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;프레임 포인터(FP)&lt;/b&gt;&lt;/span&gt;는 함수의 변수와 리턴 주소를 저장하는 스택을 관리하는 데 사용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 내용:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스는 실행 중인 프로그램의 개념.&lt;/li&gt;
&lt;li&gt;프로세스는 메모리, 레지스터, 파일 등의 자원을 사용.&lt;/li&gt;
&lt;li&gt;프로그램 카운터는 실행 중인 명령어를 알려준다.&lt;/li&gt;
&lt;li&gt;스택 포인터와 프레임 포인터는 스택을 관리하는 데 사용.&lt;/li&gt;
&lt;li&gt;프로세스는 영구 저장장치에 접근할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;정책&lt;/b&gt;&lt;/span&gt;과 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;구현&lt;/b&gt;&lt;/span&gt;의 분리&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;핵심 개념:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;고수준 정책&lt;/b&gt;: 운영체제가 &lt;b&gt;무엇&lt;/b&gt;을 해야 하는지 결정하는 규칙&lt;/li&gt;
&lt;li&gt;&lt;b&gt;저수준 기법&lt;/b&gt;: 운영체제가 &lt;b&gt;어떻게&lt;/b&gt; 작업을 수행하는지 구현하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설계 패러다임:&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;고수준 정책과 저수준 기법을 분리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이점:&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정책 변경 용이&lt;/b&gt;:&amp;nbsp;정책 변경 시 기법 변경 필요 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모듈성 향상&lt;/b&gt;:&amp;nbsp;코드 재사용 및 유지 관리 용이&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성 향상&lt;/b&gt;:&amp;nbsp;새로운 정책 및 기법 추가 용이&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정책&lt;/b&gt;:&amp;nbsp;어느 프로세스를 실행할 것인가?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기법&lt;/b&gt;:&amp;nbsp;스케줄링 알고리즘 (예: 라운드 로빈, 우선 순위 기반)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결론:&lt;/b&gt;&lt;br /&gt;고수준 정책과 저수준 기법 분리는 운영체제 설계의 중요한 패러다임이며, 이는 모듈성, 확장성, 유지 관리 용이성을 향상시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;프로세스 API&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제가 반드시 API로 제공해야 하는 몇몇 기본 기능은 다음과 같다. 이 API 등은 형태는 다르지만 모든 현대 운영체제에서 제공된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성(Create): 쉘에 명령어를 입력하거나, 응용 프로그램의 아이콘을 더블 클릭하여 프로그램을 실행시키면, 운영체제는 새로운 프로세스를 생성한다.&lt;/li&gt;
&lt;li&gt;제거(Destroy): 많은 프로세스는 실행되고 할 일을 다하면 스스로 종료하지만 스스로 종료하지 않는 경우가 있기 때문에 운영체제는 불필요한 프로세스를 종료시키는 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;대기(Wait): 특정 프로세스의 작업이 끝날 때까지 기다려야 할 때가 있어 여러 종류의 대기 인터페이스가 제공된다. 이는 다른 프로세스와의 작업을 동기화하거나, 특정 조건이 충족될 때까지 기다리는 데 사용될 수 있다.&lt;/li&gt;
&lt;li&gt;각종 제어(Miscellaneous Control): 프로세스를 일시정지 하거나 다시 시작하는 등의 여러 가지 제어 기능들이 제공된다. 이는 프로세스의 동작을 조절하거나 문제를 해결하는데 도움이 된다.&lt;/li&gt;
&lt;li&gt;상태(Status): 프로세스의 현재 상태 정보를 얻어내는 인터페이스도 제공된다. 이는 프로세스가 얼마 동안 실행되었는지 또는 프로세스가 어떤 상태에 있는지 등이 포함된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;프로세스 생성 : 좀 더 자세하게&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 실행하기 위해 운영체제가 가장 먼저 하는 일은 프로그램의 코드와 정적 데이터(예를 들어. 초기값을 가지는 변수)를 메모리 즉, 프로세스의 주소 공간으로 불러오는 것이다. 이를 '&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;로딩(loading)&lt;/b&gt;&lt;/span&gt;' 이라고 한다. 프로그램은 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;디스크&lt;/span&gt;&lt;/b&gt;나 요즘에는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;SSD&lt;/b&gt;&lt;/span&gt;에 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;실행 파일&lt;/span&gt;&lt;/b&gt; 형태로 저장되어 있는데, 운영체제는 이 파일에서 필요한 부분을 읽어 메모리에 적재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;677&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R5OnG/btsQpXqM4ZP/3PeCny3k4iQO9sDU7bFs70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R5OnG/btsQpXqM4ZP/3PeCny3k4iQO9sDU7bFs70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R5OnG/btsQpXqM4ZP/3PeCny3k4iQO9sDU7bFs70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR5OnG%2FbtsQpXqM4ZP%2F3PeCny3k4iQO9sDU7bFs70%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;677&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;677&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 운영체제는 프로그램 실행 전에 코드와 데이터 전체를 메모리에 로딩했지만, 최근의 운영체제는 이를 지연시켜 필요할 때만 메모리에 적재하는 방식을 사용한다. 이를 정확히 이해하려면 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;페이징(paging)&lt;/b&gt;&lt;/span&gt;과 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;스와핑(swapping)&lt;/span&gt;&lt;/b&gt;에 대한 지식이 필요하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;스택(stack) 메모리 할당&lt;/b&gt;: C 프로그램은 지역 변수, 함수 인자, 리턴 주소 등을 저장하기 위해 스택을 사용한다. 운영체제는 이를 위한 메모리를 할당하고, main() 함수의 인자(argc, argv)로 스택을 초기화한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;힙(heap) 메모리 할당&lt;/b&gt;: C 프로그램에서 힙은 동적으로 할당되는 메모리 공간. malloc()으로 메모리를 요청하고 free()로 반환하는 식으로 사용되며, 주로 가변 크기의 자료구조(연결 리스트, 해시 테이블, 트리 등)를 위해 쓰인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;입출력 초기화&lt;/b&gt;: 운영체제는 프로그램의 입출력을 위한 초기화 작업도 수행한다. 예를 들어 유닉스 시스템에서는 각 프로세스에 표준 입력(STDIN), 표준 출력(STDOUT), 표준 에러(STDERR)에 대한 &lt;b&gt;파일 디스크립터&lt;/b&gt;를 제공&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 준비 과정이 모두 끝나면, 운영체제는 프로그램의 시작점(entry point), 즉 main() 함수로 제어를 이동시켜 프로그램 실행을 시작한다. 이를 통해 CPU의 제어권이 새로 생성된 프로세스로 넘어가고, 프로그램이 실행되기 시작하는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;프로세스 상태&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;상태(state)&lt;/b&gt;&lt;/span&gt;를 단순화 하면 다음 세 상태 중 하나에 존재할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;실행 (Running)&lt;/b&gt;: 실행 상태에서 프로세스는 프로세서에서 실행 중이다. 즉, 프로세스는 명령어를 실행하고 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;준비 (Ready)&lt;/b&gt;: 준비 상태에서 프로세스는 실행할 준비가 되어 있지만 운영체제가 다른 프로세스를 실행하고 있는 등의 이유로 대기 중이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대기 (Blocked)&lt;/b&gt;: 프로세스가 다른 사건을 기다리는 동안 프로세스의 수행을 중단시키는 연산이다. 흔한 예 : 프로세스가 디스크에 대한 입출력 요청을 하였을 때 프로세스는 입출력이 완료될 때까지 대기 상태가 되고, 다른 프로세스가 실행 상태로 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V1rIQ/btsQomSAvBC/oJrnybqhSBGCgdm59z60y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V1rIQ/btsQomSAvBC/oJrnybqhSBGCgdm59z60y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V1rIQ/btsQomSAvBC/oJrnybqhSBGCgdm59z60y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV1rIQ%2FbtsQomSAvBC%2FoJrnybqhSBGCgdm59z60y1%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;481&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 '준비' 상태와 '실행' 상태를 운영제제의 정책에 따라 이동한다. 프로세스는 운영체제의 스케줄링 정책에 따라 스케줄이 되면 '준비' 상태에서 '실행' 상태로 전이한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;실행&amp;rsquo; 상태에서 &amp;lsquo;준비&amp;rsquo; 상태로의 전이는 프로세스가 나중에 다시 스케줄 될 수 있는 상태가 되었다는 것을 의미한다. 프로세스가 입출력 요청 등의 이유로 대기 상태가 되면 요청 완료 등의 이벤트가 발생할 때까지 대기 상태로 유지된다. 이벤트가 발생하면 프로세스는 다시 준비 상태로 전이되고 운영체제의 결정에 따라 바로 다시 실행될 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 개의 프로세스가 어떻게 전이될 수 있는지를 한번 알아보자. 먼저 실행 중인 두 프로세스가 있다고 했을 때, 각 프로세스가 오직 CPU만 사용하고 입출력을 행하지 않을 때의 프로세스 상태 추이를 나타내면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXMM0O/btsQqmD1QOG/GtCdtDXsPAgF2KtC5VQQ2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXMM0O/btsQqmD1QOG/GtCdtDXsPAgF2KtC5VQQ2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXMM0O/btsQqmD1QOG/GtCdtDXsPAgF2KtC5VQQ2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXMM0O%2FbtsQqmD1QOG%2FGtCdtDXsPAgF2KtC5VQQ2k%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;368&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 프로세스가 어느 정도 실행한 후에 입출력을 요청한다. 그 순간 프로세스는 대기 상태가 되고 다른 프로세스에게 실행 기회를 준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SLYps/btsQomdVw6t/a8Ot8S2jx7RKIB9ED1bVx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SLYps/btsQomdVw6t/a8Ot8S2jx7RKIB9ED1bVx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SLYps/btsQomdVw6t/a8Ot8S2jx7RKIB9ED1bVx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSLYps%2FbtsQomdVw6t%2Fa8Ot8S2jx7RKIB9ED1bVx1%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;347&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;347&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Process0은 입출력을 요청하고 요청한 작업이 완료되기를 기다린다&lt;/li&gt;
&lt;li&gt;프로세스는 디스크를 읽거나 네트워크로부터 패킷을 기다릴 때 대기 상태로 전이한다.&lt;/li&gt;
&lt;li&gt;운영체제는 Process0이 CPU를 사용하지 않는다는 것을 감지하고, Process1을 실행시킨다.&lt;/li&gt;
&lt;li&gt;Process1이 실행되는 동안 입출력이 완료되고 Process0은 준비 상태로 다시 전이된다.&lt;/li&gt;
&lt;li&gt;Process1은 종료되고, Process0이 실행되어 종료된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 간단한 예에서조차 운영체제가 내려햐 할 결정은 매우 많다. 우선 시스템이 Process0이 입출력을 요청할 때 Process1의 실행 여부를 결정해야 한다. Process1을 실행키로 한 결정은 CPU를 계속 동작시키므로 자원 이용률을 높인다. 또한, 시스템은 Process0이 요청한 입출력이 완료되었을 때, Process0을 바로 실행하지 않고 실행 중이던 Process1을 계속 실행하였다. 이게 좋은 결정이었는지는 확실하지 않다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;자료구조&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제 역시 일종의 프로그램이기에, 다른 프로그램들처럼 여러 정보를 저장하고 관리하기 위한 자료구조를 갖고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,운영체제는 프로세스의 상태를 파악하기 위해 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;프로세스 리스트&lt;/b&gt;&lt;/span&gt;라는 자료구조를 유지하는데, 이 리스트에는 실행 대기 중인 프로세스들의 정보가 담겨 있다. 또한 현재 실행 중인 프로세스가 무엇인지 알기 위한 별도의 자료구조도 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아울러 운영체제는 입출력 작업 등으로 인해 대기(blocked) 상태에 있는 프로세스도 추적해야 한다. 해당 입출력이 완료되면, 운영체제는 이 정보를 토대로 대기 중이던 프로세스를 깨워 실행 가능한 상태(ready)로 만들어 줄 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 실행을 멈출 때(예: 인터럽트 등으로 인해), 운영체제는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;레지스터 문맥(register context&lt;/b&gt;&lt;/span&gt;)이라는 자료구조에 해당 프로세스의 레지스터 값들을 저장한다. 나중에 이 프로세스를 다시 실행할 때는 저장된 레지스터 값들을 복원함으로써 중단된 지점부터 실행을 재개할 수 있게 된다. 이를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;문맥 교환(context switch)&lt;/b&gt;&lt;/span&gt;이라고 하며, 추후 더 자세히 다룬다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로세스의 상태로는 실행(running), 준비(ready), 대기(blocked) 외에도 몇 가지가 더 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 시스템에서는 프로세스가 생성 중일 때의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;초기(initial)&lt;/b&gt;&lt;/span&gt; 상태를 별도로 둔다. 또 프로세스가 종료되었지만 메모리 상에 아직 남아있는 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;종료(final)&lt;/span&gt;&lt;/b&gt; 상태가 있는데, 유닉스 계열 시스템에서는 이를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;좀비(zombie)&lt;/b&gt;&lt;/span&gt;&amp;nbsp;상태라고 부른다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;종료 상태는 해당 프로세스가 성공적으로 실행을 마쳤는지 등을 다른 프로세스(주로 부모 프로세스)가 검사하는 데 활용된다. 부모 프로세스는 자식 프로세스의 종료를 기다리는 시스템 콜(예: wait())을 호출하는데, 이를 통해 운영체제는 종료된 프로세스의 자원을 정리할 수 있게 된다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/190</guid>
      <comments>https://gowoong.tistory.com/190#entry190comment</comments>
      <pubDate>Mon, 8 Sep 2025 10:03:28 +0900</pubDate>
    </item>
    <item>
      <title>[OSTEP] 스터디 1주차 - 아주 쉬운 세가지 이야기</title>
      <link>https://gowoong.tistory.com/189</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제 아주 쉬운 세 가지 이야기 - OSTEP를 읽고 정리하는 스터디를 하게 되었다. 크래프톤 정글 8기 307반 동기들과 함께하는 스터디 시작한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;운영체제 개요&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램을 쉽게 실행하고, 프로그램 간의 메모리 공유를 가능케 하고, 장치와 상호작용을 가능케 하고, 다양한 흥미로운 일을 할 수 있게 하는 소프트웨어가 있다. 시스템을 사용하기 편리하게 하면서 정확하고 올바르게 동작시킬 책임이 있기 때문에 이 소프트웨어를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;운영체제(Operating System, OS)&lt;/b&gt;&lt;/span&gt;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 앞에서 언급한 일을 하기 위하여 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;가상화(Virtualization)&lt;/b&gt;&lt;/span&gt;라고 불리는 기법을 사용한다. 운영체제는 프로세서, 메모리, 또는 디스크와 같은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;물리적(Physical)&lt;/b&gt;&lt;/span&gt;인 자원을 이용하여 일반적이고, 강력하고, 사용이 편리한 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;가상(Virtual)&lt;/b&gt;&lt;/span&gt; 형태의 자원을 생성한다. 때문에 운영체제를 때로는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;가상 머신(Virtual Machine)&lt;/b&gt;&lt;/span&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 프로그램의 프로그램 실행, 메모리 할당, 파일 접근과 같은 가상 머신과 관련된 기능들을 운영체제에게 요청할 수 있도록, 운영체제는 사용자에게 API를 제공한다. 보통 운영체제는 응용 프로그램이 사용 가능한 수백 개의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;시스템 콜&lt;/b&gt;&lt;/span&gt;을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상화는 많은 프로그램들이 CPU를 공유하여, 동시에 실행될 수 있게 한다. 프로그램들이 각자 명령어와 데이터를 접근할 수 있게 한다. 프로그램들이 디스크 등의 장치를 공유할 수 있게 한다. 이러한 이유로 운영체제는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;자원 관리자(Resource Manager)&lt;/b&gt;&lt;/span&gt;라고도 불린다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;CPU 가상화&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제는 어떻게 자원을 가상화하는가? 운영체제가 가상화를 하는 이유가 아닌 방법에 초점을 맞춘다. 운영체제가 가상화 효과를 얻기 위하여 기법과 정책은 무엇인가? 운영체제는 이들을 어떻게 효율적으로 구현하는가? 어떤 하드웨어 지원이 필요한가?&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;sys/time.h&amp;gt;
#include &amp;lt;assert.h&amp;gt;
#include &quot;common.h&quot;

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, &quot;사용법: cpu &amp;lt;문자열&amp;gt;\n&quot;);
        exit(1);
    }
    char *str = argv[1];
    while (1) {
        Spin(1);
        printf(&quot;%s\n&quot;, str);
    }
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 많은 일을 하지 않는다. 하는 일은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;code&gt;Spin()&lt;/code&gt;&lt;/span&gt;을 호출하는 것이다. &lt;code&gt;Spin()&lt;/code&gt;은 1초 동안 실행된 후 리턴하는 함수이다. 그런 후 사용자가 명령어 라인으로 전달한 문자열을 출력한다. 이러한 작업을 무한히 반복한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 &lt;code&gt;cpu.c&lt;/code&gt;라는 이름으로 저장하고 단일 프로세서 시스템에서 컴파일하고 실행시킨다고 가정하자. 다음과 같은 출력을 볼 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.13.14.png&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ae8ur/btsQhsLxOsr/zvaj6lQ3hnbVM6h85pOp0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ae8ur/btsQhsLxOsr/zvaj6lQ3hnbVM6h85pOp0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ae8ur/btsQhsLxOsr/zvaj6lQ3hnbVM6h85pOp0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAe8ur%2FbtsQhsLxOsr%2Fzvaj6lQ3hnbVM6h85pOp0K%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;296&quot; height=&quot;185&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.13.14.png&quot; data-origin-width=&quot;296&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 같은 작업에 대한 여러 인스턴스를 동시에 실행시켜 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.16.57.png&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eoqu6V/btsQeAYw334/cOvYSXu75xaOs6KPKh8no1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eoqu6V/btsQeAYw334/cOvYSXu75xaOs6KPKh8no1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eoqu6V/btsQeAYw334/cOvYSXu75xaOs6KPKh8no1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feoqu6V%2FbtsQeAYw334%2FcOvYSXu75xaOs6KPKh8no1%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;349&quot; height=&quot;256&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.16.57.png&quot; data-origin-width=&quot;349&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세서가 하나밖에 없음에도 프로그램 4개가 모두 동시에 실행되는 것처럼 보인다. 하드웨어의 도움을 받아 운영체제가 시스템의 매우 많은 수의 가상 CPU가 존재하는 듯한 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;환상(Illusion)을&lt;/b&gt;&lt;/span&gt; 만들어 낸 것이다. 하나의 CPU 또는 소규모 CPU 집합을 무한 개의 CPU가 존재하는 것처럼 변환하여 동시에 많은 수의 프로그램을 실행시키는 것을 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;CPU 가상화(Virtualizing the CPU)라&lt;/b&gt; &lt;/span&gt; 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다수의 프로그램을 동시에 실행시킬 수 있는 기능은 새로운 종류의 문제를 발생시킨다. 예를 들어 특정 순간에 두 개의 프로그램이 실행되기를 원한다면 누가 실행되어야 하는가? 이 질문은 운영체제의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;정책(Policy)에&lt;/b&gt;&lt;/span&gt; 달려있다. 운영체제의 여러 부분에서 이러한 유형의 문제에 답하기 위한 정책들이 사용된다. 운영체제가 구현한 동시에 다수의 프로그램을 실행시키는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;기본적인 기법에&lt;/b&gt;&lt;/span&gt; 대해 다룬다. 즉 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;자원 관리자&lt;/b&gt;&lt;/span&gt; 로서의 운영체제의 역할을 다룬다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;메모리 가상화&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 우리가 사용하고 있는 컴퓨터에서의 물리 메모리(Physical Memory) 모델은 매우 단순하다. 바이트의 배열이다. 메모리를 읽기 위해서는 데이터에 주소(Address)를 명시해야 한다. 메모리에 쓰기를 위해서는 주소와 데이터를 명시해야 한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &quot;common.h&quot;

int main(int argc, char *argv[]) {
    int *p = malloc(sizeof(int)); // 정수형 포인터 p에 메모리 할당
    assert(p != NULL); // p가 NULL이 아닌지 확인
    printf(&quot;(%d) p의 메모리 주소: %08x\n&quot;, getpid(), (unsigned) p); // 프로세스 ID와 p의 메모리 주소 출력
    *p = 0; // p가 가리키는 값을 0으로 초기화
    while (1) {
        Spin(1); // 1초 대기
        *p = *p + 1; // p가 가리키는 값을 1 증가
        printf(&quot;(%d) p: %d\n&quot;, getpid(), *p); // 프로세스 ID와 p가 가리키는 값 출력
    }
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리는 프로그램이 실행되는 동안 항상 접근된다. 프로그램은 실행 중에 자신의 모든 자료 구조를 메모리에 유지하고 load와 store 또는 기타 메모리 접근을 위한 명령어를 통해 자료구조에 접근한다. 명령어 역시 메모리에 존재한다. 명령어를 반입할 때마다 메모리가 접근된다. 위 코드를 살펴보자. 이 프로그램은 몇 가지 작업을 수행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.27.42.png&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZf40N/btsQfjoNwUU/iA24zQeoFr3cKKt5s3vrQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZf40N/btsQfjoNwUU/iA24zQeoFr3cKKt5s3vrQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZf40N/btsQfjoNwUU/iA24zQeoFr3cKKt5s3vrQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZf40N%2FbtsQfjoNwUU%2FiA24zQeoFr3cKKt5s3vrQK%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;304&quot; height=&quot;130&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.27.42.png&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;우선 메모리를 할당받는다.&lt;/li&gt;
&lt;li&gt;그런 후 할당받은 메모리의 주소를 출력한다.&lt;/li&gt;
&lt;li&gt;새로 할당받은 메모리의 첫 슬롯에 숫자 0을 넣는다.&lt;/li&gt;
&lt;li&gt;마지막으로 루프로 진입하여 1초 대기 후&lt;/li&gt;
&lt;li&gt;변수 p가 가리키는 주소에 저장되어 있는 값을 1 증가시킨다.&lt;br /&gt;출력할 때마다 실행 중인 프로그램의 프로세스 식별자 (PID)라 불리는 값이 함께 출력된다. PID는 프로세스의 고유의 값이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 프로그램을 여러 번 실행시켜 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.33.43.png&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GfyNT/btsQgNvV6rA/aKrZJT1RHRGL2SuJYlnJc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GfyNT/btsQgNvV6rA/aKrZJT1RHRGL2SuJYlnJc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GfyNT/btsQgNvV6rA/aKrZJT1RHRGL2SuJYlnJc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGfyNT%2FbtsQgNvV6rA%2FaKrZJT1RHRGL2SuJYlnJc0%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;292&quot; height=&quot;214&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.33.43.png&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;프로그램들은 같은 메모리 주소를 할당받지만(내 컴퓨터는 멀티 코어라 좀 다른 가?), 각각이 독립적으로 값을 갱신한다. 각 프로그램은 물리 메모리를 다른 프로그램과 공유하는 것이 아니라 각자 자신의 메모리를 가지고 있는 것처럼 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제가 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;메모리 가상화(Virtualizing Memory)&lt;span style=&quot;color: #666666;&quot;&gt;를&lt;/span&gt;&lt;/span&gt;&lt;/b&gt; 하기 때문에 이런 현상이 생긴다. 각 프로세스는 자신만의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;가상 주소 공간&lt;/b&gt; &lt;span style=&quot;color: #333333;&quot;&gt;(&lt;/span&gt;&lt;b&gt;Virtual Address Space&lt;/b&gt;&lt;/span&gt;, 때로 그냥 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;주소 공간(Address Space)&lt;span style=&quot;color: #666666;&quot;&gt;이라고도&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; 불림)을 갖는다. 운영체제는 이 가상 주소 공간을 컴퓨터의 물리 메모리로 매핑(Mapping)한다. 하나의 프로그램이 수행하는 각종 메모리 연산은 다른 프로그램의 주소 공간에 영향을 주지 않는다. 실행 중인 프로그램의 입장에서는, 자기 자신만의 물리 메모리를 갖는 셈이다. 실제로는 물리 메모리는 공유 자원이고, 운영체제에 의해 관리된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병행성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책의 다른 주요 주제는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병행성(Concurrency)&lt;span style=&quot;color: #666666;&quot;&gt;이다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; 병행성은 프로그램이 여러 작업을 동시에 수행하려고 할 때 발생하는 문제들을 의미한다. 병행성 문제는 운영체제 만의 문제는 아니다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;멀티 쓰레드&lt;/b&gt;&lt;/span&gt; 프로그램도 동일한 문제를 가진다. 멀티 쓰레드 프로그램을 예로 들어 확인해 보자&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &quot;common.h&quot;
volatile int counter = 0;
int loops;

void *worker(void *arg) {
    for (int i = 0; i &amp;lt; loops; i++) {
        counter++;
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, &quot;사용법: threads &amp;lt;값&amp;gt;\n&quot;);
        exit(1);
    }
    loops = atoi(argv[1]);
    pthread_t p1, p2;
    printf(&quot;초기 값 : %d\n&quot;, counter);

    Pthread_create(&amp;amp;p1, NULL, worker, NULL);
    Pthread_create(&amp;amp;p2, NULL, worker, NULL);
    Pthread_join(p1, NULL);
    Pthread_join(p2, NULL);
    printf(&quot;최종 값 : %d\n&quot;, counter);
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 아이디어는 간단하다. 메인 프로그램은 &lt;code&gt;Pthread_create()를&lt;/code&gt; 사용하여 두 개의 쓰레드를 생성한다. 쓰레드를 동일한 메모리 공간에서 함께 실행 중인 여러 개의 함수라고 생각할 수 있다. 이 예제 코드에서 각 쓰레드는 &lt;code&gt;worker()&lt;/code&gt;라는 루틴을 실행한다. &lt;code&gt;worker()&lt;/code&gt; 루틴은 &lt;code&gt;loops번만큼&lt;/code&gt; 루프를 반복하면서 카운터 값을 증가시킨다. &lt;code&gt;loops&lt;/code&gt; 값을 1000으로 설정하면 아래와 같은 출력을 얻는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.42.02.png&quot; data-origin-width=&quot;150&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rCURM/btsQfarQs8w/JmpNroVUFH7GsMiFtk3o11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rCURM/btsQfarQs8w/JmpNroVUFH7GsMiFtk3o11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rCURM/btsQfarQs8w/JmpNroVUFH7GsMiFtk3o11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrCURM%2FbtsQfarQs8w%2FJmpNroVUFH7GsMiFtk3o11%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;150&quot; height=&quot;44&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.42.02.png&quot; data-origin-width=&quot;150&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 쓰레드가 1000번씩 &lt;code&gt;counter&lt;/code&gt; 값을 증가시켰기 때문에 &lt;code&gt;counter&lt;/code&gt;의 최종 값은 2000이 된다. 이번에는 loops 값을 더 큰 값으로 지정해 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.47.46.png&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;99&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zeIQJ/btsQhgEDSpk/yCkIRDJ7Ds6hPQGzkyKGPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zeIQJ/btsQhgEDSpk/yCkIRDJ7Ds6hPQGzkyKGPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zeIQJ/btsQhgEDSpk/yCkIRDJ7Ds6hPQGzkyKGPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzeIQJ%2FbtsQhgEDSpk%2FyCkIRDJ7Ds6hPQGzkyKGPK%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;284&quot; height=&quot;99&quot; data-filename=&quot;스크린샷 2025-09-01 오후 9.47.46.png&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;99&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 실행에서는 입력 값을 100,000으로&amp;nbsp;주었더니 최종 값이 200,000 이 아니라 130,072이 되었다. 다시 한번 동일한 조건으로 실행시켰을 때에는 또 잘못된 값이 출력되었을 뿐 아니라 직전 실행과도 다른 결과가 출력되었다. 예상하지 못한 결과의 원인은 명령어가 한 번에 하나씩만 실행된다는 것과 관련 있다. 앞 프로그램의 핵심 부분인 &lt;code&gt;counter&lt;/code&gt;를 증사시 키는 부분은 세 개의 명령어로 이루어진다. &lt;code&gt;counter&lt;/code&gt; 값을 메모리에서 레지스터로 탑재하는 명령어 하나, 레지스터를 1 증가시키는 명령어 하나, 이 세 개의 명령어가 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;원자적(Atomically)&lt;span style=&quot;color: #666666;&quot;&gt;으로&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; 실행되지 않기 때문에 이상한 일이 발생할 수 있다. 이게 책의 후반에 다룰 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;병행성(Concurrency)&lt;/b&gt; &lt;/span&gt;문제이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;영속성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 주요 주제는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;영속성(Persistence)&lt;span style=&quot;color: #666666;&quot;&gt;이다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; DRAM과 같은 장치는 데이터를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;휘발성(Volatile)&lt;/b&gt; &lt;/span&gt;방식으로 저장하기 때문에 메모리의 데이터는 쉽게 손실될 수 있다. 전원 공급이 끊어지거나 시스템이 갑자기 고장 나면 메모리의 모든 데이터는 사라진다. 데이터를 영속적으로 저장할 수 있는 하드웨어와 소프트웨어가 필요하다. 저장 장치는 모든 시스템에 필수적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;입력/출력(input/output)&lt;/b&gt;&lt;/span&gt; 혹은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;I/O&lt;/b&gt;&lt;/span&gt; 장치 형태로 제공된다. 요즘에는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Solid-State Drivers(SSDs)&lt;span style=&quot;color: #666666;&quot;&gt;가&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; 많이 사용되고 있기는 하지만 장기간 보존할 정보를 저장하는 장치로는 일반적으로 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;하드 드라이브(Hard Drive)&lt;span style=&quot;color: #666666;&quot;&gt;가&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크를 관리하는 운영체제 소프트웨어를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;파일 시스템(File System)&lt;span style=&quot;color: #666666;&quot;&gt;이라고&lt;/span&gt;&lt;/b&gt;&lt;/span&gt; 부른다. 파일 시스템은 사용자가 생성한 파일(file)을 시스템의 디스크에 안전하고 효율적인 방식으로 저장할 책임이 있다. 다만 운영체제는 프로그램마다 별도의 가상 디스크를 만들지 않고 대신 파일 정보를 여러 사용자와 프로그램이 공유할 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 질문 : 데이터를 어떻게 영구적으로 저장할 수 있는가? 파일 시스템은 데이터를 영속적으로 관리하는 운영체제의 일부분으로, 데이터를 안전하게, 효율적으로 저장하고 접근할 수 있는 다양한 기법과 정책이 필요하다. 또한, 하드웨어나 소프트웨어에 문제가 생겨도 데이터를 안전하게 보호할 방법에 대해서도 고려해야 한다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;assert.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;sys/types.h&amp;gt;

int main(int argc, char *argv[]) {
    int fd = open(&quot;/tmp/file&quot;, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
    assert(fd &amp;gt; -1); // 파일 열기를 확인
    int rc = write(fd, &quot;hello world\n&quot;, 13); // &quot;hello world\n&quot; 문자열 쓰기
    assert(rc == 13); // 쓰기 작업을 확인
    close(fd); // 파일 닫기
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 코드는 문자열 &quot;hello world&quot;를 포함한 파일 &lt;code&gt;tmp/file&lt;/code&gt;을 생성하는 코드다. 여기서 프로그램은 운영체제를 3번 호출한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;open() 콜은&lt;/code&gt; 파일을 생성하고 연다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;write() 콜은&lt;/code&gt; 파일에 데이터를 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;close() 콜은&lt;/code&gt; 단순히 파일을 닫는데, 프로그램이 더 이상 해당 파일을 사용하지 않는다는 것을 나타낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들 시스템 콜(system call)은 운영체제에서 파일 시스템(file system)이라 불리는 부분으로 전달된다. 파일 시스템은 요청을 처리하고 경우에 따라 사용자에게 에러 코드를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 디스크에 쓰기 위해서 운영체제가 실제로 하는 일은 그렇게 간단하지 않다.&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;운영체제는 시스템 콜이라는, 표준화된 방법으로 장치들을 접근할 수 있게 한다. 운영체제는 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;표준 라이브러리(standard library)&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;처럼&lt;/span&gt;&lt;/span&gt; 보이기도 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장치를 접근하는 방법과 파일 시스템이 데이터를 영속적으로 관리하는 방법은 이보다 좀 더 복잡하다. 대부분의 파일 시스템은 응용프로그램들이 요청한 쓰기 요청들을 모아서 한 번에 처리한다. 성능향상을 위해서이다. 응용프로그램 입장에서는 요청한 쓰기의 내용들이 실제로 저장장치에 기록될 때까지 일정 시간의 지연이 발생하는 셈이다. 쓰기 요청 발생 후, 이 요청이 디스크에 실제 기록되기 이전에 정전 등의 문제가 발생하면 기록을 요청했던 내용이 손실될 수 있다. 문제가 그리 간단치 않다. 디스크에 기록하려고 모아놓은 내용 중 일부가 이미 디스크에 쓰였을 수도 있고, 더욱 문제인 것은 기록순서가 뒤바꿀 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰기 중에 시스템의 갑작스러운 고장에 대비하여 많은 파일 시스템들이 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;저널링(Journaling)&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이나&lt;/span&gt;&lt;/span&gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;쓰기-시-복사(Copy-On-Write)와&lt;/b&gt;&lt;/span&gt; 같은 기법을 사용한다. 효율적인 디스크 작업을 위해 단순한 링크드 리스트에서 복잡한 B-Tree까지 다양한 종류의 자료 구조를 사용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;설계 목표&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제의 역할을 이해하기 시작했다면, 이제 그것이 어떤 목표를 가지고 설계되어야 하는지 알아보겠다. 운영체제는 컴퓨터의 CPU, 메모리, 디스크와 같은 물리적 자원을 가상화하고, 병행성 문제를 다루며, 데이터를 영구적으로 저장한다. 이러한 시스템을 만들기 위해서는 추상화, 성능, 보호, 신뢰성 등 여러 목표를 염두에 둬야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째로, 시스템을 사용하기 쉽게 만드는 데 필요한 추상화를 정의하는 것이 중요하다. 추상화는 복잡한 프로그램을 이해하기 쉬운 작은 부분으로 나누는 데 도움을 준다. 또한, 시스템의 성능을 최적화하면서 오버헤드를 최소화하는 것도 중요한 목표다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;응용 프로그램 간의 보호와 운영체제 자체의 보호도 필수적이다. 멀티프로그래밍 환경에서는 한 프로그램의 오류가 다른 프로그램이나 운영체제 전체에 영향을 주지 않도록 해야 한다. 또한, 운영체제는 계속해서 안정적으로 실행되어야 하며, 복잡해질수록 신뢰성을 유지하는 것이 더 어려워진다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;에너지 효율성, 보안, 이동성 등 다른 중요한 목표들도 있다. 특히 현재와 같은 네트워크 연결 환경에서는 보안이 더욱 중요해졌고, 모바일 장치 사용이 증가함에 따라 운영체제의 이동성도 중요해졌다. 이 수업을 통해 다양한 운영체제의 주제들을 탐구하면서 이러한 목표들이 어떻게 실현되는지 배우게 될 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;역사 부분은 넘어가겠다. 이미 분량이 꽤 많기 때문이다. 궁금하면&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222832; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://os2024.jeju.ai/week01/intro.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://os2024.jeju.ai/week01/intro.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1756732549848&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;운영체제 개요 &amp;mdash; 운영체제 2024&quot; data-og-description=&quot;운영체제 개요 프로그램이 실행될 때, 그것은 단순히 명령어를 실행합니다. 프로세서는 초당 수백만에서 수십억 번 명령어를 가져와 해석하고 실행합니다. 이 과정은 프로그램이 완전히 종료될&quot; data-og-host=&quot;os2024.jeju.ai&quot; data-og-source-url=&quot;https://os2024.jeju.ai/week01/intro.html&quot; data-og-url=&quot;https://os2024.jeju.ai/week01/intro.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://os2024.jeju.ai/week01/intro.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://os2024.jeju.ai/week01/intro.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;운영체제 개요 &amp;mdash; 운영체제 2024&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;운영체제 개요 프로그램이 실행될 때, 그것은 단순히 명령어를 실행합니다. 프로세서는 초당 수백만에서 수십억 번 명령어를 가져와 해석하고 실행합니다. 이 과정은 프로그램이 완전히 종료될&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;os2024.jeju.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서 확인하면 좋을 것이다.&lt;/p&gt;</description>
      <category>Deep Dive/OS</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/189</guid>
      <comments>https://gowoong.tistory.com/189#entry189comment</comments>
      <pubDate>Mon, 1 Sep 2025 22:32:02 +0900</pubDate>
    </item>
    <item>
      <title>[크래프톤 정글 8기] 수료 및 마지막 회고</title>
      <link>https://gowoong.tistory.com/188</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;7월 31일 부로 크래프톤 정글 8기 과정을 모두 마치고 수료를 했다. 꽤나 시간이 지나 마지막 회고를 작성하고 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;크래프톤 정글 8기 회고&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0주 차 때 에세이를 적으며 크래프톤 정글이 끝났을 때 나는 과연 어떤 상태일까를 적어놨었다.&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;b&gt;내가 정말 고민하던 것이 부족한 코딩테스트 실력, CS 지식, 프로젝트 경험이었다. &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;5개월 후에는 더 많고 깊은 CS 지식을 가지는 것과 정말 잘하지는 못하더라도 원하는 기업에 입사 지원을 해 코딩테스트에 통과할 수 있을 수준의 알고리즘 지식과 실력을 가질 수 있었으면 한다. &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;마지막으로 자주 연락하며 안부도 묻고 같이 프로젝트도 할 수 있는 동료를 만들었으면 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gowoong.tistory.com/25&quot;&gt;https://gowoong.tistory.com/25&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754716107900&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;크래프톤 정글 8기 에세이&quot; data-og-description=&quot;크래프톤 정글 8기에 입소하기까지 많은 일들이 있었다. 입소한 뒤 정신을 채 차리기도 전에 시작된 미니 프로젝트를 끝마치고 에세이 작성을 과제로 받았다. 에세이를 작성하며 과거를 돌아보&quot; data-og-host=&quot;www.gowoong.com&quot; data-og-source-url=&quot;https://gowoong.tistory.com/25&quot; data-og-url=&quot;https://www.gowoong.com/25&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AYCSa/hyZuCZHDEI/WZfssnSOc9ZpbReytysVI0/img.png?width=309&amp;amp;height=163&amp;amp;face=0_0_309_163,https://scrap.kakaocdn.net/dn/IOwkt/hyZvwEKT0y/lYNVQRp4UWtiP4VpyO2fO1/img.png?width=309&amp;amp;height=163&amp;amp;face=0_0_309_163&quot;&gt;&lt;a href=&quot;https://gowoong.tistory.com/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gowoong.tistory.com/25&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AYCSa/hyZuCZHDEI/WZfssnSOc9ZpbReytysVI0/img.png?width=309&amp;amp;height=163&amp;amp;face=0_0_309_163,https://scrap.kakaocdn.net/dn/IOwkt/hyZvwEKT0y/lYNVQRp4UWtiP4VpyO2fO1/img.png?width=309&amp;amp;height=163&amp;amp;face=0_0_309_163');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;크래프톤 정글 8기 에세이&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;크래프톤 정글 8기에 입소하기까지 많은 일들이 있었다. 입소한 뒤 정신을 채 차리기도 전에 시작된 미니 프로젝트를 끝마치고 에세이 작성을 과제로 받았다. 에세이를 작성하며 과거를 돌아보&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.gowoong.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 적어놨었는데 지금 와서 돌아보면 어떻게 발전했을까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 여전히 부족한 코딩 테스트 실력 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&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;스크린샷 2025-08-09 오후 2.11.46.png&quot; data-origin-width=&quot;1173&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s6fvQ/btsPNLkBYRU/kx0UECDLsmCq9xOROnzPy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s6fvQ/btsPNLkBYRU/kx0UECDLsmCq9xOROnzPy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s6fvQ/btsPNLkBYRU/kx0UECDLsmCq9xOROnzPy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs6fvQ%2FbtsPNLkBYRU%2Fkx0UECDLsmCq9xOROnzPy0%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;1173&quot; height=&quot;380&quot; data-filename=&quot;스크린샷 2025-08-09 오후 2.11.46.png&quot; data-origin-width=&quot;1173&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3월 14일 이 후 하루 빼고는 적어도 1문제 이상은 풀고 있다. - 하루 빠진 날은 나 만무 기획하느라 매우 많은 고민과 갈등이 있었기 때문에 까먹고 하지 못했다.(너무 아쉽다...)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. CS지식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 정글을 가야겠다고 생각했던 것이 이 CS지식 때문이었다. 비전공자로서 회사에서 개발을 하다 보면 많은 부분에서 CS지식이 있었다면 좋았을 것 같다는 생각을 많이 했다. 가령 특정 API의 성능(시간)이 너무 안 좋을 때 이게 왜 문제가 있을까? 어떻게 수정해야 할까? 이런 생각이 들었을 때 알고리즘이나 컴퓨터적인 특성 등 놓치고 있던 부분이 많았다.&amp;nbsp;&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;3. 프로젝트 경험&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 처음 국비지원으로 개발을 배웠을 때 경험했던 프로젝트는 정말 아무것도 아니었구나를 느낄 수 있었다. 물론 내가 그 때 보다는 2년 반 이상의 경험을 가진 상태에서 진행했다는 점이 있지만 매우 잘 진행되었다 팀의 분위기도 초반의 기획 때를 제외하면 모두 각자 또는 같이 구현을 위해 노력한 경험은 매우 값지다고 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 툴의 발전도 우리가 막힘없이 프로젝트를 진행할 수 있도록 도와준 한 축이 되었다고 생각한다. 처음 국비 학원에서 진행했던 프로젝트의 웹 페이지와 정글에서 진행한 프로젝트의 웹 페이지는 정말 차원이 다른 수준이기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4wv3J/btsPLWUX2rJ/OG1ftel1znZ3ak9NC63rn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4wv3J/btsPLWUX2rJ/OG1ftel1znZ3ak9NC63rn0/img.png&quot; data-alt=&quot;국비지원 당시 진행한 프로젝트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4wv3J/btsPLWUX2rJ/OG1ftel1znZ3ak9NC63rn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4wv3J%2FbtsPLWUX2rJ%2FOG1ftel1znZ3ak9NC63rn0%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;524&quot; height=&quot;440&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;국비지원 당시 진행한 프로젝트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-08-04 오후 9.37.32.png&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;1033&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b65y7t/btsPM0P61t3/VcQcLX23TUygMz3j9oBNv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b65y7t/btsPM0P61t3/VcQcLX23TUygMz3j9oBNv1/img.png&quot; data-alt=&quot;정글에서 진행한 프로젝트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b65y7t/btsPM0P61t3/VcQcLX23TUygMz3j9oBNv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb65y7t%2FbtsPM0P61t3%2FVcQcLX23TUygMz3j9oBNv1%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;1906&quot; height=&quot;1033&quot; data-filename=&quot;스크린샷 2025-08-04 오후 9.37.32.png&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;1033&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;정글에서 진행한 프로젝트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 동료&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 가장 큰 수확은 같은 개발자 동료를 얻을 수 있었다는 것이다. 국비 지원학원을 다녔을 때는 출퇴근 하듯이 다녔는데 크래프톤 정글은 합숙을 하며 많게는 하루 12시간 이상 같은 공간에 있으면서 공부를 하는 시간을 가지면서 친해지기도 하고 동기부여도 되었다. 사실 국비 학원을 다닐 때의 사람들과는 연락을 하지 않고 있었다. 이미 연락이 끊어진 지 오래되었고 서로 연락도 하지 않는다. 단톡방에도 톡이 없는지 오래다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 크래프톤 정글에서는 정말 다양한 사람이 모였고 크래프톤 정글과 크래프톤에서 진행한 다른 분야의 부트캠프 수료자들도 있다. 특히 이번 기수에서 JDC라는 커뮤니티를 개설해서 운영하려고 한다. 많게는 수백명의 정글 출신 개발자들과의 커뮤니티를 활성화하려는 점이 계속해서 연락할 동료가 생겼다는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇보다 지방에서 서울로 상경했기 때문에 주말이나 퇴근 후에 만날 사람도 없이 혼자 개발하던 과거와 비교하면 수도권의 동기들이 많아 자주 모여 시간을 보낼 수 있을 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&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;size16&quot;&gt;빠른 시간 안에 이직을 하고 경제활동을 재개해야 할 것 같다. 카드비와 전기세.. 내야할 돈이 많다. 그리고 교육을 받았다고 하지만 기업 입장에서는 경력 단절로 보지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작정 공부만 하는 것 보다는 역시 직접 경험하고 깨닫는 것이 매우 도움이 된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같이 5개월 간 고생한 정글 8기 모두 성공적인 취업과 이직 등 정글에서 얻은 지식과 고생을 보상받을 수 있길 바란다.&lt;/p&gt;</description>
      <category>크래프톤 정글</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/188</guid>
      <comments>https://gowoong.tistory.com/188#entry188comment</comments>
      <pubDate>Sat, 9 Aug 2025 14:45:28 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] BLoC(Bussiness Login Component)란?</title>
      <link>https://gowoong.tistory.com/187</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서의 &lt;b&gt;BLoC&lt;/b&gt;(Business Logic Component)은 애플리케이션의 &lt;b&gt;비즈니스 로직&lt;/b&gt;을 UI와 분리하여 &lt;b&gt;유지보수성&lt;/b&gt;, &lt;b&gt;테스트 용이성&lt;/b&gt;, &lt;b&gt;재사용성&lt;/b&gt;을 높이기 위해 사용하는 &lt;b&gt;상태 관리(State Management) 아키텍처 패턴&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;241&quot; data-start=&quot;221&quot; data-ke-size=&quot;size26&quot;&gt;1. BLoC이란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://bloclibrary.dev/ko/why-bloc/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://bloclibrary.dev/ko/why-bloc/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753668100238&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;왜 Bloc인가?&quot; data-og-description=&quot;어떤 요소가 Bloc을 견고한 상태 관리 솔루션으로 만드는 지에 대한 개요입니다.&quot; data-og-host=&quot;bloclibrary.dev&quot; data-og-source-url=&quot;https://bloclibrary.dev/ko/why-bloc/&quot; data-og-url=&quot;https://bloclibrary.dev/ko/why-bloc/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cgZFlM/hyZnfYEOIx/rVogaCsKmxKVi3TkYnrcY0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ofD2h/hyZnBAvSYV/dR1eWmKu3QycwKsaADGHpk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://bloclibrary.dev/ko/why-bloc/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bloclibrary.dev/ko/why-bloc/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cgZFlM/hyZnfYEOIx/rVogaCsKmxKVi3TkYnrcY0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ofD2h/hyZnBAvSYV/dR1eWmKu3QycwKsaADGHpk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;왜 Bloc인가?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;어떤 요소가 Bloc을 견고한 상태 관리 솔루션으로 만드는 지에 대한 개요입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bloclibrary.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-end=&quot;382&quot; data-start=&quot;243&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BLoC (Business Logic Component)&lt;/b&gt; 패턴은 &lt;b&gt;Reactive Programming (반응형 프로그래밍)&lt;/b&gt;을 기반으로 하며, &lt;b&gt;Dart의 Stream&lt;/b&gt;과 &lt;b&gt;Sink&lt;/b&gt;를 사용하여 UI와 비즈니스 로직을 분리한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;470&quot; data-start=&quot;384&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;428&quot; data-start=&quot;384&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: 사용자의 이벤트 (예: 버튼 클릭)를 Sink에 전달&lt;/li&gt;
&lt;li data-end=&quot;470&quot; data-start=&quot;429&quot;&gt;&lt;b&gt;Output&lt;/b&gt;: 처리된 결과를 Stream을 통해 UI에 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bloc은 다음 세 가지 핵심 가치를 염두에 두고 설계되었다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #353841; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;간단함:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이해하기 쉽고 다양한 스킬 수준을 가진 개발자가 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;강력함:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;더 작은 구성 요소로 구성하여 놀랍고 복잡한 애플리케이션을 만드는 데 도움을 준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 가능:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;애플리케이션의 모든 측면을 쉽게 테스트할 수 있으므로 자신 있게 테스트를 반복할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353841; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전반적으로, Bloc은 상태 변경이 발생할 수 있는 시기를 규제하고 애플리케이션 전체에 걸쳐 단일한 상태를 변경하는 방법을 시행함으로써 상태 변경을 예측 가능하게 만들려고 시도한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #353841; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #17181c; text-align: start;&quot;&gt;Streams&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stream은 연속적인 비동기 데이터다.&lt;/li&gt;
&lt;li&gt;Streams 을 쉽게 이해하기 위해서는 물이 흐르는 파이프를 생각하자. 파이프는 Streams이고 물은 비동기 데이터이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;async* (비동기 생성기) 함수를 작성하여 Dart에서 Stream을 생성할 수 있다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1753668291660&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stream&amp;lt;int&amp;gt; countStream(int max) async* {
    for (int i = 0; i &amp;lt; max; i++) {
        yield i;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수를&amp;nbsp;async*로 표시하면&amp;nbsp;yield&amp;nbsp;키워드를 사용하여 데이터의&amp;nbsp;Stream을 반환할 수 있다. 위 예시에서는&amp;nbsp;max&amp;nbsp;정수 파라미터까지의 정수&amp;nbsp;Stream을 반환하고 있다.&lt;/li&gt;
&lt;li&gt;async*&amp;nbsp;함수에서&amp;nbsp;yield&amp;nbsp;할 때 마다 해당 데이터를&amp;nbsp;Stream을 통해 푸시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cubit&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cubit 은 BlocBase를 extends 한 클래스로, 모든 유형의 state를 관리하도록 확장할 수 있다. 즉 BLoC의 간단한 버전이다. flutter_bloc 패키지에서 제공하며 Stream 기반이다. 이벤트 클래스를 따로 만들지 않고, 메서드 호출 -&amp;gt; 상태 변경을 하는 구조다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. BLoC vs Cubit&lt;/h2&gt;
&lt;div&gt;위에서 Cubit 이 BLoC 의 간단한 버전이라고 했는데 더 자세히 알아보자&lt;/div&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;BLoC&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Cubit&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;이벤트 (Event)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;반드시 정의 필요&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;코드 구조&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Event &amp;rarr; Bloc &amp;rarr; State&lt;/td&gt;
&lt;td&gt;직접 메서드 호출로 상태 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;복잡도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;복잡한 상태 흐름에 적합&lt;/td&gt;
&lt;td&gt;간단한 로직에 적합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Stream 기반&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;on&amp;lt;Event&amp;gt;((e, emit) {...})&lt;/td&gt;
&lt;td&gt;emit(State) 직접 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;유즈케이스&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;로그인, 결제, 인증 등 다중 이벤트 로직&lt;/td&gt;
&lt;td&gt;토글, 카운터, 단순 상태 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BLoC 작동 흐름&lt;/h3&gt;
&lt;pre id=&quot;code_1753669973318&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[UI] --이벤트--&amp;gt; [Bloc] --로직처리--&amp;gt; [State] --&amp;gt; [UI]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: 로그인 버튼 클릭 &amp;rarr; 로그인 Bloc에 이벤트 전달 &amp;rarr; 로그인 로직 수행 &amp;rarr; 결과 상태 방출 &amp;rarr; UI가 상태에 따라 리렌더링&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BLoC 사용 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 이벤트 정의&lt;/p&gt;
&lt;pre id=&quot;code_1753670035280&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class CounterEvent {}

class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 상태 정의&lt;/p&gt;
&lt;pre id=&quot;code_1753670050656&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterState {
  final int count;
  const CounterState(this.count);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. BLoC 구현&lt;/p&gt;
&lt;pre id=&quot;code_1753670064019&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterBloc extends Bloc&amp;lt;CounterEvent, CounterState&amp;gt; {
  CounterBloc() : super(CounterState(0)) {
    on&amp;lt;Increment&amp;gt;((event, emit) =&amp;gt; emit(CounterState(state.count + 1)));
    on&amp;lt;Decrement&amp;gt;((event, emit) =&amp;gt; emit(CounterState(state.count - 1)));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. UI에서 사용&lt;/p&gt;
&lt;pre id=&quot;code_1753670080418&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BlocProvider(
  create: (_) =&amp;gt; CounterBloc(),
  child: BlocBuilder&amp;lt;CounterBloc, CounterState&amp;gt;(
    builder: (context, state) {
      return Column(
        children: [
          Text('Count: ${state.count}'),
          ElevatedButton(
            onPressed: () =&amp;gt; context.read&amp;lt;CounterBloc&amp;gt;().add(Increment()),
            child: Text('Increment'),
          ),
        ],
      );
    },
  ),
)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cubit 사용 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 상태 정의&lt;/p&gt;
&lt;pre id=&quot;code_1753670424781&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterState {
  final int count;
  const CounterState(this.count);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. cubit 클래스 정의&lt;/p&gt;
&lt;pre id=&quot;code_1753670444776&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class CounterCubit extends Cubit&amp;lt;CounterState&amp;gt; {
  CounterCubit() : super(CounterState(0));

  void increment() =&amp;gt; emit(CounterState(state.count + 1));
  void decrement() =&amp;gt; emit(CounterState(state.count - 1));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. UI에서 사용&lt;/p&gt;
&lt;pre id=&quot;code_1753670462536&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BlocProvider(
  create: (_) =&amp;gt; CounterCubit(),
  child: BlocBuilder&amp;lt;CounterCubit, CounterState&amp;gt;(
    builder: (context, state) {
      return Column(
        children: [
          Text('${state.count}'),
          ElevatedButton(
            onPressed: () =&amp;gt; context.read&amp;lt;CounterCubit&amp;gt;().increment(),
            child: Text('증가'),
          )
        ],
      );
    },
  ),
);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 실제 서비스에서의 사용 예&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 1: 로그인 흐름(BLoC)&lt;/p&gt;
&lt;pre id=&quot;code_1753670542529&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;이벤트: LoginButtonPressed
&amp;darr;
BLoC: 로그인 시도, 서버 요청
&amp;darr;
상태: LoginLoading &amp;rarr; LoginSuccess / LoginFailure
&amp;darr;
UI: 로딩 표시 / 에러 메시지 / 홈 화면 이동&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 2: 다크모드 토글(Cubit)&lt;/p&gt;
&lt;pre id=&quot;code_1753670562540&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Cubit: ThemeCubit &amp;rarr; toggleDarkMode()
&amp;darr;
상태: DarkModeOn / DarkModeOff
&amp;darr;
UI: 테마 변경&lt;/code&gt;&lt;/pre&gt;</description>
      <category>앱 개발/Flutter</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/187</guid>
      <comments>https://gowoong.tistory.com/187#entry187comment</comments>
      <pubDate>Mon, 28 Jul 2025 11:43:20 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 입문 - 시작과 기초 개념 잡기</title>
      <link>https://gowoong.tistory.com/186</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;237&quot; data-start=&quot;152&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;172&quot; data-start=&quot;152&quot;&gt;Flutter란 무엇인지 이해하기&lt;/li&gt;
&lt;li data-end=&quot;196&quot; data-start=&quot;173&quot;&gt;Flutter 설치 및 개발 환경 세팅&lt;/li&gt;
&lt;li data-end=&quot;216&quot; data-start=&quot;197&quot;&gt;첫 번째 Flutter 앱 실행&lt;/li&gt;
&lt;li data-end=&quot;237&quot; data-start=&quot;217&quot;&gt;Flutter 프로젝트 구조 이해&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Flutter란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flutter&lt;/b&gt;는 Google에서 개발한 &lt;b&gt;오픈소스 UI 프레임워크&lt;/b&gt;로, 하나의 코드베이스로 iOS, Android, Web, 데스크탑까지 다양한 플랫폼에서 앱을 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 빠른 개발, 직관적인 UI 구성, 그리고 &lt;b&gt;Dart&lt;/b&gt; 언어를 기반으로 한다는 점이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;446&quot; data-start=&quot;431&quot; data-ke-size=&quot;size26&quot;&gt;  개발 환경 세팅&lt;/h2&gt;
&lt;h3 data-end=&quot;466&quot; data-start=&quot;448&quot; data-ke-size=&quot;size23&quot;&gt;1. Flutter 설치&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;590&quot; data-start=&quot;467&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;519&quot; data-start=&quot;467&quot;&gt;공식 사이트: &lt;a href=&quot;https://flutter.dev&quot;&gt;https://flutter.dev&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;590&quot; data-start=&quot;520&quot;&gt;설치 방법: OS에 따라 제공되는 zip 파일을 다운 받아 압축 해제 후, flutter/bin 경로를 PATH에 등록&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;622&quot; data-start=&quot;592&quot; data-ke-size=&quot;size23&quot;&gt;2. Android Studio 설치 (권장)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;686&quot; data-start=&quot;623&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;654&quot; data-start=&quot;623&quot;&gt;Flutter와 함께 사용하는 IDE 중 가장 안정적&lt;/li&gt;
&lt;li data-end=&quot;686&quot; data-start=&quot;655&quot;&gt;플러그인 설치:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;686&quot; data-start=&quot;668&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;677&quot; data-start=&quot;668&quot;&gt;Flutter&lt;/li&gt;
&lt;li data-end=&quot;686&quot; data-start=&quot;680&quot;&gt;Dart&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;701&quot; data-start=&quot;688&quot; data-ke-size=&quot;size23&quot;&gt;3. 환경 점검&lt;/h3&gt;
&lt;pre id=&quot;code_1753400380737&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter doctor&lt;/code&gt;&lt;/pre&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;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 번째 앱 실행&lt;/h2&gt;
&lt;pre id=&quot;code_1753400435982&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter create hello_flutter
cd hello_flutter
flutter run&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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 data-end=&quot;902&quot; data-start=&quot;868&quot;&gt;위 명령어로 간단한 Flutter 프로젝트를 생성하고 실행&lt;/li&gt;
&lt;li data-end=&quot;937&quot; data-start=&quot;903&quot;&gt;기본 Counter App이 동작하는 것을 확인할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 구조 이해&lt;/h2&gt;
&lt;pre id=&quot;code_1753400454463&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hello_flutter/
│
├─ lib/              # Dart 코드가 들어가는 폴더
│   └─ main.dart     # 앱 진입점 (main 함수)
│
├─ pubspec.yaml      # 패키지 및 의존성 설정
├─ android/          # 안드로이드 관련 설정
├─ ios/              # iOS 관련 설정&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>앱 개발/Flutter</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/186</guid>
      <comments>https://gowoong.tistory.com/186#entry186comment</comments>
      <pubDate>Fri, 25 Jul 2025 08:42:18 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 입문 - 화면 전환(Navigation)</title>
      <link>https://gowoong.tistory.com/185</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Navifator를 통한 화면 전환 이해하기&lt;/li&gt;
&lt;li&gt;MaterialPageRoute 사용법&lt;/li&gt;
&lt;li&gt;Named Route 설정 및 사용&lt;/li&gt;
&lt;li&gt;화면 간 데이터 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;306&quot; data-start=&quot;281&quot; data-ke-size=&quot;size26&quot;&gt;  Flutter에서 화면 전환이란?&lt;/h2&gt;
&lt;p data-end=&quot;436&quot; data-start=&quot;308&quot; data-ke-size=&quot;size16&quot;&gt;Flutter 앱은 여러 개의 &quot;화면(Screen)&quot; 혹은 &quot;페이지(Page)&quot;를 가질 수 있다. 이를 Flutter에서는 &lt;b&gt;Route(경로)라고&lt;/b&gt; 부르며, 화면 간 이동은 Navigator 클래스를 통해 처리한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;466&quot; data-start=&quot;443&quot; data-ke-size=&quot;size26&quot;&gt;Navigator 기본 사용법&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;화면 이동 (push)&lt;/h4&gt;
&lt;pre id=&quot;code_1753400066261&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Navigator.push(
  context,
  MaterialPageRoute(builder: (context) =&amp;gt; SecondPage()),
);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;화면 돌아가기 (pop)&lt;/h4&gt;
&lt;pre id=&quot;code_1753400093212&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Navigator.pop(context);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;675&quot; data-start=&quot;653&quot; data-ke-size=&quot;size26&quot;&gt;Named Route 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 페이지가 생기면 &lt;b&gt;라우트 이름으로 페이지를 구분&lt;/b&gt;하는 것이 편리하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. main.dart에서 라우트 등록&lt;/h4&gt;
&lt;pre id=&quot;code_1753400134632&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  runApp(MaterialApp(
    initialRoute: '/',
    routes: {
      '/': (context) =&amp;gt; HomePage(),
      '/second': (context) =&amp;gt; SecondPage(),
    },
  ));
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 화면 이동&lt;/h4&gt;
&lt;pre id=&quot;code_1753400147983&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Navigator.pushNamed(context, '/second');&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 돌아오기&lt;/h4&gt;
&lt;pre id=&quot;code_1753400161120&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Navigator.pop(context);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;화면 간 데이터 전달&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전달 (push 할 때)&lt;/h4&gt;
&lt;pre id=&quot;code_1753400189546&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) =&amp;gt; SecondPage(message: 'Hello from first!'),
  ),
);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;받기 (SecondPage)&lt;/h4&gt;
&lt;pre id=&quot;code_1753400204589&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class SecondPage extends StatelessWidget {
  final String message;

  const SecondPage({super.key, required this.message});

  @override
  Widget build(BuildContext context) {
    return Text(message);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&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 data-end=&quot;1678&quot; data-start=&quot;1641&quot;&gt;Flutter에서 페이지 전환은 Navigator로 처리한다.&lt;/li&gt;
&lt;li data-end=&quot;1741&quot; data-start=&quot;1679&quot;&gt;MaterialPageRoute로 직접 이동하거나, routes를 등록해서 이름 기반 이동도 가능하다.&lt;/li&gt;
&lt;li data-end=&quot;1796&quot; data-start=&quot;1742&quot;&gt;화면 간에 데이터를 주고받을 수 있으며, 앱 구조가 복잡해질수록 명확한 라우팅 전략이 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>앱 개발/Flutter</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/185</guid>
      <comments>https://gowoong.tistory.com/185#entry185comment</comments>
      <pubDate>Fri, 25 Jul 2025 08:37:49 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 입문 - 기본 위젯 &amp;amp; Stateful vs Stateless 이해</title>
      <link>https://gowoong.tistory.com/184</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flutter의 기본 위젯 사용법 익히기&lt;/li&gt;
&lt;li&gt;레이아웃 구성 방법 배우기&lt;/li&gt;
&lt;li&gt;StatelessWidget 과 StatefulWidget의 차이 이해하기&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Flutter 기본 위젯&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter는 '모든 것이 위젯이다.' 라는 철학을 가지고 있다. 그만큼 다양한 UI 요소들이 Widget 형태로 제공된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 위젯 소개&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;위젯&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;텍스트 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container&lt;/td&gt;
&lt;td&gt;박스 형태, 배경색, 패딩, 마진 등을 설정할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row, Column&lt;/td&gt;
&lt;td&gt;수평 / 수직 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;네트워크 이미지 또는 에셋 이미지 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Center&lt;/td&gt;
&lt;td&gt;자식 위젯을 중앙 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ElevatedButton&lt;/td&gt;
&lt;td&gt;기본 버튼 위젯&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SizedBox&lt;/td&gt;
&lt;td&gt;여백 주기용 (공간 확보)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 : 간단한 레이아웃 구성&lt;/p&gt;
&lt;pre id=&quot;code_1753399718048&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Text('Hello Flutter!'),
    SizedBox(height: 20),
    ElevatedButton(
      onPressed: () {},
      child: Text('Click Me'),
    ),
  ],
)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-end=&quot;966&quot; data-start=&quot;926&quot; data-ke-size=&quot;size26&quot;&gt;StatelessWidget vs StatefulWidget&lt;/h2&gt;
&lt;h3 data-end=&quot;966&quot; data-start=&quot;926&quot; data-ke-size=&quot;size23&quot;&gt;1. StatelessWidget (비상태 위젯)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;한번 그려지면 다시 변하지 않는 UI&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;사용 예 : 로고, 단순 텍스트 등&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1753399794202&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('I never change!');
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. StatefulWidget 상태 위젯)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;내부 상태에 따라 다시 그려질 수 있는 위젯&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;버튼 클릭, 입력 필드 등 동적 변화에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1753399853258&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyCounter extends StatefulWidget {
  @override
  State&amp;lt;MyCounter&amp;gt; createState() =&amp;gt; _MyCounterState();
}

class _MyCounterState extends State&amp;lt;MyCounter&amp;gt; {
  int count = 0;

  void increment() {
    setState(() {
      count += 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(onPressed: increment, child: Text('Increment')),
      ],
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;setState() 는 화면을 다시 그리게 만들어주는 중요한 함수이다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&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 data-end=&quot;1888&quot; data-start=&quot;1843&quot;&gt;Flutter는 다양한 &lt;b&gt;기본 위젯&lt;/b&gt;을 제공하며, 이를 조합해 UI를 구성&lt;/li&gt;
&lt;li data-end=&quot;1948&quot; data-start=&quot;1889&quot;&gt;&lt;b&gt;Stateless&lt;/b&gt;는 변하지 않는 UI, &lt;b&gt;Stateful&lt;/b&gt;은 상태 변화가 필요한 UI에 사용&lt;/li&gt;
&lt;li data-end=&quot;2001&quot; data-start=&quot;1949&quot;&gt;build() 메서드에서 UI를 정의하며, 상태 변경 시 setState()로 갱신&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>앱 개발/Flutter</category>
      <author>고웅</author>
      <guid isPermaLink="true">https://gowoong.tistory.com/184</guid>
      <comments>https://gowoong.tistory.com/184#entry184comment</comments>
      <pubDate>Fri, 25 Jul 2025 08:32:08 +0900</pubDate>
    </item>
  </channel>
</rss>