Linux文件—文件锁

Linux文件—文件锁通过之前的open()/close()/read()/write()/lseek()函数已经可以实现文件的打开、关闭、读写等基本操作,但是这些基本操作是不够的。对于文件的操作而言,“锁定”操作是对文件(尤其是对共享文件)的一种高级的文件操作。当某进程在更新文件内数据时,期望某种机制能防止多个进程同时更新文件从而导致数据丢失,或者防止文件内容在未更新完毕时被读取并引发后续问题,这种机制就是“文件锁”。

大家好,又见面了,我是你们的朋友全栈君。

通过之前的open()/close()/read()/write()/lseek()函数已经可以实现文件的打开、关闭、读写等基本操作,但是这些基本操作是不够的。

对于文件的操作而言,“锁定”操作是对文件(尤其是对共享文件)的一种高级的文件操作。当某进程在更新文件内数据时,期望某种机制能防止多个进程同时更新文件从而导致数据丢失,或者防止文件内容在未更新完毕时被读取并引发后续问题,这种机制就是“文件锁”。

对于共享文件而言,不同的进程对同一个文件进行同时读写操作将极有可能出现读写错误、数据乱码等情况。在Linux系统中,通常采用“文件锁”的方式,当某个进程独占资源的时候,该资源被锁定,其他进程无法访问,这样就解决了共享资源的竞争问题。

文件锁包括建议性锁(又名“协同锁”)和强制性锁两种。建议性锁要求每个相关进程访问文件的时候检查是否已经有锁存在并尊重当前的锁。一般情况下不建议使用建议性锁,因为无法保证每个进程都能自动检测是否有锁,Linux内核与系统总体上都坚持不使用建议性锁。而强制性锁是由内核指定的锁,当一个文件被加强制性锁的过程中,直至该所被释放之前,内核将阻止其他任何进程对该文件进行读或写操作,每次读或写操作都得检测锁是否存在。当然,采用强制性锁对内核的性能影响较大,每次内核在操作文件的时候都需要检查是否有强制性锁。

在Linux内核提供的系统调用中,实现文件上锁的函数有lockf()fcntl(),其中lockf()用于对文件加建议性锁,这里不再讲解。fcntl()函数既可以加建议性锁,也可以加强制性锁。同时,fcntl()还能对文件某部分上记录锁。所谓记录锁,其实就是字节范围锁,它能锁定文件内某个特定区域,当然也可锁定整个文件。

记录锁又分为读锁和写锁两种。其中读锁又称为共享锁,它用来防止进程读取的文件记录被更改。记录内可设置多个读锁,但当有一个读锁存在的时候就不能在该记录区域设置写锁。写锁又称为排斥锁,在任何时刻只能有一个程序对文件的记录加写锁,它用来保证文件记录被某一进程更新数据的时候不被其他进程干扰,确保文件数据的正确性,同时也避免其他进程“弄脏”数据。文件记录一旦被设置写锁,就不能再设置任何锁直至该写锁解锁。

函数fcntl()
需要头文件:#include< unistd.h>
#include< sys/types.h>
#include< fcntl.h>
函数原型:int fcntl(int fd,int cmd,struct flock *lock_set);
函数参数:fd:文件描述符
cmd:检测锁或设置锁
lock_set:结构体类型指针,结构体struct flock需要事先设置,与第二个参数连用
函数返回值:成功:0
失败:-1
第二个参数cmd表示该操作对文件的命令,若该命令是对文件检测锁或施加锁,则需要第三个参数:
F_GETLK:检测文件锁状态,检测结果存放在第三个参数的结构体的l_type内
F_SETLK:对文件进行锁操作,锁操作类型存放在第三个参数的结构体的l_type内
F_SETLKW:同F_SETLK,不过使用该参数时若不能对文件进行锁操作则会阻塞直至可以进行锁操作为止(W即wait,等待)
(更多参数请参阅fcntl()函数的使用手册)
第三个参数是对文件施加锁操作的相关参数设置的结构体
注意:必须定义struct flock类型结构体并初始化结构体内的数据,然后使用地址传递的方式传递参数,不允许直接定义struct flock* 类型指针直接传参

关于struct flock的成员如下:

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

结构体成员说明:
l_type:有三个参数
F_RDLCK:读锁(共享锁)
F_WRLCK:写锁(排斥锁)
F_UNLCK:无锁/解锁
l_whence:相对于偏移量的起点,参数等同于fseek()与lseek()中的whence参数
SEEK_SET:位置为文件开头位置
SEEK_CUR:位置为文件当前读写位置
SEEK_END:位置为文件结尾位置
l_start:加锁区域在文件中的相对位移量,与l_whence的值共同决定加锁区域的起始位置
l_len:加锁区域的长度,若为0则表示直至文件结尾EOF
l_pid:具有阻塞当前进程的锁,其持有的进程号会存放在l_pid中,仅由F_GETLK返回

示例:使用fcntl()函数对文件进行锁操作。
首先初始化结构体flock中的值,然后调用两次fcntl()函数。第一次参数设定为F_GETLK判断是否可以执行flock内所描述的锁操作;第二次参数设定为F_SETLK或F_SETLKW对该文件进行锁操作。
注意:需要至少两个终端运行该程序才能看到效果

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
int lock_set(int fd,int type)
{
    struct flock lock;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 0;//三个参数设置锁的范围是全文件
    lock.l_type = type;//type的参数由主调函数传参而来
    lock.l_pid = -1;

    //第一次操作,判断该文件是否可以上锁
    fcntl(fd,F_GETLK,&lock);
    if(lock.l_type!=F_UNLCK)//如果l_type得到的返回值不是F_UNLCK则证明不能加锁,需判断原因
    {
        if(lock.l_type==F_RDLCK)
        {
            printf("This is a ReadLock set by %d\n",lock.l_pid);
        }
        else if(lock.l_type==F_WRLCK)
        {
            printf("This is a WriteLock set by %d\n",lock.l_pid);
        }
    }

    //第二次操作,对文件进行相应锁操作
    lock.l_type = type;
    if((fcntl(fd,F_SETLKW,&lock))<0)
    {
        printf("Lock Failed:type = %d\n",lock.l_type);
        return -1;
    }
    switch(lock.l_type)
    {
        case F_RDLCK:
            printf("ReadLock set by %d\n",getpid());break;
        case F_WRLCK:
            printf("WriteLock set by %d\n",getpid());break;
        case F_UNLCK:
            printf("ReleaseLock by %d\n",getpid());
            return 1;
            break;
    }
    return 0;
}
int main(int argc, const char *argv[])
{
    int fd;
    if((fd=open("hello.txt",O_RDWR))<0)
    {

        perror("fail to open hello.txt");
        exit(0);
    }
    printf("This pid_no is %d\n",getpid());
    //给文件上锁
    lock_set(fd,F_WRLCK);
    printf("Press ENTER to continue...\n");
    getchar();
    //给文件解锁
    lock_set(fd,F_UNLCK);
    close(fd);
    return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/153484.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • android的timertask,Android 中 Timer 和 TimerTask的使用「建议收藏」

    Android中Timer是一个普通的类,其中有几个重要的方法;而TimerTask则是一个抽象类,其中含有一个抽象方法run()。使用Timer类中的schedule()方法可以完成对TimerTask的调度,该方法具有三个参数,其函数声明如下:publicvoidschedule(TimerTasktask,longdelay,longperiod)其中第一个参数为TimerTa…

    2022年4月18日
    41
  • 普通大一学生的自我反思[通俗易懂]

    普通大一学生的自我反思[通俗易懂]​ 暑假高考完,得知自己被计科(普通本科)录取,便开始在知乎等地方搜索相关知识,以此来提高自己的认识。​ 先是在b站上跟着比特鹏哥学完了c语言(基础),这里又要有一大段故事了,我家在江苏扬州,大概是9月份突然爆发了疫情,就被关在家里不让出去,(在家里坐牢,核酸检测,基本隔一天就要做一次)就在B站上天天看鹏哥学c语言,一开始有新鲜劲,学起来还是很有动力的,后来学到指针,很是痛苦,就又专门去B站听了其他的指针教程,学着学着慢慢就明白了,当时每天的生活节奏基本就是,起床打开电脑学习c语言,学饿了,吃点饭(由于疫

    2022年7月15日
    13
  • rsync @ERROR: auth failed on module backup 错误分析及解决[通俗易懂]

    rsync @ERROR: auth failed on module backup 错误分析及解决[通俗易懂]今天搭配rsync–daemon守护模式,服务搭配起来不是很复杂,比较简单,配置如下:首先检查#rpm–qarsync是否有安装rsync软件,如若没有的话,请执行yuminstall–yrsync;1.新建守护模式的配置文件:/etc/rsyncd.conf…

    2022年7月15日
    18
  • latex字母斜体加粗_latex加粗字体

    latex字母斜体加粗_latex加粗字体显示直立文本: \textup{文本}意大利斜体: \textit{文本}slanted斜体: \textsl{文本}显示小体大写文本:\textsc{文本}中等权重:

    2022年8月3日
    12
  • class文件与dex文件解析

    class文件与dex文件解析这篇笔记是我去年的时候创建的,结果放到草稿箱里给忘记了,大写的尴尬啊,所以急忙给补上了,此处鄙视一下自己!今天的正题——解析class文件和dex文件。

    2022年6月27日
    79
  • 使用 Laravel-Excel 进行 CSV/EXCEL 文件读写

    使用 Laravel-Excel 进行 CSV/EXCEL 文件读写

    2021年10月24日
    34

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号