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

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

このコミットは、GoランタイムがFreeBSD上でユーザーモードの同期プリミティブであるumtxを使用する際の挙動を修正するものです。具体的には、UMTX_OP_WAITオペレーションをUMTX_OP_WAIT_UINTに変更し、umtxシステムコールに渡されるアドレスが指すデータ型と、Goランタイム内部で実際に使用されているデータ型との整合性を確保します。

コミット

commit 9fe8681df6c16d5c534fe43a04e5fd43d7cdc521
Author: Ian Lance Taylor <iant@golang.org>
Date:   Fri Apr 12 05:20:15 2013 -0700

    runtime: use UMTX_OP_WAIT_UINT on FreeBSD
    
    UMTX_OP_WAIT expects that the address points to a uintptr, but
    the code in lock_futex.c uses a uint32.  UMTX_OP_WAIT_UINT is
    just like UMTX_OP_WAIT, but the address points to a uint32.
    This almost certainly makes no difference on a little-endian
    system, but since the kernel supports it we should do the
    right thing.  And, who knows, maybe it matters.
    
    R=golang-dev, bradfitz, r, ality
    CC=golang-dev
    https://golang.org/cl/8699043

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

https://github.com/golang/go/commit/9fe8681df6c16d5c534fe43a04e5fd43dcdc521

元コミット内容

GoランタイムがFreeBSD上でumtxシステムコールを使用する際、UMTX_OP_WAITオペレーションが期待するアドレスの型がuintptrであるにもかかわらず、lock_futex.c内のコードではuint32を使用していた。UMTX_OP_WAIT_UINTUMTX_OP_WAITと同様の機能を提供するが、アドレスがuint32を指すことを想定している。この変更は、リトルエンディアンシステムではほとんど影響がない可能性が高いが、カーネルがこの機能(UMTX_OP_WAIT_UINT)をサポートしているため、正しい実装に合わせるべきである。将来的に問題が発生する可能性を排除するためにも、この修正は重要である。

変更の背景

Goランタイムは、スレッド間の同期のためにオペレーティングシステムが提供する低レベルの同期プリミティブを利用します。FreeBSDにおいては、これはumtx(user-mode mutex)システムコールを通じて行われます。umtxは、ユーザー空間での高速な同期を可能にし、競合が発生した場合にのみカーネルにトラップするという点で、Linuxのfutexに似ています。

このコミットの背景には、umtxシステムコールが期待する引数の型と、Goランタイムが実際に渡していた引数の型との間に不整合があったという問題があります。具体的には、UMTX_OP_WAITという操作は、待機対象のアドレスがuintptr型(ポインタを保持できる符号なし整数型)を指すことを想定していました。しかし、Goランタイムのlock_futex.c内のコードでは、このアドレスがuint32型(32ビット符号なし整数型)を指しているものとして扱われていました。

この不整合は、特にリトルエンディアンシステムでは、uintptruint32のサイズが異なる場合でも、下位32ビットが同じであるため、実際には問題を引き起こさないことがほとんどでした。しかし、これは潜在的なバグであり、将来的なアーキテクチャの変更や、エンディアンネスが異なるシステムでの予期せぬ挙動につながる可能性がありました。カーネルがUMTX_OP_WAIT_UINTという、uint32型のアドレスを明示的にサポートするオペレーションを提供している以上、Goランタイムがその正しいインターフェースを使用することは、堅牢性と正確性を高める上で重要であると判断されました。

前提知識の解説

  1. Futex (Fast Userspace Mutex) / UMTX (User-Mode Mutex):

    • これらは、ユーザー空間でスレッド間の同期を行うための低レベルなメカニズムです。Linuxではfutex、FreeBSDではumtxと呼ばれます。
    • 基本的な考え方は、競合がない限りカーネルへのシステムコールを避け、ユーザー空間でロックやアンロックを行うことで、パフォーマンスを向上させることです。
    • スレッドがロックを取得しようとして失敗した場合(競合が発生した場合)、futexumtxシステムコールを呼び出してカーネルに待機を指示します。ロックが解放された際には、別のスレッドがカーネルを介して待機中のスレッドを起こします。
    • これらのシステムコールは、特定のメモリアドレス(通常は整数値)を監視し、その値が変化したときに待機中のスレッドを再開させます。
  2. uintptruint32:

    • uint32: 32ビットの符号なし整数型です。通常、32ビットの値を格納するために使用されます。
    • uintptr: ポインタを保持するのに十分な大きさを持つ符号なし整数型です。これは、システムが32ビットアーキテクチャか64ビットアーキテクチャかによってサイズが異なります。32ビットシステムではuint32と同じサイズになることが多いですが、64ビットシステムではuint64と同じサイズになります。
    • システムコールにおいて、メモリアドレスを引数として渡す場合、そのアドレスが指すデータの型が重要になります。UMTX_OP_WAITuintptrが指す値を期待し、UMTX_OP_WAIT_UINTuint32が指す値を期待します。
  3. エンディアンネス (Endianness):

    • マルチバイトデータ(例えば、uint32uintptrのような4バイト以上の整数)がメモリにどのように格納されるかを示すバイト順序のことです。
    • リトルエンディアン (Little-endian): 最下位バイトが最も低いメモリアドレスに格納されます。Intel x86アーキテクチャがこれに該当します。
    • ビッグエンディアン (Big-endian): 最上位バイトが最も低いメモリアドレスに格納されます。ネットワークバイトオーダーや、一部のARM、PowerPCなどがこれに該当します。
    • このコミットの元の問題は、リトルエンディアンシステムではuintptruint32のサイズが異なっても、下位バイトの並びが同じであるため、実質的な問題が生じにくかったという点にあります。しかし、これはあくまで「たまたま問題が顕在化しなかった」だけであり、厳密な型の一致は重要です。

技術的詳細

FreeBSDのumtxシステムコールは、様々な操作(UMTX_OP_*)をサポートしており、それぞれの操作は特定の引数と期待されるデータ型を持っています。

  • UMTX_OP_WAIT: この操作は、指定されたアドレスが指す値が特定の値と一致するまでスレッドを待機させます。この操作は、歴史的に、または一般的なポインタの扱いの観点から、アドレスがuintptr型の値を指すことを期待していました。つまり、ポインタのサイズに合わせたデータ幅で値を読み書きすることを想定しています。
  • UMTX_OP_WAIT_UINT: この操作は、UMTX_OP_WAITと機能的には非常に似ていますが、明確に指定されたアドレスがuint32型の値を指すことを期待します。これは、特定のユースケース(例えば、32ビットのカウンタやフラグを待機する場合)において、より厳密な型安全性を保証するために導入されたものです。

Goランタイムのlock_futex.c(このコミットでは直接変更されていないが、関連するロジックを持つファイル)では、同期変数としてuint32型の値が使用されていました。しかし、runtime·sys_umtx_op関数を呼び出す際に、UMTX_OP_WAITオペレーションを使用していたため、カーネルはuintptrが指す値を読み取ろうとしていました。

この不整合は、特に32ビットシステムや、リトルエンディアンの64ビットシステムでは、uint32uintptrの下位32ビットが同じであるため、実質的な問題を引き起こしにくいという特性がありました。しかし、これは「たまたま動いていた」状態であり、厳密な意味での正しいインターフェースの使用ではありませんでした。

このコミットでは、UMTX_OP_WAITUMTX_OP_WAIT_UINTに置き換えることで、Goランタイムがumtxシステムコールに渡すアドレスが、実際にuint32型の値を指しているというGoランタイムの内部的な仮定と、カーネルが期待するデータ型との間の整合性を確立します。これにより、コードの正確性が向上し、将来的な互換性の問題や、エンディアンネスに起因する潜在的なバグのリスクが低減されます。

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

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

  1. src/pkg/runtime/defs_freebsd.go
  2. src/pkg/runtime/defs_freebsd_386.h
  3. src/pkg/runtime/defs_freebsd_amd64.h
  4. src/pkg/runtime/defs_freebsd_arm.h
  5. src/pkg/runtime/os_freebsd.c

これらの変更は、FreeBSD向けのGoランタイムの定義ファイルと、FreeBSD固有のOSインタラクションを扱うCコードに集中しています。

コアとなるコードの解説

変更の核心は、UMTX_OP_WAITという定数をUMTX_OP_WAIT_UINTに置き換えることです。

1. src/pkg/runtime/defs_freebsd.go および src/pkg/runtime/defs_freebsd_*.h ファイル群:

これらのファイルは、GoランタイムがFreeBSDのシステムコールや定数にアクセスするための定義を含んでいます。各アーキテクチャ(386, amd64, arm)ごとにヘッダーファイルが存在します。

変更前:

// src/pkg/runtime/defs_freebsd.go
const (
    UMTX_OP_WAIT = C.UMTX_OP_WAIT
    UMTX_OP_WAKE = C.UMTX_OP_WAKE
)
// src/pkg/runtime/defs_freebsd_386.h (同様にamd64, armも)
enum {
    UMTX_OP_WAIT    = 0x2,
    UMTX_OP_WAKE    = 0x3,
};

変更後:

// src/pkg/runtime/defs_freebsd.go
const (
    UMTX_OP_WAIT_UINT = C.UMTX_OP_WAIT_UINT // UMTX_OP_WAIT を UMTX_OP_WAIT_UINT に変更
    UMTX_OP_WAKE      = C.UMTX_OP_WAKE
)
// src/pkg/runtime/defs_freebsd_386.h (同様にamd64, armも)
enum {
    UMTX_OP_WAIT_UINT   = 0xb, // UMTX_OP_WAIT の値 0x2 から UMTX_OP_WAIT_UINT の値 0xb に変更
    UMTX_OP_WAKE        = 0x3,
};

これらの変更により、GoランタイムがFreeBSDのumtxシステムコールを呼び出す際に使用する定数が、uint32を対象とするUMTX_OP_WAIT_UINTに正しくマッピングされるようになります。UMTX_OP_WAIT_UINTの具体的な値(0xb)は、FreeBSDカーネルによって定義されたものです。

2. src/pkg/runtime/os_freebsd.c ファイル:

このファイルは、FreeBSD固有のOSレベルの操作、特にfutex(Goランタイムではruntime·futexsleepとして実装)の実装を含んでいます。

変更前:

// src/pkg/runtime/os_freebsd.c
ret = runtime·sys_umtx_op(addr, UMTX_OP_WAIT, val, nil, tsp);

変更後:

// src/pkg/runtime/os_freebsd.c
ret = runtime·sys_umtx_op(addr, UMTX_OP_WAIT_UINT, val, nil, tsp); // UMTX_OP_WAIT を UMTX_OP_WAIT_UINT に変更

runtime·futexsleep関数内で、実際にumtxシステムコールを呼び出すruntime·sys_umtx_opの第二引数として渡されるオペレーションコードが、UMTX_OP_WAITからUMTX_OP_WAIT_UINTに変更されています。これにより、addrが指すuint32型の値に対して、カーネルが正しく待機操作を実行するようになります。

この一連の変更は、GoランタイムがFreeBSDのumtxシステムコールとより正確かつ意図された方法で対話することを保証し、潜在的な型不整合の問題を解消します。

関連リンク

参考にした情報源リンク