为什么Linux内核中的KCOV代码中存在barrier()?

福尔摩斯·夏洛克

在Linux KCOV代码中,为什么要barrier()放置它?

void notrace __sanitizer_cov_trace_pc(void)
{
    struct task_struct *t;
    enum kcov_mode mode;

    t = current;
    /*
     * We are interested in code coverage as a function of a syscall inputs,
     * so we ignore code executed in interrupts.
     */
    if (!t || in_interrupt())
        return;
    mode = READ_ONCE(t->kcov_mode);
    if (mode == KCOV_MODE_TRACE) {
        unsigned long *area;
        unsigned long pos;

        /*
         * There is some code that runs in interrupts but for which
         * in_interrupt() returns false (e.g. preempt_schedule_irq()).
         * READ_ONCE()/barrier() effectively provides load-acquire wrt
         * interrupts, there are paired barrier()/WRITE_ONCE() in
         * kcov_ioctl_locked().
         */
        barrier();
        area = t->kcov_area;
        /* The first word is number of subsequent PCs. */
        pos = READ_ONCE(area[0]) + 1;
        if (likely(pos < t->kcov_size)) {
            area[pos] = _RET_IP_;
            WRITE_ONCE(area[0], pos);
        }
    }
}

一个barrier()呼叫阻止重新排序指令编译器。但是,这与这里的中断有什么关系?为什么需要语义正确性?

彼得·科德斯

如果没有barrier(),编译器将可以t->kcov_area在之前自由访问t->kcov_mode在实践中不太可能要这样做,但这不是重点。在没有任何障碍的情况下,C规则允许编译器创建无法执行我们想要的操作的asm。(C11内存模型没有超出您明确规定的顺序保证;在C11中通过stdatomic或在Linux / GNU C中通过诸如barrier()或的障碍smp_rb())。


如评论中所述,barrier()正在创建获取负载wrt。代码在同一内核上运行,这是中断所需要的全部。

    mode = READ_ONCE(t->kcov_mode);
    if (mode == KCOV_MODE_TRACE) {
        ...
        barrier();
        area = t->kcov_area;
        ...

我一般都不熟悉kcov,但是看起来像在t->kcov_mode带有获取负载的情况下看到某个值可以安全读取t->kcov_area(因为无论该对象写的是什么代码,它都会kcov_area一个对象,然后再发布存储kcov_mode。)

https://preshing.com/20120913/acquire-and-release-semantics/大致说明了acq / rel同步。


为什么smp_rb()不需要?(即使是在订购较弱的ISA上,也需要使用围篱指令来确保获得由另一个核心完成的其他商店的订单)。

中断处理程序在执行其他操作的同一内核上运行,就像信号处理程序中断线程并在其上下文中运行一样。struct task_struct *t = current意味着我们正在查看的数据是单个任务的本地数据。这等效于用户空间中单个线程中的内容。(导致在另一个内核上重新调度的内核抢占将使用在其他内核访问此任务一直在使用的内存时保持单个线程正确执行所需的任何内存屏障)。

stdatomic相当于此障碍的用户空间C11atomic_signal_fence(memory_order_acquire)信号隔离栅仅需阻止编译时重新排序(如Linux barrier()),不像atomic_thread_fence必须发出内存屏障asm指令。

序的CPU确实会在内部事物进行重新排序,但是OoO exec的基本原则是保留每次运行一条指令的错觉,以使核心运行指令这就是为什么不需要asm的内存屏障a = 1; b = a;即可正确加载刚存储的1的原因。硬件保留了按程序顺序执行串行执行1的错觉(通常,通过使加载来监视存储缓冲区,并将存储从存储转发到尚未提交到L1d高速缓存的存储的加载。)

中断处理程序中的指令在逻辑上在发生中断的点之后运行(根据中断返回地址)。因此,我们只需要按正确的顺序(barrier()进行asm指令,硬件就可以使一切正常。

脚注1:有些明确并行的ISA,例如IA-64和Mill,但它们提供了asm可以遵循的规则,以确保一条指令能够看到另一条指令的效果。与经典MIPS相同,我加载延迟插槽之类的东西。编译器会针对已编译的C进行处理。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么Linux内核中的这段代码不会引起死循环?

为什么Linux内核2.6版中不存在fs.inode-max内核可调参数?

为什么在此openCL内核代码中,localSum是本地的?

为什么Linux内核包含重复的代码?

Linux内核代码中的“当前”

__init在Linux内核代码中是什么意思?

Linux内核代码中的EXPORT_SYMBOL_GPL是什么?

__section()在Linux内核源代码中是什么意思

Linux内核源代码中的“最新”是什么?

Linux内核代码中的静态结构用于什么

Linux内核崩溃消息中的“代码”是什么?

为什么Linux内核中的bitops比我的慢?

什么是__ksymtab?在Linux内核中

为什么在代码优先的EntityFramework中存在重复的引用?

为什么我在该代码中仍然存在相同的错误

为什么在代码动态常量赋值Ruby中存在错误

为什么此代码中存在分段错误?

为什么我的代码中存在细分错误?

为什么我的代码中存在“死枝”?

为什么我的JavaScript代码中可能存在无限循环?

为什么在内核代码中某些变量地址存储在char指针中?

为什么自旋锁是Linux内核设计中的好选择,而不是用户区代码中更常见的东西,例如信号量或互斥锁?

在Linux内核中返回错误代码

为什么Linux内核需要15+百万行代码?

为什么在Unix / Linux中存在超级用户?

在Linux内核源代码中,为什么只有一个defconfig文件用于arch / arm64?(对于arch / arm,有很多xxx_defconfig文件)

为什么中断在这个内核代码中不起作用?

Linux内核代码中的“ EXPORT_SYMBOL”是什么意思?

在Linux中什么是受污染的内核?