持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情
前言
📫作者简介:小明java问道之路,专注于研究 Java/ Liunx内核/ C++及汇编/计算机底层原理/源码,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。
📫热衷分享,喜欢原创~ 关注我会给你带来一些不一样的认知和成长。
🏆InfoQ签约作者、CSDN专家博主/后端领域优质创作者/内容合伙人、阿里云专家/签约博主、51CTO专家🏆
🔥如果此文还不错的话,还请👍关注 、点赞 、收藏三连支持👍一下博主~
本文导读
内核抢占就是允许正在内核中执行的任务抢占另一个正在内核中执行的任务。本文详解Linux内核抢占原理以及内核源码实现过程。
一、Linux内核自旋锁原理
想一想,在 SMP 中的操作和内核任务抢占的操作是不是一样的?
多个任务可以并发或者并行执行,所以内核在实现多任务并发安全处理时都是用的同一套操作。是时候展示暴露出去的可供调用的自旋锁了。
二、Linux内核自旋锁源码解析
如果配置SMP和内核抢占机制,那么以下宏定义生效,在我们描述完该内容后,再看看这两个函数做了什么?
可以看到,调用禁止抢占函数和原始自旋锁_raw_spin_lock,意味着上了自旋锁不允许被抢占。想相为什么。如果任务A持有自旋锁,任务B抢占了A,B要获取自旋锁,但自旋锁被A持有,那么B只能一直自旋,这显然造成了死锁。怎么强占的呢?读者可以考虑中断机制和 TIF_NEED_RESCHED标志位
同理开启了强占功能,那么在释放锁时需要打开强占,关闭抢占,获取自旋锁时保存EFLAGS,也就是说上锁前后需要保证EFLAGS不变,不保存 EFLAGS 状态,而是直接关闭中断、关闭抢占,因此这个宏定义绝对安全
下面我们来看内核源码:
//如果配置SMP和内核抢占机制,那么以下宏定义生效,在我们描述完该内容后,再看看这两个函数做了什么
#if defined(CONFIG SMP)&& defined(CONFIG_PREEMPT)
void_preempt_spin_lock(spinlock_t*lock);
void_preept_write_lock(rwlock_t *lock);
#define spin_lock(lock)
do {
preempt_disable();
if(unlikely(!_raw_spin_trylock(lock)))
__preempt_spin_lock(lock);
} while (0)
#define write_lock(lock)
do {
preempt_disable();
if(unlikely(!raw_write_trylock(lock)))
preempt_write_lock(lock);
} while (0)
#else // 如果没有定义,那么自旋锁不包含_preemp_spin_lock和_preempt_write_lock
#define spin_lock(lock)
do {
// 可以看到,调用禁止抢占函数和原始自旋锁_raw_spin_lock
// 意味着上了自旋锁不允许被抢占。想相为什么。如果任务A持有自旋锁
// 任务B抢占了A,B要获取自旋锁,但自旋锁被A持有,那么B只能一直自旋
// 这显然造成了死锁。怎么强占的呢?读者可以考虑中断机制和 TIF_NEED_RESCHED标志位
preempt_disable();
raw_spin_lock(lock);
} while(0)
#define write_lock(lock)
do {
preempt_disable();
_raw_write_lock(lock);
} while(0)
#endif
#define read_lock(lock)
do {
preempt_disable();
_raw_read_lock(lock);
} while(0)
// 同理开启了强占功能,那么在释放锁时需要打开强占
#define spin_unlock(lock)
do {
raw_spin_unlock(lock);
preempt_enable();
} while (0)
#define write_unlock(lock)
do {
_raw_write_unlock(lock);
preempt_enable();
} while(0)
#define read_unlock(lock)
do {
raw read unlock(lock);
preempt_enable();
} while(0)
// 关闭抢占,获取自旋锁时保存EFLAGS,也就是说上锁前后需要保证EFLAGS不变
#define spin_lock_irqsave(lock,flags)
do {
local_irq_save(flags);
preempt_disable();
_raw_spin_lock(lock);
} while (0)
// 不保存 EFLAGS 状态,而是直接关闭中断、关闭抢占,因此这个宏定义绝对安全
#define spin_lock_irq(lock)
do {
local_irq_disable();
preempt_disable();
raw_spin_lock(lock);
} while (0)
可以看到最后都是调用我们之前讲过的自旋锁代码,包括读写自旋锁,但是这里的锁类型被分为 3类,即spin_lock(关闭抢占,但是不关闭中断的自旋锁)、spin_lock_irqsave(关闭抢占并保存 EFLAGS,但是不关闭中断的自旋锁)、spin_lock_irq(关闭抢占、关闭中断的自旋锁)。
关于这3类锁类型的操作,说明如下。
1、如果用 spin_lock 函数,请不要在可中断的上下文中调用,因为一旦中断后,中断处理程序就令使用这个自旋锁,进而发生死锁。
2、如果用 spin_lock_irqsave 函数,也请不要在可中断的上下文中调用,因为一旦中断后,中断处理程序就会使用这个自旋锁,进而发生死锁,这个函数和 spin_lock 相比仅仅多了保存和还原 EFLAGS 操作。
3、对于 spin lock_irq(lock) 函数,可任意使用,它是绝对安全的,因为不会有任务来抢占,也不
会有中断发生。
这里我们来收尾上面的判断宏#if defined(CONFIG SMP)&&defined(CONFIGPREEMPT),如果使用了 SMP 对称多处理器的架构且打开了任务抢占,那么多出了
void_preempt_spin_lock(spinlock_t*lock);
void_preempt_write lock(rwlock_t*lock);