Linux下C/C++给文件加锁

31 篇文章 7 订阅

本文主要讲述Linux下如何在C/C++程序中给文件加锁,防止多个进程并发读写同一个文件导致文件内容不一致,使用fcntl()函数,这是一个POSIX函数。


一 为何需要文件锁

假设有一个菜谱文件,其内容是今天需要买的菜,现在有2个进程A和B,要去访问这个文件,它们的操作如下,

  1. A读取了这个文件内容
  2. B读取了这个文件内容
  3. A对读出的内容做了一些修改,添加了番茄,然后写回文件里
  4. B也对读出的内容做了一些修改,添加了土豆,但是它不知道A也修改了文件,然后就把自己的修改写回文件里
  5. 最后文件内容里只包含了B的修改,A的修改被覆盖了,最终的菜谱里就没有番茄了

这不是我们想要结果,我们希望最终文件里同时包含了A和B的修改,因为番茄和土豆都需要。所以就需要文件锁,当A访问文件时,B只能等到A访问结束后才可以访问,这样就保证了文件的一致性。


二 锁的分类

Linux下文件锁分为事务锁和强制锁,

1. 事务锁(Advisory Locking)

事务锁需要并发访问同一文件的进程之间进行合作。如果进程A获取了文件锁,然后开始访问文件,而进程B没有去尝试获取锁就直接访问文件,那么进程B就是不合作进程。如果进程B在访问文件之前去尝试获取文件锁,那么进程B就是合作进程。

只有并发访问同一文件的进程在访问文件之前,去尝试获取文件锁,那么事务锁才能发挥作用。

2. 强制锁(Mandatory Locking)

强制锁不需要进程之间进行合作,但是需要对文件系统和被访问的文件进行一些设置。设置OK后,当进程A获取了文件锁并开始访问文件,此时其它进程即使直接去访问文件,也不能访问这个文件。

设置方式如下:

  1. 挂载文件系统时使用-o mand选项,这个mand就是mandatory的简写
  2. 打开被访问文件的set-group-ID位,并关闭其group-execute位,这样就在这个文件上enable了强制锁的功能

三 示例代码

代码使用fcntl()这个标准函数,其原型如下,

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

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

这个函数的功能主要是对打开文件的描述符执行一些操作。功能很多,具体可以看man手册,这里只讨论其锁的功能。
当我们使用fcntl()的文件锁功能时,其原型就变成如下,

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

第一个参数fd就是需要加锁的文件描述符,第二个参数有三个可用:F_SETLK,F_SETLKW和F_GETLK,分别用于获取锁,释放锁和测试锁的存在。第三个参数是struct flock类型的指针,其定义如下,只列出了需要的部分,

struct flock {
               ...
               short l_type;    /* Type of lock: F_RDLCK,
                                   F_WRLCK, F_UNLCK */
               short l_whence;  /* How to interpret l_start:
                                   SEEK_SET, SEEK_CUR, SEEK_END */
               off_t l_start;   /* Starting offset for lock */
               off_t l_len;     /* Number of bytes to lock */
               pid_t l_pid;     /* PID of process blocking our lock
                                   (set by F_GETLK and F_OFD_GETLK) */
               ...
};

这个结构体可以锁住文件的部分内容,不过本文只锁住整个文件,这样操作起来更简单一点。下面再来看一下fcntl()的第二个参数的意义,如下,

  • F_SETLK:SET LOCK的缩写,获取(当l_type为F_RDLCK或F_WRLCK)或释放文件锁(当l_type为F_UNLCK ),当一个进程获取到了文件锁,别的进程使用F_SETLK去尝试获取文件锁,那么fcntl()就会返回-1,errno的值会变成EACCES或EAGAIN。可以看做是一个非阻塞的锁。
  • F_SETLKW:SET LOCK WAIT的缩写,其功能和F_SETLK基本一样,只是当一个进程获取到了文件锁,别的进程使用F_SETLKW去尝试获取文件锁时,这个进程就会在那等待。可以看做是阻塞锁。
  • F_GETLK:GET LOCK的缩写,可以用于检查一个文件上是否被锁住,如果是就可以获取该文件锁,并存放于fcntl()的第三个参数lock里
1.事务锁示例

文件名为file_lock.c

#include <sys/fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
	if (argc != 2) {
		fprintf(stderr, "usage: %s file\n", argv[0]);
		return 1;
	}

	int fd = open(argv[1], O_RDWR);
	if (fd == -1) {
		perror("open");
		return 1;
	}


    // 锁住整个文件
	struct flock lock = {};
    lock.l_type = F_WRLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;

    lock.l_pid = getpid();

    if (fcntl(fd, F_SETLKW, &lock) == -1) {
        perror("fcntl");
        return 1;
    }

    printf("hello, on access file %s\n", argv[1]);

    sleep(10); // 等待10秒,或者在这段时间里写文件
    
    // 释放锁
    lock.l_type = F_UNLCK;
    if (fcntl(fd, F_SETLKW, &lock) == -1) {
        perror("fcntl");
        return 1;
    }


    return 0;
}

使用gcc编译,

gcc file_lock.c -o file_lock

操作如下和打印如下,
在这里插入图片描述
此时该进程获取了文件锁,然后我们开另外一个终端,再次运行这个程序,这样就会有另外一个进程尝试去获取锁,
在这里插入图片描述
可以看出运行后并没有打印,该进程在那等着,因为使用的cmd是F_SETLKW。10秒后第一个进程释放了文件锁并终结,第二个进程就可以获取到文件锁了。

这里简单看下不合作进程的例子,当我们使用以下命令去获取文件锁,
在这里插入图片描述
在另外一个终端使用echo命令去修改文件,

echo "hello" > aa.txt

会发现文件直接被修改了,这样事务锁就不起作用了。

2. 强制锁示例

强制锁的使用的代码例子和事务锁一样,还需要对文件系统和被访问文件进行配置,这里使用tmpfs文件系统做个示范,
关于tmpfs的简单使用可以参照这篇文章,然后按照如下操作进行配置,

  1. mkdir dir
  2. sudo mount -t tmpfs -o mand,size=1m tmpfs ./dir
  3. touch ./dir/aa.txt
  4. chmod g+s,g-x dir/lockfile

设置ok后,先使用之前生成的file_lock去访问文件,
在这里插入图片描述
此时,在另外一个终端下,使用echo去同时访问这个文件,
在这里插入图片描述
可以看出直接访问文件是不允许的。


四 总结

本文主要讲述linux下使用C/C++代码给文件加锁,防止多个进程并发访问同一文件造成文件内容的不一致,写本文的过程中也参考了网上很多其它资料。详细内容也可以查阅Linux的man手册。

如果有写的不对的地方,希望能留言指正,谢谢阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值