linux文件I/O之 fcntl() 函数用法:设置文件的 flags、设置文件锁(记录锁)
头文件和函数声明
#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[]) {// 创建socketint 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 bufwhile (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-1if (is_errno)snprintf(ibuf + strlen(ibuf), BUF_SIZE - strlen(ibuf) - 1, ": %s", strerror(errno));strcat(ibuf, "\n");fflush(stdout); // 刷新stdoutfputs(ibuf, stderr); // 错误信息写到stderrfflush(NULL); // 参数为 NULL, fflush() 会刷新所有 stdiova_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系统编程手册》
相关文章:

linux文件I/O之 fcntl() 函数用法:设置文件的 flags、设置文件锁(记录锁)
头文件和函数声明 #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ ); 函数功能 获取、设置已打开文件的属性 返回值 成功时返回根据 cmd 传递的命令类型的执行结,失败时返回 -1,并设置 errno 为相…...
黑马项目一完结后阶段面试45题 JavaSE基础部分20题(一)
一、Java数据类型 基本数据类型——四类八种 整数型 byte short int long 浮点型 float double 字符型 char 布尔型 boolean 引用数据类型 String字符串 类(对象) 接口类型 数组类型 枚举类型 二、面向对象的三大特性 1.封装 把同一类事物…...

(一)创建型设计模式:3、建造者模式(Builder Pattern)
目录 1、建造者模式含义 2、建造者模式的讲解 3、使用C实现建造者模式的实例 4、建造者模式的优缺点 5、建造者模式VS工厂模式 1、建造者模式含义 The intent of the Builder design pattern is to separate the construction of a complex object from its representatio…...

指针进阶大冒险:解锁C语言中的奇妙世界!
目录 引言 第一阶段:🔍 独特的字符指针 什么是字符指针? 字符指针的用途 演示:使用字符指针拷贝字符串 字符指针与字符串常量 小试牛刀 第二阶段:🎯 玩转指针数组 指针数组是什么? 指针…...

2.0 Maven基础
1. Maven概述 Maven概念 Apache Maven是一个软件项目管理工具,将项目开发和管理过程抽象程一个项目对象模型(POM,Project Object Model)。 Maven作用 项目构建 提供标准的、跨平台的自动化项目构建方式。 依赖管理 方便快捷…...

在Linux虚拟机内配置nginx以及docker
目录 1、nginx源码包编译以及安装依赖 1、配置安装所需的编译环境 2、安装函数库(pcre、zlib、openssl) 2、安装nginx 1、获取源码包 2、解压编译 3、启动nginx服务 1、关闭防火墙 2、运行nginx 3、使用本地浏览器进行验证 3、安装docker 1、…...

数据结构-带头双向循环链表的实现
前言 带头双向循环链表是一种重要的数据结构,它的结构是很完美的,它弥补了单链表的许多不足,让我们一起来了解一下它是如何实现的吧! 1.节点的结构 它的节点中存储着数据和两个指针,一个指针_prev用来记录前一个节点…...

android Ndk Jni动态注册方式以及静态注册
目录 一.静态注册方式 二.动态注册方式 三.源代码 一.静态注册方式 1.项目名\app\src\main下新建一个jni目录 2.在jni目录下,再新建一个Android.mk文件 写入以下配置 LOCAL_PATH := $(call my-dir)//获取当前Android.mk所在目录 inclu...

MySQL中的索引
1.2.MySQL中的索引 InnoDB存储引擎支持以下几种常见的索引:B树索引、全文索引、哈希索引,其中比较关键的是B树索引 1.2.1.B树索引 InnoDB中的索引自然也是按照B树来组织的,前面我们说过B树的叶子节点用来放数据的,但是放什么数…...

idea中如何处理飘红提示
idea中如何处理飘红提示 在写sql时,总是会提示各种错误 查找资料,大部分都是说关提示,这里把错误提示选择为None即可 关掉以后,也确实不显示任何提示了,但总有一种掩耳盗铃的感觉 这个sms表明明存在,但是还…...
Elasticsearch使用中出现的错误
Elasticsearch使用中出现的错误 1、分页查询异常 在分页的过程中出现了一个问题是当查询的数据超过10000条的时候报了异常: from size must be less than or equal to: [10000]这个问题最快捷的解决方式是增大窗口大小: curl -XPUT http://127.0.0.…...

【IMX6ULL驱动开发学习】01.编写第一个hello驱动+自动创建设备节点(不涉及硬件操作)
目录 一、驱动程序编写流程 二、代码编写 2.1 驱动程序hello_drv.c 2.2 测试程序 2.3 编写驱动程序的Makefile 三、上机实验 3.1 NFS 挂载 3.2 测试示例 一、驱动程序编写流程 构造file_operations结构体 在里面填充open/read/write/ioctl成员 注册file_operations结…...

决策规划仿真平台搭建
决策规划仿真平台搭建 自动驾驶决策规划算法第二章第一节 决策规划仿真平台搭建 这部分的主要难点在于多个软件的连通与适配,环境的搭建总是折磨人的,主要是 4 个软件,各软件版本如下 Visual Studio2017PreScan8.5.0CarSim2019.0MATLAB2019b…...
计算图像哈希SHA-512
1、MATLAB实现 计算图像哈希值SHA-512,在文献[1]提到的算法如下: % Example Code: Create an MD5 crypto-hash of an arbitrary string, "str" % Main class of interest: System.Security.Cryptography.HashAlgorithm% Example String to hash with MD5 %…...

Android之消除APP图标的白色边框
有问题的效果: 解决方案: 第一步:app右键—>new—>Image Asset 第二步:上传Logo图标,选择每种分辨率,预览看效果,选择Resize,可以微调 第三步:点击 Nextÿ…...

java线程的优先级、守护线程的概念
1.线程的调度 抢占式调度 非抢占式调度 1.1 抢占式调度 优先级越高,抢到cpu的概率越高 1.2 守护线程 守护线程,非守护线程。当其他的非守护线程执行完毕以后,守护线程会陆续结束。 守护线程的应用场景...
asp.net core 6.0 efcore +sqlserver增删改查的demo
asp.net core 6.0 efcore sqlserver增删改查的demo 下面是一个使用ASP.NET Core 5.0和Entity Framework Core进行增删改查操作的示例。 首先,创建一个空的ASP.NET Core 6.0 Web应用程序项目。 然后,安装以下NuGet包: Microsoft.EntityFra…...

HC32L110B6芯片测试
到货之后,直观上感觉的确很小,小包装盒里面还装了说明书。 下载器单独在一个盒里面,但是这个T-U2T没用上,还是用的STLINK。 开发之前先去网上找了一些别人遇到的坑,的确不少。 涉及的方面也是挺全的,供电、…...
关于我乱删注册表导致电脑没有声音这件事
之前因为想彻底删除迅雷,照着网上进入注册表一顿乱删,也忘记删了啥,反正把一顿xmp的文件,和搜索出来迅雷的全删了。结果迅雷确实没了,被带走的还有电脑的声音。 很离谱,就试过了所有方法都没用,…...
Linux 命令 su 和 sudo 的区别
之前一直对 su 和 sudo 这两个命令犯迷糊,最近专门搜了这方面的资料,总算是把两者的关系以及用法搞清楚了,这篇文章来系统总结一下。 1. 准备工作 因为本篇博客中涉及到用户切换,所以我需要提前准备好几个测试用户,方…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...

R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...