带您进入内核开发的大门 源程序量


配套的代码可以从本号的github下载,https://github.com/shuningzhang/linux_kernel
前面我们介绍了Linux内核中的自旋锁的使用和具体的实现 。接下来我们介绍一下在Linux内核中使用比较广泛的另外一种锁机制---信号量 。信号量又称为信号灯(semaphore),其与自旋锁不同的地方是它可以引起调用者休眠,也就是信号量本质上是一种睡眠锁 。如果有一个任务试图获得一个不可用(已经被占用)的信号量时,信号量会将其推进一个等待队列,然后让其睡眠 。这时处理器能重获自由,从而去执行其他代码 。当持有的信号量可用(被释放后),处于等待队列中的那个任务将被唤醒,并将获得该信号量 。

带您进入内核开发的大门 源程序量

文章插图
信号量一个有用的特性是它可以同时允许任意数量的锁持有者,而自旋锁和互斥锁在一个时刻最多允许一个任务持有它 。信号量同时允许的持有者数量可以在声明信号量时指定 。这个值称为使用者数量(usage count)或简单的叫做数量(count) 。通常情况下,信号量和自旋锁一样,在一个时刻仅允许有一个锁持有者 。这时计数等于1,这样的信号量被称为二值信号量或者称为互斥信号量 。另一方面,初始化时也可以把数量设置为大于1的非0值 。这种情况,信号量被称为计数信号量(counting semaphore),它允许在同一时刻至多有count个锁持有者 。
基本接口【带您进入内核开发的大门 源程序量】本节我们介绍一下信号量的基本接口,信号量的用法与自旋锁非常相似(分为初始化、加锁和解锁三部分) 。主要需要注意的地方是信号量会引起休眠,因此不要在不能休眠的地方使用信号量 。
信号量初始化
带您进入内核开发的大门 源程序量

文章插图
信号量加锁
带您进入内核开发的大门 源程序量

文章插图
信号量解锁
带您进入内核开发的大门 源程序量

文章插图
应用示例
带您进入内核开发的大门 源程序量

文章插图
基本原理本节介绍一下信号量的具体实现 。照例,我们先从信号量的数据结构走起 。下面是信号量的数据结构,可以看出其由自旋锁、计数和一个链表头组成 。
带您进入内核开发的大门 源程序量

文章插图
这里自旋锁 lock用于保护数据的访问,而链表 wait_list则用于记录有那些线程在等待该信号量 。
如果之前看过本号前面的文章,结合这里关于结构体的介绍,估计能够猜出来信号量的实现方式 。其大概原理是当有线程企图加锁的时候调用加锁函数(down),如果count大于0则表示可以加锁,并加锁成功 。如果小于等于0则表示不可加锁,此时线程将被放入wait_list队列,然后线程被调度出CPU(休眠) 。
加锁流程分析
如下代码是加锁的接口,可以看到入参就是上面定义的信号量的结构体指针 。在进行实际操作之前需要使用自旋锁进行保护 。
带您进入内核开发的大门 源程序量

文章插图
函数的调用关系是down->__down->__down_common,实际执行函数是__down_common 。下面是函数的源代码,具体请看函数内的注释 。
带您进入内核开发的大门 源程序量

文章插图
信号量解锁
信号量解锁的流程比较简单,如下是其实现代码,主函数比较简单,这里不做解释了 。
带您进入内核开发的大门 源程序量

文章插图
需要真正解锁的情况下将执行本函数 。可以看到本函数的实现也并不复杂,在这里首先从队列中取出第一个等待的任务,然后调用线程唤醒函数进行唤醒 。
带您进入内核开发的大门 源程序量

文章插图
能力增强除了上面的基本的接口外,还有一些辅助功能的接口 。比如down_trylock可以探测一下是否可以加锁 。而down_interruptible则设置当前线程的状态为TASK_INTERRUPTIBLE状态 。
带您进入内核开发的大门 源程序量

文章插图

    推荐阅读