詳解Linuxカーネルの12章VFSに関連するコードを読んだ。
カーネルバージョン
v4.1.13
flockとは
manに書いてある。
- ファイル(inode)に対してロックをかける、引数はファイルディスクリプタ。
- 排他、共有のロック設定、解除ができる。
- 勧告ロックである。追記:強制ロックも出来るかも?
プロセスはどうやって判別するのか
file_lock構造体のfl_file
にfile構造体へのポインタが入っているので、それが同一か否かで判定する。
file構造体はopenごとに生成されるので、forkした場合は子プロセスで同じロックを参照することができる。
struct file_lock {
struct file_lock *fl_next; /* singly linked list for this inode */
struct list_head fl_list; /* link into file_lock_context */
struct hlist_node fl_link; /* node in global lists */
struct list_head fl_block; /* circular list of blocked processes */
fl_owner_t fl_owner;
unsigned int fl_flags;
unsigned char fl_type;
unsigned int fl_pid;
int fl_link_cpu; /* what cpu's list is this on? */
struct pid *fl_nspid;
wait_queue_head_t fl_wait;
struct file *fl_file;
loff_t fl_start;
loff_t fl_end;
struct fasync_struct * fl_fasync; /* for lease break notifications */
/* for lease breaks: */
unsigned long fl_break_time;
unsigned long fl_downgrade_time;
const struct file_lock_operations *fl_ops; /* Callbacks for filesystems */
const struct lock_manager_operations *fl_lmops; /* Callbacks for lockmanagers */
union {
struct nfs_lock_info nfs_fl;
struct nfs4_lock_info nfs4_fl;
struct {
struct list_head link; /* link in AFS vnode's pending_locks list */
int state; /* state of grant or error if -ve */
} afs;
} fl_u;
};
仕組み
同じfl_file(つまり、同一プロセスの同一fdのファイル)ですでにロックが存在する場合、いままでのロックを消す(正確には、disposeに繋いでout:
セクションでまとめて削除している)、ということをしている。
完全に同じロックがかかっていた場合、何もせずにそのまま関数を抜ける。
list_for_each_entry(fl, &ctx->flc_flock, fl_list) {
if (request->fl_file != fl->fl_file)
continue;
if (request->fl_type == fl->fl_type)
goto out;
found = true;
locks_delete_lock_ctx(fl, &dispose);
break;
}
ロックの操作がアンロックだった場合はそのまま関数を抜ける。前のロックは上のブロックで消されている。
if (request->fl_type == F_UNLCK) {
if ((request->fl_flags & FL_EXISTS) && !found)
error = -ENOENT;
goto out;
}
次に、ロックの衝突を探す。flock_locks_conflict()
は、自分以外からのロックのうち排他ロック(F_WRLOCK
)がある場合、もしくは自分がF_WRLOCK
で他のロックがあった場合は衝突になる。
MAND_LOCK
もチェックしているが・・flockで強制ロックも出来るのかな?ここはまだよくわからない。
find_conflict:
list_for_each_entry(fl, &ctx->flc_flock, fl_list) {
if (!flock_locks_conflict(request, fl))
continue;
error = -EAGAIN;
if (!(request->fl_flags & FL_SLEEP))
goto out;
error = FILE_LOCK_DEFERRED;
locks_insert_block(fl, request);
goto out;
}
衝突がなければ、ロックを取得する。
関数内ではflc_lock
のスピンロックをとっているため処理の競合はない。noblockの指定をしない場合の待ち処理はさらに上位の関数で書かれているようだ。
locks_copy_lock(new_fl, request);
locks_insert_lock_ctx(new_fl, &ctx->flc_flock);
new_fl = NULL;
error = 0;