文件锁之flock
文件锁的作用:当第一个进程正在读或修改文件的某个部分时,使用文件锁可以阻止其他进程修改文件的相同部分。
因此文件锁可用于多个进程之间进行同步,防止进程间的竞争状态。
Linux系统支持两组给文件加锁的不同API,分别是fcntl
与flock
。本节主要记录flock
的实现原理以及使用方式。
flock
锁与文件表项相关
flock
锁与文件表项相关,这意味着如果同一个进程通过调用open
打开同名文件(这会生成一个新的文件表项)得到文件描述符,在该文件描述符上加flock
锁时会当作两把锁,按照锁的互斥原则进行判断。这一点与fcntl
记录锁不同,fcntl
锁在不同文件描述符上会被视为同一把锁,之间不会产生冲突。
而如果通过dup
、dup2
以及fork
得到的文件描述符,在该文件描述符加锁时会被认为是相同的锁,因为这些文件描述符指向相同的文件表项。此时如果通过任意一个文件描述符解锁,那么其他文件描述符上的锁也会被解除。
如果不显式进行解锁,则需要所有文件描述符关闭之后才可以解锁,而fcntl
锁则是指向相同i
节点的任意一个文件描述符关闭,都会解锁。
参见源码:fs/locks.c
/* Determine if lock sys_fl blocks lock caller_fl. Common functionality
* checks for shared/exclusive status of overlapping locks.
*/
static bool locks_conflict(struct file_lock* caller_fl, struct file_lock* sys_fl)
{
if (sys_fl->fl_type == F_WRLCK)
return true;
if (caller_fl->fl_type == F_WRLCK)
return true;
return false;
}
/* Determine if lock sys_fl blocks lock caller_fl. FLOCK specific
* checking before calling the locks_conflict().
*/
/* 判断系统已存在的锁是否阻塞请求添加的锁 */
static bool flock_locks_conflict(struct file_lock* caller_fl, struct file_lock* sys_fl)
{
/* FLOCK locks referring to the same filp do not conflict with
* each other.
*/
/* 如果请求添加的锁与系统已存在的锁的文件表项相同,则不阻塞,锁不冲突 */
/* 这种情况一般出现在对duo或fork之后的文件描述符进行加锁 */
if (caller_fl->fl_file == sys_fl->fl_file)
return false;
/* 如果文件表项不相同,则只要其中任何一个为写锁则阻塞 */
return (caller_fl, sys_fl);
}
/* Try to create a FLOCK lock on filp. We always insert new FLOCK locks
* after any leases, but before any posix locks.
*
* Note that if called with an FL_EXISTS argument, the caller may determine
* whether or not a lock was successfully freed by testing the return
* value for -ENOENT.
*/
static int flock_lock_inode(struct inode* inode, struct file_lock* request)
{
// ...
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;
// 从系统锁列表中删除文件表项相同的锁fl,并将其添加到dispose链表中
// 如果是请求锁则重新添加,解锁则清除dispose
locks_delete_lock_ctx(fl, &dispose);
break;
}
// 这里:表示存在锁并且与请求的锁文件表项相同但类型不同
if (request->fl_type == F_UNLCK)
{
// 解锁
if ((request->fl_flags & FL_EXISTS) && !found)
error = -ENOENT;
/* 如果是解锁并且锁已找到,执行out,out中会解锁 */
goto out;
}
/* 这里:已经去掉了文件表项相同的锁,判断是否存在文件表项不同的锁,导致冲突 */
find_conflict:
/* 遍历文件描述符指向的i节点中的flock链表 */
list_for_each_entry(fl, &ctx->flc_flock, fl_list)
{
/* 判断是否有锁冲突,如果不冲突则继续循环 */
if (!flock_locks_conflict(request, fl))
continue;
error = -EAGAIN;
/* 这里:发生了所冲突,设置错误码FILE_LOCK_DEFERRED,休眠标志(FL_SLEEP) */
/* 用于通知外层,当前进程需要休眠等待锁 */
if (!(request->fl_flags & FL_SLEEP))
goto out;
error = FILE_LOCK_DEFERRED;
/* 将当前请求的锁加入到阻塞的锁列表中 */
locks_insert_block(fl, request, flock_locks_conflict);
goto out;
}
/* 没有锁冲突,则添加当前锁到flock锁链表中 */
if (request->fl_flags & FL_ACCESS)
goto out;
locks_copy_lock(new_fl, request);
locks_move_blocks(new_fl, request);
locks_insert_lock_ctx(new_fl, &ctx->flc_flock);
new_fl = NULL;
error = 0;
out:
spin_unlock(&ctx->flc_lock);
percpu_up_read(&file_rwsem);
if (new_fl)
locks_free_lock(new_fl);
/* 清空dispose中的所有锁 */
locks_dispose_list(&dispose);
trace_flock_lock_inode(inode, request, error);
return error;
}
/**
* flock_lock_inode_wait - Apply a FLOCK-style lock to a file
* @inode: inode of the file to apply to
* @fl: The lock to be applied
*
* Apply a FLOCK style lock request to an inode.
*/
static int flock_lock_inode_wait(struct inode* inode, struct file_lock* fl)
{
int error;
might_sleep();
for (;;)
{
error = flock_lock_inode(inode, fl);
/* 当错误码为FILE_LOCK_DEFERRED时,继续循环 */
if (error != FILE_LOCK_DEFERRED)
break;
error = wait_event_interruptible(fl->fl_wait, list_empty(&fl->fl_blocked_member));
if (error)
break;
}
locks_delete_block(fl);
return error;
}
flock
非显式解锁
如果flock
锁不显式进行解锁,则需要所有文件描述符关闭之后才可以解锁,而fcntl
锁则是指向相同i
节点的任意一个文件描述符关闭,都会解锁。
参见源码:fs/open.c, fs/file.c fs/file_table.c
/*
* Careful here! We test whether the file pointer is NULL before
* releasing the fd. This ensures that one clone task can't release
* an fd while another clone is opening it.
*/
SYSCALL_DEFINE1(close, unsigned int, fd)
{
int retval = __close_fd(current->files, fd);
/* ... */
return retval;
}
/*
* The same warnings as for __alloc_fd()/__fd_install() apply here...
*/
int __close_fd(struct files_struct* files, unsigned fd)
{
struct file* file;
struct fdtable* fdt;
// 从进程的文件管理结构体中获取文件描述符表
spin_lock(&files->file_lock);
fdt = files_fdtable(files);
/* 判断要关闭的文件描述符是否大于已打开的最大文件描述符 */
if (fd >= fdt->max_fds)
goto out_unlock;
/* 通过文件描述符表以及要关闭的文件描述符,获取内核维护的文件表项 */
file = fdt->fd[fd];
if (!file)
goto out_unlock;
rcu_assign_pointer(fdt->fd[fd], NULL);
/* 获取到文件表项之后,释放文件描述符 */
__put_unused_fd(files, fd);
/* 释放完文件描述符,则对进程表中管理当前进程已打开文件的操作已经结束 */
spin_unlock(&files->file_lock);
// 内核处理文件表项
return filp_close(file, files);
out_unlock:
spin_unlock(&files->file_lock);
return -EBADF;
}
/*
* "id" is the POSIX thread ID. We use the
* files pointer for this..
*/
int filp_close(struct file* filp, fl_owner_t id)
{
int retval = 0;
if (!file_count(filp))
{
printk(KERN_ERR "VFS: Close: file count is 0\n");
return 0;
}
// 关闭文件时刷新
if (filp->f_op->flush)
retval = filp->f_op->flush(filp, id);
if (likely(!(filp->f_mode & FMODE_PATH)))
{
dnotify_flush(filp, id);
// 释放posix锁
locks_remove_posix(filp, id);
}
// 进一步处理文件表项,判断是否引用计数为0,进而清理
fput(filp);
return retval;
}
void fput(struct file* file)
{
// 文件引用计数检测
if (atomic_long_dec_and_test(&file->f_count))
{
struct task_struct* task = current;
if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD)))
{
// 创建任务,并设置处理函数为____fput,____fput中会执行清理动作
init_task_work(&file->f_u.fu_rcuhead, ____fput);
// 添加任务并开始执行
if (!task_work_add(task, &file->f_u.fu_rcuhead, true))
return;
/*
* After this task has run exit_task_work(),
* task_work_add() will fail. Fall through to delayed
* fput to avoid leaking *file.
*/
}
if (llist_add(&file->f_u.fu_llist, &delayed_fput_list))
schedule_delayed_work(&delayed_fput_work, 1);
}
}
static void ____fput(struct callback_head* work)
{
__fput(container_of(work, struct file, f_u.fu_rcuhead));
}
/* the real guts of fput() - releasing the last reference to file
*/
static void __fput(struct file* file)
{
/* ... */
// 移除flock文件锁
locks_remove_file(file);
/* ... */
out:
file_free(file);
}
其他
flock
锁只能对文件整体加锁,无法对文件内容加锁。flock
锁不受文件打开模式(O_RDONLY, O_WRONLY, O_RDWR
)影响,fcntl
锁需要和打开模式保持一致。flock
锁如果冲突会直接阻塞,不支持检测,fcntl
锁除非设置命令为F_SETLKW
才会阻塞,否则会出错返回。
- 原文作者:生如夏花
- 原文链接:https://blduan.top/post/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/apue/%E6%96%87%E4%BB%B6%E9%94%81%E4%B9%8Bflock/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。