feat: docs & ARCH 2.2, 2.3, 2.4

This commit is contained in:
Faynot
2026-07-07 16:40:41 +03:00
parent c092a81331
commit 1bfde637d0
50 changed files with 5113 additions and 1823 deletions

View 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 глубок.