1056 lines
53 KiB
Markdown
1056 lines
53 KiB
Markdown
# Глоссарий: термины операционных систем и ядра Elyz
|
||
|
||
> Эта статья — **минимальная база** для понимания всей документации ядра.
|
||
> Если вы не знаете, что такое MMU, TLB или Page Fault — начните здесь.
|
||
> Термины сгруппированы по темам: от фундаментальных к специфическим.
|
||
|
||
---
|
||
|
||
## 1. Фундаментальные концепции
|
||
|
||
### Процессор и память: базовая модель
|
||
|
||
Представьте, что процессор (CPU) — это человек, который выполняет инструкции
|
||
из блокнота (оперативная память, RAM). У него есть две проблемы:
|
||
|
||
1. **Скорость**: RAM медленная. Процессор в 100+ раз быстрее.
|
||
2. **Изоляция**: программы не должны видеть память друг друга.
|
||
|
||
Для решения этих проблем существуют MMU и TLB.
|
||
|
||
---
|
||
|
||
### MMU (Memory Management Unit)
|
||
|
||
**Что это?** MMU — это аппаратный блок внутри процессора, который
|
||
автоматически преобразует **виртуальные адреса** (которыми оперирует
|
||
программа) в **физические адреса** (реальная ячейка RAM).
|
||
|
||
**Зачем?** Без MMU каждая программа работала бы с реальными физическими
|
||
адресами. Программа А может случайно прочитать данные программы Б.
|
||
Одна программа может испортить данные другой. С MMU каждая программа
|
||
думает, что она одна в памяти, и работает со своими «виртуальными» адресами.
|
||
|
||
**Как это работает упрощённо:**
|
||
```
|
||
Программа: "запиши 42 по адресу 0x1000"
|
||
│
|
||
▼
|
||
MMU: "адрес 0x1000 у этой программы → реальный адрес 0x7F00"
|
||
│
|
||
▼
|
||
RAM: запись по адресу 0x7F00
|
||
```
|
||
|
||
MMU использует **таблицы страниц** (page tables) для этого преобразования.
|
||
В x86-64 это 4-уровневая структура: PML4 → PDPT → PD → PT (см. Paging).
|
||
|
||
**Где в коде:** `src/mem/paging.rs` — `PageTable`, которая представляет
|
||
одну таблицу (512 entry). `walk_to_p1_mut()` проходит все 4 уровня.
|
||
|
||
---
|
||
|
||
### TLB (Translation Lookaside Buffer)
|
||
|
||
**Что это?** TLB — это крошечный, но очень быстрый кэш внутри процессора,
|
||
который запоминает результаты трансляции адресов. Если MMU — это
|
||
переводчик, то TLB — его записная книжка.
|
||
|
||
**Зачем?** Каждый раз, когда программа читает или пишет память, MMU должен
|
||
пройти через 4 уровня таблиц страниц, чтобы перевести виртуальный адрес
|
||
в физический. Это **дорого** (4 обращения к RAM вместо 1). TLB запоминает
|
||
пары «виртуальный адрес → физический адрес», поэтому для часто
|
||
используемых страниц трансляция занимает 1 такт, а не 100+.
|
||
|
||
**Проблема:** Когда ядро меняет таблицы страниц (например, отображает
|
||
новую страницу), TLB содержит **устаревшие** записи. Их нужно сбросить
|
||
(flush). Для этого есть инструкции:
|
||
- `INVLPG [addr]` — сбросить одну запись для addr.
|
||
- Запись в CR3 — сбросить **весь** TLB.
|
||
- `INVPCID` — сбросить TLB для конкретного PCID (более тонко).
|
||
|
||
**Где в коде:** `src/mem/paging.rs` — `INVLPG` после map/unmap.
|
||
`src/mem/vmm.rs` — `tlb_flush_asid()`, `local_tlb_flush_asid()`,
|
||
`tlb_flush_all()`, `handle_tlb_shootdown_ipi()`.
|
||
|
||
---
|
||
|
||
### Page Fault (исключение #PF, вектор 14)
|
||
|
||
**Что это?** Это аппаратное исключение, которое происходит, когда
|
||
процессор не может преобразовать виртуальный адрес через MMU.
|
||
Причины: страница не отображена, нарушение прав, Copy-on-Write и т.д.
|
||
|
||
**Как работает:**
|
||
1. Программа пытается обратиться к адресу.
|
||
2. MMU проверяет таблицы — записи нет (или нет прав).
|
||
3. MMU сохраняет адрес сбоя в регистр **CR2**.
|
||
4. Процессор пушит error code и номер вектора (14) на стек.
|
||
5. Процессор переходит в обработчик (через IDT).
|
||
6. Обработчик решает: выделить страницу? COW? Или убить процесс?
|
||
|
||
**Зачем?** Page Fault — это **инструмент**, а не только ошибка:
|
||
- **Demand paging**: не выделяем страницу, пока она не понадобится.
|
||
Первый доступ → Page Fault → аллоцируем. Экономит память.
|
||
- **Copy-on-Write**: при fork страницы разделяются. При записи → fault →
|
||
копируем. Экономит время и память.
|
||
- **Lazy mapping**: регион зарезервирован (VMA есть), страниц нет.
|
||
Fault → alloc + map.
|
||
|
||
**Error code (что в нём):**
|
||
```
|
||
Bit 0 (P): 0 = страница не присутствует, 1 = защита (была, но нельзя)
|
||
Bit 1 (W): 0 = чтение, 1 = запись
|
||
Bit 2 (U): 0 = supervisor (ядро), 1 = user
|
||
Bit 3 (RSVD): нарушение зарезервированных битов
|
||
Bit 4 (ID): 0 = обычный fault, 1 = instruction fetch
|
||
```
|
||
|
||
**Где в коде:** `src/cpu/interrupts.rs` — `rust_page_fault_handler()`.
|
||
Читает CR2, проверяет error code, вызывает VMM.
|
||
|
||
---
|
||
|
||
### CR2 (Control Register 2)
|
||
|
||
**Что это?** Регистр процессора x86-64, который автоматически заполняется
|
||
**адресом, вызвавшим Page Fault**. При каждом #PF процессор сохраняет
|
||
виртуальный адрес, к которому пыталась обратиться программа.
|
||
|
||
**Зачем?** Обработчик Page Fault должен знать, какой адрес вызвал fault,
|
||
чтобы выделить правильную страницу.
|
||
|
||
**Как читается в коде:**
|
||
```rust
|
||
asm!("mov {}, cr2", out(reg) fault_addr);
|
||
```
|
||
`src/cpu/interrupts.rs:265-266`
|
||
|
||
> **Примечание:** Только чтение. Запись в CR2 возможна, но не нужна.
|
||
> CR2 — это read-only диагностический регистр.
|
||
|
||
---
|
||
|
||
### STI (Set Interrupt Flag) / CLI (Clear Interrupt Flag)
|
||
|
||
**Что это?** Инструкции процессора x86-64, которые управляют флагом
|
||
прерываний `IF` в регистре RFLAGS.
|
||
|
||
- `STI` (STart Interrupts) — **разрешает** маскируемые прерывания.
|
||
Процессор начинает реагировать на внешние прерывания (таймер, клавиатура).
|
||
- `CLI` (CLear Interrupts) — **запрещает** маскируемые прерывания.
|
||
Процессор игнорирует внешние прерывания.
|
||
|
||
**Зачем в ядре?**
|
||
- На этапе инициализации ядро работает с `CLI` (без прерываний), чтобы
|
||
никто не помешал последовательности настройки.
|
||
- Когда всё готово — `STI`, и ядро начинает обрабатывать прерывания.
|
||
|
||
**Где в коде:** `src/main.rs:150` — `asm!("sti")` после инициализации
|
||
IDT, VMM, PM Router. `hcf()` — `asm!("cli")`.
|
||
|
||
---
|
||
|
||
### HCF (Halt and Catch Fire) — остановка CPU
|
||
|
||
**Что это?** Состояние, в котором процессор перестаёт выполнять
|
||
программу, ожидая внешнего прерывания. Реализуется инструкцией `HLT`.
|
||
|
||
**Зачем?**
|
||
- Когда ядру больше нечего делать (нет процессов).
|
||
- При панике (panic): `CLI + HLT` — процессор остановлен навсегда.
|
||
- Ожидание в цикле: вместо busy-wait (сжигает 100% CPU) используем HLT.
|
||
|
||
**Где в коде:**
|
||
```rust
|
||
fn hcf() -> ! {
|
||
unsafe { asm!("cli"); } // запретить прерывания
|
||
loop {
|
||
unsafe { asm!("hlt"); } // остановить CPU
|
||
}
|
||
}
|
||
```
|
||
`src/main.rs:253-257`
|
||
|
||
---
|
||
|
||
## 2. Процессор и прерывания
|
||
|
||
### CPU Subsystem (подсистема процессора)
|
||
|
||
**Что это?** Совокупность кода ядра, который управляет процессором:
|
||
- IDT — таблица обработчиков прерываний.
|
||
- Interrupt Handlers — сами обработчики.
|
||
- LAPIC — контроллер прерываний.
|
||
- Системные регистры (CR0, CR2, CR3, CR4).
|
||
|
||
**Зачем?** Процессор — это не просто «вычислитель». Он генерирует
|
||
события (исключения, прерывания), и ядро должно на них реагировать.
|
||
Подсистема CPU — это прослойка между аппаратурой и остальным ядром.
|
||
|
||
**Где в коде:** `src/cpu/` — модули `idt.rs`, `interrupts.rs`, `lapic.rs`.
|
||
|
||
---
|
||
|
||
### IDT (Interrupt Descriptor Table)
|
||
|
||
**Что это?** Таблица в памяти, которая говорит процессору:
|
||
«Когда случится событие с номером N, перейди по адресу A_N».
|
||
Аналог — оглавление книги: «Если вам нужна глава 14, откройте стр. 100».
|
||
|
||
**Размер:** 256 entry × 16 байт = 4096 байт (ровно 1 страница).
|
||
|
||
**Каждая entry содержит:**
|
||
- Адрес обработчика (64 бита, разбит на 3 части из-за совместимости).
|
||
- Селектор сегмента кода (0x28 для ring 0 ядра).
|
||
- Тип вентиля: Interrupt Gate (0xE) или Trap Gate (0xF).
|
||
Разница: Interrupt Gate автоматически запрещает прерывания (CLI) при входе.
|
||
|
||
**Загрузка:** инструкция `LIDT` (Load IDT) с указателем на `IdtPtr`
|
||
(6 байт: 2 — размер, 4 — адрес).
|
||
|
||
**Где в коде:** `src/cpu/idt.rs` — `IdtEntry`, `InterruptDescriptorTable`,
|
||
`IdtPtr`. `src/cpu/interrupts.rs` — глобальная `static mut IDT`.
|
||
|
||
---
|
||
|
||
### Interrupt Handlers (обработчики прерываний)
|
||
|
||
**Что это?** Функции, которые вызываются процессором при прерываниях
|
||
и исключениях. Каждый обработчик — это ассемблерный «мостик» (stub),
|
||
который сохраняет контекст, вызывает Rust-функцию и возвращается.
|
||
|
||
**Анатомия обработчика:**
|
||
```asm
|
||
; Ассемблерный stub
|
||
vector_14_handler:
|
||
push rax ; сохранить регистры (контекст)
|
||
push rcx
|
||
push rdx
|
||
...
|
||
mov rdi, [rsp+15*8] ; error code как аргумент 1
|
||
call rust_handler ; вызов Rust функции
|
||
add rsp, 8 ; очистить error code
|
||
pop ... ; восстановить регистры
|
||
iretq ; возврат в прерванный код
|
||
```
|
||
|
||
**Почему два этапа (asm + Rust)?**
|
||
- Rust не умеет напрямую работать с сохранением/восстановлением регистров.
|
||
- Соглашение C (и Rust) требует определённого состояния стека.
|
||
- Ассемблер — единственный способ правильно войти и выйти.
|
||
|
||
**Типы обработчиков в Elyz:**
|
||
- **Early** (вектора 0-31): HALT с сообщением — при ранних сбоях до VMM.
|
||
- **Page Fault** (14): полноценный — COW, lazy, или паника.
|
||
- **Double Fault** (8): всегда паника.
|
||
- **GPF** (13): всегда паника.
|
||
- **TLB Shootdown** (0xFD): межпроцессорный TLB flush.
|
||
|
||
**Где в коде:** `src/cpu/interrupts.rs` — `exception_stub!()` макрос,
|
||
`early_stub` макрос, `rust_page_fault_handler`, `rust_gpf_handler`,
|
||
`rust_double_fault_handler`.
|
||
|
||
---
|
||
|
||
### LAPIC (Local Advanced Programmable Interrupt Controller)
|
||
|
||
**Что это?** Встроенный в каждое ядро процессора контроллер прерываний.
|
||
Он:
|
||
- Принимает прерывания от устройств (через системный контроллер APIC).
|
||
- Отправляет межпроцессорные прерывания (IPI).
|
||
- Имеет таймер (не используется в текущей версии).
|
||
- Обрабатывает EOI (End Of Interrupt).
|
||
|
||
**Зачем?** Без LAPIC каждое прерывание шло бы на единственный CPU.
|
||
С LAPIC можно распределять прерывания между ядрами.
|
||
|
||
**MMIO:** LAPIC программируется через Memory-Mapped I/O по физическому
|
||
адресу `0xFEE0_0000` + HHDM offset. Обращение через `write_volatile`.
|
||
|
||
**Регистры:**
|
||
- `0x0B0` — EOI: запись 0 подтверждает завершение обработки.
|
||
- `0x300` — ICR (Interrupt Command Register): для отправки IPI.
|
||
- `0x020` — IRR (In-Service Register): какие прерывания обрабатываются.
|
||
|
||
**Где в коде:** `src/cpu/lapic.rs`.
|
||
|
||
---
|
||
|
||
### IPI (Inter-Processor Interrupt)
|
||
|
||
**Что это?** Прерывание, которое одно ядро отправляет другому (или всем).
|
||
|
||
**Зачем?** Используется для **TLB shootdown**: когда одно ядро меняет
|
||
page tables, оно должно сообщить другим ядрам, чтобы они сбросили TLB.
|
||
|
||
**Как работает в Elyz:**
|
||
1. Ядро A хочет flush TLB для ASID 5.
|
||
2. Ядро A пишет в LAPIC ICR: вектор TLB_SHOOTDOWN (0xFD),
|
||
destination = All Excluding Self.
|
||
3. LAPIC отправляет IPI всем ядрам, кроме A.
|
||
4. Каждое ядро B получает прерывание → вызывает
|
||
`rust_tlb_shootdown_handler()` → `local_tlb_flush_asid(5)`.
|
||
5. Каждое ядро B пишет ACK и отправляет EOI.
|
||
6. Ядро A ждёт ACK от всех ядер, затем продолжает.
|
||
|
||
**Где в коде:** `src/cpu/lapic.rs` — `broadcast_ipi_exclude_self()`.
|
||
`src/mem/vmm.rs` — `tlb_flush_asid()`, `handle_tlb_shootdown_ipi()`.
|
||
|
||
---
|
||
|
||
## 3. Управление памятью: фундамент
|
||
|
||
### Физическая vs Виртуальная память
|
||
|
||
**Физическая память** — это реальные микросхемы RAM. У неё есть адреса
|
||
начиная с 0 и до максимума (например, 16 GiB).
|
||
|
||
**Виртуальная память** — это иллюзия, которую создаёт MMU для каждой
|
||
программы. Программа видит «свой» адрес с 0 до 2^48 (256 TiB), но
|
||
за каждым виртуальным адресом стоит какой-то физический.
|
||
|
||
```
|
||
Виртуальные адреса программы A:
|
||
0x0000 ─────────────────► физический 0x1000 (через MMU)
|
||
0x1000 ─────────────────► физический 0x5000
|
||
0xFFFF_8000_0000_0000 ─► физический 0x0000 (HHDM — отображение ядра)
|
||
|
||
Виртуальные адреса программы B:
|
||
0x0000 ─────────────────► физический 0x3000 (другой!)
|
||
0x1000 ─────────────────► физический 0x7000
|
||
```
|
||
|
||
**Преимущества виртуальной памяти:**
|
||
1. **Изоляция**: программа A не видит программу B.
|
||
2. **Безопасность**: ядро живёт в higher half, пользователь не может
|
||
к нему обратиться (из-за флага USER/SUPERVISOR в PTE).
|
||
3. **Эффективность**: можно отобразить только часть программы, остальное
|
||
подкачивать по требованию (demand paging).
|
||
4. **Упрощение**: каждая программа думает, что у неё вся память с 0.
|
||
|
||
---
|
||
|
||
### Paging / 4-уровневые таблицы (Page Tables)
|
||
|
||
**Что это?** Структура данных, которую использует MMU для трансляции
|
||
адресов. В x86-64 это 4 уровня:
|
||
|
||
```
|
||
Виртуальный адрес (64 бита, используются 48):
|
||
┌─────────┬─────────┬─────────┬─────────┬─────────┐
|
||
│ PML4 │ PDPT │ PD │ PT │ offset │
|
||
│ 9 бит │ 9 бит │ 9 бит │ 9 бит │ 12 бит │
|
||
└────┬────┴────┬────┴────┬────┴────┬────┴─────────┘
|
||
│ │ │ │
|
||
▼ ▼ ▼ ▼
|
||
PML4 ──► PDPT ──► PD ──► PT ──► Фрейм (4 KiB)
|
||
(P4) (P3) (P2) (P1)
|
||
```
|
||
|
||
- **Каждая таблица**: 512 entry × 8 байт = 4096 байт (1 страница).
|
||
- **9 бит на уровень**: 512 вариантов, 2^9 = 512.
|
||
- **12 бит offset**: 4096 байт внутри страницы.
|
||
- **Итого**: 48 бит = 256 TiB адресуемого пространства.
|
||
|
||
**Entry (PTE, Page Table Entry):**
|
||
```
|
||
Bit 63: NX (No Exec) — защита от исполнения
|
||
Bits 51:12: Физический адрес следующей таблицы (или фрейма)
|
||
Bits 11:0: Флаги (PRESENT, WRITABLE, USER, ACCESSED, DIRTY...)
|
||
```
|
||
|
||
**Huge Pages:**
|
||
- 2 MiB: если P2 entry имеет PS (Page Size) = 1.
|
||
- 1 GiB: если P3 entry имеет PS = 1.
|
||
- Используются для отображения больших областей (HHDM).
|
||
|
||
**Где в коде:** `src/mem/paging.rs` — `PageTable`, `PageTableFlags`,
|
||
`walk_to_p1_mut()`, `translate()`, `map_page()`, `unmap_page()`.
|
||
|
||
---
|
||
|
||
### PML4 (Page Map Level 4)
|
||
|
||
**Что это?** Самый верхний (корневой) уровень таблиц страниц.
|
||
Физический адрес PML4 хранится в регистре **CR3**.
|
||
|
||
**Зачем?** Каждое адресное пространство имеет свой PML4. Когда
|
||
процессор переключается между задачами, он загружает новый CR3,
|
||
и MMU начинает транслировать адреса по-новому.
|
||
|
||
**PCID (Process Context Identifier):** В младшие 12 бит CR3 можно
|
||
записать номер контекста (ASID). Это позволяет TLB хранить записи
|
||
для нескольких процессов одновременно, не сбрасывая TLB при каждом
|
||
переключении. Elyz использует PCID для kernel (ASID=0) и процессов
|
||
(ASID 1-4094).
|
||
|
||
**Где в коде:** `src/mem/vmm.rs:708-716` — `AddressSpace::activate()`.
|
||
|
||
---
|
||
|
||
### HHDM (Higher-Half Direct Map)
|
||
|
||
**Что это?** Область виртуальной памяти (обычно начинается с
|
||
`0xFFFF_8000_0000_0000`), где **вся физическая память отображена 1:1
|
||
с фиксированным смещением**.
|
||
|
||
**Формула:** `virt_addr = phys_addr + HHDM_OFFSET`
|
||
|
||
**Зачем?** Когда ядру нужно прочитать или записать физический фрейм
|
||
(например, обнулить страницу, прочитать Page Table), оно не может
|
||
использовать физический адрес напрямую — процессор работает только
|
||
с виртуальными адресами. HHDM даёт простой способ получить доступ
|
||
к любому физическому адресу: просто добавь HHDM_OFFSET.
|
||
|
||
**Где используется:**
|
||
- `PhysAddr::to_virt()` → `VirtAddr(self.0 + get_hhdm())`
|
||
- `PageTable` доступ: `PhysAddr(pte & PTE_ADDR_MASK).to_virt().as_mut_ptr()`
|
||
- `BitmapPMM`: обнуление фреймов.
|
||
- `BuddyAllocator`: intrusive list (next/prev на странице).
|
||
|
||
**Где в коде:** `src/mem/address.rs` — `HHDM_OFFSET`, `init_hhdm()`,
|
||
`get_hhdm()`, `PhysAddr::to_virt()`.
|
||
|
||
---
|
||
|
||
### Address Space (адресное пространство)
|
||
|
||
**Что это?** Полное описание всех виртуальных адресов, доступных
|
||
программе (или ядру). Включает:
|
||
- **PML4** (CR3) — аппаратная структура для трансляции.
|
||
- **VMA list** — список регионов (программное описание, что где отображено).
|
||
|
||
**В Elyz:**
|
||
```rust
|
||
pub struct AddressSpace {
|
||
pub asid: u16, // PCID для TLB
|
||
pub pml4_phys: PhysAddr, // адрес корневой таблицы в CR3
|
||
regions: Vec<VmaRegion>, // отсортированный список VMA
|
||
}
|
||
```
|
||
|
||
**Зачем?** Переключение между процессами = переключение между
|
||
AddressSpace → загрузка нового CR3 + TLB flush.
|
||
|
||
**Где в коде:** `src/mem/vmm.rs` — `AddressSpace` (методы: `new()`,
|
||
`from_active()`, `map_region()`, `unmap_region()`, `handle_fault()`,
|
||
`clone_for_fork()`, `activate()`, `revoke_by_token()`).
|
||
|
||
---
|
||
|
||
### VMA (Virtual Memory Area) и VMA-деревья
|
||
|
||
**Что это?** Регион непрерывного виртуального адресного пространства
|
||
с едиными правами доступа. Аналог — «отрезок» памяти.
|
||
|
||
**Пример:** программа просит 1 MiB памяти. Ядро создаёт VMA:
|
||
```
|
||
VmaRegion {
|
||
virt_start: 0x7F00_0000,
|
||
virt_end: 0x7F10_0000, // 1 MiB позже
|
||
flags: READ | WRITE | USER,
|
||
cap_token: 0x1234,
|
||
backing: Anonymous(Vec<Option<PhysAddr>>),
|
||
}
|
||
```
|
||
|
||
**Типы VMA (backing):**
|
||
- **Anonymous**: own-фреймы, выделенные из PMM. При unmap → free.
|
||
- **Physical**: непрерывный физический диапазон (например, framebuffer).
|
||
- **Shared**: zero-copy mapping чужих фреймов (capability-based).
|
||
|
||
**VMA-деревья:** VMA хранятся в сортированном списке (Vec), по
|
||
virt_start. Поиск: binary search (`partition_point`). В будущем
|
||
может быть заменено на дерево для O(log N) вставки/удаления.
|
||
|
||
**Где в коде:** `src/mem/vmm.rs` — `VmaRegion`, `VmaBacking`.
|
||
Управление: `find_idx()`, `insert_sorted()`, `map_region()`.
|
||
|
||
---
|
||
|
||
### KERNEL SPACE (пространство ядра)
|
||
|
||
**Что это?** Глобальное адресное пространство ядра. В Elyz:
|
||
```rust
|
||
pub static KERNEL_SPACE: Locked<Option<AddressSpace>> = Locked::new(None);
|
||
```
|
||
|
||
**Особенности:**
|
||
- Использует ASID = 0 (PCID 0, kernel).
|
||
- Живёт постоянно (не освобождается).
|
||
- Page Fault handler работает в контексте KERNEL_SPACE.
|
||
- Содержит:
|
||
- HHDM (вся физическая память отображена).
|
||
- Код ядра (текст).
|
||
- Кучу (heap, slab allocator).
|
||
- Page tables ядра.
|
||
|
||
**Зачем отдельно?** Чтобы ядро могло обрабатывать Page Fault'ы,
|
||
оно должно знать, какие VMA у его собственного AddressSpace.
|
||
Kernel space — это AddressSpace ядра для себя.
|
||
|
||
**Где в коде:** `src/mem/vmm.rs:809`. Инициализация `init_kernel_space()`.
|
||
|
||
---
|
||
|
||
## 4. Менеджеры физической памяти
|
||
|
||
### PMM (Physical Memory Manager)
|
||
|
||
**Что это?** Компонент ядра, который отслеживает, какие физические
|
||
фреймы (страницы по 4 KiB) свободны, а какие заняты.
|
||
|
||
**Основные операции:**
|
||
- `alloc_frame()` — «дай мне один свободный фрейм».
|
||
- `free_frame(addr)` — «этот фрейм больше не нужен».
|
||
- `alloc_contiguous(n)` — «дай n последовательных фреймов» (для DMA).
|
||
|
||
**Зачем?** Без PMM каждое выделение памяти было бы вручную. PMM
|
||
автоматизирует и централизует этот процесс.
|
||
|
||
---
|
||
|
||
### BitmapPMM (Bitmap Physical Memory Manager)
|
||
|
||
**Что это?** Реализация PMM через битовую карту (bitmap). Каждый бит
|
||
в bitmap представляет один фрейм (4 KiB):
|
||
- `1` = фрейм занят.
|
||
- `0` = фрейм свободен.
|
||
|
||
**Сколько нужно памяти для bitmap:**
|
||
Если у нас 16 GiB RAM → 16 GiB / 4 KiB = 4 млн фреймов.
|
||
4 млн бит = 512 KiB для bitmat.
|
||
|
||
**Трёхуровневое ускорение:**
|
||
|
||
```
|
||
L1 (уровень 1): массив u64
|
||
Каждый бит = одно слово из L0
|
||
1 = в этом слове есть хотя бы один свободный фрейм
|
||
|
||
L0 (уровень 0): массив u8 (основной bitmap)
|
||
Каждый байт = 8 фреймов, каждый бит = 1 фрейм
|
||
|
||
Ref-counts: массив u16
|
||
Счётчик ссылок на каждый фрейм (для COW: несколько
|
||
виртуальных отображений могут ссылаться на один фрейм)
|
||
```
|
||
|
||
**Аллокация (alloc_frame):**
|
||
1. Ищем L1-слово с ненулевым битом (есть свободные фреймы).
|
||
2. Вычисляем, какое L0-слово соответствует этому биту.
|
||
3. Если L0-слово == !0 (все заняты) — очищаем бит L1, ищем дальше.
|
||
4. `(!word).trailing_zeros()` — находим свободный бит в L0-слове.
|
||
5. Возвращаем физический адрес: `page_idx * 4096`.
|
||
|
||
**Почему не отдельный L0-слово?** L1 позволяет пропускать целые
|
||
группы по 64 слова (4096 фреймов), где нет ни одного свободного.
|
||
|
||
**Ref-counts:**
|
||
- `free_frame()` уменьшает счётчик. Если стал 0 — очищает бит.
|
||
- `inc_ref_frame()` увеличивает счётчик (для COW).
|
||
- `lock_frame()` — принудительное занятие (для метаданных PMM).
|
||
|
||
**Где в коде:** `src/mem/pmm.rs` — `BitmapPMM`.
|
||
|
||
---
|
||
|
||
### PM Actor (Physical Memory Actor)
|
||
|
||
**Что это?** Распределённый менеджер памяти. В отличие от глобального
|
||
BitmapPMM, у каждого PMActor есть свой **диапазон** физической памяти,
|
||
которым он управляет самостоятельно.
|
||
|
||
**Зачем?**
|
||
1. **Изоляция**: актор A не может истощить память актора B.
|
||
2. **Масштабирование**: каждый актор может работать на своём ядре.
|
||
3. **Предсказуемость**: каждый актор знает свой лимит.
|
||
4. **Модель акторов**: запросы приходят асинхронно через очередь.
|
||
|
||
**Как работает:**
|
||
1. PMActor владеет диапазоном: `managed_range = (start_phys, end_phys)`.
|
||
2. Внутри использует `BuddyAllocator` для своего диапазона.
|
||
3. Получает запросы через lock-free MPSC очередь (PMActorQueue).
|
||
4. Обрабатывает (`process_messages()`): Alloc, Free, Carve.
|
||
5. Ответы отправляет через PMRouter.
|
||
|
||
**Где в коде:** `src/mem/pm_manages.rs` — `PMActor`.
|
||
|
||
---
|
||
|
||
### PM Router (Physical Memory Router)
|
||
|
||
**Что это?** Центральный коммутатор для асинхронной маршрутизации
|
||
ответов от PMActor'ов к запросившим потокам.
|
||
|
||
**Как работает:**
|
||
- 65536 предварительно выделенных **каналов**.
|
||
- `alloc_channel()`: CAS на стеке свободных каналов (lock-free).
|
||
- `route_responses()`: запись результата в канал.
|
||
- `wait_for_response()`: HLT-ожидание + чтение результата.
|
||
|
||
| Операция | Что происходит |
|
||
|----------|---------------|
|
||
| Выделить канал | `alloc_channel()` → получаем u16 ID |
|
||
| Отправить запрос | `actor.submit_request(req)` с channel_id |
|
||
| Актёр обрабатывает | `process_messages()` → buddy.alloc() |
|
||
| Отправить ответ | `router.route_responses([response])` |
|
||
| Получить ответ | `router.wait_for_response(ch)` |
|
||
|
||
**Где в коде:** `src/mem/pm_router.rs` — `PMRouter`, `Channel`.
|
||
|
||
---
|
||
|
||
### Buddy Allocator (аллокатор пар)
|
||
|
||
**Что это?** Алгоритм управления памятью, который делит память на
|
||
блоки размером `2^order` страниц. Название от английского «buddy»
|
||
(товарищ, партнёр) — потому что блоки объединяются со своим
|
||
«напарником» (buddy) при освобождении.
|
||
|
||
**Как работает:**
|
||
|
||
```
|
||
Изначально: один блок 16 страниц (order 4)
|
||
|
||
alloc(1 страница, order 0):
|
||
16 → [8][8] → [4][4][8] → [2][2][4][8] → [1][1][2][4][8]
|
||
Выдан порядок 0, блок в начале.
|
||
|
||
free(первый блок, order 0):
|
||
[свободный][свободный][2][4][8]
|
||
→ объединяем buddy → [2][2][4][8] → [4][4][8] → [8][8] → [16]
|
||
Восстановлен полный блок order 4.
|
||
```
|
||
|
||
**Формула buddy:** `buddy_idx = block_idx XOR (1 << order)`
|
||
|
||
**Почему эффективно?**
|
||
1. **Нет фрагментации**: блоки объединяются обратно.
|
||
2. **Быстро**: alloc и free — O(log N) в худшем, O(1) в среднем.
|
||
3. **Intrusive list**: next/prev хранятся прямо на страницах.
|
||
|
||
**Intrusive List в Buddy:**
|
||
|
||
Вместо отдельного массива указателей, свободные блоки хранят
|
||
указатели next/prev прямо на своей физической странице:
|
||
|
||
```
|
||
Физическая страница 0x4000 (свободна):
|
||
┌──────────────┬──────────────┬────────────────────┐
|
||
│ next = 0x5000│ prev = NULL │ (остаток страницы) │
|
||
└──────────────┴──────────────┴────────────────────┘
|
||
```
|
||
|
||
Это требует только HHDM-доступа к странице, никаких дополнительных
|
||
аллокаций для структур данных.
|
||
|
||
**Где в коде:** `src/mem/buddy.rs` — `BuddyAllocator`.
|
||
|
||
---
|
||
|
||
### Intrusive Linked List
|
||
|
||
**Что это?** Связный список, где указатели next/prev хранятся **внутри**
|
||
самих элементов, а не в отдельной структуре.
|
||
|
||
**Сравнение:**
|
||
|
||
```
|
||
Обычный список:
|
||
Node { data, next, prev } — Node выделяется отдельно
|
||
list: {... Node A ... Node B ...} — узлы в куче
|
||
|
||
Интрузивный список:
|
||
Page { ... next, prev, ...} — поле next/prev прямо на странице
|
||
list: [Page A] ↔ [Page B] ↔ [Page C] — страницы сами себе узлы
|
||
```
|
||
|
||
**Преимущество:** никаких дополнительных аллокаций. Список — это
|
||
сами управляемые объекты (страницы).
|
||
|
||
**Использование в Elyz:**
|
||
- `BuddyAllocator`: free_heads[order] — intrusive list свободных блоков.
|
||
- `flist_push`, `flist_remove`, `flist_pop` управляют списком.
|
||
|
||
**Где в коде:** `src/mem/buddy.rs:45-82`.
|
||
|
||
---
|
||
|
||
### Slab Allocator (плитный аллокатор)
|
||
|
||
**Что это?** Аллокатор для **мелких объектов** (8 — 2048 байт) —
|
||
куча ядра. Работает так:
|
||
|
||
1. Есть фиксированные размеры блоков: 8, 16, 32, 64, 128, 256, 512, 1024, 2048.
|
||
2. Для каждого размера — список свободных блоков (slab list).
|
||
3. Аллокация: берём блок из списка.
|
||
4. Деаллокация: возвращаем блок в список.
|
||
|
||
**Пример:** `alloc(37 байт)`:
|
||
- `list_index(37)` → размер 64 (ближайший ≥ 37).
|
||
- Если есть блок 64 в list_heads — отдаём.
|
||
- Если нет — берём новый блок (bump alloc) размером 64.
|
||
|
||
**Для больших блоков (> 2048):** используется отдельный freelist.
|
||
Если freelist пуст — bump alloc (последовательная раздача из heap).
|
||
|
||
**Bump alloc:** просто двигаем указатель `next_bump` вперёд.
|
||
Быстро, но не переиспользует память (только slab возвращает).
|
||
|
||
**Отличие от Buddy Allocator:**
|
||
| Характеристика | Slab | Buddy |
|
||
|---------------|------|-------|
|
||
| Размер блоков | Фиксированный (8-2048) | Степени двойки (1,2,4,8… страниц) |
|
||
| Для чего | Ядерная куча (Vec, Box) | Физическая память (PMActor) |
|
||
| Где память | Виртуальная (heap 0xFFFF_9000...) | Физическая |
|
||
| Переиспользование | Да (slab list) | Да (coalesce) |
|
||
|
||
**Где в коде:** `src/mem/allocator.rs` — `SlabAllocator`, `ALLOCATOR` (global_allocator).
|
||
|
||
---
|
||
|
||
### Heap Init (инициализация кучи)
|
||
|
||
**Что это?** Выделение и настройка области памяти для кучи ядра.
|
||
|
||
**В Elyz:**
|
||
```rust
|
||
let heap_start = 0xFFFF_9000_0000_0000; // виртуальный адрес
|
||
let heap_size = 8 * 1024 * 1024; // 8 MiB
|
||
// Сначала map'им все страницы кучи (alloc_frame для каждой)
|
||
for i in (0..heap_size).step_by(4096) {
|
||
let frame = pmm::alloc_frame().expect("OOM");
|
||
p4.map_page(VirtAddr(heap_start + i), frame, flags);
|
||
}
|
||
// Затем инициализируем аллокатор
|
||
allocator::ALLOCATOR.lock().init(heap_start, heap_size);
|
||
```
|
||
|
||
**Почему в 2 шага?**
|
||
1. Сначала подготавливаем page tables: куча должна быть отображена
|
||
физически, иначе при первом `alloc` будет Page Fault.
|
||
2. Затем говорим SlabAllocator'у: «вот твоя область, раздавай».
|
||
|
||
---
|
||
|
||
## 5. Виртуальная память: продвинутые концепции
|
||
|
||
### VMM (Virtual Memory Manager)
|
||
|
||
**Что это?** Компонент ядра, который управляет виртуальными адресными
|
||
пространствами (AddressSpace). Его работа:
|
||
- Создание/уничтожение AddressSpace.
|
||
- Отображение (map) и удаление (unmap) регионов.
|
||
- Обработка Page Fault (COW, lazy).
|
||
- Fork с Copy-on-Write.
|
||
- TLB shootdown при SMP.
|
||
|
||
**Где в коде:** `src/mem/vmm.rs` — `AddressSpace`, `VmaRegion`, `VmaBacking`.
|
||
|
||
---
|
||
|
||
### Demand Paging (ленивое отображение, lazy mapping)
|
||
|
||
**Что это?** Стратегия, при которой физическая страница выделяется
|
||
только когда программа действительно к ней обращается, а не когда
|
||
она запрашивает память.
|
||
|
||
**Как работает:**
|
||
1. Программа: `malloc(1 MiB)` или ядро: `map_region(LAZY)`.
|
||
2. Ядро создаёт VMA с флагом LAZY.
|
||
3. VMA есть, но Page Table entries = NOT PRESENT.
|
||
4. При первом чтении/записи → **Page Fault**.
|
||
5. Обработчик: alloc_frame → zero → map_page → return.
|
||
6. Программа продолжает, даже не зная, что был fault.
|
||
|
||
**Зачем?**
|
||
- **Экономия памяти**: программа может зарезервировать 1 GiB,
|
||
но использовать 1 MiB. Остальное не занимает физическую память.
|
||
- **Скорость запуска**: не нужно allocating всё сразу.
|
||
|
||
**Где в коде:** `src/mem/vmm.rs` — `VmaFlags::LAZY`, `handle_fault()`
|
||
проверяет LAZY и выделяет страницу.
|
||
|
||
---
|
||
|
||
### Copy-on-Write (COW, копирование при записи)
|
||
|
||
**Что это?** Техника, при которой ресурс (страница) разделяется между
|
||
несколькими потребителями, пока один из них не попытается изменить
|
||
(записать) его. Тогда делается копия.
|
||
|
||
**Как работает (на примере fork):**
|
||
1. Родитель и потомок имеют одинаковые страницы.
|
||
2. Вместо копирования всех страниц (дорого!), ядро отображает
|
||
**те же** фреймы в AddressSpace потомка.
|
||
3. Все PTE помечаются флагом **COW** и WRITABLE снимается.
|
||
4. Когда кто-то пишет → Page Fault.
|
||
5. Обработчик: alloc_frame → copy(old → new) → map(new) → free(old).
|
||
6. Каждый теперь имеет свою копию.
|
||
|
||
**Зачем?**
|
||
- **fork() становится O(1)**: не нужно копировать всё адресное
|
||
пространство. Только page tables.
|
||
- **Экономия**: если потомок только читает (или exec'ится сразу),
|
||
копирования не происходит вообще.
|
||
|
||
**COW-bit:** Elyz использует бит 9 PTE как кастомный флаг COW
|
||
(аппаратура его игнорирует).
|
||
|
||
**Где в коде:** `src/mem/vmm.rs`:
|
||
- `clone_for_fork()`: устанавливает COW на родительские страницы.
|
||
- `handle_fault()`: при write + COW → copy.
|
||
- `src/mem/paging.rs` — `PageTableFlags::COW`.
|
||
|
||
---
|
||
|
||
### INVPCID (Invalidate PCID)
|
||
|
||
**Что это?** Инструкция x86-64 для выборочного сброса TLB по PCID
|
||
(Process Context Identifier). Более тонкая, чем полная перезапись CR3.
|
||
|
||
**Типы INVPCID:**
|
||
- **Тип 0**: сброс одной страницы в конкретном контексте.
|
||
- **Тип 1**: сброс всех записей для одного контекста (используется в Elyz).
|
||
- **Тип 2**: сброс всех записей для всех контекстов.
|
||
- **Тип 3**: сброс всех записей для всех контекстов, кроме текущего.
|
||
|
||
**Зачем?** На старых CPU (до Broadwell) INVPCID может отсутствовать.
|
||
Elyz проверяет CPUID.07H:EBX[10] и падает на CR3 reload если нет.
|
||
|
||
**Где в коде:** `src/mem/vmm.rs` — `INVPCID_SUPPORTED`, `init_cpu_features()`,
|
||
`local_tlb_flush_asid()`.
|
||
|
||
---
|
||
|
||
### Lazy Region (ленивый регион)
|
||
|
||
**Что это?** VMA с флагом LAZY. Страницы не выделены заранее,
|
||
аллоцируются по требованию при Page Fault.
|
||
|
||
**Отличие от обычного региона:**
|
||
- Обычный: alloc сразу выделяет все фреймы (eager).
|
||
- Ленивый: alloc создаёт Vec<None>. Фреймы при fault.
|
||
|
||
**Где используется:** большие выделения памяти, стеки потоков,
|
||
mapped файлы.
|
||
|
||
---
|
||
|
||
## 6. Система Capability
|
||
|
||
### Capability (возможность, дескриптор доступа)
|
||
|
||
**Что это?** Неподделываемый токен, дающий право выполнить операцию
|
||
над объектом. Аналог: ключ от номера в гостинице.
|
||
|
||
**В традиционных ОС (Linux):**
|
||
- У вас есть UID (0 = root, 1000 = user).
|
||
- Система проверяет: «есть ли у user 1000 права на этот файл?»
|
||
- Это называется **Access Control List (ACL)**.
|
||
|
||
**В capability-системе:**
|
||
- У вас есть capability (структура в памяти ядра).
|
||
- Capability говорит: «я даю право ЧИТАТЬ эту страницу».
|
||
- Если у вас нет capability — у вас нет доступа. Точка.
|
||
|
||
**Преимущества:**
|
||
- **Принцип минимальных привилегий**: вы можете передать только READ,
|
||
без WRITE. С ACL это сложнее.
|
||
- **Иерархия**: из capability можно сделать «дочерний» с урезанными правами.
|
||
- **Отзыв**: capability можно отозвать (revoke), включая всех «детей».
|
||
|
||
**Capability в Elyz:**
|
||
```rust
|
||
pub struct Capability {
|
||
pub object: CapObject, // Memory / CNode / PMActor
|
||
pub rights: CapRights, // READ / WRITE / EXECUTE / GRANT
|
||
pub relation: Relation, // Strong / Borrow / Transfer
|
||
pub token_sig: u64, // уникальный ID
|
||
}
|
||
```
|
||
|
||
**Где в коде:** `src/cap/descriptor.rs`.
|
||
|
||
---
|
||
|
||
### Mint (создание capability-потомка)
|
||
|
||
**Что это?** Операция создания дочернего capability с урезанными правами.
|
||
|
||
**Аналогия:** у вас есть ключ-карта от номера (можете открыть дверь,
|
||
минибар, сейф). Вы делаете копию, которая открывает только дверь.
|
||
|
||
**Правила:**
|
||
- `final_rights = parent.rights & requested_rights`
|
||
- Нельзя расширить права (только сузить).
|
||
- Нужен флаг GRANT у родителя.
|
||
|
||
**Пример:**
|
||
```rust
|
||
// Есть capability с R|W|X|G на страницу
|
||
// Создаём потомка только с R|W
|
||
cnode.mint(src=0, dest=10, Relation::Borrow, rights=R|W)?;
|
||
```
|
||
|
||
**Где в коде:** `src/cap/mod.rs:40-80`.
|
||
|
||
---
|
||
|
||
### CNode (Capability Node)
|
||
|
||
**Что это?** Таблица (массив) слотов, каждый слот хранит один capability.
|
||
Аналог: таблица открытых файлов в ядре Linux (fd table).
|
||
|
||
```rust
|
||
pub struct CNode {
|
||
pub slots: Vec<Locked<CNodeSlot>>,
|
||
}
|
||
pub struct CNodeSlot {
|
||
pub cap: Capability,
|
||
pub parent_idx: Option<usize>,
|
||
}
|
||
```
|
||
|
||
**Операции:**
|
||
- `insert(slot, cap)` — поместить в слот.
|
||
- `mint(src, dest, relation, rights)` — создать потомка.
|
||
- `revoke(slot)` — отозвать (каскадно).
|
||
- `get_cap(slot)` — прочитать.
|
||
|
||
**Отзыв (revoke):**
|
||
1. Найти всех потомков (parent_idx == slot).
|
||
2. Рекурсивно revoke каждого потомка.
|
||
3. Уничтожить сам capability.
|
||
4. Отправить token_sig в MMU_REVOCATION_QUEUE.
|
||
|
||
**Lock Ranking:** при mint захватываются две блокировки. Чтобы избежать
|
||
дедлока — захватываем всегда в порядке возрастания индекса.
|
||
|
||
**Где в коде:** `src/cap/mod.rs`.
|
||
|
||
---
|
||
|
||
## 7. Конкурентность и синхронизация
|
||
|
||
### Lock-free (без блокировок)
|
||
|
||
**Что это?** Техника синхронизации, где несколько потоков могут
|
||
читать/писать общие данные без мьютексов. Используются атомарные
|
||
операции: CAS (Compare-And-Swap), fetch_add, store/load.
|
||
|
||
**CAS (Compare-And-Swap) в Rust:**
|
||
```rust
|
||
// Атомарно: если *addr == old, то *addr = new, вернуть Ok
|
||
// Иначе: вернуть Err(actual_value)
|
||
match atom.compare_exchange_weak(old, new, Ordering::AcqRel, Ordering::Relaxed) {
|
||
Ok(_) => {}, // успешно обновили
|
||
Err(actual) => {}, // кто-то другой изменил
|
||
}
|
||
```
|
||
|
||
**Где используется в Elyz:**
|
||
- `PMActorQueue::send()` — несколько продюсеров конкурируют за слот.
|
||
- `PM Router::alloc_channel()` — CAS на free_head.
|
||
- `RevocationQueue::push()` — несколько CNode могут одновременно пушить.
|
||
- `Locked<T>::lock()` — CAS на AtomicBool.
|
||
|
||
**Преимущества:**
|
||
- Нет блокировок → нет deadlock'ов.
|
||
- Нет переключения контекста при ожидании.
|
||
- Работает в обработчиках прерываний.
|
||
|
||
---
|
||
|
||
### MPSC (Multiple Producer, Single Consumer)
|
||
|
||
**Что это?** Паттерн очереди, где **много** потоков могут писать
|
||
(producer), но только **один** поток читает (consumer).
|
||
|
||
**Где в Elyz:**
|
||
- `PMActorQueue`: produce = любой поток/ядро, consume = только PMActor.
|
||
- `RevocationQueue`: produce = любой CNode revoke, consume = VMM (page fault).
|
||
|
||
**Реализация:**
|
||
- Producer: CAS на tail (конкуренция).
|
||
- Consumer: просто читает head (без CAS, Single Consumer гарантия).
|
||
|
||
**Почему Single Consumer?**
|
||
- Проще: не нужна блокировка на чтение.
|
||
- Быстрее: pop() без атомики.
|
||
- Соответствует модели: один актор потребляет свои сообщения.
|
||
|
||
---
|
||
|
||
## 8. Вопрос архитектуры
|
||
|
||
### Нужно ли переносить LAPIC и serial в userspace для микроядра?
|
||
|
||
**Краткий ответ:**
|
||
- **Serial (COM1)**: да, должен быть в userspace. Драйвер UART
|
||
не требует привилегированного доступа, только порты I/O.
|
||
В микроядре он был бы userspace-процессом, получающим символы
|
||
через IPC и отправляющим их в порт.
|
||
- **LAPIC**: частично — да, частично — нет.
|
||
|
||
**Подробнее про LAPIC:**
|
||
|
||
| Функция LAPIC | Микроядро | Почему |
|
||
|---------------|-----------|--------|
|
||
| EOI (End Of Interrupt) | **Ядро** | EOI — это acknowledging прерывания. Если отдать userspace, процесс может «забыть» отправить EOI, и прерывания зависнут. |
|
||
| ICR (IPI отправка) | **Ядро** | IPI может сбить TLB других ядер. Только ядро должно контролировать, когда и кому отправлять IPI. |
|
||
| APIC ID | Userspace | Чтение ID — безопасно, полезно для affinity. |
|
||
| Timer | Userspace | Таймер LAPIC может быть отдан userspace-процессу (например, драйверу таймера). |
|
||
| LVT (остальное) | **Ядро** | LVT настраивает векторы прерываний. Нельзя отдавать userspace. |
|
||
|
||
**Вывод: LAPIC не может быть полностью в userspace**, но может быть
|
||
split-драйвером: часть в ядре (безопасность), часть в userspace.
|
||
|
||
**Текущее состояние Elyz (гибридное/монолитное):**
|
||
На данном этапе Elyz не является микроядром. И LAPIC, и serial
|
||
находятся в ядре. Это нормально для early стадии. При переходе
|
||
к микроядру:
|
||
1. Serial → userspace процесс (драйвер UART).
|
||
2. LAPIC → split: EOI/ICR в ядре, timer в userspace.
|
||
|
||
**Где в коде:** `src/cpu/lapic.rs`, `src/debug/serial.rs`.
|
||
|
||
---
|
||
|
||
## 9. Краткий справочник: для чего всё?
|
||
|
||
| Термин | Для чего |
|
||
|--------|----------|
|
||
| **MMU** | Преобразует виртуальные адреса в физические. Даёт изоляцию процессов. |
|
||
| **TLB** | Кэш трансляции адресов. Ускоряет работу MMU в ~100x. |
|
||
| **Page Fault** | Ловит момент, когда страницы нет в памяти. Используется для demand paging и COW. |
|
||
| **IDT** | Таблица, которая говорит CPU, какой код вызвать при исключении. |
|
||
| **LAPIC** | Контроллер прерываний. Нужен для IPI (TLB shootdown). |
|
||
| **HHDM** | Отображение всей физической памяти в higher half. Упрощает доступ к фреймам. |
|
||
| **PMM** | Кто даёт и забирает физические фреймы. |
|
||
| **BitmapPMM** | Реализация PMM через битовую карту. Быстрый поиск свободных фреймов. |
|
||
| **BuddyAllocator** | Аллокатор физической памяти. Объединяет блоки при освобождении — нет фрагментации. |
|
||
| **SlabAllocator** | Аллокатор кучи ядра для мелких объектов. Vec, Box, String — через него. |
|
||
| **PMActor** | Распределённый менеджер физической памяти. Изоляция — актор A не ест память B. |
|
||
| **PM Router** | Асинхронная доставка результатов от акторов к потокам. |
|
||
| **AddressSpace** | Виртуальное адресное пространство процесса. PML4 + VMA. |
|
||
| **VMA** | Регион памяти с едиными правами. |
|
||
| **COW** | Копирование страницы только при записи. Fork без копирования всего. |
|
||
| **Demand paging** | Выделение страницы по требованию, а не заранее. Экономит память. |
|
||
| **VMM** | Управляет AddressSpace. Создаёт, удаляет, обрабатывает fault. |
|
||
| **INVPCID** | Выборочный сброс TLB для одного контекста. Ускоряет context switch. |
|
||
| **TLB shootdown** | Рассылка TLB flush на все ядра при изменении page tables. |
|
||
| **Capability** | Неподделываемый токен доступа. «Если нет ключа — нет доступа». |
|
||
| **Mint** | Создание дочернего capability с урезанными правами. |
|
||
| **Revoke** | Отзыв capability и всех его потомков. |
|
||
| **CNode** | Таблица capability. Хранит их в слотах. |
|
||
| **Lock-free** | Синхронизация без мьютексов, через CAS. Нет дедлоков. |
|
||
| **MPSC** | Очередь: много пишут, один читает. Паттерн для акторов. |
|
||
| **Intrusive list** | Список, где next/prev в самих элементах. Не нужно доп. аллокаций. |
|
||
| **KERNEL_SPACE** | AddressSpace ядра. Page Fault handler работает в нём. |
|
||
| **HCF** | Останов CPU (CLI + HLT). Конечная точка после паники. |
|
||
| **STI/CLI** | Включить/выключить прерывания. |
|
||
| **CR2** | Регистр с адресом, вызвавшим Page Fault. |
|