【问题标题】: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() 就足以禁用或启用抢占?

【问题讨论】:

    标签: c linux linux-kernel barrier preemption


    【解决方案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。
      猜你喜欢
      • 2014-01-13
      • 2018-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-12
      • 2010-10-23
      • 2011-07-07
      • 2020-01-30
      相关资源
      最近更新 更多