为啥“barrier()”足以禁用或启用抢占?
Posted
技术标签:
【中文标题】为啥“barrier()”足以禁用或启用抢占?【英文标题】:Why a "barrier()" is enough for disabling or enabling the preemption?为什么“barrier()”足以禁用或启用抢占? 【发布时间】:2016-02-25 04:43:33 【问题描述】:从Linux内核代码,我可以看到preempt_enable()
和preempt_disable()
除了barrier()
之外什么都不是:
#define preempt_disable() barrier()
#define preempt_enable() barrier()
我无法理解。为什么只需 barrier()
就足以禁用或启用抢占?
【问题讨论】:
【参考方案1】:因为您没有使用可抢占式内核。 不过,用户空间进程是可抢占的。
检查是否在内核配置中设置了 CONFIG_PREEMPT_VOLUNTARY。
看 What is preemption / What is a preemtible kernel? What is it good for?
从https://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux-stable/+/386afc91144b36b42117b0092893f15bc8798a80%5E!/开始添加屏障
【讨论】:
【参考方案2】:在 Kernel v4.3 中,preempt_enable
的正确定义是 here:
#define preempt_enable() \
do \
barrier(); \
if (unlikely(preempt_count_dec_and_test())) \
__preempt_schedule(); \
while (0)
preempt_disable
同样是here:
#define preempt_disable() \
do \
preempt_count_inc(); \
barrier(); \
while (0)
preempt_enable
在启用抢占之前插入优化屏障,preempt_disable
在抢占计数器增加后插入屏障。然而,根据comment,当不涉及抢占,而只是保护被抢占区域的障碍时。
EDIT:分别在 UP 和 non-preempt 中,自旋锁和抢占 禁用/启用点被完全剔除,因为没有 可以达到预期的并发性的常规代码 防止。
但是,虽然没有可以导致调度的常规代码,但我们 做 最终有一些特殊的(字面意思!)代码可以做到这一点, 我们需要确保永远不会进入关键 编译器的区域。
特别是 get_user() 和 put_user() 通常实现为 内联 asm 语句(即使内联 asm 可以调用 指令离线调用),显然会导致页面错误 结果是 IO。如果该内联汇编已安排到 在抢占安全(或自旋锁保护)代码区域的中间,我们 明显输了。
现在,诚然,这非常不太可能真正发生,并且 我们还没有看到与此相关的实际错误示例。但部分 正是因为它很难触发,而由此产生的错误是如此 微妙的,我们应该格外小心才能做到这一点。
因此,请确保即使禁用了抢占,我们也不必这样做 生成任何实际的代码来明确告诉系统我们在 一个禁止抢占的区域,我们至少需要告诉编译器不要 在关键区域周围移动东西。 Source
【讨论】:
preempt_disable的实现需要CONFIG_PREEMPT_COUNT
开启,This(#define preempt_disable() barrier()
)是CONFIG_PREEMPT_COUNT
关闭时的版本。
补充上面的描述,内核可以通过两种方式编译,即抢占模式和非抢占模式。 (我不会考虑始终存在的用户空间抢占)。 Kconfig [source] (lxr.free-electrons.com/source/kernel/Kconfig.preempt) 控制抢占的启用/禁用。当我们启用 CONFIG_PREEMPT 时,CONFIG_PREEMPT_COUNT 会自动启用(参考 kconfig.preempt)。当 PREEMPT 被禁用时,调用将替换为 barrier(),它只是将写入刷新到 DRAM。以上是关于为啥“barrier()”足以禁用或启用抢占?的主要内容,如果未能解决你的问题,请参考以下文章