我试图通过个人的理解方式讲解Linux文件锁的实现,使用的内核版本是3.13.0。
POSIX文件锁简介
先简单说下什么是文件锁。
Linux文件锁有两种:协同锁(有些成为建议锁)和强制锁。Linux读写文件时不会对协同锁做校验,只会对强制锁做验证。我只想看Linux内核如何同步多个进程同步读写,因此不考虑协同锁。
对于强制锁,按读写属性分,有读锁和写锁,或者解释为共享锁和排斥锁。很明显,因为多个进程可以同时读取同一区域的文件,而只有一个进程对文件某一区域写才是安全的。Linux对文件锁的处理
先不看加锁相关的代码,先看看Linux读取文件时对锁的处理。提一下,本人在阅读代码时发现,跟踪某个调用时总是容易发散,或者因为逻辑过于复杂导致阅读很难进行,因此这里说代码逻辑的时候,紧跟目标,简化逻辑,以帮助理解为主。
相关数据结构file_lock
介绍代码逻辑之前,先看下Linux使用的文件锁的数据结构file_lock:
struct file_lock {
struct file_lock*fl_next; /* singly linked list forthis inode */
struct hlist_nodefl_link; /* node in global lists */
struct list_headfl_block; /* circular list of blockedprocesses */
fl_owner_t <strong><span style="color:#ff0000;">fl_owner</span></strong>;
unsigned int <strong><span style="color:#ff0000;">fl_flags</span></strong>;
unsigned char <strong><span style="color:#ff0000;">fl_type</span></strong>;
unsigned int fl_pid;
int fl_link_cpu; /* what cpu's list is this on? */
struct pid *fl_nspid;
wait_queue_head_t fl_wait;
struct file *fl_file;
loff_t <strong><span style="color:#ff0000;">fl_start</span></strong>;
loff_t <strong><span style="color:#ff0000;">fl_end</span></strong>;
struct fasync_struct* fl_fasync; /* for lease breaknotifications */
/* for lease breaks: */
unsigned longfl_break_time;
unsigned longfl_downgrade_time;
const structfile_lock_operations *fl_ops; /*Callbacks for filesystems */
const structlock_manager_operations *fl_lmops; /*Callbacks for lockmanagers */
union {
structnfs_lock_info nfs_fl;
structnfs4_lock_info nfs4_fl;
struct {
struct list_headlink; /* link in AFS vnode'spending_locks list */
int state; /* state of grant or error if -ve */
} afs;
} fl_u;
};
这里使用的锁的类型是posix,用到的主要成员包括以下几个:
fl_owner: 当前任务打开文件链表,文件锁属于打开的文件,初始化为current->files;
fl_file: 与该锁关联的文件;
fl_flags:锁的标记位,它的枚举值包括:#define FL_POSIX 1
#define FL_FLOCK 2
#define FL_ACCESS 8 /* not trying to lock,just looking */
#define FL_EXISTS 16 /* when unlocking, test forexistence */
#define FL_LEASE 32 /* lease held on this file*/
#define FL_CLOSE 64 /* unlock on close */
#define FL_SLEEP 128 /* A blocking lock */
在这里,使用的默认值是FL_POSIX | FL_ACCESS,如果本次读取操作是阻塞的,还会加上FL_SLEEP。
fl_type: 锁的类型,比如读、写等,在sys_read中,当然是读,即F_RDLCK;
fl_start:加锁的起始位置;
fl_end: 加锁的结束位置,与fl_start一起表示本次操作的文件区间。
Linux锁冲突检测
对锁的数据结构有个大致了解,现在开始看相关的系统处理。
读文件对应的系统调用为read,在内核中是sys_read,在fs/read_write.c中定义:
SYSCALL_DEFINE3(read,unsigned int, fd, char __user *, buf, size_t,count)
该调用做一些基本的参数校验动作后调用vfs_read。在这里,vfs_read首先对参数做校验,然后校验用户空间内存,接下来就是需要讨论的,对文件锁的验证:rw_verify_area。
rw_verify_area有四个参数:
int read_write:读(READ),写(WRITE);
struct file *file:系统记录文件读写操作的信息,与文件描述符关联;
const loff_t *ppos:读写的文件当前位置;
size_t count:读写的字节数。
下面是rw_verify_area的函数实现。
</pre><pre name="code" class="cpp">int rw_verify_area(int read_write, struct file *file, const loff_t *ppos, size_t count)
{
struct inode *inode;
loff_t pos;
int retval = -EINVAL;
inode = file_inode(file); // 获取文件的inode数据。file是打开文件后才会产生的记录文件读写
// 信息等的数据结构。而inode是记录文件本身的信息,比如文件创建
// 时间、文件大小等信息,详细可以参考
// http://blog.csdn.net/panda19881/article/details/7799499
// (讲解struct inode)和
// http://www.cnblogs.com/QJohnson/archive/2011/06/24/2089414.html
// (讲解inode 与file的关系)。
if (unlikely((ssize_t) count < 0)) // 校验参数:读取的字节数不能是负数
return retval;
pos = *ppos;
if (unlikely(pos < 0)) { // 读取的位置
if (!unsigned_offsets(file))
return retval;
if (count >= -pos) /* both values are in 0..LLONG_MAX */ <==> count >= pos + INT_MAX
return -EOVERFLOW;
} else if (unlikely((loff_t) (pos + count) < 0)) {
if (!unsigned_offsets(file))
return retval;
}
if (unlikely(inode->i_flock && mandatory_lock(inode))) { // 这里判断是否需要校验强制锁,
// 不管怎么处理的,直接跳过去。
retval = <strong><span style="color:#ff0000;">locks_mandatory_area</span></strong>( // 看名字,就是这里了
read_write == READ ? FLOCK_VERIFY_READ : FLOCK_VERIFY_WRITE,
inode, file, pos, count);
if (retval < 0)
return retval;
}
retval = security_file_permission(file, // 这个是做安全相关验证的,不管它
read_write == READ ? MAY_READ : MAY_WRITE);
if (retval)
return retval;
return count > MAX_RW_COUNT ? MAX_RW_COUNT : count;
}
这里面与文件锁相关的关键点在这里: locks_mandatory_area ,可惜它也是对核心的一个封装,看下实现。
int locks_mandatory_area(int read_write, struct inode *inode,
struct file *filp, loff_t offset,
size_t count)
{
struct file_lock fl; // file_lock是内核中用来记录文件锁相关信息的,它的主要成员会在下面做初始化,
// 因此看看下面的几行就可以对它有个基本了解了。
int error;
locks_init_lock(&fl); // 最基本的数据结构初始化,忽略它先。
fl.fl_owner = current->files; // 文件锁的owner,这里是指当前task的文件files列表
// (fl_owner: struct files_struct *)。
fl.fl_pid = current->tgid; // tgid是线程相对于进程的线程号,thread group id
fl.fl_file = filp; // 文件(struct file *)
fl.fl_flags = FL_POSIX | FL_ACCESS; // 文件锁标志(POSIX锁,FL_ACCESS表示仅仅锁校验)
if (filp && !(filp->f_flags & O_NONBLOCK)) // 判断是否允许阻塞操作
fl.fl_flags |= FL_SLEEP; // 如果可以阻塞,加上FL_SLEEP参数
fl.fl_type = (read_write == FLOCK_VERIFY_WRITE) ? F_WRLCK : F_RDLCK; // 锁类型,读还是写
fl.fl_start = offset; // 锁的起始位置
fl.fl_end = offset + count - 1; // 锁的结束为止,与fl_start一起表示一个文件区域
for (;;) { // 这里做一个循环,一直等到锁冲突接触或者出现异常
error = <strong><span style="color:#ff0000;">__posix_lock_file</span></strong>(inode, &fl, NULL); // 这里对文件锁做校验
if (error != FILE_LOCK_DEFERRED)
break;
error = wait_event_interruptible(fl.fl_wait, !fl.fl_next); // 等待其他进程解锁
if (!error) {
/*
* If we've been sleeping someone might have
* changed the permissions behind our back.
*/
if (__mandatory_lock(inode)) // Linux的注释很清楚,就是这个循环过程中,可能有人
// 修改文件属性,导致不需要加锁
continue;
}
locks_delete_block(&fl); // 把锁从阻塞队列中移除
break;
}
return error;
}
锁冲突检测核心逻辑
紧跟步伐,看看这个函数怎么做的:__posix_lock_file,函数太长,先看一部分(代码都是一点点看的^_^)。
static int __posix_lock_file(struct inode *inode, struct file_lock *request, struct file_lock *conflock)
{
struct file_lock *fl;
struct file_lock *new_fl = NULL;
struct file_lock *new_fl2 = NULL;
struct file_lock *left = NULL;
struct file_lock *right = NULL;
struct file_lock **before;
int error;
bool added = false;
/*
* We may need two file_lock structures for this operation,
* so we get them in advance to avoid races.
*
* In some cases we can be sure, that no new locks will be needed
*/
if (!(request->fl_flags & FL_ACCESS) &&
(request->fl_type != F_UNLCK ||
request->fl_start != 0 || request->fl_end != OFFSET_MAX)) {
new_fl = locks_alloc_lock();
new_fl2 = locks_alloc_lock();
} // 这里弄了两个新的锁,先不管他,待会儿哪里用到了再看(让流程简化)
spin_lock(&inode->i_lock);
/*
* New lock request. Walk all POSIX locks and look for conflicts. If
* there are any, either return error or put the request on the
* blocker's list of waiters and the global blocked_hash.
*/
if (request->fl_type != F_UNLCK) { // 从读函数过来的,fl_type是F_RDLCK。
for_each_lock(inode, before) { // 这里是对inode相关的锁做一个遍历
// (inode是什么?它记录了一个文件的信
// 息,不是文件打开时创建的,文件在,
// 它就在。打开一次文件,就会有一个
// struct file 与它关联,当然加一次锁,
// 也会记录到这个结构上来)
fl = *before;
if (!IS_POSIX(fl)) // 我们看的都是POSIX锁。
continue;
if (!<strong><span style="color:#ff0000;">posix_locks_conflict</span></strong>(request, fl)) // 检测冲突,待会儿重点看下它
continue;
if (conflock) // 这个参数是传过来的,是NULL,不管它
__locks_copy_lock(conflock, fl);
error = -EAGAIN;
if (!(request->fl_flags & FL_SLEEP)) // 冲突了,也不等,直接退出
goto out;
/*
* Deadlock detection and insertion into the blocked
* locks list must be done while holding the same lock!
*/
error = -EDEADLK;
spin_lock(&blocked_lock_lock); // 这里检测死锁
if (likely(!<strong><span style="color:#ff0000;">posix_locks_deadlock</span></strong>(request, fl))) { // 这是核心,检测死锁的。
error = FILE_LOCK_DEFERRED;
__locks_insert_block(fl, request);
}
spin_unlock(&blocked_lock_lock);
goto out;
}
}
/* If we're just looking for a conflict, we're done. */
error = 0;
if (request->fl_flags & FL_ACCESS) // 到这里,说明inode中的锁全部校验完了,
// 没有跟我们的锁有冲突的,而且我们也就是
// 检测是否有锁冲突,不加锁也不解锁
// (FL_ACCESS)。read读取校验锁的部分结
// 束了,所以这段代码暂时看到这里!
goto out;
// …….. // 到此为止,简化逻辑
这段代码看下来,对检测锁冲突的流程有了了解,它是拿读的区域跟当前inode(即文件)的所有锁做检测,看看哪个有冲突,所有都没有冲突,那就可以返回成功。否则,如果不是阻塞操作,也退出。允许阻塞时,做一个死锁检测(死锁检测是干啥的?)。
现在看里面两个重点:posix_locks_conflict和posix_locks_deadlock。检测两个锁之间是否存在冲突
锁冲突检测:这个函数看起来简单,就几行代码,其实也真的好简单。
/* Determine if lock sys_fl blocks lock caller_fl. POSIX specific
* checking before calling the locks_conflict().
*/
static int posix_locks_conflict(struct file_lock *caller_fl, struct file_lock *sys_fl)
{
/* POSIX locks owned by the same process do not conflict with
* each other.
*/
if (!IS_POSIX(sys_fl) || <strong><span style="color:#ff0000;">posix_same_owner</span></strong>(caller_fl, sys_fl)) // 1. 只检测POSIX锁,这个逻辑跟上面的重复了
// 2. 看看两个锁的owner是否相同,是同一个
// owner也不算冲突
return (0);
/* Check whether they overlap */
if (!<strong><span style="color:#ff0000;">locks_overlap</span></strong>(caller_fl, sys_fl)) // 判断两个锁的区域是否有重叠
return 0;
return (<strong><span style="color:#ff0000;">locks_conflict</span></strong>(caller_fl, sys_fl)); // 两个都不能是写锁,写锁就是冲突
}
抛开锁的类型(只考虑POSIX锁),判断两个锁有冲突条件是:
锁的owner不同;
锁的区域有重叠;
其中一个是写锁(排斥锁)。
file_lock有个成员变量是fl_owner,但是检测两个锁的owner是否相同不是仅仅判断下这两个变量相同:
static int posix_same_owner(struct file_lock *fl1, struct file_lock *fl2)
{
if (fl1->fl_lmops && fl1->fl_lmops->lm_compare_owner)
return fl2->fl_lmops == fl1->fl_lmops &&
fl1->fl_lmops->lm_compare_owner(fl1, fl2); // 他还支持自定义比较两把锁
// owner的函数,好吧,我也脱离主干分岔了
return fl1->fl_owner == fl2->fl_owner; // 在这里,其实就是检测下owner变量是否相等。
}
再看看怎么检测区域重叠,就像检测一条直线上的两个线段是否有重叠:
/* Check if two locks overlap each other.
*/
static inline int locks_overlap(struct file_lock *fl1, struct file_lock *fl2)
{
return ((fl1->fl_end >= fl2->fl_start) &&
(fl2->fl_end >= fl1->fl_start));
}
简单的啥也不用说。
死锁检测
锁冲突检测完毕,看看死锁检测posix_locks_deadlock。
/* Must be called with the blocked_lock_lock held! */
static int posix_locks_deadlock(struct file_lock *caller_fl,
struct file_lock *block_fl)
// 两个参数:caller_fl,我们检测的那个锁;block_fl,有冲突的那个锁
// 如果这个函数返回1,就认为有死锁
{
int i = 0;
while ((block_fl = <strong><span style="color:#ff0000;">what_owner_is_waiting_for</span></strong>(block_fl))) { // 这个函数从阻塞<span style="font-family: Arial, Helvetica, sans-serif;">列表中找跟block_fl同</span><span style="font-family: Arial, Helvetica, sans-serif;"> </span>
<span style="font-family: Arial, Helvetica, sans-serif;"></span><pre name="code" class="cpp"> //<span style="font-family: Arial, Helvetica, sans-serif;"> 一个owner的锁,待会儿看它的实现</span>
if (i++ > MAX_DEADLK_ITERATIONS) // 如果同一个owner下等待的锁达到了上限,就<span style="font-family: Arial, Helvetica, sans-serif;">认为是死锁</span><pre name="code" class="cpp"> //<span style="font-family: Arial, Helvetica, sans-serif;">(MAX_DEADLK_ITERATIONS,我的代码中是10)</span>
return 0;
if (posix_same_owner(caller_fl, block_fl)) // 如果有个锁跟我们要检测的锁属<span style="font-family: Arial, Helvetica, sans-serif;">于同一个owner,认为</span>
<span style="font-family: Arial, Helvetica, sans-serif;"> // </span><span style="font-family: Arial, Helvetica, sans-serif;"> 是死锁</span>
return 1;
}
return 0; // 检测完了,没有相同owner的,没有死锁
}
再回过头来看__posix_lock_file函数,它的作用是检测文件关联的所有锁,看看是否有冲突,就是owner不同、区域有重叠且是个写锁。
Linux对文件读写锁/解锁的实现
如果仅仅是读取或者写入操作,那锁的校验也就到此结束了。不过现在还不知道锁是怎么添加的,怎么解锁的。
Linux对提供了一个fcntl函数来操作POSIX文件锁。
fcntl的定义如下:
int fcntl(intfildes, int cmd, ...);
fildes是文件描述符;
cmd表示本次fcntl操作的命令;
后面是命令的参数,如果是文件锁,对应的cmd是F_SETLK(加读写锁、解锁等),后面的参数是struct flock:
shortl_type 文件锁的类型: F_RDLCK, F_WRLCK, F_UNLCK
short l_whence 从哪里开始(文件起始位置、当前位置还是末尾)
off_tl_start 锁的起始相对偏移量
off_tl_len 大小,如果是0表示到文件末尾
pid_tl_pid 当前拥有这把锁的进程ID,在F_GETLK时返回。
fcntl对应的系统调用函数是(fs/fcntl.c):
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
struct fd f = fdget_raw(fd); // 根据文件描述符获取文件相关信息
long err = -EBADF;
if (!f.file)
goto out;
if (unlikely(f.file->f_mode & FMODE_PATH)) { // 验证参数有效性
if (!check_fcntl_cmd(cmd))
goto out1;
}
err = security_file_fcntl(f.file, cmd, arg); // 安全相关
if (!err)
err = do_fcntl(fd, cmd, arg, f.file); // 真正的处理系统调用
out1:
fdput(f);
out:
return err;
}
do_fcntl的定义如下:
static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
struct file *filp)
{
long err = -EINVAL;
switch (cmd) {
….. // 此处省略N个字节
case F_GETLK: // 获取锁信息
err = fcntl_getlk(filp, (struct flock __user *) arg);
break;
case F_SETLK:
case F_SETLKW: // 加锁
err = <strong><span style="color:#ff0000;">fcntl_setlk</span></strong>(fd, filp, cmd, (struct flock __user *) arg);
break;
……. // 再省略N个字节
}
return err;
}
看看加锁的fcntl_setlk函数:
int fcntl_setlk(unsigned int fd, struct file *filp, unsigned int cmd,
struct flock __user *l)
{
struct file_lock *file_lock = locks_alloc_lock(); // 申请一个file_lock
struct flock flock;
struct inode *inode;
struct file *f;
int error;
if (file_lock == NULL)
return -ENOLCK;
/*
* This might block, so we do it before checking the inode.
*/
error = -EFAULT;
if (copy_from_user(&flock, l, sizeof(flock))) // 把数据从用户空间复制到内核空间
goto out;
inode = file_inode(filp); // 获取文件的inode结构
/* Don't allow mandatory locks on files that may be memory mapped
* and shared.
*/
if (mandatory_lock(inode) && mapping_writably_mapped(filp->f_mapping)) { // 校验加锁的条件
error = -EAGAIN;
goto out;
}
again: // 这里有一个循环
error = <strong><span style="color:#ff0000;">flock_to_posix_lock</span></strong>(filp, file_lock, &flock); // 根据传入的flock信息转换为内核数据结构
// file_lock(按照flock提供的文件区域信
// 息,填充到file_lock中)
if (error)
goto out;
if (cmd == F_SETLKW) { // 最后一个W是Wait的意思,就是允许等待
file_lock->fl_flags |= FL_SLEEP;
}
error = -EBADF;
switch (flock.l_type) { // 验证锁类型与当前打开文件的模式是否匹配
case F_RDLCK:
if (!(filp->f_mode & FMODE_READ)) // 加了读锁,但是文件打开模式不包含读取
goto out;
break;
case F_WRLCK: // 写锁,校验与读锁类似
if (!(filp->f_mode & FMODE_WRITE))
goto out;
break;
case F_UNLCK: // 解锁啥都不用干了,就是解锁
break;
default:
error = -EINVAL;
goto out;
}
error = <strong><span style="color:#ff0000;">do_lock_file_wait</span></strong>(filp, cmd, file_lock); // 看名字猜,这里就是去加锁的函数
// 如果在这个过程中,这个文件被关闭了,文件描述符重新分配
// 给了其他文件,就得回退,加了的锁再解锁
/*
* Attempt to detect a close/fcntl race and recover by
* releasing the lock that was just acquired.
*/
/*
* we need that spin_lock here - it prevents reordering between
* update of inode->i_flock and check for it done in close().
* rcu_read_lock() wouldn't do.
*/
spin_lock(¤t->files->file_lock);
f = fcheck(fd); // 获取描述符fd对应的struct file 结构(文件打开时会创建对应的struct file)
spin_unlock(¤t->files->file_lock);
if (!error && f != filp && flock.l_type != F_UNLCK) { // !error: 加锁函数执行成
// 功,表示加锁或者解锁操作成功;f != filep:描述符对
// 应的打开文件已经变了,需要做回退操作;
// flock.l_type != F_UNLCK:如果原来就是解锁了,那
// 就不用回退,解锁无需回退
flock.l_type = F_UNLCK; // 加锁变成解锁
goto again; // 执行回退操作
}
out:
locks_free_lock(file_lock);
return error;
}
flock_to_posix_lock这个函数比较简单,是把用户层的参数flock转换为内核层数据结构file_lock,既然简单,就简单看下吧。
static int flock_to_posix_lock(struct file *filp, struct file_lock *fl,
struct flock *l)
{
off_t start, end;
switch (l->l_whence) { // 文件偏移量从哪里计算
case SEEK_SET: // 从文件起始处
start = 0;
break;
case SEEK_CUR: // 文件当前位置
start = filp->f_pos; // struct file记录了当前操作的文件位置
break;
case SEEK_END: // 文件结尾
start = i_size_read(file_inode(filp)); // 这个就是文件大小
break;
default:
return -EINVAL;
}
/* POSIX-1996 leaves the case l->l_len < 0 undefined;
POSIX-2001 defines it. */
start += l->l_start; // 计算加锁的文件起始位置:文件便宜位置+相对位置
if (start < 0)
return -EINVAL;
fl->fl_end = OFFSET_MAX; // 下面几行计算加锁区域的结束位置
if (l->l_len > 0) { // 加锁的长度大于0,没什么说的
end = start + l->l_len - 1;
fl->fl_end = end;
} else if (l->l_len < 0) { // 如过给的是负数,区域结尾就是当前计算的
// 起始位置start-1,起始位置改成起始位置
// 加上这个负的长度
end = start - 1;
fl->fl_end = end;
start += l->l_len;
if (start < 0)
return -EINVAL;
} // 这里没有说长度是0的处理,其实就用的默
// 认值OFFSET_MAX,可以一直到文件末尾
fl->fl_start = start; /* we record the absolute position */
if (fl->fl_end < fl->fl_start)
return -EOVERFLOW;
fl->fl_owner = current->files; // 这几个参数跟前面说的计算方法是一样的
fl->fl_pid = current->tgid;
fl->fl_file = filp;
fl->fl_flags = FL_POSIX;
fl->fl_ops = NULL;
fl->fl_lmops = NULL;
return assign_type(fl, l->l_type); // 校验l_type是否合法:读锁、写锁和解锁之一
}
回到重点do_lock_file_wait函数,其实这个函数也没啥重点的,只是它调用了核心函数。
static int do_lock_file_wait(struct file *filp, unsigned int cmd,
struct file_lock *fl)
{
int error;
error = security_file_lock(filp, fl->fl_type); // 安全检测,略过
if (error)
return error;
for (;;) { // 循环尝试加锁或解锁
error = <strong><span style="color:#ff0000;">vfs_lock_file</span></strong>(filp, cmd, fl, NULL); // 加锁核心函数
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;
}
vfs_lock_file相当简单,也只是一个函数的封装:
int vfs_lock_file(struct file *filp, unsigned int cmd, struct file_lock *fl, struct file_lock *conf)
{
if (filp->f_op->lock) // 文件系统如果提供了自己的锁,就用自己的(ext2文件系统没有提供这个文件锁函数,所以不看它)
return filp->f_op->lock(filp, cmd, fl);
else
return <strong><span style="color:#ff0000;">posix_lock_file</span></strong>(filp, fl, conf); // 系统提供的标准POSIX文件锁
}
posix_lock_file真的也不想说什么,它就一句话:
return __posix_lock_file(file_inode(filp),fl, conflock);
加锁解锁的核心逻辑
__posix_lock_file,前面也提到了。它分为两部分,第一部分的功能是检测文件锁是否有冲突,在上面已经介绍过了;第二部分是真正的加锁,或者解锁的,主要的还是处理与当前的锁处理冲突,比如合并、分解,或者删除。
只看第二部分,看代码前提前说一点,文件锁都是按区域来的,对一个区域,只能有一种类型的锁,多个文件锁,是按照区域从小到大来排序的,而且不会有交叉重叠。好,来看代码吧,只有下半部分。/*
* Find the first old lock with the same owner as the new lock.
*/
before = &inode->i_flock; // struct file_lock *,与这个文件关联的文件锁列表
// 下面的合并或者拆解操作都是针对同一个owner的锁操作的
/* First skip locks owned by other processes. */
while ((fl = *before) && (!IS_POSIX(fl) || // 找到第一个同一个owner的锁
!posix_same_owner(request, fl))) {
before = &fl->fl_next;
}
/* Process locks with this owner. */
while ((fl = *before) && posix_same_owner(request, fl)) {
/* Detect adjacent or overlapping regions (if same lock type)
*/
if (request->fl_type == fl->fl_type) { // 锁的类型是相同的,都是读锁,或都是写锁,不会是解锁
// 这里先找到有区域重叠的锁
// 不过要注意的是,不能用end+1,只能用start -1
// 来比较,因为end可能是OFFSET_MAX,这时候
// end+1就溢出了
if (fl->fl_end < request->fl_start - 1) // 新锁在fl的右边
goto next_lock;
/* If the next lock in the list has entirely bigger
* addresses than the new one, insert the lock here.
*/
if (fl->fl_start - 1 > request->fl_end) // 新锁在左边
break;
/* If we come here, the new and old lock are of the
* same type and adjacent or overlapping. Make one
* lock yielding from the lower start address of both
* locks to the higher end address.
*/
// 有重叠或者相同,把它们合并起来
if (fl->fl_start > request->fl_start) // 找左边界
fl->fl_start = request->fl_start;
else
request->fl_start = fl->fl_start;
if (fl->fl_end < request->fl_end) // 找右边界
fl->fl_end = request->fl_end;
else
request->fl_end = fl->fl_end;
if (added) { // 这个added是说是不是第一次合并,如果不是的话,得删掉一把
// 锁,第一次不用删
locks_delete_lock(before); // 删除后fl自动移动到了下一个锁
continue;
}
request = fl;
added = true;
}
else { // 处理不同类型的锁,如果有重叠的话,新锁的区域应该覆盖旧锁
/* Processing for different lock types is a bit
* more complex.
*/
if (fl->fl_end < request->fl_start) // 没有重叠,不过这里比较的不是start – 1,而是start,
// 为什么?因为相同类型的锁,边界相邻,需要做合并
goto next_lock;
if (fl->fl_start > request->fl_end) // 没有重叠
break;
if (request->fl_type == F_UNLCK)
added = true; // added = true将锁删掉
if (fl->fl_start < request->fl_start)// 当前锁的左边界在request左边界的左边
left = fl; // 记录下left
/* If the next lock in the list has a higher end
* address than the new one, insert the new one here.
*/
if (fl->fl_end > request->fl_end) { // 当前的锁右边界在request右边界的右边
right = fl; // 记录下right
break; // 找到了比request还要靠右的,那就不用找了,后面的肯定跟
// request没有重叠的
}
if (fl->fl_start >= request->fl_start) { // 这把锁的左边界在request左边界的右边,结合上面
// 那个分析判断,这时候fl肯定完全被request覆盖掉了
/* The new lock completely replaces an old
* one (This may happen several times).
*/
if (added) { // 如果已经添加过一把新锁或者是解锁操作
locks_delete_lock(before); // 直接删除旧锁,否则à
continue;
}
/* Replace the old lock with the new one.
* Wake up anybody waiting for the old one,
* as the change in lock type might satisfy
* their needs.
*/
locks_wake_up_blocks(fl);
fl->fl_start = request->fl_start; // 用新的锁覆盖旧的锁
fl->fl_end = request->fl_end;
fl->fl_type = request->fl_type;
locks_release_private(fl);
locks_copy_private(fl, request);
request = fl;
added = true;
}
}
/* Go on to next lock.
*/
next_lock:
before = &fl->fl_next;
}
// 这个循环做完了,可能会得到这样的结果:<span style="font-family: Arial, Helvetica, sans-serif;"> </span>
// 1. 与相同类型的锁合并了,或者合并了一部分;
// 2. 替换掉了不同类型的锁;
// 3. 找到了一个这个锁左边的一把锁(指左边界);
// 4. 找到了一个这个锁右边的一把锁(指右边界)
/*
* The above code only modifies existing locks in case of merging or
* replacing. If new lock(s) need to be inserted all modifications are
* done below this, so it's safe yet to bail out.
*/
error = -ENOLCK; /* "no luck" */
if (right && left == right && !new_fl2) // 如果找到了一个可以覆盖request的锁,但是没有创建一个新锁
// new_fl2的话,直接退出。什么时候会创建new_fl2?在这个函数最前面有一个判断,new_fl2与
// new_fl同时创建,条件是:1. 本次不是仅仅检测锁状态,2. 不是解锁,起始位置不是0,结束
// 为止不是OFFSET_MAX(综合起来看第二个条件,就是说如果解锁时,解的是从0到最大值,就是
// 整个文件上加的锁肯定会解开,那就不可能会创建新的锁),那就会创建这两个锁
goto out;
error = 0;
if (!added) { // 这个标志打上了,说明request没有被因为锁区域冲突处理过
if (request->fl_type == F_UNLCK) { // 解锁操作
if (request->fl_flags & FL_EXISTS) // FL_EXISTS这个标志位是说,如果执行解锁操作时,仅仅查看锁是否存在
error = -ENOENT; // 返回不存在(没有冲突的锁)
goto out;
}
// request锁在所有锁的缝隙中,与其他锁没有任何区域重叠,或者就是第一把锁,就把这把锁加进去
if (!new_fl) {
error = -ENOLCK;
goto out;
}
locks_copy_lock(new_fl, request);
locks_insert_lock(before, new_fl);
new_fl = NULL;
}
// 下面处理类型不同,但是有重叠区域的锁
// right是指右边界在request右边的锁
// left是指左边界在request左边的锁
if (right) { // 如果找到了request右边的那把锁
if (left == right) { // 且左边的锁跟右边的是同一把,就是这把旧锁的区域把request的区域覆盖了,
// 那就把这把锁拆开,左边一个,中间一个request,右边一个
/* The new lock breaks the old one in two pieces,
* so we have to use the second new lock.
*/
left = new_fl2; // 左边保留
new_fl2 = NULL;
locks_copy_lock(left, right);
locks_insert_lock(before, left);
}
right->fl_start = request->fl_end + 1; // 调整右边锁的区域,左边锁的区域下面会调整
locks_wake_up_blocks(right);
}
if (left) { // 找到了request左边的那把锁,但是没有覆盖掉request(否则right也等于
// left,在上面的逻辑就处理掉了);或者是找到了覆盖掉request的那把锁,
// 现在处理左半边那把锁
left->fl_end = request->fl_start - 1;
locks_wake_up_blocks(left);
}
out:
spin_unlock(&inode->i_lock);
/*
* Free any unused locks.
*/
if (new_fl)
locks_free_lock(new_fl);
if (new_fl2)
locks_free_lock(new_fl2);
return error;
}
加锁的核心部分已经完成了,代码函数虽然有点长,但是逻辑还是很清晰,目的就是检测锁冲突,新锁覆盖旧锁。
总结
POSIX文件锁是为了保证多个程序同时访问同一个文件时数据的完整性。Linux在每次文件读写和加锁时都会检测是否有锁冲突,每次加锁或解锁,都会更新相应区域的锁为新锁类型,当然,解锁是直接把该区域的锁信息删除。另外,Linux将文件锁的区域按照从左到右排序,提高了锁的访问效率。