[インデックス 16875] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおけるLinux 32-bitアーキテクチャ(386およびARM)でのprlimit
システムコールに関する引数誤りを修正するものです。具体的には、Getrlimit
とSetrlimit
関数が内部で呼び出すprlimit
システムコールの引数が逆になっていた問題を解決し、それに伴いテストケースを追加しています。
コミット
commit 5852760088f15c68b98f18620e4fa720e1f167da
Author: Peter Mundy <go.peter.90@gmail.com>
Date: Thu Jul 25 09:56:06 2013 -0400
syscall: prlimit argument error for Getrlimit and Setrlimit on Linux 32-bit
The rlimit arguments for prlimit are reversed for linux 32-bit (386 and arm).
Getrlimit becomes Setrlimit and vice versa.
Fixes #5949.
R=iant, mikioh.mikioh, rsc
CC=golang-dev
https://golang.org/cl/11803043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5852760088f15c68b98f18620e4fa720e1f167da
元コミット内容
syscall: prlimit argument error for Getrlimit and Setrlimit on Linux 32-bit
このコミットは、Linux 32-bit (386およびarm) 環境において、prlimit
システムコールに対するrlimit
引数が逆になっていた問題を修正します。具体的には、Getrlimit
がSetrlimit
の引数を、Setrlimit
がGetrlimit
の引数を受け取っていた状態でした。この修正はGo issue #5949を解決します。
変更の背景
このコミットの背景には、Go言語のsyscall
パッケージが提供するシステムコールラッパーと、Linuxカーネルの実際のシステムコールとの間の引数マッピングの不整合がありました。特に、prlimit64
システムコールは、プロセスリソース制限(rlimit)を取得または設定するために使用されますが、その引数の順序が特定のアーキテクチャ(Linux 32-bitの386とARM)でGoのGetrlimit
およびSetrlimit
関数が期待する順序と異なっていたことが問題でした。
Go issue #5949("syscall: prlimit argument error for Getrlimit and Setrlimit on Linux 32-bit")で報告されたこのバグは、Getrlimit
を呼び出すと実際にはSetrlimit
の引数として解釈され、Setrlimit
を呼び出すとGetrlimit
の引数として解釈されるという、引数の「反転」が発生していました。これにより、32-bit Linux環境でリソース制限を正確に取得または設定することができず、アプリケーションの予期せぬ動作やクラッシュにつながる可能性がありました。
この問題は、Goのsyscall
パッケージが低レベルのシステムコールをラップする際に、アーキテクチャ固有の差異を適切に扱えていなかったことに起因します。特に、prlimit64
システムコールは、old_rlimit
とnew_rlimit
という2つのrlimit
構造体ポインタを引数として取ります。old_rlimit
は現在のリソース制限を取得するためのもので、new_rlimit
は新しいリソース制限を設定するためのものです。どちらか一方がNULL
である場合、その操作は行われません。Goのラッパー関数がこれらの引数を誤ってマッピングしていたため、意図しない動作が発生していました。
このコミットは、この引数のマッピングミスを修正し、Getrlimit
とSetrlimit
がそれぞれ期待通りのprlimit
システムコール引数を使用するようにすることで、32-bit Linux環境でのリソース制限操作の正確性を確保することを目的としています。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
-
システムコール (System Call): オペレーティングシステム (OS) のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイル操作、メモリ管理、プロセス制御など、OSの基本的な機能にアクセスするために使用されます。Go言語の
syscall
パッケージは、これらのシステムコールをGoプログラムから呼び出すためのラッパーを提供します。 -
リソース制限 (Resource Limits / rlimit): LinuxなどのUnix系OSでは、プロセスが使用できるシステムリソース(例: オープンできるファイルの最大数、CPU時間、メモリサイズなど)に制限を設けることができます。これは、システム全体の安定性を保ち、特定のプロセスがリソースを使い果たすことを防ぐために重要です。これらの制限は「ソフトリミット (soft limit)」と「ハードリミット (hard limit)」の2種類があります。
- ソフトリミット: 現在適用されている制限で、プロセスはこの値を超えてリソースを使用できません。プロセス自身がこの値をハードリミット以下で引き上げることができます。
- ハードリミット: ソフトリミットの最大値です。非特権プロセスはハードリミットを増やすことはできません。
-
getrlimit()
とsetrlimit()
システムコール: これらのシステムコールは、プロセスのリソース制限を取得 (getrlimit()
) および設定 (setrlimit()
) するための標準的なPOSIX関数です。getrlimit(resource, rlim)
: 指定されたresource
(リソースの種類、例:RLIMIT_NOFILE
)の現在のリミットをrlim
構造体に格納します。setrlimit(resource, rlim)
: 指定されたresource
の新しいリミットをrlim
構造体から読み取り、設定します。
-
prlimit()
/prlimit64()
システムコール:getrlimit()
とsetrlimit()
は現在のプロセスに対してのみ機能しますが、prlimit()
(または64-bit対応のprlimit64()
) は、任意のプロセスID (PID) を指定して、そのプロセスのリソース制限を取得または設定できる、より汎用的なシステムコールです。prlimit(pid, resource, new_rlimit, old_rlimit)
のような形式を取ります。pid
: 対象プロセスのID。0を指定すると現在のプロセス。resource
: リソースの種類。new_rlimit
: 設定したい新しいリソース制限を含むrlimit
構造体へのポインタ。設定しない場合はNULL
。old_rlimit
: 現在のリソース制限を格納するrlimit
構造体へのポインタ。取得しない場合はNULL
。
このシステムコールは、
new_rlimit
とold_rlimit
のどちらか一方または両方をNULL
にすることで、取得のみ、設定のみ、または取得と設定の両方を行うことができます。 -
Go言語の
syscall
パッケージ: Go言語の標準ライブラリの一部であり、OSの低レベルな機能(システムコール)にアクセスするためのインターフェースを提供します。OSごとに異なるシステムコールを抽象化し、Goプログラムから統一的な方法で呼び出せるようにします。しかし、OSやアーキテクチャによっては、システムコールの引数の順序や型が微妙に異なる場合があり、それがこのコミットで修正されたようなバグの原因となることがあります。 -
32-bit vs 64-bit アーキテクチャ: CPUアーキテクチャには32-bitと64-bitがあります。これらは、レジスタのサイズ、メモリアドレス空間のサイズ、データ型の表現、そしてシステムコールの呼び出し規約などに影響を与えます。特に、システムコールの引数の渡し方や、構造体のメモリレイアウトが異なる場合があり、これがクロスアーキテクチャ互換性の問題を引き起こすことがあります。このコミットでは、Linuxの32-bitアーキテクチャ(386/x86およびARM)に特有の問題が修正されています。
これらの知識を前提として、Goのsyscall
パッケージがprlimit
システムコールをラップする際に、32-bit Linux環境でnew_rlimit
とold_rlimit
の引数を誤ってマッピングしていたという問題が、このコミットの核心となります。
技術的詳細
このコミットは、Linux 32-bitアーキテクチャ(386
およびarm
)におけるsyscall
パッケージのGetrlimit
およびSetrlimit
関数の実装に存在するバグを修正します。このバグは、これらの関数が内部で呼び出すprlimit
システムコールの引数渡しに誤りがあったことに起因します。
Linuxカーネルのprlimit64
システムコール(Goのsyscall
パッケージではprlimit
としてラップされていることが多い)のシグネチャは、一般的に以下のようになっています(簡略化):
long prlimit64(pid_t pid, int resource, const struct rlimit64 *new_limit, struct rlimit64 *old_limit);
ここで、new_limit
は設定したい新しいリソース制限へのポインタ、old_limit
は現在のリソース制限を取得して格納するためのポインタです。new_limit
がNULL
の場合、制限は設定されず、old_limit
がNULL
の場合、制限は取得されません。
Goのsyscall
パッケージにおけるGetrlimit
関数は、現在のリソース制限を取得することを目的としています。したがって、prlimit
システムコールを呼び出す際には、new_limit
引数にはNULL
を渡し、old_limit
引数には取得した制限を格納するためのRlimit
構造体へのポインタを渡す必要があります。
同様に、Setrlimit
関数は、新しいリソース制限を設定することを目的としています。したがって、prlimit
システムコールを呼び出す際には、new_limit
引数には設定したい新しい制限を含むRlimit
構造体へのポインタを渡し、old_limit
引数にはNULL
を渡す必要があります。
しかし、このコミット以前のLinux 32-bitアーキテクチャ向けの実装では、この引数の渡し方が逆になっていました。
修正前のコード (src/pkg/syscall/syscall_linux_386.go
および src/pkg/syscall/syscall_linux_arm.go
):
-
Getrlimit
関数内:err = prlimit(0, resource, rlim, nil) // 誤り: rlimがnew_limitの位置に渡されている
ここでは、
rlim
(取得した制限を格納するはずのポインタ)がnew_limit
の位置に渡され、old_limit
の位置にはnil
が渡されていました。これにより、Getrlimit
を呼び出すと、実際にはrlim
の内容でリソース制限を設定しようとし(ただし、rlim
は通常ゼロ値で初期化されているため、意図しない設定が行われる)、現在の制限は取得されませんでした。 -
Setrlimit
関数内:err = prlimit(0, resource, nil, rlim) // 誤り: rlimがold_limitの位置に渡されている
ここでは、
rlim
(設定したい新しい制限を含むポインタ)がold_limit
の位置に渡され、new_limit
の位置にはnil
が渡されていました。これにより、Setrlimit
を呼び出すと、実際には新しい制限は設定されず、rlim
の内容で現在の制限を取得しようとしていました。
この引数の反転は、Goのsyscall
パッケージがprlimit
システムコールをラップする際の、32-bitアーキテクチャ特有の呼び出し規約や、Goの関数シグネチャとC言語のシステムコールシグネチャの間のマッピングの誤解釈が原因と考えられます。
修正後のコード:
-
Getrlimit
関数内:err = prlimit(0, resource, nil, rlim) // 修正後: new_limitはnil, old_limitはrlim
これにより、
Getrlimit
は正しく現在のリソース制限をrlim
に取得するようになります。 -
Setrlimit
関数内:err = prlimit(0, resource, rlim, nil) // 修正後: new_limitはrlim, old_limitはnil
これにより、
Setrlimit
は正しくrlim
で指定された新しいリソース制限を設定するようになります。
この修正は、syscall_linux_386.go
とsyscall_linux_arm.go
の両方に適用されており、32-bit Linux環境におけるGetrlimit
とSetrlimit
の動作の正確性を保証します。
また、このコミットでは、src/pkg/syscall/rlimit_linux_test.go
という新しいテストファイルが追加されています。このテストは、Getrlimit
とSetrlimit
が期待通りに機能するかどうかを検証します。具体的には、RLIMIT_NOFILE
(オープンできるファイルの最大数)のリソース制限を対象に、現在の制限を保存し、新しい制限を設定し、それが正しく適用されたことを確認し、最後に元の制限に戻すという一連の操作を行います。これにより、将来的に同様の回帰バグが発生することを防ぎます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、以下の3つのファイルに集中しています。
src/pkg/syscall/rlimit_linux_test.go
: 新規追加されたテストファイル。src/pkg/syscall/syscall_linux_386.go
: Linux 32-bit (x86) アーキテクチャ向けのシステムコール実装ファイル。src/pkg/syscall/syscall_linux_arm.go
: Linux ARM アーキテクチャ向けのシステムコール実装ファイル。
src/pkg/syscall/rlimit_linux_test.go
(新規追加)
このファイルは、Getrlimit
とSetrlimit
関数の動作を検証するための新しいテストケースTestRlimit
を定義しています。
--- /dev/null
+++ b/src/pkg/syscall/rlimit_linux_test.go
@@ -0,0 +1,41 @@
+// Copyright 2013 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.
+
+package syscall_test
+
+import (
+ "syscall"
+ "testing"
+)
+
+func TestRlimit(t *testing.T) {
+ var rlimit, zero syscall.Rlimit
+ err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit)
+ if err != nil {
+ t.Fatalf("Getrlimit: save failed: %v", err)
+ }
+ if zero == rlimit {
+ t.Fatalf("Getrlimit: save failed: got zero value %#v", rlimit)
+ }
+ set := rlimit
+ set.Cur = set.Max - 1
+ err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &set)
+ if err != nil {
+ t.Fatalf("Setrlimit: set failed: %#v %v", set, err)
+ }
+ var get syscall.Rlimit
+ err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &get)
+ if err != nil {
+ t.Fatalf("Getrlimit: get failed: %v", err)
+ }
+ set = rlimit
+ set.Cur = set.Max - 1
+ if set != get {
+ t.Fatalf("Rlimit: change failed: wanted %#v got %#v", set, get)
+ }
+ err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit)
+ if err != nil {
+ t.Fatalf("Setrlimit: restore failed: %#v %v", rlimit, err)
+ }
+}
src/pkg/syscall/syscall_linux_386.go
このファイルでは、Getrlimit
とSetrlimit
関数内のprlimit
システムコール呼び出しの引数が修正されています。
--- a/src/pkg/syscall/syscall_linux_386.go
+++ b/src/pkg/syscall/syscall_linux_386.go
@@ -78,7 +78,7 @@ const rlimInf32 = ^uint32(0)
const rlimInf64 = ^uint64(0)
func Getrlimit(resource int, rlim *Rlimit) (err error) {
- err = prlimit(0, resource, rlim, nil)
+ err = prlimit(0, resource, nil, rlim)
if err != ENOSYS {
return err
}
@@ -106,7 +106,7 @@ func Getrlimit(resource int, rlim *Rlimit) (err error) {
//sysnb setrlimit(resource int, rlim *rlimit32) (err error) = SYS_SETRLIMIT
func Setrlimit(resource int, rlim *Rlimit) (err error) {
- err = prlimit(0, resource, nil, rlim)
+ err = prlimit(0, resource, rlim, nil)
if err != ENOSYS {
return err
}
src/pkg/syscall/syscall_linux_arm.go
このファイルでも、syscall_linux_386.go
と同様に、Getrlimit
とSetrlimit
関数内のprlimit
システムコール呼び出しの引数が修正されています。
--- a/src/pkg/syscall/syscall_linux_arm.go
+++ b/src/pkg/syscall/syscall_linux_arm.go
@@ -119,7 +119,7 @@ const rlimInf32 = ^uint32(0)
const rlimInf64 = ^uint64(0)
func Getrlimit(resource int, rlim *Rlimit) (err error) {
- err = prlimit(0, resource, rlim, nil)
+ err = prlimit(0, resource, nil, rlim)
if err != ENOSYS {
return err
}
@@ -147,7 +147,7 @@ func Getrlimit(resource int, rlim *rlimit32) (err error) = SYS_SETRLIMIT
//sysnb setrlimit(resource int, rlim *rlimit32) (err error) = SYS_SETRLIMIT
func Setrlimit(resource int, rlim *Rlimit) (err error) {
- err = prlimit(0, resource, nil, rlim)
+ err = prlimit(0, resource, rlim, nil)
if err != ENOSYS {
return err
}
コアとなるコードの解説
このコミットの核心は、Linux 32-bitアーキテクチャ(386およびARM)におけるprlimit
システムコールの引数マッピングの修正です。
Go言語のsyscall
パッケージは、OSのシステムコールをGoの関数としてラップしています。Getrlimit
とSetrlimit
は、それぞれプロセスのリソース制限を取得および設定するための関数です。Linuxでは、これらの操作は内部的にprlimit
(またはprlimit64
)システムコールを介して行われます。
prlimit
システムコールは、一般的に以下のような引数を取ります。
prlimit(pid, resource, new_rlimit_ptr, old_rlimit_ptr)
pid
: 対象プロセスのID。0は現在のプロセスを意味します。resource
: 取得または設定するリソースの種類(例:RLIMIT_NOFILE
)。new_rlimit_ptr
: 新しいリソース制限を設定する場合に、その値を含むRlimit
構造体へのポインタ。設定しない場合はnil
(C言語のNULL
に相当)。old_rlimit_ptr
: 現在のリソース制限を取得する場合に、その値を格納するRlimit
構造体へのポインタ。取得しない場合はnil
。
修正前:
-
Getrlimit
関数:Getrlimit
はリソース制限を「取得」する関数であるため、new_rlimit_ptr
はnil
であるべきで、old_rlimit_ptr
に取得結果を格納するポインタを渡すべきです。 しかし、修正前のコードではprlimit(0, resource, rlim, nil)
となっていました。これは、rlim
(取得結果を格納するポインタ)が誤ってnew_rlimit_ptr
の位置に渡され、old_rlimit_ptr
がnil
になっていました。このため、Getrlimit
を呼び出してもリソース制限は取得されず、むしろrlim
の初期値(通常はゼロ値)でリソース制限を「設定」しようとするという、意図しない動作を引き起こしていました。 -
Setrlimit
関数:Setrlimit
はリソース制限を「設定」する関数であるため、new_rlimit_ptr
に設定する値のポインタを渡し、old_rlimit_ptr
はnil
であるべきです。 しかし、修正前のコードではprlimit(0, resource, nil, rlim)
となっていました。これは、rlim
(設定する値のポインタ)が誤ってold_rlimit_ptr
の位置に渡され、new_rlimit_ptr
がnil
になっていました。このため、Setrlimit
を呼び出してもリソース制限は設定されず、むしろrlim
の内容で現在のリソース制限を「取得」しようとするという、こちらも意図しない動作を引き起こしていました。
修正後:
-
Getrlimit
関数:err = prlimit(0, resource, nil, rlim)
この修正により、new_rlimit_ptr
にnil
が、old_rlimit_ptr
にrlim
が正しく渡されるようになりました。これにより、Getrlimit
は期待通りに現在のリソース制限を取得し、rlim
に格納します。 -
Setrlimit
関数:err = prlimit(0, resource, rlim, nil)
この修正により、new_rlimit_ptr
にrlim
が、old_rlimit_ptr
にnil
が正しく渡されるようになりました。これにより、Setrlimit
は期待通りにrlim
で指定された新しいリソース制限を設定します。
これらの変更は、Linux 32-bit環境におけるsyscall
パッケージのGetrlimit
とSetrlimit
の動作を、他のアーキテクチャや期待される動作と一致させるための重要な修正です。
また、src/pkg/syscall/rlimit_linux_test.go
の追加は、この修正が正しく機能することを検証するためのものです。このテストは、RLIMIT_NOFILE
(オープンできるファイルの最大数)を例にとり、以下の手順で検証を行います。
- 現在の
RLIMIT_NOFILE
の値をGetrlimit
で取得し、rlimit
変数に保存します。 rlimit
がゼロ値でないことを確認します(ゼロ値であれば取得に失敗している可能性が高い)。set
変数にrlimit
の値をコピーし、Cur
(ソフトリミット)をMax - 1
に設定します。Setrlimit
を使って、この新しい制限を適用します。- 再度
Getrlimit
で現在の制限をget
変数に取得します。 set
とget
が一致することを確認し、制限が正しく変更されたことを検証します。- 最後に、
Setrlimit
を使って元のrlimit
の値に戻し、テスト環境をクリーンアップします。
このテストの追加により、将来的に同様の引数マッピングの誤りが発生した場合に、自動的に検出できるようになります。
関連リンク
- Go issue #5949: https://github.com/golang/go/issues/5949
- Go CL 11803043: https://golang.org/cl/11803043 (このコミットに対応するGoの変更リスト)
参考にした情報源リンク
prlimit(2)
- Linux man page: https://man7.org/linux/man-pages/man2/prlimit.2.htmlgetrlimit(2)
/setrlimit(2)
- Linux man page: https://man7.org/linux/man-pages/man2/getrlimit.2.html- Go
syscall
package documentation: https://pkg.go.dev/syscall - Go
Rlimit
struct: https://pkg.go.dev/syscall#Rlimit - Go
RLIMIT_NOFILE
constant: https://pkg.go.dev/syscall#pkg-constants