精华内容
下载资源
问答
  • 2020-10-16 12:00:56

    在__list_del接口中使用了WRITE_ONCE接口,为何这样做,参考:

    WRITE_ONCE函数和list.h

    https://www.kernel.org/doc/Documentation/memory-barriers.txt

    更多相关内容
  • Linux内核中的READ_ONCEWRITE_ONCE

    万次阅读 2020-07-16 09:08:46
    #define __READ_ONCE(x, check) \ ({ \ ... __read_once_size(&(x), __u.__c, sizeof(x)); \ else \ __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \ smp...

    在Linux内核代码中,经常可以看到读取一个变量时,不是直接读取的,而是需要借助一个叫做READ_ONCE的宏;同样,在写入一个变量的时候,也不是直接赋值的,而是需要借助一个叫做WRITE_ONCE的宏。

    代码分析

    READ_ONCE宏定义如下(代码位于include/linux/compiler.h中):

    #define __READ_ONCE(x, check)						\
    ({									\
    	union { typeof(x) __val; char __c[1]; } __u;			\
    	if (check)							\
    		__read_once_size(&(x), __u.__c, sizeof(x));		\
    	else								\
    		__read_once_size_nocheck(&(x), __u.__c, sizeof(x));	\
    	smp_read_barrier_depends();                                     \
    	__u.__val;							\
    })
    #define READ_ONCE(x) __READ_ONCE(x, 1)

    READ_ONCE宏直接调用了另一个内部定义的宏__READ_ONCE,第二个参数check传的是1。

    在__READ_ONCE宏中其实定义了一个大的表达式,表达式的值是最后一个语句的值。这个表达式中先定义了一个联合体,联合体的第一个组成部分是__val,它的类型就是要读取变量的类型;联合体的第二个组成部分是一个只包含一个元素的字符数组__c,这样定义的话,__c就可以当做这个联合体的指针来使用了。然后,还用这个联合体定义了一个变量__u,这个变量是一个局部变量,因此是定义在栈上的。由于check传的是1,接着将调用__read_once_size函数:

    #define __READ_ONCE_SIZE						\
    ({									\
    	switch (size) {							\
    	case 1: *(__u8 *)res = *(volatile __u8 *)p; break;		\
    	case 2: *(__u16 *)res = *(volatile __u16 *)p; break;		\
    	case 4: *(__u32 *)res = *(volatile __u32 *)p; break;		\
    	case 8: *(__u64 *)res = *(volatile __u64 *)p; break;		\
    	default:							\
    		barrier();						\
    		__builtin_memcpy((void *)res, (const void *)p, size);	\
    		barrier();						\
    	}								\
    })
    
    static __always_inline
    void __read_once_size(const volatile void *p, void *res, int size)
    {
    	__READ_ONCE_SIZE;
    }

    这个函数的函数体是在宏__READ_ONCE_SIZE中定义的,传入的参数是要读取变量的指针,定义的联合体变量的指针,以及要读取变量的大小。在调用__read_once_size函数时,就将要读取变量的指针转换成了指向volatile变量的指针,告诉编译器要读取的这个变量是volatile的。在C语言中,volatile关键字的作用是:

    1. 声明这个变量易变,不要把它当成一个普通的变量,做出错误的优化。
    2. 保证CPU每次都从内存重新读取变量的值,而不是用寄存器中暂存的值。注意,这里说的是寄存器中缓存的值,而不是CPU缓存中存的值。很多英文文档里面都说了Cache,容易让人产生误解。

    __read_once_size函数要完成的操作是将要读取的变量的值拷贝到临时定义的局部联合体变量__u中。如果要读取变量的长度是1、2、4、8字节的时候,直接使用取指针赋值就行了,由于要读取变量的指针已经被转成了volatile的,编译器保证这个操作不会被优化。如果要读取的变量不是上面说的整字节,那么就要用__builtin_memcpy操作进行拷贝了,但前后都需要加上编译器屏障barrier(),这样就可以保证__builtin_memcpy函数调用本身不会被编译器优化掉。

    接下来__READ_ONCE宏调用了smp_read_barrier_depends函数,这个函数是为了解决某些特殊CPU架构下的缓存一致性问题的(主要是Alpha),也就是所谓的数据依赖内存屏障,在绝大多数CPU架构下都没什么用处。

    __READ_ONCE宏中定义的最后一条语句,就是直接返回局部联合体变量__u中的__val部分,也就是返回要读取变量被拷贝好了的值。由于它是这个表达式的最后一个语句,所以__READ_ONCE宏中定义的表达式的值就是这个值,从而保证了要读取值的变量在使用了READ_ONCE宏后能读取到正确的值。

    分析完READ_ONCE宏,那WRITE_ONCE宏就很简单了,基本上就是把READ_ONCE宏要做的事情反过来:

    #define WRITE_ONCE(x, val) \
    ({							\
    	union { typeof(x) __val; char __c[1]; } __u =	\
    		{ .__val = (__force typeof(x)) (val) }; \
    	__write_once_size(&(x), __u.__c, sizeof(x));	\
    	__u.__val;					\
    })

    还是定义了一个联合体变量__u,然后直接将要赋值的值读进来。接着调用了__write_once_size函数:

    static __always_inline void __write_once_size(volatile void *p, void *res, int size)
    {
    	switch (size) {
    	case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
    	case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
    	case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
    	case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
    	default:
    		barrier();
    		__builtin_memcpy((void *)p, (const void *)res, size);
    		barrier();
    	}
    }

    这次换成了把要赋值变量的指针转换成了指向volatile变量的指针。

    WRITE_ONCE宏的最后一条语句还是会返回要赋值的值的,因此也就是说WRITE_ONCE宏是返回要赋值的值的,只不过一般都没什么用。

    为什么要用READ_ONCE和WRITE_ONCE宏

    通常编译器是以函数为单位对代码进行优化编译的,而且编译器在优化的时候会假设被执行的程序是以单线程来执行的。基于这个假设优化出来的汇编代码,很有可能会在多线程执行的过程中出现严重的问题。可以举几个例子:

    1)编译器可以随意优化不相关的内存访问操作,打乱它们的执行次序。

    例如,对于如下代码:

    a[0] = x;
    a[1] = x;

    编译器可能会将其优化成:

    a[1] = x;
    a[0] = x;

    这对单线程的程序来说没有问题,因为变量x的值是不会改变的。但是,对于多线程的程序来说,变量x的值可能会被别的线程改变,如果要保证它们的执行顺序,必须加上READ_ONCE宏:

    a[0] = READ_ONCE(x);
    a[1] = READ_ONCE(x);

    注意,一定要两个语句都用READ_ONCE宏,这样才能保证次序,单独用一个还是没法保证。当然,在两条语句中间插入编译器屏障也可以解决这个问题。

    又或者,比如下面的程序:

    void process_level(void)
    {
    	msg = get_message();
    	flag = true;
    }
    
    void interrupt_handler(void)
    {
    	if (flag)
    		process_message(msg);
    }

    编译器在编译的时候,有可能会把process_level函数优化成:

    void process_level(void)
    {
    	flag = true;
    	msg = get_message();
    }

    因为它发现这两条语句没有任何关系,而且第二条语句比第一条语句执行速度要快,但是它并不知道flag位其实是一个标志位,必须要在获得消息后才能被设置成真。这时只能将process_level函数改成:

    void process_level(void)
    {
    	WRITE_ONCE(msg, get_message());
    	WRITE_ONCE(flag, true);
    }

    2)如果在编译的时候就能确定某些代码不会被执行到那可能会完全把代码删除。

    例如,对于如下的代码:

    while (tmp = a)
    	do_something_with(tmp);

    如果编译的时候,编译器发现变量a的值永远都是0,那么这条语句就会被优化成:

    do { } while (0);

    直接删除,什么都不做。这时候,为了保留一定会按照代码执行,那么必须改写成:

    while (tmp = READ_ONCE(a))
    		do_something_with(tmp);

    还有,对于如下代码:

    a = 0;
    /* 中间代码没有对变量a赋值 */
    ...... 
    a = 0;

    编译器发现,变量a的值一直是0,那后面再对变量a赋值0就是没有必要的,会直接删除掉最后一个赋值。但是,在多线程程序中,有可能另一个线程更改了变量a。为了保证一定赋值,可以用下面的代码:

    WRITE_ONCE(a, 0);
    /* 中间代码没有对变量a赋值 */
    ...... 
    WRITE_ONCE(a, 0);

    还存在许多奇奇怪怪的编译器优化,都可以用READ_ONCE和WRITE_ONCE宏告诉编译器别这么做。

    不过,READ_ONCE和WRITE_ONCE宏只能保证读写操作不被编译器优化掉,造成多线程执行中出问题,但是它并不能干预CPU执行编译出来的程序,也就是不能解决CPU重排序的问题和缓存一致性的问题,这类问题还是需要使用内存屏障来解决。

    而且,由于READ_ONCE和WRITE_ONCE宏的实现原理本身就是借助了C语言的volatile变量,因此如果要读取或者写入的变量本来就是volatile的就不需要再使用这两个宏了。

    READ_ONCE和WRITE_ONCE宏与编译器屏障的关系

    编译器屏障在Linux内核中是通过调用barrier()宏来实现的,其定义如下(代码位于include/linux/compiler-gcc.h中):

    #define barrier() __asm__ __volatile__("": : :"memory")

    所以,其实barrier()宏就是往正常的C语言代码里插入了一条汇编指令。这条指令告诉编译器(上面的汇编指令只对GCC编译器有效,其它编译器有对应的别的方法),不要将这条汇编指令前的内存读写指令优化到这条汇编指令之后,同时也不能将这条汇编指令之后的内存读写指令优化到这条汇编指令之前。但是,对于这条汇编指令之前的内存读写指令,以及之后的内存读写指令,想怎么优化都行,没有任何限制。 

    而READ_ONCE和WRITE_ONCE针对的是读写操作本身,只会影响使用这两个宏的内存访问操作,不能阻止对其它变量的优化操作。

    展开全文
  • WRITE_ONCE READ_ONCE 函数的介绍与使用

    千次阅读 2020-06-23 14:49:04
    今天看 内核中链表中的代码 include/linux/list.h ,发现其中有很多代码用到了WRITE_ONCE ,就引发了我的思考 上面的代码是初始化一个双向循环链表 ,将list中的两个指针 next 和 prev 都指向 自己,也就是 list ,...

    今天看 内核中链表中的代码 include/linux/list.h ,发现其中有很多代码用到了  WRITE_ONCE ,就引发了我的思考

    上面的代码是初始化一个双向循环链表 ,将list中的两个指针 next 和 prev 都指向 自己,也就是 list , 那为什么不直接赋值呢?笔者就查了查以前版本的内核代码,发现 linux4.5 以下的版本都是直接赋值的,linux4.5以上的版本都进行了优化。

    那我们进行思考以下两个问题:

    1、内核出于什么原因进行优化呢? 它和直接赋值有什么区别?

    2、我们什么时候要使用  WRITE_ONCE?

     

    来,先看看它的定义

    为什么要用READ_ONCE()和WRITE_ONCE()这两个宏呢? 这里起到关键作用的就是 volatile ,它主要告诉编译器:

    1、声明这个变量很重要,不要把它当成一个普通的变量,做出错误的优化。

    2、保证 CPU 每次都从内存重新读取变量的值,而不是用寄存器中暂存的值。

    因为在 多线程/多核 环境中,不会被当前线程修改的变量,可能会被其他的线程修改,从内存读才可靠。

    还有一部分原因是,这两个宏可以作为标记,提醒编程人员这里面是一个多核/多线程共享的变量,必要的时候应该加互斥锁来保护。

     

    搞明白了之后,开头提到的两个问题是不是就有了答案呢?

    总结一下:

    在多核多线程编程时,要注意共享变量的使用,要保证是 volatile的

     

    展开全文
  • READ_ONCE宏 #define READ_ONCE(x) __READ_ONCE(x, 1) #define __READ_ONCE(x, check) \ ({ \ union { typeof(x) __val; char __c[1]; } __u; \ if (check) \ _

    READ_ONCE宏

    #define READ_ONCE(x) __READ_ONCE(x, 1)
    
    #define __READ_ONCE(x, check)                       \
    ({                                  \
        union { typeof(x) __val; char __c[1]; } __u;            \
        if (check)                          \
            __read_once_size(&(x), __u.__c, sizeof(x));     \
        else                                \
            __read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \
        __u.__val;                          \
    })
    
    static __always_inline
    void __read_once_size(const volatile void *p, void *res, int size)
    {
        __READ_ONCE_SIZE;
    }
    
    static __always_inline
    void __read_once_size_nocheck(const volatile void *p, void *res, int size)
    {
        __READ_ONCE_SIZE;
    }
    
    #define __READ_ONCE_SIZE                        \
    ({                                  \
        switch (size) {                         \
        case 1: *(__u8 *)res = *(volatile __u8 *)p; break;      \
        case 2: *(__u16 *)res = *(volatile __u16 *)p; break;        \
        case 4: *(__u32 *)res = *(volatile __u32 *)p; break;        \
        case 8: *(__u64 *)res = *(volatile __u64 *)p; break;        \
        default:                            \
            barrier();                      \
            __builtin_memcpy((void *)res, (const void *)p, size);   \
            barrier();                      \
        }                               \
    })
    
    barrier() __asm__ __volatile__("": : :"memory")
    

    WRITE_ONCE宏

    #define WRITE_ONCE(x, val) \
    ({                          \
        union { typeof(x) __val; char __c[1]; } __u =   \
            { .__val = (__force typeof(x)) (val) }; \
        __write_once_size(&(x), __u.__c, sizeof(x));    \
        __u.__val;                  \
    })
    
    static __always_inline void __write_once_size(volatile void *p, void *res, int size)
    {
        switch (size) {
        case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
        case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
        case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
        case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
        default:
            barrier();
            __builtin_memcpy((void *)p, (const void *)res, size);
            barrier();
        }
    }
    

    知识点 barrier宏
    barrier()宏往C语言代码中插入一条汇编指令,告诉编译器,
    不要将这条汇编指令前的内存读写指令,优化到这条汇编指令之后,
    同时也不能将这条汇编指令之后的内存读写指令,优化到这条汇编指令之前。

    知识点 volatile 防止编译优化:
    这里并不是把变量定义为 volatile 而是赋值操作的指针类型定义为 volatile。原因见下。

    知识点 volatile 作恶:
    [RFC/PATCH] doc: volatile considered evil
    Nine ways to break your systems code using volatile

    知识点 编译参数 -fstrict-aliasing
    gcc手册
    __may_alias__

    知识点 内存屏障
    READ_ONCE和WRITE_ONCE宏只能保证读写操作不被编译器优化掉,造成多线程执行中出问题,但是它并不能干预CPU执行编译出来的程序,也就是不能解决CPU重排序的问题缓存一致性的问题,这类问题还是需要使用内存屏障来解决。

    这几尊佛的坑位太大了,需要深挖。。。

    参考链接
    READ_ONCE和WRITE_ONCE
    内存屏障

    还有谢宝友老师也写过这方面的资料。深入理解RCU、深入理解并行编程、DIM-SUM自研操作系统。
     
     
    完。

    展开全文
  • 这里居然又出现了函数__write_once_size(),追根溯源把,转到__write_once_size的定义: static __always_inline void __write_once_size(volatile void *p, void *res, int size) { switch (size) { case 1: ...
  • READ_ONCE, WRITE_ONCE, ACCESS_ONCE Data race:数据竞争 READ_ONCE(), WRITE_ONCE() only affects the compiler, not the CPU.
  • Linux内核的WRITE_ONCE函数分析

    千次阅读 2018-03-24 17:30:05
    Linux kernel中list.h中 链表的初始化函数如下static inline void INIT_LIST_HEAD(struct list_head *list){ WRITE_ONCE(list->next, list); list->prev = list;}上面一段代码的作用是初始化链表,...
  • int __thread theft = 0;static void flush_local_count_sig(int unused){if (READ_ONCE(theft) != THEFT_REQ) (*)return;...WRITE_ONCE(theft, THEFT_ACK)if (!counting) {WRITE_ONCE(theft, THEFT_READY);}s...
  • [源码分析][Linux]READ_ONCEWRITE_ONCE

    万次阅读 2016-06-15 00:52:26
    READ_ONCE#define READ_ONCE(x) __READ_ONCE(x, 1)#define __READ_ONCE(x, check) \ ({ \ union { typeof(x) __val; char __c[1]; } __u; \
  • WRITE_ONCE()

    2021-06-18 15:54:43
    WRITE_ONCE(prev->next, new)的作用就是安全地将prev->next指向new! 相当于new = prev->next
  • Flink的CheckPoint(EXACTLY_ONCE,AT_LEAST_ONCE)

    千次阅读 2019-05-27 09:49:38
    消息语义概述, 在分布式系统中,构成系统的任何节点都是被定义为可以彼此独立失败的。比如在 Kafka中,broker可能会...at-most-once:如果在ack超时或返回错误时producer不重试,则该消息可能最终不会写入Kafka,...
  • FFmpeg源代码简单分析:av_write_frame()

    万次阅读 多人点赞 2015-03-11 16:03:35
    打算写两篇文章简单分析FFmpeg的写文件用到的3个函数avformat_write_header(),av_write_frame()以及av_write_trailer()。上篇文章已经分析了avformat_write_header(),这篇文章继续分析av_write_frame()。
  • they try to be rendered all at once at the beggining of the video when playing it. I am setting the stream time base to 1/25, but just after calling avformat_write_header, it has the value of ...
  • C++11 实现读写锁 read_write_mutex

    千次阅读 2019-09-12 11:37:23
    读写锁 read_write_mutex 对于一个数据操作,简单的分可以分为读和写。 但是经常会遇到多人同时访问一个数据的情况: 多人读 多人写 有人读,有人写 处理这种情况,常用的方式是加锁(Mutex)...#pragma once #in...
  • write_cfgmem 产生存储器配置文件?

    千次阅读 2019-04-01 16:37:18
    write_cfgmem命令用于产生存储器配置文件,用于配置FPGA。在Vivado的tools工具下的Create a configuration file to program the device中,其GUI界面如下: 也可以使用Tcl命令来使用,如下实例。 实例: write_...
  • CTF_Web长征路细刷题笔记

    千次阅读 2022-01-02 15:01:43
    _main__": event=threading.Event() with requests.session() as session: for i in range(1,30): threading.Thread(target=write,args=(session,)).start() for i in range(1,30): threading.Thread(target=read,...
  • Write once, run anywhere     编写一次,到处运行,直观的描述了Java具有强的跨平台能力,Java的跨平台特性与Java虚拟机的存在密不可分,在不同的平台都有相应的JDK,安装好JDK就能给Java提供相应的运行环境,...
  • Write-Once-Read-Many-Timesand Bipolar Resistive Switching Characteristics of TiN/HfO2/PtDevices Dependent on the Electroforming Polarity
  • int mnt_want_write(struct vfsmount *m)获得一个mount点的写权限 其源码分析如下: int mnt_want_write(struct vfsmount *m) { int ret; #标志开始对super block开始写 sb_start_write(m->mnt_sb); #...
  • 深入浅出Linux内核中的内存屏障

    千次阅读 2021-01-16 17:17:35
    总是以这样的顺序执行: a = LOAD *X, STORE *X = b 而 WRITE_ONCE(*X, c); d = READ_ONCE(*X); 总是以下面的顺序执行: STORE *X = c, d = LOAD *X 最小保证不适用于位图。假设我们有一个长度为 8 的位图,CPU 1 ...
  • 现象:查看页面,发现数据出现异常,今天生成数据比平常水平偏低好多,不大正常 ...原因查找:查看日志文件,发现有出现了几个这样的警告:** WARNING ** Mnesia is overloaded: {dump_log, write_thresh
  • static __always_inline void __write_once_size(volatile void *p, void *res, int size) { switch (size) { case 1: *(volatile __u8 *)p = *(__u8 *)res; break; case 2: *(volatile __u16 *)p = *(__u16 *)...
  • Write Once,Run Anywhere——是SUN计算机系统公司用来展示Java程序设计语言的跨平台特性的口号。 理想中,这意味着Java可以在任何设备上开发,编译成一段标准的字节码并且可以在任何安装有Java虚拟机(JVM)的设备...
  • 参考资料: ... “一次编写,到处运行”(Write once, run anywhere、WORA,有时也作“write once, run everywhere”、WORE),是太阳计算机系统公司用来展示Java程序设计语言的跨平台特性的口号。 理想中
  • src/os/unix/ngx_files.c: In function 鈔gx_write_chain_to_file? src/os/unix/ngx_files.c:160: error: 釯OV_MAX?undeclared (first use in this function) src/os/unix/ngx_files.c:160: error: (Each ...
  • AUTOSAR —— NVM 3

    千次阅读 2019-09-15 18:35:29
    7. NVM_E_WRITE_ONCE_STATUS_UNKNOWN (0x1A):如果NVRAM block 配置启用了 NVM_WRITE_BLOCK_ONCE (TRUE),则在对该block进行第一次读之前,如果调用了Write/Erase/Invalidate,就会返回该错误码。   7.3.2 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 137,980
精华内容 55,192
关键字:

WRITE_ONCE