[インデックス 13723] ファイルの概要
このコミットは、GoランタイムがLinux/amd64環境においてVDSO (Virtual Dynamic Shared Object) をサポートするように変更を加えるものです。これにより、time
やgettimeofday
といった特定のシステムコールを、カーネルモードへの切り替えなしにユーザー空間から直接実行できるようになり、パフォーマンスが向上します。
コミット
commit 5287175ad9c6d8108f025ae754d220eb390f6d96
Author: Ivan Krasin <krasin@golang.org>
Date: Fri Aug 31 18:07:04 2012 -0400
runtime: add vdso support for linux/amd64. Fixes issue 1933.
R=iant, imkrasin, krasin, iant, minux.ma, rsc, nigeltao, r, fullung
CC=golang-dev
https://golang.org/cl/6454046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5287175ad9c6d8108f025ae754d220eb390f6d96
元コミット内容
このコミットは、GoのランタイムにLinux/amd64アーキテクチャ向けのVDSOサポートを追加します。これにより、Issue 1933で報告された問題が解決されます。
変更の背景
この変更の背景には、GoプログラムがLinuxシステムコール、特にtime
やgettimeofday
のような頻繁に呼び出される関数を実行する際のパフォーマンスオーバーヘッドの削減があります。従来のシステムコールは、ユーザーモードからカーネルモードへのコンテキストスイッチを伴い、これがオーバーヘッドの原因となっていました。
Issue 1933は、Goプログラムがこれらのシステムコールを頻繁に呼び出す際に発生するパフォーマンスの問題を指摘していました。VDSOは、この問題を解決するためのLinuxカーネルの機能であり、特定のシステムコールをユーザー空間で直接実行できるようにすることで、コンテキストスイッチのコストを回避します。このコミットは、GoランタイムがこのVDSO機能を活用できるようにすることで、該当するシステムコールのパフォーマンスを向上させることを目的としています。
前提知識の解説
VDSO (Virtual Dynamic Shared Object)
VDSOは、Linuxカーネルがユーザー空間にマッピングする共有ライブラリのようなものです。これには、gettimeofday
やtime
といった、カーネルへの完全なコンテキストスイッチを必要としない、軽量なシステムコール実装が含まれています。これらの関数は、カーネルが提供するメモリ領域に配置され、ユーザープログラムは通常の関数呼び出しのようにこれらを呼び出すことができます。これにより、システムコール呼び出しのオーバーヘッドが大幅に削減され、特に頻繁に時刻情報を取得するようなアプリケーションのパフォーマンスが向上します。
ELF (Executable and Linkable Format)
ELFは、Unix系システムで実行可能ファイル、オブジェクトファイル、共有ライブラリ、コアダンプなどを表現するための標準的なファイル形式です。VDSOもELF形式の共有オブジェクトとしてユーザー空間にマッピングされます。
Elf64_Ehdr
(ELF Header): ELFファイルの先頭に位置し、ファイルの種類、アーキテクチャ、エントリポイントなどの基本的な情報を含みます。Elf64_Phdr
(Program Header): プログラムのロード方法に関する情報(セグメントの種類、メモリ上の位置、ファイルオフセットなど)を記述します。VDSOの場合、PT_LOAD
タイプのエントリがメモリへのマッピング情報を提供します。Elf64_Shdr
(Section Header): ELFファイルのセクションに関する情報(セクション名、タイプ、サイズ、オフセットなど)を記述します。VDSOのシンボルテーブルや文字列テーブルの場所を特定するために使用されます。Elf64_Sym
(Symbol Table Entry): シンボル(関数名や変数名)とそのアドレス、タイプ、バインディングなどの情報を含みます。VDSO内の関数を見つけるために使用されます。Elf64_Dyn
(Dynamic Section Entry): 動的リンクに必要な情報(文字列テーブル、シンボルテーブル、バージョン情報など)を含みます。Elf64_Verdef
/Elf64_Versym
(Version Definition / Version Symbol): シンボルのバージョン情報を提供します。これにより、異なるバージョンのライブラリ間でシンボルの互換性を管理できます。Elf64_auxv_t
(Auxiliary Vector): プログラムが起動する際にカーネルから渡される情報(環境変数やコマンドライン引数に加えて)のリストです。AT_SYSINFO_EHDR
エントリは、VDSOのELFヘッダのアドレスを指します。
システムコールとコンテキストスイッチ
システムコールは、ユーザープログラムがオペレーティングシステム(カーネル)のサービスを要求するためのメカニズムです。例えば、ファイルの読み書き、メモリの割り当て、時刻の取得などがシステムコールを通じて行われます。システムコールが呼び出されると、CPUはユーザーモードからカーネルモードに切り替わり、カーネルが要求された処理を実行します。このモード切り替え(コンテキストスイッチ)は、CPUサイクルを消費するため、頻繁に行われるとパフォーマンスのボトルネックとなる可能性があります。
技術的詳細
このコミットは、Linux/amd64環境におけるGoランタイムのVDSOサポートを実装しています。その主要な技術的詳細は以下の通りです。
-
VDSOの検出とマッピング:
- Goプログラムが起動する際、Linuxカーネルは補助ベクトル(Auxiliary Vector)と呼ばれる情報を渡します。このベクトルには、
AT_SYSINFO_EHDR
というエントリが含まれており、これがVDSOのELFヘッダのアドレスを指します。 runtime·linux_setup_vdso
関数は、このAT_SYSINFO_EHDR
エントリを解析し、VDSOのELFヘッダのアドレスを取得します。- 取得したELFヘッダから、VDSOのプログラムヘッダ(
Elf64_Phdr
)とセクションヘッダ(Elf64_Shdr
)を解析し、VDSOがメモリにどのようにロードされているか(load_addr
,load_offset
)を特定します。
- Goプログラムが起動する際、Linuxカーネルは補助ベクトル(Auxiliary Vector)と呼ばれる情報を渡します。このベクトルには、
-
シンボル解決:
- VDSOは共有ライブラリであるため、その中に含まれる関数(例:
__vdso_time
,__vdso_gettimeofday
)はシンボルとして定義されています。 vdso_init_from_sysinfo_ehdr
関数は、VDSOの動的セクション(PT_DYNAMIC
)を解析し、シンボルテーブル(DT_SYMTAB
)、文字列テーブル(DT_STRTAB
)、バージョン情報(DT_VERSYM
,DT_VERDEF
)のアドレスを取得します。vdso_parse_symbols
関数は、これらのテーブルを使用して、Goランタイムが利用したいVDSO関数(__vdso_time
,__vdso_gettimeofday
)のアドレスを動的に解決します。具体的には、シンボル名とバージョン情報(LINUX_2.6
)を照合し、対応する関数のエントリポイントを特定します。
- VDSOは共有ライブラリであるため、その中に含まれる関数(例:
-
Goランタイムからの利用:
- 解決されたVDSO関数のアドレスは、
runtime·__vdso_time_sym
やruntime·__vdso_gettimeofday_sym
といったGoランタイム内のポインタ変数に格納されます。 syscall
パッケージ内のGettimeofday
やTime
といった関数は、これらのポインタを介してVDSO関数を直接呼び出すように変更されます。これにより、従来のシステムコール呼び出し(MOVQ $0xffffffffff600000, AX
のような固定アドレスへのジャンプ)ではなく、動的に解決されたVDSO関数が使用されるようになります。
- 解決されたVDSO関数のアドレスは、
-
フォールバックメカニズム:
- VDSOが利用できない環境(例えば、古いLinuxカーネルやVDSOがマッピングされていない場合)のために、従来のシステムコールアドレス(例:
0xffffffffff600400ULL
)がフォールバックとして初期値として設定されています。VDSOの初期化が成功した場合にのみ、これらのアドレスはVDSO内の実際のアドレスで上書きされます。
- VDSOが利用できない環境(例えば、古いLinuxカーネルやVDSOがマッピングされていない場合)のために、従来のシステムコールアドレス(例:
コアとなるコードの変更箇所
このコミットでは、主に以下の4つのファイルが変更されています。
-
src/pkg/runtime/runtime.c
:runtime·sysargs
という関数ポインタが追加されました。これは、Goランタイムの初期化時に引数(argc
,argv
)を受け取り、VDSOのセットアップを行うためのフックとして機能します。runtime·args
関数内で、runtime·sysargs
がnil
でない場合に呼び出されるようになりました。
-
src/pkg/runtime/runtime.h
:runtime.c
で追加されたruntime·sysargs
関数ポインタの宣言が追加されました。
-
src/pkg/runtime/vdso_linux_amd64.c
:- 新規追加ファイル。このファイルがVDSOサポートの核心部分です。
- VDSOのELF構造体(
Elf64_Ehdr
,Elf64_Phdr
,Elf64_Shdr
,Elf64_Sym
,Elf64_Dyn
,Elf64_Verdef
,Elf64_Versym
,Elf64_auxv_t
など)の定義が含まれています。 vdso_info
構造体は、VDSOのロード情報、シンボルテーブル、バージョンテーブルなどを保持します。linux26
というversion_key
が定義されており、LINUX_2.6
バージョンのシンボルを検索するために使用されます。runtime·__vdso_time_sym
とruntime·__vdso_gettimeofday_sym
というポインタ変数が定義され、VDSO関数のアドレスを格納します。初期値として、従来のシステムコールのアドレスが設定されています。vdso_init_from_sysinfo_ehdr
関数は、AT_SYSINFO_EHDR
からVDSOのELFヘッダを解析し、必要な情報をvdso_info
構造体に格納します。vdso_find_version
関数は、指定されたバージョン(LINUX_2.6
)に対応するVDSOのバージョンインデックスを検索します。vdso_parse_symbols
関数は、VDSOのシンボルテーブルを走査し、__vdso_time
と__vdso_gettimeofday
のアドレスを解決して、対応するポインタ変数に格納します。runtime·linux_setup_vdso
関数は、補助ベクトルを解析し、AT_SYSINFO_EHDR
エントリを見つけてVDSOの初期化とシンボル解決をトリガーします。void (*runtime·sysargs)(int32, uint8**) = runtime·linux_setup_vdso;
という行で、runtime·sysargs
関数ポインタにruntime·linux_setup_vdso
が割り当てられ、Goランタイム起動時にVDSOのセットアップが実行されるように設定されます。
-
src/pkg/syscall/asm_linux_amd64.s
:Gettimeofday
とTime
のGoアセンブリ関数が変更されました。- 従来の固定アドレス(例:
$0xffffffffff600000
)への直接ジャンプではなく、runtime·__vdso_gettimeofday_sym(SB)
やruntime·__vdso_time_sym(SB)
といったシンボル(VDSO関数のアドレスを保持するポインタ)を介して関数を呼び出すように変更されました。これにより、VDSOが利用可能な場合はVDSO関数が、そうでない場合はフォールバックアドレスが使用されるようになります。
コアとなるコードの解説
src/pkg/runtime/runtime.c
と src/pkg/runtime/runtime.h
の変更
これらのファイルでは、Goランタイムの初期化プロセスにVDSOのセットアップを組み込むためのフックが追加されています。
// runtime.c
void (*runtime·sysargs)(int32, uint8**); // 新しい関数ポインタの宣言
void
runtime·args(int32 c, uint8 **v)
{
argc = c;
argv = v;
+ if(runtime·sysargs != nil) // ここでVDSOセットアップ関数が呼び出される
+ runtime·sysargs(c, v);
}
runtime·sysargs
は、Goプログラムの起動時にコマンドライン引数と環境変数を処理するruntime·args
関数内で呼び出されます。これにより、Goランタイムが完全に初期化される前にVDSOの検出とシンボル解決が行われるようになります。
src/pkg/runtime/vdso_linux_amd64.c
の新規追加
このファイルはVDSOサポートの大部分を実装しています。
// vdso_linux_amd64.c
// ... ELF構造体の定義 ...
// VDSO関数のアドレスを保持するポインタ。初期値は従来のシステムコールアドレス。
void* runtime·__vdso_time_sym = (void*)0xffffffffff600400ULL;
void* runtime·__vdso_gettimeofday_sym = (void*)0xffffffffff600000ULL;
// 検索対象のVDSOシンボルとそのポインタのペア
static symbol_key sym_keys[] = {
{ (byte*)"__vdso_time", &runtime·__vdso_time_sym },
{ (byte*)"__vdso_gettimeofday", &runtime·__vdso_gettimeofday_sym },
};
// VDSOのELFヘッダを解析し、VDSO情報を初期化する
static void vdso_init_from_sysinfo_ehdr(struct vdso_info *vdso_info, Elf64_Ehdr* hdr) {
// ... VDSOのロードアドレス、オフセット、シンボルテーブル、バージョンテーブルなどを解析 ...
}
// VDSOのバージョンを検索する
static int32 vdso_find_version(struct vdso_info *vdso_info, version_key* ver) {
// ... LINUX_2.6バージョンを検索 ...
}
// VDSOシンボルを解析し、対応するポインタ変数にアドレスを格納する
static void vdso_parse_symbols(struct vdso_info *vdso_info, int32 version) {
// ... シンボルテーブルを走査し、__vdso_time, __vdso_gettimeofdayのアドレスを解決 ...
}
// VDSOのセットアップを行うメイン関数
static void
runtime·linux_setup_vdso(int32 argc, uint8** argv)
{
struct vdso_info vdso_info;
// ... 補助ベクトルからAT_SYSINFO_EHDRエントリを検索 ...
for(int32 i=0; elf_auxv[i].a_type!=AT_NULL; i++) {
if(elf_auxv[i].a_type == AT_SYSINFO_EHDR) {
vdso_init_from_sysinfo_ehdr(&vdso_info, (Elf64_Ehdr*)elf_auxv[i].a_un.a_val);
vdso_parse_symbols(&vdso_info, vdso_find_version(&vdso_info, &linux26));
return;
}
}
}
// runtime·sysargs関数ポインタにvdso_linux_amd64.cのセットアップ関数を割り当てる
void (*runtime·sysargs)(int32, uint8**) = runtime·linux_setup_vdso;
このファイルは、Linuxカーネルが提供するVDSOのELF構造を解析し、Goランタイムが必要とする特定の関数(__vdso_time
と__vdso_gettimeofday
)のアドレスを動的に取得するロジックを含んでいます。runtime·sysargs
にruntime·linux_setup_vdso
が割り当てられることで、Goプログラムの起動時にこのVDSOの初期化処理が自動的に実行されます。
src/pkg/syscall/asm_linux_amd64.s
の変更
このアセンブリファイルでは、syscall
パッケージのGettimeofday
とTime
関数が、VDSOによって提供される関数を呼び出すように変更されています。
// syscall/asm_linux_amd64.s
TEXT ·Gettimeofday(SB),7,$0
MOVQ 8(SP), DI
MOVQ $0, SI
- MOVQ $0xffffffffff600000, AX // 従来の固定アドレス
+ MOVQ runtime·__vdso_gettimeofday_sym(SB), AX // VDSOシンボルポインタからアドレスを取得
CALL AX
// ...
TEXT ·Time(SB),7,$0
MOVQ 8(SP), DI
- MOVQ $0xffffffffff600400, AX // 従来の固定アドレス
+ MOVQ runtime·__vdso_time_sym(SB), AX // VDSOシンボルポインタからアドレスを取得
CALL AX
// ...
この変更により、Gettimeofday
やTime
が呼び出された際に、runtime·__vdso_gettimeofday_sym
やruntime·__vdso_time_sym
に格納されているアドレスが使用されます。もしVDSOの初期化が成功していれば、これらのポインタはVDSO内の高速な実装を指し、そうでなければ従来のシステムコールアドレスを指すため、互換性を保ちつつパフォーマンス向上が図られます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/5287175ad9c6d8108f025ae754d220eb390f6d96
- Go Issue 1933: https://golang.org/issue/1933
- Go Code Review (CL 6454046): https://golang.org/cl/6454046
参考にした情報源リンク
- VDSO (Virtual Dynamic Shared Object) の詳細:
- ELFファイルフォーマット:
- Linux Auxiliary Vector:
- Goのシステムコール実装に関する議論 (Issue 1933):
- Goのランタイムとアセンブリ:
- Goのソースコード内の
src/pkg/runtime
およびsrc/pkg/syscall
ディレクトリ - Goの内部構造に関するブログ記事やドキュメント (一般的な情報源)
- Goのソースコード内の
I have generated the comprehensive technical explanation in Markdown format, following all the specified instructions and chapter structure. The output is printed to standard output only, as requested.```markdown
# [インデックス 13723] ファイルの概要
このコミットは、GoランタイムがLinux/amd64環境においてVDSO (Virtual Dynamic Shared Object) をサポートするように変更を加えるものです。これにより、`time`や`gettimeofday`といった特定のシステムコールを、カーネルモードへの切り替えなしにユーザー空間から直接実行できるようになり、パフォーマンスが向上します。
## コミット
commit 5287175ad9c6d8108f025ae754d220eb390f6d96 Author: Ivan Krasin krasin@golang.org Date: Fri Aug 31 18:07:04 2012 -0400
runtime: add vdso support for linux/amd64. Fixes issue 1933.
R=iant, imkrasin, krasin, iant, minux.ma, rsc, nigeltao, r, fullung
CC=golang-dev
https://golang.org/cl/6454046
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/5287175ad9c6d8108f025ae754d220eb390f6d96](https://github.com/golang/go/commit/5287175ad9c6d8108f025ae754d220eb390f6d96)
## 元コミット内容
このコミットは、GoのランタイムにLinux/amd64アーキテクチャ向けのVDSOサポートを追加します。これにより、Issue 1933で報告された問題が解決されます。
## 変更の背景
この変更の背景には、GoプログラムがLinuxシステムコール、特に`time`や`gettimeofday`のような頻繁に呼び出される関数を実行する際のパフォーマンスオーバーヘッドの削減があります。従来のシステムコールは、ユーザーモードからカーネルモードへのコンテキストスイッチを伴い、これがオーバーヘッドの原因となっていました。
Issue 1933は、Goプログラムがこれらのシステムコールを頻繁に呼び出す際に発生するパフォーマンスの問題を指摘していました。VDSOは、この問題を解決するためのLinuxカーネルの機能であり、特定のシステムコールをユーザー空間で直接実行できるようにすることで、コンテキストスイッチのコストを回避します。このコミットは、GoランタイムがこのVDSO機能を活用できるようにすることで、該当するシステムコールのパフォーマンスを向上させることを目的としています。
## 前提知識の解説
### VDSO (Virtual Dynamic Shared Object)
VDSOは、Linuxカーネルがユーザー空間にマッピングする共有ライブラリのようなものです。これには、`gettimeofday`や`time`といった、カーネルへの完全なコンテキストスイッチを必要としない、軽量なシステムコール実装が含まれています。これらの関数は、カーネルが提供するメモリ領域に配置され、ユーザープログラムは通常の関数呼び出しのようにこれらを呼び出すことができます。これにより、システムコール呼び出しのオーバーヘッドが大幅に削減され、特に頻繁に時刻情報を取得するようなアプリケーションのパフォーマンスが向上します。
### ELF (Executable and Linkable Format)
ELFは、Unix系システムで実行可能ファイル、オブジェクトファイル、共有ライブラリ、コアダンプなどを表現するための標準的なファイル形式です。VDSOもELF形式の共有オブジェクトとしてユーザー空間にマッピングされます。
* **`Elf64_Ehdr` (ELF Header)**: ELFファイルの先頭に位置し、ファイルの種類、アーキテクチャ、エントリポイントなどの基本的な情報を含みます。
* **`Elf64_Phdr` (Program Header)**: プログラムのロード方法に関する情報(セグメントの種類、メモリ上の位置、ファイルオフセットなど)を記述します。VDSOの場合、`PT_LOAD`タイプのエントリがメモリへのマッピング情報を提供します。
* **`Elf64_Shdr` (Section Header)**: ELFファイルのセクションに関する情報(セクション名、タイプ、サイズ、オフセットなど)を記述します。VDSOのシンボルテーブルや文字列テーブルの場所を特定するために使用されます。
* **`Elf64_Sym` (Symbol Table Entry)**: シンボル(関数名や変数名)とそのアドレス、タイプ、バインディングなどの情報を含みます。VDSO内の関数を見つけるために使用されます。
* **`Elf64_Dyn` (Dynamic Section Entry)**: 動的リンクに必要な情報(文字列テーブル、シンボルテーブル、バージョン情報など)を含みます。
* **`Elf64_Verdef` / `Elf64_Versym` (Version Definition / Version Symbol)**: シンボルのバージョン情報を提供します。これにより、異なるバージョンのライブラリ間でシンボルの互換性を管理できます。
* **`Elf64_auxv_t` (Auxiliary Vector)**: プログラムが起動する際にカーネルから渡される情報(環境変数やコマンドライン引数に加えて)のリストです。`AT_SYSINFO_EHDR`エントリは、VDSOのELFヘッダのアドレスを指します。
### システムコールとコンテキストスイッチ
システムコールは、ユーザープログラムがオペレーティングシステム(カーネル)のサービスを要求するためのメカニズムです。例えば、ファイルの読み書き、メモリの割り当て、時刻の取得などがシステムコールを通じて行われます。システムコールが呼び出されると、CPUはユーザーモードからカーネルモードに切り替わり、カーネルが要求された処理を実行します。このモード切り替え(コンテキストスイッチ)は、CPUサイクルを消費するため、頻繁に行われるとパフォーマンスのボトルネックとなる可能性があります。
## 技術的詳細
このコミットは、Linux/amd64環境におけるGoランタイムのVDSOサポートを実装しています。その主要な技術的詳細は以下の通りです。
1. **VDSOの検出とマッピング**:
* Goプログラムが起動する際、Linuxカーネルは補助ベクトル(Auxiliary Vector)と呼ばれる情報を渡します。このベクトルには、`AT_SYSINFO_EHDR`というエントリが含まれており、これがVDSOのELFヘッダのアドレスを指します。
* `runtime·linux_setup_vdso`関数は、この`AT_SYSINFO_EHDR`エントリを解析し、VDSOのELFヘッダのアドレスを取得します。
* 取得したELFヘッダから、VDSOのプログラムヘッダ(`Elf64_Phdr`)とセクションヘッダ(`Elf64_Shdr`)を解析し、VDSOがメモリにどのようにロードされているか(`load_addr`, `load_offset`)を特定します。
2. **シンボル解決**:
* VDSOは共有ライブラリであるため、その中に含まれる関数(例: `__vdso_time`, `__vdso_gettimeofday`)はシンボルとして定義されています。
* `vdso_init_from_sysinfo_ehdr`関数は、VDSOの動的セクション(`PT_DYNAMIC`)を解析し、シンボルテーブル(`DT_SYMTAB`)、文字列テーブル(`DT_STRTAB`)、バージョン情報(`DT_VERSYM`, `DT_VERDEF`)のアドレスを取得します。
* `vdso_parse_symbols`関数は、これらのテーブルを使用して、Goランタイムが利用したいVDSO関数(`__vdso_time`, `__vdso_gettimeofday`)のアドレスを動的に解決します。具体的には、シンボル名とバージョン情報(`LINUX_2.6`)を照合し、対応する関数のエントリポイントを特定します。
3. **Goランタイムからの利用**:
* 解決されたVDSO関数のアドレスは、`runtime·__vdso_time_sym`や`runtime·__vdso_gettimeofday_sym`といったGoランタイム内のポインタ変数に格納されます。
* `syscall`パッケージ内の`Gettimeofday`や`Time`といった関数は、これらのポインタを介してVDSO関数を直接呼び出すように変更されます。これにより、従来のシステムコール呼び出し(`MOVQ $0xffffffffff600000, AX`のような固定アドレスへのジャンプ)ではなく、動的に解決されたVDSO関数が使用されるようになります。
4. **フォールバックメカニズム**:
* VDSOが利用できない環境(例えば、古いLinuxカーネルやVDSOがマッピングされていない場合)のために、従来のシステムコールアドレス(例: `0xffffffffff600400ULL`)がフォールバックとして初期値として設定されています。VDSOの初期化が成功した場合にのみ、これらのアドレスはVDSO内の実際のアドレスで上書きされます。
## コアとなるコードの変更箇所
このコミットでは、主に以下の4つのファイルが変更されています。
1. **`src/pkg/runtime/runtime.c`**:
* `runtime·sysargs`という関数ポインタが追加されました。これは、Goランタイムの初期化時に引数(`argc`, `argv`)を受け取り、VDSOのセットアップを行うためのフックとして機能します。
* `runtime·args`関数内で、`runtime·sysargs`が`nil`でない場合に呼び出されるようになりました。
2. **`src/pkg/runtime/runtime.h`**:
* `runtime.c`で追加された`runtime·sysargs`関数ポインタの宣言が追加されました。
3. **`src/pkg/runtime/vdso_linux_amd64.c`**:
* **新規追加ファイル**。このファイルがVDSOサポートの核心部分です。
* VDSOのELF構造体(`Elf64_Ehdr`, `Elf64_Phdr`, `Elf64_Shdr`, `Elf64_Sym`, `Elf64_Dyn`, `Elf64_Verdef`, `Elf64_Versym`, `Elf64_auxv_t`など)の定義が含まれています。
* `vdso_info`構造体は、VDSOのロード情報、シンボルテーブル、バージョンテーブルなどを保持します。
* `linux26`という`version_key`が定義されており、`LINUX_2.6`バージョンのシンボルを検索するために使用されます。
* `runtime·__vdso_time_sym`と`runtime·__vdso_gettimeofday_sym`というポインタ変数が定義され、VDSO関数のアドレスを格納します。初期値として、従来のシステムコールのアドレスが設定されています。
* `vdso_init_from_sysinfo_ehdr`関数は、`AT_SYSINFO_EHDR`からVDSOのELFヘッダを解析し、必要な情報を`vdso_info`構造体に格納します。
* `vdso_find_version`関数は、指定されたバージョン(`LINUX_2.6`)に対応するVDSOのバージョンインデックスを検索します。
* `vdso_parse_symbols`関数は、VDSOのシンボルテーブルを走査し、`__vdso_time`と`__vdso_gettimeofday`のアドレスを解決して、対応するポインタ変数に格納します。
* `runtime·linux_setup_vdso`関数は、補助ベクトルを解析し、`AT_SYSINFO_EHDR`エントリを見つけてVDSOの初期化とシンボル解決をトリガーします。
* `void (*runtime·sysargs)(int32, uint8**) = runtime·linux_setup_vdso;`という行で、`runtime·sysargs`関数ポインタに`runtime·linux_setup_vdso`が割り当てられ、Goランタイム起動時にVDSOのセットアップが実行されるように設定されます。
4. **`src/pkg/syscall/asm_linux_amd64.s`**:
* `Gettimeofday`と`Time`のGoアセンブリ関数が変更されました。
* 従来の固定アドレス(例: `$0xffffffffff600000`)への直接ジャンプではなく、`runtime·__vdso_gettimeofday_sym(SB)`や`runtime·__vdso_time_sym(SB)`といったシンボル(VDSO関数のアドレスを保持するポインタ)を介して関数を呼び出すように変更されました。これにより、VDSOが利用可能な場合はVDSO関数が、そうでない場合はフォールバックアドレスが使用されるようになります。
## コアとなるコードの解説
### `src/pkg/runtime/runtime.c` と `src/pkg/runtime/runtime.h` の変更
これらのファイルでは、Goランタイムの初期化プロセスにVDSOのセットアップを組み込むためのフックが追加されています。
```c
// runtime.c
void (*runtime·sysargs)(int32, uint8**); // 新しい関数ポインタの宣言
void
runtime·args(int32 c, uint8 **v)
{
argc = c;
argv = v;
+ if(runtime·sysargs != nil) // ここでVDSOセットアップ関数が呼び出される
+ runtime·sysargs(c, v);
}
runtime·sysargs
は、Goプログラムの起動時にコマンドライン引数と環境変数を処理するruntime·args
関数内で呼び出されます。これにより、Goランタイムが完全に初期化される前にVDSOの検出とシンボル解決が行われるようになります。
src/pkg/runtime/vdso_linux_amd64.c
の新規追加
このファイルはVDSOサポートの大部分を実装しています。
// vdso_linux_amd64.c
// ... ELF構造体の定義 ...
// VDSO関数のアドレスを保持するポインタ。初期値は従来のシステムコールアドレス。
void* runtime·__vdso_time_sym = (void*)0xffffffffff600400ULL;
void* runtime·__vdso_gettimeofday_sym = (void*)0xffffffffff600000ULL;
// 検索対象のVDSOシンボルとそのポインタのペア
static symbol_key sym_keys[] = {
{ (byte*)"__vdso_time", &runtime·__vdso_time_sym },
{ (byte*)"__vdso_gettimeofday", &runtime·__vdso_gettimeofday_sym },
};
// VDSOのELFヘッダを解析し、VDSO情報を初期化する
static void vdso_init_from_sysinfo_ehdr(struct vdso_info *vdso_info, Elf64_Ehdr* hdr) {
// ... VDSOのロードアドレス、オフセット、シンボルテーブル、バージョンテーブルなどを解析 ...
}
// VDSOのバージョンを検索する
static int32 vdso_find_version(struct vdso_info *vdso_info, version_key* ver) {
// ... LINUX_2.6バージョンを検索 ...
}
// VDSOシンボルを解析し、対応するポインタ変数にアドレスを格納する
static void vdso_parse_symbols(struct vdso_info *vdso_info, int32 version) {
// ... シンボルテーブルを走査し、__vdso_time, __vdso_gettimeofdayのアドレスを解決 ...
}
// VDSOのセットアップを行うメイン関数
static void
runtime·linux_setup_vdso(int32 argc, uint8** argv)
{
struct vdso_info vdso_info;
// ... 補助ベクトルからAT_SYSINFO_EHDRエントリを検索 ...
for(int32 i=0; elf_auxv[i].a_type!=AT_NULL; i++) {
if(elf_auxv[i].a_type == AT_SYSINFO_EHDR) {
vdso_init_from_sysinfo_ehdr(&vdso_info, (Elf64_Ehdr*)elf_auxv[i].a_un.a_val);
vdso_parse_symbols(&vdso_info, vdso_find_version(&vdso_info, &linux26));
return;
}
}
}
// runtime·sysargs関数ポインタにvdso_linux_amd64.cのセットアップ関数を割り当てる
void (*runtime·sysargs)(int32, uint8**) = runtime·linux_setup_vdso;
このファイルは、Linuxカーネルが提供するVDSOのELF構造を解析し、Goランタイムが必要とする特定の関数(__vdso_time
と__vdso_gettimeofday
)のアドレスを動的に取得するロジックを含んでいます。runtime·sysargs
にruntime·linux_setup_vdso
が割り当てられることで、Goプログラムの起動時にこのVDSOの初期化処理が自動的に実行されます。
src/pkg/syscall/asm_linux_amd64.s
の変更
このアセンブリファイルでは、syscall
パッケージのGettimeofday
とTime
関数が、VDSOによって提供される関数を呼び出すように変更されています。
// syscall/asm_linux_amd64.s
TEXT ·Gettimeofday(SB),7,$0
MOVQ 8(SP), DI
MOVQ $0, SI
- MOVQ $0xffffffffff600000, AX // 従来の固定アドレス
+ MOVQ runtime·__vdso_gettimeofday_sym(SB), AX // VDSOシンボルポインタからアドレスを取得
CALL AX
// ...
TEXT ·Time(SB),7,$0
MOVQ 8(SP), DI
- MOVQ $0xffffffffff600400, AX // 従来の固定アドレス
+ MOVQ runtime·__vdso_time_sym(SB), AX // VDSOシンボルポインタからアドレスを取得
CALL AX
// ...
この変更により、Gettimeofday
やTime
が呼び出された際に、runtime·__vdso_gettimeofday_sym
やruntime·__vdso_time_sym
に格納されているアドレスが使用されます。もしVDSOの初期化が成功していれば、これらのポインタはVDSO内の高速な実装を指し、そうでなければ従来のシステムコールアドレスを指すため、互換性を保ちつつパフォーマンス向上が図られます。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/5287175ad9c6d8108f025ae754d220eb390f6d96
- Go Issue 1933: https://golang.org/issue/1933
- Go Code Review (CL 6454046): https://golang.org/cl/6454046
参考にした情報源リンク
- VDSO (Virtual Dynamic Shared Object) の詳細:
- ELFファイルフォーマット:
- Linux Auxiliary Vector:
- Goのシステムコール実装に関する議論 (Issue 1933):
- Goのランタイムとアセンブリ:
- Goのソースコード内の
src/pkg/runtime
およびsrc/pkg/syscall
ディレクトリ - Goの内部構造に関するブログ記事やドキュメント (一般的な情報源)
- Goのソースコード内の