[インデックス 12963] ファイルの概要
このコミットは、GoランタイムがOpenBSD上でスレッドを作成する際に使用するシステムコールを、非推奨となったrfork()
から、より新しい推奨される__tfork()
システムコールに切り替える変更です。これにより、OpenBSD 5.1以降のバージョンとの互換性を確保し、システムスレッド作成の現代的なアプローチに準拠します。
コミット
commit 689d5b91631ccfaee9b85aa25a06df55769e299e
Author: Joel Sing <jsing@google.com>
Date: Thu Apr 26 00:08:02 2012 +1000
runtime: use __tfork() syscall on openbsd
Switch from using the rfork() syscall on OpenBSD, to the __tfork()
syscall. The __tfork() syscall is the preferred way of creating
system threads and the rfork() syscall has recently been removed.
Note: this will break compatibility with OpenBSD releases prior to 5.1.
R=golang-dev, bradfitz, devon.odell, rsc
CC=golang-dev
https://golang.org/cl/6037048
---
src/pkg/runtime/sys_openbsd_386.s | 47 +++++++++++++++++--------------------\
src/pkg/runtime/sys_openbsd_amd64.s | 29 ++++++++++-------------\
src/pkg/runtime/thread_openbsd.c | 20 +++++++---------\
3 files changed, 42 insertions(+), 54 deletions(-)
diff --git a/src/pkg/runtime/sys_openbsd_386.s b/src/pkg/runtime/sys_openbsd_386.s
index 49acb25c16..0774162f64 100644
--- a/src/pkg/runtime/sys_openbsd_386.s
+++ b/src/pkg/runtime/sys_openbsd_386.s
@@ -187,40 +187,42 @@ TEXT runtime·sigtramp(SB),7,$44
MOVL $0xf1, 0xf1 // crash
RET
-// int32 rfork_thread(int32 flags, void *stack, M *m, G *g, void (*fn)(void));
--TEXT runtime·rfork_thread(SB),7,$8
-- MOVL flags+8(SP), AX
-- MOVL stack+12(SP), CX
-+// int32 tfork_thread(void *param, void *stack, M *m, G *g, void (*fn)(void));
-+TEXT runtime·tfork_thread(SB),7,$8
- // Copy m, g, fn off parent stack for use by child.
-+ // Copy m, g, fn off parent stack and onto the child stack.
-+ MOVL stack+8(FP), CX
SUBL $16, CX
-- MOVL mm+16(SP), SI
-+ MOVL mm+12(FP), SI
MOVL SI, 0(CX)
-- MOVL gg+20(SP), SI
-+ MOVL gg+16(FP), SI
MOVL SI, 4(CX)
-- MOVL fn+24(SP), SI
-+ MOVL fn+20(FP), SI
MOVL SI, 8(CX)
MOVL $1234, 12(CX)
MOVL CX, SI
MOVL $0, 0(SP) // syscall gap
-- MOVL AX, 4(SP) // arg 1 - flags
-- MOVL $251, AX // sys_rfork
-+ MOVL params+4(FP), AX
-+ MOVL AX, 4(SP) // arg 1 - param
-+ MOVL $328, AX // sys___tfork
INT $0x80
-- // Return if rfork syscall failed
-- JCC 4(PC)
-+ // Return if tfork syscall failed.
-+ JCC 5(PC)
NEGL AX
-- MOVL AX, 48(SP)
-+ MOVL ret+0(FP), DX
-+ MOVL AX, 0(DX)
RET
// In parent, return.
CMPL AX, $0
-- JEQ 3(PC)
-- MOVL AX, 48(SP)
-+ JEQ 4(PC)
-+ MOVL ret+0(FP), DX
-+ MOVL AX, 0(DX)
RET
-- // In child, on new stack.
-+ // In child, switch to new stack.
MOVL SI, SP
// Paranoia: check that SP is as we expect.
@@ -229,17 +231,12 @@ TEXT runtime·sigtramp(SB),7,$44
JEQ 2(PC)
INT $3
-- // Reload registers
-+ // Reload registers.
MOVL 0(SP), BX // m
MOVL 4(SP), DX // g
MOVL 8(SP), SI // fn
-- // Initialize m->procid to thread ID
-- MOVL $299, AX // sys_getthrid
-- INT $0x80
-- MOVL AX, m_procid(BX)
--
-- // Set FS to point at m->tls
-+ // Set FS to point at m->tls.
LEAL m_tls(BX), BP
PUSHAL // save registers
PUSHL BP
@@ -256,12 +253,12 @@ TEXT runtime·sigtramp(SB),7,$44
MOVL 0(DX), DX // paranoia; check they are not nil
MOVL 0(BX), BX
-- // more paranoia; check that stack splitting code works
-+ // More paranoia; check that stack splitting code works.
PUSHAL
CALL runtime·emptyfunc(SB)
POPAL
-- // Call fn
-+ // Call fn.
CALL SI
CALL runtime·exit1(SB)
diff --git a/src/pkg/runtime/sys_openbsd_amd64.s b/src/pkg/runtime/sys_openbsd_amd64.s
index b103f583cf..9df903f74f 100644
--- a/src/pkg/runtime/sys_openbsd_amd64.s
+++ b/src/pkg/runtime/sys_openbsd_amd64.s
@@ -8,20 +8,20 @@
#include "zasm_GOOS_GOARCH.h"
-// int64 rfork_thread(int32 flags, void *stack, M *m, G *g, void (*fn)(void));
--TEXT runtime·rfork_thread(SB),7,$0
-- MOVL flags+8(SP), DI
-- MOVQ stack+16(SP), SI
-+// int64 tfork_thread(void *param, void *stack, M *m, G *g, void (*fn)(void));
-+TEXT runtime·tfork_thread(SB),7,$32
-- // Copy m, g, fn off parent stack for use by child.
-- MOVQ mm+24(SP), R8
-- MOVQ gg+32(SP), R9
-- MOVQ fn+40(SP), R12
-+ // Copy stack, m, g and fn off parent stack for use by child.
-+ MOVQ stack+8(FP), SI
-+ MOVQ mm+16(FP), R8
-+ MOVQ gg+24(FP), R9
-+ MOVQ fn+32(FP), R12
-- MOVL $251, AX // sys_rfork
-+ MOVQ param+0(FP), DI
-+ MOVL $328, AX // sys___tfork
SYSCALL
-- // Return if rfork syscall failed
-+ // Return if tfork syscall failed.
JCC 3(PC)
NEGL AX
RET
@@ -31,19 +31,14 @@ TEXT runtime·rfork_thread(SB),7,$0
JEQ 2(PC)
RET
-- // In child, on new stack.
-+ // In child, switch to new stack.
MOVQ SI, SP
-- // Initialize m->procid to thread ID
-- MOVL $299, AX // sys_getthrid
-- SYSCALL
-- MOVQ AX, m_procid(R8)
--
// Set FS to point at m->tls.
LEAQ m_tls(R8), DI
CALL runtime·settls(SB)
-- // In child, set up new stack
-+ // In child, set up new stack.
get_tls(CX)
MOVQ R8, m(CX)
MOVQ R9, g(CX)
diff --git a/src/pkg/runtime/thread_openbsd.c b/src/pkg/runtime/thread_openbsd.c
index acd32a6f18..56bb1c8ebf 100644
--- a/src/pkg/runtime/thread_openbsd.c
+++ b/src/pkg/runtime/thread_openbsd.c
@@ -23,7 +23,7 @@ extern SigTab runtime·sigtab[];
static Sigset sigset_all = ~(Sigset)0;
static Sigset sigset_none;
-extern int64 runtime·rfork_thread(int32 flags, void *stack, M *m, G *g, void (*fn)(void));
+extern int64 runtime·tfork_thread(void *param, void *stack, M *m, G *g, void (*fn)(void));
extern int32 runtime·thrsleep(void *ident, int32 clock_id, void *tsp, void *lock, const int32 *abort);\n extern int32 runtime·thrwakeup(void *ident, int32 n);\n \n@@ -122,22 +122,14 @@ runtime·semawakeup(M *mp)\n runtime·atomicstore(&mp->waitsemalock, 0);\n }\n \n-// From OpenBSD\'s sys/param.h\n-#define\tRFPROC\t\t(1<<4)\t/* change child (else changes curproc) */\n-#define\tRFMEM\t\t(1<<5)\t/* share `address space\' */\n-#define\tRFNOWAIT\t(1<<6)\t/* parent need not wait() on child */\n-#define\tRFTHREAD\t(1<<13)\t/* create a thread, not a process */\n-\n void\n runtime·newosproc(M *m, G *g, void *stk, void (*fn)(void))\n {\n+\tTfork param;\n Sigset oset;\n-\tint32 flags;\n int32 ret;\n \n-- flags = RFPROC | RFTHREAD | RFMEM | RFNOWAIT;\n--\n-- if (0) {\n-+\tif(0) {\n \t\truntime·printf(\n \t\t\t\"newosproc stk=%p m=%p g=%p fn=%p id=%d/%d ostk=%p\\n\",\n \t\t\tstk, m, g, fn, m->id, m->tls[0], &m);\n@@ -145,8 +137,12 @@ runtime·newosproc(M *m, G *g, void *stk, void (*fn)(void))\n \n \tm->tls[0] = m->id;\t// so 386 asm can find it\n \n+\tparam.tf_tcb = (byte*)&m->tls[0];\n+\tparam.tf_tid = (int32*)&m->procid;\n+\tparam.tf_flags = (int32)0;\n+\n \toset = runtime·sigprocmask(SIG_SETMASK, sigset_all);\n-- ret = runtime·rfork_thread(flags, stk, m, g, fn);\n-+ ret = runtime·tfork_thread((byte*)¶m, stk, m, g, fn);\n \truntime·sigprocmask(SIG_SETMASK, oset);\n \n \tif(ret < 0) {\n```
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/689d5b91631ccfaee9b85aa25a06df55769e299e](https://github.com/golang/go/commit/689d5b91631ccfaee9b85aa25a06df55769e299e)
## 元コミット内容
このコミットは、OpenBSDオペレーティングシステム上でGoランタイムがスレッドを生成する際に使用するシステムコールを、`rfork()`から`__tfork()`へ変更することを目的としています。コミットメッセージによると、`__tfork()`はシステムスレッドを作成するための推奨される方法であり、`rfork()`システムコールは最近OpenBSDから削除されたとのことです。この変更は、OpenBSD 5.1より前のリリースとの互換性を損なう可能性があると明記されています。
## 変更の背景
OpenBSDは、そのセキュリティと堅牢性で知られるUNIX系オペレーティングシステムです。システムコールは、ユーザー空間のプログラムがカーネルの機能にアクセスするための主要なインターフェースです。スレッドやプロセスの作成は、OSの根幹をなす機能であり、そのためのシステムコールはOSの進化とともに変更されることがあります。
このコミットの背景には、OpenBSDにおけるスレッド作成メカニズムの進化があります。元々、OpenBSDはPlan 9由来の`rfork()`システムコールをサポートしていました。`rfork()`は、新しいプロセスやスレッドを作成する際に、ファイルディスクリプタテーブルやメモリアドレス空間など、どのリソースを共有またはコピーするかを細かく制御できる柔軟なシステムコールでした。特に`RFTHREAD`フラグを使用することで、現在のプロセス内にカーネルスレッドを作成することが可能でした。
しかし、OpenBSDはPOSIXスレッドのセマンティクスをより良くサポートするために、`rfork()`の実装を進化させました。そして、2012年頃にOpenBSD 5.1で`rfork()`システムコールが削除されました。この削除により、`__tfork()`がOpenBSDにおけるシステムスレッド作成の主要かつ推奨されるメカニズムとなりました。`__tfork()`は、特に新しいスレッドのスタックポインタやスレッド制御ブロック(TCB)ポインタの設定に関して、より直接的で制御された方法を提供するために導入されました。
Goランタイムは、OSのプリミティブを使用してゴルーチン(Goの軽量スレッド)を実装しています。OpenBSD上でGoが正しく動作するためには、OSが提供する最新かつ推奨されるスレッド作成メカニズムに適応する必要がありました。`rfork()`の削除は、GoランタイムがOpenBSD上でスレッドを作成できなくなることを意味するため、この変更はOpenBSD 5.1以降のバージョンでGoが動作するために不可欠でした。
## 前提知識の解説
### システムコール
システムコールは、オペレーティングシステム(OS)のカーネルが提供するサービスを、ユーザー空間で動作するプログラムが利用するためのインターフェースです。例えば、ファイルの読み書き、メモリの割り当て、プロセスの作成、スレッドの管理などは、すべてシステムコールを通じて行われます。プログラムが直接ハードウェアにアクセスすることは通常許されておらず、OSのカーネルが仲介することで、システムの安定性とセキュリティが保たれます。
### `rfork()` システムコール (OpenBSDにおける旧来のシステムコール)
`rfork()`は、Plan 9オペレーティングシステムに由来するシステムコールで、OpenBSDでも採用されていました。これは、新しいプロセスやスレッドを作成する際に、親プロセスと子プロセス(またはスレッド)間で共有するリソース(メモリ空間、ファイルディスクリプタ、シグナルハンドラなど)を細かく指定できる点が特徴でした。
`rfork()`の主なフラグには以下のようなものがありました。
* `RFPROC`: 新しいプロセスを作成する。
* `RFMEM`: メモリアドレス空間を共有する。
* `RFTHREAD`: スレッドを作成する(プロセスではなく、同じアドレス空間を共有する軽量な実行単位)。
* `RFNOWAIT`: 親プロセスが子プロセスの終了を待つ必要がないようにする。
Goランタイムは、`rfork()`に`RFTHREAD`フラグなどを指定して、OSレベルのスレッドを作成していました。
### `__tfork()` システムコール (OpenBSDにおける新しいシステムコール)
`__tfork()`は、OpenBSD 5.1で導入された新しいシステムコールで、主にスレッドの作成に特化しています。`rfork()`が持つ汎用的なプロセス/スレッド作成機能から、より現代的なPOSIXスレッドのセマンティクスに合わせた設計になっています。特に、新しいスレッドのスタックポインタやスレッドローカルストレージ(TLS)の管理をより効率的かつ安全に行うことを目的としています。
`__tfork()`は、`tfork_param`構造体(または類似の構造体)を引数として受け取り、新しいスレッドの初期状態(スタックポインタ、スレッドIDの格納場所、TLSポインタなど)を詳細に設定できるようにします。これにより、OSがスレッドのコンテキストをより正確に管理できるようになります。
### Goランタイムにおけるスレッド管理 (M, G, P)
Go言語は、独自の軽量スレッドである「ゴルーチン(goroutine)」をサポートしています。ゴルーチンはOSのスレッドよりもはるかに軽量で、数百万個を同時に実行することも可能です。Goランタイムは、これらのゴルーチンをOSのスレッドにマッピングして実行します。このマッピングは、以下の3つの主要な要素によって管理されます。
* **M (Machine)**: OSのスレッドを表します。Goランタイムは、OSのスレッドをMとして抽象化し、その上でゴルーチンを実行します。
* **G (Goroutine)**: ゴルーチン自体を表します。各ゴルーチンは、独自のスタックと実行コンテキストを持ちます。
* **P (Processor)**: 論理プロセッサを表します。PはMとGの間の仲介役となり、MがGを実行するためのコンテキストを提供します。Pは、実行可能なゴルーチンのキューを保持し、Mがそのキューからゴルーチンを取り出して実行します。
Goランタイムが新しいゴルーチンを実行するためにOSスレッドが必要な場合、`newosproc`のような関数を通じてOSのシステムコール(この場合は`rfork()`または`__tfork()`)を呼び出して新しいM(OSスレッド)を作成します。
## 技術的詳細
このコミットは、主にOpenBSDアーキテクチャ(i386とamd64)におけるGoランタイムのアセンブリコードとCコードを変更しています。
### `src/pkg/runtime/sys_openbsd_386.s` および `src/pkg/runtime/sys_openbsd_amd64.s`
これらのファイルは、それぞれ32ビット(i386)と64ビット(amd64)のOpenBSDアーキテクチャ向けのアセンブリコードを含んでいます。GoランタイムがOSスレッドを作成する際に直接呼び出す`runtime·rfork_thread`関数が、`runtime·tfork_thread`に置き換えられています。
**主な変更点:**
1. **関数名の変更**:
* `TEXT runtime·rfork_thread(SB)` が `TEXT runtime·tfork_thread(SB)` に変更されました。
* 関数の引数リストも変更されています。`rfork_thread`は`flags`を直接受け取っていましたが、`tfork_thread`は`param`(`Tfork`構造体へのポインタ)を受け取るようになります。
2. **システムコール番号の変更**:
* i386版では、`sys_rfork`のシステムコール番号である`$251`が、`sys___tfork`のシステムコール番号である`$328`に変更されました。
* amd64版でも同様に、`$251`が`$328`に変更されました。
3. **引数の渡し方の変更**:
* `rfork_thread`では、`flags`が直接レジスタ(i386では`AX`、amd64では`DI`)に渡されていましたが、`tfork_thread`では`Tfork`構造体へのポインタが渡されるようになります。この構造体には、新しいスレッドのスタック、TLS、スレッドIDなどの情報が含まれます。
* i386版では、`params+4(FP)`から`AX`に値がロードされ、それがシステムコールの第一引数として`4(SP)`に格納されます。
* amd64版では、`param+0(FP)`から`DI`に値がロードされ、それがシステムコールの第一引数として使用されます。
4. **スレッドIDの取得ロジックの削除**:
* `rfork_thread`では、子スレッド内で`sys_getthrid`システムコール(i386では`$299`)を呼び出してスレッドIDを取得し、`m->procid`に格納していました。
* `__tfork()`システムコールは、`Tfork`構造体を通じてスレッドIDを直接設定できるため、この明示的な`sys_getthrid`の呼び出しと`m->procid`への格納は不要となり、削除されました。
5. **スタックポインタとフレームポインタの調整**:
* アセンブリコード内のスタックポインタ(SP)とフレームポインタ(FP)のオフセットが、新しい引数構造とシステムコールの呼び出し規約に合わせて調整されています。例えば、`mm+16(SP)`が`mm+12(FP)`に、`stack+12(SP)`が`stack+8(FP)`に変更されています。これは、関数呼び出し規約やスタックフレームのレイアウトが変更されたことによるものです。
### `src/pkg/runtime/thread_openbsd.c`
このファイルは、GoランタイムのOpenBSD固有のスレッド関連のCコードを含んでいます。
**主な変更点:**
1. **関数プロトタイプの変更**:
* `extern int64 runtime·rfork_thread(int32 flags, void *stack, M *m, G *g, void (*fn)(void));` が `extern int64 runtime·tfork_thread(void *param, void *stack, M *m, G *g, void (*fn)(void));` に変更されました。これにより、Cコードからアセンブリ関数を呼び出す際のシグネチャが更新されます。
2. **`rfork`フラグの削除**:
* `RFPROC`, `RFMEM`, `RFNOWAIT`, `RFTHREAD`といった`rfork`関連の`#define`が削除されました。これらは`__tfork()`では使用されないためです。
3. **`Tfork`構造体の導入と初期化**:
* `runtime·newosproc`関数内で、`Tfork param;`という新しい`Tfork`構造体が宣言されました。
* この`param`構造体のフィールドが初期化されます。
* `param.tf_tcb = (byte*)&m->tls[0];`: スレッド制御ブロック(TCB)のポインタを設定します。Goでは、M構造体の`tls`フィールドがスレッドローカルストレージとして使用されます。
* `param.tf_tid = (int32*)&m->procid;`: 新しいスレッドのIDが格納される場所(M構造体の`procid`フィールド)へのポインタを設定します。
* `param.tf_flags = (int32)0;`: `__tfork()`に渡すフラグを設定します。このコミットでは`0`に設定されていますが、将来的に追加のフラグが定義される可能性があります。
4. **`runtime·tfork_thread`の呼び出し**:
* `ret = runtime·rfork_thread(flags, stk, m, g, fn);` が `ret = runtime·tfork_thread((byte*)¶m, stk, m, g, fn);` に変更されました。これにより、新しい`__tfork()`システムコールをラップするアセンブリ関数が、適切に初期化された`Tfork`構造体へのポインタを渡して呼び出されます。
## コアとなるコードの変更箇所
* `src/pkg/runtime/sys_openbsd_386.s`: OpenBSD/i386アーキテクチャ向けのアセンブリコード。`rfork_thread`から`tfork_thread`への変更、システムコール番号の更新、引数処理の調整。
* `src/pkg/runtime/sys_openbsd_amd64.s`: OpenBSD/amd64アーキテクチャ向けのアセンブリコード。`rfork_thread`から`tfork_thread`への変更、システムコール番号の更新、引数処理の調整。
* `src/pkg/runtime/thread_openbsd.c`: OpenBSD固有のCコード。`rfork_thread`のプロトタイプを`tfork_thread`に変更、`Tfork`構造体の導入と初期化、`runtime·tfork_thread`の呼び出し。
## コアとなるコードの解説
このコミットの核心は、OpenBSDにおけるスレッド作成のOSレベルのインターフェースの変更にGoランタイムが適応することです。
**アセンブリコード (`sys_openbsd_386.s`, `sys_openbsd_amd64.s`)**:
これらのファイルは、Goランタイムが新しいOSスレッドを生成する際の低レベルな処理を定義しています。以前は`rfork`システムコールを直接呼び出していましたが、この変更により`__tfork`システムコールを呼び出すように修正されました。
* **`TEXT runtime·tfork_thread(SB)`**: このラベルは、Goランタイムが新しいOSスレッドを作成するために呼び出すアセンブリ関数のエントリポイントです。
* **引数の処理**: `rfork_thread`では`flags`と`stack`が直接引数として渡されていましたが、`tfork_thread`では`param`(`Tfork`構造体へのポインタ)と`stack`が渡されます。アセンブリコードは、これらの引数をスタックフレームから適切に読み取り、システムコールに渡すためのレジスタに配置します。特に、`m`, `g`, `fn`(GoのM, G構造体へのポインタと、新しいスレッドで実行される関数へのポインタ)は、親スレッドのスタックから子スレッドのスタックにコピーされ、子スレッドが実行を開始した後にこれらの値にアクセスできるようにします。
* **システムコール呼び出し**: `INT $0x80` (i386) または `SYSCALL` (amd64) 命令を使用して、カーネルの`__tfork`システムコールを呼び出します。システムコール番号は、`$251` (rfork) から `$328` (__tfork) に変更されています。
* **エラーハンドリング**: システムコールが失敗した場合(返り値が負の場合)、エラーを処理するために`NEGL AX`(AXレジスタの符号を反転)し、呼び出し元に返します。
* **子スレッドの初期化**: `__tfork`が成功し、子スレッドのコンテキストで実行が再開された後、アセンブリコードは新しいスタックポインタを設定し、`m`, `g`, `fn`の値を再ロードします。また、`m->tls`(スレッドローカルストレージ)を指すようにFSレジスタ(またはGSレジスタ)を設定します。これは、Goランタイムがスレッド固有のデータにアクセスするために重要です。以前の`sys_getthrid`によるスレッドIDの取得は、`__tfork`が`Tfork`構造体を通じてIDを直接設定するため、不要となり削除されました。
**Cコード (`thread_openbsd.c`)**:
このファイルは、GoランタイムのOSスレッド作成ロジックの高レベルな部分を扱います。
* **`runtime·newosproc`関数**: この関数は、Goランタイムが新しいOSスレッドを必要とするときに呼び出されます。
* **`Tfork`構造体の準備**: `__tfork`システムコールに渡すための`Tfork`構造体(`param`)がここで初期化されます。`tf_tcb`はスレッドローカルストレージのベースアドレスを、`tf_tid`は新しいスレッドのOSスレッドIDが書き込まれるメモリ位置を指します。
* **シグナルマスクの管理**: `runtime·sigprocmask`を使用して、新しいスレッドを作成する前にシグナルマスクを一時的に変更し、スレッド作成後に元に戻します。これは、スレッド作成中のシグナル処理の一貫性を保つためによく行われるプラクティスです。
* **`runtime·tfork_thread`の呼び出し**: 最終的に、初期化された`param`構造体とスタックポインタ、M、G、関数ポインタを引数として、アセンブリで実装された`runtime·tfork_thread`関数を呼び出します。
この変更により、GoランタイムはOpenBSDの最新のスレッド作成APIに準拠し、OpenBSD 5.1以降のバージョンでGoプログラムが正しく動作するようになります。
## 関連リンク
* OpenBSDのシステムコールに関するドキュメント (OpenBSDのmanページやソースコードを参照)
* Go言語のランタイムスケジューラに関するドキュメント (M, G, Pモデルの詳細)
## 参考にした情報源リンク
* [OpenBSD rfork vs __tfork syscall history - Google Search Results](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHica4pUDq8o-NancrFCXMGB7V3fmBDLDCZtSWEFkTdNCMa9Bu4W16tMBtBtdG30BUNn-qA-fcARlUnd3PFnoeDTdcGbUQSUi-8zbIaGO5yPPC_eMGycdu9551ow0zo0Q8ynNG2HxE=)
* [OpenBSD 5.1 rfork removed - Google Search Results](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGEpBAl9K25n86hV848UrczWm6LNSVADsLuwQu7ht7QA6K74DYgMlJah37BWN-fcTW7GpFh-NfJF_kutz4HsHgyuw-svtjCDJlMfQpKJ7L_PGt4tbXpQP8DqZCQxECKCrs7i7riIBP6L2BarcCBf5T5tyTHhq-j0CQvM_2r1lk_ryaJem1WC-xr)
* [OpenBSD 5.1 release notes (mentioning rfork removal)](https://www.openbsd.org/51.html) (直接の検索結果にはないが、関連情報として推測される)
* [OpenBSD source code (for __tfork and related structures)](https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/kern/syscalls.master?rev=1.249;content-type=text%2Fplain) (直接の検索結果にはないが、関連情報として推測される)