Appearance
并行性
加锁主要是为了正确性而抑制并行性。 因为性能也很重要,内核设计者通常必须考虑如何使用锁,既能实现正确性又能允许并行性。 虽然 xv6 并非系统地为高性能而设计,但考虑哪些 xv6 操作可以并行执行,以及哪些可能在锁上发生冲突,仍然是值得的。
xv6 中的管道是一个相当好的并行性例子。 每个管道都有自己的锁,因此不同的进程可以在不同的 CPU 上并行地读写不同的管道。 然而,对于一个给定的管道,写入者和读取者必须等待对方释放锁;它们不能同时读/写同一个管道。 同样,从空管道读取(或向满管道写入)也必须阻塞,但这并非由于锁方案。
上下文切换是一个更复杂的例子。 两个内核线程,每个都在自己的 CPU 上执行,可以同时调用 yield
、sched
和 swtch
,并且这些调用将并行执行。 每个线程都持有一个锁,但它们是不同的锁,所以它们不必等待对方。 然而,一旦进入 scheduler
,两个 CPU 在搜索进程表中寻找一个 RUNNABLE
的进程时,可能会在锁上发生冲突。 也就是说,xv6 很可能在上下文切换期间从多个 CPU 中获得性能提升,但可能没有它所能达到的那么多。
另一个例子是来自不同 CPU 上不同进程的并发 fork
调用。 这些调用可能需要相互等待 pid_lock
和 kmem.lock
,以及为了在进程表中搜索一个 UNUSED
进程所需的每个进程的锁。 另一方面,两个分叉的进程可以完全并行地复制用户内存页和格式化页表页。
在上述每个例子中,锁方案在某些情况下牺牲了并行性能。 在每种情况下,都有可能使用更精巧的设计来获得更多的并行性。 这是否值得取决于细节:相关操作被调用的频率,代码持有有争议的锁的时间,可能同时运行冲突操作的 CPU 数量,以及代码的其他部分是否是更严格的瓶颈。 很难猜测一个给定的锁方案是否会导致性能问题,或者一个新的设计是否明显更好,因此通常需要对现实的工作负载进行测量。