unix高级编程系列之文件I/O
背景
作为linux 开发者,我们不可避免会接触到文件编程。比如通过文件记录程序配置参数,通过字符设备与外设进行通信。因此作为合格的linux开发者,一定要熟练掌握文件编程。在文件编程中,我们一般会有两类接口函数:标准I/O(带缓冲)和POXIS.1 I/O(不带缓冲)。本章节主要介绍不带缓冲的相关API及注意事项。
open 接口
open
函数的作用是打开或创建一个文件。其声明如下:
int open(const char* path, int oflag, .../*mode_t mode*/);
参数解析:
- path :是打开或需要创建的文件名称;
- oflag : 设置打开文件的权限,该参数取值范围较广。并且需要区分。大致可以分为两类:
- 权限类型标识。需要关注的有O_RDONLY(只读权限)、O_WRONLY(只写权限)、O_RDWR(可读写权限)。这三个标识位必须指定一个且只能指定一个。(O_ECEC(只执行)和O_SEARCH(只搜索)已被移除)。
- 特性类标识。这些标识可多选,常见的有如下:
标识常量 | 含义 |
---|---|
O_APPEND | 每次写都追加到文件的尾端。即使你显式的调用lseek改变文件当前偏移量,但是在write 时,依然会追加到文件末尾 |
O_CREAT | 若文件不存在则创建它。 |
O_EXCL | 如果同时指定了O_CREAT,而文件已经存在,则出错。经常用于判断文件是否存在,与access() 函数功能类似。 |
O_NOBLOCK | 如果path引用的是一个FIFO、块特殊文件、字符特殊文件。那么本文件描述符后续的I/O操作,都设置为非阻塞方式。 |
O_SYNC | 每次write 都会等待物理I/O操作完成,包括文件属性更新。 在linux ext4 系统中,该标识可能不生效 |
O_TRUNC | 如果文件存在,且以只写或读写权限打开。则将长度截断为0。常见的业务场景就是更新配置文件。 |
- mode 可选参数。只有当oflag参数中,具备O_CREAT属性时,才需要指定新创建的文件权限。
知识点: open 函数返回的文件描述符一定时最小的未用描述符数值。
基于上述知识点,经常会被用来重定向程序的标准输入、标准输出或标准错误输出。
场景如下:有一个封装库内部是通过采用的是printf
进行日志打印,无法体现在我们日志系统中。我们如何观察其日志输出呢?常见做法如下:
#include<stdlib.h>
#include<stdio.h>
int main()
{ printf("hello world\n");return 0;
}
默认情况下,日志输出到终端:
xieyihua@xieyihua:~/test$ gcc 1.c -o 1
xieyihua@xieyihua:~/test$ ./1
hello world
xieyihua@xieyihua:~/test$
可以做以下修改:
#include<stdlib.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{close(1); /* 关闭 文件描述符1'*/open("./log",O_WRONLY|O_CREAT,0755);/* 此时文件描述符1 与 ./log文件绑定*/printf("hello world\n");return 0;
}
输出如下:
xieyihua@xieyihua:~/test$ gcc 1.c -o 1
xieyihua@xieyihua:~/test$ ./1
xieyihua@xieyihua:~/test$ cat log
hello world
xieyihua@xieyihua:~/test$
creat 接口
creat
函数主要用于创建一个新文件。函数原型声明如下:
#include <fcntl.h>
int creat(const char* path, mode_t mode);
其等效于:open(path,O_WRONLY|O_CREAT|O_TRUNC,mode);
但是由于creat
只能以只写方式打开文件,使用场景便较少,渐渐被‘冷落’了。
close 接口
close
函数关闭一个打开文件。其函数原型声明如下:
#include<unistd.h>
int close(int fd);
知识点: 当一个进程终止时,内核会自动关闭它所有的打开文件。很多程序都利用了这一功能而不显示调用
close
。
lseek 接口
每个打开文件都有一个与其相关的“当前文件偏移量”。它通常是一个非负整数(/dev/kmem/
支持负的偏移量),用于度量从文件开始处计算的字节数。通常读、写操作都是从当前文件偏移处开始的,并使偏移量增加所读写的字节数。
lseek
接口就可以显式的为一个打开的文件设置偏移量。其函数原型声明如下:
#include <unistd.h>
off_t lseek(int fd,off_t offset,int whence);
- fd, 文件描述符
offset
,其含义与whence
的值相关。
- 若
whence
是SEEK_SET
,则将该文件的偏移量设置为距文件开始处的offset
个字节。 - 若
whence
是SEEK_CUR
,则将该文件的偏移量设置为当前值加上offset
个字节,offset
可为正或负。 - 若
whence
是SEEK_END
,则将该文件的偏移量设置为文件长度加上offset
个字节,offset
可为正或负。
知识点: 系统默认情况下,当打开一个文件时,除非指定
O_APPEND
选项,否则该偏移量被设置为0。
lseek 仅是将当前的文件偏移量记录在内核中,并不引起任何I/O操作,其目的是用于接下来的读写操作。
空洞文件
文件偏移量可以被设置为大于文件当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构成一个空洞。位于文件中没有写过的字节都被读为0。并且文件中的空洞并不要求在磁盘上占用存储区。示例代码如下:
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>int main()
{int fd;char* buf1 = "123456789a";char* buf2 = "abcdefghij";if((fd = creat("file.hole",0755)) < 0){printf("creat error\n");return -1;}printf("fd = %d\n",fd);if(write(fd,buf1,10) != 10){printf("write buf1 error\n");}if(lseek(fd,16384,SEEK_SET) == -1){printf("lseek error\n");}if(write(fd,buf2,10) != 10){printf("write buf1 error\n");}return 0;
}
结果如下:
/*文件长度有16394 Byte*/
xieyihua@xieyihua:~/test$ ls -la file.hole
-rwxr-xr-x 1 xieyihua xieyihua 16394 Jul 4 01:58 file.hole/*文本的实际内容也只有两个字符串*/
xieyihua@xieyihua:~/test$ od -c file.hole
0000000 1 2 3 4 5 6 7 8 9 a \0 \0 \0 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0010000 a b c d e f g h i j
0010012
xieyihua@xieyihua:~/test$ cat file.hole
123456789aabcdefghijxieyihua@xieyihua:~/test$/* 没有空洞的文件其占用了16个磁盘块*/
xieyihua@xieyihua:~/test$ ls -ls file.*8 -rwxr-xr-x 1 xieyihua xieyihua 16394 Jul 4 01:58 file.hole
16 -rwxr-xr-x 1 xieyihua xieyihua 16384 Jul 4 01:58 file.nohole
文件空洞的特性:分配了文件偏移量范围,但是实际却没有分配磁盘空间。我们一般可在两个方向应用:
- 多线程下载。当创建一个巨大的文件时,单个线程逐步构建文件会耗费大量时间。一种优化思路是将文件划分为多个段,利用多线程同时操作,每个线程负责写入其中一段数据。这类似于现实生活中修路的场景,如修建高速公路时,单个施工队的进度可能较慢,但通过安排多个施工队,每个队负责修建一段,最终将它们连接起来,大大提高了效率。
- 共享内存。当不同进程需要共享内存时,并不清楚实际需要多大的文件,可以先开辟一个大文件。比如:在创建虚拟机时,如果一开始就分配了100GB的磁盘空间,而实际上系统安装完成后可能只使用了3、4GB的空间,这就是空洞文件的应用。通过空洞文件,可以避免一开始就分配过多的资源,节约了存储空间的浪费。
read 接口
read
接口用于向打开文件中读数据。其原型声明如下:
#include<unistd.h>
ssize_t read(int fd, void* bff, size_t nbyte);
若read
成功,则返回读到的字节数,如果已经达到文件的尾端,则返回0。
知识点: 大多数文件系统为改善性能都会采用某种预读技术,即即使你每次仅读取100Byte内容,但是实际上会从磁盘中读取一页数据,保存在内存中。从而减少磁盘I/O操作,提高系统性能。但是也会增加内存使用压力。
write 接口
write
接口用于向打开文件写数据。其接口声明如下:
#include <unistd.h>
ssize_t write(int fd, const void* buf,size_t nbytes);
其返回值通常与参数nbytes的值相同,否则标识出错。其出错原因:
- 磁盘已写满
- 超过文件限定长度
linux 内核标识打开文件的方式
linux 内核通过三个数据结构表示打开的文件。记录项、文件表项、V节点。其三者关系大致如下:
- 进程表项中,记录中文件描述符与文件表项的关系;
- 文件表项中,记录文件状态标志位、当前文件偏移量、V节点指针;
- V节点中,记录文件类型、各种操作函数指针、指向i节点。而i节点包含文件的详细信息,比如:文件的所有者、文件长度、指向文件实际数据块再磁盘上所在位置的指针等。
注意:每一个文件只有一个唯一的V节点表;多个文件表项可以指向同一个V节点表,每调用一次open
,则创建一个新的文件表项;不同fd可以指向同一个文件表项;即可能存在以下场景:
正是这样的机制原理,linux 让我们可以多任务同时访问同一文件。但是在写文件时,我们需要关注写入时序以及数据错乱问题。
- 在完成每一个
read
或write
操作后,文件表项中的当前文件偏移量增加所读写的字节数。 - 如果使用O_APPEND标志打开一个文件,则响应标志也被设置到文件表项的文件状态标志中。每次进行写操作时,文件表项中的当前文件偏移量首先会被设置为表项中的文件长度。这就确保每次写入的数据追加到当前尾端处。
- lseek 函数只是修改文件表项中的当前文件偏移量,不进行任何I/O操作。
- 每一个进程都有它自己的文件表项和进程表项。
dup和dup2
这两个接口用于复制一个现有的文件描述符。其接口声明如下:
#include<unistd.h>int dup(int fd);
int dup2(int fd, int fd2);
/*两函数的返回值:若成功,返回新的文件描述符;若出错,返回-1*/
dup
返回新的文件描述符一定是当前可用文件描述符中的最小数值。其效果就是多个fd指向同一个文件表项。其关系与上图中多线程访问文件一致。
sync、fsync、fdatasync接口
传统的linux 系统中设备缓冲区高速缓存或页高速缓存,大多数磁盘I/O都通过缓冲区进行。当我们向文件写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,晚些时候在写入磁盘。这种方式称为“延迟写”。
“延迟写”虽然提高了write的响应速度(不需要等待数据经过IO,写入磁盘)。但是也带来了风险:当应用层认为已经将数据写入文件了,但实际数据还并没有落入磁盘。若此时系统出现异常,则会将这部分数据丢失。为了避免这种情况,linux 系统提供了sync
、fsync
、fdatasync
接口,应用层主动要求内核将缓冲区中的数据进行落盘。原型声明如下:
#include<unistd.h>
int fsync(int fd);
int fdatasync(int fd);void sync(void);
- sync 只是将所有修改过的块缓冲区排入写队列,然后就返回。它并不等待实际写磁盘操作结束。
- fsync 函数只对文件描述符fd指定的文件起作用,并等待写磁盘操作结束才返回。
- fdatasync 函数类似于 fsync,但只影响文件的数据部分。
注: open 接口中有一个标识 O_SYNC含义标识同步写,但经过验证,似乎并不起作用,与预期不一致。建议为了保险起见,还是调用fsync接口。
总结
文件编程是Linux开发者必须掌握的技能。本文介绍了Linux文件编程中常用的API及其注意事项,包括open、creat、close、lseek、read、write、dup和dup2等。还介绍了sync、fsync和fdatasync等接口,用于确保数据安全。此外,文章还解释了Linux内核如何标识打开的文件,以及文件表项、V节点和进程表项之间的关系。希望能给您带来帮助。
若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途
参考文章:https://applink.feishu.cn/client/message/link/open?token=AmX27V1AQAADZjdT9KRAgAQ%3D
相关文章:

unix高级编程系列之文件I/O
背景 作为linux 开发者,我们不可避免会接触到文件编程。比如通过文件记录程序配置参数,通过字符设备与外设进行通信。因此作为合格的linux开发者,一定要熟练掌握文件编程。在文件编程中,我们一般会有两类接口函数:标准…...
PySide(PyQt),记录最后一次访问文件的路径
1、在同目录下用文本编辑器创建JSON文件,命名为setting.json,并输入以下内容后保存: { "setting": { "last_file": [ "" ] } } 2、应用脚本: import json …...

wordpress企业网站模板免费下载
大气上档次的wordpress企业模板,可以直接免费下载,连注册都不需要,网盘就可以直接下载,是不是嘎嘎给力呢 演示 https://www.jianzhanpress.com/?p5857 下载 链接: https://pan.baidu.com/s/1et7uMYd6--NJEWx-srMG1Q 提取码:…...

[leetcode hot 150]第一百一十七题,填充每个节点的下一个右侧节点
题目: 给定一个二叉树: struct Node {int val;Node *left;Node *right;Node *next; } 填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL 。 初始状态下&#x…...
Docker 入门篇(十 一)-- 网络配置总结
Docker 容器技术的核心优势之一是其轻量级的虚拟化和隔离性,而 Docker 网络则是实现容器间以及容器与外界通信的关键。以下是对 Docker 网络的关键知识点的总结。 一、 Docker 网络概述 Docker 网络允许容器进行相互通信以及与外部网络的连接。Docker 提供了多种网…...
【Android面试八股文】Android 有哪些存储数据的方式?
在Android平台上,有多种方式可以存储数据,每种方式都适合不同类型的数据和使用场景。以下是主要的存储数据方式: SharedPreferences(轻量级数据存储): SharedPreferences是用于存储简单键值对数据的最简单方法,适合存储用户偏好设置、配置信息等。数据以XML文件形式存储…...
3. train_encoder_decoder.py
train_encoder_decoder.py #__future__ 模块提供了一种方式,允许开发者在当前版本的 Python 中使用即将在将来版本中成为标准的功能和语法特性。此处为了确保代码同时兼容Python 2和Python 3版本中的print函数 from __future__ import print_function # 导入标准库…...

Hyper-V克隆虚拟机教程分享!
方法1. 使用导出导入功能克隆Hyper-V虚拟机 导出和导入是Hyper-V服务器备份和克隆的一种比较有效的方法。使用此功能,您可以创建Hyper-V虚拟机模板,其中包括软件、VM CPU、RAM和其他设备的配置,这有助于在Hyper-V中快速部署多个虚拟机。 在…...

QDockWidget类详解
一.QDockWidget类概述 1.QDockWidget类 QDockWidget类提供了一个特殊的窗口部件,它可以是被锁在QMainWindow窗口内部或者是作为顶级窗口悬浮在桌面上。 QDockWidget类提供了dock widget的概念,dock widget也就是我们熟悉的工具面板或者是工具窗口。Do…...

vue3.0(十六)axios详解以及完整封装方法
文章目录 axios简介1. promise2. axios特性3. 安装4. 请求方法5. 请求方法别名6. 浏览器支持情况7. 并发请求 Axios的config的配置信息1.浏览器控制台相关的请求信息:2.配置方法3.默认配置4.配置的优先级5.axios请求响应结果 Axios的拦截器1.请求拦截2.响应拦截3.移…...

Python用于处理 DNS 查询库之Dnspython 使用详解
概要 Dnspython 是一个开源的 Python 库,专门用于处理 DNS 查询。它被设计为既简单易用又功能强大,可以满足从简单到复杂的各种 DNS 相关需求。无论是进行基础的 DNS 查询还是进行高级的 DNS 服务器管理,dnspython 都能提供相应的功能。 这个库支持包括 A、AAAA、MX、TXT …...
Django ORM 中过滤 JSON 数据
简介 首先,我们假设您有一个名为 MyModel 的 Django 模型,它包含一个 JSONField 类型的字段,名为 data。这个 data 字段可以存储各种 JSON 格式的数据。 过滤 JSON 字段中的键值对 您可以使用双下划线 __ 语法来访问 JSON 字段中的嵌套键值对。例如: # 过滤 data 字段中 &qu…...

深入探索C语言中的结构体:定义、特性与应用
🔥 个人主页:大耳朵土土垚 目录 结构体的介绍结构体定义结构成员的类型结构体变量的定义和初始化结构体成员的访问结构体传参 结构体的介绍 在C语言中,结构体是一种用户自定义的数据类型,它允许开发者将不同类型的变量组合在一起…...
EDEM-FLUENT耦合报错几大原因总结(持续更新)
写在前面,本篇内容主要是来源于自己做仿真时的个人总结,以及付费请教专业老师。每个人由于工况不一样,所以报错原因千奇百怪,不能一概而论,本篇内容主要是为本专栏读者在报错时提供大致的纠错方向,从而达到少走弯路的效果,debug的过程需要大家一点点试算。问题解答在文 …...

ctfshow sql注入 web234--web241
web234 $sql "update ctfshow_user set pass {$password} where username {$username};";这里被过滤了,所以我们用\转义使得变为普通字符 $sql "update ctfshow_user set pass \ where username {$username};";那么这里的话 pass\ where…...

Python的招聘数据分析与可视化管理系统-计算机毕业设计源码55218
摘要 随着互联网的迅速发展,招聘数据在规模和复杂性上呈现爆炸式增长,对数据的深入分析和有效可视化成为招聘决策和招聘管理的重要手段。本论文旨在构建一个基于Python的招聘数据分析与可视化管理系统。 该平台以主流招聘平台为数据源,利用Py…...

使用ChatGPT写学术论文的技巧和最佳实践指南
大家好,感谢关注。我是七哥,一个在高校里不务正业,折腾学术科研AI实操的学术人。关于使用ChatGPT等AI学术科研的相关问题可以和作者七哥(yida985)交流,多多交流,相互成就,共同进步&a…...
多模态图像引导手术导航进展
**摘要:**对多模态图像分割建模、手术方案决策、手术空间位姿标定与跟踪、多模态图像配准、图像融合与显示等多模态图像引导手术导航的关键技术进行总结和分析,提出其进一步发展面临的挑战并展望其未来发展趋势。 **外科手术的发展历程:**从最…...
小程序 全局数据共享 getApp()
在小程序中,可以通过 getApp() 方法获取到小程序全局唯一的App实例 因此在App() 方法中添加全局共享的数据、方法,从而实现页面、组件的数据传值 在 app.js 文件中定义 App({// 全局共享的数据globalData:{token:},// 全局共享的方法setToken(token){//…...
第一次面试的经历(java开发实习生)
面试官的问题 我想问一下你这边有做过什么项目吗?你方便讲一下你做过的那些项目吗,用了什么技术栈,包括你负责开发的内容是什么?(项目经验)八大基本数据类型是什么?(基础)你说一下…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...
书籍“之“字形打印矩阵(8)0609
题目 给定一个矩阵matrix,按照"之"字形的方式打印这个矩阵,例如: 1 2 3 4 5 6 7 8 9 10 11 12 ”之“字形打印的结果为:1,…...

6.9-QT模拟计算器
源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...

Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程
鸿蒙电脑版操作系统来了,很多小伙伴想体验鸿蒙电脑版操作系统,可惜,鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机,来体验大家心心念念的鸿蒙系统啦!注意:虚拟…...

网页端 js 读取发票里的二维码信息(图片和PDF格式)
起因 为了实现在报销流程中,发票不能重用的限制,发票上传后,希望能读出发票号,并记录发票号已用,下次不再可用于报销。 基于上面的需求,研究了OCR 的方式和读PDF的方式,实际是可行的ÿ…...