1. 文件记录锁介绍

文件锁锁定的是整个文件,而记录锁定还可以锁定文件的某一特定部分,即从文件的某一相对位置开始的一段连续的字节流。

当一个进程正在读取或者修改文件的某个部分时,使用文件记录锁可以阻止其他进程修改同一文件的相同区域。它可以用来锁定文件的某个区域或者整个文件,SylixOS 支持多种文件记录锁 API

注:SylixOS 支持多种设备驱动模型,但是目前只有 NEW_1 型设备驱动支持文件记录锁功能,此类驱动文件节点类似于UNIX 系统的 vnode

2. 文件记录锁设置

SylixOS可以通过fcntl 函数操作文件记录锁的功能。

2.1 fcntl原型

#include<fcntl.h>

int  fcntl (int  iFd, int  iCmd, ...)

函数fcntl原型分析:

1. 此函数成功时根据参数iCmd的不同而返回不同的值,失败返回-1并设置错误号;

2. 参数 iFd 是文件描述符;

3. 参数 iCmd 是命令;

4. 参数 ...是命令参数。

fcntl设置文件记录锁时iCmd对应3个命令:F_GETLKF_SETLK F_SETLKW。命令解释分别是:F_GETLK表示获取文件锁;F_SETLK表示设置文件锁(非阻塞)F_SETLKW表示设置文件锁(阻塞)。 3 个参数是一个 flock 结构体指针,结构体成员如程序清单 2-1所示。

程序清单 2-1 flock结构体成员

struct flock {

    short   l_type;                        /* F_RDLCK, F_WRLCK, or F_UNLCK */

    short   l_whence;                      /* flag to choose starting      */

                                           /* offset                       */

    off_t   l_start;                       /* relative offset, in bytes    */

    off_t   l_len;                         /* length, in bytes; 0 means    */

                                           /* lock to EOF                  */

    pid_t   l_pid;                         /* returned with F_GETLK        */

    long    l_xxx[4];                      /* reserved for future use      */

};

  1. l_type表示锁的类型分别为:F_RDLOCK(共享读锁)、F_WRLOCK(独占写锁)和F_UNLCK解锁);

  2. l_whence表示文件记录锁的起始位置,其值如图 2-1所示。

图 2-1 l_Whence值相关

iWhence

oftOffset说明

SEEK_SET

将文件的偏移量设置为距文件开始处 oftOffset 个字节

SEEK_CUR

将文件的偏移量设置为当前值加oftOffset个字节,oftOffset可为负

SEEK_END

将文件的偏移量设置为文件长度加oftOffset个字节,oftOffset可为负

l_start是相对l_whence偏移开始位置(注意不可以从文件开始的之前部分锁起);

l_len是锁定区域长度,如果为0则锁定文件尾(EOF),如果向文件中追加数据也将被锁;

l_pid是已占用锁的进程ID(由命令F_GETLK返回)。

2.2  文件记录锁使用规则

文件记录锁中的F_RDLOCK(共享读锁)F_WRLOCK(独占写锁)的基本规则是:任意多个进程在一个给定字节上可以有一把共享的读锁,但是在一个给定字节上只能有一个进程有一把独占的写锁。进一步而言,如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个给定字节上有一把写锁,则不能再加任何锁。基本规则如 2-1所示。

表 2-1  记录锁规则

当前字节区锁状态

请求

读锁

写锁

无锁

允许

允许

一个或多个读锁

允许

拒绝

一个写锁

拒绝

拒绝

上面的规则适用于不同进程提出的锁请求,并不适用于单个进程提出的锁请求。也就是说,如果一个进程对一个文件区间已经有了一把锁,后来该进程又企图在同一个区间再加一把锁,那么也是可以的,这个时候新锁将替换已有锁。因此,如果一个进程将某个文件加了一把写锁,然后又企图给文件加一把读锁,那么将会成功执行,原来的写锁会被替换为读锁。

2.3  文件记录锁特点

  1. 记录锁采用(pidstartend)三元组作为锁标识,一个文件可拥有多个记录锁,同一区域只允许有一个记录锁。

  2. 当进程终止(正常/不正常),该进程拥有的所有记录锁都将释放。同一个进程中,指向同一文件(i-node)fd都可以操作该文件上的记录锁:如释放、修改等。显式调用F_UNLCKclose(fd)都将释放锁,close将释放整个文件中该进程拥有的所有记录锁。

  3. 记录锁不被spawn的子进程继承(PID不同)。

记录锁的类型转换、改变锁范围等操作均为原子操作。

未设置FD_CLOEXEC时,记录锁将被exec后的进程继承(PID相同)。

记录锁对文件打开mode有要求:加读锁要求文件句柄fd有读权限,加写锁要求fd有写权限。

3. 文件记录锁使用

比如进程A对文件“/apps/file”加上写锁(进程A先上锁),当A进程用户操作结束后会释放锁给进程B操作,进程A代码如程序清单 3-1所示。 

程序清单 3-1  进程A代码

#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

 

#define FILE_PATH   "/apps/file"

intmain(intargc, char *argv[])

{

    int           iFd     = 0;

    struct flock flck;

    short         sLockt  = F_WRLCK;

 

    iFd = open(FILE_PATH, O_RDWR);                /*打开文件*/                                  

    if (iFd < 0) {

        fprintf(stderr, "open file failed.\n");

        return -1;

    }

    /*

     *  l_whence = SEEK_SETl_start  = 0;表示从文件开始起偏移量为0开始上锁

     *  l_len    = 0;表示锁定到文件尾

     */

    flck.l_type   = sLockt;                       /*  文件记录锁类型设置为独写锁     */

    flck.l_whence = SEEK_SET;

    flck.l_start  = 0;

    flck.l_len    = 0;

 

    if (fcntl(iFd, F_SETLK, &flck) < 0) {         /*  fcntl设置文件记录锁            */

        fprintf(stderr, "add write lock failed.\n");

        close(iFd);

        return -1;

    }

    /*

     *  用户对文件被锁定区域操作

     */

sLockt        = F_UNLCK;                      /*  文件记录锁类型设置为解锁       */

    flck.l_type   = sLockt;

    flck.l_whence = SEEK_SET;

    flck.l_start  = 0;

    flck.l_len    = 0;

 

    if (fcntl(iFd, F_SETLK, &flck) < 0) {

        fprintf(stderr, "unlock failed.\n");

        close(iFd);

        return -1;

    }

    close(iFd);

    return 0;

}

进程B也对文件“/apps/file”操作,区别是进程B设置为F_SETLKW(阻塞等待解锁)。进程B阻塞等待进程A解锁方可对文件进行操作,进程B代码如程序清单 3-2所示。

程序清单 3-2  进程B代码

#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

 

#define FILE_PATH   "/apps/file"

intmain(intargc, char *argv[])

{

    int          iFd     = 0;

    struct flock flck;

    short        sLockt  = F_WRLCK;

 

    iFd = open(FILE_PATH, O_RDWR);                  /*  打开文件                      */

    if (iFd < 0) {

        fprintf(stderr, "open file failed.\n");

        return -1;

    }

    /*

     *  l_whence = SEEK_SETl_start  = 0;表示从文件开始起偏移量为0开始上锁

     *  l_len    = 0;表示锁定到文件尾

     */

    flck.l_type   = sLockt;                          /*  文件记录锁类型设置为独写锁   */

    flck.l_whence = SEEK_SET;

    flck.l_start  = 0;

    flck.l_len    = 0;

 

    if (fcntl(iFd, F_SETLKW, &flck) < 0) {          /*  fcntl设置文件记录锁          */

        fprintf(stderr, "add write lock failed.\n");

        close(iFd);

        return -1;

    }

    /*

     *  用户对文件被锁定区域进行操作

     */

    close(iFd);

    return 0;

}