のマクロ展開__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
拡張機能です。したがって、理論的には、それらは小さなユーザーランドの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
、アセンブリコードはほぼ同じままです。
これはdiffです
--- 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を分解しても、違いは見られません。
それで、それらについての私の最後の結論は、そのリンカーの仕事は、特別な名前でマークされたデータセクションに対して何かをするということです。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)
リンクはかなり複雑なことのようです:)