LEASEロック
LEASEロックは、設定された共有/排他ロックを貸し出します。貸し出し対象のファイルに、別プロセスが参照し競合が発生した場合、貸し出しているプロセスにシグナルが送られ、/proc/sys/fs/lease-break-time:45秒後にそのロックが解除されます。以下はその検証を行うサンプルです。このシグナルでリースしているプロセスは、ロックが解除されてもいい旨の処理をする事になります。
lease_alloc()でキャッシュからfile_lockオブジェクトを取得し、lease_init()をコールして初期化する。初期化からロックは全ファイルに渡って行われる。 fl->fl_lmops = &lease_manager_opsのコールバック関数で、LEASEプロセスにシグナルを送る。
fasync_alloc()のfasync_structオブジェクトは、本来キャラクタデバイスのFASYNCシグナル処理(入力毎にシグナルを送る。)のためであるが、LEASEロックのシグナル送信も、このfasync_structオブジェクトで行っている。
inode下に__vfs_setlease()でロックオブジェクトを、file_lockオブジェクト下にfasync_insert_entry()で、fasync_structオブジェクトを登録する。
次にLEASEロック間での競合が無いか、inodeのロックリストを走査することでチェックする。if (fl->fl_file == filp)は、同じFILEオブジェクトを有するプロセスが、LEASEロックを有していて、そしてそのファイルのLEASEロックを取得しようとする場合で、この場合LEASEロックの変更で、file_lockオブジェクトのコールバック関数lm_change()が、そしてlocks_insert_lock()で、file_lockをリストする。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> // コンパイルで宣言されていないと怒られた # define F_SETLEASE 1024 void sigio(int sig) { printf("someone access\n"); } int main(int argc, char *argv[]) { int fd; struct sigaction sa = { .sa_handler = sigio }; sigaction(SIGIO, &sa, NULL); fd = open("/tmp/test", O_RDWR); fcntl(fd, F_SETLEASE, F_WRLCK); while (1) { sleep(1); } return 0; }
実行結果
[kitamura@localhost test]$ echo "LEASE lock" > /tmp/test [kitamura@localhost test]$ ./lease & [1] 4751 [kitamura@localhost test]$ date ; cat /tmp/test ; date 2012年 10月 24日 水曜日 11:22:35 JST someone access LEASE lock 2012年 10月 24日 水曜日 11:23:20 JST [kitamura@localhost test]$ [kitamura@localhost test]$ date ; cat /tmp/test ; date 2012年 10月 24日 水曜日 11:23:33 JST LEASE lock 2012年 10月 24日 水曜日 11:23:34 JST [kitamura@localhost test]$cat /tmp/testは45秒後に/tmp/testに参照している。この時、リースしているプロセスにSIGIOが送られ、sigio()が動作している。なお次の/tmp/testの参照ではウエイトしていない。リースは解除されている事になる。
実装
LEASEロックは、F_SETLEASEでのfcntlシステムコールで設定する。この時do_fcntl_add_lease()がコールされここでLEASEロックの設定が行われる。lease_alloc()でキャッシュからfile_lockオブジェクトを取得し、lease_init()をコールして初期化する。初期化からロックは全ファイルに渡って行われる。 fl->fl_lmops = &lease_manager_opsのコールバック関数で、LEASEプロセスにシグナルを送る。
fasync_alloc()のfasync_structオブジェクトは、本来キャラクタデバイスのFASYNCシグナル処理(入力毎にシグナルを送る。)のためであるが、LEASEロックのシグナル送信も、このfasync_structオブジェクトで行っている。
inode下に__vfs_setlease()でロックオブジェクトを、file_lockオブジェクト下にfasync_insert_entry()で、fasync_structオブジェクトを登録する。
static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg) { struct file_lock *fl, *ret; struct fasync_struct *new; int error; fl = lease_alloc(filp, arg); if (IS_ERR(fl)) return PTR_ERR(fl); new = fasync_alloc(); if (!new) { locks_free_lock(fl); return -ENOMEM; } ret = fl; lock_flocks(); error = __vfs_setlease(filp, arg, &ret); if (error) { unlock_flocks(); locks_free_lock(fl); goto out_free_fasync; } if (ret != fl) locks_free_lock(fl); if (!fasync_insert_entry(fd, filp, &ret->fl_fasync, new)) new = NULL; error = __f_setown(filp, task_pid(current), PIDTYPE_PID, 0); unlock_flocks(); out_free_fasync: if (new) fasync_free(new); return error; } static int lease_init(struct file *filp, int type, struct file_lock *fl) { if (assign_type(fl, type) != 0) return -EINVAL; fl->fl_owner = current->files; fl->fl_pid = current->tgid; fl->fl_file = filp; fl->fl_flags = FL_LEASE; fl->fl_start = 0; fl->fl_end = OFFSET_MAX; fl->fl_ops = NULL; fl->fl_lmops = &lease_manager_ops; return 0; }__vfs_setlease()からgeneric_add_lease()がコールされる。最初に競合のチェックが行われる。共有ロックの場合、ファイルが書き込みで使用されていないか、また排他ロックの場合、ファイルがどこからも参照されていないかチェックする。
次にLEASEロック間での競合が無いか、inodeのロックリストを走査することでチェックする。if (fl->fl_file == filp)は、同じFILEオブジェクトを有するプロセスが、LEASEロックを有していて、そしてそのファイルのLEASEロックを取得しようとする場合で、この場合LEASEロックの変更で、file_lockオブジェクトのコールバック関数lm_change()が、そしてlocks_insert_lock()で、file_lockをリストする。
int generic_add_lease(struct file *filp, long arg, struct file_lock **flp) { struct file_lock *fl, **before, **my_before = NULL, *lease; struct dentry *dentry = filp->f_path.dentry; struct inode *inode = dentry->d_inode; int error; lease = *flp; error = -EAGAIN; if ((arg == F_RDLCK) && (atomic_read(&inode->i_writecount) > 0)) goto out; if ((arg == F_WRLCK) && ((dentry->d_count > 1) || (atomic_read(&inode->i_count) > 1))) goto out; error = -EAGAIN; for (before = &inode->i_flock; ((fl = *before) != NULL) && IS_LEASE(fl); before = &fl->fl_next) { if (fl->fl_file == filp) { my_before = before; continue; } if (arg == F_WRLCK) goto out; if (fl->fl_flags & FL_UNLOCK_PENDING) goto out; } if (my_before != NULL) { error = lease->fl_lmops->lm_change(my_before, arg); if (!error) *flp = *my_before; goto out; } error = -EINVAL; if (!leases_enable) goto out; locks_insert_lock(before, lease); return 0; out: return error; }