핵심 인사이트 (3줄 요약)
- 본질: 페이징 환경에서의 공유 페이지(Shared Pages)는 여러 프로세스가 공통으로 사용하는 코드나 라이브러리를 물리 메모리에 딱 **한 번만 적재(Load)**하고, 각 프로세스의 페이지 테이블이 동일한 물리 프레임을 가리키게 하여 메모리를 공유하는 기법이다.
- 가치: 메모리 소비량이 기하급수적으로 폭발하는 다중 사용자 환경(예: 40명의 사용자가 동시에 워드프로세서를 실행할 때)에서, 중복되는 코드 용량을 완벽하게 0으로 만들어 시스템 메모리를 수십~수백 배 절약해 준다.
- 융합: 이 기법이 성립하기 위해서는 공유되는 코드가 실행 중에 절대 내용이 변하지 않는 **재진입 가능 코드(Reentrant Code, Pure Code)**여야만 하며, 이는 동적 연결 라이브러리(DLL/SO)의 하드웨어적 구현 토대가 된다.
Ⅰ. 개요 및 필요성
-
개념: 페이징 시스템은 프로세스마다 독립된 페이지 테이블을 가진다. 보통은 자기만의 물리 프레임을 가리키지만, '공유 페이지' 기법에서는 여러 프로세스의 페이지 테이블 엔트리(Entry)가 동일한 물리 프레임 번호를 가리키게끔 의도적으로 매핑(Mapping)한다.
-
필요성: 만약 50MB짜리 텍스트 에디터를 10명이 동시에 띄웠다고 가정해 보자. 연속 메모리 할당 환경에서는 50MB 코드를 10명분으로 각각 램에 올려 총 500MB의 메모리가 증발했다. 똑같은 코드(기계어 명령어)를 메모리에 10번이나 중복해서 올리는 것은 막대한 낭비다. "코드 원본은 하나만 두고, 각자 읽어(Read) 가기만 하자!"라는 공유의 철학이 절실했다.
-
등장 배경 및 아키텍처 제약:
- 초기 공유 불가: 과거에는 코드와 데이터가 섞여 있어(Self-modifying code 등), 한 놈이 코드를 바꾸면 다른 놈이 엉뚱한 코드를 실행하게 되어 공유가 불가능했다.
- 재진입 코드(Reentrant Code)의 등장: 개발자들이 프로그램의 '명령어(Code)' 부분과 '변수/데이터(Data)' 부분을 엄격히 분리해서 컴파일하기 시작했다. 코드는 절대 변하지 않는 순수 기계어(Pure Code)로 작성되었다.
- 페이징의 활용: OS는 이 변하지 않는 코드 조각들만 핀셋으로 집어 하나의 프레임에 올린 뒤, 수천 개의 프로세스가 이 프레임을 가리키게 하는 마법을 부렸다.
┌──────────────────────────────────────────────────────────────────────────┐
│ 페이징 시스템에서의 공유 페이지(Shared Page) 매핑 구조 │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ [ 프로세스 P1 (유저 A의 크롬) ] [ 프로세스 P2 (유저 B의 크롬) ] │
│ P1의 페이지 테이블 P2의 페이지 테이블 │
│ ┌──────┬───────┐ ┌──────┬───────┐ │
│ │ Page │ Frame │ │ Page │ Frame │ │
│ ├──────┼───────┤ ├──────┼───────┤ │
│ │ 0(Ed)│ 3 │──┐ ┌────────│ 0(Ed)│ 3 │ (공유!) │
│ │ 1(Ed)│ 4 │──┼────┼──┐ │ 1(Ed)│ 4 │ (공유!) │
│ │ 2(Ed)│ 6 │──┼────┼──┼──┐ │ 2(Ed)│ 6 │ (공유!) │
│ │ 3(D1)│ 1 │─┐│ │ │ │ │ 3(D2)│ 8 │ (독립됨) │
│ └──────┴───────┘ ││ │ │ │ └──────┴───────┘ │
│ ││ │ │ │ │
│ ▼▼ ▼ ▼ ▼ │
│ [ 물리 메모리 (RAM) ] │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │ Fr 0│Fr 1 │ Fr 2│Fr 3 │Fr 4 │ Fr 5│Fr 6 │ Fr 7│Fr 8 │ │
│ │ (빈) │ Data│ (빈) │ Ed 1│ Ed 2│ (빈) │ Ed 3│ (빈) │ Data│ │
│ │ │ (P1)│ │(공유)│(공유)│ │(공유)│ │ (P2)│ │
│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │
│ * Ed: Editor 코드 (읽기 전용, 재진입 가능) │
│ * Data: 사용자 개별 데이터 (읽기/쓰기 가능, 각각 독립적 프레임 할당) │
└──────────────────────────────────────────────────────────────────────────┘
[다이어그램 해설] P1과 P2는 모두 크롬 브라우저다. 논리 주소의 0, 1, 2번 페이지는 크롬의 핵심 실행 코드(Ed)다. OS는 두 프로세스의 테이블에서 이 코드 영역이 모두 똑같은 물리 프레임 [3, 4, 6]을 가리키도록 연결한다. 하지만 3번 페이지(Data)는 유저가 검색창에 입력한 글자나 로컬 상태값이므로 절대 공유하면 안 된다. 그래서 P1의 데이터는 프레임 1에, P2의 데이터는 프레임 8에 완전히 격리해서 매핑해 둔다.
- 📢 섹션 요약 비유: 게임방에서 10명이 똑같은 게임을 할 때, 게임 설치 파일(공유 페이지)은 중앙 서버 1대(물리 메모리)에만 깔아두고 모니터로 화면만 공유받되, 각자의 게임 세이브 파일(개별 데이터)만 자기들 USB(독립 프레임)에 따로 저장하는 완벽한 분리형 시스템입니다.
Ⅱ. 아키텍처 및 핵심 원리
재진입 가능 코드 (Reentrant Code / Pure Code)의 필수성
공유 페이지가 안전하게 작동하려면, 공유되는 프레임 안의 기계어 코드들이 **"실행 도중에 절대 자신의 내용(Data)을 변경하지 않는다"**는 속성을 무조건 보장해야 한다. 이를 재진입 가능(Reentrant) 하다고 부른다.
- 만약 프로그램 A가 공유 코드를 실행하다가 내부 변수 값을
count = 10으로 바꿔버렸는데, 스케줄링이 넘어가서 프로그램 B가 그 코드를 실행하면 B는 0이 아니라 10부터 시작하게 되어 완전히 버그가 나버린다. - 따라서 공유 페이지 영역은 페이지 테이블의 **R/W 비트가 반드시
Read-Only(읽기 전용)로 강제 락(Lock)**이 걸려 있어야 한다. 누군가 여기에 Write를 시도하면 MMU가 하드웨어 트랩(SegFault)을 날려 죽여버린다.
논리 주소 위치의 강제 일치성 (The Same Logical Address Rule)
과거 시스템에서 코드를 공유할 때 가장 까다로웠던 물리적 제약 조건이다.
-
공유되는 페이지(예: Ed 1, Ed 2, Ed 3)는 모든 공유 프로세스들의 논리 주소 공간에서 **정확히 똑같은 위치(동일한 논리 페이지 번호)**에 존재해야만 했다.
-
크롬 A에서 공유 코드가 페이지
0, 1, 2에 있다면, 크롬 B에서도 반드시 페이지0, 1, 2에 있어야 한다. -
이유: 기계어 코드 안에
JUMP 5000 (페이지 1번으로 점프)이라는 상대 주소가 박혀있기 때문이다. 만약 B에서 공유 코드가 페이지10, 11, 12에 매핑되어 있다면 저 JUMP 명령은 엉뚱한 곳으로 날아가 버린다. (현대에는 주소 독립 코드 PIC 기술로 이 제약이 많이 느슨해졌다.) -
📢 섹션 요약 비유: 두 학교가 영어 시험지(공유 페이지)를 공유하기로 했다면, 반드시 두 학교 모두 1교시(동일 논리 주소)에 영어 시험을 쳐야 합니다. 한 학교는 1교시, 한 학교는 3교시에 치면 부정행위(주소 점프 오류)가 발생하기 때문입니다.
Ⅲ. 비교 및 연결
비교 1: 스레드(Thread) 공유 vs 페이징(Paging) 공유
메모리를 공유한다는 목적은 같지만, 범주와 아키텍처가 완전히 다르다.
| 비교 항목 | 멀티 스레드 (Thread Sharing) | 페이징 공유 페이지 (Process Paging Sharing) |
|---|---|---|
| 공유 대상 | 같은 프로세스 소속의 스레드들 간의 공유 | 서로 다른 독립된 프로세스들 간의 공유 |
| 공유 범위 | 코드(Code), 데이터(Data), 힙(Heap) 전부 공유 | 오직 읽기 전용 **코드(Code)**나 특정 DLL/SO만 제한적 공유 |
| 페이지 테이블 | 스레드 100개가 하나의 PTBR(장부)을 통째로 공유 | 프로세스 100개가 각자 자기 PTBR(장부)을 가짐 |
| 매핑 방식 | 그냥 장부 하나를 같이 쳐다보는 것 | 각자의 장부 특정 줄(Entry)이 우연히 같은 프레임을 가리킴 |
시스템적 파급 효과: DLL과 SO의 탄생
이 '공유 페이지' 하드웨어 아키텍처가 없었다면 윈도우의 .dll (Dynamic Link Library)이나 리눅스의 .so (Shared Object) 파일은 존재할 수 없었다.
- 모든 프로그램은
printf()같은 표준 C 라이브러리를 호출한다. - 100개의 앱이 실행될 때, 리눅스는
libc.so파일을 물리 프레임에 단 1번만 로드한다 (약 2MB). - 그리고 100개 앱의 페이지 테이블에
libc.so의 500개 페이지 프레임 번호를 스터브(Stub)를 통해 모두 똑같이 매핑해 준다. - 이로 인해 시스템 전체의 메모리 낭비가 테라바이트 수준으로 절약된다.
┌──────────┬────────────┬────────────┬──────────────────────────────────────────┐
│ 환경 │ 라이브러리 적재│ 물리 메모리 소비│ 페이지 테이블 세팅 │
├──────────┼────────────┼────────────┼──────────────────────────────────────────┤
│ 정적 링킹 │ 앱마다 통째로 복사│ 2MB × 100 = 200MB│ 각자 다른 프레임 매핑 │
│ 동적/페이징│ 램에 딱 1번 로드│ 2MB × 1 = 2MB │ 100명이 **같은 프레임** 가리킴│
└──────────┴────────────┴────────────┴──────────────────────────────────────────┘
[매트릭스 해설] 공유 페이지는 동적 링킹(Dynamic Linking)을 하드웨어 레벨에서 완성시켜주는 핵심 톱니바퀴다. 소프트웨어가 외부 라이브러리를 동적으로 연결하겠다고 포인터를 넘기면, 하드웨어(페이징 유닛)가 다른 앱이 쓰고 있는 프레임 주소를 똑같이 쏴주어 이 거대한 절약 시스템을 완성한다.
- 📢 섹션 요약 비유: 수백 명의 요리사가 각자 주방에 똑같은 공용 전자레인지(printf)를 돈 주고 들이는 것(정적 링킹)이 아니라, 복도에 있는 업소용 대형 전자레인지 하나(공유 라이브러리)를 모두가 자기 주방 문 열고 나가서 같이 쓰는(공유 페이지) 구조입니다.
Ⅳ. 실무 적용 및 기술사 판단
실무 시나리오: 안드로이드 Zygote(지고트) 프로세스의 흑마술
안드로이드 스마트폰이 램 용량이 적음에도 불구하고 수십 개의 앱을 부드럽게 띄울 수 있는 비밀이 바로 이 '공유 페이지'다.
- Zygote의 탄생: 안드로이드 커널이 부팅되면, 자바 가상 머신(Dalvik/ART)과 모든 안드로이드 앱이 공통으로 쓰는 수백 MB의 프레임워크 라이브러리(UI, 네트워크 등)를 메모리에 몽땅 올려서 **Zygote(수정란)**라는 마스터 프로세스를 하나 만든다.
- 앱 실행 (fork): 사용자가 카카오톡, 유튜브, 인스타 앱을 터치해서 켤 때마다, 안드로이드는 이 무거운 프레임워크를 디스크에서 새로 읽지 않는다. 그냥 Zygote 프로세스를
fork()해버린다. - Copy-on-Write와 페이지 공유:
fork()되는 순간, 카톡과 유튜브의 페이지 테이블은 몽땅 Zygote가 물고 있던 수백 MB의 물리 프레임(공유 페이지)을 그대로 똑같이 가리키게 된다.- 즉, 10개의 앱을 켜도 프레임워크 코드는 물리 램에 Zygote가 올린 딱 1벌(100MB)뿐이다.
- 앱이 각자 자기 데이터를 수정(Write)하는 페이지에 대해서만 Copy-on-Write가 발동해 물리 램을 1장씩 분리해 나간다.
- 결과: 안드로이드 앱 실행 속도가 빛처럼 빠르고, 메모리를 극단적으로 덜 먹게 된다. 이것이 스마트폰 OS 메모리 관리의 최고봉 기술이다.
- 📢 섹션 요약 비유: 김밥천국을 새로 차릴 때마다 레시피와 주방 기구를 몽땅 새로 세팅하는 게 아니라, 완벽하게 세팅된 본점(Zygote)을 도장 찍듯 그대로 복사(fork)해서 체인점(앱)을 내고 간판 이름(독립 데이터)만 살짝 바꿔 달아서 1초 만에 오픈하는 사기적인 프랜차이즈 비법입니다.
Ⅴ. 기대효과 및 결론
정량/정성 기대효과
| 구분 | 내용 |
|---|---|
| 물리 램(RAM) 절약 극대화 | 다중 사용자 환경(터미널 서버 등)과 멀티 탭 브라우저에서 코드 중복 적재를 없애 메모리 소비량을 N분의 1로 압축 |
| 초기 로딩(Booting) 속도 향상 | 누군가 한 번 로드해둔 라이브러리(공유 페이지)를 내가 쓸 때는 디스크에서 읽어올 필요 없이 테이블 포인터만 연결하면 즉시 실행 |
| IPC(프로세스 간 통신) 성능 | 코드가 아닌 데이터 페이지를 의도적으로 R/W 권한으로 공유하면, 가장 빠른 프로세스 간 통신(Shared Memory IPC) 채널로 진화 |
결론 및 미래 전망
페이징에서의 공유 페이지 (Shared Pages)는 "메모리는 프로세스마다 독점해야 한다"는 원칙을 우아하게 깨버리고, "변하지 않는 것은 묶어두고 변하는 것만 찢어놓는다"는 완벽한 추상화를 이뤄냈다. 이 기술은 동적 라이브러리(.so/.dll) 생태계를 만들었고, 메모리 맵 매핑(mmap)을 통한 초고속 파일 I/O를 탄생시켰으며, 도커(Docker) 컨테이너와 모바일 OS(Android Zygote)의 근간을 이루는 뼈대가 되었다. 앞으로 AI 시대에 동일한 거대 가중치(LLM Weights) 모델을 수많은 세션이 공유해야 하는 추론(Inference) 서버 아키텍처에서도, 이 낡지만 위대한 공유 페이지 기술의 중요성은 더욱 눈부시게 빛날 것이다.
- 📢 섹션 요약 비유: 수만 명의 사람들이 각자 달(Moon)을 갖겠다고 우주로 날아가는 대신, 지구라는 거대한 텐트(OS) 안에서 모두가 고개만 들어 하늘에 뜬 단 하나의 달(공유 페이지)을 각자의 눈(페이지 테이블)에 담아 감상하는 가장 평화롭고 경제적인 우주 관측법입니다.
📌 관련 개념 맵
| 개념 | 연결 포인트 |
|---|---|
| PTBR (Page-Table Base Register) / PTLR (Page-Table Length Register) | 현재 개념으로 들어오기 전에 함께 이해하면 경계가 선명해지는 기반 개념이다. |
| 페이징의 메모리 보호 | 현재 개념이 등장하게 만든 직접적인 선행 흐름이다. |
| TLB (Translation Look-aside Buffer) | 현재 개념이 구현·세분화될 때 바로 연결되는 후속 개념이다. |
| TLB 적중 (TLB Hit) / TLB 미스 (TLB Miss) | 확장 학습이나 심화 비교로 이어지는 다음 단계의 키워드다. |
📈 관련 키워드 및 발전 흐름도
[페이징의 메모리 보호]
│
▼
[페이징에서의 공유 페이지 (Shared Pages)]
│
├──▶ [TLB (Translation Look-aside Buffer)]
└──▶ [TLB 적중 (TLB Hit) / TLB 미스 (TLB Miss)]
이 흐름도는 선행 개념에서 현재 개념으로 넘어온 뒤, 구현 세분화와 후속 확장으로 이어지는 학습 순서를 압축해 보여준다.
👶 어린이를 위한 3줄 비유 설명
- 페이징에서의 공유 페이지 (Shared Pages)은 컴퓨터가 메모리를 방처럼 나눠 쓰고 주소를 찾는 방법이에요.
- 먼저 페이징의 메모리 보호을 이해하면 페이징에서의 공유 페이지 (Shared Pages)이 왜 필요한지 더 쉽게 보여요.
- 그래서 페이징에서의 공유 페이지 (Shared Pages)을 잘 알면 나중에 TLB (Translation Look-aside Buffer)도 훨씬 쉽게 배울 수 있어요.