Release log:
2021-04-27 二: 完成初版
说明
前一段时间学习了《LINUX 设备驱动程序》中的“并发与竞态”章节,没过多久在阅读代码时,看到了 spin_lock_bh
这个函数,然后一脸瞢逼,这个函数后缀有啥意义来着?所以决定对这一章节做一个简单整理,一是为了加深印象,二是为了后续的快速回顾以及查找
资源共享的规则
- 只要可能,就应该避免资源的共享。如果我们将资源放在多个线程都会找到的地方,则必须有足够的理由
- 在单个执行线程之外共享硬件或软件资源的任何时候,因为另外一个线程可能产生对该资源的不一致观察,因此必须显式地管理对该资源的访问
- 当内核代码创建了一个可能和其他内核部分共享的对象时,该对象必须在还有其他组件引用自己时保持存在(并正确工作)
信号量和互斥体
在计算机科学中,信号量是一个众所周知的概念。一个信号量本质上是一个整数值,它和一对函数联合使用,这一对函数通常称为 P 和 V。希望进入临界区的进程将在相关信号量上调用 P;如果信号量的值大于零,则该值减小一,而进程可以继续。相反,如果信号量的值为零(或更小),进程必须等待直到其他人释放该信号量。对信号量的解锁通常调用 V 完成;该函数增加信号量的值,并在必要时唤醒等待的进程
当信号量用于互斥时(即避免多个进程同时在一个临界区中运行),信号量的值应初始化为 1
Linux 内核中信号量的使用
在 Linux 内核中,要使用信号量需要包含头文件 <linux/semaphore.h>
,相关的类型为 struct semaphore
信号量的声明以及初始化函数
DECLARE_MUTEX(name);
void sema_init(struct semaphore *sem, int val);
void init_MUTEX(struct semaphore *sem);
Linux 中,P 函数被称为 down,下面是他的几个版本:
void down(struct semaphore *sem); // 非可中断操作,用于建立不可杀进程(ps 输出中的 "D state")
int down_interruptible(struct semaphore *sem); // 可中断,被中断时返回非零值,调用者未拥有该信号量
int down_killable(struct semaphore *sem); // 可以被 fatal signal 打断
int down_trylock(struct semaphore *sem); // 不会休眠,如果信号量在调用时不可获取,立即返回非零值
int down_timeout(struct semaphore *sem, long timeout); // timeout 是以 jiffies 计数
Linux 中,等价与 V 的函数是 up
void up(struct semaphore *sem);
读取者/写入者信号量
读取者/写入者信号量被称为 rwsem,使用时需要包含 <linux/rwsem.h>
,相关的数据类型为 struct rw_semaphore
。
rwsem 的初始化函数如下
void init_rwsem(struct rw_semaphore *sem);
rwsem 用于只读访问的操作如下
void down_read(struct rw_semaphore *sem); // 可能将进程至于不可中断的休眠中
int down_read_trylock(struct rw_semaphore *sem); // 成功返回 1,否则返回 0
void up_read(struct rw_semaphore *sem);
rwsem 用于写入者的操作如下
void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem); // 将写入者降级为读取者
写入者具有更高优先级,如果大量写入者竞争一个信号量,这种实现会导致读取者“饿死”