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
属性指定函数位于特定部分中.例如,声明:Run Code Online (Sandbox Code Playgroud)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
甚至十六进制转储生成的目标文件以查看它的不同之处.
art*_*ise 18
据我所知,这些宏只由内核使用.从理论上讲,它们可以应用于用户空间,但我不相信这种情况.所有组相似的变量和代码一起用于不同的效果.
设置内核需要很多代码; 这种情况发生在任何用户空间运行之前.即,在init任务运行之前.在许多情况下,此代码永远不会再次使用.因此,在启动后使用不可交换的 RAM 将是一种浪费.熟悉的内核消息Freeing init memory是该init
部分的结果.某些驱动程序可能配置为模块.在这些情况下,他们退出.但是,如果将它们编译到内核中,则不一定退出(它们可能会关闭).这是对此类代码/数据进行分组的另一部分.
每个缓存行都有固定的大小.您可以通过在其中放入相同类型的数据/函数来最大化缓存.这个想法是经常使用的代码可以并排.如果缓存是四条指令,则一个热例程的结束应该与下一个热例程的开头合并.同样,最好将很少使用的代码放在一起,因为我们希望它永远不会进入缓存.
这里的想法类似于热 ; 与数据的差异我们可以更新值.完成此操作后,整个高速缓存行变脏,必须重新写入主RAM.这对于多CPU一致性以及该缓存行过时是必需的.如果CPU 缓存版本和主内存之间的差异没有任何改变,那么在驱逐时就不需要发生任何事情.这样可以优化RAM总线,以便发生其他重要事情.
这些项目严格用于内核.对用户空间可以实现类似的技巧(是?).这取决于使用的装载机 ; 根据使用的libc,这通常是不同的.