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

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

このコミットは、Go言語のsyscallパッケージにファイルロック機能を追加するものです。具体的には、Flock_t構造体にLockメソッドが追加され、fcntlシステムコールを介したファイルロック操作を可能にしています。また、32-bit Linuxシステムにおけるfcntlシステムコールの差異を吸収するためのファイルも新規追加されています。

変更されたファイルは以下の通りです。

  • src/pkg/syscall/consistency_unix_test.go: Flock_t構造体のフィールド型がint64に変更されています。これは、StartLenフィールドが32-bitシステムと64-bitシステムで一貫した型を持つようにするための修正です。
  • src/pkg/syscall/flock.go: Flock_t構造体にLockメソッドが追加され、fcntlシステムコールを呼び出すための共通ロジックが実装されています。このファイルは新規作成です。
  • src/pkg/syscall/flock_linux_32bit.go: 32-bit Linuxシステム(linux/386およびlinux/arm)において、fcntlシステムコールがSYS_FCNTL64として扱われるべきであることを設定するinit関数が含まれています。このファイルも新規作成です。

コミット

commit 5d3033c5907956fd982409dd4a543f6866dd675e
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Jan 21 14:52:44 2014 -0800

    syscall: add Flock_t.Lock method

    Fixes #7059

    R=golang-codereviews, iant, mikioh.mikioh
    CC=golang-codereviews
    https://golang.org/cl/53470043

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

https://github.com/golang/go/commit/5d3033c5907956fd982409dd4a543f6866dd675e

元コミット内容

syscall: add Flock_t.Lock method

Fixes #7059

R=golang-codereviews, iant, mikioh.mikioh
CC=golang-codereviews
https://golang.org/cl/53470043

変更の背景

このコミットは、Go言語のsyscallパッケージにファイルロック機能を追加することを目的としています。コミットメッセージにあるFixes #7059が示す通り、この変更はGoのIssue 7059「syscall: add flock(2)」に対応するものです。

Issue 7059では、GoプログラムからUnix系のflock(2)システムコール(またはfcntl(2)のファイルロック機能)を直接利用できるようにする要望が出されていました。ファイルロックは、複数のプロセスが同じファイルに同時にアクセスする際に、データの整合性を保つために不可欠な機能です。例えば、ログファイルへの書き込み、設定ファイルの更新、データベースファイルの排他制御など、多くのアプリケーションで利用されます。

Goの標準ライブラリには、osパッケージを通じて基本的なファイル操作は提供されていましたが、低レベルなファイルロック機能は直接サポートされていませんでした。そのため、Goでファイルロックを必要とするアプリケーションを開発する場合、Cgoを使ってC言語のライブラリを呼び出すか、あるいはGoのsyscallパッケージを使って手動でシステムコールを構築する必要がありました。これは開発者にとって負担であり、移植性にも課題がありました。

このコミットは、syscallパッケージにFlock_t構造体とそれに関連するLockメソッドを追加することで、Goから直接、よりGoらしい方法でファイルロックを操作できるようにし、開発者の利便性とコードの可読性・保守性を向上させることを目的としています。特に、fcntlシステムコールを用いたファイルロックは、flock(2)よりも柔軟なロック(バイト範囲ロックなど)を提供できるため、より高度な制御が必要な場合に有用です。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

1. システムコール (Syscall)

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

2. ファイルロック (File Locking)

ファイルロックは、複数のプロセスが共有ファイルに同時にアクセスする際に、データの破損や競合状態を防ぐためのメカニズムです。ファイルロックには主に2つの種類があります。

  • 共有ロック (Shared Lock / Read Lock): 複数のプロセスが同時にファイルを読み取ることができますが、書き込みはできません。
  • 排他ロック (Exclusive Lock / Write Lock): 一度に1つのプロセスのみがファイルを読み書きできます。他のプロセスは、ロックが解除されるまで待機するか、エラーを受け取ります。

ファイルロックは、ファイル全体に適用されることもあれば、ファイルの一部(バイト範囲)に適用されることもあります。

3. fcntl(2) システムコール

fcntl (file control) はUnix系OSで利用されるシステムコールで、オープンされたファイルディスクリプタのプロパティを操作するために使用されます。ファイルロック機能もfcntlを通じて提供されます。fcntlを用いたファイルロックは、flock(2)システムコールによるロックとは異なり、プロセス間で継承されず、より粒度の細かいバイト範囲ロックが可能です。

fcntlシステムコールでファイルロックを行う際には、以下の引数を使用します。

  • fd: ロックを適用するファイルのファイルディスクリプタ。
  • cmd: 実行する操作(例: F_GETLKでロック状態の取得、F_SETLKで非ブロックロック設定、F_SETLKWでブロックロック設定)。
  • struct flock*: ロックの種類、開始オフセット、長さ、プロセスIDなどの情報を含むflock構造体へのポインタ。

4. flock 構造体 (Flock_t in Go)

fcntlシステムコールでファイルロックを操作する際に使用されるデータ構造です。GoのsyscallパッケージではFlock_tとして定義されています。この構造体には、ロックの種類(共有ロックか排他ロックか)、ロックの開始オフセット、ロックの長さ、ロックを保持しているプロセスのIDなどが含まれます。

一般的なflock構造体のフィールドは以下の通りです(システムによって多少異なる場合があります)。

  • l_type: ロックの種類 (F_RDLCK (共有ロック), F_WRLCK (排他ロック), F_UNLCK (ロック解除))
  • l_whence: l_startの基準位置 (SEEK_SET (ファイルの先頭), SEEK_CUR (現在の位置), SEEK_END (ファイルの末尾))
  • l_start: ロックの開始オフセット
  • l_len: ロックの長さ (0はファイルの最後まで)
  • l_pid: ロックを保持しているプロセスのID (主にF_GETLKで使用)

5. 32-bit と 64-bit システムにおけるシステムコールの違い

Unix系OSでは、システムコールの番号や引数の型、構造体のレイアウトが、CPUアーキテクチャ(32-bitか64-bitか)によって異なる場合があります。特に、fcntlのような汎用的なシステムコールは、32-bitシステムではファイルオフセットや長さが32-bit整数で表現されるため、大きなファイル(2GB以上)を扱う際に問題が生じることがありました。

この問題を解決するため、32-bit Linuxでは、大きなファイルオフセットを扱えるようにfcntl64というシステムコールが導入されました。これは、fcntlと同じ機能を提供しますが、引数として64-bitのオフセットと長さを取ります。Goのsyscallパッケージは、このようなアーキテクチャ間の差異を吸収し、開発者が意識することなく正しいシステムコールが呼び出されるように設計されています。このコミットでは、まさにこの32-bit Linuxにおけるfcntlの差異を吸収するためのロジックが追加されています。

技術的詳細

このコミットの技術的な核心は、Goのsyscallパッケージが、異なるUnix系OSやアーキテクチャ(特に32-bit Linux)間でfcntlシステムコールの挙動の違いをどのように吸収し、一貫したファイルロックAPIを提供するかにあります。

Flock_t構造体の修正

src/pkg/syscall/consistency_unix_test.goの変更は、Flock_t構造体のStartLenフィールドの型をint64に明示的に変更しています。これは、異なるアーキテクチャ(32-bitと64-bit)間でこれらのフィールドのサイズとアライメントを統一し、fcntlシステムコールに渡す際の互換性を確保するためです。以前はリテラル0が使われていましたが、これはコンパイラによってデフォルトのint型(32-bitシステムでは32-bit、64-bitシステムでは64-bit)に推論される可能性があり、クロスコンパイルや異なる環境での実行時に問題を引き起こす可能性がありました。int64(0)と明示することで、常に64-bit整数として扱われることが保証されます。

flock.goの新規追加とFlock_t.Lockメソッド

src/pkg/syscall/flock.goは、Flock_t構造体にLockメソッドを追加し、ファイルロック操作の主要なロジックをカプセル化しています。

// +build linux darwin freebsd openbsd netbsd dragonfly

package syscall

import "unsafe"

// fcntl64Syscall is usually SYS_FCNTL, but is overridden on 32-bit Linux
// systems by flock_linux_32bit.go to be SYS_FCNTL64.
var fcntl64Syscall uintptr = SYS_FCNTL

// Lock performs a fcntl syscall for F_GETLK, F_SETLK or F_SETLKW commands.
func (lk *Flock_t) Lock(fd uintptr, cmd int) error {
	_, _, errno := Syscall(fcntl64Syscall, fd, uintptr(cmd), uintptr(unsafe.Pointer(lk)))
	if errno == 0 {
		return nil
	}
	return errno
}
  • ビルドタグ: // +build linux darwin freebsd openbsd netbsd dragonfly は、このファイルが指定されたOSでのみコンパイルされることを示します。これは、fcntlベースのファイルロックがこれらのUnix系OSで共通して利用可能であるためです。
  • fcntl64Syscall変数: このグローバル変数は、fcntlシステムコールの番号を保持します。デフォルトではSYS_FCNTLに初期化されます。しかし、後述するflock_linux_32bit.goによって、32-bit LinuxシステムではSYS_FCNTL64に上書きされます。これにより、Goのコードは常に正しいfcntlシステムコールを呼び出すことができます。
  • Lockメソッド:
    • fd uintptr: ロックを適用するファイルのファイルディスクリプタ。
    • cmd int: F_GETLK, F_SETLK, F_SETLKWなどのfcntlコマンド。
    • Syscall関数: Goのsyscallパッケージが提供する低レベルなシステムコール呼び出し関数です。
      • 第一引数にはシステムコール番号(fcntl64Syscall)を渡します。
      • 第二引数にはファイルディスクリプタfdを渡します。
      • 第三引数にはコマンドcmdを渡します。
      • 第四引数にはFlock_t構造体lkへのポインタをuintptrにキャストして渡します。unsafe.Pointerを使用しているのは、Goの型安全性を一時的にバイパスして、構造体のアドレスをシステムコールに渡すためです。
    • 戻り値: システムコールの結果として返されるエラーコード(errno)をチェックし、エラーがあればそれを返します。errno == 0は成功を意味します。

flock_linux_32bit.goの新規追加とinit関数

src/pkg/syscall/flock_linux_32bit.goは、32-bit Linuxシステムに特化したロジックを提供します。

// +build linux,386 linux,arm

package syscall

func init() {
	// On 32-bit Linux systems, the fcntl syscall that matches Go's
	// Flock_t type is SYS_FCNTL64, not SYS_FCNTL.
	fcntl64Syscall = SYS_FCNTL64
}
  • ビルドタグ: // +build linux,386 linux,arm は、このファイルがLinux上で32-bitアーキテクチャ(Intel 386互換およびARM)の場合にのみコンパイルされることを示します。
  • init関数: Goのinit関数は、パッケージが初期化される際に自動的に実行されます。このinit関数では、グローバル変数fcntl64SyscallSYS_FCNTL64に上書きしています。これにより、32-bit Linuxシステムでは、Flock_t.Lockメソッドが内部的にSYS_FCNTL64システムコールを呼び出すようになります。これは、32-bitシステムで64-bitオフセットを扱うためのfcntlのバリアントであり、GoのFlock_t構造体(StartLenint64)と整合性が取れます。

この設計により、Goのsyscallパッケージは、異なるOSやアーキテクチャの差異を内部で吸収し、開発者には一貫したFlock_t.Lockインターフェースを提供するという、移植性と利便性を両立させています。

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

このコミットにおけるコアとなるコードの変更箇所は以下の3つのファイルにまたがっています。

  1. src/pkg/syscall/consistency_unix_test.go

    • Flock_t構造体の初期化部分で、StartLenフィールドにint64(0)が明示的に指定されました。
      --- a/src/pkg/syscall/consistency_unix_test.go
      +++ b/src/pkg/syscall/consistency_unix_test.go
      @@ -37,8 +37,8 @@ func _() {
       	_ = syscall.Flock_t{
       	\tType:   int16(0),
       	\tWhence: int16(0),
      -\t\tStart:  0,
      -\t\tLen:    0,
      +\t\tStart:  int64(0),
      +\t\tLen:    int64(0),
       	\tPid:    int32(0),
       	}
       }
      
  2. src/pkg/syscall/flock.go (新規ファイル)

    • Flock_t構造体にLockメソッドが追加されました。
    • fcntl64Syscallというuintptr型のグローバル変数が定義され、デフォルトでSYS_FCNTLに初期化されます。
      // +build linux darwin freebsd openbsd netbsd dragonfly
      
      // Copyright 2014 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
      
      import "unsafe"
      
      var fcntl64Syscall uintptr = SYS_FCNTL
      
      func (lk *Flock_t) Lock(fd uintptr, cmd int) error {
      	_, _, errno := Syscall(fcntl64Syscall, fd, uintptr(cmd), uintptr(unsafe.Pointer(lk)))
      	if errno == 0 {
      		return nil
      	}
      	return errno
      }
      
  3. src/pkg/syscall/flock_linux_32bit.go (新規ファイル)

    • 32-bit Linuxシステム(linux,386およびlinux,arm)でのみコンパイルされるinit関数が追加されました。
    • このinit関数内で、fcntl64Syscall変数がSYS_FCNTL64に上書きされます。
      // +build linux,386 linux,arm
      
      // Copyright 2014 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
      
      func init() {
      	// On 32-bit Linux systems, the fcntl syscall that matches Go's
      	// Flock_t type is SYS_FCNTL64, not SYS_FCNTL.
      	fcntl64Syscall = SYS_FCNTL64
      }
      

コアとなるコードの解説

src/pkg/syscall/consistency_unix_test.go の変更

このテストファイルの変更は、Flock_t構造体のStartLenフィールドが、32-bitと64-bitの両方のシステムで一貫してint64として扱われることを保証するためのものです。以前はリテラル0が使われていましたが、これはGoの型推論によってプラットフォーム依存のint型(32-bitシステムでは32-bit、64-bitシステムでは64-bit)に解決される可能性がありました。int64(0)と明示することで、これらのフィールドが常に64-bit整数として扱われるようになり、fcntlシステムコールに渡されるFlock_t構造体のメモリレイアウトが、異なるアーキテクチャ間で安定します。これは、特に32-bitシステムでfcntl64システムコールを使用する際に、正しい引数構造を渡すために重要です。

src/pkg/syscall/flock.go の新規追加

このファイルは、Goプログラムからfcntlシステムコールを用いたファイルロックを操作するための主要なAPIを提供します。

  • fcntl64Syscall 変数:

    • この変数は、実際に呼び出すfcntlシステムコールの番号を動的に決定するためのキーポイントです。
    • デフォルトではSYS_FCNTLに設定されています。これは、ほとんどの64-bit Unix系システムで標準的なfcntlシステムコールを指します。
    • しかし、32-bit Linuxシステムでは、fcntlが64-bitオフセットを扱えないため、fcntl64という別のシステムコール(SYS_FCNTL64)を使用する必要があります。この変数を介することで、Goは実行環境に応じて適切なシステムコールを透過的に選択できます。
  • Flock_t.Lock メソッド:

    • このメソッドは、Flock_t構造体のインスタンスに対して呼び出され、指定されたファイルディスクリプタfdとコマンドcmd(例: F_SETLK, F_SETLKW)を使ってファイルロック操作を実行します。
    • Syscall(fcntl64Syscall, fd, uintptr(cmd), uintptr(unsafe.Pointer(lk))) の行が核心です。
      • fcntl64Syscall: 前述の通り、環境に応じてSYS_FCNTLまたはSYS_FCNTL64のいずれかのシステムコール番号が使用されます。
      • fd: ロック対象のファイルディスクリプタ。
      • uintptr(cmd): 実行するfcntlコマンド。
      • uintptr(unsafe.Pointer(lk)): Flock_t構造体lkのアドレスをシステムコールに渡すために、unsafe.Pointerを使って型安全性を一時的に解除し、uintptrに変換しています。これにより、カーネルはメモリ上のFlock_t構造体の内容を読み取り、ロック操作を実行できます。
    • システムコールが成功した場合(errno == 0)、nilエラーを返します。失敗した場合は、対応するerrnoを返します。

src/pkg/syscall/flock_linux_32bit.go の新規追加

このファイルは、32-bit Linuxシステムに特化した初期化ロジックを提供します。

  • ビルドタグ // +build linux,386 linux,arm:

    • このタグにより、このファイルはLinux上で32-bitアーキテクチャ(386またはarm)の場合にのみコンパイルされます。これにより、64-bit Linuxや他のOSではこのコードは含まれず、無関係な変更を避けることができます。
  • init 関数:

    • Goのinit関数は、パッケージがロードされる際に自動的に実行されます。
    • このinit関数内で、fcntl64Syscallグローバル変数がSYS_FCNTL64に上書きされます。
    • この上書きは、32-bit Linuxシステムにおいて、fcntlシステムコールが64-bitオフセットを扱うためのfcntl64システムコール(SYS_FCNTL64)として提供されているというOSの特性に対応するためのものです。GoのFlock_t構造体のStartLenフィールドがint64であるため、このSYS_FCNTL64を呼び出すことで、Goの構造体とOSのシステムコールが正しく連携し、大きなファイルに対するロックも適切に機能するようになります。

これらの変更により、Goのsyscallパッケージは、異なるUnix系OSやアーキテクチャの差異を内部で吸収し、開発者には一貫したFlock_t.Lockインターフェースを提供することで、ファイルロック機能の移植性と利便性を大幅に向上させています。

関連リンク

参考にした情報源リンク