Appearance
类锁模式
在许多地方,xv6 使用引用计数或标志以类似锁的方式来指示一个对象已被分配,不应被释放或重用。 进程的 p->state
就是这样起作用的,file
、inode
和 buf
结构中的引用计数也是如此。 虽然在每种情况下都有一个锁来保护标志或引用计数,但正是后者防止了对象被过早释放。
文件系统使用 struct inode
引用计数作为一种可以被多个进程持有的共享锁,以避免如果代码使用普通锁会发生的死锁。 例如,namex
(kernel/fs.c:namex
) 中的循环会依次锁定每个路径名组件所命名的目录。 然而,namex
必须在循环结束时释放每个锁,因为如果它持有多个锁,当路径名包含一个点(例如,a/./b
)时,它可能会与自身发生死锁。 它也可能与涉及该目录和 ..
的并发查找发生死锁。 正如第 6 章所解释的,解决方案是让循环将目录 inode 带到下一次迭代,其引用计数增加,但未被锁定。
一些数据项在不同时间受不同机制的保护,并且有时可能通过 xv6 代码的结构隐式地免受并发访问,而不是通过显式锁。 例如,当一个物理页空闲时,它受 kmem.lock
(kernel/kalloc.c:kmem
) 保护。 如果该页随后被分配为一个管道 (kernel/pipe.c:pipealloc
),它将受到一个不同的锁(嵌入的 pi->lock
)的保护。 如果该页被重新分配给一个新进程的用户内存,它根本不受锁的保护。 相反,分配器不会将该页分配给任何其他进程(直到它被释放)这一事实保护了它免受并发访问。 一个新进程内存的所有权是复杂的:首先父进程在 fork
中分配和操作它,然后子进程使用它,并且(在子进程退出后)父进程再次拥有该内存并将其传递给 kfree
。 这里有两个教训:一个数据对象在其生命周期的不同点可能以不同的方式受到并发保护,并且这种保护可能采取隐式结构的形式,而不是显式锁。
最后一个类锁的例子是在调用 mycpu()
(kernel/proc.c:myproc
) 前后需要禁用中断。 禁用中断使得调用代码相对于可能强制进行上下文切换的定时器中断是原子的,从而将进程移动到不同的 CPU。