Files
Elyz/kernel/docs/memory/pm-actor.md
2026-07-07 16:40:41 +03:00

259 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 глубок.