linux之fcntl&flock建议锁的操作

前言

当我们要进行多进程编程的时候,经常存在多个进程需要访问同一个文件的情况,因此会产生进程间访问不一致的问题,那么我们可以用到fcntl函数,我们可以用它来对文
件或者文件的一部分进行上锁。

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

fcntl 可以施加建议性锁,也可以施加强制锁。同时还能对文件的某一记录进行上锁,也就是记录锁。
所谓建议性锁,就是说它不具备强制性,只是作为程序员之间的约定,如果你愿意,仍然可以直接去对一个上锁的文件进行操作。

fcntl

这里我们只讲述加建议性锁,即arg为struct flock指针类型的时候,如下。

int fcntl(int fd, int cmd, struct flock*);

从fcntl的函数声明可以看到,它的参数是可变的。

第一个参数fd:

要操作的文件描述符,

第二个参数cmd:

要操作的指令类型,我们只考虑在第三个参数为flock结构体指针的情况下,cmd有三种取值情况:
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type
值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
F_SETLKW F_SETLK
作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。

第三个参数struct flock*:

flock结构体定义如下

struct flock
{
    short int l_type;
    short int l_whence;
    off_t l_start;
    off_t l_len;
    pid_t l_pid;
};

l_type 有三种状态:

F_RDLCK 建立读锁
F_WRLCK 建立写锁
F_UNLCK 删除之前建立的锁

l_whence 也有三种方式:

SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。

l_start 表示相对l_whence位置的偏移量,两者一起确定锁定区域的开始位置。
l_len表示锁定区域的长度,若为0则表示整个文件的长度,即不管在后面增加多少数据都在锁的范围内。
返回值 成功返回依赖于cmd的值,若有错误则返回-1,错误原因存于errno.

测试过程中发现的问题!!!

  1. 用fcntl获取锁的时候(F_GETLK),struct flock参数的l_type取值必须为(F_RDLCK|F_WRLCK|F_UNL
    CK)中的一个,否则fcntl会执行失败,这个坑了我半天的时间,因为以为struct flock
    只是作为接受锁信息的载体,没想到其type也必须要赋值才行
    。测试证明,l_type的取值跟获取锁的结果没有任何关系,其赋值的意义仅在于赋值合法而已。

  2. 读锁作为共享锁,是可以存在多个的,所以在A进程里设置读锁后,在B进程里是获取不到的。

  3. 进程A设置的锁对进程A是不可见的,也就是说进程A无法GET到自己获得的锁。

测试程序

/*fcntl_write.c测试文件写入锁主函数部分*/
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

/*lock_set函数*/
void lock_set(int fd, int type)
{
    struct flock lock;
    lock.l_whence = SEEK_SET;//赋值lock结构体
    lock.l_start = 0;
    lock.l_len =0;

    lock.l_type = type;
    /*根据不同的type值给文件上锁或解锁*/
    if((fcntl(fd, F_SETLK, &lock)) == 0){
        if( lock.l_type == F_RDLCK )
            printf("read lock set by %d\n",getpid());
        else if( lock.l_type == F_WRLCK )
            printf("write lock set by %d\n",getpid());
        else if( lock.l_type == F_UNLCK )
            printf("release lock by %d\n",getpid());
    }
    else
    {
        /*判断文件是否可以上锁*/
        fcntl(fd, F_GETLK,&lock);
        /*判断文件不能上锁的原因*/
        if(lock.l_type == F_UNLCK)
            printf("no lock by %d\n",lock.l_pid);
        /*/该文件已有写入锁*/
        if( lock.l_type == F_RDLCK )
            printf("read lock already set by %d\n",lock.l_pid);
        /*该文件已有读取锁*/
        else if( lock.l_type == F_WRLCK )
            printf("write lock already set by %d\n",lock.l_pid);
    }
}

void lock_get(int fd)
{
    struct flock lock;
    lock.l_whence = SEEK_SET;//赋值lock结构体
    lock.l_start = 0;
    lock.l_len =0;
    lock.l_type = F_RDLCK;

    /*判断文件是否可以上锁*/
    if(0 > fcntl(fd, F_GETLK,&lock))
    {
        printf("get lock failure by %d\n",getpid());

    }
    /*判断文件不能上锁的原因*/
    if(lock.l_type == F_UNLCK)
        printf("no lock by %d\n",lock.l_pid);
    /*/该文件已有写入锁*/
    else if( lock.l_type == F_RDLCK )
        printf("read lock already set by %d\n",lock.l_pid);
    /*该文件已有读取锁*/
    else if( lock.l_type == F_WRLCK )
        printf("write lock already set by %d\n",lock.l_pid);
}

int main()
{
    int fd;
    /*首先打开文件*/
    fd=open("hello",O_RDWR | O_CREAT, 0666);
    if(fd < 0){
        perror("open");
        exit(1);
    }

    while(1)
    {
        char str[4];
        int type;
        scanf("%s", str);
        switch(str[0]){
            //write lock
            case 'w':
                type = F_WRLCK;
                break;
            //read lock
            case 'r':
                type = F_RDLCK;
                break;
            //unlock
            case 'u':
                type = F_UNLCK;
                break;
            //getlock
            case 'g':
                type = 0;
                break;
        }
        if(type == 0)
            lock_get(fd);
        else
            lock_set(fd, type);
    }

    close(fd);
    return 0;
}
请赐予我钱进的动力吧~
0%