<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>edward-official.log</title>
        <link>https://velog.io/</link>
        <description>there ain't no shortcuts</description>
        <lastBuildDate>Wed, 30 Sep 2026 01:09:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>edward-official.log</title>
            <url>https://velog.velcdn.com/images/edward-official/profile/e3129c89-a3cf-46f3-8abe-c2e0319257cb/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. edward-official.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/edward-official" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[카데인 알고리즘의 수학적 증명]]></title>
            <link>https://velog.io/@edward-official/kadanesalgorithm</link>
            <guid>https://velog.io/@edward-official/kadanesalgorithm</guid>
            <pubDate>Wed, 30 Sep 2026 01:09:47 GMT</pubDate>
            <description><![CDATA[<h3 id="1-전체-탐색-공간의-분할-partitioning">1. 전체 탐색 공간의 분할 (Partitioning)</h3>
<p>길이가 $n$인 배열 $A$에서 만들 수 있는 연속 부분 배열의 개수는 총 $\frac{n(n+1)}{2}$개입니다.</p>
<p>이 전체 부분 배열들의 집합을 &quot;어느 인덱스에서 끝나는가?&quot;를 기준으로 $n$개의 서로소 부분집합(Disjoint Sets)으로 나눕니다.</p>
<ul>
<li>$S_0$: 인덱스 $0$에서 끝나는 부분 배열들의 집합</li>
<li>$S_1$: 인덱스 $1$에서 끝나는 부분 배열들의 집합</li>
<li>$\dots$</li>
<li>$S_j$: 인덱스 $j$에서 끝나는 부분 배열들의 집합 ($0 \le j &lt; n$)</li>
</ul>
<p>모든 연속 부분 배열은 반드시 $0$부터 $n-1$ 중 <strong>단 하나의 끝점</strong>을 가지므로, 전체 부분 배열 중 최대합(전역 최적해)은 각 집합의 최댓값들 중 가장 큰 값과 같습니다.</p>
<p>$$\text{Global Max} = \max_{0 \le j &lt; n} \Big( \max(S_j) \Big)$$</p>
<hr>
<h3 id="2-점화식의-수학적-유도-최적-부분-구조">2. 점화식의 수학적 유도 (최적 부분 구조)</h3>
<p>이제 문제를 &quot;$j$번째 원소로 끝나는 부분 배열 중 최대합 $M[j]$를 어떻게 구할 것인가?&quot;로 좁힐 수 있습니다.</p>
<p>수학적으로 $M[j]$는 다음과 같이 정의됩니다.</p>
<p>$$M[j] = \max_{0 \le i \le j} \sum_{k=i}^j A[k]$$</p>
<p>이 식에서 시작 인덱스 $i$의 경우의 수를 $i = j$인 경우(원소 1개짜리)와 $i &lt; j$인 경우(길이가 2 이상인 경우)로 분리합니다.</p>
<p>$$M[j] = \max \left( A[j],; \max_{0 \le i \le j-1} \left( \sum_{k=i}^{j-1} A[k] + A[j] \right) \right)$$</p>
<p>여기서 $A[j]$는 $i$와 무관한 공통 덧셈 상수이므로 밖으로 묶어낼 수 있습니다.</p>
<p>$$M[j] = \max \left( A[j],; \left( \max_{0 \le i \le j-1} \sum_{k=i}^{j-1} A[k] \right) + A[j] \right)$$</p>
<p>괄호 안의 $\max_{0 \le i \le j-1} \sum_{k=i}^{j-1} A[k]$는 정확히 이전 단계의 정의인 $M[j-1]$입니다.</p>
<p>따라서 다음과 같은 전형적인 동적 계획법의 점화식이 도출됩니다.</p>
<p>$$M[j] = \max(A[j],; M[j-1] + A[j])$$</p>
<p>식을 $A[j]$를 기준으로 정리하면 더욱 직관적인 형태가 됩니다.</p>
<p>$$M[j] = A[j] + \max(0,; M[j-1])$$</p>
<ul>
<li><strong>$M[j-1] &gt; 0$이면</strong>: 이전 누적합을 붙이는 것이 이득이므로 $M[j-1] + A[j]$ 선택</li>
<li><strong>$M[j-1] \le 0$이면</strong>: 이전까지의 최적합이 음수이므로, 이전 기록을 버리고 $A[j]$ 단독으로 새로 시작하는 것이 무조건 이득</li>
</ul>
<hr>
<h3 id="3-메모리-최적화-on-to-o1-공간">3. 메모리 최적화 ($O(N) \to O(1)$ 공간)</h3>
<p>점화식 $M[j] = \max(A[j], M[j-1] + A[j])$를 보면, $M[j]$를 계산할 때 필요한 이전 값은 오직 <strong>직전 값인 $M[j-1]$ 하나뿐</strong>입니다. $M[j-2], M[j-3]$ 등 그 이전의 값들은 알 필요가 없습니다.</p>
<p>따라서 $N$ 크기의 배열 $M[\ ]$을 메모리에 유지할 필요 없이, 단 하나의 변수(<code>currentSum</code>)만 계속 덮어쓰면서 갱신하면 충분하므로 공간 복잡도가 $O(1)$이 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[gemini와 cors 이해하기]]></title>
            <link>https://velog.io/@edward-official/gemini%EC%99%80-cors-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@edward-official/gemini%EC%99%80-cors-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 06 Mar 2026 05:19:40 GMT</pubDate>
            <description><![CDATA[<h1 id="웹에서-cors가-뭐야">웹에서 cors가 뭐야?</h1>
<h2 id="cors-cross-origin-resource-sharing-이해하기">CORS (Cross-Origin Resource Sharing) 이해하기</h2>
<p><strong>CORS</strong>는 웹 브라우저에서 실행되는 스크립트가 <strong>다른 도메인(출처)</strong>의 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.</p>
<p>기본적으로 웹 브라우저는 보안상의 이유로 <strong>SOP (Same-Origin Policy, 동일 출처 정책)</strong>를 따릅니다. 이는 자바스크립트가 자신이 로드된 도메인과 다른 도메인으로 HTTP 요청을 보내는 것을 제한하는 규칙입니다. 하지만 현대 웹 개발에서는 API 서버와 프론트엔드 서버가 분리된 경우가 많아, 이를 안전하게 허용하기 위해 CORS가 사용됩니다.</p>
<h3 id="1-출처origin의-기준">1. &#39;출처(Origin)&#39;의 기준</h3>
<p>브라우저는 다음 세 가지 요소가 모두 일치해야 동일한 출처로 판단합니다.</p>
<ul>
<li><strong>Protocol</strong> (http, https)</li>
<li><strong>Host</strong> (domain.com, localhost)</li>
<li><strong>Port</strong> (80, 443, 3000)</li>
</ul>
<p>예를 들어, <code>https://naver.com</code>에서 <code>https://api.naver.com</code>으로 요청을 보내면 호스트가 다르기 때문에 CORS 정책의 적용을 받게 됩니다.</p>
<h3 id="2-cors의-작동-방식">2. CORS의 작동 방식</h3>
<p>CORS는 HTTP 헤더를 통해 작동하며, 크게 두 가지 요청 방식으로 나뉩니다.</p>
<h4 id="단순-요청-simple-request">단순 요청 (Simple Request)</h4>
<p>다음 조건을 만족하는 경우, 브라우저는 바로 본 요청을 보냅니다.</p>
<ul>
<li>메서드가 <code>GET</code>, <code>POST</code>, <code>HEAD</code> 중 하나일 것</li>
<li>헤더가 <code>Accept</code>, <code>Content-Language</code> 등 기본적인 것만 포함될 것</li>
</ul>
<h4 id="예비-요청-preflight-request">예비 요청 (Preflight Request)</h4>
<p>대부분의 현대적인 API 요청(JSON 데이터 전송 등)은 이 방식에 해당합니다.</p>
<ol>
<li><strong>OPTIONS 메서드</strong>를 사용하여 본 요청을 보내기 전, 서버에 &quot;내가 이런 요청을 보내도 되니?&quot;라고 먼저 물어봅니다.</li>
<li>서버는 응답 헤더에 허용되는 출처(<code>Access-Control-Allow-Origin</code>) 등을 담아 보냅니다.</li>
<li>브라우저가 응답을 확인하고 안전하다고 판단하면 그제야 진짜 요청을 보냅니다.</li>
</ol>
<h3 id="3-주요-응답-헤더">3. 주요 응답 헤더</h3>
<p>서버 측에서 CORS 문제를 해결하기 위해 설정해야 하는 핵심 헤더들입니다.</p>
<table>
<thead>
<tr>
<th>헤더</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Access-Control-Allow-Origin</strong></td>
<td>자원 접근이 허용된 출처를 지정합니다. (예: <code>*</code> 또는 <code>https://my-site.com</code>)</td>
</tr>
<tr>
<td><strong>Access-Control-Allow-Methods</strong></td>
<td>허용되는 HTTP 메서드 목록을 지정합니다. (GET, POST, PUT 등)</td>
</tr>
<tr>
<td><strong>Access-Control-Allow-Headers</strong></td>
<td>실제 요청 시 사용할 수 있는 HTTP 헤더를 지정합니다.</td>
</tr>
<tr>
<td><strong>Access-Control-Allow-Credentials</strong></td>
<td>쿠키나 인증 정보를 포함한 요청을 허용할지 여부를 결정합니다.</td>
</tr>
</tbody></table>
<h3 id="4-흔히-발생하는-오해">4. 흔히 발생하는 오해</h3>
<ul>
<li><strong>서버 간 통신에는 해당되지 않음:</strong> CORS는 웹 브라우저가 강제하는 보안 정책입니다. 따라서 서버(Node.js, Java 등)에서 다른 서버로 요청을 보낼 때는 발생하지 않습니다.</li>
<li><strong>서버는 요청을 수행할 수도 있음:</strong> 브라우저가 응답을 차단하는 것이지, 서버가 요청을 아예 안 받는 것은 아닙니다. 서버 로그에는 성공으로 뜨는데 브라우저 콘솔에서만 에러가 날 수 있습니다.</li>
</ul>
<h1 id="cors가-왜-보안이랑-연결되는-거지">cors가 왜 보안이랑 연결되는 거지??</h1>
<p>SOP(Same-Origin Policy, 동일 출처 정책)가 보안의 핵심인 이유는 간단합니다. <strong>&quot;내가 로그인한 A 사이트의 권한을 B 사이트가 마음대로 가로채지 못하게 하기 위해서&quot;</strong>입니다.</p>
<p>만약 SOP가 없다면, 여러분이 브라우저에서 탭을 여러 개 띄워놓고 사용하는 것 자체가 매우 위험한 일이 됩니다.</p>
<h3 id="1-sop가-막아주는-핵심-공격-csrf-사이트-간-요청-위조">1. SOP가 막아주는 핵심 공격: CSRF (사이트 간 요청 위조)</h3>
<p>가장 대표적인 시나리오는 다음과 같습니다.</p>
<ol>
<li><strong>상황:</strong> 여러분이 <code>bank.com</code>(은행 사이트)에 로그인되어 있고, 브라우저에는 해당 사이트의 로그인 세션(쿠키)이 남아 있습니다.</li>
<li><strong>공격:</strong> 이때 피싱 메일 등을 통해 악성 사이트인 <code>evil.com</code>에 접속하게 됩니다.</li>
<li><strong>실행:</strong> <code>evil.com</code>의 자바스크립트가 배경에서 몰래 <code>bank.com/transfer?to=attacker&amp;amount=10000</code>이라는 요청을 보냅니다.</li>
<li><strong>결과:</strong> 브라우저는 <code>bank.com</code>으로 요청을 보낼 때 저장된 쿠키(인증 정보)를 함께 실어 보냅니다. SOP가 없다면 은행 서버는 &quot;오, 정당한 사용자가 돈을 보내라고 했네?&quot;라고 판단하여 송금을 처리해 버립니다.</li>
</ol>
<h3 id="2-정보-유출-방지-data-privacy">2. 정보 유출 방지 (Data Privacy)</h3>
<p>SOP는 단순히 &#39;요청&#39;을 막는 것뿐만 아니라, <strong>&#39;응답 데이터에 접근하는 것&#39;</strong>을 막습니다.</p>
<ul>
<li><code>naver.com</code>에 접속 중인 상태에서 다른 탭으로 들어간 <code>malicious-site.com</code>이 자바스크립트를 이용해 <code>naver.com/my-profile</code>의 내용을 읽어오려 한다고 가정해 봅시다.</li>
<li>SOP가 적용되어 있다면, 브라우저는 출처(Origin)가 다르다는 것을 확인하고 <code>malicious-site.com</code>이 네이버 서버로부터 온 응답 데이터를 읽지 못하도록 차단합니다.</li>
<li>이 정책 덕분에 개인 정보나 이메일 내용 등이 다른 사이트의 스크립트에 의해 노출되지 않습니다.</li>
</ul>
<h3 id="3-브라우저의-샌드박스-역할">3. 브라우저의 &#39;샌드박스&#39; 역할</h3>
<p>웹 브라우저는 수많은 서로 다른 서비스들이 한 공간(브라우저)에서 실행되는 환경입니다. SOP는 각 웹사이트가 자신만의 <strong>독립된 보안 구역(Sandbox)</strong> 안에서만 놀 수 있도록 벽을 세우는 역할을 합니다.</p>
<ul>
<li><strong>벽이 없다면:</strong> 웹은 무법천지가 되어, 어떤 사이트를 방문하든 내 브라우저에 열려 있는 다른 모든 사이트의 정보를 털어갈 수 있게 됩니다.</li>
</ul>
<h3 id="요약하자면">요약하자면</h3>
<p>SOP는 <strong>브라우저 차원의 최소한의 방어선</strong>입니다.</p>
<ol>
<li><strong>누가 나인 척하는 것(요청 위조)</strong>을 어렵게 만들고,</li>
<li><strong>내 정보를 훔쳐보는 것(데이터 탈취)</strong>을 원천 봉쇄합니다.</li>
</ol>
<p>CORS는 바로 이 엄격한 보안 벽(SOP)에 <strong>&quot;이 사이트는 믿을 만하니까 우리가 정한 규칙 하에 벽을 살짝 열어주자&quot;</strong>라고 예외를 두는 허가증 같은 개념인 셈입니다.</p>
<h1 id="그럼-cors를-허용해주는건-특정-서버에서-다른-서버를-믿을-수-있다고-등록해줘야-가능한건가-그러니까-예를-들어서-edward_giver-서버에서-edward_taker-서버는-믿을-수-있는-서버라고-등록을-해놨다면-edward_takercom이-edward_giver-서버로-요청을-보내도-정상응답을-받을-수-있는-것이고-edward_taker-서버가-edward_giver-서버를-등록하지-않았다면-그-요청은-불발되고-브라우저가-에러를-띄우는-건가">그럼 cors를 허용해주는건 특정 서버에서 다른 서버를 믿을 수 있다고 등록해줘야 가능한건가?? 그러니까 예를 들어서 edward_giver 서버에서 edward_taker 서버는 믿을 수 있는 서버라고 등록을 해놨다면 edward_taker.com이 edward_giver 서버로 요청을 보내도 정상응답을 받을 수 있는 것이고 edward_taker 서버가 edward_giver 서버를 등록하지 않았다면 그 요청은 불발되고 브라우저가 에러를 띄우는 건가?</h1>
<p>정확하게 이해하셨습니다. 다만, 주체와 방향을 조금 더 명확히 정리하면 완벽합니다. CORS 설정은 <strong>자원을 가진 서버(Giver)</strong>가 <strong>누구에게 허용할지(Taker)</strong>를 결정하는 보안 정책입니다.</p>
<p>말씀하신 예시를 바탕으로 구조를 다시 정리해 드릴게요.</p>
<h3 id="1-누가-누구를-등록해야-하나요">1. 누가 누구를 등록해야 하나요?</h3>
<p>CORS는 <strong>&#39;데이터를 주는 서버&#39;</strong>가 설정하는 것입니다.</p>
<ul>
<li><strong>상황:</strong> <code>edward_taker.com</code>(클라이언트/프론트엔드)이 <code>edward_giver.com</code>(API 서버)에 데이터를 요청합니다.</li>
<li><strong>설정 주체:</strong> <code>edward_giver</code> 서버가 설정을 가지고 있어야 합니다.</li>
<li><strong>내용:</strong> <code>edward_giver</code> 서버 설정 파일에 <strong>&quot;우리 데이터를 <code>edward_taker.com</code> 출처에서 가져가는 것을 허용한다&quot;</strong>라는 화이트리스트를 등록하는 방식입니다.</li>
</ul>
<h3 id="2-요청이-불발되는-시나리오">2. 요청이 불발되는 시나리오</h3>
<p>질문하신 내용처럼 등록 여부에 따라 결과가 달라집니다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th><code>edward_giver</code> 서버의 상태</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td><strong>허용 등록 시</strong></td>
<td><code>Access-Control-Allow-Origin: edward_taker.com</code> 헤더를 응답에 포함</td>
<td>브라우저가 응답을 확인하고 <code>edward_taker</code>의 스크립트에 데이터를 전달함 (<strong>성공</strong>)</td>
</tr>
<tr>
<td><strong>미등록 시</strong></td>
<td>해당 헤더가 없거나 다른 도메인만 허용됨</td>
<td>브라우저가 응답을 가로채서 에러를 발생시킴 (<strong>실패</strong>)</td>
</tr>
</tbody></table>
<h3 id="3-브라우저가-에러를-띄우는-타이밍">3. 브라우저가 에러를 띄우는 타이밍</h3>
<p>여기서 재미있는 점은 <strong>&quot;요청 자체는 서버에 도달할 수도 있다&quot;</strong>는 것입니다.</p>
<ol>
<li><strong>단순 요청의 경우:</strong> 브라우저가 일단 <code>edward_giver</code>에게 데이터를 달라고 요청합니다. 서버는 평소처럼 데이터를 보내주지만, 응답 헤더에 &quot;허용&quot; 표시가 없으면 <strong>브라우저가 그 데이터를 파기</strong>하고 콘솔에 빨간색 CORS 에러를 띄웁니다.</li>
<li><strong>예비 요청(Preflight)의 경우:</strong> 브라우저가 본 요청을 보내기 전에 &quot;얘한테 데이터 줘도 돼?&quot;라고 먼저 물어봅니다(<code>OPTIONS</code> 메서드). 이때 서버가 &quot;안 돼&quot;라고 하면 본 요청은 시작조차 하지 않습니다.</li>
</ol>
<h3 id="4-왜-받는-쪽taker은-설정이-필요-없나요">4. 왜 &#39;받는 쪽(Taker)&#39;은 설정이 필요 없나요?</h3>
<p>CORS 에러는 <strong>&#39;내 소중한 데이터를 아무나 가져가지 못하게 보호&#39;</strong>하려는 서버의 방어 기제이기 때문입니다.</p>
<ul>
<li><code>edward_taker</code> 서버는 그저 요청을 보내는 입장일 뿐입니다.</li>
<li>만약 <code>edward_taker</code>가 보낸 요청이 실패한다면, 그것은 <code>edward_taker</code>가 잘못해서가 아니라 <code>edward_giver</code>가 문을 열어주지 않았기 때문입니다.</li>
</ul>
<h3 id="요약">요약</h3>
<ul>
<li><strong>Giver(서버):</strong> &quot;나는 <code>edward_taker.com</code>만 믿어. 얘네한테는 데이터 보내줄게.&quot; (설정 필요)</li>
<li><strong>Taker(클라이언트):</strong> &quot;나 <code>edward_taker</code>인데 데이터 좀 줘!&quot; (설정 불필요, 요청만 보냄)</li>
<li><strong>브라우저(경찰):</strong> &quot;Giver가 너(Taker) 허용 안 했네? 이 데이터는 못 보여줘!&quot; (에러 발생)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[python에서는 copy-on-write가 예상과 다르게 동작할 수 있다?]]></title>
            <link>https://velog.io/@edward-official/python-cow</link>
            <guid>https://velog.io/@edward-official/python-cow</guid>
            <pubDate>Mon, 02 Feb 2026 12:47:54 GMT</pubDate>
            <description><![CDATA[<h2 id="copy-on-write는-어떤-의미가-있나">copy-on-write는 어떤 의미가 있나?</h2>
<blockquote>
<p><img src="https://velog.velcdn.com/images/edward-official/post/b4b3ff61-ae65-4ff8-871e-11bdf2d66d23/image.png" alt="">
아무런 의미 없이 부모 프로세스의 가상 메모리 전체를 복제하는 오버헤드를 피할 수 있습니다.</p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/edward-official/post/56d0b434-5295-44ab-a29e-53d2770bd86a/image.png" alt="">
다수의 프로세스들이 같은 파일을 공유할 때 프로세스마다 곧바로 독립적인 파일을 자신의 가상 메모리에 갖는 것이 아니라 일단은 동일한 페이지를 공유하기 때문에 메모리 낭비를 줄일 수 있습니다.</p>
</blockquote>
<h2 id="ref-count">ref count??</h2>
<p>python에 대해서 공부하다가 ref count라는 정책을 발견했다.
이 정책은 python의 모든 객체에 ref count라는 값을 두고 이 값을 조절하면서 ref count가 0이 되었을 때 해당 객체를 deallocate하는 정책이다.
CPython에서 ref count라는 값은 실제 객체의 값과 물리/가상 메모리 레벨에서 붙어있다고 한다. (즉 한번에 같이 malloc된다고 보면 될 것 같다.)
이는 C++처럼 기계 레벌에 가까운 언어와는 다르게 명시적으로 메모리를 관리하는 방법이 없고, 언어 차원에서 메모리 관리를 담당해야하기 때문에 생겨난 정책으로 보인다.</p>
<h2 id="그래서-이게-copy-on-write이랑-무슨-상관이야">그래서 이게 copy-on-write이랑 무슨 상관이야?</h2>
<p><img src="https://velog.velcdn.com/images/edward-official/post/4d1f2439-654c-4b04-8621-2943ab2f0362/image.png" alt="">
celery에 대해서 공부를 하다보니 concurrency라는 개념을 알게 되었는데, 이는 하나의 celery worker가 처리할 수 있는 task의 수를 말한다.
하나의 worker가 여러 task를 처리하려면 당연히 execution unit이 여러개 필요할 텐데, 이 unit의 타입을 지정할 수 있다.
default 값으로는 prefork(multiprocess)가 있고 이외에도 threads, gevents, ...가 있는데, 이 중에 prefork 방식에 대해서 생각해보자.
이름에서 유추할 수 있듯이 fork를 통해서 자식 프로세스를 미리 생성해두는 방식인데, 흔히 생각하기로는 당연히 copy-on-write 정책을 통해서 메모리 최적화가 어느 정도는 이루어질 것이라고 예상할 수 있는데..
사실은 그렇지 않다.
단순히 특정 객체를 read하는 작업만으로도 ref count가 바뀌기 때문에 사실은 내부적으로 write가 발생하게 되고 바로 그 객체가 속한 페이지 테이블을 복사하게 되는 것. (heap)
따라서 우리가 생각하는 것과는 다르게 cow가 진행되므로 python 기반의 multiprocess 방식은 생각보다 메모리를 많이 잡아먹을 수 있다. (이런 점에 미루어 볼 때 python은 CPU bound의 작업에 좋지 않은 언어인 것 같다...)
일단 다른 언어와 프레임워크에서는 모르겠지만 python 기반의 프레임 워크에서는 이런 메모리 내부 동작을 의식하는 것이 필요할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Exception과 시그널에 대해서 간단히 알아보자.]]></title>
            <link>https://velog.io/@edward-official/exception-and-signal</link>
            <guid>https://velog.io/@edward-official/exception-and-signal</guid>
            <pubDate>Fri, 14 Nov 2025 06:41:34 GMT</pubDate>
            <description><![CDATA[<h2 id="exception의-4가지-종류">Exception의 4가지 종류</h2>
<p><img src="https://velog.velcdn.com/images/edward-official/post/6ddb3f53-b815-437e-aa89-72c37030dbcd/image.png" alt=""></p>
<ul>
<li><strong>Interrupt</strong>: <strong>외부 하드웨어</strong>에 의해서 <strong>인스트럭션들 사이</strong>에 발생하는 비동기적 예외 상황 <strong>(타이머 인터럽트, 입출력 장치)</strong></li>
<li><strong>Trap</strong>: 인스트럭션 직후에 발생하는 <strong>의도적인</strong> 예외 상황 <strong>(시스템 콜)</strong></li>
<li><strong>Fault</strong>: <strong>인스트럭션 실행 중</strong>에 발생하는 동기적 예외 상황 <strong>(page fault, segmentation fault, divide by zero fault)</strong></li>
<li><strong>Abort</strong>: 주로 하드웨어에서 발생하는 <strong>복구 불가능</strong>한 예외 상황</li>
</ul>
<h2 id="시그널">시그널</h2>
<p><img src="https://velog.velcdn.com/images/edward-official/post/c957693f-0d6f-45d5-816c-b57ba66d4959/image.png" alt="">
이런 <strong>예외 상황에 대해서 커널이 핸들러</strong>를 실행하고 그 후 <strong>프로세스가 특정 조치를 취해야한다고 판단</strong>이 되면,
<strong>커널은 프로세스에게 시그널</strong>을 보내고 프로세스는 이에 대해서 <strong>default action</strong>을 취하거나 <strong>signal handler</strong>를 실행한다.
시그널은 <strong>도착 순서를 보장하지 않으며 같은 시그널이 여러번 도착해도 1번만 처리한다는 특징</strong>이 있다.
따라서 시그널 핸들링은 <strong>매우 주의</strong>해서 해야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[가상 주소 다이어그램 때문에 내 두뇌 회로에 asynchronous exception이 발생했던 일에 관하여.]]></title>
            <link>https://velog.io/@edward-official/multi-level-paging</link>
            <guid>https://velog.io/@edward-official/multi-level-paging</guid>
            <pubDate>Fri, 14 Nov 2025 04:51:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/edward-official/post/dc4acd66-008a-45c6-b963-7649b99bde43/image.png" alt=""></p>
<h2 id="사건의-발단">사건의 발단</h2>
<p>내가 CSAPP을 읽고난 후 알고 있었던 가상 메모리는 <strong>VPN + VPO</strong>의 조합이었는데, 깃북을 읽어보니까 내가 알고 있던 것보다 훨씬 더 자세하게 <strong>세분화</strong>가 되어있었다.
그래서 내 공부의 흐름에 갑자기 asynchronous exception이 일어났다.</p>
<p><img src="https://velog.velcdn.com/images/edward-official/post/ee8a8495-49c0-418f-8ad0-7be194fcf838/image.png" alt=""></p>
<h2 id="아-일단-예전에-공부했던-내용도-기억이-잘-안나네">아 일단 예전에 공부했던 내용도 기억이 잘 안나네..</h2>
<p>일단 CPU(프로세스)가 메모리에 접근할 때, <strong>가상 주소</strong>를 사용한다.
<code>(자세한 이해를 돕기 위해서 내부 원리를 보충 설명하자면 어떤 코드가 ELF 파일로 컴파일 될 때는 가상 주소만 배정이 되고 그 가상 주소가 실제로 어떤 물리 공간을 배정받고 어떤 물리 주소를 가질 지는 로드되기 전까지는 모른다. 즉 페이지 테이블이 생성되는 것 자체가 로드 시점 이후부터 시작된다.)</code></p>
<p>이 과정에서 <strong>MMU</strong>는 <strong>페이지 테이블</strong>을 참조하여 가상 주소를 물리 주소로 변환한다.</p>
<p>이 지점에서 그럼 MMU는 <strong>어떻게 메모리에 있는 페이지 테이블에 접근</strong>하는 건지 의문이 들었다.
<code>(알아보니 어떤 프로세스가 시작될 때 그 프로세스의 페이지 테이블 주소를 CR3이라는 레지스터에 넣어 둔다고 한다.)</code></p>
<p>또 내 생각에 <strong>페이지 테이블</strong>은 가상 메모리의 <strong>커널 영역</strong>에 있을 것 같은 데 그 생각이 맞는 지 궁금해졌다.
<code>(그렇다고 한다.)</code></p>
<p>또 대부분 사람들이 가상 메모리의 커널 영역이 프로세스들 간에 완전히 공통인 영역이라고 생각하는데, <strong>페이지 테이블이나 파일 디스크립터 테이블 그리고 스레드의 커널 스택</strong>(각 스레드의 레지스터, 지역 변수 등을 백업하기 위해서 생성되는 각 스레드들을 위한 독립적인 커널 공간)을 고려하면 이 커널 영역이 완전히 공통인 건 <strong>말이 안된다는 생각</strong>을 했다.
<code>(이것도 내 생각이 맞았다.)</code></p>
<p>암튼 <strong>VPN</strong>으로 페이지 테이블 내에서 <strong>엔트리</strong>를 찾아내고 그 곳에 저장된 <strong>PPN</strong>을 얻어낸다.
이렇게 얻어낸 <strong>PPN에 PPO(VPO)를 조합</strong>해서 <strong>물리주소</strong>를 얻어내는 것이다.</p>
<h2 id="그래서-깃북에-나온-그-가상주소-다이어그램의-정체는-뭐지">그래서 깃북에 나온 그 가상주소 다이어그램의 정체는 뭐지??</h2>
<p>지금까지가 내가 원래 알고 있던 가상 메모리의 형태인데, 이를 <strong>단일 레벨 페이지 모델</strong>이라고 부른다고 한다.
이는 구조가 단순하지만 <strong>페이지 테이블 크기가 너무 커진다는 문제</strong>가 있다.
<code>(전체 논리 주소 공간의 페이지 테이블을 무조건 생성. 논리 주소 공간이란 프로세스가 가질 수 있는 최대 가상 메모리 범위)</code></p>
<p>이에 대한 해결책으로 단일 거대 페이지 테이블을 <strong>여러 레벨로 분리</strong>하여 <strong>멀티 레벨 페이지 모델</strong>이라는 개념이 탄생했다고 한다.
이 자세한 구조가 뭔지는 공부를 더 해야겠지만, 핵심은 <strong>&quot;필요한 만큼만&quot; 페이지 테이블을 동적으로 생성</strong>한다는 점이라고 말할 수 있다.
이 모델은 <strong>구조의 복잡성 때문에 속도가 느리다는 문제</strong>가 있지만 <strong>TLB가 이 문제를 상당 부분 해소</strong>해준다는 특징이 있다.
아래는 깃북에 나온 <strong>가상 메모리 주소 영역별 역할에 대한 설명</strong>이다.
<code>(아직 코드를 통해서 기술적으로 다뤄보지 않았기 때문에 깊이 있는 이해가 더해지면 내용을 추가로 업데이트할 계획이다.)</code></p>
<h4 id="1-sign-extend-상위-16비트">1. Sign Extend (상위 16비트):</h4>
<p>x86-64는 실제로 <strong>48비트 가상 주소만 사용</strong>한다.
<code>(전체 64비트 주소 공간(16EB)을 사용하는 것은 비현실적으로 큼)</code></p>
<p>따라서 나머지 상위 16비트(63~48)는 48번째 비트(Sign Bit)를 복사해 채운다. (canonical address)
<strong>즉, 상위 16비트는 주소 번역에는 참여하지 않고, 단지 CPU가 유효한 주소인지 확인하는 용도.</strong></p>
<h4 id="2-page-map-level-4-offset-pml4-index-9비트">2. Page-Map Level-4 Offset (PML4 index, 9비트):</h4>
<p><strong>최상위 페이지 테이블(PML4)을</strong> 인덱싱하는 값 (0~511)
CPU가 <strong>CR3 레지스터에 저장된 PML4 물리 주소</strong>를 가져와, 이 9비트로 <strong>PML4 엔트리</strong>(512개 중 하나)를 선택함
<strong>PML4 엔트리</strong>는 <strong>PDPT(Page Directory Pointer Table)의 물리 주소</strong>를 가리킴
즉, <strong>다음 단계의 테이블</strong>을 가리키는 포인터</p>
<h4 id="3-page-directory-pointer-offset-pdpt-index-9비트">3. Page-Directory Pointer Offset (PDPT index, 9비트):</h4>
<p>PML4가 가리킨 PDPT에서 512개 엔트리 중 하나를 고름
<strong>PDPT 엔트리</strong>는 <strong>PD(Page Directory)의 물리 주소</strong>를 가리킴</p>
<h4 id="4-page-directory-offset-pd-index-9비트">4. Page-Directory Offset (PD index, 9비트):</h4>
<p>PDPT에서 선택된 PD에서 512개 중 하나를 찾음
<strong>PD 엔트리</strong>는 <strong>PT(Page Table)의 물리 주소</strong>를 가리킴</p>
<h4 id="5-page-table-offset-pt-index-9비트">5. Page-Table Offset (PT index, 9비트):</h4>
<p><strong>PT(페이지 테이블)</strong>에서 512개 중 하나의 <strong>PTE(Page Table Entry)</strong>를 찾음
PTE는 페이지가 매핑된 <strong>물리 프레임의 주소 + 플래그들(r/w, user, present…)</strong></p>
<pre><code>예시
PFN: 0x12345
Flags: RW=1, USER=1, PRESENT=1</code></pre><h4 id="6-physical-offset-page-offset-12비트">6. Physical Offset (Page Offset, 12비트):</h4>
<p><strong>페이지(4KB) 내부에서의 오프셋</strong> (페이지 크기가 2^12 = 4096 byte이기 때문에 12비트)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[메모리 매핑 영역이 동작하는 방법]]></title>
            <link>https://velog.io/@edward-official/memory-mapped-region</link>
            <guid>https://velog.io/@edward-official/memory-mapped-region</guid>
            <pubDate>Wed, 22 Oct 2025 04:42:43 GMT</pubDate>
            <description><![CDATA[<h4 id="메모리-매핑이란">메모리 매핑이란?</h4>
<p>디스크 파일의 내용을 가상 메모리 주소 공간에 직접 연결(<code>mmap</code>) 하는 방식.
즉, 파일의 데이터를 <code>read()</code> 없이 바로 메모리처럼 접근할 수 있게 만드는 기술이에요.</p>
<hr>
<h4 id="동작-원리">동작 원리</h4>
<ol>
<li><p><strong>매핑 설정</strong></p>
<ul>
<li>OS가 파일을 <strong>가상 주소 공간의 일부(=가상 페이지 집합)</strong> 에 연결만 해둠.</li>
<li>이 시점엔 아직 RAM에 올라가지 않음.</li>
</ul>
</li>
<li><p><strong>접근 시 (on-demand load)</strong></p>
<ul>
<li>프로그램이 해당 주소를 읽으면 <strong>페이지 폴트 발생</strong></li>
<li>OS가 디스크에서 해당 부분을 읽어 <strong>물리 메모리(RAM)</strong> 에 올림.</li>
</ul>
</li>
<li><p><strong>미사용 시 (swap-out)</strong></p>
<ul>
<li>오래 사용하지 않거나 메모리 부족 시 OS가 해당 페이지를 다시 <strong>디스크로 내림</strong>.</li>
</ul>
</li>
</ol>
<hr>
<h4 id="관련-개념-정리">관련 개념 정리</h4>
<table>
<thead>
<tr>
<th>용어</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>가상 페이지 집합</strong></td>
<td>가상 주소 공간 상의 페이지들 (아직 RAM에 없을 수도 있음)</td>
</tr>
<tr>
<td><strong>메모리 매핑 영역</strong></td>
<td>mmap으로 파일이 연결되는 가상 메모리 구역 (힙과 스택 사이에 위치)</td>
</tr>
<tr>
<td><strong>mmap() 시스템 콜</strong></td>
<td>프로그램이 직접 메모리 매핑을 수행할 수 있게 해주는 함수</td>
</tr>
<tr>
<td><strong>on-demand paging</strong></td>
<td>접근할 때만 디스크에서 페이지를 읽어오는 지연 로드 방식</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[ELF 섹션과 가상 메모리 페이지의 관계]]></title>
            <link>https://velog.io/@edward-official/section-virtual-memory</link>
            <guid>https://velog.io/@edward-official/section-virtual-memory</guid>
            <pubDate>Wed, 22 Oct 2025 04:17:33 GMT</pubDate>
            <description><![CDATA[<h4 id="기본-개념">기본 개념</h4>
<table>
<thead>
<tr>
<th>구분</th>
<th><strong>섹션(Section)</strong></th>
<th><strong>페이지(Page)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>소속</strong></td>
<td>실행 파일(ELF) 내부 구조</td>
<td>운영체제가 관리하는 메모리 단위</td>
</tr>
<tr>
<td><strong>역할</strong></td>
<td>코드/데이터를 논리적으로 구분 (.text, .data, .bss 등)</td>
<td>실제 메모리에서 접근 권한과 관리 단위</td>
</tr>
<tr>
<td><strong>크기</strong></td>
<td>가변 (몇 바이트 ~ 수 KB)</td>
<td>고정 (보통 4KB)</td>
</tr>
<tr>
<td><strong>권한</strong></td>
<td>섹션별로 지정 (r-x, rw-, r--)</td>
<td>페이지 단위로 적용 (모든 바이트 동일 권한)</td>
</tr>
</tbody></table>
<hr>
<h4 id="세그먼트와-권한의-관계">세그먼트와 권한의 관계</h4>
<p>실행 파일은 실제로는 <strong>세그먼트(LOAD 단위)</strong> 로 메모리에 매핑됩니다.
즉, <strong>섹션은 세그먼트 내부에 포함</strong>되는 구조예요.</p>
<table>
<thead>
<tr>
<th>세그먼트</th>
<th>포함 섹션</th>
<th>접근 권한</th>
</tr>
</thead>
<tbody><tr>
<td>LOAD #1</td>
<td><code>.text</code>, <code>.rodata</code></td>
<td><strong>r-x</strong> 또는 <strong>r--</strong></td>
</tr>
<tr>
<td>LOAD #2</td>
<td><code>.data</code>, <code>.bss</code></td>
<td><strong>rw-</strong></td>
</tr>
</tbody></table>
<p>➡️ 즉, <strong>권한이 같은 섹션들만 같은 세그먼트(=페이지 영역)</strong> 에 묶입니다.
권한이 다르면 세그먼트가 분리되고, <strong>페이지도 분리</strong>됩니다.</p>
<hr>
<h4 id="페이지-배치-규칙">페이지 배치 규칙</h4>
<table>
<thead>
<tr>
<th>상황</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td><strong>섹션 권한이 같음</strong></td>
<td>같은 세그먼트에 포함되어, 필요시 한 페이지에 함께 들어갈 수도 있음 (예: <code>.data</code> + <code>.bss</code>)</td>
</tr>
<tr>
<td><strong>섹션 권한이 다름</strong></td>
<td>절대 같은 페이지에 배치되지 않음 (예: <code>.rodata</code>(r--) vs <code>.data</code>(rw-))</td>
</tr>
<tr>
<td><strong>섹션 크기가 페이지보다 큼</strong></td>
<td>여러 페이지에 걸쳐 저장됨</td>
</tr>
<tr>
<td><strong>섹션 크기가 페이지보다 작음</strong></td>
<td>남은 공간은 패딩(padding)으로 채워짐 (낭비 발생)</td>
</tr>
<tr>
<td><strong>페이지는 4KB 단위 정렬</strong></td>
<td>OS가 페이지 경계에 맞춰 매핑함</td>
</tr>
</tbody></table>
<hr>
<h4 id="예시-1">예시 1</h4>
<p><code>페이지 크기 =  4KB</code>
<code>.rodata = 1KB (r--)</code>
<code>.data = 2KB (rw-)</code></p>
<p>➡️ 메모리 배치 결과:</p>
<pre><code>[Page 1]
| .rodata (1KB) | [padding 3KB] |  ← 권한: r--
[Page 2]
| .data (2KB)   | [padding 2KB] |  ← 권한: rw-</code></pre><blockquote>
</blockquote>
<p>서로 다른 권한 → 반드시 다른 페이지 사용 (남는 공간은 패딩 처리)
만약 권한이 같고 두 섹션의 크기 합이 페이지 크기보다 작거나 같다면 같은 페이지로 들어갈 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실행 파일이 가상 메모리 공간을 형성하는 과정]]></title>
            <link>https://velog.io/@edward-official/execution-process</link>
            <guid>https://velog.io/@edward-official/execution-process</guid>
            <pubDate>Wed, 22 Oct 2025 02:36:53 GMT</pubDate>
            <description><![CDATA[<h4 id="실행-파일이-뭔데">실행 파일이 뭔데?</h4>
<ul>
<li>컴파일 + 링크 결과물로 만들어진 <strong>하나의 파일</strong>이에요.</li>
<li><strong>디스크(SSD/HDD)</strong> 위에만 존재하고, 아직 “실행 중”은 아닙니다.</li>
<li>안에는 코드(.text), 데이터(.data), 전역 변수(.bss) 등이 들어 있어요.</li>
</ul>
<p>👉 즉, <strong>“프로그램의 설계도”</strong> 같은 존재예요.</p>
<hr>
<h4 id="이-파일을-실행하면-어떤-동작이-일어날까">이 파일을 실행하면 어떤 동작이 일어날까?</h4>
<ol>
<li><strong>운영체제(OS)</strong> 가 새로운 <strong>프로세스(process)</strong> 를 만듭니다.</li>
<li>그 프로세스에 <strong>독립된 가상 메모리 공간</strong>을 만들어줘요.</li>
<li>실행 파일의 각 부분을 이 가상 공간에 <strong>매핑(mapping)</strong> 합니다.</li>
</ol>
<p>이때 아직 메모리에 다 올라간 건 아닙니다❗️
(이건 &quot;가상의 주소만 연결해둔 상태&quot;예요.)</p>
<hr>
<h4 id="그럼-언제-메모리에-올라갈까">그럼 언제 메모리에 올라갈까?</h4>
<p>프로그램이 실행되면서 <strong>CPU가 어떤 코드나 데이터를 처음 사용할 때</strong>,
그 부분만 디스크에서 RAM으로 <strong>조금씩 불러옵니다.</strong></p>
<p>이걸 <strong>“온디맨드(on-demand) 로딩”</strong> 또는 <strong>“페이지 단위 캐싱”</strong>이라고 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[링크: 심볼 해석 알고리즘]]></title>
            <link>https://velog.io/@edward-official/link-symbol-resolution</link>
            <guid>https://velog.io/@edward-official/link-symbol-resolution</guid>
            <pubDate>Fri, 17 Oct 2025 05:36:01 GMT</pubDate>
            <description><![CDATA[<p><strong>심볼 해석(symbol resolution)</strong> 단계에서,
링커는 명령줄에 나타난 순서대로
재배치 가능한 오브젝트 파일들과 정적 라이브러리(archive)를 <strong>왼쪽에서 오른쪽으로</strong> 스캔한다.</p>
<p>(컴파일러 드라이버는 <code>.c</code> 파일 이름을 내부적으로 <code>.o</code> 파일로 변환하여 링커에 전달한다.)</p>
<hr>
<p>이 과정에서 링커는 세 개의 집합을 유지한다:</p>
<ul>
<li><strong>E</strong>: 실행 파일에 포함될 재배치 가능한 오브젝트 파일들의 집합</li>
<li><strong>U</strong>: 아직 정의되지 않은, 즉 해결되지 않은 심볼들의 집합</li>
<li><strong>D</strong>: 이미 정의된 심볼들의 집합</li>
</ul>
<p>초기에는 <strong>E</strong>, <strong>U</strong>, <strong>D</strong> 모두 비어 있다.</p>
<hr>
<h3 id="링커의-동작-알고리즘">링커의 동작 알고리즘</h3>
<ol>
<li><p><strong>명령줄의 각 입력 파일 <code>f</code>에 대해</strong>,
링커는 <code>f</code>가 오브젝트 파일인지 아카이브(라이브러리)인지 확인한다.</p>
<ul>
<li>만약 <code>f</code>가 <strong>오브젝트 파일</strong>이면:
링커는 <code>f</code>를 <code>E</code>에 추가하고,
<code>f</code>의 심볼 정의 및 참조 정보를 기반으로 <code>U</code>와 <code>D</code>를 갱신한 뒤,
다음 입력 파일로 넘어간다.</li>
</ul>
</li>
<li><p>만약 <code>f</code>가 <strong>라이브러리(archive)</strong> 라면:
링커는 <code>U</code>에 있는 <strong>아직 해결되지 않은 심볼들</strong>이
라이브러리 안의 어떤 멤버 오브젝트 파일의 정의와 일치하는지 검사한다.</p>
<ul>
<li>만약 어떤 멤버 <code>m</code>이 <code>U</code>에 있는 심볼을 해결한다면,
링커는 <code>m</code>을 <code>E</code>에 추가하고,
<code>U</code>와 <code>D</code>를 갱신한다.</li>
</ul>
<p>이 과정은 <strong>U와 D가 더 이상 변하지 않을 때까지</strong> 반복된다.
그 이후, 참조되지 않은 나머지 멤버 오브젝트 파일들은 무시된다.</p>
</li>
<li><p>링커가 명령줄의 모든 입력 파일을 처리한 뒤에도
<strong>U가 비어 있지 않으면</strong>,
링커는 <strong>오류 메시지를 출력하고 종료</strong>한다.</p>
<p>반대로, 모든 참조가 해결되면
<code>E</code> 안의 오브젝트 파일들을 병합하고 재배치하여
최종 실행 파일을 만든다.</p>
</li>
</ol>
<hr>
<h3 id="⚠️-명령줄-순서의-중요성">⚠️ 명령줄 순서의 중요성</h3>
<p>이 알고리즘의 단점은,
<strong>명령줄에 나열된 파일의 순서가 매우 중요하다</strong>는 것이다.</p>
<p>만약 어떤 라이브러리가 자신이 필요한 오브젝트 파일보다 <strong>앞에</strong> 위치한다면,
그 라이브러리의 심볼 정의는 무시되고
참조가 해결되지 않은 채로 남는다.</p>
<hr>
<h3 id="예시">예시</h3>
<pre><code class="language-bash">linux&gt; gcc -static ./libvector.a main2.c
/tmp/cc9XH6Rp.o: In function ‘main’:
/tmp/cc9XH6Rp.o(.text+0x18): undefined reference to ‘addvec’</code></pre>
<p>무슨 일이 일어난 걸까?</p>
<ul>
<li>링커가 <code>libvector.a</code>를 처리할 때는 아직 <code>main2.c</code>가 처리되지 않았기 때문에
<strong>U 집합이 비어 있음</strong> → <code>addvec</code> 참조가 존재하지 않음.</li>
<li>따라서 <code>libvector.a</code>의 어떤 오브젝트 파일도 <code>E</code>에 추가되지 않음.</li>
<li>이후 <code>main2.c</code>를 처리할 때 <code>addvec</code> 참조가 생기지만,
이미 <code>libvector.a</code>는 지나간 상태라 링커는 이를 해결하지 못함.</li>
</ul>
<p>→ 결과적으로 <code>undefined reference to &#39;addvec&#39;</code> 오류가 발생한다.</p>
<hr>
<h3 id="✅-올바른-순서">✅ 올바른 순서</h3>
<p>라이브러리는 <strong>명령줄의 끝에 위치해야 한다.</strong>
이렇게 하면 링커가 모든 오브젝트 파일의 참조를 먼저 수집한 뒤,
그 참조를 해결하기 위해 라이브러리를 뒤에서부터 읽을 수 있다.</p>
<hr>
<h3 id="여러-라이브러리의-순서">여러 라이브러리의 순서</h3>
<pre><code class="language-bash">linux&gt; gcc foo.c libx.a libz.a liby.a</code></pre>
<p>만약 <code>foo.c</code>가 <code>libx.a</code>와 <code>libz.a</code>의 함수를 호출하고,
이 두 라이브러리가 또 <code>liby.a</code>의 함수를 호출한다면,
<code>libx.a</code>와 <code>libz.a</code>는 반드시 <code>liby.a</code> <strong>앞</strong>에 있어야 한다.</p>
<p>그렇지 않으면, <code>liby.a</code>에서 정의된 심볼을 찾지 못하게 된다.</p>
<hr>
<h3 id="반복-사용-라이브러리-재참조">반복 사용 (라이브러리 재참조)</h3>
<p>필요하다면 명령줄에서 <strong>같은 라이브러리를 여러 번 명시할 수도 있다.</strong></p>
<p>예를 들어:</p>
<pre><code class="language-bash">linux&gt; gcc foo.c libx.a liby.a libx.a</code></pre>
<p>이 경우,</p>
<ul>
<li><code>foo.c</code>가 <code>libx.a</code>의 함수를 호출하고,</li>
<li><code>libx.a</code>가 <code>liby.a</code>의 함수를 호출하며,</li>
<li>다시 <code>liby.a</code>가 <code>libx.a</code>의 함수를 호출하는 경우에도
모든 참조가 올바르게 해결된다.</li>
</ul>
<hr>
<h3 id="대안적-방법">대안적 방법</h3>
<p>두 라이브러리(<code>libx.a</code>, <code>liby.a</code>)가 서로를 참조하는 경우라면,
<strong>두 라이브러리를 하나의 아카이브로 합치는 것</strong>도 좋은 해결책이 될 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[링크 과정 오류: weak symbol의 중복]]></title>
            <link>https://velog.io/@edward-official/link-error-identical-weak-symbols</link>
            <guid>https://velog.io/@edward-official/link-error-identical-weak-symbols</guid>
            <pubDate>Fri, 17 Oct 2025 05:11:48 GMT</pubDate>
            <description><![CDATA[<h3 id="두-개의-weak-심볼이-있을-때">두 개의 weak 심볼이 있을 때</h3>
<pre><code class="language-c">/* foo4.c */
#include &lt;stdio.h&gt;
void f(void);

int x;  // weak

int main()
{
    x = 15213;
    f();
    printf(&quot;x = %d\n&quot;, x);
    return 0;
}

/* bar4.c */
int x;  // weak

void f()
{
    x = 15212;
}</code></pre>
<p>이 경우 링커는 (규칙 3에 따라)
<strong>두 weak 심볼 중 하나를 임의로 선택</strong>한다.
→ 결과는 예측 불가능할 수 있으며, 버그를 유발할 수 있다.</p>
<hr>
<h3 id="타입-불일치에-의한-메모리-덮어쓰기">타입 불일치에 의한 메모리 덮어쓰기</h3>
<pre><code class="language-c">/* foo5.c */
#include &lt;stdio.h&gt;
void f(void);

int y = 15212;
int x = 15213;

int main()
{
    f();
    printf(&quot;x = 0x%x y = 0x%x\n&quot;, x, y);
    return 0;
}

/* bar5.c */
double x;

void f()
{
    x = -0.0;
}</code></pre>
<hr>
<h4 id="세부-동작-타입-불일치에-의한-메모리-덮어쓰기">세부 동작 (타입 불일치에 의한 메모리 덮어쓰기)</h4>
<p>x86-64 리눅스에서:</p>
<ul>
<li><code>double</code>은 8바이트</li>
<li><code>int</code>는 4바이트</li>
</ul>
<p>주소 배치:</p>
<pre><code>x = 0x601020
y = 0x601024</code></pre><p><code>x = -0.0</code>은 <code>double</code> 값이므로
그 8바이트가 <code>x</code>와 <code>y</code>의 메모리 공간을 모두 덮어쓴다.</p>
<p>결과:</p>
<pre><code class="language-bash">linux&gt; gcc -Wall -Og -o foobar5 foo5.c bar5.c
/usr/bin/ld: Warning: alignment 4 of symbol ‘x’ ... is smaller than 8
linux&gt; ./foobar5
x = 0x0 y = 0x80000000</code></pre>
<p>→ <code>x</code>는 0으로, <code>y</code>는 double 음수 0의 하위 비트가 덮여 <strong>0x80000000</strong>이 됨.</p>
<hr>
<h3 id="⚠️-이런-버그의-위험성">⚠️ 이런 버그의 위험성</h3>
<p>이런 오류는 링커가 <strong>오류가 아닌 경고만 출력</strong>하기 때문에
발견이 어렵고, 나중에 프로그램 실행 중 전혀 엉뚱한 시점에 나타날 수 있다.</p>
<p>대규모 시스템에서는 이런 종류의 버그를 찾기가 <strong>매우 어렵다.</strong>
대부분의 프로그래머는 링커 동작에 익숙하지 않아
경고를 무시하기 때문이다.</p>
<blockquote>
<p>💡 <strong>팁:</strong>
GCC 옵션 <code>-fno-common</code>을 사용하면
중복 전역 심볼 정의 시 오류를 발생시킨다.
또는 <code>-Werror</code> 옵션으로 모든 경고를 오류로 취급할 수도 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[ELF .data 섹션에는 어떤 형태로 데이터가 들어갈까?]]></title>
            <link>https://velog.io/@edward-official/elf-data</link>
            <guid>https://velog.io/@edward-official/elf-data</guid>
            <pubDate>Thu, 16 Oct 2025 13:50:06 GMT</pubDate>
            <description><![CDATA[<p><code>.data</code> 섹션은 <strong>단순한 바이트 배열</strong>일 뿐입니다.
즉, 이 영역 안에는 <strong>데이터의 타입이나 이름, 크기 정보가 전혀 포함되지 않습니다.</strong></p>
<hr>
<h3 id="예시-정수-데이터-저장">예시: 정수 데이터 저장</h3>
<p>예를 들어 다음과 같은 코드가 있다고 해봅시다:</p>
<pre><code class="language-c">int x = 0x11223344;</code></pre>
<p>이 변수의 초기값은 ELF의 <code>.data</code> 섹션에 <strong>그대로 저장</strong>됩니다.</p>
<table>
<thead>
<tr>
<th>메모리 오프셋</th>
<th>내용(16진수)</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>0x00</td>
<td><code>44 33 22 11</code></td>
<td><code>int x = 0x11223344;</code> (리틀 엔디안 기준)</td>
</tr>
</tbody></table>
<p>즉, <strong>타입 정보 없이 순수한 바이트 나열</strong>입니다.</p>
<hr>
<h3 id="심볼-테이블의-역할">심볼 테이블의 역할</h3>
<p>이렇게 단순히 저장된 데이터는 <strong>링킹(linking)</strong> 과정에서
<strong>심볼 테이블(Symbol Table)</strong> 의 도움을 받아 의미를 얻게 됩니다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><strong>심볼 이름</strong> (<code>st_name</code>)</td>
<td>변수나 함수의 이름 (<code>x</code>, <code>printf</code>, 등)</td>
</tr>
<tr>
<td><strong>주소 / 오프셋</strong> (<code>st_value</code>)</td>
<td>해당 데이터가 메모리상 어디에 위치하는지</td>
</tr>
<tr>
<td><strong>크기</strong> (<code>st_size</code>)</td>
<td>몇 바이트짜리 데이터인지</td>
</tr>
<tr>
<td><strong>타입 정보</strong> (<code>st_info</code>)</td>
<td>함수, 변수, 섹션 등 어떤 종류의 심볼인지</td>
</tr>
</tbody></table>
<p>즉, <code>.data</code> 섹션의 <strong>“의미 없는 바이트 덩어리”</strong>들은
심볼 테이블을 통해 “이 바이트들이 변수 <code>x</code>의 값이다”라는 의미를 얻게 되는 거예요.</p>
<hr>
<h3 id="요약">요약</h3>
<blockquote>
<p>ELF의 <code>.data</code> 섹션은
단순히 초기화된 전역/정적 변수의 값들의 <strong>바이트 배열</strong> 을 담는 공간이며,
데이터의 <strong>이름, 타입, 크기 등의 의미는 심볼 테이블을 통해 해석된다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[ELF 파일의 3가지 핵심 구성 요소]]></title>
            <link>https://velog.io/@edward-official/elf-structure</link>
            <guid>https://velog.io/@edward-official/elf-structure</guid>
            <pubDate>Thu, 16 Oct 2025 08:53:50 GMT</pubDate>
            <description><![CDATA[<p>ELF(Executable and Linkable Format) 파일은 리눅스 계열 운영체제에서 표준으로 사용하는 오브젝트 파일 포멧으로, 링킹 및 로딩에 필요한 모든 정보를 논리적으로 구조화하여 담고 있으며, 크게 세 가지 요소로 이루어져 있습니다.</p>
<h3 id="1-elf-헤더-elf-header">1. ELF 헤더 (ELF Header)</h3>
<ul>
<li><strong>역할:</strong> 파일의 <strong>맨 앞에 위치</strong>하며, ELF 파일 전체를 이해하기 위한 <strong>가장 기본적인 정보</strong>를 제공합니다. 파일의 &#39;신분증&#39;과 같습니다.</li>
<li><strong>포함 정보:</strong> 파일의 종류(재배치 가능, 실행 가능 등), 대상 CPU 아키텍처, 엔디안(바이트 순서), <strong>섹션 헤더 테이블과 프로그램 헤더 테이블이 파일 내에서 시작하는 위치(오프셋)</strong> 및 크기 등.</li>
</ul>
<h3 id="2-섹션-데이터-section-data">2. 섹션 데이터 (Section Data)</h3>
<ul>
<li><strong>역할:</strong> 프로그램의 <strong>실제 내용물</strong>이 들어 있는 부분입니다.</li>
<li><strong>구성:</strong> <code>.text</code> (코드), <code>.data</code> (초기화된 데이터), <code>.rodata</code> (읽기 전용 데이터) 등 앞서 설명된 모든 섹션의 <strong>실제 바이트 데이터</strong>가 저장됩니다.</li>
</ul>
<h3 id="3-메타데이터-테이블-metadata-tables">3. 메타데이터 테이블 (Metadata Tables)</h3>
<p>ELF 파일은 데이터를 해석하는 데 필요한 두 가지 중요한 테이블을 포함합니다.</p>
<ul>
<li><strong>섹션 헤더 테이블 (Section Header Table):</strong> 섹션 데이터 영역에 있는 <strong>각 섹션의 상세한 정보</strong> (이름, 타입, 크기, 파일 오프셋, 메모리 주소 등)를 담고 있는 목록입니다. 링커가 파일을 처리할 때 주로 사용합니다.</li>
<li><strong>프로그램 헤더 테이블 (Program Header Table):</strong> (주로 실행 파일에서 중요) 파일이 메모리에 로드될 때 <strong>어떻게 로드되어야 하는지</strong>를 설명합니다. 파일의 연속된 섹션들을 묶어 <strong>세그먼트(Segment)</strong>라는 단위로 메모리 매핑 정보를 제공합니다. 로더(Loader)가 파일을 메모리에 올릴 때 주로 사용합니다.</li>
</ul>
<p>따라서 ELF 파일은 <strong>헤더</strong>를 통해 파일의 시작점을 알리고, <strong>테이블</strong>을 통해 파일의 구조와 로딩 방법을 정의하며, <strong>데이터</strong>에 실제 프로그램을 담는 구조를 가집니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[C 프로그램 빌드 과정과 각 단계의 역할]]></title>
            <link>https://velog.io/@edward-official/build-procedure</link>
            <guid>https://velog.io/@edward-official/build-procedure</guid>
            <pubDate>Thu, 16 Oct 2025 06:30:35 GMT</pubDate>
            <description><![CDATA[<h3 id="1-프리프로세싱-단계-preprocessor-include-처리">1. 프리프로세싱 단계 (Preprocessor: <code>#include</code> 처리)</h3>
<ul>
<li><strong>역할:</strong> 소스 코드를 컴파일러가 처리하기 쉬운 형태로 <strong>텍스트 치환 및 확장</strong>합니다.</li>
<li><strong>결과:</strong> <code>#include &lt;stdio.h&gt;</code>와 같은 지시문을 만나면, <code>stdio.h</code>와 같은 <strong>헤더 파일의 내용</strong>이 메인 소스 파일에 복사되어 들어옵니다.</li>
<li><strong>포함되는 내용:</strong> 이 헤더 파일에는 주로 <code>printf</code>와 같은 함수의 <strong>선언 (Declaration)</strong>, 즉 함수의 <strong>이름, 반환 타입, 매개변수 정보</strong>만 포함되어 있습니다. <strong>함수의 실제 구현 코드(바디)</strong>는 포함되어 있지 않습니다.</li>
</ul>
<h3 id="2-컴파일-단계-compiler-목적-파일-생성">2. 컴파일 단계 (Compiler: 목적 파일 생성)</h3>
<ul>
<li><strong>역할:</strong> 확장된 소스 코드를 읽어 <strong>기계어 코드</strong>로 번역하고 <strong>목적 파일(.o)</strong>을 생성합니다.</li>
<li><strong>처리:</strong> 컴파일러는 <code>printf()</code> 호출을 만나면, 선언을 통해 그 형태가 유효함을 확인하고, 이 함수가 <strong>&quot;외부 어딘가에 정의되어 있다&quot;</strong>는 <strong>심볼(Symbol) 참조 정보</strong>만 목적 파일에 남깁니다.</li>
</ul>
<h3 id="3-링킹-단계-linker-최종-결합">3. 링킹 단계 (Linker: 최종 결합)</h3>
<ul>
<li><strong>역할:</strong> 여러 목적 파일과 라이브러리를 하나로 <strong>결합</strong>하여 <strong>실행 파일</strong>을 만듭니다.</li>
<li><strong>처리:</strong><ul>
<li>링커는 목적 파일들을 검사하여 <code>printf</code>와 같이 <strong>정의가 없는 심볼(Undefined Symbols)</strong> 목록을 확인합니다.</li>
<li>이 심볼들을 해결하기 위해 <strong>표준 라이브러리 파일</strong>(<code>libc.a</code> 또는 <code>libc.so</code>)을 탐색합니다.</li>
<li>라이브러리 파일 내에 <strong>미리 컴파일된 <code>printf</code> 함수의 구체적인 구현체 (이진 코드)</strong>가 정의되어 있습니다.</li>
<li>링커는 이 <strong>구현체의 주소</strong>를 사용자 코드의 <code>printf</code> 호출 위치와 <strong>연결(Link)</strong>해줍니다.</li>
</ul>
</li>
</ul>
<p><strong>결론적으로:</strong></p>
<ul>
<li><strong>프리프로세서</strong>는 <strong>선언</strong>을 붙여주고,</li>
<li><strong>링커</strong>는 <strong>구현체</strong>를 연결해 주는 역할을 수행하는 것입니다.</li>
</ul>
<p>따라서 링커 단계가 되어서야 프로그램이 필요로 하는 모든 코드(사용자 코드 + 시스템/라이브러리 구현체)가 하나로 합쳐져 실행 가능한 파일이 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성: 스레드(thread)]]></title>
            <link>https://velog.io/@edward-official/thread</link>
            <guid>https://velog.io/@edward-official/thread</guid>
            <pubDate>Tue, 14 Oct 2025 08:31:50 GMT</pubDate>
            <description><![CDATA[<p>우리는 일반적으로 하나의 <strong>프로세스(process)</strong> 가 하나의 <strong>제어 흐름(control flow)</strong> 을 가진다고 생각하지만,
현대 시스템에서는 하나의 프로세스가 실제로는 여러 개의 <strong>실행 단위(execution unit)</strong> 로 구성될 수 있다.
이 실행 단위들을 <strong>스레드(thread)</strong> 라고 한다.</p>
<p>각 스레드는 동일한 프로세스의 <strong>문맥(context)</strong> 내에서 실행되며,
<strong>같은 코드와 전역 데이터(global data)</strong> 를 공유한다.</p>
<hr>
<p>스레드는 오늘날 점점 더 중요한 프로그래밍 모델이 되고 있다.
특히 <strong>네트워크 서버</strong> 같은 환경에서 <strong>동시성(concurrency)</strong> 이 필요하기 때문이다.</p>
<p>그 이유는 다음과 같다:</p>
<ul>
<li><p>여러 <strong>프로세스 간</strong>에 데이터를 공유하는 것보다,
여러 <strong>스레드 간</strong>에 데이터를 공유하는 것이 훨씬 쉽다.</p>
</li>
<li><p>스레드는 일반적으로 <strong>프로세스보다 효율적</strong>이다.
(프로세스 간 전환보다 스레드 간 전환이 비용이 훨씬 적음)</p>
</li>
<li><p>또한, <strong>멀티코어 프로세서</strong> 환경에서는 여러 스레드를 동시에 실행함으로써
프로그램의 실행 속도를 크게 향상시킬 수 있다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성: 프로세스, 컨텍스트 스위칭]]></title>
            <link>https://velog.io/@edward-official/context-switching</link>
            <guid>https://velog.io/@edward-official/context-switching</guid>
            <pubDate>Tue, 14 Oct 2025 08:24:31 GMT</pubDate>
            <description><![CDATA[<h3 id="프로세스의-정의">프로세스의 정의</h3>
<p><strong>프로세스(process)</strong> 는 “실행 중인 프로그램”을 표현하는
운영체제의 <strong>추상화(abstraction)</strong> 이다.</p>
<p>하나의 시스템에서는 여러 개의 프로세스가 동시에(concurrently) 실행될 수 있다.
각 프로세스는 마치 자신이 <strong>하드웨어를 독점적으로 사용하는 것처럼</strong> 보인다.</p>
<p>여기서 <strong>concurrently</strong> 라는 말은,
한 프로세스의 명령어들이 다른 프로세스의 명령어들과 <strong>섞여(interleaved)</strong> 실행된다는 뜻이다.</p>
<p>대부분의 시스템에서는 실행할 프로세스의 수가
실제 CPU의 개수보다 많기 때문에,
운영체제는 이들 사이에서 CPU를 번갈아 가며 사용하게 한다.</p>
<hr>
<h3 id="단일-프로세서에서의-동작">단일 프로세서에서의 동작</h3>
<p>전통적인 시스템은 한 번에 하나의 프로그램만 실행할 수 있었지만,
현대의 <strong>멀티코어 프로세서(multicore processor)</strong> 는
여러 프로그램을 동시에 실행할 수 있다.</p>
<p>하지만 단일 CPU에서도 여러 프로세스가 동시에 실행되는 것처럼 보일 수 있다.
운영체제가 <strong>프로세스 간 전환(context switching)</strong> 을 수행하기 때문이다.</p>
<hr>
<h3 id="컨텍스트-스위칭-context-switching">컨텍스트 스위칭 (Context Switching)</h3>
<p>운영체제는 프로세서가 어떤 프로세스에서 다른 프로세스로 전환할 때
그 상태(state)를 저장하고 복원해야 한다.
이 상태를 <strong>컨텍스트(context)</strong> 라고 부른다.</p>
<p>컨텍스트에는 다음과 같은 정보가 포함된다:</p>
<ul>
<li><strong>PC (Program Counter)</strong> 의 현재 값</li>
<li><strong>레지스터 파일(Register file)</strong> 의 내용</li>
<li><strong>주기억장치(Main memory)</strong> 의 상태</li>
</ul>
<hr>
<p>단일 CPU 시스템(단일 프로세서 시스템, <em>uniprocessor system</em>)에서는
한 시점에 오직 하나의 프로세스만 실제로 실행된다.</p>
<p>운영체제가 현재 실행 중인 프로세스를 다른 프로세스로 전환하기로 결정하면,
다음 순서로 <strong>컨텍스트 스위치(context switch)</strong> 를 수행한다:</p>
<ol>
<li>현재 프로세스의 컨텍스트를 저장한다.</li>
<li>새 프로세스의 컨텍스트를 복원한다.</li>
<li>CPU의 제어권을 새 프로세스에 넘긴다.</li>
</ol>
<p>이 과정을 통해 새 프로세스는 <strong>중단되었던 지점부터 정확히 다시 실행을 이어간다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[시간 지역성을 높이는 전략: BLOCKING]]></title>
            <link>https://velog.io/@edward-official/blocking</link>
            <guid>https://velog.io/@edward-official/blocking</guid>
            <pubDate>Tue, 14 Oct 2025 07:27:36 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경-캐시는-작고-데이터는-크다">1. 배경: 캐시는 작고, 데이터는 크다</h2>
<ul>
<li>큰 배열이나 행렬 연산(예: 행렬 곱셈)을 수행할 때, 데이터가 캐시에 다 안 들어가면
<strong>같은 데이터</strong>를 계속 <strong>메모리에서 다시 불러와야</strong> 하죠. → 느림</li>
<li>하지만 <strong>캐시에 들어와 있는 동안</strong> 여러 번 다시 쓰면(=시간적 지역성), 성능이 크게 올라갑니다.</li>
</ul>
<hr>
<h2 id="2-blocking의-기본-아이디어">2. BLOCKING의 기본 아이디어</h2>
<blockquote>
<p>&quot;큰 데이터를 한 번에 다루지 말고, <strong>작은 덩어리(block)</strong>로 쪼개서 처리하자.&quot;</p>
</blockquote>
<h3 id="예시-행렬-곱셈">예시 (행렬 곱셈)</h3>
<pre><code class="language-c">for (i = 0; i &lt; N; i++)
  for (j = 0; j &lt; N; j++)
    for (k = 0; k &lt; N; k++)
      C[i][j] += A[i][k] * B[k][j];</code></pre>
<p>이 코드는 단순하지만, 실제로는 캐시가 자주 미스 납니다.
왜냐하면 <code>A</code>, <code>B</code>, <code>C</code>가 너무 커서 한 번 읽은 데이터가 캐시에서 금방 쫓겨나기 때문이죠.</p>
<p>그래서 <strong>Blocking</strong>을 씁니다:</p>
<pre><code class="language-c">for (ii = 0; ii &lt; N; ii += B)
  for (jj = 0; jj &lt; N; jj += B)
    for (kk = 0; kk &lt; N; kk += B)
      // 작은 BxB 블록끼리만 곱함
      for (i = ii; i &lt; ii + B; i++)
        for (j = jj; j &lt; jj + B; j++)
          for (k = kk; k &lt; kk + B; k++)
            C[i][j] += A[i][k] * B[k][j];</code></pre>
<p>이제 프로그램은:</p>
<ol>
<li><code>A</code>, <code>B</code>, <code>C</code>의 <strong>작은 블록 하나씩</strong>을 캐시에 불러옴</li>
<li>그 블록 안에서 필요한 모든 연산을 끝냄</li>
<li>다 끝나면 버리고 다음 블록으로 넘어감</li>
</ol>
<p>이렇게 하면 같은 데이터(예: <code>A[i][k]</code>)를 <strong>캐시에 있을 때 여러 번</strong> 사용합니다 → <strong>시간적 지역성 향상</strong></p>
<hr>
<h2 id="3-하지만-core-i7에서는-효과가-적은-이유">3. 하지만 Core i7에서는 효과가 적은 이유</h2>
<p>본문의 마지막 문장:</p>
<blockquote>
<p>“Blocking does not improve the performance of matrix multiply on the Core i7, because of its sophisticated prefetching hardware.”</p>
</blockquote>
<ul>
<li>최신 CPU는 <strong>하드웨어 프리패처(prefetcher)</strong>가 자동으로 데이터를 미리 읽습니다.</li>
<li>그래서 <strong>Blocking이 수동으로 해주는 캐시 최적화 효과가 이미 구현되어 있음</strong> → 효과가 덜함.</li>
</ul>
<p>즉, Blocking은 <strong>이론적으로 강력</strong>하지만,
<strong>하드웨어가 단순한 시스템(임베디드, 구형 CPU 등)</strong>에서 더 유용합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[직접 매핑 캐시(direct-mapped cache)의 동작]]></title>
            <link>https://velog.io/@edward-official/direct-mapped-cache</link>
            <guid>https://velog.io/@edward-official/direct-mapped-cache</guid>
            <pubDate>Mon, 13 Oct 2025 05:25:04 GMT</pubDate>
            <description><![CDATA[<h2 id="직접-매핑-캐시의-실제-동작">직접 매핑 캐시의 실제 동작</h2>
<p>캐시가 세트를 선택하고 라인을 식별하는 메커니즘은 매우 단순합니다.
그럴 수밖에 없습니다 — 하드웨어는 이 과정을 <strong>수 나노초 단위로</strong> 수행해야 하기 때문입니다.
하지만 이런 비트 조작은 인간에게 혼란스러울 수 있으므로, 구체적인 예를 통해 과정을 살펴보겠습니다.</p>
<p>다음과 같은 직접 매핑 캐시가 있다고 가정합시다:</p>
<pre><code>(S, E, B, m) = (4, 1, 2, 4)</code></pre><p>즉,</p>
<ul>
<li>캐시는 <strong>4개의 세트(S=4)</strong> 를 가지고 있고,</li>
<li>세트당 <strong>1개의 라인(E=1)</strong>,</li>
<li><strong>블록 크기(B)=2바이트</strong>,</li>
<li><strong>주소 크기(m)=4비트</strong>입니다.
또한, 각 워드는 <strong>1바이트</strong>라고 가정합니다 (비현실적이지만 예시 단순화를 위한 가정입니다).</li>
</ul>
<hr>
<h3 id="주소-공간-나누기">주소 공간 나누기</h3>
<p>4비트 주소 공간 전체를 나누어보면, 아래 그림(Figure 6.30)처럼
각 주소는 [Tag | Index | Offset] 비트로 구성됩니다.</p>
<h4 id="흥미로운-점">흥미로운 점:</h4>
<ul>
<li><p><strong>Tag와 Index의 조합</strong>은 메모리의 각 블록을 유일하게 식별합니다.
예:</p>
<ul>
<li>블록 0은 주소 0과 1로 구성</li>
<li>블록 1은 주소 2와 3</li>
<li>블록 2는 주소 4와 5</li>
<li>블록 3은 주소 6과 7</li>
<li>블록 4는 주소 8과 9, … 이런 식으로 구성됩니다.</li>
</ul>
</li>
<li><p><strong>메모리 블록은 8개지만</strong>, <strong>캐시 세트는 4개뿐</strong>이므로
여러 블록이 같은 세트에 매핑됩니다.
예:</p>
<ul>
<li>블록 0과 4 → 세트 0</li>
<li>블록 1과 5 → 세트 1</li>
<li>블록 2와 6 → 세트 2</li>
<li>블록 3과 7 → 세트 3</li>
</ul>
</li>
<li><p>같은 세트에 매핑된 블록들은 <strong>Tag 비트로 구분</strong>됩니다.
예:</p>
<ul>
<li>블록 0의 태그 = 0, 블록 4의 태그 = 1</li>
<li>블록 1의 태그 = 0, 블록 5의 태그 = 1 등.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="figure-630--4비트-주소-공간-예시">Figure 6.30 — 4비트 주소 공간 예시</h3>
<table>
<thead>
<tr>
<th>주소(10진수)</th>
<th>Tag (t=1)</th>
<th>Index (s=2)</th>
<th>Offset (b=1)</th>
<th>블록 번호(10진수)</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>0</td>
<td>00</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>00</td>
<td>1</td>
<td>0</td>
</tr>
<tr>
<td>2</td>
<td>0</td>
<td>01</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>3</td>
<td>0</td>
<td>01</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>4</td>
<td>0</td>
<td>10</td>
<td>0</td>
<td>2</td>
</tr>
<tr>
<td>5</td>
<td>0</td>
<td>10</td>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>6</td>
<td>0</td>
<td>11</td>
<td>0</td>
<td>3</td>
</tr>
<tr>
<td>7</td>
<td>0</td>
<td>11</td>
<td>1</td>
<td>3</td>
</tr>
<tr>
<td>8</td>
<td>1</td>
<td>00</td>
<td>0</td>
<td>4</td>
</tr>
<tr>
<td>9</td>
<td>1</td>
<td>00</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>10</td>
<td>1</td>
<td>01</td>
<td>0</td>
<td>5</td>
</tr>
<tr>
<td>11</td>
<td>1</td>
<td>01</td>
<td>1</td>
<td>5</td>
</tr>
<tr>
<td>12</td>
<td>1</td>
<td>10</td>
<td>0</td>
<td>6</td>
</tr>
<tr>
<td>13</td>
<td>1</td>
<td>10</td>
<td>1</td>
<td>6</td>
</tr>
<tr>
<td>14</td>
<td>1</td>
<td>11</td>
<td>0</td>
<td>7</td>
</tr>
<tr>
<td>15</td>
<td>1</td>
<td>11</td>
<td>1</td>
<td>7</td>
</tr>
</tbody></table>
<hr>
<h2 id="캐시-시뮬레이션">캐시 시뮬레이션</h2>
<p>초기 상태: 캐시는 비어 있음 (모든 valid 비트 = 0)</p>
<table>
<thead>
<tr>
<th>Set</th>
<th>Valid</th>
<th>Tag</th>
<th>block[0]</th>
<th>block[1]</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>3</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="1️⃣-read-word-at-address-0">1️⃣ Read word at address 0</h3>
<ul>
<li><strong>세트 0의 valid bit = 0 → cache miss</strong></li>
<li>메모리에서 <strong>block 0</strong>을 읽어와 세트 0에 저장.</li>
<li><code>m[0]</code>, <code>m[1]</code>이 캐시로 복사됨.</li>
</ul>
<table>
<thead>
<tr>
<th>Set</th>
<th>Valid</th>
<th>Tag</th>
<th>block[0]</th>
<th>block[1]</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>1</td>
<td>0</td>
<td>m[0]</td>
<td>m[1]</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>3</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="2️⃣-read-word-at-address-1">2️⃣ Read word at address 1</h3>
<ul>
<li>같은 블록 내 데이터 (<code>m[1]</code>) → <strong>cache hit</strong></li>
<li>세트 0의 상태는 변하지 않음.</li>
</ul>
<hr>
<h3 id="3️⃣-read-word-at-address-13">3️⃣ Read word at address 13</h3>
<ul>
<li>주소 13 → <strong>세트 2</strong>
valid bit = 0 → <strong>cache miss</strong></li>
<li>메모리에서 block 6을 가져와 세트 2에 저장.</li>
</ul>
<table>
<thead>
<tr>
<th>Set</th>
<th>Valid</th>
<th>Tag</th>
<th>block[0]</th>
<th>block[1]</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>1</td>
<td>0</td>
<td>m[0]</td>
<td>m[1]</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>1</td>
<td>m[12]</td>
<td>m[13]</td>
</tr>
<tr>
<td>3</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="4️⃣-read-word-at-address-8">4️⃣ Read word at address 8</h3>
<ul>
<li>주소 8 → <strong>세트 0</strong>, valid=1 → 확인 필요</li>
<li>하지만 <strong>Tag(1)</strong> ≠ 기존 Tag(0) → <strong>cache miss</strong></li>
<li>블록 4를 세트 0에 로드 (기존 block 0 교체)</li>
</ul>
<table>
<thead>
<tr>
<th>Set</th>
<th>Valid</th>
<th>Tag</th>
<th>block[0]</th>
<th>block[1]</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>1</td>
<td>1</td>
<td>m[8]</td>
<td>m[9]</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>1</td>
<td>m[12]</td>
<td>m[13]</td>
</tr>
<tr>
<td>3</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<hr>
<h3 id="5️⃣-read-word-at-address-0">5️⃣ Read word at address 0</h3>
<ul>
<li>다시 주소 0을 읽음 → 세트 0,
하지만 이제 태그가 다름 (현재 Tag=1, 필요 Tag=0)</li>
<li><strong>Cache miss 발생</strong></li>
<li>block 0이 다시 세트 0에 로드됨 → block 4가 교체됨</li>
</ul>
<table>
<thead>
<tr>
<th>Set</th>
<th>Valid</th>
<th>Tag</th>
<th>block[0]</th>
<th>block[1]</th>
</tr>
</thead>
<tbody><tr>
<td>0</td>
<td>1</td>
<td>0</td>
<td>m[0]</td>
<td>m[1]</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>1</td>
<td>m[12]</td>
<td>m[13]</td>
</tr>
<tr>
<td>3</td>
<td>0</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p>이와 같은 미스는 <strong>충돌 미스(conflict miss)</strong> 라고 부릅니다.
캐시 공간은 충분하지만, 서로 다른 블록들이 <strong>같은 세트</strong>에 매핑되어
계속 서로를 덮어쓰는 상황에서 발생합니다.</p>
]]></description>
        </item>
    </channel>
</rss>