__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)
链接似乎很复杂:)
答案 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任务运行之前。在许多情况下,此代码永远不会再次使用。因此,在启动后使用 un-swappable RAM会很浪费。熟悉的内核消息释放init内存是init
部分的结果。某些驱动程序可能配置为模块。在这些情况下,他们退出。但是,如果它们被编译到内核中,则不一定退出(它们可能 shutdown )。这是对此类代码/数据进行分组的另一部分。
每个缓存行的大小都是固定的。您可以通过在其中放置相同类型的数据/函数来最大化缓存。这个想法是经常使用的代码可以并排。如果缓存是四条指令,则一个 hot 例程的结尾应该与下一个 hot 例程的开头合并。同样,最好将很少使用的代码放在一起,因为我们希望它永远不会进入缓存。
这里的想法类似于 hot ;与数据的差异我们可以更新值。完成此操作后,整个缓存行将变为 dirty ,并且必须重写到主RAM。这对于多CPU一致性以及缓存行过时是必需的。如果CPU 缓存版本与主内存之间的差异没有任何变化,那么逐出就不需要发生任何事情。这样可以优化RAM总线,以便发生其他重要事情。
这些项目严格用于内核。 用户空间可以实现类似的技巧(是?)。这取决于正在使用的 loader ;这通常是不同的,具体取决于使用中的 libc 。