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

时间:2012-07-16 13:44:08

标签: c gcc linux-kernel

__read_mostly的宏扩展:

#define __read_mostly __attribute__((__section__(".data..read_mostly"))

这是来自cache.h

__init

#define __init          __section(.init.text) __cold notrace

来自init.h

__exit

#define __exit          __section(.exit.text) __exitused __cold notrace

通过网络搜索后我没有找到任何好的解释 那里发生了什么。

附加问题:我听说过各种“链接魔术” 用于内核开发。任何信息 关于这将是美好的。

我对这些关于他们做什么的宏有一些想法。像__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;
}

现在使用__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

现在没有__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

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

即使是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)和文字<的内容/ strong>(在__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);

本节似乎正在使用“.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)

链接似乎很复杂:)

2 个答案:

答案 0 :(得分:23)

GCC attributes是一种向编译器发出指令的通用机制,这些指令不在语言本身的规范之内。

您列出的宏的常用功能是使用__section__ attribute,其描述如下:

  

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

extern void foobar (void) __attribute__ ((section ("bar")));
     

将函数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甚至十六进制转储生成的目标文件以查看它的不同之处。

答案 1 :(得分:18)

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

的init /出口

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

冷/热

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

read_mostly

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

这些项目严格用于内核。 用户空间可以实现类似的技巧(是?)。这取决于正在使用的 loader ;这通常是不同的,具体取决于使用中的 libc