的宏展开__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
通过网络搜索后,我没有找到任何关于那里发生的事情的好的解释。
附加问题:我听说过内核开发中使用的各种“链接器魔术”。任何有关此的信息都会很棒。
我对这些宏的作用有一些想法。Like__init
应该表示初始化后可以删除功能代码。__read_mostly
用于指示数据很少被写入,从而最大限度地减少缓存未命中。但我不知道他们是如何做到的。我的意思是它们是gcc
扩展。所以理论上它们可以通过小的用户空间 c 代码来演示。
更新 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
所以基本上只创建了 a 小节,没什么特别的。
即使是 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);
这部分似乎正在使用“.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)
链接似乎很复杂:)