是谁关闭了Linux抢占?

是谁关闭了Linux抢占?,第1张

本人的目的在于解惑,把读者从糊里糊涂的状态解救出来。本文对比分析:

preempt_disable() 

local_irq_disable()/local_irq_save(flags)

spin_lock()

spin_lock_irq()/spin_lock_irqsave(lock, flags)

哪些关闭了抢占?另外,再说清楚,抢占又关闭了谁。

首先,把这几个API的关系图再勾勒一下。

我们理解,spin_lock()会调用preempt_disable() 导致本核的抢占调度被关闭(preempt_disable函数实际增加preempt_count来达到此效果),其次我们理解spin_lock_irq()是local_irq_disable()+preempt_disable()的合体。

是谁关闭了Linux抢占?,poYBAGLvgiiAYJjYAACXYEIrEv0737.png,第2张

下面一幅图描述了几个函数之间的包含关系如下:

是谁关闭了Linux抢占?,pYYBAGLvglSAVK2ZAACQq4GUDmU812.png,第3张是谁关闭了Linux抢占?,poYBAGLvgluAXJmdAACPDVd8i_w227.png,第4张

local_irq_disable()/local_irq_save()的disable和save版的唯一区别是,要不要保存CPU对中断的屏蔽状态。

spin_lock_irq()/spin_lock_irqsave(lock, flags)的唯一区别是,要不要保存CPU对中断的屏蔽状态。

是谁杀了抢占?

Kernel的代码明确显示,执行抢占调度的时候,会同时检测“non-zero preempt_count or interrupts are disabled”:

是谁关闭了Linux抢占?,poYBAGLvgoSAbcVnAACQy8ixE_4243.png,第5张

我们可以进一步展开preempTIble():

是谁关闭了Linux抢占?,pYYBAGLvgouAPHOPAABVsd34aek844.png,第6张

对于ARM处理器而言,判断irqs_disabled(),其实就是判断CPSR中的IRQMASK_I_BIT是否被设置。

所以,我们得出一个结论,前言这一节里面,列出的所有函数,都能关闭本核的抢占调度。因为,无论是preempt_count计数状态,还是中断被关闭,都会导致kernel认为无法抢占!

通杀逻辑如下:

是谁关闭了Linux抢占?,pYYBAGLvgrWALXt_AAD8icASaFI023.png,第7张是谁关闭了Linux抢占?,pYYBAGLvgruAbAKtAADvYJQUENg885.png,第8张

杀手的差异在哪里?

既然都关闭了抢占,那么区别在哪里呢?

我们看两段代码,假设下面的代码都发生在NICE为0的普通进程:

preempt_disable()

xxx(1)

preempt_enable()

local_irq_disable()

xxx(2)

local_irq_enable()

首先,xxx(1)和xxx(2)里面,都是不可以抢占的。一个搞定了preempt_count,一个搞定了中断。

但是假设xxx(1)内唤醒了一个高优先级的RT任务,那么在preempt_enable()的时刻,直接就是一个抢占点,这个时候,发生schedule,高优先级RT任务进来跑;假设xxx(2)内唤醒了一个高优先级的RT任务,那么在local_irq_enable()的时刻,不是一个抢占点,高优先级RT的任务必须等待下一个抢占点。下一个抢占点,可能是时钟TIck处理返回、中断返回、软中断结束、yield()等等多种情况。

在preempt_enable()中,会执行一次preempt_schedule():

是谁关闭了Linux抢占?,poYBAGLvgt-AYL0KAABZzs0ravs265.png,第9张

而local_irq_enable()只是单纯的开启CPU对中断的响应,对于ARM而言,它就是:

是谁关闭了Linux抢占?,pYYBAGLvgvWALC42AABMEnaJFng124.png,第10张

再来看大boss,spin_lock_irq是同时调用了preempt_disable和local_irq_disable:

是谁关闭了Linux抢占?,poYBAGLvgxCAeYgRAACY6glVmqo997.png,第11张

而对应的spin_unlock_irq()则同时调用了local_irq_enable()和preempt_enable():

是谁关闭了Linux抢占?,pYYBAGLvgyeAMpHqAACJbb0V3HU159.png,第12张

大家想一想,为何preempt_enable()比local_irq_enable()后发生呢?如果代码顺序是这样的:

preempt_enable()

local_irq_enable()

第一句preempt_enable()想执行抢占调度的话,即便调用了preempt_schedule(),但是由于IRQ还是关门的,preempt_schedule()函数会立即返回(详见《是谁杀了抢占?》一节),所以无法抢占;后一句local_irq_enable()不会执行抢占调度。所以,如果这么干的话,

spin_lock_irq()

xxx(3)

spin_unlock_irq()

如果xxx(3)唤醒了高优先级的RT,在spin_unlock_irq()的时刻,将无法直接抢占!

还好,真正的顺序是:

local_irq_enable()

preempt_enable()

所以,在spin_unlock_irq()的时刻,RT进程就换入执行了。

看小一点的boss,spin_lock():

spin_lock()

xxx(4)

spin_unlock()

如果xxx(4)唤醒了RT进程,在spin_unlock()的时刻,会立即抢入。因为spin_unlock()会调用preempt_enable()。

而抢占又杀了谁?

理论上,关抢占,并没有彻底的关闭调度器,因为进程还是可以主动地sleep:

是谁关闭了Linux抢占?,pYYBAGLvgz-AY-z0AABYTTiKn9U857.png,第13张

上述代码,在spin_lock的区间里面,调用了msleep(),这个时候,不属于抢占,Linux还是会pick下一个task来跑。

不过这样的代码,一般在后期蕴藏着巨大的风险,导致后期的莫名崩溃。所以呢,实际的工程里面,我们是严格地禁止的。

建议大家打开Kernel里面的config里面的DEBUG_ATOMIC_SLEEP,一旦出现这种情况,让kernel去汇报错误。

是谁关闭了Linux抢占?,poYBAGLvg1SAcM65AACu-SECC2U613.png,第14张

这种情况下,kernel检测到有人在atomic上下文里面执行可能睡眠的行为,会直接报执行的栈回溯。



审核编辑:刘清

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/dianzi/3000100.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存