fcntlでのファイルのロック

Linuxでのプログラムではオープンしているファイルに書き込みや読み込みを行う際にはロックを介さずに行われてします.
ファイルに対して排他アクセスを制御するために,fcntlまたはflockを用いて行われることが多い.

今回はfcntlを用いてファイルに対してロックを行うことを考えていく.
まずmanコマンドでのfcntlはこんな感じ.

#include 
#include
int fcntl(int fd, int cmd, ... /* arg */ );
fcntlはファイルに対してさまざまな操作を行うシステムコールであるため,引数cmdによって実行する操作を指定する.
今回のロックを行う場合には以下のパラメータを指定する.

  • F_GETLK
  • F_SETLK
  • F_SETLKW

これらのパラメータを指定する場合にはfcntlの3番目以降の引数も定まり,プロトタイプは以下の用になる.

int fcntl(int fd, int cmd, struct flock *flock_structure);

ここでflock構造体!?となる.
flock構造体は主に以下のメンバから構成されている.

struct flock{
short l_type;
short l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
};
このflock構造体の各メンバの値によってロックの状態やリージョンを指示していく.
まずメンバl__typeによって以下からロックの種類を指定する.

  • F_RDLCK : 共有ロック
  • F_WLCLK : 排他ロック
  • F_UNLCK : アンロック

l_whence,l_start,l_lenによってロックを行うファイルのリージョンを指定する.
l_whenceはfseekなどにも使われる,SEEK_SET,SEK_CUR,SEEK_ENDを指定.
それぞれの意味はfseek関数を参照.
l_startはl_whenceからのリージョンの相対オフセットを指定.
l_lenはリージョンの長さを指定する(バイト数).
l_pidはロックを保持しているプロセスを調べるために用いられる.

  • F_GETLK

F_GETLKパラメータをfcntlに対して理容すると,flock構造体を用いて指定してたロックの種類とリージョンに対しての情報を得ることが出来る.
具体的には,ロックの種類とリージョンに対して競合するロックが既にされている場合には,flock構造体のメンバl_pidにロックを行っているプロセスのプロセスIDが格納されて返ってくる.

  • F_SETLK

F_SETLKはfcntlの第1引数でしていいたファイルディスクリプタのたいるに対してロックまたはアンロックを試みる.
l_typeでのロック(アンロック)をflock構造体のリージョンに対して試みて,ロックが成功した場合にはfcntlは-1以外の値を返し,失敗下端会いには-1を返す.
F_SETLKはロックに失敗した場合は直ちに関数から戻る.
またl_pidは用いらない.

  • F_SETLKW

F_SETLKWはF_SETLK同じだが,ロックの取得に失敗した場合はロックが取得できるまで,またはシグナルが発生するまで待機を行う.

またロックは対象のディスクリプタをクローズした時に自動的に解除される.

以下はロックのサンプル.
時際にロックがかかっているかは複数のプロセスを実行する必要がある.

#include
#include
#include
#include

const char *file = "test_lock";

int main()
{
int file_desc;
int byte_count;
struct flock region_1;
struct flock region_2;
int res;

file_desc = open(file, O_RDWR | O_CREAT, 0666);
if(!file_desc){
fprintf(stderr, "Unable to open %s for read/write\n", file);
exit(EXIT_FAILURE);
}
region_1.l_type = F_RDLCK;
region_1.l_whence = SEEK_SET;
region_1.l_start = 0;
region_1.l_len = 20;

region_2.l_type = F_WRLCK;
region_2.l_whence = SEEK_SET;
region_2.l_start = 30;
region_2.l_len = 20;

printf("Process %d locking file\n", getpid());
res = fcntl(file_desc, F_SETLK, ®ion_1);
if(res == -1)fprintf(stderr, "Failed to lock region 1\n");
res = fcntl(file_desc, F_SETLK, ®ion_2);
if(res == -1)fprintf(stderr, "Failed to lock region 2\n");

printf("Process %d closing file\n", getpid());
close(file_desc);
exit(EXIT_SUCCESS);
}

最後にfcntlの注意点としてWikipediaに以下のことが記述されている.

プロセスはひとつのファイルに対応するファイル記述子を複数個持つことができる。
このうちのいずれかを使ってfcntlでロックをかけていたとする。
このとき、同じファイルに対応する別のファイル記述子をクローズすると、そのファイル上にそのプロセスが設定した全ロックが解除されてしまう。
また、fcntlのロックは子プロセスに受け継がれない。
このfcntlのクローズに関連した動作は、ライブラリのサブルーチン内でファイルにアクセスすることが多いアプリケーションなどで問題を発生することが多い。