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;
}




