本篇文章主要介绍riscv-xv6中的锁机制,在riscv-xv6系统中,内核有不少的共享变量,如进程表,物理内存空闲链表,用于console读写的缓冲区等,这些共享变量在多个进程中共享需要互斥进行访问,否则就会出现数据一致性问题。
在riscv-xv6中有两种锁机制,一种为自旋锁(spinlock),一种为睡眠锁(sleeplock)。下面分别介绍一起其实现的机制原理以及应用的场合。
spinlock的实现: 关于__sync_lock_test_and_set函数和__sync_synchronize函数的实现和详细注释可以参考[1];用push_off关闭中断可以解决在嵌套调用情况下可能会导致的误开中断的问题[2]。
问答:在锁的获取和释放之间的过程中断为什么要关闭,如果中断不关闭,可能会出现哪些问题?自旋锁的临界区代码可以在不同 CPU 上执行吗?也就是自旋锁中为什么加上cpu结构体的指针?
回答:这三个问题是spinlock实现中的相互有关联的问题,将做一起回答。中断与特定的cpu相关,如果中断没有关闭,有可能会出现中断导致当前进程被挂起,下次调度运行时自旋锁的临界区代码就可以运行在不同的cpu上,这样可能会带来:(1)、cpu缓存一致性问题带来的性能问题;(2)、进程A已经获取锁并由于中断(如定时器中断)被让出cpu进入等待状态,进程B刚好也要获取锁,但由于锁已经被进程A获取,进程B将阻塞,导致系统处于较为复杂的进程间依赖情况,影响调度性能,有可能会导致死锁;(3)如果临界区在同一个cpu上不间断运行,如果当前 CPU 已持有锁并再次尝试获取,就可以直接报错,防止出现递归锁问题和死锁。(4)在多核系统中,调试并发问题是非常困难的,如果锁结构中存有持有锁的 CPU,那么:可以方便地打印日志,追踪哪个 CPU 在哪个时刻 获取了锁。在出现死锁或资源竞争时,能够快速确定哪个 CPU 或线程导致的问题。
睡眠锁的实现:睡眠锁的使用场景可以参考[3],sleeplock是阻塞式锁,用于长时间拥有资源的使用权限和持有锁的场景,如文件系统。关于acquiresleep和sleep函数的实现,可以参考[4]中的注释说明。在文件系统的缓冲区缓存的访问中,就调用了acquiresleep来访问缓冲区(如果已经被其他进程锁住,则进入睡眠等待,否则加锁并返回缓冲区,见[5]中的bget函数的实现和注释)
References
- [1]、spinlock的实现说明: https://gitee.com/kindlytree/riscv-xv6/issues/IBJZUK#note_36922660_link
- [2]、push_off解决中断误开的问题:https://gitee.com/kindlytree/riscv-xv6/issues/IBJZUK#note_36923757_link
- [3]、sleeplock的实现说明:https://gitee.com/kindlytree/riscv-xv6/issues/IBJZUK#note_36924421_link
- [4]、acquiresleep和sleep的函数实现和注释说明:https://gitee.com/kindlytree/riscv-xv6/issues/IBJZUK#note_36923906_link
- [5]、bget函数获取缓冲区buffer的实现和注释:https://gitee.com/kindlytree/riscv-xv6/issues/IBK79P#note_36956738_link
Leave a Reply