位置:进入主函数执行的第一个函数
kinit1(end, P2V(4*1024*1024)); // phys page allocator
void
kinit1(void *vstart, void *vend)
{
initlock(&kmem.lock, "kmem");
kmem.use_lock = 0;
freerange(vstart, vend);
}
void
initlock(struct spinlock *lk, char *name)
{
lk->name = name;
lk->locked = 0;
lk->cpu = 0;
}
void
kfree(char *v)
{
struct run *r;
if((uint)v % PGSIZE || v < end || v2p(v) >= PHYSTOP)
panic("kfree");
// Fill with junk to catch dangling refs.
memset(v, 1, PGSIZE);
if(kmem.use_lock)
acquire(&kmem.lock);
r = (struct run*)v;
r->next = kmem.freelist;
kmem.freelist = r;
if(kmem.use_lock)
release(&kmem.lock);
}
####代码:物理内存分配器
分配器中的数据结构是⼀个由可分配物理内存页构成的*空 表*。这个空闲页的链表的元素是结构体
`struct run`(2764)。那么分配器从哪⾥获得内存来存放这些数据结构呢?实际上,分配器将每个空
闲页的`run` 结构体保存在该空闲页本身中,因为空闲页中没有其他数据。分配器还⽤⼀个spin lock
(2764-2766)来保护空闲链表。链表和这个锁都封装在⼀个结构体中,这样逻辑就比较明晰:锁保护了
该结构体中的域。不过现在让我们先忽略这个锁,以及对`acquire` 和 `release`的调⽤;我们会在
第 4 章了解其细节。
`main` 函数调⽤了`kinit1` 和 `kinit2`两个函数对分配器进⾏初始化(2780)。这样做是由于
`main` 中的⼤部分代码都不能使⽤锁以及4MB 以上的内存。`kinit1`在前 4MB 进⾏了不需要锁的内
存分配。⽽ `kinit2` 允许了锁的使⽤,并使得更多的内存可⽤于分配。原本应该由`main` 决定有多
少物理内存可⽤于分配,但在 x86 上很难实现。所以它假设机器中有240MB(`PHYSTOP`)物理内存,
并将内核末尾和 `PHYSTOP` 之间的内存都作为⼀个初始的空闲内存池。`kinit1`和 `kinit2` 调⽤
`freerange`将内存加入空闲链表中,`freerange`则是通过对每⼀页调⽤`kfree` 实现该功能。⼀
个 PTE 只能指向⼀个 4096 字节对齐的物理地址(即是4096 的倍数),因此 `freerange`⽤
`PGROUNDUP`来保证分配器只会释放对齐的物理地址。分配器原本⼀开始没有内存可⽤,正是对
`kfree` 的调⽤将可⽤内存交给了分配器来管理。
分配器⽤映射到⾼内存区域的虚拟地址找到对应的物理页,⽽⾮物理地址。所以`kinit` 会使⽤ `p2v
(PHYSTOP)`来将`PHYSTOP`(⼀个物理地址)翻译为虚拟地址。分配器有时将地址看作是整型,这是为
了对其进⾏运算(譬如在`kinit` 中遍历所有页);⽽有时将地址看作读写内存⽤的指针(譬如操作每
个