接上一篇:[程序员] epoll与wait-queue的工作机制。除了回调的横跳,还看到以下的情况,所见非所得的问题。
比如READ_ONCE在Linux内核中是一个宏,它的主要作用是防止编译器对内存读取操作进行不当的优化,确保每次都从内存中实际读取值,而不是字面意义上的“只读一次”。这就是内核代码比较难读的另一个原因。
READ_ONCE是防止编译器优化,旨在告诉编译器,每次遇到这个宏时,都必须从内存中重新读取变量的值,而不是使用之前缓存的寄存器值或进行其他优化(例如,将多次读取合并为一次,或者重新排序读写操作)。
在多线程或中断上下文中,一个变量的值可能在任何时候被其他CPU或硬件修改。如果没有READ_ONCE,编译器可能会认为变量的值在两次读取之间不会改变,从而使用旧的缓存值,导致程序行为不正确。
它不是指变量在整个程序生命周期中只能被读取一次。它的意思是“每次调用READ_ONCE时,都执行一次新的、不受优化的内存读取”。你可以多次使用READ_ONCE来读取同一个变量,每次都会确保从内存中获取最新值。
与volatile的区别,C语言的volatile关键字也有防止编译器优化的作用,但它的语义更广,会影响所有对volatile变量的读写操作。
READ_ONCE在Linux内核中通常被认为是更精细和更安全的替代方案,因为它只影响读取操作,并且其行为在内核中经过了严格定义和测试,以确保跨不同架构的正确性。
从代码上看,READ_ONCE是创建了一个临时变量来存放变量的值,如果是简单类型变量,要加volatile,如果是复杂变量,要用memcpy来复制内存。
/** Prevent the compiler from merging or refetching reads or writes. The* compiler is also forbidden from reordering successive instances of* READ_ONCE and WRITE_ONCE, but only when the compiler is aware of some* particular ordering. One way to make the compiler aware of ordering is to* put the two invocations of READ_ONCE or WRITE_ONCE in different C* statements.** These two macros will also work on aggregate data types like structs or* unions. If the size of the accessed data type exceeds the word size of* the machine (e.g., 32 bits or 64 bits) READ_ONCE() and WRITE_ONCE() will* fall back to memcpy and print a compile-time warning.** Their two major use cases are: (1) Mediating communication between* process-level code and irq/NMI handlers, all running on the same CPU,* and (2) Ensuring that the compiler does not fold, spindle, or otherwise* mutilate accesses that either do not require ordering or that interact* with an explicit memory barrier or atomic instruction that provides the* required ordering.*/#define READ_ONCE(x) \({ \union { typeof(x) __val; char __c[1]; } __u = \{ .__c = { 0 } }; \__read_once_size(&(x), __u.__c, sizeof(x)); \__u.__val; \})
static __always_inline void __read_once_size(const volatile void *p, void *res, int size){switch (size) {case 1: *(__u8_alias_t *) res = *(volatile __u8_alias_t *) p; break;case 2: *(__u16_alias_t *) res = *(volatile __u16_alias_t *) p; break;case 4: *(__u32_alias_t *) res = *(volatile __u32_alias_t *) p; break;case 8: *(__u64_alias_t *) res = *(volatile __u64_alias_t *) p; break;default:barrier();__builtin_memcpy((void *)res, (const void *)p, size);barrier();}}