linux 文件锁的实现及其使用
1, 文件锁要点
1.1 文件锁是在一个打开文件的inode->i_flock字段组织成的链表中。
1.2 同一个进程(或有亲属关系并且继承了打开的文件对象的进程)可以把SH锁换成EX锁,也可以把EX锁换成SH锁,linux会把该进程以前的文件锁删除,并替换成新的锁(把新的锁添加到该文件对象对应的i_node的i_flock为头的链表中)。
1.3 不同进程间添加文件锁,要遵循一定的规则:同一个文件可以添加多个SH锁,但只能有一个EX锁存在。
1.4 文件关闭时,该文件对应的文件锁释放,但是其他进程的文件锁还照样存在。
2, 数据结构
与文件锁相关数据结构的之间的关系:
task_struct
|
(struct files_struct *)files
|
(struct dentry *)f_dentry
|
(struct inode *)d_inode
|
(struct file_lock *)i_flock
每个文件可能有多个文件锁,这些文件锁由一个双向链表组织起来,
链表的第一个结点就是inode结构中的i_flock。file_lock结构中的
fl_next执行该双向链表的下一个结点。
//进程描述符结构
struct task_struct {
...
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
...
};
/*
* Open file table structure
*/
// 进程打开一个文件时需要填写的结构
struct files_struct {
atomic_t count;
spinlock_t file_lock; /* Protects all the below members. Nests inside tsk->alloc_lock */
int max_fds;
int max_fdset;
int next_fd;
struct file ** fd; /* current fd array */
fd_set *close_on_exec;
fd_set *open_fds;
fd_set close_on_exec_init;
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];
};
//文件表结构
struct file {
struct list_head f_list;
struct dentry *f_dentry;
struct vfsmount *f_vfsmnt;
struct file_operations *f_op;
...
}
//目录描述符结构
struct dentry {
atomic_t d_count;
unsigned int d_flags; /* protected by d_lock */
spinlock_t d_lock; /* per dentry lock */
struct inode *d_inode; /* Where the name belongs to - NULL is * negative */
...
};
//文件inode结构
struct inode {
struct hlist_node i_hash;
struct list_head i_list;
struct list_head i_sb_list;
struct list_head i_dentry;
loff_t i_size;
...
struct file_lock *i_flock;
...
};
//文件锁结构
struct file_lock {
struct file_lock *fl_next; //文件锁链表的下一个结点
struct list_head fl_link; //活动或阻塞链表的指针
struct list_head fl_block; //被文件锁阻塞的等待者
fl_owner_t fl_owner;
unsigned int fl_pid;
wait_queue_head_t fl_wait; //阻塞进程的等待队列
struct file *fl_file;
unsigned char fl_flags;
unsigned char fl_type;
loff_t fl_start;
loff_t fl_end;
struct fasync_struct * fl_fasync; /* for lease break notifications */
unsigned long fl_break_time; /* for nonblocking lease breaks */
struct file_lock_operations *fl_ops; /* Callbacks for filesystems */
struct lock_manager_operations *fl_lmops; /* Callbacks for lockmanagers */
union {
struct nfs_lock_info nfs_fl;
} fl_u;
};
3, 实现
/**
* sys_flock: - flock() system call.
* @fd: the file descriptor to lock.
* @cmd: the type of lock to apply.
*
* Apply a %FL_FLOCK style lock to an open file descriptor.
* The @cmd can be one of
*
* %LOCK_SH -- a shared lock.
*
* %LOCK_EX -- an exclusive lock.
*
* %LOCK_UN -- remove an existing lock.
*
* %LOCK_MAND -- a `mandatory' flock. This exists to emulate Windows Share Modes.
*
* %LOCK_MAND can be combined with %LOCK_READ or %LOCK_WRITE to allow other
* processes read and write access respectively.
*/
asmlinkage long sys_flock(unsigned int fd, unsigned int cmd)
{
struct file *filp;
struct file_lock *lock;
int can_sleep, unlock;
int error;
error = -EBADF;
//检查fd是否是一个有效的文件描述符,若是获取该fd对应的文件对象的指针。
//否则返回错误。
filp = fget(fd);
if (!filp)
goto out;
can_sleep = !(cmd & LOCK_NB); //若没有设置LOCK_NB标记,可以睡眠置1
cmd &= ~LOCK_NB; //清楚cmd中的LOCK_NB标志位
unlock = (cmd == LOCK_UN); //unlock表示是否删除
//检查文件权限是否可读/写权限,若没有返回一个错误码
if (!unlock && !(cmd & LOCK_MAND) && !(filp->f_mode & 3))
goto out_putf;
//获取一个新的file_lock对象锁,并用适当的锁操作初始化它。
error = flock_make_lock(filp, &lock, cmd);
if (error)
goto out_putf;
//可以阻塞的话,需要把锁标志位掩码的FL_SLEEP置位
if (can_sleep)
lock->fl_flags |= FL_SLEEP;
error = security_file_lock(filp, cmd);
if (error)
goto out_free;
//若文件操作列表中有flock函数,就调用它,
//否则调用flock_lock_file_wait函数
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:
//若新的锁还没有加入到活动或阻塞链表中,则释放它
if (list_empty(&lock->fl_link)) {
locks_free_lock(lock);
}
out_putf:
//释放文件结构,删除该打开文件的所有文件锁
fput(filp);
out:
return error;
}
/* Fill in a file_lock structure with an appropriate FLOCK lock. */
//该函数获取一个新的file_lock锁对象,并根据cmd初始化它
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; //获取线程组的pid
fl->fl_flags = FL_FLOCK; //锁标志位
fl->fl_type = type;
fl->fl_end = OFFSET_MAX; //锁整个文件
*lock = fl;
return 0;
}
/**
* flock_lock_file_wait - Apply a FLOCK-style lock to a file
* @filp: The file to apply the lock to
* @fl: The lock to be applied
*
* Add a FLOCK style lock to a 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 != -EAGAIN) || !(fl->fl_flags & FL_SLEEP))
break;
error = wait_event_interruptible(fl->fl_wait, !fl->fl_next);
if (!error)
continue;
locks_delete_block(fl);
break;
}
return error;
}
/* Try to create a FLOCK lock on filp. We always insert new FLOCK locks
* at the head of the list, but that's secret knowledge known only to
* flock_lock_file and posix_lock_file.
*/
static int flock_lock_file(struct file *filp, struct file_lock *new_fl)
{
struct file_lock **before;
struct inode * inode = filp->f_dentry->d_inode;
int error = 0;
int found = 0;
lock_kernel();
//遍历inode->i_flock指向的链表,并检查该链表上每一个锁的类型。
for_each_lock(inode, before) {
struct file_lock *fl = *before;
if (IS_POSIX(fl)) //是posix锁
break;
if (IS_LEASE(fl)) //是租借锁
continue;
if (filp != fl->fl_file) //不同文件对象,不同进程/没有文件对象继承关系的进程
continue;
if (new_fl->fl_type == fl->fl_type) //文件锁类型相同,什么也不做返回0
goto out;
//否则,从索引节点链表和全局文件锁链表中删除该文件锁
//并呼醒fl_block链表中在该锁的等待队列上睡眠的所有进程,释放file_lock结构
found = 1;
locks_delete_lock(before); //锁找到把该锁从inode->i_flock和全局锁链表中删除
break;
}
unlock_kernel();
//若在执行开锁操作则什么都不做,该锁已经不存在了
if (new_fl->fl_type == F_UNLCK)
return 0;
/*
* If a higher-priority process was blocked on the old file lock,
* give it the opportunity to lock the file.
*/
if (found)
cond_resched();
lock_kernel();
//再次搜索索引节点锁链表,来验证现有的FL_FLOCK锁并不与请求的的锁冲突,
//在索引节点链表中,肯定没有FL_FLOCK写锁,若正在请求一个写锁,那说明本来就没有写锁
for_each_lock(inode, before) {
struct file_lock *fl = *before;
if (IS_POSIX(fl))
break;
if (IS_LEASE(fl))
continue;
if (!flock_locks_conflict(new_fl, fl)) //新锁和链表中的锁都不是EX(WRLCK)锁
continue;
error = -EAGAIN;
//若发现冲突:若fl_flags的FL_SLEEP标志位被设置,这把新的锁,插入到blocker锁循环链表和全局阻塞链表中。
if (new_fl->fl_flags & FL_SLEEP) {
locks_insert_block(fl, new_fl);
}
goto out;
}
//若不存在冲突,把新的file_lock结构插入到索引节点锁链表和全局文件锁链表中,返回0
locks_insert_lock(&inode->i_flock, new_fl);
error = 0;
out:
unlock_kernel();
return error;
}
4, 使用实例
//共享文件对象的进程间,可以替换掉以前的文件锁。
int main(void)
{
int fd;
pid_t pid;
fd = open("all.h", O_RDWR);
if (-1 == fd) {
perror("open");
goto done;
}
pid = fork();
if (pid == 0) { //child
sleep(2);
if(-1 == flock(fd, LOCK_EX|LOCK_NB)) {
perror("flock");
goto done;
}
fprintf(stderr, "ex lock ok!\n");
while (1);
} else if ( pid > 0) {
// parents
if(-1 == flock(fd, LOCK_SH)) {
perror("flock");
goto done;
}
fprintf(stderr, "sh lock ok!\n");
while (1);
}
}
执行结果:
[root@localhost stdkernel]# ./dolock
sh lock ok!
ex lock ok!
根据源码的解析,kernel用ex锁替换掉了sh锁,现在打开的文件只有一把ex锁,sh已被删掉。