__read_mostly,__ init,__ exit宏的很好的解释

Aft*_*nix 35 c gcc linux-kernel

宏观扩张__read_mostly:

#define __read_mostly __attribute__((__section__(".data..read_mostly"))
Run Code Online (Sandbox Code Playgroud)

这个来自 cache.h

__init:

#define __init          __section(.init.text) __cold notrace
Run Code Online (Sandbox Code Playgroud)

init.h

__exit:

#define __exit          __section(.exit.text) __exitused __cold notrace
Run Code Online (Sandbox Code Playgroud)

在通过网络搜索后,我没有找到任何关于那里发生的事情的好解释.

附加问题:我听说过内核开发中使用的各种"链接器魔术".任何有关这方面的信息都会很精彩.

我对这些宏的一些想法,他们做什么.就像__init假设初始化后可以删除功能代码一样.__read_mostly用于指示数据很少被写入,并且由此最小化缓存未命中.但我不知道他们是如何做到的.我的意思是他们是gcc扩展.因此理论上它们可以通过小型用户区域代码进行演示.

更新1:

我试图__section__用任意部分名称测试.测试代码:

#include <stdio.h>

#define __read_mostly __attribute__((__section__("MY_DATA")))

struct ro {
    char a;
    int b;
    char * c;
};

struct ro my_ro  __read_mostly = {
    .a = 'a',
    .b = 3,
    .c = NULL,
};


int main(int argc, char **argv) {
    printf("hello");
    printf("my ro %c %d %p \n", my_ro.a, my_ro.b, my_ro.c);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在使用__read_mostly生成的汇编代码:

    .file   "ro.c"
.globl my_ro
    .section    MY_DATA,"aw",@progbits
    .align 16
    .type   my_ro, @object
    .size   my_ro, 16
my_ro:
    .byte   97
    .zero   3
    .long   3
    .quad   0
    .section    .rodata
.LC0:
    .string "hello"
.LC1:
    .string "my ro %c %d %p \n"
    .text
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    pushq   %rbx
    subq    $24, %rsp
    movl    %edi, -20(%rbp)
    movq    %rsi, -32(%rbp)
    movl    $.LC0, %eax
    movq    %rax, %rdi
    movl    $0, %eax
    .cfi_offset 3, -24
    call    printf
    movq    my_ro+8(%rip), %rcx
    movl    my_ro+4(%rip), %edx
    movzbl  my_ro(%rip), %eax
    movsbl  %al, %ebx
    movl    $.LC1, %eax
    movl    %ebx, %esi
    movq    %rax, %rdi
    movl    $0, %eax
    call    printf
    movl    $0, %eax
    addq    $24, %rsp
    popq    %rbx
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.4.6 20110731 (Red Hat 4.4.6-3)"
    .section    .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)

现在没有__read_mostly宏,汇编代码或多或少保持不变.

这是差异

--- rm.S    2012-07-17 16:17:05.795771270 +0600
+++ rw.S    2012-07-17 16:19:08.633895693 +0600
@@ -1,6 +1,6 @@
    .file   "ro.c"
 .globl my_ro
-   .section    MY_DATA,"aw",@progbits
+   .data
    .align 16
    .type   my_ro, @object
    .size   my_ro, 16
Run Code Online (Sandbox Code Playgroud)

所以基本上只创建一个子部分,没什么特别的.

即使是objdump disassmbly也没有任何区别.

所以关于它们的最终结论,它的链接器的工作为标有特殊名称的数据部分做了一些事情.我认为linux内核使用某种自定义链接器脚本来实现这些功能.

其中一个问题是__read_mostly,放在那里的数据可以以某种方式进行分组和管理,以便减少缓存未命中.

lkml的个人提交了一个要删除的补丁__read_mostly.这引发了一场关于...的优点和缺点的着迷讨论__read_mostly.

这是链接:https://lkml.org/lkml/2007/12/13/477

我将发布进一步更新__init__exit.

更新2

这些宏__init,__exit并将数据__read_mostly的内容(在情况下)和文本(在和的情况下)放入自定义命名部分.链接器使用这些部分.现在由于各种原因链接器未被用作其默认行为,因此使用链接描述文件来实现这些宏的目的.__read_mostly__init__exit

可以找到后台如何使用自定义链接描述文件来消除死代码(链接器链接到但从未执行过的代码).此问题在嵌入式方案中非常重要.本文档讨论如何微调链接描述以删除死代码:elinux.org/images/2/2d/ELC2010-gc-sections_Denys_Vlasenko.pdf

如果是内核,则可以找到初始链接描述文件include/asm-generic/vmlinux.lds.h.这不是最终的脚本.这是一种起点,链接器脚本针对不同平台进一步修改.

快速查看此文件可立即找到感兴趣的部分:

#define READ_MOSTLY_DATA(align)                     \
    . = ALIGN(align);                       \
    *(.data..read_mostly)                       \
    . = ALIGN(align);
Run Code Online (Sandbox Code Playgroud)

看来这部分正在使用".data..readmostly"部分.

您还可以查找__init__exit部分相关的链接器命令:

#define INIT_TEXT                           \
    *(.init.text)                           \
    DEV_DISCARD(init.text)                      \
    CPU_DISCARD(init.text)                      \
    MEM_DISCARD(init.text)

#define EXIT_TEXT                           \
    *(.exit.text)                           \
    DEV_DISCARD(exit.text)                      \
    CPU_DISCARD(exit.text)                      \
    MEM_DISCARD(exit.text)
Run Code Online (Sandbox Code Playgroud)

链接似乎很复杂:)

eca*_*mur 23

GCC属性是一种向编译器提供指令的通用机制,这些指令不在语言本身的规范之内.

您列出的宏的常用工具是使用__section__属性,该属性描述为:

section属性指定函数位于特定部分中.例如,声明:

extern void foobar (void) __attribute__ ((section ("bar")));
Run Code Online (Sandbox Code Playgroud)

将功能foobar放在条形部分.

那么在某个部分放置什么意味着什么呢?目标文件分为几个部分:.text可执行机器代码,.data读写数据,.rodata只读数据,.bss初始化为零的数据等.这些部分的名称和用途是平台惯例,有些特殊只能使用__attribute__ ((section))语法从C访问节.

在您的示例中,您可以猜测这.data..read_mostly.data主要读取的数据的子部分; .init.text是一个文本(机器代码)部分,将在程序初始化时运行,等等.

在Linux上,决定如何处理各个部分是内核的工作; 当用户空间对exec程序的请求时,它将逐节读取程序图像并对其进行适当处理:.data各部分被映射为读写页面,.rodata只读,只.text执行等等.据推测,这些部分.init.text将在执行之前执行.程序启动; 这可以由内核或放置在程序入口点的用户空间代码完成(我猜测后者).

如果你想看到这些属性的影响,一个好的测试是使用-S输出汇编代码的选项运行gcc,汇编代码将包含section指令.然后,您可以使用和不使用section指令运行汇编程序,并使用objdump甚至十六进制转储生成的目标文件以查看它的不同之处.


art*_*ise 18

据我所知,这些宏由内核使用.从理论上讲,它们可以应用于用户空间,但我不相信这种情况.所有相似的变量和代码一起用于不同的效果.

初始化/退出

设置内核需要很多代码; 这种情况发生在任何用户空间运行之前.即,在init任务运行之前.在许多情况下,此代码永远不会再次使用.因此,在启动后使用不可交换的 RAM 将是一种浪费.熟悉的内核消息Freeing init memory是该init部分的结果.某些驱动程序可能配置为模块.在这些情况下,他们退出.但是,如果将它们编译到内核中,则不一定退出(它们可能会关闭).这是对此类代码/数据进行分组的另一部分.

冷热

每个缓存行都有固定的大小.您可以通过在其中放入相同类型的数据/函数来最大化缓存.这个想法是经常使用的代码可以并排.如果缓存是四条指令,则一个例程的结束应该与下一个例程的开头合并.同样,最好将很少使用的代码放在一起,因为我们希望它永远不会进入缓存.

read_mostly

这里的想法类似于 ; 与数据的差异我们可以更新值.完成此操作后,整个高速缓存行变,必须重新写入主RAM.这对于多CPU一致性以及该缓存行过时是必需的.如果CPU 缓存版本和主内存之间的差异没有任何改变,那么在驱逐时就不需要发生任何事情.这样可以优化RAM总线,以便发生其他重要事情.

这些项目严格用于内核.对用户空间可以实现类似的技巧(是?).这取决于使用的装载机 ; 根据使用的libc,这通常是不同的.

  • 其中一些想法可以应用于用户空间.例如,功能可能在单独的文件中,但相互调用.使用*LTO*(加载时间优化),功能可能彼此相邻.使用用户空间,您可以使用L1/L2缓存,也可以使用4k块进行文本分页.人们已经使用这个想法来优化Qt应用程序的加载.Linux在加载代码时很懒,并且会在代码执行时从(NAND)磁盘获取.将早期Qt初始化代码组合在一起将最小化4k页面加载.用户空间支持冷热属性(`.text.hot` ...) (3认同)