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

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

このコミットは、Go言語のsyscallパッケージにおけるLinux 32-bitアーキテクチャ(386およびARM)でのprlimitシステムコールに関する引数誤りを修正するものです。具体的には、GetrlimitSetrlimit関数が内部で呼び出す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引数が逆になっていた問題を修正します。具体的には、GetrlimitSetrlimitの引数を、SetrlimitGetrlimitの引数を受け取っていた状態でした。この修正は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_rlimitnew_rlimitという2つのrlimit構造体ポインタを引数として取ります。old_rlimitは現在のリソース制限を取得するためのもので、new_rlimitは新しいリソース制限を設定するためのものです。どちらか一方がNULLである場合、その操作は行われません。Goのラッパー関数がこれらの引数を誤ってマッピングしていたため、意図しない動作が発生していました。

このコミットは、この引数のマッピングミスを修正し、GetrlimitSetrlimitがそれぞれ期待通りのprlimitシステムコール引数を使用するようにすることで、32-bit Linux環境でのリソース制限操作の正確性を確保することを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念について理解しておく必要があります。

  1. システムコール (System Call): オペレーティングシステム (OS) のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイル操作、メモリ管理、プロセス制御など、OSの基本的な機能にアクセスするために使用されます。Go言語のsyscallパッケージは、これらのシステムコールをGoプログラムから呼び出すためのラッパーを提供します。

  2. リソース制限 (Resource Limits / rlimit): LinuxなどのUnix系OSでは、プロセスが使用できるシステムリソース(例: オープンできるファイルの最大数、CPU時間、メモリサイズなど)に制限を設けることができます。これは、システム全体の安定性を保ち、特定のプロセスがリソースを使い果たすことを防ぐために重要です。これらの制限は「ソフトリミット (soft limit)」と「ハードリミット (hard limit)」の2種類があります。

    • ソフトリミット: 現在適用されている制限で、プロセスはこの値を超えてリソースを使用できません。プロセス自身がこの値をハードリミット以下で引き上げることができます。
    • ハードリミット: ソフトリミットの最大値です。非特権プロセスはハードリミットを増やすことはできません。
  3. getrlimit()setrlimit() システムコール: これらのシステムコールは、プロセスのリソース制限を取得 (getrlimit()) および設定 (setrlimit()) するための標準的なPOSIX関数です。

    • getrlimit(resource, rlim): 指定されたresource(リソースの種類、例: RLIMIT_NOFILE)の現在のリミットをrlim構造体に格納します。
    • setrlimit(resource, rlim): 指定されたresourceの新しいリミットをrlim構造体から読み取り、設定します。
  4. 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_rlimitold_rlimitのどちらか一方または両方をNULLにすることで、取得のみ、設定のみ、または取得と設定の両方を行うことができます。

  5. Go言語のsyscallパッケージ: Go言語の標準ライブラリの一部であり、OSの低レベルな機能(システムコール)にアクセスするためのインターフェースを提供します。OSごとに異なるシステムコールを抽象化し、Goプログラムから統一的な方法で呼び出せるようにします。しかし、OSやアーキテクチャによっては、システムコールの引数の順序や型が微妙に異なる場合があり、それがこのコミットで修正されたようなバグの原因となることがあります。

  6. 32-bit vs 64-bit アーキテクチャ: CPUアーキテクチャには32-bitと64-bitがあります。これらは、レジスタのサイズ、メモリアドレス空間のサイズ、データ型の表現、そしてシステムコールの呼び出し規約などに影響を与えます。特に、システムコールの引数の渡し方や、構造体のメモリレイアウトが異なる場合があり、これがクロスアーキテクチャ互換性の問題を引き起こすことがあります。このコミットでは、Linuxの32-bitアーキテクチャ(386/x86およびARM)に特有の問題が修正されています。

これらの知識を前提として、Goのsyscallパッケージがprlimitシステムコールをラップする際に、32-bit Linux環境でnew_rlimitold_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_limitNULLの場合、制限は設定されず、old_limitNULLの場合、制限は取得されません。

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.gosyscall_linux_arm.goの両方に適用されており、32-bit Linux環境におけるGetrlimitSetrlimitの動作の正確性を保証します。

また、このコミットでは、src/pkg/syscall/rlimit_linux_test.goという新しいテストファイルが追加されています。このテストは、GetrlimitSetrlimitが期待通りに機能するかどうかを検証します。具体的には、RLIMIT_NOFILE(オープンできるファイルの最大数)のリソース制限を対象に、現在の制限を保存し、新しい制限を設定し、それが正しく適用されたことを確認し、最後に元の制限に戻すという一連の操作を行います。これにより、将来的に同様の回帰バグが発生することを防ぎます。

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

このコミットにおけるコアとなるコードの変更は、以下の3つのファイルに集中しています。

  1. src/pkg/syscall/rlimit_linux_test.go: 新規追加されたテストファイル。
  2. src/pkg/syscall/syscall_linux_386.go: Linux 32-bit (x86) アーキテクチャ向けのシステムコール実装ファイル。
  3. src/pkg/syscall/syscall_linux_arm.go: Linux ARM アーキテクチャ向けのシステムコール実装ファイル。

src/pkg/syscall/rlimit_linux_test.go (新規追加)

このファイルは、GetrlimitSetrlimit関数の動作を検証するための新しいテストケース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

このファイルでは、GetrlimitSetrlimit関数内の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と同様に、GetrlimitSetrlimit関数内の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の関数としてラップしています。GetrlimitSetrlimitは、それぞれプロセスのリソース制限を取得および設定するための関数です。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_ptrnilであるべきで、old_rlimit_ptrに取得結果を格納するポインタを渡すべきです。 しかし、修正前のコードでは prlimit(0, resource, rlim, nil) となっていました。これは、rlim(取得結果を格納するポインタ)が誤ってnew_rlimit_ptrの位置に渡され、old_rlimit_ptrnilになっていました。このため、Getrlimitを呼び出してもリソース制限は取得されず、むしろrlimの初期値(通常はゼロ値)でリソース制限を「設定」しようとするという、意図しない動作を引き起こしていました。

  • Setrlimit関数: Setrlimitはリソース制限を「設定」する関数であるため、new_rlimit_ptrに設定する値のポインタを渡し、old_rlimit_ptrnilであるべきです。 しかし、修正前のコードでは prlimit(0, resource, nil, rlim) となっていました。これは、rlim(設定する値のポインタ)が誤ってold_rlimit_ptrの位置に渡され、new_rlimit_ptrnilになっていました。このため、Setrlimitを呼び出してもリソース制限は設定されず、むしろrlimの内容で現在のリソース制限を「取得」しようとするという、こちらも意図しない動作を引き起こしていました。

修正後:

  • Getrlimit関数: err = prlimit(0, resource, nil, rlim) この修正により、new_rlimit_ptrnilが、old_rlimit_ptrrlimが正しく渡されるようになりました。これにより、Getrlimitは期待通りに現在のリソース制限を取得し、rlimに格納します。

  • Setrlimit関数: err = prlimit(0, resource, rlim, nil) この修正により、new_rlimit_ptrrlimが、old_rlimit_ptrnilが正しく渡されるようになりました。これにより、Setrlimitは期待通りにrlimで指定された新しいリソース制限を設定します。

これらの変更は、Linux 32-bit環境におけるsyscallパッケージのGetrlimitSetrlimitの動作を、他のアーキテクチャや期待される動作と一致させるための重要な修正です。

また、src/pkg/syscall/rlimit_linux_test.goの追加は、この修正が正しく機能することを検証するためのものです。このテストは、RLIMIT_NOFILE(オープンできるファイルの最大数)を例にとり、以下の手順で検証を行います。

  1. 現在のRLIMIT_NOFILEの値をGetrlimitで取得し、rlimit変数に保存します。
  2. rlimitがゼロ値でないことを確認します(ゼロ値であれば取得に失敗している可能性が高い)。
  3. set変数にrlimitの値をコピーし、Cur(ソフトリミット)をMax - 1に設定します。
  4. Setrlimitを使って、この新しい制限を適用します。
  5. 再度Getrlimitで現在の制限をget変数に取得します。
  6. setgetが一致することを確認し、制限が正しく変更されたことを検証します。
  7. 最後に、Setrlimitを使って元のrlimitの値に戻し、テスト環境をクリーンアップします。

このテストの追加により、将来的に同様の引数マッピングの誤りが発生した場合に、自動的に検出できるようになります。

関連リンク

参考にした情報源リンク