06. vm-api

  • Haram Lee
  • 2026-04-14
  • studies / 26-1 / operating-systems

Virtual Memory

  • Virtual memory는 일종의 level of indirection이다.
  • 프로그램은 physical address를 직접 쓰는 대신 virtual address를 사용하고, 실행 중에 address translation mechanism이 이를 physical address로 바꾼다.
  • physical memory를 프로세스에 직접 노출하면 unsafe하다. 여러 프로세스를 메모리에 함께 올려 두면, 잘못된 memory access가 다른 프로세스나 OS를 망가뜨릴 수 있기 때문이다.
  • 그래서 OS는 virtual address라는 새로운 추상화를 만들고, 이를 통해 Safety, Convenience, Efficiency를 얻고자 한다.

Goals

  • Protection
    • 다른 프로세스와 OS를 보호해야 한다.
    • 한 프로세스가 실패해도 다른 프로세스에 영향을 주지 않도록 isolation을 제공해야 한다.
    • cooperating processes라면 일부 메모리를 공유할 수도 있어야 한다.
  • Transparency
    • 프로세스는 메모리가 실제로 공유되고 있다는 사실을 몰라도 된다.
    • 프로그래머에게는 크고 연속적인 메모리 공간처럼 보여야 한다.
  • Efficiency
    • variable-sized request 때문에 생기는 fragmentation을 줄여야 한다.
    • address translation은 자주 일어나므로 시간 오버헤드를 줄이기 위해 하드웨어 지원도 필요하다.

(Virtual) Address Space

  • 각 프로세스는 자기만의 virtual address space를 가진다.
  • 이것은 프로세스가 보는 추상적인 메모리 모습이다.
  • 주소 공간에는 그 프로세스의 모든 memory state가 들어 있다.
  • 크게 보면 다음과 같이 나뉜다.
    • Static area
      • exec() 시점에 잡힌다.
      • code와 data가 여기에 들어간다.
    • Dynamic area
      • runtime에 할당된다.
      • heap과 stack이 여기에 들어가며, 커지거나 줄어들 수 있다.
  • 슬라이드 그림에서는 아래쪽에 read-only segment인 .init, .text, .rodata가 있고, 그 위에 read/write segment인 .data, .bss가 있다.
  • heap은 위로 자라고, 그 끝 위치를 brk가 나타낸다.
  • stack은 반대편에서 생성되고 자라난다.
  • kernel virtual memory는 user code에서 접근할 수 없다.
  • 각 프로세스는 large하고 contiguous한 private address space를 가진 것처럼 보인다.
  • 실제 translation은 run time에 일어난다.
  • 또한 virtual memory는 lazy allocation을 지원하므로, 프로그램 전체 주소 공간이 한꺼번에 physical memory에 올라가 있을 필요는 없다.

Virtual Memory APIs

  • 프로그래머가 직접 만지는 메모리 API는 크게 두 층으로 볼 수 있다.
    • libc 쪽 API
      • malloc()
      • free()
      • calloc()
      • realloc()
    • OS system call 쪽 API
      • brk()
      • sbrk()
      • mmap()
  • malloc/free는 heap을 관리하는 데 쓰이고, heap 자체 크기를 바꾸는 일은 내부적으로 brk/sbrk가 맡는다.
  • 즉, 프로그래머는 보통 malloc()을 쓰지만, 그 아래에서는 libc와 OS가 함께 주소 공간을 관리한다.

malloc() / free()

  • malloc()은 heap에 memory region을 할당한다.
    • 인자
      • size_t size
      • 할당할 메모리 크기(byte 단위)
    • 반환값
      • 성공하면 할당된 block의 시작 주소를 가리키는 포인터
      • 실패하면 NULL
  • free()malloc()으로 할당한 memory region을 해제한다.
    • 인자
      • void *ptr
      • 해제할 memory block의 포인터
    • 반환값은 없다.
c
#include <stdlib.h>

void* malloc(size_t size);
void free(void* ptr);
  • 슬라이드 예시에서는 지역 변수 포인터 pi가 stack에 있고, 실제로 할당된 int 4개짜리 공간은 heap에 생긴다.
  • pi 자체는 stack에 있지만, malloc(sizeof(int) * 4)가 만든 배열 공간은 heap에 있다.
  • 이후 free(pi)를 호출하면 그 heap block이 해제된다.
c
int *pi;    // local variable

pi = malloc(sizeof(int) * 4);

free(pi);

calloc() / realloc()

  • calloc()은 memory를 할당하면서 0으로 초기화한다.
    • 인자
      • size_t num
      • 객체 개수
      • size_t size
      • 각 객체 크기(byte)
  • realloc()은 기존 memory block의 크기를 바꾼다.
    • 인자
      • void *ptr
      • 기존 block 포인터
      • size_t size
      • 새 크기
    • 실패하면 원래 포인터를 그대로 반환한다.
c
#include <stdlib.h>

void *calloc(size_t num, size_t size);
void *realloc(void *ptr, size_t size);

brk() / sbrk()

  • heap 공간이 부족하면 libc의 allocator가 OS에게 heap을 더 크게 만들어 달라고 요청해야 한다.
  • 여기서 break는 주소 공간에서 heap의 끝 위치를 뜻한다.
  • malloc()은 내부적으로 brk()sbrk()를 사용해 program break를 확장할 수 있다.
  • sbrk()brk()와 비슷하다.
  • 슬라이드는 프로그래머가 직접 brk()sbrk()를 호출하지 말라고 강조한다. 보통은 malloc()이 내부적으로 사용한다.
c
#include <unistd.h>

int brk(void *addr);
void *sbrk(intptr_t increment);

mmap()

  • mmap()은 호출한 프로세스의 virtual address space에 새로운 mapping을 만든다.
  • 주요 인자는 다음과 같다.
    • addr
      • 새로운 mapping의 시작 주소
      • page boundary에 맞게 정렬되어야 한다
      • NULL이면 커널이 적절한 주소를 골라 준다
      • NULL이 아니면 커널에게 “여기쯤 배치해 줘”라는 hint를 주는 의미다
    • length
      • mapping 길이
    • prot
      • protection 정보
      • PROT_EXEC, PROT_READ, PROT_WRITE, PROT_NONE
    • flags
      • mapping 속성
      • MAP_PRIVATE, MAP_SHARED, MAP_ANONYMOUS
    • fd, offset
      • file mapping에 사용할 file descriptor와 file offset
  • mmap()은 “주소 공간의 어떤 구간을 무언가와 연결하는 함수”라고 볼 수 있다.
c
#include <sys/mman.h>

void *mmap(void *ptr, size_t length, int prot, int flags,
           int fd, off_t offset);

Uniformity of Memory Mapping

  • 가상 메모리의 중요한 장점 중 하나는, 여러 종류의 backing store를 통일된 방식으로 다룰 수 있다는 점이다.
  • 동적으로 할당된 virtual memory area는 다음을 backing store로 가질 수 있다.
    • file
    • device memory
    • shared memory
    • none
      • 즉 anonymous mapping
  • 결국 프로세스 입장에서는 이 모든 것이 주소 공간 안에 매핑된 memory region으로 보인다.

File Mapping vs. Anonymous Mapping

  • File mapping
    • backing store가 regular file이다.
    • memory region을 file region에 대응시킨다.
    • 그래서 파일 내용을 read()/write() 대신 load/store instruction으로 다룰 수 있다.
  • Anonymous mapping
    • file로 backing되지 않는 virtual address space다.
    • memory region을 0으로 채워진 memory area에 매핑한다.
    • 흔히 zero-page mapping처럼 생각하면 된다.

Shared Mapping vs. Private Mapping

  • 여러 프로세스가 같은 backing store를 자기 virtual address space에 매핑할 수 있다.
  • 이때 두 가지 방식이 있다.
    • Shared mapping
      • shared page에 대한 수정이 모든 관련 프로세스에게 보인다.
    • Private mapping
      • 수정 내용이 다른 프로세스에게 보이지 않는다.
      • 내부적으로 copy-on-write와 연결된다.
  • 슬라이드는 이걸 네 가지 경우로 정리한다.
    • Private file mapping
    • Private anonymous mapping
    • Shared file mapping
    • Shared anonymous mapping
  • 즉 file이냐 anonymous냐, shared냐 private냐는 서로 독립적인 축이다.
Discussion