在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
相当于此障碍的用户空间C11为atomic_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] 删除。
我来说两句