Страничная организация памяти

Как ОС превращает «адрес 9000» в реальное место в микросхеме памяти.

Страничная организация (paging) делит виртуальную память на страницы, а физическую — на кадры одинакового размера; таблица страниц задаёт, какой странице какой кадр соответствует.

Зачем делить память на страницы

Если выдавать процессам память сплошными непрерывными кусками, быстро возникает фрагментация: свободная память есть, но разбита на мелкие дыры, в которые большой запрос не влезает. Paging решает это: память нарезается на одинаковые блоки фиксированного размера, и процессу выдаётся любой свободный набор блоков — необязательно соседних.

  • Страница (page) — блок виртуальной памяти.
  • Кадр (frame) — блок физической памяти того же размера.

Типичный размер страницы — 4 КБ (4096 байт). Страница процесса может лежать в любом свободном кадре, и они даже не обязаны идти подряд.

Таблица страниц

Таблица страниц — это «карта»: для каждой виртуальной страницы она хранит номер физического кадра, где страница лежит. У каждого процесса своя таблица — отсюда и изоляция.

Виртуальная страницаФизический кадр
05
19
22
37

Как транслируется адрес

Виртуальный адрес разбивается на две части:

  • Номер страницы = адрес ÷ размер страницы (целочисленно).
  • Смещение = адрес mod размер страницы (где внутри страницы).

Дальше по таблице страниц находим кадр и собираем физический адрес: физический = кадр × размер_страницы + смещение.

def translate(virtual, page_size, page_table):
    page = virtual // page_size      # номер страницы
    offset = virtual % page_size     # смещение внутри страницы
    if page not in page_table:
        print(f"Адрес {virtual}: страница {page} не в таблице -> page fault")
        return
    frame = page_table[page]
    physical = frame * page_size + offset
    print(f"Виртуальный адрес {virtual}:")
    print(f"  страница = {virtual} // {page_size} = {page}")
    print(f"  смещение = {virtual} % {page_size} = {offset}")
    print(f"  страница {page} -> кадр {frame}")
    print(f"  физический = {frame} * {page_size} + {offset} = {physical}")

page_table = {0: 5, 1: 9, 2: 2, 3: 7}
translate(9000, 4096, page_table)

Вывод:

Виртуальный адрес 9000:
  страница = 9000 // 4096 = 2
  смещение = 9000 % 4096 = 808
  страница 2 -> кадр 2
  физический = 2 * 4096 + 808 = 9000

Цена трансляции и TLB

Каждое обращение к памяти теперь требует ещё одного обращения — к таблице страниц. Это удвоило бы стоимость! Поэтому процессор кэширует недавние трансляции в специальном кэше — TLB (Translation Lookaside Buffer). Если нужная запись в TLB (TLB hit), трансляция почти бесплатна. Здесь опять выручает локальность: программы обращаются к небольшому набору страниц, и они быстро оседают в TLB.

Многоуровневые таблицы

Для 64-битных адресов плоская таблица страниц была бы гигантской. Поэтому таблицы делают многоуровневыми (таблица таблиц): хранятся только реально используемые части. Это экономит память на саму таблицу страниц.

Итог

  • Paging делит виртуальную память на страницы, а физическую — на кадры равного размера.
  • Таблица страниц сопоставляет страницам кадры; у каждого процесса она своя.
  • Адрес = номер страницы + смещение; физический = кадр × размер + смещение.
  • TLB кэширует трансляции, чтобы paging не удваивал стоимость доступа.
  • Многоуровневые таблицы экономят память на огромных адресных пространствах.
Проверьте себя
1. Чем страница отличается от кадра?
AСтраница больше кадра
BСтраница — блок виртуальной памяти, кадр — блок физической памяти того же размера
CКадр — это часть таблицы страниц
DЭто полные синонимы
2. Как из виртуального адреса 9000 при размере страницы 4096 получают номер страницы и смещение?
AСтраница = 9000 + 4096, смещение = 9000 − 4096
BСтраница = 9000 // 4096 = 2, смещение = 9000 % 4096 = 808
CСтраница = 4096, смещение = 9000
DСтраница = 9000, смещение = 0
3. Зачем процессору нужен TLB?
AЧтобы хранить весь код программы
BЧтобы кэшировать недавние трансляции адресов и не удваивать стоимость доступа
CЧтобы заменять оперативную память
DЧтобы увеличить размер страницы
Поддержать проект