feat: docs & ARCH 2.2, 2.3, 2.4
This commit is contained in:
258
kernel/docs/memory/pm-actor.md
Normal file
258
kernel/docs/memory/pm-actor.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# PM Actor: распределённый менеджер физической памяти
|
||||
|
||||
## Концептуальная модель
|
||||
|
||||
**PMActor** (Physical Memory Actor) — это изолированный агент, который
|
||||
владеет фиксированным диапазоном физической памяти и управляет им через
|
||||
BuddyAllocator. Он следует **модели акторов**:
|
||||
|
||||
- У каждого актора есть **почтовый ящик** (MPSC queue).
|
||||
- Другие компоненты **отправляют ему сообщения** (Allocate, Free, Carve).
|
||||
- Актёр **обрабатывает сообщения**, когда получает CPU time.
|
||||
- Актёр **отправляет ответы** через PM Router.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ PMActor #1 │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ MPSC Inbox (PMActorQueue) │ │
|
||||
│ │ [Alloc] [Free] [Carve] [Alloc] [Free] ... │ │
|
||||
│ └──────────────────────┬───────────────────────┘ │
|
||||
│ │ pop │
|
||||
│ ┌──────────────────────▼───────────────────────┐ │
|
||||
│ │ process_messages() │ │
|
||||
│ │ ┌────────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||
│ │ │handle_alloc│ │handle_free│ │handle_carve│ │ │
|
||||
│ │ └──────┬─────┘ └────┬─────┘ └─────┬────┘ │ │
|
||||
│ │ ▼ ▼ ▼ │ │
|
||||
│ │ ┌────────────────────────────────────────┐ │ │
|
||||
│ │ │ BuddyAllocator │ │ │
|
||||
│ │ │ [диапазон: 0x4000_0000 — 0x4000_xxxx] │ │ │
|
||||
│ │ └────────────────────────────────────────┘ │ │
|
||||
│ └────────────────────────┬──────────────────────┘ │
|
||||
│ │ Vec<PMResponse> │
|
||||
│ ▼ │
|
||||
│ Через PMRouter.route_responses() │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Структура PMActor
|
||||
|
||||
```rust
|
||||
pub struct PMActor {
|
||||
pub actor_id: u64, // Уникальный ID
|
||||
pub root_untyped: Capability, // Capability на весь диапазон
|
||||
pub managed_range: (PhysAddr, PhysAddr), // [start, end)
|
||||
queue: PMActorQueue, // MPSC inbox
|
||||
buddy: BuddyAllocator, // Buddy-аллокатор
|
||||
}
|
||||
```
|
||||
|
||||
- `actor_id` — используется в `token_sig` для уникальной идентификации.
|
||||
- `root_untyped` — Strong capability на весь диапазон (корень дерева).
|
||||
- `managed_range` — физические границы (начало, конец).
|
||||
- `queue` — lock-free MPSC кольцевой буфер.
|
||||
- `buddy` — аллокатор для этого диапазона.
|
||||
|
||||
## PMActorQueue — lock-free MPSC кольцевой буфер
|
||||
|
||||
```rust
|
||||
pub struct PMActorQueue {
|
||||
buffer: [AtomicU64; QUEUE_SIZE], // 1024 entry
|
||||
_pad0: [u8; 64],
|
||||
head: AtomicUsize, // читатель (актор)
|
||||
_pad1: [u8; 64],
|
||||
tail: AtomicUsize, // писатели (производители)
|
||||
}
|
||||
```
|
||||
|
||||
### Зачем padding?
|
||||
|
||||
`_pad0` и `_pad1` размером 64 байта — это размер кэш-линии x86-64.
|
||||
Разделение head и tail по разным кэш-линиям предотвращает **false sharing**
|
||||
(когда два ядра пишут в разные переменные, но они на одной кэш-линии).
|
||||
|
||||
### send(req) — Multi-Producer
|
||||
|
||||
```rust
|
||||
pub fn send(&self, req: PMRequest) -> Result<(), &'static str> {
|
||||
let packed = req.pack();
|
||||
let mut tail = self.tail.load(Ordering::Relaxed);
|
||||
loop {
|
||||
let head = self.head.load(Ordering::Acquire);
|
||||
if tail.wrapping_sub(head) >= QUEUE_SIZE {
|
||||
return Err("Queue overflow");
|
||||
}
|
||||
// CAS: пытаемся захватить слот
|
||||
match self.tail.compare_exchange_weak(tail, tail+1, AcqRel, Relaxed) {
|
||||
Ok(_) => {
|
||||
self.buffer[tail & QUEUE_MASK].store(packed, Release);
|
||||
return Ok(());
|
||||
}
|
||||
Err(actual) => tail = actual,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `wrapping_sub`: кольцевой буфер, переполнение при разнице >= QUEUE_SIZE.
|
||||
- `compare_exchange_weak`: позволяет нескольким продюсерам соревноваться.
|
||||
- Store с `Release`: гарантирует, что данные видны потребителю.
|
||||
|
||||
### pop() — Single-Consumer
|
||||
|
||||
```rust
|
||||
pub fn pop(&self) -> Option<PMRequest> {
|
||||
let head = self.head.load(Ordering::Relaxed);
|
||||
let tail = self.tail.load(Ordering::Acquire);
|
||||
if head == tail { return None; }
|
||||
let packed = self.buffer[head & QUEUE_MASK].load(Ordering::Acquire);
|
||||
self.head.store(head + 1, Ordering::Release);
|
||||
Some(PMRequest::unpack(packed))
|
||||
}
|
||||
```
|
||||
|
||||
- Только актор вызывает pop — никакой конкуренции.
|
||||
- Acquire на tail: видим последнюю запись продюсера.
|
||||
|
||||
## Message Packing
|
||||
|
||||
Каждое сообщение упаковывается в одно `u64`:
|
||||
|
||||
```
|
||||
Bit 63:56 OPCODE (8 bits) 1=Alloc, 2=Free, 3=Carve
|
||||
Bit 55:40 CHANNEL_ID (16 bits) маршрутизация ответа; 0 = discard
|
||||
Bit 39:20 ARG1 (20 bits) size_pages/local_frame_idx/offset_pages
|
||||
Bit 19: 0 ARG2 (20 bits) token_sig (low 20 bits) / size_pages / order
|
||||
```
|
||||
|
||||
Максимум: 2^20 = 1 048 576 страниц = 4 GiB на одну аллокацию.
|
||||
|
||||
## Типы сообщений (PMRequest)
|
||||
|
||||
```rust
|
||||
pub enum PMRequest {
|
||||
Allocate {
|
||||
size_pages: usize, // сколько страниц
|
||||
token_sig: u32, // тег capability
|
||||
channel_id: u16, // куда отправить ответ
|
||||
},
|
||||
Free {
|
||||
local_frame_idx: usize, // относительный индекс
|
||||
order: usize, // buddy order
|
||||
},
|
||||
Carve {
|
||||
offset_pages: usize, // смещение от начала
|
||||
size_pages: usize, // размер
|
||||
channel_id: u16, // куда отправить ответ
|
||||
},
|
||||
None,
|
||||
}
|
||||
```
|
||||
|
||||
## PMResponse — результат
|
||||
|
||||
```rust
|
||||
pub struct PMResponse {
|
||||
pub channel_id: u16,
|
||||
pub result: PMResult,
|
||||
}
|
||||
|
||||
pub enum PMResult {
|
||||
Allocated { cap: Capability, order: usize },
|
||||
OutOfMemory { size_pages: usize },
|
||||
Carved { cap: Capability },
|
||||
Freed { pages_returned: usize },
|
||||
}
|
||||
```
|
||||
|
||||
## Обработка сообщений
|
||||
|
||||
### handle_allocate(size_pages, token_sig, channel_id)
|
||||
|
||||
```rust
|
||||
fn handle_allocate(&mut self, size_pages, token_sig, channel_id) -> Option<PMResponse> {
|
||||
match self.buddy.alloc_pages(size_pages) {
|
||||
Some((rel_idx, order)) => {
|
||||
let phys = PhysAddr(self.managed_range.0.0 + rel_idx * PAGE_SIZE);
|
||||
let cap = Capability {
|
||||
object: CapObject::Memory { phys, size_pages: 1 << order },
|
||||
rights: CapRights::READ | WRITE | GRANT,
|
||||
relation: Relation::Strong,
|
||||
token_sig: (token_sig as u64) ^ self.actor_id ^ (rel_idx as u64),
|
||||
};
|
||||
Some(PMResponse { channel_id, result: Allocated { cap, order } })
|
||||
}
|
||||
None => {
|
||||
Some(PMResponse { channel_id, result: OutOfMemory { size_pages } })
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Детали:**
|
||||
- `rel_idx` — относительный индекс в buddy (от 0 до total_pages).
|
||||
- Физический адрес: `managed_range.start + rel_idx * 4096`.
|
||||
- `token_sig` — XOR от token_sig запроса, actor_id и rel_idx для уникальности.
|
||||
- Размер в capability: `1 << order` (округляется до степени двойки).
|
||||
|
||||
### handle_free(local_frame_idx, order)
|
||||
|
||||
```rust
|
||||
fn handle_free(&mut self, local_frame_idx, order) -> Option<PMResponse> {
|
||||
// Валидация границ
|
||||
let block_end = local_frame_idx.saturating_add(1usize << order);
|
||||
if block_end > self.buddy.total_pages() {
|
||||
debug_assert!(false, "Out of range");
|
||||
return None;
|
||||
}
|
||||
self.buddy.free(local_frame_idx, order);
|
||||
None // Free не требует ответа
|
||||
}
|
||||
```
|
||||
|
||||
### handle_carve(offset_pages, size_pages, channel_id)
|
||||
|
||||
Carve — выделение под-диапазона без участия buddy-аллокатора.
|
||||
Используется для статических размещений (framebuffer alias).
|
||||
|
||||
```rust
|
||||
fn handle_carve(&mut self, offset_pages, size_pages, channel_id) -> Option<PMResponse> {
|
||||
let range_pages = self.buddy.total_pages();
|
||||
// Проверка границ
|
||||
if offset_pages >= range_pages || size_pages == 0 || ... {
|
||||
return Some(PMResponse { channel_id, result: OutOfMemory { size_pages } });
|
||||
}
|
||||
let phys = PhysAddr(self.managed_range.0.0 + offset_pages * PAGE_SIZE);
|
||||
let cap = Capability {
|
||||
object: CapObject::Memory { phys, size_pages },
|
||||
rights: CapRights::READ | WRITE | GRANT,
|
||||
relation: Relation::Strong,
|
||||
token_sig: self.actor_id ^ offset_pages,
|
||||
};
|
||||
Some(PMResponse { channel_id, result: Carved { cap } })
|
||||
}
|
||||
```
|
||||
|
||||
## Ballooning hook (TODO)
|
||||
|
||||
Когда `BuddyAllocator::alloc_pages` возвращает `None`, актор генерирует
|
||||
`OutOfMemory`. Будущая подсистема ballooning будет перехватывать этот
|
||||
ответ до того, как он дойдёт до запросившего, и договариваться о передаче
|
||||
памяти от соседнего актора.
|
||||
|
||||
## Владение и capabilities
|
||||
|
||||
- PMActor держит Strong capability на весь свой диапазон.
|
||||
- Все выделенные или вырезанные под-диапазоны — тоже Strong (дочерние).
|
||||
- При освобождении через Free buddy-дерево объединяет блоки.
|
||||
- Актёр **никогда** не обращается к глобальному BitmapPMM.
|
||||
|
||||
## Максимальное количество сообщений за вызов
|
||||
|
||||
```rust
|
||||
const MAX_MESSAGES_PER_CALL: usize = 64;
|
||||
```
|
||||
|
||||
Ограничение предотвращает голодание ядра, когда inbox глубок.
|
||||
Reference in New Issue
Block a user