[インデックス 10299] ファイルの概要
このコミットは、Go言語のランタイムにおける src/pkg/runtime/proc.c
ファイルに対して行われたものです。proc.c
はGoランタイムの非常に重要な部分であり、主にゴルーチン(goroutine)のスケジューリング、スタック管理、メモリ割り当て、パニック(panic)とリカバリー(recover)の処理、そしてCgoとの連携など、Goプログラムの実行を支える低レベルな機能が実装されています。このファイルは、Goの並行処理モデルと効率的なリソース管理の根幹をなす部分です。
コミット
- コミットハッシュ:
4ac425fcddd7e3a923fe59f2375a2a75fa18ed33
- 作者: Ian Lance Taylor iant@golang.org
- コミット日時: 2011年11月8日 火曜日 18:16:25 -0800
- 変更ファイル:
src/pkg/runtime/proc.c
- 変更概要: 56行の追加、12行の削除
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4ac425fcddd7e3a923fe59f2375a2a75fa18ed33
元コミット内容
runtime: add comments for various functions in proc.c
R=rsc
CC=golang-dev
https://golang.org/cl/5357047
変更の背景
このコミットの主な目的は、src/pkg/runtime/proc.c
内の様々な関数にコメントを追加することです。Go言語のランタイムは非常に複雑で、低レベルな操作が多いため、コードの可読性と理解を深めることが重要です。特に、スケジューリング、スタック管理、エラーハンドリングといったGoのコア機能に関わる部分は、その動作原理を正確に把握することが開発者にとって不可欠です。
コメントの追加は、以下の点で重要です。
- 可読性の向上: 複雑なロジックや最適化が施された関数について、その目的、引数、戻り値、副作用などを明確にすることで、コードを読み解く労力を軽減します。
- メンテナンス性の向上: 将来の機能追加やバグ修正の際に、既存のコードの意図を素早く理解できるようになり、誤った変更を防ぎます。
- 新規開発者のオンボーディング: Goランタイムに初めて触れる開発者が、コードベースの構造と各コンポーネントの役割を効率的に学習できるようになります。
- デバッグの支援: 特定の関数の動作が不明瞭な場合、コメントがデバッグのヒントとなり、問題の特定と解決を早めます。
このコミットは、Goランタイムの内部構造をよりアクセスしやすく、理解しやすいものにするための継続的な努力の一環と言えます。
前提知識の解説
このコミットの変更内容を深く理解するためには、以下のGoランタイムに関する基本的な概念を把握しておく必要があります。
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション(GC)、ゴルーチン(goroutine)のスケジューリング、チャネル(channel)の操作、メモリ管理、スタック管理、パニックとリカバリーの処理、システムコールとの連携などが含まれます。Goプログラムは、OSのプロセス上で直接実行されるのではなく、Goランタイムという抽象化レイヤーの上で動作します。
Goスケジューラ (Go Scheduler)
Goのスケジューラは、Goの並行処理モデルの核心です。OSのスレッド(M: Machine)上で、多数のゴルーチン(G: Goroutine)を効率的に実行するために、論理プロセッサ(P: Processor)という概念を導入しています。
- G (Goroutine): Goにおける軽量な実行単位。数KB程度のスタックを持ち、数百万個作成することも可能です。
- M (Machine): OSのスレッドに対応します。Goランタイムは、OSスレッドをMとして抽象化し、その上でGを実行します。
- P (Processor): 論理プロセッサ。MとGの間に位置し、MがGを実行するためのコンテキストを提供します。Pは実行可能なGのキューを保持し、MはPからGを取得して実行します。
GOMAXPROCS
環境変数によってPの数を制御できます。
proc.c
は、このスケジューラの主要なロジック、特にMとGの管理、Gの生成と破棄、Gの実行状態の遷移などを担当しています。
スタック管理 (Stack Management)
Goのゴルーチンは、可変サイズのスタックを持ちます。初期スタックサイズは小さく(通常は数KB)、必要に応じて自動的に拡張されます。このスタックの拡張・縮小は、Goランタイムによって透過的に行われます。
- スタックの成長 (Stack Growth): 関数呼び出しによってスタックが不足しそうになると、ランタイムはより大きな新しいスタックセグメントを割り当て、古いスタックの内容を新しいスタックにコピーします。このプロセスは「スタックスプリット(stack split)」と呼ばれます。
morestack
/lessstack
: スタックの成長と縮小を処理するためのランタイム関数です。コンパイラは、関数プロローグにスタックチェックコードを挿入し、スタックが不足しそうな場合にmorestack
を呼び出すようにします。runtime·newstack
/runtime·oldstack
:morestack
やreflect·call
などから呼ばれ、新しいスタックセグメントの割り当てや、古いスタックセグメントへの復帰を処理します。
go
ステートメントと defer
ステートメントの内部動作
go
ステートメント:go
キーワードに続く関数呼び出しは、新しいゴルーチンを生成し、そのゴルーチン内で関数を実行します。ランタイム内部では、runtime·newproc
やruntime·newproc1
といった関数が呼ばれ、新しいゴルーチン構造体(G)が割り当てられ、実行キューに追加されます。defer
ステートメント:defer
キーワードに続く関数呼び出しは、現在の関数の実行が終了する直前(returnする前、またはpanicが発生する前)に実行されるようにスケジュールされます。ランタイム内部では、runtime·deferproc
が呼ばれ、遅延実行される関数とその引数が現在のゴルーチンの遅延実行リストに登録されます。関数が終了する際にはruntime·deferreturn
が呼ばれ、登録された遅延関数が実行されます。
panic
と recover
の内部動作
panic
: Goにおける実行時エラーのメカニズムです。panic
が発生すると、現在のゴルーチンの実行は中断され、遅延関数が逆順に実行されながらスタックがアンワインド(unwind)されます。recover
:panic
から回復するための組み込み関数です。recover
はdefer
関数内でのみ有効で、panic
が発生している場合にその値を捕捉し、パニックによるスタックアンワインドを停止させ、通常の実行フローに戻します。proc.c
には、runtime·panic
やrecovery
、runtime·recover
といった関数が実装されており、これらのメカニズムを支えています。
textflag 7
の意味 (no split)
Goのコンパイラは、関数がスタックを分割(成長)する必要があるかどうかを判断し、必要に応じて morestack
への呼び出しを挿入します。しかし、一部のランタイム関数、特にスタック管理やゴルーチン生成に関わる関数は、スタックの分割中に呼び出されると問題を引き起こす可能性があります。
#pragma textflag 7
は、Goのコンパイラに対する指示で、その関数がスタックを分割しない(no split
)ことを意味します。これは、関数が非常に短い場合や、スタックポインタやフレームポインタに直接アクセスするような低レベルな操作を行う場合に用いられます。スタック分割中にこれらの関数が実行されると、スタックの状態が不安定になり、予期せぬ動作を引き起こす可能性があるため、明示的にスタック分割を抑制します。
このコミットでは、runtime·newproc
、runtime·deferproc
、runtime·deferreturn
、runtime·recover
といった関数にこの textflag 7
が適用されており、追加されたコメントでその理由が説明されています。これは、これらの関数が引数にアクセスする方法や、スタックの状態に依存する性質があるためです。
技術的詳細
このコミットでコメントが追加された主な関数とその技術的詳細は以下の通りです。
-
matchmg
:- 役割: 必要に応じて新しいM(OSスレッド)を起動し、実行可能なゴルーチンを探させるための関数です。
mcpumax
(最大M数)までMを起動します。 - 詳細: スケジューラがロックされた状態で呼び出されます。既存のMがゴルーチンを探している場合でも、必要に応じて新しいMを起動し、システムのリソースを最大限に活用してゴルーチンの実行を促進します。
- 役割: 必要に応じて新しいM(OSスレッド)を起動し、実行可能なゴルーチンを探させるための関数です。
-
startm
:- 役割: 新しいM(OSスレッド)を作成し、そのMが
runtime·mstart
関数から実行を開始するように設定します。 - 詳細:
runtime·mstart
は、新しく起動されたMが最初に実行するランタイム関数であり、そのMの初期化やゴルーチンの取得・実行ループへの移行を担います。
- 役割: 新しいM(OSスレッド)を作成し、そのMが
-
runtime·oldstack
:- 役割: 新しいスタックセグメントを割り当てた関数から戻る際に、古いスタックセグメントに戻るために呼び出されます。
- 詳細:
runtime·lessstack
から呼ばれることを想定しています。関数の戻り値はm->cret
に格納されており、この関数はgobuf
を使って古いスタックフレームにジャンプし、実行を再開します。
-
runtime·newstack
:- 役割:
reflect·call
やruntime·morestack
から、新しいスタックセグメントが必要な場合に呼び出されます。 - 詳細:
m->moreframesize
バイト分の新しいスタックを割り当て、m->moreargsize
バイトの引数を新しいフレームにコピーします。その後、m->morepc
の関数がruntime·lessstack
によって呼び出されたかのように動作します。これは、スタックの成長メカニズムの核心部分です。
- 役割:
-
mstackalloc
:- 役割:
runtime·malg
から呼ばれるフックで、スケジューラスタック上でruntime·stackalloc
を呼び出すためのものです。 - 詳細:
runtime·stackalloc
は、新しいスタックセグメントを割り当てる際に、スタックの成長を試みないように、スケジューラスタック上で呼び出される必要があります。この関数は、その制約を満たすために存在します。
- 役割:
-
runtime·malg
:- 役割: 新しいG(ゴルーチン)を割り当て、
stacksize
バイト分のスタックを確保します。 - 詳細: 新しいゴルーチン構造体を初期化し、そのスタックを割り当てます。Goプログラムで新しいゴルーチンが起動される際に内部的に呼び出される重要な関数です。
- 役割: 新しいG(ゴルーチン)を割り当て、
-
runtime·newproc
:- 役割:
go
ステートメントによって新しいGを生成し、fn
関数をsiz
バイトの引数で実行するように設定します。 - 詳細: コンパイラは
go
ステートメントをこの関数への呼び出しに変換します。この関数は、引数が&fn
の後に連続して利用可能であることを前提としているため、スタック分割が発生すると引数がコピーされない可能性があるため、#pragma textflag 7
(スタック分割なし)が適用されています。
- 役割:
-
runtime·newproc1
:- 役割:
fn
関数をnarg
バイトの引数(argp
から開始)で実行し、nret
バイトの結果を返す新しいGを生成します。 - 詳細:
callerpc
は、このGを作成したgo
ステートメントのアドレスです。新しいGは実行待ちのGのキューに追加されます。runtime·newproc
から内部的に呼び出されます。
- 役割:
-
runtime·deferproc
:- 役割:
defer
ステートメントによって遅延関数fn
をsiz
バイトの引数で登録します。 - 詳細: コンパイラは
defer
ステートメントをこの関数への呼び出しに変換します。runtime·newproc
と同様に、引数が&fn
の後に連続して利用可能であることを前提としているため、#pragma textflag 7
が適用されています。
- 役割:
-
runtime·deferreturn
:- 役割: 遅延関数が存在する場合にそれを実行します。
- 詳細: コンパイラは、
defer
を呼び出す任意の関数の終わりにこの関数への呼び出しを挿入します。遅延関数が存在する場合、runtime·jmpdefer
を呼び出して遅延関数にジャンプし、deferreturn
が呼び出された直前のポイントで呼び出されたかのように見せかけます。これにより、遅延関数がなくなるまでdeferreturn
が繰り返し呼び出されます。呼び出し元のフレームを再利用して遅延関数を呼び出すため、#pragma textflag 7
が適用されています。
-
rundefer
:- 役割: 現在のゴルーチンのすべての遅延関数を実行します。
- 詳細:
deferreturn
が繰り返し呼び出されることで、この関数が最終的にすべての遅延関数を処理します。
-
printpanics
:- 役割: 現在アクティブなすべてのパニック情報を出力します。
- 詳細: プログラムがクラッシュする際に、デバッグ情報としてパニックスタックを出力するために使用されます。
-
runtime·panic
:- 役割: 組み込み関数
panic
の実装です。 - 詳細:
panic
が発生した際に、ランタイムがどのようにスタックをアンワインドし、遅延関数を実行するかを制御します。
- 役割: 組み込み関数
-
recovery
:- 役割: パニック後に遅延関数が
recover
を呼び出した際に、スタックをアンワインドし、遅延関数の呼び出し元が正常に復帰したかのように実行を継続するように調整します。 - 詳細:
recover
が成功した場合の実行フローを制御する重要な関数です。
- 役割: パニック後に遅延関数が
-
runtime·recover
:- 役割: 組み込み関数
recover
の実装です。 - 詳細: 信頼性高く呼び出し元のスタックセグメントを見つける必要があるため、
#pragma textflag 7
が適用されています。
- 役割: 組み込み関数
-
runtime·Gosched
:- 役割:
runtime.Gosched
関数の実装です。 - 詳細: 現在のゴルーチンを一時停止し、他のゴルーチンにCPUを譲ります。スケジューラに制御を戻すために使用されます。
- 役割:
-
runtime·gomaxprocsfunc
:- 役割:
runtime.GOMAXPROCS
関数の実装です。 - 詳細: 論理プロセッサ(P)の数を設定します。スケジューラの動作に直接影響を与えます。
- 役割:
-
runtime·sigprof
:- 役割:
SIGPROF
シグナルを受信した場合に呼び出されます。 - 詳細: CPUプロファイリングのために使用され、定期的にスタックトレースを収集します。
- 役割:
-
runtime·setcpuprofilerate
:- 役割:
fn
関数をhz
回/秒の頻度でトレースバックとともに呼び出すように設定します。 - 詳細: CPUプロファイリングのレートを設定するための関数です。
- 役割:
-
os·setenv_c
:- 役割: Cgoがロードされている場合にC環境を更新します。
- 詳細:
os.Setenv
から呼び出され、Cライブラリの環境変数を設定する際に使用されます。
これらのコメントは、Goランタイムの内部動作、特にスタック管理、ゴルーチン生成、パニック/リカバリー、そしてスケジューリングの複雑な相互作用を理解する上で非常に貴重な情報を提供しています。
コアとなるコードの変更箇所
このコミットでは、src/pkg/runtime/proc.c
ファイルに多数のコメントが追加され、一部の古いコメントが削除されています。以下に、主要な変更箇所を抜粋して示します。
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -739,8 +739,6 @@ struct CgoThreadStart
};
// Kick off new m's as needed (up to mcpumax).
-// There are already `other' other cpus that will
-// start looking for goroutines shortly.
// Sched is locked.
static void
matchmg(void)
@@ -763,6 +761,7 @@ matchmg(void)
}
}
+// Create a new m. It will start off with a call to runtime·mstart.
static M*
startm(void)
{
@@ -995,6 +994,9 @@ runtime·exitsyscall(void)
g->gcstack = nil;
}
+// Called from runtime·lessstack when returning from a function which
+// allocated a new stack segment. The function's return value is in
+// m->cret.
void
runtime·oldstack(void)
{
@@ -1026,6 +1031,11 @@ runtime·oldstack(void)
runtime·gogo(&old.gobuf, m->cret);
}
+// Called from reflect·call or from runtime·morestack when a new
+// stack segment is needed. Allocate a new stack big enough for
+// m->moreframesize bytes, copy m->moreargsize bytes to the new frame,
+// and then act as though runtime·lessstack called the function at
+// m->morepc.
void
runtime·newstack(void)
{
@@ -1113,6 +1124,10 @@ runtime·newstack(void)
*(int32*)345 = 123; // never return
}
+// Hook used by runtime·malg to call runtime·stackalloc on the
+// scheduler stack. This exists because runtime·stackalloc insists
+// on being called on the scheduler stack, to avoid trying to grow
+// the stack while allocating a new stack segment.
static void
mstackalloc(G *gp)
{
@@ -1120,6 +1135,7 @@ mstackalloc(G *gp)
runtime·gogo(&gp->sched, 0);
}
+// Allocate a new g, with a stack big enough for stacksize bytes.
G*
runtime·malg(int32 stacksize)
{
@@ -1146,15 +1162,13 @@ runtime·malg(int32 stacksize)
return newg;
}
-/*
- * Newproc and deferproc need to be textflag 7
- * (no possible stack split when nearing overflow)
- * because they assume that the arguments to fn
- * are available sequentially beginning at &arg0.
- * If a stack split happened, only the one word
- * arg0 would be copied. It's okay if any functions
- * they call split the stack below the newproc frame.
- */
+// Create a new g running fn with siz bytes of arguments.
+// Put it on the queue of g's waiting to run.
+// The compiler turns a go statement into a call to this.
+// Cannot split the stack because it assumes that the arguments
+// are available sequentially after &fn; they would not be
+// copied if a stack split occurred. It's OK for this to call
+// functions that split the stack.
#pragma textflag 7
void
runtime·newproc(int32 siz, byte* fn, ...)
@@ -1168,6 +1182,10 @@ runtime·newproc(int32 siz, byte* fn, ...)\
runtime·newproc1(fn, argp, siz, 0, runtime·getcallerpc(&siz));
}
+// Create a new g running fn with narg bytes of arguments starting
+// at argp and returning nret bytes of results. callerpc is the
+// address of the go statement that created this. The new g is put
+// on the queue of g's waiting to run.
G*
runtime·newproc1(byte *fn, byte *argp, int32 narg, int32 nret, void *callerpc)
{
@@ -1228,6 +1246,12 @@ runtime·newproc1(byte *fn, byte *argp, int32 narg, int32 nret, void *callerpc)
//printf(" goid=%d\n", newg->goid);\
}
+// Create a new deferred function fn with siz bytes of arguments.
+// The compiler turns a defer statement into a call to this.
+// Cannot split the stack because it assumes that the arguments
+// are available sequentially after &fn; they would not be
+// copied if a stack split occurred. It's OK for this to call
+// functions that split the stack.
#pragma textflag 7
uintptr
runtime·deferproc(int32 siz, byte* fn, ...)
@@ -1256,6 +1280,16 @@ runtime·deferproc(int32 siz, byte* fn, ...)
return 0;
}
+// Run a deferred function if there is one.
+// The compiler inserts a call to this at the end of any
+// function which calls defer.
+// If there is a deferred function, this will call runtime·jmpdefer,
+// which will jump to the deferred function such that it appears
+// to have been called by the caller of deferreturn at the point
+// just before deferreturn was called. The effect is that deferreturn
+// is called again and again until there are no more deferred functions.
+// Cannot split the stack because we reuse the caller's frame to
+// call the deferred function.
#pragma textflag 7
void
runtime·deferreturn(uintptr arg0)
@@ -1277,6 +1311,7 @@ runtime·deferreturn(uintptr arg0)
runtime·jmpdefer(fn, argp);
}
+// Run all deferred functions for the current goroutine.
static void
rundefer(void)
{
@@ -1318,6 +1353,7 @@ unwindstack(G *gp, byte *sp)
}
}
+// Print all currently active panics. Used when crashing.
static void
printpanics(Panic *p)
{
@@ -1334,6 +1370,7 @@ printpanics(Panic *p)
static void recovery(G*);
+// The implementation of the predeclared function panic.
void
runtime·panic(Eface e)
{
@@ -1376,6 +1413,9 @@ runtime·panic(Eface e)
runtime·dopanic(0);
}
+// Unwind the stack after a deferred function calls recover
+// after a panic. Then arrange to continue running as though
+// the caller of the deferred function returned normally.
static void
recovery(G *gp)
{
@@ -1407,7 +1447,10 @@ recovery(G *gp)
runtime·gogo(&gp->sched, 1);
}
-#pragma textflag 7 /* no split, or else g->stackguard is not the stack for fp */
+// The implementation of the predeclared function recover.
+// Cannot split the stack because it needs to reliably
+// find the stack segment of its caller.
+#pragma textflag 7
void
runtime·recover(byte *argp, Eface ret)
{
@@ -1519,6 +1562,7 @@ runtime·Gosched(void)
runtime·gosched();
}
+// Implementation of runtime.GOMAXPROCS.
// delete when scheduler is stronger
int32
runtime·gomaxprocsfunc(int32 n)
@@ -1634,6 +1678,7 @@ static struct {
uintptr pcbuf[100];
} prof;
+// Called if we receive a SIGPROF signal.
void
runtime·sigprof(uint8 *pc, uint8 *sp, uint8 *lr, G *gp)
{
@@ -1653,6 +1698,7 @@ runtime·sigprof(uint8 *pc, uint8 *sp, uint8 *lr, G *gp)
runtime·unlock(&prof);\
}
+// Arrange to call fn with a traceback hz times a second.
void
runtime·setcpuprofilerate(void (*fn)(uintptr*, int32), int32 hz)
{
@@ -1683,6 +1729,8 @@ runtime·setcpuprofilerate(void (*fn)(uintptr*, int32), int32 hz)
void (*libcgo_setenv)(byte**);
+// Update the C environment if cgo is loaded.
+// Called from os.Setenv.
void
os·setenv_c(String k, String v)
{
コアとなるコードの解説
上記の変更箇所は、Goランタイムの proc.c
内の様々な関数に、その役割、動作原理、および特定の制約(特にスタック分割に関する textflag 7
の理由)を説明するコメントを追加しています。
例えば、runtime·newproc
の変更を見てみましょう。
変更前:
/*
* Newproc and deferproc need to be textflag 7
* (no possible stack split when nearing overflow)
* because they assume that the arguments to fn
* are available sequentially beginning at &arg0.
* If a stack split happened, only the one word
* arg0 would be copied. It's okay if any functions
* they call split the stack below the newproc frame.
*/
#pragma textflag 7
void
runtime·newproc(int32 siz, byte* fn, ...)
変更後:
// Create a new g running fn with siz bytes of arguments.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
// Cannot split the stack because it assumes that the arguments
// are available sequentially after &fn; they would not be
// copied if a stack split occurred. It's OK for this to call
// functions that split the stack.
#pragma textflag 7
void
runtime·newproc(int32 siz, byte* fn, ...)
この変更では、古いCスタイルのコメントブロックが、より現代的なC++スタイルの行コメントに置き換えられています。内容は基本的に同じですが、より簡潔で読みやすくなっています。
新しいコメントは、以下の点を明確にしています。
- 関数の目的: 「
fn
をsiz
バイトの引数で実行する新しいGを作成する。」 - スケジューリング: 「実行待ちのGのキューに入れる。」
- コンパイラの役割: 「コンパイラは
go
ステートメントをこれへの呼び出しに変換する。」 - スタック分割の制約: 「スタックを分割できない。なぜなら、引数が
&fn
の後に連続して利用可能であることを前提としており、スタック分割が発生した場合、それらはコピーされないからである。」 - 許容される動作: 「この関数が呼び出す関数が
newproc
フレームの下でスタックを分割することは問題ない。」
特に「Cannot split the stack...」の部分は、#pragma textflag 7
がなぜ必要であるかという技術的な理由を詳細に説明しており、Goランタイムの低レベルなスタック管理の複雑さを浮き彫りにしています。引数がスタック上に連続して配置されていることを前提とする関数では、スタック分割によって引数の配置が変更されると、関数が正しく動作しなくなる可能性があるため、このような制約が設けられています。
他の関数についても同様に、その機能、呼び出し元、呼び出し先の関係、そしてスタック管理やスケジューリングに関する特定の制約が詳細にコメントされています。これにより、Goランタイムの内部構造がより透過的になり、開発者がその動作を深く理解するための手助けとなります。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
- Goランタイムのソースコード: https://github.com/golang/go/tree/master/src/runtime
- Goスケジューラに関するブログ記事 (例: The Go scheduler): https://go.dev/blog/go11sched (Go 1.1のスケジューラに関する古い記事ですが、基本的な概念は参考になります)
参考にした情報源リンク
- Go言語のソースコード (
src/pkg/runtime/proc.c
および関連ファイル) - Go言語の公式ドキュメント
- Goランタイムに関する一般的な技術記事やブログポスト (具体的なURLは検索時に参照しましたが、特定の記事を直接引用したものではありません)
[インデックス 10299] ファイルの概要
このコミットは、Go言語のランタイムにおける src/pkg/runtime/proc.c
ファイルに対して行われたものです。proc.c
はGoランタイムの非常に重要な部分であり、主にゴルーチン(goroutine)のスケジューリング、スタック管理、メモリ割り当て、パニック(panic)とリカバリー(recover)の処理、そしてCgoとの連携など、Goプログラムの実行を支える低レベルな機能が実装されています。このファイルは、Goの並行処理モデルと効率的なリソース管理の根幹をなす部分です。
コミット
- コミットハッシュ:
4ac425fcddd7e3a923fe59f2375a2a75fa18ed33
- 作者: Ian Lance Taylor iant@golang.org
- コミット日時: 2011年11月8日 火曜日 18:16:25 -0800
- 変更ファイル:
src/pkg/runtime/proc.c
- 変更概要: 56行の追加、12行の削除
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4ac425fcddd7e3a923fe59f2375a2a75fa18ed33
元コミット内容
runtime: add comments for various functions in proc.c
R=rsc
CC=golang-dev
https://golang.org/cl/5357047
変更の背景
このコミットの主な目的は、src/pkg/runtime/proc.c
内の様々な関数にコメントを追加することです。Go言語のランタイムは非常に複雑で、低レベルな操作が多いため、コードの可読性と理解を深めることが重要です。特に、スケジューリング、スタック管理、エラーハンドリングといったGoのコア機能に関わる部分は、その動作原理を正確に把握することが開発者にとって不可欠です。
コメントの追加は、以下の点で重要です。
- 可読性の向上: 複雑なロジックや最適化が施された関数について、その目的、引数、戻り値、副作用などを明確にすることで、コードを読み解く労力を軽減します。
- メンテナンス性の向上: 将来の機能追加やバグ修正の際に、既存のコードの意図を素早く理解できるようになり、誤った変更を防ぎます。
- 新規開発者のオンボーディング: Goランタイムに初めて触れる開発者が、コードベースの構造と各コンポーネントの役割を効率的に学習できるようになります。
- デバッグの支援: 特定の関数の動作が不明瞭な場合、コメントがデバッグのヒントとなり、問題の特定と解決を早めます。
このコミットは、Goランタイムの内部構造をよりアクセスしやすく、理解しやすいものにするための継続的な努力の一環と言えます。
前提知識の解説
このコミットの変更内容を深く理解するためには、以下のGoランタイムに関する基本的な概念を把握しておく必要があります。
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション(GC)、ゴルーチン(goroutine)のスケジューリング、チャネル(channel)の操作、メモリ管理、スタック管理、パニックとリカバリーの処理、システムコールとの連携などが含まれます。Goプログラムは、OSのプロセス上で直接実行されるのではなく、Goランタイムという抽象化レイヤーの上で動作します。
Goスケジューラ (Go Scheduler)
Goのスケジューラは、Goの並行処理モデルの核心です。OSのスレッド(M: Machine)上で、多数のゴルーチン(G: Goroutine)を効率的に実行するために、論理プロセッサ(P: Processor)という概念を導入しています。これはM-P-Gモデルとして知られています。
- G (Goroutine): Goにおける軽量な実行単位。数KB程度のスタックを持ち、数百万個作成することも可能です。OSスレッドよりもはるかに軽量で、Goランタイムによって管理されます。
- M (Machine/OS Thread): オペレーティングシステムのスレッドに対応します。Goランタイムは、OSスレッドをMとして抽象化し、その上でGを実行します。MはGoコードを実行するためにPと関連付けられている必要があります。
- P (Processor/Logical Processor): 論理プロセッサ。MとGの間に位置し、MがGを実行するためのコンテキストを提供します。Pは実行可能なGのローカル実行キュー(LRQ)を保持し、MはPからGを取得して実行します。
GOMAXPROCS
環境変数によってPの数を制御でき、通常は利用可能な論理CPUの数に設定されます。
proc.c
は、このスケジューラの主要なロジック、特にMとGの管理、Gの生成と破棄、Gの実行状態の遷移などを担当しています。また、アイドル状態のPが他のPのローカル実行キューからゴルーチンを「盗む」ワークスティーリング(Work Stealing)アルゴリズムも実装されており、リソースの効率的な利用を保証します。
スタック管理 (Stack Management)
Goのゴルーチンは、可変サイズのスタックを持ちます。初期スタックサイズは小さく(通常は数KB)、必要に応じて自動的に拡張されます。このスタックの拡張・縮小は、Goランタイムによって透過的に行われます。
- スタックの成長 (Stack Growth): 関数呼び出しによってスタックが不足しそうになると、ランタイムはより大きな新しいスタックセグメントを割り当て、古いスタックの内容を新しいスタックにコピーします。このプロセスは「スタックスプリット(stack split)」と呼ばれます。
morestack
/lessstack
: スタックの成長と縮小を処理するためのランタイム関数です。コンパイラは、関数プロローグにスタックチェックコードを挿入し、スタックが不足しそうな場合にmorestack
を呼び出すようにします。runtime·newstack
/runtime·oldstack
:morestack
やreflect·call
などから呼ばれ、新しいスタックセグメントの割り当てや、古いスタックセグメントへの復帰を処理します。
go
ステートメントと defer
ステートメントの内部動作
go
ステートメント:go
キーワードに続く関数呼び出しは、新しいゴルーチンを生成し、そのゴルーチン内で関数を実行します。ランタイム内部では、runtime·newproc
やruntime·newproc1
といった関数が呼ばれ、新しいゴルーチン構造体(G)が割り当てられ、実行キューに追加されます。defer
ステートメント:defer
キーワードに続く関数呼び出しは、現在の関数の実行が終了する直前(returnする前、またはpanicが発生する前)に実行されるようにスケジュールされます。ランタイム内部では、runtime·deferproc
が呼ばれ、遅延実行される関数とその引数が現在のゴルーチンの遅延実行リストに登録されます。関数が終了する際にはruntime·deferreturn
が呼ばれ、登録された遅延関数が実行されます。
panic
と recover
の内部動作
panic
: Goにおける実行時エラーのメカニズムです。panic
が発生すると、現在のゴルーチンの実行は中断され、遅延関数が逆順に実行されながらスタックがアンワインド(unwind)されます。recover
:panic
から回復するための組み込み関数です。recover
はdefer
関数内でのみ有効で、panic
が発生している場合にその値を捕捉し、パニックによるスタックアンワインドを停止させ、通常の実行フローに戻します。proc.c
には、runtime·panic
やrecovery
、runtime·recover
といった関数が実装されており、これらのメカニズムを支えています。
textflag 7
の意味 (no split)
Goのコンパイラは、関数がスタックを分割(成長)する必要があるかどうかを判断し、必要に応じて morestack
への呼び出しを挿入します。しかし、一部のランタイム関数、特にスタック管理やゴルーチン生成に関わる関数は、スタックの分割中に呼び出されると問題を引き起こす可能性があります。
#pragma textflag 7
は、Goのコンパイラに対する指示で、その関数がスタックを分割しない(no split
)ことを意味します。これは、関数が非常に短い場合や、スタックポインタやフレームポインタに直接アクセスするような低レベルな操作を行う場合に用いられます。スタック分割中にこれらの関数が実行されると、スタックの状態が不安定になり、予期せぬ動作を引き起こす可能性があるため、明示的にスタック分割を抑制します。
このコミットでは、runtime·newproc
、runtime·deferproc
、runtime·deferreturn
、runtime·recover
といった関数にこの textflag 7
が適用されており、追加されたコメントでその理由が説明されています。これは、これらの関数が引数にアクセスする方法や、スタックの状態に依存する性質があるためです。
技術的詳細
このコミットでコメントが追加された主な関数とその技術的詳細は以下の通りです。
-
matchmg
:- 役割: 必要に応じて新しいM(OSスレッド)を起動し、実行可能なゴルーチンを探させるための関数です。
mcpumax
(最大M数)までMを起動します。 - 詳細: スケジューラがロックされた状態で呼び出されます。既存のMがゴルーチンを探している場合でも、必要に応じて新しいMを起動し、システムのリソースを最大限に活用してゴルーチンの実行を促進します。
- 役割: 必要に応じて新しいM(OSスレッド)を起動し、実行可能なゴルーチンを探させるための関数です。
-
startm
:- 役割: 新しいM(OSスレッド)を作成し、そのMが
runtime·mstart
関数から実行を開始するように設定します。 - 詳細:
runtime·mstart
は、新しく起動されたMが最初に実行するランタイム関数であり、そのMの初期化やゴルーチンの取得・実行ループへの移行を担います。
- 役割: 新しいM(OSスレッド)を作成し、そのMが
-
runtime·oldstack
:- 役割: 新しいスタックセグメントを割り当てた関数から戻る際に、古いスタックセグメントに戻るために呼び出されます。
- 詳細:
runtime·lessstack
から呼ばれることを想定しています。関数の戻り値はm->cret
に格納されており、この関数はgobuf
を使って古いスタックフレームにジャンプし、実行を再開します。
-
runtime·newstack
:- 役割:
reflect·call
やruntime·morestack
から、新しいスタックセグメントが必要な場合に呼び出されます。 - 詳細:
m->moreframesize
バイト分の新しいスタックを割り当て、m->moreargsize
バイトの引数を新しいフレームにコピーします。その後、m->morepc
の関数がruntime·lessstack
によって呼び出されたかのように動作します。これは、スタックの成長メカニズムの核心部分です。
- 役割:
-
mstackalloc
:- 役割:
runtime·malg
から呼ばれるフックで、スケジューラスタック上でruntime·stackalloc
を呼び出すためのものです。 - 詳細:
runtime·stackalloc
は、新しいスタックセグメントを割り当てる際に、スタックの成長を試みないように、スケジューラスタック上で呼び出される必要があります。この関数は、その制約を満たすために存在します。
- 役割:
-
runtime·malg
:- 役割: 新しいG(ゴルーチン)を割り当て、
stacksize
バイト分のスタックを確保します。 - 詳細: 新しいゴルーチン構造体を初期化し、そのスタックを割り当てます。Goプログラムで新しいゴルーチンが起動される際に内部的に呼び出される重要な関数です。
- 役割: 新しいG(ゴルーチン)を割り当て、
-
runtime·newproc
:- 役割:
go
ステートメントによって新しいGを生成し、fn
関数をsiz
バイトの引数で実行するように設定します。 - 詳細: コンパイラは
go
ステートメントをこの関数への呼び出しに変換します。この関数は、引数が&fn
の後に連続して利用可能であることを前提としているため、スタック分割が発生すると引数がコピーされない可能性があるため、#pragma textflag 7
(スタック分割なし)が適用されています。
- 役割:
-
runtime·newproc1
:- 役割:
fn
関数をnarg
バイトの引数(argp
から開始)で実行し、nret
バイトの結果を返す新しいGを生成します。 - 詳細:
callerpc
は、このGを作成したgo
ステートメントのアドレスです。新しいGは実行待ちのGのキューに追加されます。runtime·newproc
から内部的に呼び出されます。
- 役割:
-
runtime·deferproc
:- 役割:
defer
ステートメントによって遅延関数fn
をsiz
バイトの引数で登録します。 - 詳細: コンパイラは
defer
ステートメントをこの関数への呼び出しに変換します。runtime·newproc
と同様に、引数が&fn
の後に連続して利用可能であることを前提としているため、#pragma textflag 7
が適用されています。
- 役割:
-
runtime·deferreturn
:- 役割: 遅延関数が存在する場合にそれを実行します。
- 詳細: コンパイラは、
defer
を呼び出す任意の関数の終わりにこの関数への呼び出しを挿入します。遅延関数が存在する場合、runtime·jmpdefer
を呼び出して遅延関数にジャンプし、deferreturn
が呼び出された直前のポイントで呼び出されたかのように見せかけます。これにより、遅延関数がなくなるまでdeferreturn
が繰り返し呼び出されます。呼び出し元のフレームを再利用して遅延関数を呼び出すため、#pragma textflag 7
が適用されています。
-
rundefer
:- 役割: 現在のゴルーチンのすべての遅延関数を実行します。
- 詳細:
deferreturn
が繰り返し呼び出されることで、この関数が最終的にすべての遅延関数を処理します。
-
printpanics
:- 役割: 現在アクティブなすべてのパニック情報を出力します。
- 詳細: プログラムがクラッシュする際に、デバッグ情報としてパニックスタックを出力するために使用されます。
-
runtime·panic
:- 役割: 組み込み関数
panic
の実装です。 - 詳細:
panic
が発生した際に、ランタイムがどのようにスタックをアンワインドし、遅延関数を実行するかを制御します。
- 役割: 組み込み関数
-
recovery
:- 役割: パニック後に遅延関数が
recover
を呼び出した際に、スタックをアンワインドし、遅延関数の呼び出し元が正常に復帰したかのように実行を継続するように調整します。 - 詳細:
recover
が成功した場合の実行フローを制御する重要な関数です。
- 役割: パニック後に遅延関数が
-
runtime·recover
:- 役割: 組み込み関数
recover
の実装です。 - 詳細: 信頼性高く呼び出し元のスタックセグメントを見つける必要があるため、
#pragma textflag 7
が適用されています。
- 役割: 組み込み関数
-
runtime·Gosched
:- 役割:
runtime.Gosched
関数の実装です。 - 詳細: 現在のゴルーチンを一時停止し、他のゴルーチンにCPUを譲ります。スケジューラに制御を戻すために使用されます。
- 役割:
-
runtime·gomaxprocsfunc
:- 役割:
runtime.GOMAXPROCS
関数の実装です。 - 詳細: 論理プロセッサ(P)の数を設定します。スケジューラの動作に直接影響を与えます。
- 役割:
-
runtime·sigprof
:- 役割:
SIGPROF
シグナルを受信した場合に呼び出されます。 - 詳細: CPUプロファイリングのために使用され、定期的にスタックトレースを収集します。
- 役割:
-
runtime·setcpuprofilerate
:- 役割:
fn
関数をhz
回/秒の頻度でトレースバックとともに呼び出すように設定します。 - 詳細: CPUプロファイリングのレートを設定するための関数です。
- 役割:
-
os·setenv_c
:- 役割: Cgoがロードされている場合にC環境を更新します。
- 詳細:
os.Setenv
から呼び出され、Cライブラリの環境変数を設定する際に使用されます。
これらのコメントは、Goランタイムの内部動作、特にスタック管理、ゴルーチン生成、パニック/リカバリー、そしてスケジューリングの複雑な相互作用を理解する上で非常に貴重な情報を提供しています。
コアとなるコードの変更箇所
このコミットでは、src/pkg/runtime/proc.c
ファイルに多数のコメントが追加され、一部の古いコメントが削除されています。以下に、主要な変更箇所を抜粋して示します。
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -739,8 +739,6 @@ struct CgoThreadStart
};
// Kick off new m's as needed (up to mcpumax).
-// There are already `other' other cpus that will
-// start looking for goroutines shortly.
// Sched is locked.
static void
matchmg(void)
@@ -763,6 +761,7 @@ matchmg(void)
}
}
+// Create a new m. It will start off with a call to runtime·mstart.
static M*
startm(void)
{
@@ -995,6 +994,9 @@ runtime·exitsyscall(void)
g->gcstack = nil;
}
+// Called from runtime·lessstack when returning from a function which
+// allocated a new stack segment. The function's return value is in
+// m->cret.
void
runtime·oldstack(void)
{
@@ -1026,6 +1031,11 @@ runtime·oldstack(void)
runtime·gogo(&old.gobuf, m->cret);
}
+// Called from reflect·call or from runtime·morestack when a new
+// stack segment is needed. Allocate a new stack big enough for
+// m->moreframesize bytes, copy m->moreargsize bytes to the new frame,
+// and then act as though runtime·lessstack called the function at
+// m->morepc.
void
runtime·newstack(void)
{
@@ -1113,6 +1124,10 @@ runtime·newstack(void)
*(int32*)345 = 123; // never return
}
+// Hook used by runtime·malg to call runtime·stackalloc on the
+// scheduler stack. This exists because runtime·stackalloc insists
+// on being called on the scheduler stack, to avoid trying to grow
+// the stack while allocating a new stack segment.
static void
mstackalloc(G *gp)
{
@@ -1120,6 +1135,7 @@ mstackalloc(G *gp)
runtime·gogo(&gp->sched, 0);
}
+// Allocate a new g, with a stack big enough for stacksize bytes.
G*
runtime·malg(int32 stacksize)
{
@@ -1146,15 +1162,13 @@ runtime·malg(int32 stacksize)
return newg;
}
-/*
- * Newproc and deferproc need to be textflag 7
- * (no possible stack split when nearing overflow)
- * because they assume that the arguments to fn
- * are available sequentially beginning at &arg0.
- * If a stack split happened, only the one word
- * arg0 would be copied. It's okay if any functions
- * they call split the stack below the newproc frame.
- */
+// Create a new g running fn with siz bytes of arguments.
+// Put it on the queue of g's waiting to run.
+// The compiler turns a go statement into a call to this.
+// Cannot split the stack because it assumes that the arguments
+// are available sequentially after &fn; they would not be
+// copied if a stack split occurred. It's OK for this to call
+// functions that split the stack.
#pragma textflag 7
void
runtime·newproc(int32 siz, byte* fn, ...)
@@ -1168,6 +1182,10 @@ runtime·newproc(int32 siz, byte* fn, ...)\
runtime·newproc1(fn, argp, siz, 0, runtime·getcallerpc(&siz));
}
+// Create a new g running fn with narg bytes of arguments starting
+// at argp and returning nret bytes of results. callerpc is the
+// address of the go statement that created this. The new g is put
+// on the queue of g's waiting to run.
G*
runtime·newproc1(byte *fn, byte *argp, int32 narg, int32 nret, void *callerpc)
{
@@ -1228,6 +1246,12 @@ runtime·newproc1(byte *fn, byte *argp, int32 narg, int32 nret, void *callerpc)
//printf(" goid=%d\n", newg->goid);\
}
+// Create a new deferred function fn with siz bytes of arguments.
+// The compiler turns a defer statement into a call to this.
+// Cannot split the stack because it assumes that the arguments
+// are available sequentially after &fn; they would not be
+// copied if a stack split occurred. It's OK for this to call
+// functions that split the stack.
#pragma textflag 7
uintptr
runtime·deferproc(int32 siz, byte* fn, ...)
@@ -1256,6 +1280,16 @@ runtime·deferproc(int32 siz, byte* fn, ...)
return 0;
}
+// Run a deferred function if there is one.
+// The compiler inserts a call to this at the end of any
+// function which calls defer.
+// If there is a deferred function, this will call runtime·jmpdefer,
+// which will jump to the deferred function such that it appears
+// to have been called by the caller of deferreturn at the point
+// just before deferreturn was called. The effect is that deferreturn
+// is called again and again until there are no more deferred functions.
+// Cannot split the stack because we reuse the caller's frame to
+// call the deferred function.
#pragma textflag 7
void
runtime·deferreturn(uintptr arg0)
@@ -1277,6 +1311,7 @@ runtime·deferreturn(uintptr arg0)
runtime·jmpdefer(fn, argp);
}
+// Run all deferred functions for the current goroutine.
static void
rundefer(void)
{
@@ -1318,6 +1353,7 @@ unwindstack(G *gp, byte *sp)
}
}
+// Print all currently active panics. Used when crashing.
static void
printpanics(Panic *p)
{
@@ -1334,6 +1370,7 @@ printpanics(Panic *p)
static void recovery(G*);
+// The implementation of the predeclared function panic.
void
runtime·panic(Eface e)
{
@@ -1376,6 +1413,9 @@ runtime·panic(Eface e)
runtime·dopanic(0);
}
+// Unwind the stack after a deferred function calls recover
+// after a panic. Then arrange to continue running as though
+// the caller of the deferred function returned normally.
static void
recovery(G *gp)
{
@@ -1407,7 +1447,10 @@ recovery(G *gp)
runtime·gogo(&gp->sched, 1);
}
-#pragma textflag 7 /* no split, or else g->stackguard is not the stack for fp */
+// The implementation of the predeclared function recover.
+// Cannot split the stack because it needs to reliably
+// find the stack segment of its caller.
+#pragma textflag 7
void
runtime·recover(byte *argp, Eface ret)
{
@@ -1519,6 +1562,7 @@ runtime·Gosched(void)
runtime·gosched();
}
+// Implementation of runtime.GOMAXPROCS.
// delete when scheduler is stronger
int32
runtime·gomaxprocsfunc(int32 n)
@@ -1634,6 +1678,7 @@ static struct {
uintptr pcbuf[100];
} prof;
+// Called if we receive a SIGPROF signal.
void
runtime·sigprof(uint8 *pc, uint8 *sp, uint8 *lr, G *gp)
{
@@ -1653,6 +1698,7 @@ runtime·sigprof(uint8 *pc, uint8 *sp, uint8 *lr, G *gp)
runtime·unlock(&prof);\
}
+// Arrange to call fn with a traceback hz times a second.
void
runtime·setcpuprofilerate(void (*fn)(uintptr*, int32), int32 hz)
{
@@ -1683,6 +1729,8 @@ runtime·setcpuprofilerate(void (*fn)(uintptr*, int32), int32 hz)
void (*libcgo_setenv)(byte**);
+// Update the C environment if cgo is loaded.
+// Called from os.Setenv.
void
os·setenv_c(String k, String v)
{
コアとなるコードの解説
上記の変更箇所は、Goランタイムの proc.c
内の様々な関数に、その役割、動作原理、および特定の制約(特にスタック分割に関する textflag 7
の理由)を説明するコメントを追加しています。
例えば、runtime·newproc
の変更を見てみましょう。
変更前:
/*
* Newproc and deferproc need to be textflag 7
* (no possible stack split when nearing overflow)
* because they assume that the arguments to fn
* are available sequentially beginning at &arg0.
* If a stack split happened, only the one word
* arg0 would be copied. It's okay if any functions
* they call split the stack below the newproc frame.
*/
#pragma textflag 7
void
runtime·newproc(int32 siz, byte* fn, ...)
変更後:
// Create a new g running fn with siz bytes of arguments.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
// Cannot split the stack because it assumes that the arguments
// are available sequentially after &fn; they would not be
// copied if a stack split occurred. It's OK for this to call
// functions that split the stack.
#pragma textflag 7
void
runtime·newproc(int32 siz, byte* fn, ...)
この変更では、古いCスタイルのコメントブロックが、より現代的なC++スタイルの行コメントに置き換えられています。内容は基本的に同じですが、より簡潔で読みやすくなっています。
新しいコメントは、以下の点を明確にしています。
- 関数の目的: 「
fn
をsiz
バイトの引数で実行する新しいGを作成する。」 - スケジューリング: 「実行待ちのGのキューに入れる。」
- コンパイラの役割: 「コンパイラは
go
ステートメントをこれへの呼び出しに変換する。」 - スタック分割の制約: 「スタックを分割できない。なぜなら、引数が
&fn
の後に連続して利用可能であることを前提としており、スタック分割が発生した場合、それらはコピーされないからである。」 - 許容される動作: 「この関数が呼び出す関数が
newproc
フレームの下でスタックを分割することは問題ない。」
特に「Cannot split the stack...」の部分は、#pragma textflag 7
がなぜ必要であるかという技術的な理由を詳細に説明しており、Goランタイムの低レベルなスタック管理の複雑さを浮き彫りにしています。引数がスタック上に連続して配置されていることを前提とする関数では、スタック分割によって引数の配置が変更されると、関数が正しく動作しなくなる可能性があるため、このような制約が設けられています。
他の関数についても同様に、その機能、呼び出し元、呼び出し先の関係、そしてスタック管理やスケジューリングに関する特定の制約が詳細にコメントされています。これにより、Goランタイムの内部構造がより透過的になり、開発者がその動作を深く理解するための手助けとなります。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
- Goランタイムのソースコード: https://github.com/golang/go/tree/master/src/runtime
- Goスケジューラに関するブログ記事 (例: The Go scheduler): https://go.dev/blog/go11sched (Go 1.1のスケジューラに関する古い記事ですが、基本的な概念は参考になります)
参考にした情報源リンク
- Go言語のソースコード (
src/pkg/runtime/proc.c
および関連ファイル) - Go言語の公式ドキュメント
- Goランタイムに関する一般的な技術記事やブログポスト (Web検索結果で得られた情報源を含む)
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEPgaU7a1I-aTeICxrvPQV03ylWhEIPMJQ9i_ZKgGilT8zSyWrTLr5knt70Qqpy42bFtS9TObz-48W6caQMWBo9A6Gdf5OhaFd1pNAVvJpvXZoPFgK4bm8NTpCzRBp_NBkw41iFlBjl-9CA2PL_v5WhdASjN90UZXDwmKcPjEH2FQHH6oF7absgHtr9qLE8UIlpd6bKlKxBu0Sasa4LPZqGQIZa4OyZ0OKTWloSpV8Rm3szdT3s8cfKbXvN5x-WOnw=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFUz50jgQJ9BqsZzWM7TW9gmfNhUby4kd5ls3r6FyFlLFghcOXXcvzmwAmjw0jTFmV3ywRqgAxCxh9ArvB9Sx8vlts-ZqyAEOG4rP7OiWqaapU3D3A_aK5hwWI-jWc_cAWg4FYvKgJ6DR80Ng==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGsu-0jlpWR_WGVUT4flEFg6rOb_qBjOg2HKFUnv7ZwpQ3ix46WJv7lQYQ3K8FEI6ZqaRRiimfs4bH3UVQmtexFzy24g7U3MBJWLGqA-lH0yFPD28SSTB7Qo-7rWvEO2EfzgtnUjSS_2YwgXEG8JeMyLofclhJmephL8A==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEGilvv7fkMXxj9T_1nKIvobiNqI-ecy52ajst8rqrrCymwsh4TJjsvksckIa2KikYU2wmU7519JFIPxDrpMeTmlpM3VK4uskW4wo181Tovbzg7cSn4zYcUmNya
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQERSrin-iBcpOxVTntu8I6HxE0MgJM_RqJnUxB-13oqvDaKe6Bjyxq32sNY--O1VcW8Fv81G6I_g7ALhrUn1DNisNv7IweQNowLyC5jyO6CBN8LN-joMHDfxdYgfRap1eC1ciFreIZE9HsRcS3AVZumX7N9BEce6yxqfzLw
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHBjqSMxK9ThN0q290nwLrkq2eTo18AuQ0Wp8URhjiYjxpmBboWvqwxTVzJqckEyB83v33-EK98YZxXcDP4607Cb995AXfBhnW_VwcBgqLUEwAqmt2s6bOYfIO3JV_f4H4xWtEHRXSnrStFNRyROOo_K75_y2k9
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGsp5CSdtsi0Q2X76FQ_1B10I7D81xWk0VxKu6_n7XXGmdK6ptC_JezH2-k_moo5pY3thHhURyFLmAmkaBAxJR0CzwCYy6aDqueu4XtiOpYTjR-gdqel9lBbrWBFLfcTYVSdQcG5QOEo7_6dSafUZ5ipsLivh1Q8mG3e6loT4vJHi-Tw-kLKkNkD4e3zlMMfqMxWuyteCqn72QtDxVHwTmUKONrTSCnsIxPrDJZzhF304hxO75IOg==