Linux中POSIX文件锁的实现

我试图通过个人的理解方式讲解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将文件锁的区域按照从左到右排序,提高了锁的访问效率。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值