38

のマクロ展開__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)

リンクはかなり複雑なことのようです:)

4

2 に答える 2

23

GCC属性は、言語自体の仕様の範囲外である命令をコンパイラーに与えるための一般的なメカニズムです。

リストするマクロに共通する機能は、次のように記述される__section__属性の使用です。

このsection属性は、関数が特定のセクションに存在することを指定します。たとえば、宣言:

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

関数foobarをbarセクションに配置します。

では、セクションに何かを入れるとはどういう意味ですか?.textオブジェクトファイルは、実行可能マシンコード、.data読み取り/書き込みデータ、.rodata読み取り専用データ、ゼロに初期化されたデータなどのセクションに分割されます.bss。これらのセクションの名前と目的は、プラットフォームの規則の問題であり、いくつかの特別なものです。セクションには、__attribute__ ((section))構文を使用してCからのみアクセスできます。

あなたの例では、それが主に読み取られるデータ.data..read_mostlyのサブセクションであると推測できます。プログラムの初期化などで実行されるテキスト(マシンコード)セクションです。.data.init.text

Linuxでは、さまざまなセクションをどうするかを決めるのはカーネルの仕事です。ユーザースペースがプログラムに要求するとexec、プログラムイメージがセクションごとに読み取られ、適切に処理されます。セクションは、読み取り/書き込みページ、読み取り専用、実行専用などとして.dataマップされます。おそらく、プログラムが開始します。これは、カーネルまたはプログラムのエントリポイントに配置されたユーザースペースコードのいずれかによって実行できます(後者を推測しています)。.rodata.text.init.text

これらの属性の効果を確認したい場合-Sは、セクションディレクティブを含むアセンブラコードを出力するオプションを指定してgccを実行することをお勧めします。次に、セクションディレクティブを使用して、または使用せずにアセンブラを実行しobjdump、結果のオブジェクトファイルを使用または16進ダンプして、その違いを確認できます。

于 2012-07-16T15:23:11.430 に答える
18

私の知る限り、これらのマクロはカーネルによって排他的に使用されます。理論的には、ユーザースペースに適用できますが、そうではないと思います。それらはすべて、異なる効果のために同様の変数とコードをグループ化します。

init / exit

カーネルをセットアップするには、多くのコードが必要です。これは、ユーザースペースが実行される前に発生します。つまり、 initタスクが実行される前です。多くの場合、このコードは二度と使用されません。したがって、起動後にスワップできないRAMを消費するのは無駄です。おなじみのカーネルメッセージ「初期メモリの解放init」は、このセクションの結果です。一部のドライバーはモジュールとして構成されている可能性があります。これらの場合、それらは終了します。ただし、カーネルにコンパイルされている場合は、必ずしも終了する必要はありません(シャットダウンする可能性があります)。これは、このタイプのコード/データをグループ化するための別のセクションです。

コールド/ホット

キャッシュラインのサイズは固定されています。同じタイプのデータ/関数をキャッシュに入れることで、キャッシュを最大化できます。よく使われるコードは並べて使用できるという考え方です。キャッシュが4つの命令である場合、 1つのホットルーチンの終了は次のホットルーチンの開始とマージする必要があります。同様に、めったに使用されないコードを一緒に保持することは良いことです。なぜなら、それがキャッシュに入らないことを望んでいるからです。

read_mostly

ここでの考え方はhotに似ています。データとの違いは、値を更新できます。これを行うと、キャッシュライン全体がダーティになり、メインRAMに書き込みする必要があります。これは、マルチCPUの一貫性と、そのキャッシュラインが古くなったときに必要です。CPUキャッシュバージョンとメインメモリの違いに何も変更がない場合は、エビクションで何も発生する必要はありません。これによりRAMバスが最適化され、他の重要なことが発生する可能性があります。

これらのアイテムは、厳密にはカーネル専用です。同様のトリックをユーザースペースに実装できます(ありますか?) 。これは、使用しているローダーによって異なります。これは、使用中のlibcによって異なることがよくあります。

于 2013-10-07T19:50:13.187 に答える