FLOCKロック
Rev.1を表示中。最新版はこちら。
FLOCKロックの設定は、flockシステムコールで行われる。ファイルが、FMODE_READ|FMODE_WRITEかどうか等のオプションチェックをする。OKなら、引数に応じてflock_make_lock()でstruct file_lock(ロックオブジェクト)を初期化する。ファイルロックは、このロックオブジェクトを管理することである。security_file_lock()は、selinuxのファイルロックのセキュリティのチェックで、flockの処理と関係ない。ファイルオペレーションのflockコールバック関数が設定されていれば、それがコールされる。これはカーネルがサポートするファイルロック処理でなく、ハードに依存したインプリメントの方がいい場合に、ドライバとしてサポートするケースかと?(たぶん)、通常は、カーネルがサポートしているflock_lock_file_wait()がコールされ、ここでロックの処理が行われる。
SYSCALL_DEFINE2(flock, unsigned int, fd, unsigned int, cmd) { struct file *filp; struct file_lock *lock; int can_sleep, unlock; int error; error = -EBADF; filp = fget(fd); if (!filp) goto out; can_sleep = !(cmd & LOCK_NB); cmd &= ~LOCK_NB; unlock = (cmd == LOCK_UN); if (!unlock && !(cmd & LOCK_MAND) && !(filp->f_mode & (FMODE_READ|FMODE_WRITE))) goto out_putf; error = flock_make_lock(filp, &lock, cmd); if (error) goto out_putf; if (can_sleep) lock->fl_flags |= FL_SLEEP; error = security_file_lock(filp, lock->fl_type); if (error) goto out_free; if (filp->f_op && filp->f_op->flock) error = filp->f_op->flock(filp, (can_sleep) ? F_SETLKW : F_SETLK, lock); else error = flock_lock_file_wait(filp, lock); out_free: locks_free_lock(lock); out_putf: fput(filp); out: return error; }flock_make_lock()でロックオブジェクトを初期化する。ロックオブジェクトはPOSIXロックでも同じテンプレートが使われる。ここでfl->fl_end = OFFSET_MAXとし、ファイル全体のロックを表している。なお、fl->fl_file = filpは、flock処理で該当するロックオブジェクトを取得するとき、その呼出し元のファイルオブジェクトと、fl->fl_fileが同じかどうかでチェックする。従って別プロセス(ファイルオブジェクトを共有するスレッドでない。)から、ロック済みファイルにflockする事は、新規のflockの取り扱いになる。なお、同じファイルオブジェクトを有するプロセスのロック済みファイルにflockは、ロックの変更を意味する。
static int flock_make_lock(struct file *filp, struct file_lock **lock, unsigned int cmd) { struct file_lock *fl; int type = flock_translate_cmd(cmd); if (type < 0) return type; fl = locks_alloc_lock(); if (fl == NULL) return -ENOMEM; fl->fl_file = filp; fl->fl_pid = current->tgid; fl->fl_flags = FL_FLOCK; fl->fl_type = type; fl->fl_end = OFFSET_MAX; *lock = fl; return 0; }flock_lock_file_wait()がflock処理の本体となる。flock_lock_file()でロックを設定する。設定できればそのまま復帰する。できなければwait_event_interruptible()でウエイトキューに登録する。起床されるとウエイトキューに登録したロックオブジェクトをリストから取り除き、再度flock_lock_file()でロックを設定しにいく。
int flock_lock_file_wait(struct file *filp, struct file_lock *fl) { int error; might_sleep(); for (;;) { error = flock_lock_file(filp, fl); if (error != FILE_LOCK_DEFERRED) break; error = wait_event_interruptible(fl->fl_wait, !fl->fl_next); if (!error) continue; locks_delete_block(fl); break; } return error; }flock_lock_file()でstruct file_lockをinode->i_flockにリストする事で、ロックの実際の処理を行う。なおこのリストはrelese/flock/posixロックの順次リストするようになっている。
まず、FL_ACCESS/F_UNLCKでないなら、スラブからロックオブジェクトを取得する。なおFL_ACCESSは実際のロックは行わないで、そのreturn値としてerror(結果)を返すものである。
lock_flocks()でまずスピンロックを取得する。FL_ACCESSならfind_conflictにジャンプする。直下のfor_each_lockマクロは、引数のファイルオブジェクトのflockがあるかどうかチェックする。あるならロックの変更ということで、そのロックを削除して新規にロックを設定することになり、find_conflict:以降のfor_each_lockマクロが、実際のロック設定となる。
inode->i_flockに、lease/flock/posixの順にリストされているfile_lockをfor_each_lockマクロで順に検索する。posixロックなら以降flockはない。leaseならさらに次へ検索する。この2つの条件をすり抜けてきたロックは、flockである。ここでロックとflockを設定しようとするプロセスのファイルオブジェクトが同じかチェックする。同じならflockの書き換えとなる。なお、 if (request->fl_type == fl->fl_type)は更新するロックが、設定済みロックが同じなら、何も処理する必要はない。goto outで復帰する。locks_delete_lock()で設定済みロックを開放し、もしそのロックで待っているプロセスがいるとそれを起床させ、cond_resched()をコールすることで、起床されたプロセスは先に動作させる。
find_conflict:のfor_each_lockマクロも、flockオブジェクトの検索は、先のfor_each_lockマクロと考え方は同じで、flockのロックオブジェクトなら、flock_locks_conflict()で、ロックオブジェクトの競合がないかチェックする。
競合があればlocks_insert_block()で、競合先のロックオブジェクトのウエイトリストに登録する。競合がなければ、locks_insert_lock()で、inode->i_floc下に登録する。
static int flock_lock_file(struct file *filp, struct file_lock *request) { struct file_lock *new_fl = NULL; struct file_lock **before; struct inode * inode = filp->f_path.dentry->d_inode; int error = 0; int found = 0; if (!(request->fl_flags & FL_ACCESS) && (request->fl_type != F_UNLCK)) { new_fl = locks_alloc_lock(); if (!new_fl) return -ENOMEM; } lock_flocks(); if (request->fl_flags & FL_ACCESS) goto find_conflict; for_each_lock(inode, before) { struct file_lock *fl = *before; if (IS_POSIX(fl)) break; if (IS_LEASE(fl)) continue; if (filp != fl->fl_file) continue; if (request->fl_type == fl->fl_type) goto out; found = 1; locks_delete_lock(before); break; } if (request->fl_type == F_UNLCK) { if ((request->fl_flags & FL_EXISTS) && !found) error = -ENOENT; goto out; } if (found) { unlock_flocks(); cond_resched(); lock_flocks(); } find_conflict: for_each_lock(inode, before) { struct file_lock *fl = *before; if (IS_POSIX(fl)) break; if (IS_LEASE(fl)) continue; 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; } if (request->fl_flags & FL_ACCESS) goto out; locks_copy_lock(new_fl, request); locks_insert_lock(before, new_fl); new_fl = NULL; error = 0; out: unlock_flocks(); if (new_fl) locks_free_lock(new_fl); return error; }flock_locks_conflict()で2つのロックオブジェクトの競合をチェックする。要求ロックがFLOCKでなければ(FLOCKとPOSIXロックは別ロック)、2つのロックが同じファイルオブジェクトなら(この場合上記の処理でロックを解除しているのでは?)またどちらか1つが強制ロックなら、競合しない。(強制ロックは、競合しようか関係なしに、ロックを設定すると言うことかな?)
そのどちらでもないなら、locks_conflict()でチェックする。これはどちらか1つが、排他ロック(書き込み用ロック)をしていたら競合することになる。
static int flock_locks_conflict(struct file_lock *caller_fl, struct file_lock *sys_fl) { if (!IS_FLOCK(sys_fl) || (caller_fl->fl_file == sys_fl->fl_file)) return (0); if ((caller_fl->fl_type & LOCK_MAND) || (sys_fl->fl_type & LOCK_MAND)) return 0; return (locks_conflict(caller_fl, sys_fl)); } static int locks_conflict(struct file_lock *caller_fl, struct file_lock *sys_fl) { if (sys_fl->fl_type == F_WRLCK) return 1; if (caller_fl->fl_type == F_WRLCK) return 1; return 0; }