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. 准备工作 因为本篇博客中涉及到用户切换,所以我需要提前准备好几个测试用户,方…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
