“DMA允许设备直接读写指定内存缓冲,而非CPU逐字节搬运。本文要求驱动使用已登记DMA地址,并在设备持有期间保持物理页pin与所有权。真实系统DMA地址可以经IOMMU或桥接映射,不能把普通…”
形式陈述
物理页分配器管理一组等长页框理路页表地址转换与访问权限Page table address translation · VPN PFN offset · 虚实地址转换由虚页号、页内偏移与PTE计算物理地址,分别检查驻留、访问类型和用户权限,并建立可检查的隔离不变量。。OS-16将内核保留页表帧与可分配数据帧分开;每个数据帧恰属于FREE或ALLOCATED之一;已分配帧附有持有者集合,PINNED是可与用户映射同时成立的回收资格标记,不是互斥的第三种所有权。空闲链F只能含FREE帧且无重复。alloc取出一个帧并转为私有内核所有;调用者初始化后才能发布用户PTE。free只接受没有用户映射、没有内核持有者、没有未完成设备引用的已分配帧。
对含COW映射的子池,mapcount记录用户PTE数;对设备使用的帧另有pincount。可回收条件为
等式左边任一项为零都不单独充分。匿名页首次发布前清零,文件页首次发布前从正确backing读取;分配器本身不需要把两者都定义成零页。
使用单链表时,受保护地移除表头和插回表头可为O(1)。清零B字节为O(B),空间清理、等待pin释放或物理连续多页的搜寻是另计成本。多核上链头与状态更新需要原子协议或锁,不能让两个CPU同时取得同一帧。
直觉
页框分配器给内核一整块内存,语言运行时再把已取得的区域切成对象。用户调用一次小对象分配,不必每次陷入内核;内核新增一个256B页,也不意味着里面只有一个对象。两层分配器管理不同所有权边界。
回收的关键不是“程序说不用了”,而是所有能继续访问该帧的路径都已消失。一个PTE被删除后若TLB还缓存着它,或设备仍按旧DMA描述符写入,帧就还没有真正脱离旧使用者。
例子与边界
四个动作维护自由集合
OS-16数据空闲链最初为[0x60,0x61,0x62]。P的合法缺页取走0x60,链变[0x61,0x62];清零256B后发布PTE,mapcount[0x60]=1。P fork C让mapcount升为2,但不再取页。C解除该映射后mapcount降到1,仍不能把0x60放回链;P也解除、完成TLB失效后计数为0,才把0x60插回链头。
假设两进程都已解除映射,但设备读取0x60的DMA尚未完成,pincount=1。若此时重分配给Q并清零,设备就可能读到Q的新数据或清零结果,而非提交时的旧缓冲。等待设备完成并解除pin后才可回收,避免跨对象的晚到访问。
重复释放的实际后果
错误地把0x60连续插入空闲链两次,会让后续两次alloc都返回0x60。两个调用者都认为自己独占,写入相互覆盖。给空闲帧填固定“毒化”字节有助于测试,却不能代替状态检查;它检测不到所有重复释放,也不能阻止别名分配。
若进程只请求1B但区域必须按256B页建立,最多255B暂时未被应用使用。这是页粒度内部碎片;用户堆分配器可以把多个小对象放在同页改善利用率。要求两个连续页却只有两个不相邻空闲帧,则是连续性要求与外部碎片问题,不能用“总空闲字节足够”解决。
推论与应用
设数据池共有N帧,把每帧分到空闲集合F或已分配集合A,二者应互斥且并集恰为池;同一已分配帧的持有者集合允许包含多个进程。alloc把一个元素从F转出,free按前置条件转回,归纳保持守恒;发布映射增加mapcount,撤销完成后减少它。若mapcount与实际PTE数量不一致,不能仅修数字而忽略仍存在的映射。
堆对象布局与分配理路堆对象布局与分配Heap object layout and allocation · Object descriptor · Bump-pointer allocation用对象头、字段偏移与指针描述符规定可扫描的堆布局,并给出带溢出检查和初始化约束的顺序分配器。已经给出对象头、描述符、对齐和快速分配,不在此重复。OS分配的是物理页及映射支持;运行时回收的是语义可达对象。某对象free后,它所在页仍可能含其他对象,因此物理页数未必立即下降。
在真实系统中,页引用还可能包含页缓存、映射、内核临时引用和设备pin,计数语义往往比本例复杂。必须先声明计数覆盖哪些持有者,再使用“减到零即释放”;单一数字的名字不是安全证明。
参考资料
- Cox、Kaashoek、Morris,xv6教材 rev5,§3.4–3.5,页粒度free list及分配/释放。
- Linux Kernel Documentation,pin_user_pages,2026-10-08核查,DMA pin与普通页面引用的用途区分;本页不照搬其实际计数编码。