Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14671] ファイルの概要

このコミットは、GoランタイムがPlan 9オペレーティングシステム上で環境変数を読み取れるようにするための getenv 関数の実装を追加します。これにより、GOMAXPROCSGOGC といったGoのランタイム設定を環境変数経由で制御できるようになります。

コミット

commit 432f18221fa77b814854053e751c125c9920886c
Author: Anthony Martin <ality@pbrane.org>
Date:   Mon Dec 17 11:07:40 2012 -0500

    runtime: implement getenv for Plan 9
    
    With this change the runtime can now read GOMAXPROCS, GOGC, etc.
    
    I'm not quite sure how we missed this.
    
    R=seed, lucio.dere, rsc
    CC=golang-dev
    https://golang.org/cl/6935062

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/432f18221fa77b814854053e751c125c9920886c

元コミット内容

runtime: implement getenv for Plan 9

With this change the runtime can now read GOMAXPROCS, GOGC, etc.

I'm not quite sure how we missed this.

R=seed, lucio.dere, rsc
CC=golang-dev
https://golang.org/cl/6935062

変更の背景

Goランタイムは、GOMAXPROCS(並行実行するOSスレッドの最大数)や GOGC(ガベージコレクションのトリガーとなるヒープ使用量の割合)など、いくつかの重要な設定を環境変数から読み取ります。しかし、このコミット以前は、Plan 9オペレーティングシステム向けのGoランタイムには、これらの環境変数を読み取るための getenv 関数が実装されていませんでした。

コミットメッセージにある「I'm not quite sure how we missed this.(なぜこれを見落としていたのか、よくわからない)」という記述は、この機能がGoランタイムの基本的な要件であり、Plan 9サポートの初期段階で実装されるべきだったにもかかわらず、これまで欠落していたことへの驚きを示しています。この欠落により、Plan 9上でGoプログラムを実行する際に、環境変数によるランタイムの挙動制御が不可能でした。このコミットは、この重要なギャップを埋めることを目的としています。

前提知識の解説

Plan 9 from Bell Labs

Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。Unixの設計思想をさらに推し進め、すべてのリソース(ファイル、デバイス、ネットワーク接続など)をファイルシステムとして表現するという「すべてはファイルである」という原則を徹底しています。環境変数も例外ではなく、/env ディレクトリ以下のファイルとして表現されます。例えば、PATH 環境変数の値は /env/PATH ファイルの内容として読み取られます。

Goランタイム (runtime)

Go言語のプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。このランタイムは、ガベージコレクション、ゴルーチン(軽量スレッド)のスケジューリング、チャネル通信、システムコールインターフェースなど、Goプログラムの実行に必要な低レベルの機能を提供します。ランタイムはGo言語自体で書かれている部分が多いですが、OSとの直接的なやり取りやパフォーマンスが重要な部分はC言語やアセンブリ言語で書かれています。

環境変数 (Environment Variables)

環境変数は、オペレーティングシステムが提供する動的な名前付きの値であり、実行中のプロセスに影響を与えます。プログラムはこれらの変数を読み取って、その挙動をカスタマイズできます。Goにおいては、GOMAXPROCSGOGC のようなランタイム設定の他にも、GOPATHGOROOT といった開発環境に関する設定にも利用されます。

getenv 関数

getenv は、指定された環境変数の値を取得するための標準的な関数です。Unix系システムでは通常、C標準ライブラリの一部として提供されます。Goランタイムのような低レベルのコードでは、OS固有のシステムコールやファイル操作を直接利用して、この機能を実現する必要があります。

システムコール (System Calls)

システムコールは、ユーザー空間のプログラムがオペレーティングシステムのカーネル空間のサービス(ファイルI/O、プロセス管理、メモリ管理など)を要求するためのインターフェースです。Plan 9では、ファイル操作(open, read, write, seek, close など)もシステムコールを通じて行われます。

技術的詳細

このコミットの主要な変更点は、Plan 9における環境変数の取得方法をGoランタイムに組み込んだことです。Plan 9では、環境変数は特殊なファイルシステム /env の下にファイルとして存在します。例えば、GOMAXPROCS の値を取得するには、/env/GOMAXPROCS というファイルを読み込む必要があります。

runtime·getenv 関数(C言語で記述)は、以下の手順で環境変数の値を取得します。

  1. ファイルパスの構築: 取得したい環境変数名(例: "GOMAXPROCS")を受け取り、それを /env/ と結合して完全なファイルパス(例: "/env/GOMAXPROCS")を構築します。
  2. ファイルのオープン: 構築したパスを使用して、runtime·open システムコールでファイルを読み取りモード (OREAD) で開きます。
  3. ファイルサイズの取得: runtime·seek システムコールを使用して、ファイルの末尾にシークし、ファイルのサイズ(バイト数)を取得します。これは、環境変数の値の長さを知るために必要です。
  4. メモリの割り当て: 取得したファイルサイズに基づいて、環境変数の値を格納するためのメモリを runtime·malloc で動的に割り当てます。
  5. 値の読み取り: runtime·pread システムコールを使用して、ファイルの先頭から割り当てたメモリに環境変数の値を読み込みます。
  6. ファイルのクローズ: runtime·close システムコールでファイルを閉じます。
  7. 値の返却: 読み取った環境変数の値が格納されたメモリへのポインタを返します。

この実装は、Plan 9の「すべてはファイルである」という哲学に忠実に従っており、環境変数を通常のファイルとして扱うことで、その値を取得しています。

また、このコミットでは、runtime·getenv の実装を runtime.c から env_plan9.c および env_posix.c に分離しています。これは、OS固有の環境変数取得ロジックをそれぞれのファイルにカプセル化し、コードのモジュール性と保守性を向上させるためのリファクタリングです。env_posix.c は、Unix系OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD)およびWindows向けの getenv 実装を含み、既存の環境変数リストを走査して値を見つけるアプローチを取っています。

さらに、Plan 9向けの seek システムコールを呼び出すためのアセンブリコードが sys_plan9_386.ssys_plan9_amd64.s に追加されています。これは、Goランタイムが直接OSのシステムコールを呼び出すために必要な低レベルのインターフェースです。

コアとなるコードの変更箇所

このコミットでは、以下のファイルが変更されています。

  • src/pkg/runtime/env_plan9.c (新規追加):
    • Plan 9向けの runtime·getenv 関数が実装されています。この関数は、/env/ ディレクトリ下のファイルを読み取ることで環境変数の値を取得します。
  • src/pkg/runtime/env_posix.c (新規追加):
    • POSIX互換OS(Unix系OSおよびWindows)向けの runtime·getenv 関数が実装されています。この関数は、プロセスに渡された環境変数リストを走査して値を見つけます。
    • syscall·setenv_c 関数も含まれており、cgoがロードされている場合にC環境を更新する役割を担います。
  • src/pkg/runtime/os_plan9.h:
    • Plan 9固有のシステムコール関数のプロトタイプ宣言が含まれるヘッダーファイルです。runtime·seek 関数のプロトタイプが追加されています。
  • src/pkg/runtime/runtime.c:
    • 以前 runtime.c にあった汎用的な runtime·getenv 関数と syscall·setenv_c 関数が削除され、OS固有のファイルに移動されました。
  • src/pkg/runtime/sys_plan9_386.s:
    • Plan 9/386アーキテクチャ向けの runtime·seek システムコールを呼び出すアセンブリコードが追加されています。
  • src/pkg/runtime/sys_plan9_amd64.s:
    • Plan 9/AMD64アーキテクチャ向けの runtime·seek システムコールを呼び出すアセンブリコードが追加されています。

コアとなるコードの解説

src/pkg/runtime/env_plan9.cruntime·getenv 関数

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "runtime.h"
#include "os_GOOS.h"

byte*
runtime·getenv(int8 *s)
{
	int32 fd, len, n, r;
	byte file[128];
	byte *p;

	len = runtime·findnull((byte*)s); // 環境変数名の長さを取得
	if(len > sizeof file-6) // パスバッファのサイズチェック
		return nil;

	runtime·memclr(file, sizeof file); // バッファをクリア
	runtime·memmove((void*)file, (void*)"/env/", 5); // "/env/" をコピー
	runtime·memmove((void*)(file+5), (void*)s, len); // 環境変数名を結合

	fd = runtime·open(file, OREAD); // ファイルを読み取りモードでオープン
	if(fd < 0)
		return nil;
	n = runtime·seek(fd, 0, 2); // ファイルの末尾にシークしてサイズを取得 (whence=2はSEEK_END)
	p = runtime·malloc(n+1); // 値を格納するメモリを割り当て (null終端のため+1)
	r = runtime·pread(fd, p, n, 0); // ファイルの先頭から値を読み取り
	runtime·close(fd); // ファイルをクローズ
	if(r < 0)
		return nil;
	return p; // 読み取った値へのポインタを返す
}

このコードは、Plan 9のファイルシステムを通じて環境変数を読み取る具体的な実装です。runtime·open, runtime·seek, runtime·pread, runtime·close といった関数は、Goランタイムが提供するOS固有のシステムコールラッパーです。

src/pkg/runtime/env_posix.cruntime·getenv 関数

// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin freebsd linux netbsd openbsd windows

#include "runtime.h"

Slice syscall·envs; // 環境変数リストを保持するスライス

byte*
runtime·getenv(int8 *s)
{
	int32 i, j, len;
	byte *v, *bs;
	String* envv;
	int32 envc;

	bs = (byte*)s;
	len = runtime·findnull(bs); // 検索する環境変数名の長さを取得
	envv = (String*)syscall·envs.array; // 環境変数リストの配列
	envc = syscall·envs.len; // 環境変数リストの要素数
	for(i=0; i<envc; i++){ // 環境変数リストを走査
		if(envv[i].len <= len) // 環境変数名が短すぎる場合はスキップ
			continue;
		v = envv[i].str; // 現在の環境変数文字列 (例: "GOMAXPROCS=4")
		for(j=0; j<len; j++) // 環境変数名を比較
			if(bs[j] != v[j])
				goto nomatch; // 一致しない場合は次の環境変数へ
		if(v[len] != '=') // 環境変数名の直後に'='がない場合はスキップ
			goto nomatch;
		return v+len+1; // '='の次の文字から値が始まるので、そのポインタを返す
	nomatch:;
	}
	return nil; // 見つからなかった場合
}

このコードは、Unix系OSやWindowsで一般的な、プロセス起動時に渡される環境変数リスト(environ ポインタや _environ グローバル変数など)を走査して環境変数を検索する実装です。Plan 9とは根本的に異なるアプローチを取っています。

src/pkg/runtime/sys_plan9_386.s および src/pkg/runtime/sys_plan9_amd64.sruntime·seek

これらのファイルには、Plan 9の seek システムコールを呼び出すためのアセンブリコードが追加されています。例えば、sys_plan9_386.s の関連部分:

TEXT runtime·seek(SB),7,$0
	MOVL	$39, AX  // システムコール番号 39 (seek) をAXレジスタにロード
	INT	$64      // Plan 9のシステムコール割り込み
	CMPL	AX, $-1  // 戻り値が-1 (エラー) かどうかチェック
	JNE	4(PC)    // エラーでなければ次の命令へ
	MOVL	a+0(FP), CX // エラーの場合、戻り値を格納するポインタをCXにロード
	MOVL	AX, 0(CX)   // 戻り値をポインタの先に格納
	MOVL	AX, 4(CX)   // 64ビット値の下位32ビットも格納 (Plan 9のseekは64ビットオフセットを返す)
	RET

このアセンブリコードは、GoのCコードから runtime·seek が呼び出された際に、実際のPlan 9システムコール seek を実行するためのゲートウェイとして機能します。システムコール番号をレジスタに設定し、特定の割り込み (INT $64) を発行することでカーネルに処理を依頼します。

関連リンク

参考にした情報源リンク

このコミットは、GoランタイムがPlan 9オペレーティングシステム上で環境変数を読み取れるようにするための getenv 関数の実装を追加します。これにより、GOMAXPROCSGOGC といったGoのランタイム設定を環境変数経由で制御できるようになります。

コミット

commit 432f18221fa77b814854053e751c125c9920886c
Author: Anthony Martin <ality@pbrane.org>
Date:   Mon Dec 17 11:07:40 2012 -0500

    runtime: implement getenv for Plan 9
    
    With this change the runtime can now read GOMAXPROCS, GOGC, etc.
    
    I'm not quite sure how we missed this.
    
    R=seed, lucio.dere, rsc
    CC=golang-dev
    https://golang.org/cl/6935062

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/432f18221fa77b814854053e751c125c9920886c

元コミット内容

runtime: implement getenv for Plan 9

With this change the runtime can now read GOMAXPROCS, GOGC, etc.

I'm not quite sure how we missed this.

R=seed, lucio.dere, rsc
CC=golang-dev
https://golang.org/cl/6935062

変更の背景

Goランタイムは、GOMAXPROCS(並行実行するOSスレッドの最大数)や GOGC(ガベージコレクションのトリガーとなるヒープ使用量の割合)など、いくつかの重要な設定を環境変数から読み取ります。しかし、このコミット以前は、Plan 9オペレーティングシステム向けのGoランタイムには、これらの環境変数を読み取るための getenv 関数が実装されていませんでした。

コミットメッセージにある「I'm not quite sure how we missed this.(なぜこれを見落としていたのか、よくわからない)」という記述は、この機能がGoランタイムの基本的な要件であり、Plan 9サポートの初期段階で実装されるべきだったにもかかわらず、これまで欠落していたことへの驚きを示しています。この欠落により、Plan 9上でGoプログラムを実行する際に、環境変数によるランタイムの挙動制御が不可能でした。このコミットは、この重要なギャップを埋めることを目的としています。

前提知識の解説

Plan 9 from Bell Labs

Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。Unixの設計思想をさらに推し進め、すべてのリソース(ファイル、デバイス、ネットワーク接続など)をファイルシステムとして表現するという「すべてはファイルである」という原則を徹底しています。環境変数も例外ではなく、/env ディレクトリ以下のファイルとして表現されます。例えば、PATH 環境変数の値は /env/PATH ファイルの内容として読み取られます。

Goランタイム (runtime)

Go言語のプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。このランタイムは、ガベージコレクション、ゴルーチン(軽量スレッド)のスケジューリング、チャネル通信、システムコールインターフェースなど、Goプログラムの実行に必要な低レベルの機能を提供します。ランタイムはGo言語自体で書かれている部分が多いですが、OSとの直接的なやり取りやパフォーマンスが重要な部分はC言語やアセンブリ言語で書かれています。

環境変数 (Environment Variables)

環境変数は、オペレーティングシステムが提供する動的な名前付きの値であり、実行中のプロセスに影響を与えます。プログラムはこれらの変数を読み取って、その挙動をカスタマイズできます。Goにおいては、GOMAXPROCSGOGC のようなランタイム設定の他にも、GOPATHGOROOT といった開発環境に関する設定にも利用されます。

getenv 関数

getenv は、指定された環境変数の値を取得するための標準的な関数です。Unix系システムでは通常、C標準ライブラリの一部として提供されます。Goランタイムのような低レベルのコードでは、OS固有のシステムコールやファイル操作を直接利用して、この機能を実現する必要があります。

システムコール (System Calls)

システムコールは、ユーザー空間のプログラムがオペレーティングシステムのカーネル空間のサービス(ファイルI/O、プロセス管理、メモリ管理など)を要求するためのインターフェースです。Plan 9では、ファイル操作(open, read, write, seek, close など)もシステムコールを通じて行われます。

技術的詳細

このコミットの主要な変更点は、Plan 9における環境変数の取得方法をGoランタイムに組み込んだことです。Plan 9では、環境変数は特殊なファイルシステム /env の下にファイルとして存在します。例えば、GOMAXPROCS の値を取得するには、/env/GOMAXPROCS というファイルを読み込む必要があります。

runtime·getenv 関数(C言語で記述)は、以下の手順で環境変数の値を取得します。

  1. ファイルパスの構築: 取得したい環境変数名(例: "GOMAXPROCS")を受け取り、それを /env/ と結合して完全なファイルパス(例: "/env/GOMAXPROCS")を構築します。
  2. ファイルのオープン: 構築したパスを使用して、runtime·open システムコールでファイルを読み取りモード (OREAD) で開きます。
  3. ファイルサイズの取得: runtime·seek システムコールを使用して、ファイルの末尾にシークし、ファイルのサイズ(バイト数)を取得します。これは、環境変数の値の長さを知るために必要です。
  4. メモリの割り当て: 取得したファイルサイズに基づいて、環境変数の値を格納するためのメモリを runtime·malloc で動的に割り当てます。
  5. 値の読み取り: runtime·pread システムコールを使用して、ファイルの先頭から割り当てたメモリに環境変数の値を読み込みます。
  6. ファイルのクローズ: runtime·close システムコールでファイルを閉じます。
  7. 値の返却: 読み取った環境変数の値が格納されたメモリへのポインタを返します。

この実装は、Plan 9の「すべてはファイルである」という哲学に忠実に従っており、環境変数を通常のファイルとして扱うことで、その値を取得しています。

また、このコミットでは、runtime·getenv の実装を runtime.c から env_plan9.c および env_posix.c に分離しています。これは、OS固有の環境変数取得ロジックをそれぞれのファイルにカプセル化し、コードのモジュール性と保守性を向上させるためのリファクタリングです。env_posix.c は、Unix系OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD)およびWindows向けの getenv 実装を含み、既存の環境変数リストを走査して値を見つけるアプローチを取っています。

さらに、Plan 9向けの seek システムコールを呼び出すためのアセンブリコードが sys_plan9_386.ssys_plan9_amd64.s に追加されています。これは、Goランタイムが直接OSのシステムコールを呼び出すために必要な低レベルのインターフェースです。

コアとなるコードの変更箇所

このコミットでは、以下のファイルが変更されています。

  • src/pkg/runtime/env_plan9.c (新規追加):
    • Plan 9向けの runtime·getenv 関数が実装されています。この関数は、/env/ ディレクトリ下のファイルを読み取ることで環境変数の値を取得します。
  • src/pkg/runtime/env_posix.c (新規追加):
    • POSIX互換OS(Unix系OSおよびWindows)向けの runtime·getenv 関数が実装されています。この関数は、プロセスに渡された環境変数リストを走査して値を見つけます。
    • syscall·setenv_c 関数も含まれており、cgoがロードされている場合にC環境を更新する役割を担います。
  • src/pkg/runtime/os_plan9.h:
    • Plan 9固有のシステムコール関数のプロトタイプ宣言が含まれるヘッダーファイルです。runtime·seek 関数のプロトタイプが追加されています。
  • src/pkg/runtime/runtime.c:
    • 以前 runtime.c にあった汎用的な runtime·getenv 関数と syscall·setenv_c 関数が削除され、OS固有のファイルに移動されました。
  • src/pkg/runtime/sys_plan9_386.s:
    • Plan 9/386アーキテクチャ向けの runtime·seek システムコールを呼び出すアセンブリコードが追加されています。
  • src/pkg/runtime/sys_plan9_amd64.s:
    • Plan 9/AMD64アーキテクチャ向けの runtime·seek システムコールを呼び出すアセンブリコードが追加されています。

コアとなるコードの解説

src/pkg/runtime/env_plan9.cruntime·getenv 関数

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "runtime.h"
#include "os_GOOS.h"

byte*
runtime·getenv(int8 *s)
{
	int32 fd, len, n, r;
	byte file[128];
	byte *p;

	len = runtime·findnull((byte*)s); // 環境変数名の長さを取得
	if(len > sizeof file-6) // パスバッファのサイズチェック
		return nil;

	runtime·memclr(file, sizeof file); // バッファをクリア
	runtime·memmove((void*)file, (void*)"/env/", 5); // "/env/" をコピー
	runtime·memmove((void*)(file+5), (void*)s, len); // 環境変数名を結合

	fd = runtime·open(file, OREAD); // ファイルを読み取りモードでオープン
	if(fd < 0)
		return nil;
	n = runtime·seek(fd, 0, 2); // ファイルの末尾にシークしてサイズを取得 (whence=2はSEEK_END)
	p = runtime·malloc(n+1); // 値を格納するメモリを割り当て (null終端のため+1)
	r = runtime·pread(fd, p, n, 0); // ファイルの先頭から値を読み取り
	runtime·close(fd); // ファイルをクローズ
	if(r < 0)
		return nil;
	return p; // 読み取った値へのポインタを返す
}

このコードは、Plan 9のファイルシステムを通じて環境変数を読み取る具体的な実装です。runtime·open, runtime·seek, runtime·pread, runtime·close といった関数は、Goランタイムが提供するOS固有のシステムコールラッパーです。

src/pkg/runtime/env_posix.cruntime·getenv 関数

// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin freebsd linux netbsd openbsd windows

#include "runtime.h"

Slice syscall·envs; // 環境変数リストを保持するスライス

byte*
runtime·getenv(int8 *s)
{
	int32 i, j, len;
	byte *v, *bs;
	String* envv;
	int32 envc;

	bs = (byte*)s;
	len = runtime·findnull(bs); // 検索する環境変数名の長さを取得
	envv = (String*)syscall·envs.array; // 環境変数リストの配列
	envc = syscall·envs.len; // 環境変数リストの要素数
	for(i=0; i<envc; i++){ // 環境変数リストを走査
		if(envv[i].len <= len) // 環境変数名が短すぎる場合はスキップ
			continue;
		v = envv[i].str; // 現在の環境変数文字列 (例: "GOMAXPROCS=4")
		for(j=0; j<len; j++) // 環境変数名を比較
			if(bs[j] != v[j])
				goto nomatch; // 一致しない場合は次の環境変数へ
		if(v[len] != '=') // 環境変数名の直後に'='がない場合はスキップ
			goto nomatch;
		return v+len+1; // '='の次の文字から値が始まるので、そのポインタを返す
	nomatch:;
	}
	return nil; // 見つからなかった場合
}

このコードは、Unix系OSやWindowsで一般的な、プロセス起動時に渡される環境変数リスト(environ ポインタや _environ グローバル変数など)を走査して環境変数を検索する実装です。Plan 9とは根本的に異なるアプローチを取っています。

src/pkg/runtime/sys_plan9_386.s および src/pkg/runtime/sys_plan9_amd64.sruntime·seek

これらのファイルには、Plan 9の seek システムコールを呼び出すためのアセンブリコードが追加されています。例えば、sys_plan9_386.s の関連部分:

TEXT runtime·seek(SB),7,$0
	MOVL	$39, AX  // システムコール番号 39 (seek) をAXレジスタにロード
	INT	$64      // Plan 9のシステムコール割り込み
	CMPL	AX, $-1  // 戻り値が-1 (エラー) かどうかチェック
	JNE	4(PC)    // エラーでなければ次の命令へ
	MOVL	a+0(FP), CX // エラーの場合、戻り値を格納するポインタをCXにロード
	MOVL	AX, 0(CX)   // 戻り値をポインタの先に格納
	MOVL	AX, 4(CX)   // 64ビット値の下位32ビットも格納 (Plan 9のseekは64ビットオフセットを返す)
	RET

このアセンブリコードは、GoのCコードから runtime·seek が呼び出された際に、実際のPlan 9システムコール seek を実行するためのゲートウェイとして機能します。システムコール番号をレジスタに設定し、特定の割り込み (INT $64) を発行することでカーネルに処理を依頼します。

関連リンク

参考にした情報源リンク