【原创】Linux信号量机制分析
背景
Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基
说明:
- Kernel版本:4.14
- ARM64处理器,Contex-A53,双核
- 使用工具:Source Insight 3.5, Visio
1. 概述
- 信号量
semaphore
,是操作系统中一种常用的同步与互斥的机制; - 信号量允许多个进程(计数值>1)同时进入临界区;
- 如果信号量的计数值为1,一次只允许一个进程进入临界区,这种信号量叫二值信号量;
- 信号量可能会引起进程睡眠,开销较大,适用于保护较长的临界区;
- 与读写自旋锁类似,linux内核也提供了读写信号量的机制;
本文将分析信号量与读写信号量的机制,开始吧。
2. 信号量
2.1 流程分析
- 可以将信号量比喻成一个盒子,初始化时在盒子里放入N把钥匙,钥匙先到先得,当N把钥匙都被拿走完后,再来拿钥匙的人就需要等待了,只有等到有人将钥匙归还了,等待的人才能拿到钥匙;
信号量的实现很简单,先看一下数据结构:
struct semaphore { raw_spinlock_t lock; //自旋锁,用于count值的互斥访问 unsigned int count; //计数值,能同时允许访问的数量,也就是上文中的N把锁 struct list_head wait_list; //不能立即获取到信号量的访问者,都会加入到等待列表中};struct semaphore_waiter { struct list_head list; //用于添加到信号量的等待列表中 struct task_struct *task; //用于指向等待的进程,在实际实现中,指向current bool up; //用于标识是否已经释放};
流程如下:
down
接口用于获取信号量,up
用于释放信号量;- 调用
down
时,如果sem->count > 0
时,也就是盒子里边还有多余的锁,直接自减并返回了,当sem->count == 0
时,表明盒子里边的锁被用完了,当前任务会加入信号量的等待列表中,设置进程的状态,并调用schedule_timeout
来睡眠指定时间,实际上这个时间设置的无限等待,也就是只能等着被唤醒,当前任务才能继续运行; - 调用
up
时,如果等待列表为空,表明没有多余的任务在等待信号量,直接将sem->count
自加即可。如果等待列表非空,表明有任务正在等待信号量,那就需要对等待列表中的第一个任务(等待时间最长)进行唤醒操作,并从等待列表中将需要被唤醒的任务进行删除操作;
2.2 信号量缺点
- 对比下
《Linux Mutex机制分析》
说过的Mutex
,Semaphore
与Mutex
在实现上有一个重大的区别:ownership
。Mutex
被持有后有一个明确的owner
,而Semaphore
并没有owner
,当一个进程阻塞在某个信号量上时,它没法知道自己阻塞在哪个进程(线程)之上; - 没有
ownership
会带来以下几个问题:- 在保护临界区的时候,无法进行优先级反转的处理;
- 系统无法对其进行跟踪断言处理,比如死锁检测等;
- 信号量的调试变得更加麻烦;
因此,在Mutex
能满足要求的情况下,优先使用Mutex
。
2.3 其他接口
信号量提供了多种不同的信号量获取的接口,介绍如下:
/* 未获取信号量时,进程轻度睡眠: TASK_INTERRUPTIBLE */int down_interruptible(struct semaphore *sem)/* 未获取到信号量时,进程中度睡眠: TASK_KILLABLE */int down_killable(struct semaphore *sem)/* 非等待的方式去获取信号量 */int down_trylock(struct semaphore *sem)/* 获取信号量,并指定等待时间 */int down_timeout(struct semaphore *sem, long timeout)
3. 读写信号量
【原创】linux spinlock/rwlock/seqlock原理剖析(基于ARM64)文章中,我们分析过读写自旋锁,读写信号量的功能类似,它能有效提高并发性,我们先明确下它的特点:
- 允许多个读者同时进入临界区;
- 读者与写者不能同时进入临界区(读者与写者互斥);
- 写者与写者不能同时进入临界区(写者与写者互斥);
3.1 数据结构
读写信号量的数据结构与信号量的结构比较相似:
struct rw_semaphore { atomic_long_t count; //用于表示读写信号量的计数 struct list_head wait_list; //等待列表,用于管理在该信号量上睡眠的任务 raw_spinlock_t wait_lock; //锁,用于保护count值的操作#ifdef CONFIG_RWSEM_SPIN_ON_OWNER struct optimistic_spin_queue osq; /* spinner MCS lock */ //MCS锁,参考上一篇文章Mutex中的介绍 /* * Write owner. Used as a speculative check to see * if the owner is running on the cpu. */ struct task_struct *owner; //当写者成功获取锁时,owner会指向锁的持有者
没有评论:
发表评论