读书笔记(7)—— kernel 信号量

信号量

Linux 内核中应用最广泛的同步原语除了自旋锁就是信号量(Semaphore)。可以说自旋锁和信号量在很大程度上是一种互补的关系,它们有各自适用的场景,两者的场景加起来基本上可以覆盖一切内核中的所有场景。
信号量可以是多值的(多值信号量),当其为二值信号量(只有两个值)时,类似于锁:一个值代表未锁,另一个值代表已锁。其工作原理与自旋锁最大的不同在于:获取锁的进程(内核的一个执行路径),若不能立即得到锁,就会发生调度转入睡眠;另外的进程释放锁时,唤醒等待该锁的进程。
前面已经说过自旋锁的使用的限制主要有两点:
(1)持有自旋锁的临界区不允许调度和睡眠
(2)其次就是竞争激烈时整体性能不好
而信号量恰好解决这两个问题:因为锁的竞争者 不是忙等,信号量的临界区允许调度和睡眠而不会导致死锁;因为锁的竞争者会转入睡眠, 从而让出 CPU 资源给别的进程,因此对锁的竞争不会影响整体性能。有优点就有缺点,中断上下文要求整体运行时间可预测(不能太长),而信号量临界区可能发生调度, 因此不能用于中断上下文(只能用于进程上下文)。另外,如果临界区的代码很短,那么用信号量并不合算,因为进程睡眠-唤醒的代价太大,消耗的 CPU 资源可能远远大于短时间的忙等。和自旋锁一样,信号量也分为普通信号量和读写信号量两种。

(1)普通信号量

在 Linux-2.6.26 以前,普通信号量的实现是体系结构相关的。考虑到信号量的原语并不像自旋锁那样用在对性能要求很高的场景,Linux-2.6.26 开始从可读性出发实现了通用的普通信号量。通用普通信号量的数据类型定义如下:

struct semaphore { 
  raw_spinlock_t lock;
  unsigned int  count;
  struct list_head wait_list;
};

这里面,count 是最重要的计数器字段,它标识了信号量的状态:0 表示忙已 锁,值为正代表自由未锁,允许竞争者进入临界区。因此 count 的初值就是最大允许进入临界区的进程数目,初值为 2 的信号量就是二值信号量。二值信号量类似于一个普通的锁,而多值信号量类似于一个允许一定并发性的锁。wait_list 字段是当信号量为忙时, 所有等待信号量的进程列表,而 lock 则是保护 wait_list 的自旋锁。
普通信号量的主要 API 有:

/*静态定义一个名为 sem 信号量*/
DEFINE_SEMAPHORE(sem):

/*初始化一个信号量 sem,计数器初值为 val,也就是count的值初始化为val*/
void sema_init(struct semaphore *sem, int val):

/*减少信号量 sem 的计数器(类似于获取锁)。如果失败(计数器已经是 0),那么转入睡眠(状态为 TASK_UNINTERRUPTIBLE,不会被任何信号唤醒)并把当前进程挂到 wait_list;被唤醒后继续尝试获取锁*/
void down(struct semaphore *sem)

/*增加信号量 sem 的计数器(类似于释放锁),然后唤醒 wait_list 里面的第一个进程(如果里面有的话)*/
void up(struct semaphore *sem)

/*尝试减少信号量 sem 的计数器(类似于获取锁),如果成功就返回 0,如果失败(计数器已经是 0)就返回 1,进程不会睡眠*/
int down_trylock(struct semaphore *sem)

/*减少信号量 sem 的计数器(类似于获取锁)。如果失败(计数器已经是 0),那么转入 睡眠(状态为TASK_KILLABLE,会被致命信号唤醒)并把当前进程挂到wait_list;被唤 醒后继续尝试获取锁。正常返回 0,被信号唤醒则返回-EINTR*/
int down_killable(struct semaphore *sem)

/*减少信号量 sem 的计数器(类似于获取锁)。如果失败(计数器已经是 0),那么转入睡眠(状态为 TASK_INTERRUPTIBLE,会被任意信号唤醒)并把当其进程挂到 wait_list; 被唤醒后继续尝试获取锁。正常返回 0,被信号唤醒则返回-EINTR*/
int down_interruptible(struct semaphore *sem)

/*减少信号量 sem 的计数器(类似于获取锁)。如果失败(计数器已经是 0),那么转入 睡眠(状态为 TASK_ UNINTERRUPTIBLE,但睡眠时间达到超时值 jiffies 后会被唤醒)并 把当其进程挂到 wait_list;被唤醒后继续尝试获取锁。正常返回 0,被超时唤醒则返回 -ETIME*/
int down_timeout(struct semaphore *sem, long jiffies)

(2)读写信号量

读写信号量的引入原因类似于读写自旋锁,是为了区分不同的竞争者(读者和写者), 以便允许读者共享而写者互斥。读写信号量既有通用版本,也有各种体系结构自己实现的版本。龙芯使用的是通用版本。

struct rw_semaphore {
  long count;
  struct list_head wait_list;
  raw_spinlock_t wait_lock;
  
  #ifdef CONFIG_RWSEM_SPIN_ON_OWNER
  struct optimistic_spin_queue osq;
  struct task_struct *owner;
   #endif
」

主要字段 count、wait_list 和 wait_lock 的含义与普通信号量的含义基本相同,用法也相同。而 CONFIG_ RWSEM_SPIN_ON_OWNER 是 Linux-3.16 开始引入的,通过 MCS 锁来优化读写信号量的性能,其原理类似于 Queued Spinlock。
读写信号量的主要 API 如下:

/*静态声明一个名为 sem 信号量*/
DECLARE_RWSEM(sem)

/*初始化一个读写信号量 sem*/
init_rwsem(sem)

/*读者减少信号量 sem 的计数器(类似于获取锁)*/
down_read(struct rw_semaphore *sem)

/*读者增加信号量 sem 的计数器(类似于释放锁)*/
up_read(struct rw_semaphore *sem)

/*写者减少信号量 sem 的计数器(类似于获取锁)*/
 down_write(struct rw_semaphore *sem)

/*写者增加信号量 sem 的计数器(类似于释放锁)*/
up_write(struct rw_semaphore *sem)

/*读者尝试减少信号量 sem 的计数器(类似于获取锁),如果count的值已经为0,立即返回不睡眠*/
 down_read_trylock(struct rw_semaphore *sem)

/*写者尝试减少信号量 sem 的计数器(类似于获取锁),如果count的值已经为0,立即返回不睡眠*/
 down_write_trylock(struct rw_semaphore *sem):

/*写者锁降级,即将写锁转换成读锁*/
downgrade_write(struct rw_semaphore *sem)

具体的代码实现请看include/linux/semaphore.h中的实现,这里不做详细的介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值