CPU, 메모리, 디스크, 브라우저 캐시, Redis 같은 DB 캐시까지…

다 따로 노는 개념처럼 보이지만, 사실 전부 “느린 것 대신 빠른 곳에 미리/자주 저장해두자”는 같은 아이디어에서 출발한다.

이번 글에서는:

  • 메모리 계층 구조
  • 캐시와 지역성의 원리
  • 캐시 히트 / 캐시 미스
  • 캐시 매핑 방식
  • 웹 브라우저의 캐시 (쿠키, localStorage, sessionStorage)
  • 데이터베이스 캐싱 계층 (Redis)

까지 한 번에 묶어서 정리해본다.


1. 메모리 계층 구조: 왜 계층이 필요할까?

CPU는 엄청 빠른데, 메모리는 그 정도로 빠르지 않다.

디스크나 네트워크까지 가면 더더욱 느려진다. 그래서 시스템 구조는 이렇게 생겼다:

레지스터 (Register)
       ↓
L1 캐시
       ↓
L2 캐시
       ↓
L3 캐시
       ↓
메인 메모리 (DRAM)
       ↓
SSD / HDD
       ↓
원격 스토리지, DB, 네트워크

아래로 갈수록:

  • 속도는 느려지고
  • 용량은 커지고
  • 비용은 싸진다

이 계층 구조 덕분에, 자주 쓰는 데이터는 위쪽(빠른 계층)에 두고,

덜 자주 쓰는 데이터는 아래쪽(느린 계층)에 둬서 속도와 비용을 동시에 잡는 전략을 쓴다.


2. 캐시(Cache)란? — “위 층에 두는 복사본”

캐시는 한 줄로 정리하면:

느린 저장장치(메모리/디스크/네트워크)에 있는 데이터를 빠른 저장장치에 “복사본”으로 보관해두는 구조

CPU 입장에서는:

  • 캐시 = 메모리의 복사본

웹 브라우저 입장에서는:

  • 캐시 = 서버 리소스(HTML, JS, 이미지 등)의 복사본

DB 입장에서는:

  • 캐시 = 디스크나 원격 DB에 있는 데이터의 복사본 (예: Redis)

공통점:

자주 쓰는 것을 더 빠른 공간에 미리 올려둠으로써 평균 접근 시간을 줄인다.


3. 지역성의 원리: 캐시가 먹히는 이유

캐시는 “운 좋으면 빠르고, 아니면 말고”가 아니다.

CPU나 프로그램이 실제로 “지역성(Locality)” 이라는 패턴을 보이기 때문에 잘 먹힌다.

3-1. 시간 지역성 (Temporal Locality)

한 번 접근한 데이터는 가까운 미래에 또 접근될 가능성이 크다.

예:

  • for 루프 안에서 같은 변수 계속 사용
  • 최근에 사용한 함수가 다시 호출
  • 스택 프레임, 전역 변수, 카운터 등

그래서 “최근에 사용한 것들을 캐시에 남겨두자”는 전략이 의미가 있다.

3-2. 공간 지역성 (Spatial Locality)

어떤 주소에 접근했다면, 그 주변 주소에도 곧 접근할 가능성이 크다.

예:

  • 배열 순회 arr[0], arr[1], arr[2] …
  • 연속된 구조체 접근
  • 코드도 메모리에 연속적으로 올라가 있으므로, 인근 명령어들을 순차 실행

그래서 캐시는 한 번에 한 워드만 가져오지 않고, 그 주변까지 한 덩어리(블록 또는 라인)로 가져온다.

3-3. 요약

  • 시간 지역성: “다시 쓸 가능성이 크다 → 캐시에 오래 보관”
  • 공간 지역성: “옆 주소도 쓸 가능성이 크다 → 주변을 같이 가져오자”

CPU 캐시, 디스크 캐시, DB 캐시, 웹 캐시 모두 이 개념을 활용한다.


4. 캐시 히트와 캐시 미스

캐시를 쓴다는 건 결국 다음 둘 중 하나다.

✔ 캐시 히트(Cache Hit)

CPU(또는 프로그램)가 찾는 데이터가 캐시에 이미 있는 경우

  • CPU: L1/L2/L3 캐시에서 찾음
  • 브라우저: 로컬 캐시에 HTML/JS/이미지 이미 있음
  • Redis: 메모리에서 바로 해당 키를 찾음

→ 매우 빠르다.

→ CPU 캐시의 경우, CPU 내부 버스만 타고 끝나기 때문에 레이턴시가 극도로 짧다.

✔ 캐시 미스(Cache Miss)

캐시에 없어서 더 아래 계층(느린 계층)까지 내려가서 데이터를 가져와야 하는 경우

CPU 입장에서는:

  • 캐시에 없음 → 메인 메모리(DRAM)까지 가야 함 → 시스템 버스를 타고 왕복 → 느림

DB 입장에서는:

  • Redis에 없음 → 디스크 기반 RDB까지 조회 → 느림

웹 브라우저 입장에서는:

  • 브라우저 캐시에 없음 → 서버까지 HTTP 요청 → 느림

그래서 시스템들은 최대한 히트율(hit ratio) 을 올리려고 한다.


5. 캐시 매핑 방식 (CPU 캐시 관점)

CPU 캐시에서 데이터가 “어디에 놓이는가?” 를 결정하는 규칙이 캐시 매핑(Cache Mapping)이다.

5-1. Direct Mapped Cache

메인 메모리의 특정 블록은 캐시의 딱 한 위치에만 저장 가능

  • 장점: 구현이 단순, 빠름
  • 단점: 충돌이 많을 수 있음 (자주 쓰지만 같은 인덱스를 공유하는 데이터들)

5-2. Fully Associative Cache

메모리 블록이 캐시 어느 곳에나 저장될 수 있음

  • 장점: 충돌 최소화
  • 단점: 어디에 있는지 찾기가 복잡 (비싸고 느림)

5-3. Set Associative Cache (실제 많이 사용)

캐시를 여러 set으로 나누고,

각 set 안에서는 associative하게 배치 가능

  • 예: 4-way set associative
  • Direct와 fully associative의 타협안
  • 현대 CPU에서 가장 일반적인 방식

이 매핑과 더불어:

  • 어떤 블록을 버릴지(교체 정책: LRU, Random 등)
  • 쓰기 정책(write-back, write-through)

까지 합쳐져 캐시의 성능이 결정된다.


6. 웹 브라우저의 캐시: 메모리 계층의 “상위 레벨” 버전

웹 개발을 하다 보면 또 이런 캐시들이 등장한다:

  • HTTP 캐시 (브라우저가 리소스를 저장)
  • 쿠키(Cookie)
  • localStorage, sessionStorage

여기서 쿠키 / localStorage / sessionStorage는 좀 역할이 다르다.

6-1. 쿠키(Cookie)

  • 서버나 자바스크립트가 브라우저에 심는 작은 데이터 조각
  • 매 요청마다 HTTP 헤더에 실려서 서버로 전송됨
  • 주로 로그인 세션, 트래킹, 사용자 식별 등에 사용
  • 용량이 작음(수 KB 단위), 보안 이슈 있음(HTTP 전송)

쿠키는 “캐시”라기보다는 상태 유지용에 더 가깝지만,

“서버가 매번 다시 묻지 않기 위해” 정보를 로컬에 저장한다는 점에서 넓은 의미의 캐시로 볼 수도 있다.

6-2. localStorage

  • 도메인별로 영구 저장되는 키-값 저장소
  • 브라우저를 껐다 켜도 남아있음
  • JS에서 localStorage.setItem(key, value) 로 접근
  • 서버에 자동으로 전송되지 않음 (쿠키와 가장 큰 차이)

예:

  • 다크 모드 설정
  • 최근 본 상품 리스트
  • 사용자의 간단한 환경설정

6-3. sessionStorage

  • 탭 단위로 살아있는 저장소
  • 탭을 닫으면 사라짐
  • 페이지 리로드 시에는 유지됨

예:

  • 특정 페이지에서만 쓰는 임시 상태
  • 탭마다 분리되어야 하는 데이터

6-4. 진짜 “웹 캐시” — HTTP 캐시

여기까지 오면 진짜 캐시다운 캐시가 나온다.

  • HTML, CSS, JS, 이미지 파일 등을 브라우저가 로컬 디스크/메모리에 저장
  • 다음에 같은 URL에 접근할 때,
    • 만료 안 됐으면 그대로 사용 (Cache Hit)
    • 만료됐거나 조건부 요청시 서버에 재검증

이건 CPU–메모리 캐시와 거의 동일한 개념이다:

  • 원본: 서버의 리소스
  • 복사본: 브라우저 로컬 캐시
  • 목표: 네트워크 왕복 줄이기

7. 데이터베이스의 캐싱 계층 (Redis 등)

백엔드 쪽으로 가면 또 하나의 “메모리 계층” 구조가 보인다.

일반적인 구조:

CPU / 애플리케이션 (서버 코드)
          ↓
       Redis (인메모리 캐시, key-value)
          ↓
   RDBMS (MySQL, PostgreSQL 등, 디스크 기반)
          ↓
   디스크 / 스토리지

역시 똑같다.

  • 자주 읽히는 데이터는 메모리에 띄워놓고 빠르게 제공
  • 덜 자주 쓰이거나, 영구 보관이 필요한 데이터는 디스크 기반 DB에 저장

7-1. Redis의 역할

Redis는:

  • 메모리 기반 key-value 저장소
  • 읽기·쓰기 속도가 매우 빠름
  • 세션 관리, 랭킹, 카운터, 토큰 저장 등에서 자주 사용

예:

  • GET user:123 → Redis에서 바로 나오면 DB까지 안 내려감 → Hit
  • Redis에 없으면 → MySQL 쿼리 → 결과를 Redis에 저장 → Miss 후 Fill

이 구조를 통해:

  • DB 부하 분산
  • 응답 시간 단축
  • 스케일 아웃 용이

결국 DB도 자체적으로 “메모리 계층”을 쌓는 것이다.


8. 전체 그림: 모든 레벨의 캐시를 한 번에 묶어 보면

조금 과장해서 그려보면, 요즘 서비스 하나를 띄웠을 때 메모리 계층은 이렇게 된다:

[CPU 레지스터]
   ↓
[CPU 캐시 (L1/L2/L3)]
   ↓
[메인 메모리 (프로세스 힙/스택)]
   ↓
[OS 페이지 캐시, 버퍼 캐시]
   ↓
[DB 캐시 (MySQL InnoDB Buffer Pool 등)]
   ↓
[Redis 같은 인메모리 캐시]
   ↓
[디스크 기반 RDBMS 데이터 파일]
   ↓
[원격 스토리지, 백업, 데이터 레이크 …]

+ 클라이언트 쪽:
[브라우저 메모리]
   ↓
[브라우저 HTTP 캐시]
   ↓
[localStorage / sessionStorage / 쿠키]
   ↓
[네트워크 요청 → 서버]

위에서 아래로 내려갈수록:

  • 속도 ↓
  • 용량 ↑
  • 비용 ↓
  • 영속성 ↑

그 사이사이에 “캐시”라는 계층을 끼워넣어 성능을 끌어올리는 구조다.


9. 마무리 요약

  • 메모리 계층: 빠른 것(작고 비싸다)부터 느린 것(크고 싸다)까지 층을 쌓아둔 구조
  • 캐시: 느린 계층의 복사본을 빠른 계층에 두는 구조
  • 지역성의 원리 덕분에 캐시는 잘 먹힌다
    • 시간 지역성: 최근 쓴 걸 또 쓴다
    • 공간 지역성: 근처도 같이 쓴다
  • 캐시 히트/미스: 히트면 빠른 계층에서 해결, 미스면 아래로 내려가야 해서 느림
  • 캐시 매핑: 메모리 블록이 캐시에 어디에 배치되는지 결정(direct / associative / set-associative)
  • 웹 브라우저 캐시: HTTP 캐시 + 쿠키/localStorage/sessionStorage라는 다양한 형태로 존재
  • DB/Redis 캐시: DB 앞에 메모리 캐시 계층을 두어 읽기 성능을 끌어올리는 구조

결국 로우레벨(CPU 캐시)부터 하이레벨(웹, DB)까지 같은 아이디어가 반복해서 등장한다.

 

목표: Redis는 아직 사용해보지 않아서, 프로젝트에 적용해보려고 한다. 얼마나 빨라지려나!? 😬

오늘의 공부

'CS' 카테고리의 다른 글

[CS-운영체제] 컴퓨터의 요소  (1) 2025.11.17
[CS-운영체제] 운영체제의 역할과 구조  (0) 2025.11.16

1. CPU (Central Processing Unit)

CPU는 모든 명령어를 실제로 실행하는 중심 장치다.

내부 구성 요소들을 하나씩 보면 OS가 CPU와 어떻게 맞물려 돌아가는지 더 잘 이해할 수 있다.

제어장치(Control Unit)

  • 메모리에 저장된 명령어를 가져오고( fetch )
  • 해석하고( decode )
  • 적절한 하드웨어에 제어 신호를 준다

OS 입장에서는 스케줄러가 선택한 프로세스를 CPU가 순차적으로 수행할 수 있도록 하는 핵심 역할을 한다.

ALU(산술논리연산장치)

  • 사칙연산, 논리연산(AND, OR, XOR…), 비교연산 수행
  • 시스템콜, 프로세스 실행, 인터럽트 처리 등 모든 계산이 여기서 이루어짐

레지스터(Register)

CPU 안에 있는 초고속 임시 저장장치.

프로세스를 문맥교환(context switch)할 때 이 레지스터 값을 그대로 PCB에 저장하고 다시 복원한다.

주요 레지스터:

  • PC (Program Counter): 다음 명령어 주소
  • IR: 현재 명령어
  • SP: 스택 포인터
  • PSW: CPU 상태(조건 플래그 + 모드비트)

여기서 모드비트(mode bit)가 사용자 모드/커널 모드를 결정한다.

인터럽트(Interrupt)

CPU가 OS와 상호작용하는 공식적인 통로이다.

인터럽트가 발생하면 CPU는 하고 있던 작업을 멈추고:

  1. 현재 PC·레지스터를 PCB에 저장
  2. 커널 모드로 전환
  3. 해당 인터럽트 핸들러 실행

이렇게 돌아간다.

실제로 CPU는 사용자 프로그램이 아니라 “인터럽트 핸들러 → 스케줄러 → 다음 프로세스” 순으로 계속 점프하면서 실행한다.

IRQ(Interrupt Request) — 인터럽트를 식별하고 처리하는 커널의 기본 단위

인터럽트가 발생했을 때 운영체제가 그것을 구분하고 올바른 핸들러를 호출하기 위해 사용하는 개념이 IRQ(Interrupt Request)다.

각 하드웨어 장치는 자신만의 IRQ 번호를 가지고 있고, 이 번호를 통해 커널은 “어떤 장치가 인터럽트를 보냈는가?”를 식별한다.

  • IRQ가 존재하는 이유
    • 키보드, 마우스, 디스크, 네트워크 카드 등 모든 장치 이벤트를 구분하기 위한 번호체계
    • CPU가 단순히 “인터럽트 발생”만 알게 되는 것이 아니라
    • 커널이 어떤 장치에서 왔는지 식별할 수 있도록 하는 구조
  • 커널 내부에서의 IRQ 처리 방식 (핵심 흐름)커널은 IRQ 번호마다 그에 대응하는 처리 정보를 테이블 형태로 들고 있고,
  • 이를 기반으로 적절한 interrupt handler를 호출한다.
  • 1. 장치가 인터럽트를 발생시킴 2. CPU가 인터럽트를 받아 커널 모드로 전환 3. 커널의 IRQ 시스템이 “이건 IRQ X번”이라고 식별 4. 해당 IRQ 번호에 등록된 핸들러 함수 실행
  • request_irq()의 의미
    • 드라이버나 커널 모듈에서 인터럽트 핸들러를 등록할 때 사용한다.
    request_irq(irq_number, handler, flags, name, dev);
    

2. DMA 컨트롤러 (Direct Memory Access)

I/O 장치와 메모리 사이의 대량 데이터 전송을 CPU 없이 수행한다.

DMA 동작 방식

  1. CPU가 DMA에게 “어디서부터 얼마만큼 복사해라”라고 설정한다
  2. DMA가 버스를 직접 차지한다
  3. 장치 ↔ 메모리 사이 데이터를 CPU 개입 없이 옮긴다
  4. 끝나면 CPU에게 “끝났어” 인터럽트 발생

CPU는 이 과정 동안 다른 프로세스를 스케줄링하거나, 시스템콜을 처리하거나, 완전히 다른 일들을 수행할 수 있다.

결국 OS의 I/O 성능을 좌우하는 요소다.


3. 메모리 (Memory)

모든 프로그램의 코드와 데이터를 저장하는 공간이다.

OS 입장에서는 “프로세스마다 격리된 주소 공간을 어떻게 제공할 것인가?”가 핵심이다.

메모리 계층 구조

CPU 레지스터
   ↓
L1 / L2 / L3 캐시
   ↓
메인 메모리 (DRAM)
   ↓
SSD / HDD

빠를수록 용량이 작고, 느릴수록 크다.

운영체제 관점 핵심 포인트

  • 가상 메모리 제공
  • 페이지 테이블 관리
  • TLB 미스 처리
  • 페이지 교체 알고리즘(LRU 등)
  • 프로세스 문맥에 맞춰 MMU 설정(페이지 테이블 베이스 레지스터 변경)

결국 메모리는 CPU만큼이나 OS가 가장 집중적으로 관리하는 부분이다.


4. 타이머(Timer)

타이머는 현대 운영체제의 “선점형 시분할 방식”을 가능하게 만든 장치다.

프로세스가 CPU를 독점하지 못하게 하는 핵심 장치.

타이머 인터럽트 동작

일정 주기(보통 1ms 또는 4ms)마다 CPU에게 인터럽트를 발생시킨다.

흐름은 다음과 같다:

타이머 인터럽트 발생
      ↓
커널 모드 전환
      ↓
스케줄러 실행
      ↓
현재 프로세스 시간 종료 체크
      ↓
필요하면 컨텍스트 스위칭

만약 타이머가 없다면, 프로세스는 직접 CPU를 반납하지 않는 이상 무제한 실행된다.

타이머가 있어야 Round Robin, MLFQ 같은 스케줄링 알고리즘이 동작한다.


5. 디바이스 컨트롤러(Device Controller)

모든 I/O 장치는 자체 컨트롤러를 갖고 있고, OS는 이 컨트롤러를 통해 장치를 제어한다.

예:

  • 디스크 컨트롤러
  • 키보드 컨트롤러
  • 마우스 컨트롤러
  • GPU
  • NIC(네트워크 카드)
  • USB 컨트롤러

구성 요소

  • 데이터 레지스터
  • 제어/상태 레지스터
  • 인터럽트 라인
  • 버퍼

운영체제는 이 컨트롤러와 직접 비트를 주고받는 것이 아니라, 디바이스 드라이버를 통해 추상화된 방식으로 제어한다.


6. 전체 흐름 예시

“read() 시스템콜이 실제로 수행되는 과정”

1) 사용자 프로그램이 read() 호출
2) 시스템콜을 통해 커널 모드로 진입
3) 파일 시스템에서 읽을 블록 주소 조회
4) 디바이스 드라이버가 디스크 컨트롤러에 I/O 명령 전달
5) DMA가 메모리 버퍼 주소·크기 설정
6) DMA가 디스크 → 메모리로 직접 복사
7) 복사가 끝나면 DMA가 인터럽트 발생
8) CPU가 인터럽트 처리 → 해당 프로세스를 ready로 이동
9) 사용자 모드로 복귀

7. 정리

요소 핵심 역할 OS와의 관계

CPU 명령어 실행, 인터럽트 처리 스케줄러, 시스템콜의 중심
DMA 대량 I/O 전송 파일 시스템, 네트워크 성능 핵심
메모리 프로그램 저장 가상 메모리 관리, MMU
타이머 CPU 독점 방지, 스케줄링 선점형 스케줄링트리거
디바이스 컨트롤러 장치 제어 드라이버를 통해 추상화

 

오늘 공부

'CS' 카테고리의 다른 글

[CS-운영체제] 메모리 계층과 캐시  (0) 2025.11.18
[CS-운영체제] 운영체제의 역할과 구조  (0) 2025.11.16

1. 운영체제가 왜 중요한가?

운영체제(Operating System, OS)는 흔히 “하드웨어와 사용자(또는 응용 프로그램) 사이의 중간 계층”이라고 말한다.

하지만 전공생 입장에서 이 정의만으로는 너무 부족하다. 운영체제는 사실상 현대 컴퓨터 시스템의 규칙과 질서를 만드는 핵심 소프트웨어다.

1-1. OS가 없다면 어떤일이 생길까?

운영체제가 없다면, 개발자는 다음과 같은 일을 직접 해야한다.

  • 프로그램마다 메모리 어느 영역을 쓸지 직접 계산해서 할당/해제
  • CPU가 어떤 프로그램을 언제 실행할지 직접 결정
  • 키보드, 마우스, 디스크, 네트워크 카드 등의 레지스터를 직접 조작
  • 디스크의 어느 섹터를 쓸지 직접 관리
  • 여러 프로그램이 동시에 실행될 때 충돌(경쟁 상태 등…) 해결

즉, 하드웨어의 모든 세부사항을 직접 제어해야 한다.

이는 사람이 직접 전선 하나하나를 납땜하며 회를 구성하는 것과 비슷한 수준의 고통이다.

운영체제는 이 복잡한 것들을 대신 처리해 주면서, 우리가 사용하는 상위 레벨의 언어(C, Java, Python 등)가 편하게 돌아갈 수 있는 환경을 제공한다.

1-2. 개발자 입장에서 운영체제가 중요한 이유

  1. 성능 튜닝과 디버깅
    • 서버 CPU 사용량 100%, 메모리 부족, 디스크 I/O 병목 → 다 OS 레벨 문제
    • top, htop, ps 같은 명령어를 제대로 이해하려면 OS 개념이 필수!
  2. 동시성(Concurrency) / 병렬성(Parallelism) 이해
    • 프로세스 vs 스레드, 컨텍스트 스위칭, 임계구역, 데드락 → 전부 OS의 핵심 주제
    • 멀티코어 활용, 비동기 프로그래밍, 쓰레드 풀 구현에도 직결
  3. 메모리 구조 이해
    • Stack/Heap, 가상 메모리, 페이지 폴트, 캐시 지역성 → 알고리즘 성능, 메모리 최적화에 직접 영향
  4. 시스템 프로그래밍, 네트워크 프로그래밍
    • 파일 I/O, 소켓, 프로세스 생성, IPC(파이프, 공유 메모리 등)는 거의 다 시스템 콜로 구현
  5. 면접, 코테, 실무 전부에서 자주 나온다
    • “프로세스와 스레드의 차이”, “컨텍스트 스위칭이 뭐냐”, “세마포어와 뮤텍스 차이”, “가상 메모리의 장점” 같은 질문은 거의 OS 단골 손님

2. 운영체제의 핵심 역할

운영체제의 역할을 크게 나누면 두 가지 키워드로 정리할 수 있다.

  1. 추상화(Abstraction)
  2. 자원 관리(Resource Management) + 보호(Protection)

2-1. 추상화 (Abstraction)

운영체제는 하드웨어를 “사용하기 편한 추상화”로 감싸준다.

  • 디스크 → “파일”이라는 추상화
  • 메모리 → “가상 주소 공간”이라는 추상화
  • CPU → “프로세스/스레드”라는 실행 단위로 추상화
  • 네트워크 장치 → “소켓”이라는 추상화

예를 들어, 디스크는 실제로는 섹터, 트랙, 블록 등으로 구성된 물리 장치인데, 우리는 단지:

int fd = open("data.txt", O_RDONLY);
read(fd, buf, 100);

이렇게 “파일”을 읽는 시스템 콜로만 접근한다. 디스크가 SSD인지 HDD인지, 어떤 블록 번호에 저장됐는지 신경 쓸 필요가 없다.

2-2. 자원 관리(Resource Management)

운영체제는 컴퓨터의 한정된 자원을 여러 프로그램이 공정하고 효율적으로 사용하도록 관리한다.

  • 관리 대상 자원:
    • CPU 시간
    • 메모리 (RAM)
    • 디스크/파일 시스템
    • 네트워크 대역폭
    • 입출력 장치 (프린터, 키보드, 마우스 등)

대표적인 기능들:

  • 스케줄링(Scheduling)
    • 어떤 프로세스/스레드에게 CPU를 언제, 얼마나 줄지 결정
    • 예: FCFS, SJF, Round Robin, Priority Scheduling 등
  • 메모리 할당/해제
    • 각 프로세스에 얼마만큼의 메모리를 줄지, 어떤 페이지를 물리 메모리에 둘지 결정
    • 페이지 교체 알고리즘 (LRU, FIFO, Clock 등)

2-3. 보호(Protection)와 보안(Security)

운영체제는 각 프로그램이 서로의 메모리나 자원에 함부로 접근하지 못하게 막는 역할도 한다.

  • 한 프로세스가 다른 프로세스의 메모리를 덮어쓰면 큰 문제 → 메모리 보호 장치 + 커널 모드/사용자 모드 구조로 방지.
  • 파일 접근 권한 (읽기/쓰기/실행 권한, 사용자/그룹/기타)
  • 시스템 콜을 통한 제한된 인터페이스 제공
  • → 직접 하드웨어 레지스터를 건드리지 못하게 막고, OS를 통해서만 자원 접근 허용.

3. 운영체제의 구조(Architecture)

운영체제 내부가 어떻게 설계되어 있는지에 따라 여러 구조로 나눌 수 있다.

3-1. 커널(Kernel)과 사용자 공간(User Space)

운영체제의 핵심은 커널(Kernel)이다.

  • 커널 공간(Kernel Space)
    • OS 핵심 코드가 실행되는 특권 영역.
    • 메모리, I/O, 스케줄링 등 시스템 전체에 영향을 미치는 코드.
  • 사용자 공간(User Space)
    • 일반 응용 프로그램이 실행되는 영역.
    • 직접 하드웨어에 접근할 수 없고, 시스템 콜을 통해서만 커널 기능 사용.

이 둘을 논리적으로 나누기 위해 모드 비트(mode bit)를 사용해서:

  • 0: 커널 모드
  • 1: 사용자 모드
  • 같은 식으로 구분하는 개념을 도입한다(실제 값은 구현마다 다를 수 있지만 개념적으로 그렇다).

3-2. 모놀리식 커널(Monolithic Kernel)

  • 하나의 거대한 커널 바이너리에 거의 모든 OS 기능이 들어 있는 구조.
  • 예: 전통적 Unix, Linux
  • 특징:
    • 장점: 커널 안에서 함수 호출로 바로바로 이어지기 때문에 성능이 빠름
    • 단점: 한 부분에 버그가 있으면 전체 커널이 크래시 날 수 있고, 유지보수가 어렵다.

3-3. 마이크로커널(Microkernel)

  • 정말 “핵심 중의 핵심” 기능만 커널 안에 넣고, 나머지 기능(파일 시스템, 디바이스 드라이버, 네트워크 등)은 사용자 공간의 서버 프로세스로 뺀 구조.
  • 예: Minix, 일부 상용 RTOS, microkernel 기반 설계들
  • 장점:
    • 커널이 작아져서 안정성 증가
    • 특정 서버(예: 파일 시스템 서버)가 죽어도, 커널 전체가 죽지 않고 재시작 가능
  • 단점:
    • 커널 ↔ 사용자 공간 프로세스 간 메시지 전달(IPC)이 많아지면서 성능 오버헤드 발생

3-4. 하이브리드 커널(Hybrid Kernel)

  • 모놀리식과 마이크로커널의 장점을 어느 정도 섞은 형태.
  • 예: Windows NT 계열, macOS의 XNU
  • 실제로는 “마이크로커널 철학을 어느 정도 따르지만, 성능을 위해 여러 기능을 커널에 넣는” 현실적인 타협 구조.

3-5. 계층형 구조(Layered OS)

  • 운영체제를 여러 층으로 나눠, 각 계층이 바로 아래 계층의 서비스만 사용하도록 하는 구조.
    • 하드웨어
    • CPU 스케줄러 / 메모리 관리
    • I/O 관리
    • 파일 시스템
    • UI/쉘 등
  • 장점:
    • 설계와 이해가 쉽고, 유지 보수에 유리
  • 단점:
    • 계층간 의존성이 강해 유연성이 떨어질 수 있고, 효율보다 구조의 깔끔함을 우선할 수 있음.

4. 운영체제의 핵심 컴포넌트

운영체제의 내부를 기능별로 나누면 보통 다음과 같이 볼 수 있다.

컴포넌트 역할

프로세스 관리자(Process Manager) 프로세스/스레드 생성, 스케줄링, 종료, 동기화
스케줄러(Scheduler) CPU 자원을 어떤 프로세스에게 어떻게 배분할지 결정
메모리 관리자(Memory Manager) 가상 메모리, 페이지 테이블, 페이지 교체, 보호
파일 시스템(File System) 파일/디렉토리 추상화, 디스크 블록 관리
I/O 서브시스템(I/O Subsystem) 장치 드라이버, 인터럽트 처리, 버퍼링, 캐싱
네트워크 스택(Network Stack) TCP/IP 프로토콜 구현, 소켓 추상화 제공
시스템 콜 인터페이스(System Call Interface) 사용자 프로그램 ↔ 커널의 진입 지점
보호/보안 서브시스템(Protection & Security) 권한 체크, 접근 제어, 인증 등

조금 더 풀어서 보자.

4-1. 프로세스 관리자/스케줄러

  • 프로세스 생성: fork(), execve() 계열 호출
  • 프로세스 상태: ready, running, blocked, terminated 등
  • 스케줄러:
    • 선점(preemptive) vs 비선점(non-preemptive)

4-2. 메모리 관리자

  • 가상 주소 공간: 각 프로세스는 0번 주소부터 시작하는 독립된 주소 공간을 가진 것처럼 보임
  • 페이지: 보통 4KB 단위
  • 페이지 테이블: 가상 주소 → 물리 주소 매핑 정보 저장
  • 페이지 폴트(Page Fault): 해당 페이지가 메모리(RAM)에 없을 때 디스크에서 가져오는 이벤트

4-3. 파일 시스템

  • 디렉토리 트리 구조
  • Inode(리눅스/유닉스 파일 시스템): 파일의 메타데이터를 담는 구조체

4-4. I/O 서브시스템과 디바이스 드라이버

  • I/O는 상대적으로 느린 장치(디스크, 네트워크 등)가 많아서, 인터럽트와 DMA로 효율적인 처리가 중요하다.
  • 디바이스 드라이버: 하드웨어 종속적 코드를 OS에 연결해, 상위 계층에서는 통일된 인터페이스로 장치를 사용할 수 있게 해준다.

5. 시스템 콜(System Call)과 모드 비트(Mode Bit)

5-1. 시스템 콜이란?

시스템 콜(System Call)은 사용자 프로그램이 운영체제(커널)의 서비스를 요청하는 공식적인 인터페이스이다.

  • 예시:
    • open(), read(), write(), close() → 파일 I/O
    • fork(), execve(), exit() → 프로세스 관리
    • socket(), bind(), connect(), accept() → 네트워크

C 코드에서 read()를 호출하면, 실제로는 C 라이브러리(glibc 등)가 내부에서 시스템 콜 번호를 세팅하고, 특수 명령어(예: x86의 syscall, int 0x80)를 실행해서 커널 모드로 진입한다.

5-2. 왜 시스템 콜이 필요한가?

  • 사용자 모드에서는 보안·안정성 때문에 직접 하드웨어에 접근 불가.
  • 예를 들어, 사용자 프로그램이:
    • 임의의 물리 메모리 주소에 write
    • 임의의 I/O 포트에 write
    • 이런 것들을 허용하면 다른 프로세스, OS 자체를 망가뜨릴 수 있다.

그래서 운영체제는 커널 모드에서만 자원에 대한 직접 접근을 허용하고, 사용자 프로그램은 시스템 콜이라는 “문”을 통해서만 요청을 전달하도록 강제한다.

5-3. 모드 비트(Mode Bit)와 사용자 모드 ↔ 커널 모드 전환

CPU는 보통 특권 수준(privilege level) 을 구분하는 하드웨어 메커니즘을 제공한다.

  • 사용자 모드(User Mode): 제한된 명령만 실행 가능 (I/O 명령, 특정 제어 레지스터 접근 불가)
  • 커널 모드(Kernel Mode): 시스템 전체를 제어할 수 있는 특권 명령 실행 가능

이걸 논리적으로 모드 비트(Mode Bit)라고 부른다.

시스템 콜이 호출되면:

  1. 사용자 모드에서 시스템 콜 라이브러리 호출
  2. 시스템 콜 번호를 특정 레지스터나 스택에 세팅
  3. syscall 같은 특수 인스트럭션 실행 → 트랩(trap) 발생
  4. CPU가 모드 비트를 사용자 모드 → 커널 모드로 변경
  5. 시스템 콜 핸들러(커널 함수)로 점프
  6. 커널이 작업 수행 (예: 파일 읽기, 소켓 연결 등)
  7. 반환 시, 다시 모드 비트를 커널 모드 → 사용자 모드로 변경하고, 사용자 코드로 복귀

5-4. 시스템 콜 호출 흐름 예제 (파일 읽기)

C 코드:

int fd = open("test.txt", O_RDONLY);
char buf[100];
int n = read(fd, buf, 100);

내부적으로는 대략 이렇게 동작한다고 생각하면 된다(개념적 흐름):

  1. open() 함수 호출 → glibc 내부에서 sys_open 시스템 콜 번호 설정
  2. syscall 인스트럭션 실행 → trap 발생, 커널 모드 진입
  3. 커널의 sys_open() 함수가 실행되며:
    • 권한 체크 (파일 읽기 가능한지?)
    • 파일 시스템에서 해당 파일 위치(inode) 검색
    • 프로세스의 파일 디스크립터 테이블에 엔트리 생성
  4. 파일 디스크립터 번호(fd)를 리턴
  5. 다시 사용자 모드로 돌아와 C 코드에서 fd 값 사용

read()도 마찬가지로, 커널 모드에서 파일 내용을 메모리(커널 버퍼)에 읽어오고, 그 내용을 사용자 공간 버퍼 buf에 복사해준다.


6. 추가 설명들 + 예제

6-1. 예제 1: 프로세스 생성 흐름 (fork + exec)

Unix 계열에서 새로운 프로그램을 실행할 때는 보통:

  1. 부모 프로세스가 fork() 시스템 콜로 자신을 복제한다.
  2. 자식 프로세스는 execve() 계열 시스템 콜로 실행할 프로그램을 자신의 주소 공간에 로드한다.

간단한 예:

pid_t pid = fork();
if (pid == 0) {
    // 자식 프로세스
    execl("/bin/ls", "ls", "-l", NULL);
    // execl이 성공하면 이 아래 코드는 실행되지 않음
} else {
    // 부모 프로세스
    wait(NULL);  // 자식 종료 대기
}

이 안에서 실제로는:

  • fork() 호출 → 시스템 콜 → 커널 모드 진입
    • 부모의 PCB(Process Control Block), 페이지 테이블 등을 복사 또는 COW(Copy-On-Write) 방식으로 공유
  • 자식이 execl() → execve() 시스템 콜 → 커널 모드
    • 해당 실행 파일을 디스크에서 읽어와 자식 프로세스의 주소 공간에 매핑
    • 기존 코드/데이터/스택 내용 교체

이 모든 과정에서 프로세스, 메모리, 파일 시스템, I/O 등 OS의 여러 컴포넌트가 협력하여 동작한다.

6-2. 예제 2: 컨텍스트 스위칭(Context Switching)

CPU는 한 순간에는 한 프로세스(또는 스레드)만 실행할 수 있다.

멀티태스킹을 구현하기 위해 OS는 컨텍스트 스위칭을 수행한다.

컨텍스트 = 레지스터 값, 프로그램 카운터(PC), 스택 포인터, 페이지 테이블 포인터 등 현재 실행 상태 전체.

컨텍스트 스위칭 흐름(개략):

  1. 프로세스 A가 실행 중
  2. 타이머 인터럽트 발생 (예: 1ms마다)
  3. CPU가 커널 모드로 전환, 인터럽트 핸들러 실행
  4. 커널이 현재 프로세스 A의 레지스터 상태를 PCB에 저장
  5. 스케줄러가 다음 실행할 프로세스 B 선택
  6. PCB에서 B의 레지스터 상태를 복원
  7. CPU가 B의 컨텍스트로 재개 → 마치 B가 연속 실행되는 것처럼 보임

이렇게 해서 여러 프로세스가 동시에 실행되는 것처럼 보이는 착시 효과를 만든다.

6-3. 예제 3: 단순한 OS 레벨 사고방식 정리

(1) “파일 저장”은 OS 입장에서 무엇인가?

사용자 관점:

“메모장 켜서 memo.txt에 내용 쓰고 저장 클릭했어.”

OS 관점:

  1. 애플리케이션이 write() 시스템 콜 호출
  2. 커널 모드 진입 → 파일 디스크립터와 inode 확인
  3. 페이지 캐시(page cache)에 데이터 기록
  4. 일정 시점에 디스크 스케줄러가 실제 디스크 블록에 기록

(2) “프로그램 실행”은 OS 입장에서 무엇인가?

사용자 관점:

“크롬 아이콘 더블클릭했어.”

OS/런처 관점:

  1. GUI 셸(또는 터미널)이 fork() + exec()를 통해 크롬 프로세스를 생성
  2. OS가 크롬 프로세스에 새로운 가상 주소 공간 할당
  3. 실행 파일을 메모리에 매핑, 초기 스택 설정
  4. 스케줄러가 CPU 시간 할당 → 크롬 코드가 실행 시작

7. 정리

이 글에서는 운영체제의 역할과 구조를 전공생 관점에서 비교적 깊게 다뤄봤다.

정리하면 운영체제는:

  • 하드웨어 위에 올라가는 추상화 계층이자,
  • 여러 프로그램이 동시에 동작할 수 있도록 돕는 자원 관리자이며,
  • 시스템 전체의 안정성과 보안을 책임지는 보호자(guardian) 역할을 한다.

그리고 내부적으로는:

  • 커널 / 사용자 공간
  • 모놀리식 / 마이크로커널 / 하이브리드 구조
  • 프로세스/스케줄러, 메모리 관리자, 파일 시스템, I/O, 네트워크, 시스템 콜 인터페이스
  • 등으로 구성되어 복잡하게 맞물려 돌아간다.

오늘의 공부

'CS' 카테고리의 다른 글

[CS-운영체제] 메모리 계층과 캐시  (0) 2025.11.18
[CS-운영체제] 컴퓨터의 요소  (1) 2025.11.17

+ Recent posts