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

问题描述 投票:2回答:1

在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);
        }
    }
}

A barrier() 调用可以防止编译器重新排序指令。然而,这里与中断有什么关系?为什么需要它来保证语义的正确性?

c linux-kernel synchronization interrupt memory-barriers
1个回答
1
投票

没有 barrier(),编译器将可以自由访问 t->kcov_area 之前 t->kcov_mode. 在实践中想要这样做是不太可能的,但这不是重点。 如果没有某种障碍,C规则允许编译器创建不符合我们要求的asm。 (C11内存模型除了你显式施加的外,没有任何排序保证;在C11中通过 stdatomic 或在Linux GNU C中通过像 barrier()smp_rb().)


如评论中所述。barrier() 正在创建一个 获取负载与运行在同一核心上的代码有关。 这是你对中断的全部需求。

    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.com20120913获取并发布-semantics。 解释了acq rel同步的一般情况。


为什么不是 smp_rb() 需要吗? 即使在弱排序的ISA上,获取排序也需要一个栅栏指令来保证看到另一个核做的其他存储)。

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

用户空间C11 stdatomic 相当于这个障碍的是 atomic_signal_fence(memory_order_acquire). 信号栅栏只需要阻止编译时的重新排序(就像Linux的 barrier()),不像 atomic_thread_fence ,必须发出内存障碍asm指令。

失序的CPU内部确实会重新排序,但是 OoO exec的主要规则是保持指令一次运行的假象,以便让运行指令的核心. 这就是为什么你不需要一个内存屏障来处理asm的等价物。a = 1; b = a; 来正确加载你刚才存储的1;硬件上保留了串行执行的假象。1 按程序顺序进行。 (通常是通过让负载窥探存储缓冲区,并将尚未提交给L1d缓存的存储从存储转发到负载)。

中断处理程序中的指令在逻辑上运行在中断发生点之后(按照中断返回地址)。 因此,我们只需要按照正确的顺序来执行asm指令(barrier()),硬件会让一切都正常。

脚注1:有一些显式并行的ISA,比如IA-64和Mill,但它们提供了asm可以遵循的规则,以确保一条指令看到另一条更早指令的效果。 对于经典的MIPS I加载延迟槽之类的东西也是如此。 编译器会对编译后的C语言进行处理。

© www.soinside.com 2019 - 2024. All rights reserved.