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

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

このコミットは、Go言語のsyscallパッケージにおいて、Linuxの386およびARMアーキテクチャ向けにGetrlimitおよびSetrlimitシステムコールが32ビット構造体を使用するように修正したものです。これにより、rlimit関連のシステムコールが正しく動作しない問題(Go Issue #2492)が解決されました。

コミット

commit 8b7d39e7b633a2257f38ee77f7b558dbacf92f65
Author: Han-Wen Nienhuys <hanwen@google.com>
Date:   Mon Jul 2 22:57:32 2012 -0700

    syscall: use 32 bits structure for Getrlimit/Setrlimit on 386/ARM.
    
    Fixes #2492
    
    R=rsc, bradfitz
    CC=golang-dev
    https://golang.org/cl/6295073

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

https://github.com/golang/go/commit/8b7d39e7b633a2257f38ee77f7b558dbacf92f65

元コミット内容

このコミットは、Go言語のsyscallパッケージにおけるGetrlimitおよびSetrlimitシステムコールの実装を修正するものです。特に、386(x86 32-bit)およびARMアーキテクチャにおいて、これらのシステムコールが期待通りに動作しない問題に対処しています。修正の核心は、これらのアーキテクチャでrlimit構造体を扱う際に、64ビットではなく32ビットの構造体を使用するように変更することです。これにより、Go言語のsyscallパッケージがLinuxカーネルのrlimit関連のシステムコールと正しくインターフェースできるようになります。

変更の背景

この変更は、Go Issue #2492「syscall: use prlimit64, not prlimit」を修正するために行われました。この問題は、Goのtypes_linux.goファイルが_FILE_OFFSET_BITS 64を定義していたことに起因します。これにより、ソースコードが64ビットのrlim_t型でコンパイルされていました。しかし、getrlimit()システムコールは内部的にprlimit()straceではprlimit64として識別される)にエイリアスされており、特に32ビットアーキテクチャ(386やARM)では、期待される32ビットのrlimit構造体ではなく、64ビットの構造体でシステムコールが呼び出されることで不整合が生じていました。

この不整合により、GetrlimitSetrlimitが正しく機能せず、リソース制限の取得や設定に問題が発生していました。このコミットは、Prlimitシステムコールを優先的に使用し、それが利用できない場合にのみ従来のgetrlimit/setrlimitを使用するようにフォールバックロジックを導入することで、この問題を解決しています。さらに、32ビットアーキテクチャ向けに明示的に32ビットのrlimit構造体(rlimit32)を定義し、GoのRlimit型との間で変換を行うことで、アーキテクチャ間の互換性を確保しています。

前提知識の解説

  • システムコール (Syscall): オペレーティングシステムが提供するサービスをプログラムが利用するためのインターフェースです。ファイル操作、メモリ管理、プロセス制御など、低レベルな操作を行います。Go言語のsyscallパッケージは、これらのシステムコールをGoプログラムから呼び出すための機能を提供します。
  • rlimit (Resource Limit): プロセスが利用できるシステムリソース(例: オープンできるファイルの最大数、利用できるCPU時間、メモリ量など)を制限するための仕組みです。getrlimitシステムコールで現在のリソース制限を取得し、setrlimitシステムコールで設定します。
  • Rlimit構造体: rlimitシステムコールで使用される構造体で、通常はrlim_cur(現在のソフトリミット)とrlim_max(ハードリミット)の2つのフィールドを持ちます。これらのフィールドは、リソースの最大値を表します。
  • prlimitシステムコール: Linuxカーネル2.6.36で導入された新しいシステムコールで、特定のプロセス(pidで指定)のリソース制限を取得・設定できます。従来のgetrlimit/setrlimitは現在のプロセスのリソース制限のみを扱いました。prlimitはより柔軟で、64ビットの値を直接扱えるprlimit64として実装されることが多いです。
  • 32ビット/64ビットアーキテクチャ: CPUのレジスタ幅やメモリアドレス空間のサイズを指します。32ビットシステムではポインタや整数が32ビット(4バイト)で表現されるのに対し、64ビットシステムでは64ビット(8バイト)で表現されます。これにより、扱えるメモリ量やデータ型のサイズに違いが生じます。
  • ENOSYSエラー: システムコールが実装されていない、または利用できない場合に返されるエラーコードです。このコミットでは、Prlimitが利用できない場合にENOSYSをチェックし、従来のgetrlimit/setrlimitにフォールバックするロジックが導入されています。
  • uint32uint64: Go言語における符号なし32ビット整数型と符号なし64ビット整数型です。rlimit構造体のフィールドは、これらの型で表現されることがあります。
  • rlimInf32rlimInf64: リソース制限が無制限であることを示す定数です。32ビットシステムでは^uint32(0)(すべてのビットが1)、64ビットシステムでは^uint64(0)で表現されます。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. Prlimitの優先使用: GetrlimitおよびSetrlimit関数内で、まずPrlimitシステムコールを呼び出すように変更されました。これは、Prlimitがより新しいシステムコールであり、64ビットのrlimit値を直接扱えるため、より堅牢な方法であるためです。
  2. ENOSYSによるフォールバック: PrlimitシステムコールがENOSYSエラーを返した場合(つまり、カーネルがPrlimitをサポートしていない場合)、従来のgetrlimitまたはsetrlimitシステムコールにフォールバックするロジックが追加されました。これにより、古いLinuxカーネルでもGoプログラムが動作するように互換性が保たれます。
  3. 32ビットrlimit構造体 rlimit32の導入: 386およびARMアーキテクチャ向けに、rlimit32という新しい構造体が定義されました。この構造体は、CurMaxフィールドをuint32型として持ちます。これは、これらのアーキテクチャのLinuxカーネルがrlimitシステムコールで32ビットの構造体を期待するためです。
  4. Rlimitrlimit32間の変換: GetrlimitおよびSetrlimit関数内で、Goの標準Rlimit構造体(フィールドがuint64)と、アーキテクチャ固有のrlimit32構造体との間で値の変換が行われます。
    • Getrlimitでは、rlimit32で取得した値をRlimituint64フィールドに変換します。rlimInf32(32ビットの無制限値)はrlimInf64(64ビットの無制限値)に変換されます。
    • Setrlimitでは、Rlimituint64フィールドをrlimit32uint32フィールドに変換します。この際、rlimInf64rlimInf32に変換され、uint32の範囲を超える値が設定されようとした場合はEINVALエラーを返します。
  5. zsyscallファイルの変更: zsyscall_linux_386.gozsyscall_linux_amd64.gozsyscall_linux_arm.goファイルから、従来のGetrlimitSetrlimitの直接的なシステムコール呼び出しが削除されました。代わりに、Prlimitのシステムコール呼び出しが追加され、386およびARM向けにはgetrlimitsetrlimitという新しい内部関数が、rlimit32構造体を使用するように定義されました。これらのzsyscallファイルは、Goのツールによって自動生成されるため、この変更はGoのビルドプロセスに統合されています。

この変更により、Go言語のsyscallパッケージは、異なるアーキテクチャ(特に32ビットシステム)上でのrlimit関連のシステムコールをより正確かつ堅牢に処理できるようになりました。

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

このコミットで変更された主要なファイルとコードスニペットは以下の通りです。

  • src/pkg/syscall/syscall_linux.go:
    • GetrlimitSetrlimit//sysnbディレクティブがコメントアウトされ、Prlimit//sysディレクティブが追加されました。これは、これらの関数がアーキテクチャ固有のファイルでカスタム実装されることを示唆しています。
  • src/pkg/syscall/syscall_linux_386.go および src/pkg/syscall/syscall_linux_arm.go:
    • rlimit32構造体が定義されました。
      type rlimit32 struct {
      	Cur uint32
      	Max uint32
      }
      
    • getrlimitsetrlimit//sysnbディレクティブが追加され、rlimit32構造体を使用するように指定されました。
    • Getrlimit関数が再実装され、Prlimitを試行し、ENOSYSの場合はgetrlimitにフォールバックし、rlimit32からRlimitへの変換を行うロジックが追加されました。
      func Getrlimit(resource int, rlim *Rlimit) (err error) {
      	err = Prlimit(0, resource, rlim, nil)
      	if err != ENOSYS {
      		return err
      	}
      
      	rl := rlimit32{}
      	err = getrlimit(resource, &rl)
      	if err != nil {
      		return
      	}
      
      	if rl.Cur == rlimInf32 {
      		rlim.Cur = rlimInf64
      	} else {
      		rlim.Cur = uint64(rl.Cur)
      	}
      
      	if rl.Max == rlimInf32 {
      		rlim.Max = rlimInf64
      	} else {
      		rlim.Max = uint64(rl.Max)
      	}
      	return
      }
      
    • Setrlimit関数も同様に再実装され、Prlimitを試行し、ENOSYSの場合はsetrlimitにフォールバックし、Rlimitからrlimit32への変換と値の検証を行うロジックが追加されました。
      func Setrlimit(resource int, rlim *Rlimit) (err error) {
      	err = Prlimit(0, resource, nil, rlim)
      	if err != ENOSYS {
      		return err
      	}
      
      	rl := rlimit32{}
      	if rlim.Cur == rlimInf64 {
      		rl.Cur = rlimInf32
      	} else if rlim.Cur < uint64(rlimInf32) {
      		rl.Cur = uint32(rlim.Cur)
      	} else {
      		return EINVAL
      	}
      	if rlim.Max == rlimInf64 {
      		rl.Max = rlimInf32
      	} else if rlim.Max < uint64(rlimInf32) {
      		rl.Max = uint32(rlim.Max)
      	} else {
      		return EINVAL
      	}
      
      	return setrlimit(resource, &rl)
      }
      
  • src/pkg/syscall/syscall_linux_amd64.go:
    • GetrlimitSetrlimit//sysnbディレクティブが追加されました。これは、amd64ではRlimit構造体が64ビットであるため、直接システムコールを呼び出すように変更されたことを示唆しています。
  • src/pkg/syscall/zsyscall_linux_386.go, src/pkg/syscall/zsyscall_linux_amd64.go, src/pkg/syscall/zsyscall_linux_arm.go:
    • 従来のGetrlimitSetrlimitの自動生成された関数が削除されました。
    • Prlimitの自動生成された関数が追加されました。
    • 386およびARM向けには、getrlimitsetrlimitという新しい自動生成関数が、rlimit32構造体を使用するように追加されました。

コアとなるコードの解説

このコミットの核心は、Go言語のsyscallパッケージがLinuxカーネルのリソース制限(rlimit)システムコールを扱う方法を、アーキテクチャの特性に合わせて最適化し、バグを修正した点にあります。

  1. Prlimitの導入とフォールバック: GoのGetrlimitSetrlimit関数は、まず新しいprlimitシステムコール(GoではPrlimitとしてラップ)を呼び出そうとします。

    err = Prlimit(0, resource, rlim, nil) // Getrlimitの場合
    err = Prlimit(0, resource, nil, rlim) // Setrlimitの場合
    if err != ENOSYS {
        return err
    }
    

    Prlimitは、従来のgetrlimit/setrlimitよりも柔軟で、64ビットのrlimit値を直接扱えるため、より推奨される方法です。しかし、古いLinuxカーネルではPrlimitが利用できない場合があります。その場合、カーネルはENOSYS(No such system call)エラーを返します。このコミットでは、このENOSYSエラーを検知した場合に、従来のgetrlimit/setrlimitシステムコールにフォールバックするロジックが組み込まれています。これにより、Goプログラムは幅広いLinux環境でリソース制限を適切に扱えるようになります。

  2. 32ビットアーキテクチャ(386/ARM)におけるrlimit32の利用: GoのRlimit構造体は、CurMaxフィールドをuint64型として定義しています。これは64ビットシステムでは問題ありませんが、32ビットシステム(386やARM)のLinuxカーネルは、getrlimit/setrlimitシステムコールで32ビットのrlimit構造体(rlim_currlim_maxが32ビット)を期待します。この不一致がGo Issue #2492の原因でした。

    このコミットでは、この問題を解決するために、386およびARMアーキテクチャ向けにrlimit32という新しい構造体を導入しました。

    type rlimit32 struct {
    	Cur uint32
    	Max uint32
    }
    

    そして、GetrlimitSetrlimit関数内で、GoのRlimit構造体と、実際にシステムコールに渡されるrlimit32構造体との間で、値の変換と検証が行われます。

    • Getrlimitの変換: getrlimitシステムコールからrlimit32構造体で値を取得した後、GoのRlimit構造体に変換します。この際、32ビットの無制限値rlimInf32は64ビットの無制限値rlimInf64に適切にマッピングされます。

      if rl.Cur == rlimInf32 {
      	rlim.Cur = rlimInf64
      } else {
      	rlim.Cur = uint64(rl.Cur)
      }
      // Maxも同様
      
    • Setrlimitの変換と検証: Setrlimitでは、GoのRlimit構造体からrlimit32構造体に値を変換します。ここでの重要な点は、uint64の値をuint32に変換する際に、値がuint32の範囲内に収まっているかどうかの検証が行われることです。

      if rlim.Cur == rlimInf64 {
      	rl.Cur = rlimInf32
      } else if rlim.Cur < uint64(rlimInf32) { // uint32の最大値より小さいか
      	rl.Cur = uint32(rlim.Cur)
      } else {
      	return EINVAL // 範囲外の場合はエラー
      }
      // Maxも同様
      

      これにより、32ビットシステムで扱えない大きなリソース制限値を設定しようとした場合に、EINVAL(Invalid argument)エラーが返されるようになり、不正な状態を防ぎます。

この一連の変更により、Go言語のsyscallパッケージは、異なるLinuxアーキテクチャ間でのrlimitシステムコールの挙動の差異を吸収し、より堅牢で互換性の高いリソース制限管理機能を提供できるようになりました。

関連リンク

参考にした情報源リンク