flock,lockf,flockfile,funlockfile

flock和lockf

从底层的实现来说,Linux的文件锁主要有两种:flock和lockf。需要额外对lockf说明的是,它只是fcntl系统调用的一个封装。从使用角度讲,lockf或fcntl实现了更细粒度文件锁,即:记录锁。我们可以使用lockf或fcntl对文件的部分字节上锁,而flock只能对整个文件加锁。这两种文件锁是从历史上不同的标准中起源的,flock来自BSD而lockf来自POSIX,所以lockf或fcntl实现的锁在类型上又叫做POSIX锁。

除了这个区别外,fcntl系统调用还可以支持强制锁(Mandatory locking)。强制锁的概念是传统UNIX为了强制应用程序遵守锁规则而引入的一个概念,与之对应的概念就是建议锁(Advisory locking)。我们日常使用的基本都是建议锁,它并不强制生效。这里的不强制生效的意思是,如果某一个进程对一个文件持有一把锁之后,其他进程仍然可以直接对文件进行各种操作的,比如open、read、write。只有当多个进程在操作文件前都去检查和对相关锁进行锁操作的时候,文件锁的规则才会生效。这就是一般建议锁的行为。而强制性锁试图实现一套内核级的锁操作。当有进程对某个文件上锁之后,其他进程即使不在操作文件之前检查锁,也会在open、read或write等文件操作时发生错误。内核将对有锁的文件在任何情况下的锁规则都生效,这就是强制锁的行为。由此可以理解,如果内核想要支持强制锁,将需要在内核实现open、read、write等系统调用内部进行支持。

从应用的角度来说,Linux内核虽然号称具备了强制锁的能力,但其对强制性锁的实现是不可靠的,建议大家还是不要在Linux下使用强制锁。事实上,在我目前手头正在使用的Linux环境上,一个系统在mount -o mand分区的时候报错(archlinux kernel 4.5),而另一个系统虽然可以以强制锁方式mount上分区,但是功能实现却不完整,主要表现在只有在加锁后产生的子进程中open才会报错,如果直接write是没问题的,而且其他进程无论open还是read、write都没问题(Centos 7 kernel 3.10)。鉴于此,我们就不在此介绍如何在Linux环境中打开所谓的强制锁支持了。我们只需知道,在Linux环境下的应用程序,flock和lockf在是锁类型方面没有本质差别,他们都是建议锁,而非强制锁。

flock和lockf另外一个差别是它们实现锁的方式不同。这在应用的时候表现在flock的语义是针对文件的锁,而lockf是针对文件描述符(fd)的锁。我们用一个例子来观察这个区别:

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>
#include <wait.h>

#define PATH "/tmp/lock"

int main()
{
    int fd;
    pid_t pid;

    fd = open(PATH, O_RDWR|O_CREAT|O_TRUNC, 0644);
    if (fd < 0) {
        perror("open()");
        exit(1);
    }

    if (flock(fd, LOCK_EX) < 0) {
        perror("flock()");
        exit(1);
    }
    printf("%d: locked!\n", getpid());

    pid = fork();
    if (pid < 0) {
        perror("fork()");
        exit(1);
    }

    if (pid == 0) {
/*
        fd = open(PATH, O_RDWR|O_CREAT|O_TRUNC, 0644);
        if (fd < 0) {
                perror("open()");
                exit(1);
        }
*/
        if (flock(fd, LOCK_EX) < 0) {
            perror("flock()");
            exit(1);
        }
        printf("%d: locked!\n", getpid());
        exit(0);
    }
    wait(NULL);
    unlink(PATH);
    exit(0);
}

上面代码是一个flock的例子,其作用也很简单:

  1. 打开/tmp/lock文件。
  2. 使用flock对其加互斥锁。
  3. 打印“PID:locked!”表示加锁成功。
  4. 打开一个子进程,在子进程中使用flock对同一个文件加互斥锁。
  5. 子进程打印“PID:locked!”表示加锁成功。如果没加锁成功子进程会退出,不显示相关内容。
  6. 父进程回收子进程并退出。

这个程序直接编译执行的结果是:

 ./flock
23279: locked!
23280: locked!

父子进程都加锁成功了。这个结果似乎并不符合我们对文件加锁的本意。按照我们对互斥锁的理解,子进程对父进程已经加锁过的文件应该加锁失败才对。我们可以稍微修改一下上面程序让它达到预期效果,将子进程代码段中的注释取消掉重新编译即可:

...
/*
        fd = open(PATH, O_RDWR|O_CREAT|O_TRUNC, 0644);
        if (fd < 0) {
                perror("open()");
                exit(1);
        }
*/
...

将这段代码上下的/ /删除重新编译。之后执行的效果如下:

$ make flock
cc     flock.c   -o flock
$ ./flock
23437: locked!
  • 1
  • 2
  • 3
  • 4

此时子进程flock的时候会阻塞,让进程的执行一直停在这。这才是我们使用文件锁之后预期该有的效果。而相同的程序使用lockf却不会这样。这个原因在于flock和lockf的语义是不同的。使用lockf或fcntl的锁,在实现上关联到文件结构体,这样的实现导致锁不会在fork之后被子进程继承。而flock在实现上关联到的是文件描述符,这就意味着如果我们在进程中复制了一个文件描述符,那么使用flock对这个描述符加的锁也会在新复制出的描述符中继续引用。在进程fork的时候,新产生的子进程的描述符也是从父进程继承(复制)来的。在子进程刚开始执行的时候,父子进程的描述符关系实际上跟在一个进程中使用dup复制文件描述符的状态一样(参见《UNIX环境高级编程》8.3节的文件共享部分)。这就可能造成上述例子的情况,通过fork产生的多个进程,因为子进程的文件描述符是复制的父进程的文件描述符,所以导致父子进程同时持有对同一个文件的互斥锁,导致第一个例子中的子进程仍然可以加锁成功。这个文件共享的现象在子进程使用open重新打开文件之后就不再存在了,所以重新对同一文件open之后,子进程再使用flock进行加锁的时候会阻塞。另外要注意:除非文件描述符被标记了close-on-exec标记,flock锁和lockf锁都可以穿越exec,在当前进程变成另一个执行镜像之后仍然保留。

上面的例子中只演示了fork所产生的文件共享对flock互斥锁的影响,同样原因也会导致dup或dup2所产生的文件描述符对flock在一个进程内产生相同的影响。dup造成的锁问题一般只有在多线程情况下才会产生影响,所以应该避免在多线程场景下使用flock对文件加锁,而lockf/fcntl则没有这个问题。

为了对比flock的行为,我们在此列出使用lockf的相同例子,来演示一下它们的不同:

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>
#include <wait.h>

#define PATH "/tmp/lock"

int main()
{
    int fd;
    pid_t pid;

    fd = open(PATH, O_RDWR|O_CREAT|O_TRUNC, 0644);
    if (fd < 0) {
        perror("open()");
        exit(1);
    }

    if (lockf(fd, F_LOCK, 0) < 0) {
        perror("lockf()");
        exit(1);
    }
    printf("%d: locked!\n", getpid());

    pid = fork();
    if (pid < 0) {
        perror("fork()");
        exit(1);
    }

    if (pid == 0) {
/*
        fd = open(PATH, O_RDWR|O_CREAT|O_TRUNC, 0644);
        if (fd < 0) {
            perror("open()");
            exit(1);
        }
*/
        if (lockf(fd, F_LOCK, 0) < 0) {
            perror("lockf()");
            exit(1);
        }
        printf("%d: locked!\n", getpid());
        exit(0);
    }
    wait(NULL);
    unlink(PATH);
    exit(0);
}

编译执行的结果是:

  • 1
  • 2

在子进程不用open重新打开文件的情况下,进程执行仍然被阻塞在子进程lockf加锁的操作上。关于fcntl对文件实现记录锁的详细内容,大家可以参考《UNIX环境高级编程》中关于记录锁的14.3章节。

./lockf
27262: locked!

标准IO库文件锁(flockfile和funlockfile)

当你必学要锁住两个文件流是,总是在输出流之前锁住输入流

C语言的标准IO库中还提供了一套文件锁,它们的原型如下:
#include <stdio.h>

void flockfile(FILE *filehandle);
int ftrylockfile(FILE *filehandle);
void funlockfile(FILE *filehandle);
  • 1
  • 2
  • 3
  • 4
  • 5

从实现角度来说,stdio库中实现的文件锁与flock或lockf有本质区别。作为一种标准库,其实现的锁必然要考虑跨平台的特性,所以其结构都是在用户态的FILE结构体中实现的,而非内核中的数据结构来实现。这直接导致的结果就是,标准IO的锁在多进程环境中使用是有问题的。进程在fork的时候会复制一整套父进程的地址空间,这将导致子进程中的FILE结构与父进程完全一致。就是说,父进程如果加锁了,子进程也将持有这把锁,父进程没加锁,子进程由于地址空间跟父进程是独立的,所以也无法通过FILE结构体检查别的进程的用户态空间是否家了标准IO库提供的文件锁。这种限制导致这套文件锁只能处理一个进程中的多个线程之间共享的FILE 的进行文件操作。就是说,多个线程必须同时操作一个用fopen打开的FILE 变量,如果内部自己使用fopen重新打开文件,那么返回的FILE *地址不同,也起不到线程的互斥作用。

我们分别将两种使用线程的状态的例子分别列出来,第一种是线程之间共享同一个FILE *的情况,这种情况互斥是没问题的:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/file.h>
#include <wait.h>
#include <pthread.h>

#define COUNT 100
#define NUM 64
#define FILEPATH "/tmp/count"
static FILE *filep;

void *do_child(void *p)
{
    int fd;
    int ret, count;
    char buf[NUM];

    flockfile(filep);

    if (fseek(filep, 0L, SEEK_SET) == -1) {
        perror("fseek()");
    }
    ret = fread(buf, NUM, 1, filep);

    count = atoi(buf);
    ++count;
    sprintf(buf, "%d", count);
    if (fseek(filep, 0L, SEEK_SET) == -1) {
        perror("fseek()");
    }
    ret = fwrite(buf, strlen(buf), 1, filep);

    funlockfile(filep);

    return NULL;
}

int main()
{
    pthread_t tid[COUNT];
    int count;

    filep = fopen(FILEPATH, "r+");
    if (filep == NULL) {
        perror("fopen()");
        exit(1);
    }

    for (count=0;count<COUNT;count++) {
        if (pthread_create(tid+count, NULL, do_child, NULL) != 0) {
            perror("pthread_create()");
            exit(1);
        }
    }

    for (count=0;count<COUNT;count++) {
        if (pthread_join(tid[count], NULL) != 0) {
            perror("pthread_join()");
            exit(1);
        }
    }

    fclose(filep);

    exit(0);
}
另一种情况是每个线程都fopen重新打开一个描述符,此时线程是不能互斥的:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/file.h>
#include <wait.h>
#include <pthread.h>

#define COUNT 100
#define NUM 64
#define FILEPATH "/tmp/count"

void *do_child(void *p)
{
    int fd;
    int ret, count;
    char buf[NUM];
    FILE *filep;

    filep = fopen(FILEPATH, "r+");
    if (filep == NULL) {
        perror("fopen()");
        exit(1);
    }

    flockfile(filep);

    if (fseek(filep, 0L, SEEK_SET) == -1) {
        perror("fseek()");
    }
    ret = fread(buf, NUM, 1, filep);

    count = atoi(buf);
    ++count;
    sprintf(buf, "%d", count);
    if (fseek(filep, 0L, SEEK_SET) == -1) {
        perror("fseek()");
    }
    ret = fwrite(buf, strlen(buf), 1, filep);

    funlockfile(filep);

    fclose(filep);
    return NULL;
}

int main()
{
    pthread_t tid[COUNT];
    int count;

    for (count=0;count<COUNT;count++) {
        if (pthread_create(tid+count, NULL, do_child, NULL) != 0) {
            perror("pthread_create()");
            exit(1);
        }
    }

    for (count=0;count<COUNT;count++) {
        if (pthread_join(tid[count], NULL) != 0) {
            perror("pthread_join()");
            exit(1);
        }
    }

    exit(0);
}

文件锁相关命令

系统为我们提供了flock命令,可以方便我们在命令行和shell脚本中使用文件锁。需要注意的是,flock命令是使用flock系统调用实现的,所以在使用这个命令的时候请注意进程关系对文件锁的影响。flock命令的使用方法和在脚本编程中的使用可以参见我的另一篇文章《shell编程之常用技巧》中的bash并发编程和flock这部分内容,在此不在赘述。

我们还可以使用lslocks命令来查看当前系统中的文件锁使用情况。一个常见的现实如下:

lslocks
COMMAND           PID   TYPE  SIZE MODE  M      START        END PATH
firefox         16280  POSIX    0B WRITE 0          0          0 /home/zorro/.mozilla/firefox/bk2bfsto.default/.parentlock
dmeventd          344  POSIX    4B WRITE 0          0          0 /run/dmeventd.pid
gnome-shell       472  FLOCK    0B WRITE 0          0          0 /run/user/120/wayland-0.lock
flock           27452  FLOCK    0B WRITE 0          0          0 /tmp/lock
lvmetad           248  POSIX    4B WRITE 0          0          0 /run/lvmetad.pid

这其中,TYPE主要表示锁类型,就是上文我们描述的flock和lockf。lockf和fcntl实现的锁事POSIX类型。M表示是否事强制锁,0表示不是。如果是记录锁的话,START和END表示锁住文件的记录位置,0表示目前锁住的是整个文件。MODE主要用来表示锁的权限,实际上这也说明了锁的共享属性。在系统底层,互斥锁表示为WRITE,而共享锁表示为READ,如果这段出现*则表示有其他进程正在等待这个锁。其余参数可以参考man lslocks。

来自:https://www.cnblogs.com/wanghuaijun/p/7738788.html

 

原文地址:https://www.cnblogs.com/tianzeng/p/9310688.html

时间: 2024-10-28 23:40:34

flock,lockf,flockfile,funlockfile的相关文章

WEB页面,WEB环境版本,数据库,整站备份脚本

#!/bin/bash # #WEB页面,WEB环境版本,数据库,整站备份脚本 #当发生某个原因导致整个服务器无法恢复时,利用上面备份的相关数据即可重做一台一样的服务器 date_a=`date +%Y%m%d-%H%M%S` mkdir -p /web_bak/${date_a}/conf &> /dev/null mkdir -p /web_bak/${date_a}/web &> /dev/null mkdir -p /web_bak/${date_a}/mysql &a

Java精品高级课,架构课,java8新特性,P2P金融项目,程序设计,功能设计,数据库设计,第三方支付,web安全,视频教程

36套精品Java架构师,高并发,高性能,高可用,分布式,集群,电商,缓存,性能调优,设计模式,项目实战,P2P金融项目,大型分布式电商实战视频教程 视频课程包含: 高级Java架构师包含:Spring boot.Spring  cloud.Dubbo.Elasticsearch,Redis.ActiveMQ.Nginx.Mycat.Spring.MongoDB.ZeroMQ.Git.Nosql.Jvm.Mecached.Netty.Nio.Mina.java8新特性,P2P金融项目,程序设计,

输入password登录到主界面,录入学生编号,排序后输出

n 题目:输入password登录到主界面,录入学生编号,排序后输出 n 1.  语言和环境 A.实现语言 C语言 B.环境要求 VC++ 6.0 n 2.  要求 请编写一个C语言程序.将若干学生编号按字母顺序(由小到大)输出. 程序的功能要求例如以下: 1)  输入password"admin",正确则进入主界面,错误则直接推出(exit(0)): 2)从键盘输入5个学生编号"BJS1001","BJS2001"."BJS1011&

深度学习方法(十):卷积神经网络结构变化——Maxout Networks,Network In Network,Global Average Pooling

技术交流QQ群:433250724,欢迎对算法.技术感兴趣的同学加入. 最近接下来几篇博文会回到神经网络结构的讨论上来,前面我在"深度学习方法(五):卷积神经网络CNN经典模型整理Lenet,Alexnet,Googlenet,VGG,Deep Residual Learning"一文中介绍了经典的CNN网络结构模型,这些可以说已经是家喻户晓的网络结构,在那一文结尾,我提到"是时候动一动卷积计算的形式了",原因是很多工作证明了,在基本的CNN卷积计算模式之外,很多简

JAVAWEB开发之Session的追踪创建和销毁、JSP详解(指令,标签,内置对象,动作即转发和包含)、JavaBean及内省技术以及EL表达式获取内容的使用

Session的追踪技术 已知Session是利用cookie机制的服务器端技术,当客户端第一次访问资源时 如果调用request.getSession() 就会在服务器端创建一个由浏览器独享的session空间,并分配一个唯一且名称为JSESSIONID的cookie发送到浏览器端,如果浏览器没有禁用cookie的话,当浏览器再次访问项目中的Servlet程序时会将JSESSIONID带着,这时JSESSIONID就像唯一的一把钥匙  开启服务器端对应的session空间,进而获取到sessi

Delphi使用android的NDK是通过JNI接口,封装好了,不用自己写本地代码,直接调用

一.Android平台编程方式:      1.基于Android SDK进行开发的第三方应用都必须使用Java语言(Android的SDK基于Java实现)      2.自从ndk r5发布以后,已经允许完全用C/C++ 来开发应用或者游戏,而不再需要编写任何Java 的代码   Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C / C++之类的原生代码语言执行部分程序. 二.跨平台移动开发   Delphi使用android的NDK是通过JNI接口,封装好了,不用自己

python学习之内部执行流程,内部执行流程,编码(一)

python的执行流程: 加载内存--->词法分析--->语法分析--->编译--->转换字节码---->转换成机器码---->供给CPU调度 python的编码: 1. ASCII    2的256次表示. 2. UNICODE  最好16位表示2字节 3. UTF-8 能就8位表示就用8位表示,节省资源. python的注释: 字符 '#': 只注释一行 字符"  '''  ":注释多行 python的教程参数: 用 sys模块 的argv参数,

windows 10 smb,添加网络位置,输入的文件夹似乎无效

在windows 10中遇到一个现象,在"添加一个网络位置"的时候,弹出"输入的文件夹似乎无效.请选择另一个",我在这里是需要连接到Linux上的smb指定目录,经其它机子测试,smb服务器正常,输入的参数也正常.从smb服务器日志中也没有发现异常现象. 经过多次尝试,发现应该是在访问网络位置的时候,使用的用户及密码不对.由于登录了微软账户,默认使用微软用户进行访问的,所以导致无法访问. 解决方法: 1.改为添加"映射网络驱动器": 2.勾选&q

easymall项目的商品删除,前台商品分页,商品详情,购物车模块

简单的挑选一下昨天所学的重点知识模块,以备后续的复习 一.购物车模块1.1购物车两种实现的区别:!!!!!!!! 用session保存  缺点:浏览器关闭,session失效时保存在session中购物信息将会消失  后续优化,将购买的信息除了保存在session中以外,还要将购物的信息保存在cookie中,这样  就解决了浏览器关闭购买商品信息丢失的问题(但是解决不了跟换电脑信息丢失的问题)  优点:不用操作数据库,可以减少数据库访问压力 数据库中:  缺点:只用登录的用户才能添加购物车