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

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

このコミットは、Go言語のランタイムにおいて、Plan 9オペレーティングシステム上でのセマフォのタイムアウト付き待機機能(semasleep)を修正するものです。具体的には、Plan 9カーネルに導入された新しいシステムコールtsemacquireを利用することで、これまで未実装であったタイムアウト付きセマフォ待機を適切に処理できるようになります。

コミット

  • コミットハッシュ: f5752848fde774c5c16c5e58f15558a253a03119
  • 作者: Akshat Kumar seed@mail.nanosouffle.net
  • 日付: 2012年5月16日(水)15:09:28 -0700
  • コミットメッセージ:
    pkg/runtime: Fix semasleep on Plan 9
    
    With the timed semacquire patch
    (kernel-tsemacquire) for Plan 9,
    we can now properly do a timed
    wait for the semaphore, in
    semasleep.
    
    R=golang-dev, rsc, rminnich, ality, r
    CC=0intro, golang-dev, john, mirtchovski
    https://golang.org/cl/6197046
    

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

https://github.com/golang/go/commit/f5752848fde774c5c16c5e58f15558a253a03119

元コミット内容

pkg/runtime: Fix semasleep on Plan 9

With the timed semacquire patch
(kernel-tsemacquire) for Plan 9,
we can now properly do a timed
wait for the semaphore, in
semasleep.

変更の背景

Go言語のランタイムは、ゴルーチンのスケジューリングや同期のためにセマフォを内部的に利用しています。特に、特定の時間だけセマフォの解放を待機する「タイムアウト付きセマフォ待機」は、ネットワークI/Oのタイムアウト処理や、特定のイベントが一定時間内に発生しない場合に処理を継続するようなシナリオで不可欠です。

しかし、このコミット以前のGoランタイムでは、Plan 9オペレーティングシステムにおいて、このタイムアウト付きセマフォ待機(semasleep関数)が適切に実装されていませんでした。src/pkg/runtime/thread_plan9.c内のruntime·semasleep関数には、タイムアウト付き待機が未実装である旨のコメントと、runtime·throw("semasleep: timed sleep not implemented on Plan 9");というパニックを発生させるコードが含まれていました。これは、Plan 9カーネルがタイムアウト付きのセマフォ取得をサポートするシステムコールを提供していなかったためです。

この問題を解決するため、Plan 9カーネルにtsemacquireという新しいシステムコールを導入するkernel-tsemacquireパッチが適用されました。このコミットは、そのカーネル側の変更を受けて、Goランタイムがこの新しいシステムコールを利用できるようにするためのものです。これにより、GoプログラムがPlan 9上でタイムアウト付きセマフォ待機を必要とする場合に、正しく動作するようになります。

前提知識の解説

Goランタイム

Go言語のプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。Goランタイムは、ゴルーチンのスケジューリング、メモリ管理(ガベージコレクション)、チャネル通信、同期プリミティブ(セマフォなど)の管理といった低レベルな処理を担当します。Goの並行処理モデルは、OSのスレッドではなく、このランタイムが管理するゴルーチンによって実現されており、OSの機能(システムコール)を効率的に利用するように設計されています。

セマフォ

セマフォは、並行プログラミングにおける同期プリミティブの一つです。複数のプロセスやスレッド(Goにおいてはゴルーチン)が共有リソースにアクセスする際の競合を防ぐために使用されます。

  • semacquire: セマフォを取得する操作。セマフォが利用可能になるまで待機します。
  • semrelease: セマフォを解放する操作。待機している他のプロセスやスレッドがあれば、そのうちの一つがセマフォを取得できるようになります。
  • タイムアウト付きセマフォ待機: 特定の時間だけセマフォの解放を待機し、その時間内にセマフォが解放されなければ待機を諦めて処理を継続する機能です。これにより、無期限の待機によるデッドロックや応答性の低下を防ぐことができます。

Plan 9

Plan 9 from Bell Labsは、ベル研究所で開発された分散オペレーティングシステムです。その設計思想は「すべてがファイルである」という点で特徴的であり、デバイス、ネットワーク接続、プロセス間通信など、あらゆるリソースがファイルシステムを通じてアクセスされます。

  • システムコール: ユーザープログラムがカーネルの機能を利用するためのインターフェースです。Plan 9では、システムコールは通常、特定のレジスタにシステムコール番号をセットし、INT $64のような命令(x86アーキテクチャの場合)を発行することで呼び出されます。
  • #c/pid: Plan 9における特殊なファイルパスで、現在のプロセスのID(PID)を取得するために使用されます。

アセンブリ言語 (.sファイル)

Goランタイムは、OS固有の低レベルな処理(システムコール呼び出しなど)を行うために、アセンブリ言語で書かれたコードを含んでいます。これらのファイルは通常、sys_plan9_386.sのようにOSとアーキテクチャを示す命名規則に従います。アセンブリコードは、C言語やGo言語から直接呼び出せないOSの機能や、パフォーマンスが非常に重要な部分で使用されます。

semasleep関数

Goランタイムにおけるsemasleep関数は、ゴルーチンがセマフォの解放を待機する際に、タイムアウトを設定するための関数です。この関数は、指定された時間(ナノ秒単位)だけ待機し、その時間内にセマフォが解放されれば成功、タイムアウトすれば失敗(またはタイムアウトを示す値)を返します。

技術的詳細

このコミットの核心は、Plan 9カーネルに新しく追加されたtsemacquireシステムコールをGoランタイムが利用するように変更することです。

  1. tsemacquireシステムコールの導入:

    • tsemacquireは、従来のsemacquireにタイムアウト機能を追加したものです。これにより、セマフォの取得を試みる際に、指定された時間(ミリ秒単位)だけ待機し、その時間内にセマフォが利用可能にならなければ、待機を中断して戻ることができます。
    • このシステムコールは、Plan 9カーネルのkernel-tsemacquireパッチによって提供されます。コミットメッセージから、このシステムコールがセマフォが追加された値(成功時は1、タイムアウト時は0)を返すことが期待されていることが示唆されています。
  2. os_plan9.hの変更:

    • runtime·plan9_tsemacquire関数のプロトタイプが追加されました。これは、C言語のコードからtsemacquireシステムコールを呼び出すための宣言です。
    • int32 runtime·plan9_tsemacquire(uint32 *addr, int32 ms);
      • addr: セマフォのアドレス(通常はカウンタ変数のアドレス)。
      • ms: タイムアウト時間(ミリ秒単位)。
  3. sys_plan9_386.sの変更:

    • runtime·plan9_tsemacquireという新しいアセンブリ関数が追加されました。
    • このアセンブリ関数は、Plan 9のシステムコール呼び出し規約に従って、tsemacquireシステムコールを呼び出します。
    • MOVL $52, AX: システムコール番号52をAXレジスタにロードします。これは、Plan 9におけるtsemacquireシステムコールの番号です。
    • INT $64: システムコールを実行するための割り込み命令です。
  4. thread_plan9.cruntime·semasleepの変更:

    • これまでタイムアウト付き待機が未実装であったため、パニックを発生させていたコード(runtime·throw("semasleep: timed sleep not implemented on Plan 9");)が削除されました。
    • コメントアウトされていたタイムアウト処理のブロックが有効化され、runtime·plan9_tsemacquireが呼び出されるようになりました。
    • 時間単位の変換: ns(ナノ秒)で渡されるタイムアウト時間を、tsemacquireが期待するms(ミリ秒)に変換するために、ns/1000000という計算が行われます。これは、1ミリ秒が1,000,000ナノ秒であるためです。
    • 戻り値のハンドリング: runtime·plan9_tsemacquireの戻り値retが1であれば成功(セマフォ取得)、それ以外であればタイムアウトまたは中断と判断されます。

これらの変更により、GoランタイムはPlan 9上で、指定された時間だけセマフォの解放を待機し、タイムアウトした場合には適切に処理を継続できるようになりました。

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

src/pkg/runtime/os_plan9.h

--- a/src/pkg/runtime/os_plan9.h
+++ b/src/pkg/runtime/os_plan9.h
@@ -13,6 +13,7 @@ int32	runtime·brk_(void*);
 int32	runtime·sleep(int32 ms);
 int32	runtime·rfork(int32 flags, void *stk, M *m, G *g, void (*fn)(void));
 int32	runtime·plan9_semacquire(uint32 *addr, int32 block);
+int32	runtime·plan9_tsemacquire(uint32 *addr, int32 ms);
 int32 	runtime·plan9_semrelease(uint32 *addr, int32 count);
 int32	runtime·notify(void (*fn)(void*, byte*));
 int32	runtime·noted(int32);
  • runtime·plan9_tsemacquire関数のプロトタイプ宣言が追加されました。

src/pkg/runtime/sys_plan9_386.s

--- a/src/pkg/runtime/sys_plan9_386.s
+++ b/src/pkg/runtime/sys_plan9_386.s
@@ -49,6 +49,11 @@ TEXT runtime·plan9_semacquire(SB),7,$0
 	INT	$64
 	RET
 
+TEXT runtime·plan9_tsemacquire(SB),7,$0
+	MOVL	$52, AX
+	INT	$64
+	RET
+
 TEXT runtime·notify(SB),7,$0
 	MOVL	$28, AX
 	INT	$64
  • runtime·plan9_tsemacquireという新しいアセンブリ関数が追加されました。
  • システムコール番号52AXレジスタにロードし、INT $64でシステムコールを呼び出しています。

src/pkg/runtime/thread_plan9.c

--- a/src/pkg/runtime/thread_plan9.c
+++ b/src/pkg/runtime/thread_plan9.c
@@ -43,7 +43,7 @@ static int32
 getpid(void)
 {
 	byte b[20], *c;
-	int32 fd, n;
+	int32 fd;
 
 	runtime·memclr(b, sizeof(b));
 	fd = runtime·open((byte*)"#c/pid", 0);
@@ -276,36 +276,18 @@ runtime·semasleep(int64 ns)
 	int32 ms;
 
 	if(ns >= 0) {
-\t\t// TODO: Plan 9 needs a new system call, tsemacquire.
-\t\t// The kernel implementation is the same as semacquire
-\t\t// except with a tsleep and check for timeout.
-\t\t// It would be great if the implementation returned the
-\t\t// value that was added to the semaphore, so that on
-\t\t// timeout the return value would be 0, on success 1.
-\t\t// Then the error string does not have to be parsed
-\t\t// to detect timeout.
-\t\t//
-\t\t// If a negative time indicates no timeout, then
-\t\t// semacquire can be implemented (in the kernel)
-\t\t// as tsemacquire(p, v, -1).\n-\t\truntime·throw(\"semasleep: timed sleep not implemented on Plan 9\");
-\n-\t\t/*
-\t\tif(ns < 0)\n-\t\t\tms = -1;\n-\t\telse if(ns/1000 > 0x7fffffffll)\n+\t\tif(ns/1000000 > 0x7fffffffll)\n \t\t\tms = 0x7fffffff;\n \t\telse\n-\t\t\tms = ns/1000;\n-\t\tret = runtime·plan9_tsemacquire(&m->waitsemacount, 1, ms);\n+\t\t\tms = ns/1000000;\n+\t\tret = runtime·plan9_tsemacquire(&m->waitsemacount, ms);\n \t\tif(ret == 1)\n \t\t\treturn 0;  // success\n \t\treturn -1;  // timeout or interrupted\n-\t\t*/\n \t}\n \n \twhile(runtime·plan9_semacquire(&m->waitsemacount, 1) < 0) {\n-\t\t/* interrupted; try again */\n+\t\t/* interrupted; try again (c.f. lock_sema.c) */\n \t}\n \treturn 0;  // success\n }\n```
- `getpid`関数から未使用の変数`n`が削除されました。
- `runtime·semasleep`関数内の、タイムアウト付き待機が未実装であることを示すコメントとパニックを発生させるコードが削除されました。
- コメントアウトされていたタイムアウト処理のブロックが有効化されました。
- `ns`(ナノ秒)を`ms`(ミリ秒)に変換する計算が`ns/1000000`に変更されました。
- `runtime·plan9_tsemacquire`が`m->waitsemacount`と計算された`ms`を引数に呼び出されるようになりました。
- 戻り値`ret`が1であれば成功、それ以外であればタイムアウトまたは中断として処理されます。
- `while`ループ内のコメントがより具体的な参照(`lock_sema.c`)を含むように変更されました。

## コアとなるコードの解説

このコミットの主要な変更は、`src/pkg/runtime/thread_plan9.c`内の`runtime·semasleep`関数に集約されています。

1.  **`runtime·semasleep`のタイムアウト処理の有効化**:
    以前は、Plan 9上でのタイムアウト付きセマフォ待機がサポートされていなかったため、`runtime·semasleep`関数に負でないタイムアウト値が渡されると、`runtime·throw("semasleep: timed sleep not implemented on Plan 9");`によってプログラムがパニックを起こしていました。このコミットでは、このパニックを発生させる行が削除され、代わりにコメントアウトされていたタイムアウト処理のブロックが有効化されました。

2.  **ナノ秒からミリ秒への変換**:
    `runtime·semasleep`は`ns`(ナノ秒)単位でタイムアウト時間を受け取りますが、Plan 9の新しい`tsemacquire`システムコールは`ms`(ミリ秒)単位のタイムアウト時間を期待します。そのため、`ms = ns/1000000;`という計算によって、ナノ秒をミリ秒に変換しています。`0x7fffffff`は32ビット符号付き整数の最大値であり、非常に大きなタイムアウト値が指定された場合にオーバーフローを防ぐための上限設定です。

3.  **`runtime·plan9_tsemacquire`の呼び出し**:
    変換されたミリ秒単位のタイムアウト値`ms`を使用して、`runtime·plan9_tsemacquire(&m->waitsemacount, ms);`が呼び出されます。
    -   `&m->waitsemacount`: これは、現在のM(マシン、OSスレッドに相当)に関連付けられたセマフォカウンタのアドレスです。Goランタイムは、ゴルーチンが待機状態に入る際に、このセマフォを利用してOSスレッドをブロックします。
    -   `ms`: 計算されたタイムアウト時間です。

4.  **システムコールへの橋渡し**:
    `runtime·plan9_tsemacquire`は、`src/pkg/runtime/os_plan9.h`で宣言され、`src/pkg/runtime/sys_plan9_386.s`で実装されています。アセンブリコードでは、システムコール番号`52`を`AX`レジスタにセットし、`INT $64`命令を実行することで、Plan 9カーネルの`tsemacquire`システムコールを呼び出します。このアセンブリラッパーが、C言語のランタイムコードとPlan 9カーネルの間の橋渡しをします。

5.  **戻り値の処理**:
    `runtime·plan9_tsemacquire`の戻り値`ret`は、セマフォの取得が成功したか、タイムアウトしたかを示します。
    -   `if(ret == 1)`: 戻り値が1の場合、セマフォの取得が成功したことを意味し、`runtime·semasleep`は`0`(成功)を返します。
    -   `return -1;`: それ以外の場合(通常はタイムアウト)、`runtime·semasleep`は`-1`(タイムアウトまたは中断)を返します。

この一連の変更により、GoランタイムはPlan 9上で、OSカーネルが提供する新しい`tsemacquire`システムコールを介して、信頼性の高いタイムアウト付きセマフォ待機を実現できるようになりました。これにより、Goプログラムの応答性と堅牢性が向上します。

## 関連リンク

- Go言語の公式ドキュメント: [https://go.dev/doc/](https://go.dev/doc/)
- Plan 9 from Bell Labs: [https://9p.io/plan9/](https://9p.io/plan9/)
- Goのランタイムに関する詳細(Goのソースコードリポジトリ): [https://github.com/golang/go/tree/master/src/runtime](https://github.com/golang/go/tree/master/src/runtime)

## 参考にした情報源リンク

- Go CL 6197046 (このコミットのChange List): [https://golang.org/cl/6197046](https://golang.org/cl/6197046)
- Plan 9 System Calls (一般的な情報源): [https://man.cat-v.org/plan_9/2/intro](https://man.cat-v.org/plan_9/2/intro)
- Go runtime source code (GitHub): [https://github.com/golang/go/](https://github.com/golang/go/)
- Go runtime semaphores (一般的な情報源): [https://go.dev/src/runtime/sema.go](https://go.dev/src/runtime/sema.go) (これは一般的なGoのセマフォ実装であり、Plan 9固有のものではないが、セマフォの概念理解に役立つ)
- Plan 9 `semacquire` and `tsemacquire` (一般的な情報源): [https://man.cat-v.org/plan_9/2/semacquire](https://man.cat-v.org/plan_9/2/semacquire) (このコミットの時点では`tsemacquire`はまだ公式ドキュメントにない可能性が高いが、`semacquire`の文脈で理解を深める)
- x86 Assembly `INT` instruction (一般的な情報源): [https://en.wikipedia.org/wiki/INT_(x86_instruction)](https://en.wikipedia.org/wiki/INT_(x86_instruction))