linux文件I/O之 fcntl() 函数用法:设置文件的 flags、设置文件锁(记录锁)

news2025/1/14 0:53:54

头文件和函数声明

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

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

函数功能

获取、设置已打开文件的属性

返回值

成功时返回根据 cmd 传递的命令类型的执行结,失败时返回 -1,并设置 errno 为相对应的错误标志。

参数

fd:文件描述符

cmd:需要操作的命令类型(例如:F_GETFL、F_SETFL 等)

arg:表示要传递的参数,具体的含义和 cmd 传递的命令类型有关

1. 用于获取、设置文件的 flags

  • cmd = F_GETFL 时,获取打开文件的 flags(即调用 open() 函数打开文件,传递的 flags 参数)。
  • cmd = F_SETFL 时,设置打开文件的 flags。

在这种情况下,fcntl 函数的原型如下所示:

int fcntl(int fd, int cmd);          // 在获取打开文件的 flags 时

int fcntl(int fd, int cmd,  int flags);         // 在设置打开文件的 flags 时

例子:对于一个 socket,创建时默认是阻塞I/O,将它设置为非阻塞(O_NONBLOCK),又设置回阻塞状态。

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

void err_quit(const char *msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}
void close_fd(const int fd, char is_exit) { // is_exit: 0 (false),1(true)
    if (-1 == close(fd)) {
        perror("close error");
        
        if (is_exit)
            exit(EXIT_FAILURE);
    }
}

int main(int argc, char *argv[]) {
    // 创建socket
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == fd)
        err_quit("socket error");

    printf("socket success, fd = %d\n", fd);

    int flags = fcntl(fd, F_GETFL);
    if (-1 == flags) {
        close_fd(fd, 0);
        err_quit("fcntl error");
    }
    printf("before set O_NONBLOCK, flags = %d\n", flags);

    flags |= O_NONBLOCK;
    if (-1 == fcntl(fd, F_SETFL, flags)) {
        close_fd(fd, 0);
        err_quit("fcntl error");
    }
    printf("after set O_NONBLOCK, flags = %d\n", flags);

    // 设置回阻塞状态
    flags &= ~O_NONBLOCK;
    if (-1 == fcntl(fd, F_SETFL, flags)) {
        close_fd(fd, 0);
        err_quit("fcntl error");
    }
    printf("after set ~O_NONBLOCK, flags = %d\n", flags);

    close_fd(fd, 1);

    return 0;
}

 2. 设置文件锁

        在 linux 操作系统中,当多个进程同时操作同一个文件时,该文件的最后状态取决于写该文件的最后一个进程。但是对于有些应用程序,如数据库,进程有时需要确保它在一个时刻只能被一个进程/线程写,这时候就要用到文件锁。文件锁也被称为记录锁(record lock)

        文件锁的作用:当一个进程正在读写文件的某一区域时,其他进程就不能对文件的这个区域进行修改操作。

        提供文件锁操作的函数有两个:flock() 和 fcntl(),其中,flock() 函数是对文件锁操作的早期版本,它只能对整个文件加锁,不能对文件中的某一个区域加锁。fcntl() 函数是在 flock() 函数的基础上构造出来的,它允许对文件中任意字节区域加锁,短至一个字节,长至整个文件

在这种情况下,fcntl 函数的原型如下所示:

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

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_pid:持有锁的进程ID。(仅由于 cmd = F_GETLK 时返回)
  • l_len:区域字节长度。
  • l_start:参数 l_start 的含义跟参数 l_whence 的值有关

    l_whence = SEEK_SET 时,则将文件的偏移量设置为文件开始处 l_start 个字节,参数 l_start 必须为非负数。
    l_whence = SEEK_CUR 时,则将文件的偏移量设置为 当前文件的偏移量 + l_start 个字节,参数 l_start 可以为正数,也可以为负数,只要最终得到的文件偏移量不会小于文件的起始位置(字节0)即可。
    l_whence = SEEK_END 时,则将文件的偏移量设置为 文件长度 + l_start 个字节,参数 l_start 可以为正数,也可以为负数,只要最终得到的文件偏移量不会小于文件的起始位置(字节0)即可。

  • l_start、l_whence 和 l_len 这三个参数一起指定了待加锁的字节范围。如果 l_len = 0,则表示锁的范围会被扩展到最大的可能偏移量。这意味着对从由 l_start 和 l_whence 确定的起始位置开始,不管向该文件中追加写了多少数据,它们都处于锁的范围内。

  • 为了对整个文件加锁,可以设置 l_start = 0, l_whence = SEEK_SET, l_len = 0;

  • 锁可以在当前文件尾端处开始或越过文件尾端处开始,但不能在文件起始位置之前开始。

  • 一般来说,应用程序应该只对所需的最小字节范围进行加锁,这样其他进程就能够同时对同一个文件的不同区域进行加锁,进而取得更大的并发性。

(1)cmd = F_GETLK 时,检测能否获取 lock 指针指定的文件区域的锁(lock指针指向的结构中的 l_type 字段的值必须是 F_RDLCK 或 F_WRLCK)。如果允许加锁(即在指定的文件区域上不存在不兼容的锁),那么 l_type 字段会返回 F_UNLCK,剩余的字段保持不变。如果在指定的文件区域上存在不兼容的锁(即不能加锁),那么 lock 会返回不兼容的锁的所有相关信息(如果有多把不兼容的锁的话,会返回其中一把不兼容的锁的所有相关信息,但不确定会返回哪一把) 。

(2)cmd = F_SETLK 时,给 fd 引用的文件加锁(lock 指针指定的文件区域的锁)。如果另一个进程持有了一把待加锁的区域中任意部分上的不兼容的锁,fcntl() 就会失败并返回 -1,并设置 errno 为 EAGAIN,有的系统也可能设置 errno 为 EACCES。

(3)cmd = F_SETLKW 时,这个命令参数是 F_SETLK 命令参数的阻塞版本(命令名中的 W 表示等待 wait)。如果另一个进程持有了一把待加锁的区域中任意部分上的不兼容的锁,那么调用进程会设置为休眠。如果请求创建的锁已经可用,或者休眠由信号中断,该进程将会被唤醒。

需要注意的是,用 F_GETLK 测试能否建立一把锁,然后用 F_SETLK 或 F_SETLKW 企图建立那把锁,这两者不是一个原子操作,因此不能保证在这两次 fcntl() 调用之间会不会有另一个进程插入并建立了一把相同的锁。如果不希望在等待锁变成可用时产生阻塞,就必须处理 F_SETLK 返回的可能出错。

例子

以下程序功能:用 fcntl() 函数对一个对文件进行读写时,先加锁,为了测试需要,读写完后不解锁,需要输入 u 命令进行解锁。

#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/stat.h>

#define BUF_SIZE 4096

/*
is_errno: 是否要打印系统的出错信息,0:否 1:是
is_exit: 打印出错信息后,是否立马终止进程,0:否 1:是
*/
void err_msg(const char is_errno, const char is_exit, const char *format, ...);
void close_fd(const int fd, char is_exit);
void cmd_r(const int fd, char* const ibuf, const char *filename);
void cmd_w(const int fd, char* const ibuf, const char *filename);

int main(int argc, char *argv[])
{
    if (argc != 2)
        err_msg(0, 1, "%s filename", argv[0]);

    const char *filename = argv[1];
    int fd = open(argv[1], O_RDWR | O_APPEND);
    if (fd == -1)
        err_msg(1, 1, "open error");
    
    printf("open %s success\n", filename);

    char ibuf[BUF_SIZE] = ""; // stdin buf
    
    while (1) {
        printf("please input r or w or quit\n", filename);
        fgets(ibuf, BUF_SIZE, stdin); // 从 stdin 中读入一行
        if (ibuf[strlen(ibuf) - 1] == '\n') 
            ibuf[strlen(ibuf) - 1] = 0;

        if (strcmp(ibuf, "r") == 0) {
            cmd_r(fd, ibuf, filename);
        } else if (strcmp(ibuf, "w") == 0) {
            cmd_w(fd, ibuf, filename);
        } else if (strcmp(ibuf, "quit") == 0) {
            break;
        }
    }

    close_fd(fd, 1);

    return 0;
}

void err_msg(const char is_errno, const char is_exit, const char *format, ...) {
    char ibuf[BUF_SIZE] = "";
    va_list ap;
    va_start(ap, format);
    vsnprintf(ibuf, BUF_SIZE - 1, format, ap); // 因为最后一个字节放换行符'\n',所以 BUF_SIZE-1
    if (is_errno)
        snprintf(ibuf + strlen(ibuf), BUF_SIZE - strlen(ibuf) - 1, ": %s", strerror(errno));
    strcat(ibuf, "\n");
    fflush(stdout); // 刷新stdout
    fputs(ibuf, stderr); // 错误信息写到stderr
    fflush(NULL); // 参数为 NULL, fflush() 会刷新所有 stdio
    va_end(ap);

    if (is_exit)
        exit(EXIT_FAILURE);
}

void close_fd(const int fd, char is_exit) { // is_exit: 0 (false),1(true)
    if (-1 == close(fd)) {
        perror("close error");
        
        if (is_exit)
            exit(EXIT_FAILURE);
    }
}

void cmd_r(const int fd, char* const ibuf, const char *filename)
{
    struct flock lock;
    /* 对整个文件加锁 */
    lock.l_start = 0;
    lock.l_whence = SEEK_SET;
    lock.l_len = 0; 
    lock.l_type = F_RDLCK;
    int res = fcntl(fd, F_GETLK, &lock);
    printf("res = %d\n", res);
    if (lock.l_type == F_UNLCK) {
        lock.l_type = F_RDLCK;
        if (fcntl(fd, F_SETLK, &lock) == -1) {
            err_msg(0, 0, "lock %s fail, please try again later", filename);
        } else {
            lseek(fd, 0, SEEK_SET); // 将文件偏移量设置到文件开始处

            printf("******* 文件内容 *******\n");
            ssize_t nread = 0;
            char rbuf[BUF_SIZE] = "";
            while ((nread = read(fd, rbuf, BUF_SIZE)) > 0) {
                if (write(STDOUT_FILENO, rbuf, nread) != nread)
                    err_msg(1, 1, "write error");
            }
            printf("******* 文件内容 *******\n");
            
            while (1)
            {
                printf("%s 已经被该进程共享写锁锁住,请输入 u 进行解锁\n", filename);
                fgets(ibuf, BUF_SIZE, stdin);
                if (ibuf[strlen(ibuf) - 1] == '\n')
                    ibuf[strlen(ibuf) - 1] = 0;
                if (strcmp(ibuf, "u") == 0) {
                    lock.l_type = F_UNLCK;
                    if (fcntl(fd, F_SETLK, &lock) == -1) {
                        err_msg(1, 0, "unlock %s fail, please try again later.", filename);
                    } else {
                        break;
                    }
                }
            }
        }
    } else {
        err_msg(0, 0, "getlock %s fail, please try again later", filename);
    }
}

void cmd_w(const int fd, char* const ibuf, const char *filename) {
    struct flock lock;
    /* 对整个文件加锁 */
    lock.l_start = 0;
    lock.l_whence = SEEK_SET;
    lock.l_len = 0; 
    lock.l_type = F_WRLCK;
    int res = fcntl(fd, F_GETLK, &lock);
    printf("res = %d\n", res);
    if (lock.l_type == F_UNLCK) {
        lock.l_type = F_WRLCK;
        if (fcntl(fd, F_SETLK, &lock) == -1) {
            err_msg(0, 0, "lock %s fail, please try again later", filename);
        } else {
            lseek(fd, 0, SEEK_END); // 将文件偏移量设置到文件末尾
            
            char buf[] = "wrlck\n";
            size_t len = strlen(buf);
            if (write(fd, buf, len) != len)
                err_msg(1, 1, "write error");
            
            buf[strlen(buf) - 1] = 0;
            printf("%s 已写入文件\n", buf);
            
            while (1)
            {
                printf("%s 已经被该进程独占写锁锁住,请输入 u 进行解锁\n", filename);
                fgets(ibuf, BUF_SIZE, stdin);
                if (ibuf[strlen(ibuf) - 1] == '\n')
                    ibuf[strlen(ibuf) - 1] = 0;
                if (strcmp(ibuf, "u") == 0) {
                    lock.l_type = F_UNLCK;
                    if (fcntl(fd, F_SETLK, &lock) == -1) {
                        err_msg(1, 0, "unlock %s fail, please try again later.", filename);
                    } else {
                        break;
                    }
                }
            }
        }
    } else {
        err_msg(0, 0, "getlock %s fail, please try again later", filename);
    }
}

同时开2个进程测试

(1)共享读锁(进程1读取后,未解锁,进程2请求读取,成功,说明读时共享)

 

(2)独占性写锁(进程1写后,未解锁,进程2请求写,请求读都失败,说明写时独占)

 (3)在文件被多个进程上了共享读锁后,需要对这个文件进行读时的所有进程都释放了这把读锁后,请求上写锁才会成功。

总结

         利用 fcntl() 函数获取、设置文件的 flags 和 设置文件锁是在现实开发中比较常用到的功能,fcntl() 函数功能强大,简单一言两语比较难比较全面的对这个函数的功能进行解析,篇幅较长,还有一些其他的功能放在后面的另一篇博文进行更新吧。

参考:

《UNIX环境高级编程》(第3版)

《Linux-UNIX系统编程手册》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/869742.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

山西电力市场日前价格预测【2023-08-12】

日前价格预测 预测明日&#xff08;2023-08-12&#xff09;山西电力市场全天平均日前电价为330.52元/MWh。其中&#xff0c;最高日前电价为387.00元/MWh&#xff0c;预计出现在19: 45。最低日前电价为278.05元/MWh&#xff0c;预计出现在13: 00。 价差方向预测 1&#xff1a; 实…

【Bert101】最先进的 NLP 模型解释【02/4】

0 什么是伯特&#xff1f; BERT是来自【Bidirectional Encoder Representations from Transformers】变压器的双向编码器表示的缩写&#xff0c;是用于自然语言处理的机器学习&#xff08;ML&#xff09;模型。它由Google AI Language的研究人员于2018年开发&#xff0c;可作为…

最强自动化测试框架Playwright(9)- 下载文件

对于页面下载的每个附件&#xff0c;都会发出 page.on&#xff08;“download”&#xff09; 事件。 下载开始后&#xff0c;将发出下载事件。下载完成后&#xff0c;下载路径将变为可用 所有这些附件都下载到一个临时文件夹中。可以使用事件中的下载对象获取下载 URL、文件系…

BClinux8.6 制作openssh9.2p2 rpm升级包和升级实战

一、背景说明 BClinux8.6 安装的openssh 版本为9.3p1&#xff0c;经绿盟扫描&#xff0c;存在高危漏洞&#xff0c;需要升级到最新。 OpenSSH 命令注入漏洞(CVE-2020-15778) OpenSSH 安全漏洞(CVE-2023-38408) 目前官网只提供编译安装包&#xff0c;而BClinux8.6 为rpm方…

上市公司绿色发展专题:重污染行业企业名单与绿色创新数据

数据简介&#xff1a;上市公司&#xff0c;尤其是重污染行业上市公司实现绿色发展&#xff0c;广泛开展绿色创新&#xff0c;是我国高质量发展的必然要求&#xff0c;受到了来自学界与各级ZF的诸多关注。现有研究中对上市公司绿色发展问题的研究发现&#xff0c;重污染行业上市…

剑指offer14-I.剪绳子

昨天写的那道题是数组中除了一个元素外其余元素的乘积&#xff0c;这道题自然就想到了把一个数分成两个的和&#xff0c;然后积就是这两个数的积&#xff0c;而这两个数中的每个数又可以分成两个数&#xff0c;所以可以用动态规划的方法&#xff0c;dp[i] dp[j]*dp[i-j]。但是…

ChatGPT应用在律师行业需谨慎,南非有律师被它的幻觉误导了!

ChatGPT自去年以来大受欢迎&#xff0c;没想到它这么快会出现在法庭上。 最近&#xff0c;南非约翰内斯堡地区法院审理一个案件时&#xff0c;有律师因为使用ChatGPT生成的虚假参考资料而受到指责。[1] 根据《星期日泰晤士报》的报道&#xff0c;法院判决认为&#xff0c;该名…

pgsql checkpoint机制(1)

检查点触发时机 检查点间隔时间由checkpoint_timeout设置pg_xlog中wall段文件总大小超过参数max_WAL_size的值postgresql服务器在smart或fast模式下关闭手动checkpoint 为什么需要检查点&#xff1f; 定期保持修改过的数据块作为实例恢复时起始位置&#xff08;问题&#xf…

6.利用matlab完成 符号矩阵的秩和 符号方阵的逆矩阵和行列式 (matlab程序)

1.简述 利用M文件建立矩阵 对于比较大且比较复杂的矩阵&#xff0c;可以为它专门建立一个M文件。下面通过一个简单例子来说明如何利用M文件创建矩阵。 例2-2 利用M文件建立MYMAT矩阵。(1) 启动有关编辑程序或MATLAB文本编辑器&#xff0c;并输入待建矩阵&#xff1a;(2) 把…

Python爬虫——requests_cookie登陆古诗文网

寻找登陆需要的参数 __VIEWSTATE:aiMG0UXAfCzak10C7436ZC/RXoZbM2lDlX1iU/4wjjdUNsW8QUs6W2/3M6XIKagQZrC7ooD8Upj8uCnpQMXjDAp6fS/NM2nGhnKO0KOSXfT3jGHhJAOBouMI3QnlpJCQKPXfVDJPYwh169MGLFC6trY __VIEWSTATEGENERATOR: C93BE1AE from: http://so.gushiwen.cn/user/collect.…

springboot异步任务

在Service类声明一个注解Async作为异步方法的标识 package com.qf.sping09test.service;import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service;Service public class AsyncService {//告诉spring这是一个异步的方法Asyncp…

Day 28 C++ (映射)map 容器 / multimap 容器 (多重映射)

文章目录 map (映射)定义注意优点 map构造和赋值构造赋值示例 map大小和交换函数原型示例 map插入和删除函数原型四种插入方式示例 map查找和统计函数原型示例 map容器排序 multimap 容器 (多重映射)定义特点和map的区别示例 map (映射) 定义 C中的map是一种关联容器&#xf…

Windows - UWP - 为UWP应用创建桌面快捷方式

Windows - UWP - 为UWP应用创建桌面快捷方式 前言 这是一个较为简单的方式&#xff0c;不需要过多的命令行。 How 首先Win R -> shell:AppsFolder -> 回车&#xff0c; 这将显示电脑上的已安装应用&#xff08;Win32 & UWP&#xff09;&#xff1a; 找到想要创建…

uniapp使用阿里矢量库

然后解压复制全部到你的项目文件 最后只要这几个 然后引入 最后在你需要的页面使用

多线程的同步与互斥

文章目录 线程安全问题多线程互斥互斥量mutex互斥锁的使用理解锁加锁如何做到原子性对mutex做封装 可重入与线程安全死锁 线程同步条件变量条件变量函数接口理解条件变量条件变量的使用 线程安全问题 首先来看一段代码&#xff0c;该代码是一个多线程抢票的逻辑 #include<…

go的gin和gorm框架实现切换身份的接口

使用go的gin和gorm框架实现切换身份的接口&#xff0c;接收前端发送的JSON对象&#xff0c;查询数据库并更新&#xff0c;返回前端信息 接收前端发来的JSON对象&#xff0c;包含由openid和登陆状态组成的一个string和要切换的身份码int型 后端接收后判断要切换的身份是否低于该…

排列数字 (dfs)

希望这篇题解对你有用&#xff0c;麻烦动动手指点个赞或关注&#xff0c;感谢您的关注~ 不清楚蓝桥杯考什么的点点下方&#x1f447; 考点秘籍 想背纯享模版的伙伴们点点下方&#x1f447; 蓝桥杯省一你一定不能错过的模板大全(第一期) 蓝桥杯省一你一定不能错过的模板大全…

Dubbo2-概述

Dubbo 阿里公司开源的一个高性能&#xff0c;轻量级的javaRPC&#xff08;远程服务调用方案&#xff09;框架&#xff0c;提供高性能远程调用方案以及SOA服务治理方案 Dubbo架构 节点角色说明&#xff1a; Provider:服务提供方 Container:服务运行容器 Consumer:调用远程服务…

中科亿海微RAM使用

引言 FPGA&#xff08;Field Programmable Gate Array&#xff0c;现场可编程门阵列&#xff09;是一种可编程逻辑设备&#xff0c;能够根据特定应用的需求进行配置和重新编程。在FPGA中&#xff0c;RAM&#xff08;Random Access Memory&#xff0c;随机存取存储器&#xff09…

HTML详解连载(3)

HTML详解连载&#xff08;3&#xff09; 专栏链接 [link](http://t.csdn.cn/xF0H3)下面进行专栏介绍 开始喽表单作用使用场景 input标签基本使用示例type属性值以及说明 input标签占位文本示例注意 单选框 radio代码示例 多选框-checkbox注意代码示例 文本域作用标签&#xff1…