<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ostep on Hammer's Blog</title><link>https://lkic1625.github.io/blog/ostep/</link><description>Recent content in Ostep on Hammer's Blog</description><generator>Hugo</generator><language>ko-kr</language><copyright>© 조원빈 - All rights reserved</copyright><lastBuildDate>Mon, 02 Nov 2026 15:30:09 +0900</lastBuildDate><atom:link href="https://lkic1625.github.io/blog/ostep/index.xml" rel="self" type="application/rss+xml"/><item><title>SRAM의 구조와 용량 확장의 한계</title><link>https://lkic1625.github.io/os-sram/</link><pubDate>Mon, 02 Nov 2026 15:30:09 +0900</pubDate><guid>https://lkic1625.github.io/os-sram/</guid><description>&lt;h2 id="개요">개요&lt;/h2>
&lt;p>여태 DRAM Access는 지속적으로 CPU cycle에서 보자면 높은 latency로 인해 OS에서는 지양해야할 패턴으로 설명했었다. 반대로 SRAM은 L1, L2, L3 정도의 계층형으로 데이터 용량을 어느정도 커버할 수 있었지만, Cache Hit ratio 등 다양한 고민거리와 OS 단에서의 문제를 더욱 만드는 구조였다.&lt;/p>
&lt;p>그러면 반대로 &lt;strong>SRAM의 용량을 더 늘릴 수는 없을까?&lt;/strong> 실제 책에서도 이러한 SRAM 구조는 확장 가능하지 않은 전자공학적 문제가 있다고 설명한다. 간만에 컴구도 훑을 겸 SRAM이 왜 확장 불가능한지 이야기해보자.&lt;/p>
&lt;h2 id="sram의-구조">SRAM의 구조&lt;/h2>
&lt;p>우선 SRAM의 구조를 알아야, 실제 SRAM이 확장이 왜 어렵고 어떠한 trade-off가 있을지 알 수 있다.&lt;/p></description></item><item><title>Swap과 메모리 관리: Linux, TMO, ZRAM</title><link>https://lkic1625.github.io/os-swap/</link><pubDate>Mon, 02 Nov 2026 15:30:04 +0900</pubDate><guid>https://lkic1625.github.io/os-swap/</guid><description>&lt;h2 id="개요">개요&lt;/h2>
&lt;p>swap, 메모리 오프로딩은 물리적으로 부족한 용량 한계를 하위 단계의 메모리로 이동 시키는 기술이다. 이를 이용하여 우리는 가상화의 큰 장점을 확보하였다.
하지만, 이로 인해 생기는 여러 장단점은 명확한 trade-off이다. 우리는 우선 현대 시스템에서 이러한 Swap이 어떻게 구성되어 있는지 보고, 실제 사례를 보며 기존 시스템의 일반적인 솔루션이 가진 한계를 어떻게 해결하려고 하였는지 찾아보자.&lt;/p>
&lt;h2 id="linux">Linux&lt;/h2>
&lt;p>리눅스는 가장 기본적인 방식은 PTE가 어떻게 바뀌는지를 기본으로 설명할 수 있다. x86-64 Linux에서 특정 페이지가 Swap-out 된다면, 해당 페이지를 가리키던 PTE가 정상적인 물리적 메모리를 매핑하는 게 아닌, Swap Entry를 나타내는 값으로 변경된다.
CPU는 메모리 매핑 중에, 물리 페이지 접근이 불가능함을 판단하고, Page Fault를 통해 OS에게 이를 맡긴다. 이를 통해 Swap Entry라면, 데이터 위치에 대한 정보를 가지고 있기에 이 값을 통해 정장적인 물리 주소 매핑을 구할 수 있다. 보통의 구조는 아래와 같다.
참고로, Swap-out에 대한 victim을 구하는 Page Replacement 알고리즘은 Multi-Generational LRU(MGLRU)라는 휴리스틱을 추가한 LRU을 이용한다. 자세한 건 페이지 교체 알고리즘 챕터에서 더 설명하겠다.&lt;/p></description></item><item><title>메모리 할당기: glibc, jemalloc, TCMalloc, Java, Go</title><link>https://lkic1625.github.io/os-memory-allocation/</link><pubDate>Wed, 21 Oct 2026 13:01:48 +0900</pubDate><guid>https://lkic1625.github.io/os-memory-allocation/</guid><description>&lt;h2 id="allocation-관련-정리">Allocation 관련 정리&lt;/h2>
&lt;p>Allocator는 기본적으로, 4가지 정도 존재한다.&lt;/p>
&lt;ol>
&lt;li>얼마나 빨리 할당할 수 있는가,
&lt;ol>
&lt;li>매번 커널 진입이 아닌, 미리 확보한 메모리를 Free List 를 통해 활요하는 Fast Path를 이용&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>얼마나 적은 메모리를 낭비하는가
&lt;ol>
&lt;li>단편화 문제를 어떻게 해결하는가, 실제 요청보다 더 큰 Block을 할당하거나, 여유 공간은 충분하지만 연속 공간을 효율적으로 반환하기 어려운 경우&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>동시성 확보를 위한 방식
&lt;ol>
&lt;li>여러 스레드가 요청하는 경우의 contention 관리&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>언제 메모리를 반환할 것인가.
&lt;ol>
&lt;li>실제 메모리가 다시 쓰일 확률이 높다면 이를 가지고 재사용하는 것이 합당하다
이 네 가지를 통해 실제 설계 철학들을 엿볼 수 있다. 또한, 현대의 메모리 할당기가 서로 배타적인 구현을 하는 것이 아니고, 각 방법에서 핵심 설계 관점을 다르게 보는 것이다.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Allocator&lt;/th>
 &lt;th>핵심 설계 관점&lt;/th>
 &lt;th>대표적인 기법&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>glibc malloc (ptmalloc 계열)&lt;/td>
 &lt;td>범용성, 호환성&lt;/td>
 &lt;td>Arena, Thread Cache&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>jemalloc&lt;/td>
 &lt;td>단편화 관리, 메모리 사용량 제어&lt;/td>
 &lt;td>Multiple Arenas, Decay, Purging&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>TCMalloc&lt;/td>
 &lt;td>높은 병렬성과 빠른 할당&lt;/td>
 &lt;td>Per-CPU Cache, rseq, Hugepage-aware Allocation&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>mimalloc&lt;/td>
 &lt;td>낮은 경합, Locality, 메모리 효율&lt;/td>
 &lt;td>Free-list Sharding, Eager Purging&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>snmalloc&lt;/td>
 &lt;td>Cross-thread Free의 확장성&lt;/td>
 &lt;td>Message Passing&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Scudo&lt;/td>
 &lt;td>Heap 메모리 안전성&lt;/td>
 &lt;td>Metadata 검증, Quarantine, Randomization&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="tcache-glibc">tcache, glibc&lt;/h3>
&lt;pre tabindex="0">&lt;code>Thread 1 ── tcache ──┐
Thread 2 ── tcache ──┼── Arenas ── OS
Thread 3 ── tcache ──┘
&lt;/code>&lt;/pre>&lt;p>객체를 할당하고 해제하는데 필요한 정보를 스레드 별로 두어, lock 집중을 막는 방식&lt;/p></description></item><item><title>주소 변환과 Huge Page</title><link>https://lkic1625.github.io/os-address-translation/</link><pubDate>Thu, 15 Oct 2026 01:43:00 +0900</pubDate><guid>https://lkic1625.github.io/os-address-translation/</guid><description>&lt;p>메모리의 사용 방식과 메모리의 실제 Address Translation 에 대해서 설명한다. 이에 대해서는 크게 정리하지 않겠다. MMU라는 하드웨어 구조 장치에서 일어나는 과정이며 이를 이용하는 소프트웨어 아키텍처 등을 알아보자.&lt;/p>
&lt;h2 id="기본적인-주소-변환-과정">기본적인 주소 변환 과정&lt;/h2>
&lt;p>기본적인 translation 과정은 하드웨어 아키텍처와 OS가 함께 협업하며 작업한다. 현대 CPU 아키텍처에서는 MMU의 구성요소로는 TLB(Translation Lookaside Buffer)가 존재한다. TLB는 L1, L2에 존재하며 계층별 캐싱 구조로 이루어져있다. 만약 두 단계에서 모두 Cache miss가 난다면, Page Walker 계층에서 마지막 캐싱 여부를 판단하고, Page Table에서 데이터를 가져온다.&lt;/p></description></item><item><title>스케줄링: 네트워크, SQS, HTTP/2</title><link>https://lkic1625.github.io/os-scheduling/</link><pubDate>Thu, 08 Oct 2026 17:53:40 +0900</pubDate><guid>https://lkic1625.github.io/os-scheduling/</guid><description>&lt;ul>
&lt;li>기본적인 다단계 피드백 큐와, lottery 알고리즘을 설명한다.&lt;/li>
&lt;li>lottery 의 좋은 점은 기존의 스케쥴링된 상태를 추적하고 표현해야 하는 것들을 단순한 ticket 및 확률론에 기대서 구현하여 구현이 단순하다는 장점이 있다.&lt;/li>
&lt;/ul>
&lt;h2 id="스케쥴링이-쓰이는-다른-분야는">스케쥴링이 쓰이는 다른 분야는?&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://aws.amazon.com/ko/blogs/compute/building-resilient-multi-tenant-systems-with-amazon-sqs-fair-queues/">https://aws.amazon.com/ko/blogs/compute/building-resilient-multi-tenant-systems-with-amazon-sqs-fair-queues/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.cloudflare.com/better-http-2-prioritization-for-a-faster-web/">https://blog.cloudflare.com/better-http-2-prioritization-for-a-faster-web/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.cloudflare.com/nginx-structural-enhancements-for-http-2-performance/">https://blog.cloudflare.com/nginx-structural-enhancements-for-http-2-performance/&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://tech.channel.io/kr/articles/%EA%B8%89%EC%A6%9D%ED%95%98%EB%8A%94-%ED%8A%B8%EB%9E%98%ED%94%BD-%E1%84%8B%E1%85%A1%E1%86%AB%E1%84%8C%E1%85%A5%E1%86%BC%E1%84%8C%E1%85%A5%E1%86%A8%E1%84%8B%E1%85%B3%E1%84%85%E1%85%A9-%E1%84%8E%E1%85%A5%E1%84%85%E1%85%B5%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5-%EA%B5%AC%ED%98%84%ED%8E%B8-235661b0">https://tech.channel.io/kr/articles/급증하는-트래픽-안정적으로-처리하기-구현편-235661b0&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="network-scheduling">Network Scheduling&lt;/h2>
&lt;p>네트워크 스케쥴링은 기본적으로 전송되어야 할 패킷의 전송 순서와 전송 속도를 제한하거나 폐기하는 것을 포함할 수 있다. 일반적인 Linux 송신 경로를 단순화하면 &lt;code>Application -&amp;gt; TCP/UDP + IP -&amp;gt; qdisc -&amp;gt; NIC Driver -&amp;gt; NIC의 하드웨어 TX 큐 -&amp;gt; 링크&lt;/code>로 표현할 수 있다. 여기서 qdisc는 &lt;strong>하드웨어 큐와 별개의 소프트웨어 스케쥴링 계층&lt;/strong>이다. 이 경로는 송신 측의 개략도이며, 모든 네트워크 장치의 송수신 경로가 동일하다는 의미는 아니다.&lt;/p></description></item><item><title>LDE와 런타임 스케줄링: Go, Rust, Java</title><link>https://lkic1625.github.io/os-limited-direct-execution/</link><pubDate>Mon, 05 Oct 2026 17:22:05 +0900</pubDate><guid>https://lkic1625.github.io/os-limited-direct-execution/</guid><description>&lt;h2 id="개요">개요&lt;/h2>
&lt;p>OS에게 안전한 처리를 맡기고, 적절히 제공하기 위해 우리는 커널과 유저모드를 나누었다. 특히, 적절한 스케쥴링과 함께라면 우리는 OS로 하여금 좋은 퍼포먼스를 제공 받을 수 있을 것이다. 다만, 요즘의 프로그래밍 언어들은 여기에서 멈추지 않는다.
특히, 경량 스레드라는 모델링은 현대 시스템의 맞는 런타임 추상화를 한 번 더 진행했다. 복잡도를 늘리는 대신 OS 스레드의 스케쥴링과는 전혀 다른 영역에 런타임 스케쥴링을 하도록 하여 컨텍스트 스위칭 비용을 줄이고 효율성을 높인다. 이에 대한 간략한 추상화를 알아보자.&lt;/p></description></item><item><title>프로세스와 cgroup의 CPU 자원 관리</title><link>https://lkic1625.github.io/os-process/</link><pubDate>Wed, 30 Sep 2026 14:10:20 +0900</pubDate><guid>https://lkic1625.github.io/os-process/</guid><description>&lt;ul>
&lt;li>프로세스는 메모리에 올라가 실행되는 프로그램 흐름&lt;/li>
&lt;li>이전에 Process 라는 추상화된 개념은 각 프로그램끼리의 독립적인 흐름을 필요로 하기에 더욱이 격리될 필요가 있었음.&lt;/li>
&lt;li>다만, 근래에 와서는 프로세스는 단순히 공통 실행 흐름의 역할을 하지 실제 CPU의 적재되어 실행되는 단위라고 보기 어려움. 과거 UNIX에서 실행흐름이 아래와 같았기에 그렇게 배우는 것&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>1 Process = 1 Address Space + 1 Execution Flow
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="현대의-virtual-thread-coroutine-까지">현대의 Virtual thread, Coroutine 까지&lt;/h3>
&lt;ul>
&lt;li>현대에 와서는 더욱 복잡하다, 특히 프로세스라는 개념보다는 Kernel Thread 로 인식된다. 특히, Linux에서는 둘 다 task 라는 개념으로 컨텍스트를 저장하기도 한다.&lt;/li>
&lt;li>하지만 여전히 멀티 스레딩의 모델이어도 현대의 시스템에서는 충분하지 못하다, 실제로도 수만개의 스레드가 특정 프로세스의 생긴다고 해도 이것이 모두 cpu 자원을 사용하는 게 아니다.&lt;/li>
&lt;li>그렇기에 커널이 모르는 새로운 실행 흐름을 만들자는 게 vitual thread, coroutine, goroutine, async/await 등이 나오게 됨&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code>Process
 └─ Runtime
 ├─ N개의 Virtual Thread
 │ ↓ multiplexing
 └─ M개의 Kernel Thread
 ↓ scheduling
 CPU
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>virtual thread 는 cpu scheduling 과 독립적인 스케쥴링을 한다는 데에서 또한, 기존에 thread-per-request 모델을 확장하는 데에 있어서 중요하다. 특히 컨텍스트 스위칭의 문제라기보단 실제 커널 모드에 진입해 syscall 을 하는 과정에서 생기는 오버헤드가 크기에 이를 우회하기 위함도 있다. 물론, Carrier Thread 를 재사용한다는 측면에서 시작된 것 역시 맞다.&lt;/li>
&lt;/ul>
&lt;h2 id="cgroup-mechanisms">cgroup mechanisms&lt;/h2>
&lt;p>시분할 메커니즘은 우리에게 여러 개의 프로그램이 거의 동시에 동작하는 것처럼 보이도록하는 효과가 있다. 다만, 현대의 시스템은 이를 더더욱 활용하기 위해 확장하고 제안한다.
예를 들어보자, Docker 등의 Container Runtime 에서 Resource Limit 을 지정할 수 있고 이를 위해 vcpu, mem 를 설정해본 기억이 있을 거다. 이러한 제약은 어떻게 이루어질까? 대표적으로 알려진 이러한 자원 할당 메커니즘은 Linux 의 cgroup 이다.&lt;/p></description></item></channel></rss>