.NET 9 において、Console.WriteLine は Linux x64 環境で標準出力への書き込みを実現するために、オペレーティングシステムの低レベル機能を活用している。本稿では、その中核を成す Volatile.Write および dup/dup2 システムコールの役割と動作原理を解説する。
Volatile.Write とメモリバリア
マルチコア CPU 環境では、コンパイラやプロセッサによる命令のリオーダリングにより、複数スレッド間でメモリ操作の順序が予期せず変更される可能性がある。Volatile.Write は、指定された参照先に値を書き込む際に、その操作が他の読み取り・書き込み操作よりも前に実行されることを保証するメモリバリアとして機能する。
.NET のコンソール実装では、この機構を用いて、標準出力ストリームの初期化が適切なタイミングで完了することを保証している。特に Linux 環境では、後述する dup を通じて取得したファイルディスクリプタが確実に反映されるよう、Volatile.Write が使用される。
Linux における dup と dup2
dup の基本動作
dup システムコールは、既存のファイルディスクリプタを複製し、最も小さい未使用のファイルディスクリプタ番号を返す。標準出力(ファイルディスクリプタ 1)を複製することで、.NET ランタイムは安全な出力ハンドルを取得する。
以下の C 言語コードは、dup を使って標準入力をファイルにリダイレクトする例である:
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("output.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
int new_fd = dup(fd);
char buffer[32];
ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer));
write(new_fd, buffer, n);
close(new_fd);
close(fd);
return 0;
}
このプログラムを実行し標準入力に文字列を入力すると、その内容が output.txt に書き込まれる。これは、new_fd が元のファイルディスクリプタ fd と同じファイルを指すためである。
dup2 による明示的なリダイレクト
dup2(oldfd, newfd) は、newfd を oldfd の複製として明示的に割り当てる。もし newfd がすでに開かれている場合は自動的に閉じられる。
次の例では、ファイルディスクリプタ 3 を標準出力にバインドし、そこへ書き込むことで端末に文字列を表示する:
#include <unistd.h>
#include <string.h>
int main() {
dup2(STDOUT_FILENO, 3);
const char* msg = "Hello from fd 3\n";
write(3, msg, strlen(msg));
close(3);
return 0;
}
実行結果として Hello from fd 3 が端末に出力される。これは、ファイルディスクリプタ 3 が標準出力と同一のカーネルエントリを参照しているためである。
.NET 9 における Console.Out の初期化
Console.WriteLine("Hello") は内部で Console.Out.WriteLine を呼び出す。Out プロパティは遅延初期化され、初回アクセス時に以下のように標準出力ストリームを構築する:
public static TextWriter Out
{
get
{
return Volatile.Read(ref s_out) ?? EnsureInitialized();
}
}
private static TextWriter EnsureInitialized()
{
lock (s_syncObject)
{
if (s_out == null)
{
var handle = ConsolePal.OpenStandardOutput();
Volatile.Write(ref s_out, CreateOutputWriter(handle));
}
return s_out;
}
}
ConsolePal.OpenStandardOutput() はプラットフォーム固有の実装を持ち、Linux では次のように dup を呼び出す:
internal static SafeFileHandle OpenStandardOutput()
{
return Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDOUT_FILENO);
}
ここで使用される Interop.Sys.Dup は、ネイティブ関数 SystemNative_Dup を P/Invoke でラップしたものであり、Linux の dup(2) システムコールを直接呼び出す。
この仕組みにより、.NET アプリケーションは標準出力を安全かつ効率的に操作でき、リダイレクトやパイプなどのシェル機能とも正しく連携する。