当前位置: 首页 > news >正文

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 : 设置打开文件的权限,该参数取值范围较广。并且需要区分。大致可以分为两类:
  1. 权限类型标识。需要关注的有O_RDONLY(只读权限)、O_WRONLY(只写权限)、O_RDWR(可读写权限)。这三个标识位必须指定一个且只能指定一个。(O_ECEC(只执行)和O_SEARCH(只搜索)已被移除)。
  2. 特性类标识。这些标识可多选,常见的有如下:
标识常量含义
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 的值相关。
  1. whenceSEEK_SET,则将该文件的偏移量设置为距文件开始处的offset个字节。
  2. whenceSEEK_CUR,则将该文件的偏移量设置为当前值加上offset个字节,offset可为正或负。
  3. whenceSEEK_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

文件空洞的特性:分配了文件偏移量范围,但是实际却没有分配磁盘空间。我们一般可在两个方向应用:

  1. 多线程下载。当创建一个巨大的文件时,单个线程逐步构建文件会耗费大量时间。一种优化思路是将文件划分为多个段,利用多线程同时操作,每个线程负责写入其中一段数据。这类似于现实生活中修路的场景,如修建高速公路时,单个施工队的进度可能较慢,但通过安排多个施工队,每个队负责修建一段,最终将它们连接起来,大大提高了效率。
  2. 共享内存。当不同进程需要共享内存时,并不清楚实际需要多大的文件,可以先开辟一个大文件。比如:在创建虚拟机时,如果一开始就分配了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的值相同,否则标识出错。其出错原因:

  1. 磁盘已写满
  2. 超过文件限定长度

linux 内核标识打开文件的方式

linux 内核通过三个数据结构表示打开的文件。记录项、文件表项、V节点。其三者关系大致如下:

  • 进程表项中,记录中文件描述符文件表项的关系
  • 文件表项中,记录文件状态标志位当前文件偏移量V节点指针
  • V节点中,记录文件类型各种操作函数指针指向i节点。而i节点包含文件的详细信息,比如:文件的所有者、文件长度、指向文件实际数据块再磁盘上所在位置的指针等。

注意:每一个文件只有一个唯一的V节点表;多个文件表项可以指向同一个V节点表,每调用一次open,则创建一个新的文件表项;不同fd可以指向同一个文件表项;即可能存在以下场景:

正是这样的机制原理,linux 让我们可以多任务同时访问同一文件。但是在写文件时,我们需要关注写入时序以及数据错乱问题。

  • 在完成每一个readwrite操作后,文件表项中的当前文件偏移量增加所读写的字节数。
  • 如果使用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 系统提供了syncfsyncfdatasync接口,应用层主动要求内核将缓冲区中的数据进行落盘。原型声明如下:

#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 开发者&#xff0c;我们不可避免会接触到文件编程。比如通过文件记录程序配置参数&#xff0c;通过字符设备与外设进行通信。因此作为合格的linux开发者&#xff0c;一定要熟练掌握文件编程。在文件编程中&#xff0c;我们一般会有两类接口函数&#xff1a;标准…...

PySide(PyQt),记录最后一次访问文件的路径

1、在同目录下用文本编辑器创建JSON文件&#xff0c;命名为setting.json&#xff0c;并输入以下内容后保存&#xff1a; { "setting": { "last_file": [ "" ] } } 2、应用脚本&#xff1a; import json …...

wordpress企业网站模板免费下载

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

[leetcode hot 150]第一百一十七题,填充每个节点的下一个右侧节点

题目&#xff1a; 给定一个二叉树&#xff1a; struct Node {int val;Node *left;Node *right;Node *next; } 填充它的每个 next 指针&#xff0c;让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点&#xff0c;则将 next 指针设置为 NULL 。 初始状态下&#x…...

Docker 入门篇(十 一)-- 网络配置总结

Docker 容器技术的核心优势之一是其轻量级的虚拟化和隔离性&#xff0c;而 Docker 网络则是实现容器间以及容器与外界通信的关键。以下是对 Docker 网络的关键知识点的总结。 一、 Docker 网络概述 Docker 网络允许容器进行相互通信以及与外部网络的连接。Docker 提供了多种网…...

【Android面试八股文】Android 有哪些存储数据的方式?

在Android平台上,有多种方式可以存储数据,每种方式都适合不同类型的数据和使用场景。以下是主要的存储数据方式: SharedPreferences(轻量级数据存储): SharedPreferences是用于存储简单键值对数据的最简单方法,适合存储用户偏好设置、配置信息等。数据以XML文件形式存储…...

3. train_encoder_decoder.py

train_encoder_decoder.py #__future__ 模块提供了一种方式&#xff0c;允许开发者在当前版本的 Python 中使用即将在将来版本中成为标准的功能和语法特性。此处为了确保代码同时兼容Python 2和Python 3版本中的print函数 from __future__ import print_function # 导入标准库…...

Hyper-V克隆虚拟机教程分享!

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

QDockWidget类详解

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

vue3.0(十六)axios详解以及完整封装方法

文章目录 axios简介1. promise2. axios特性3. 安装4. 请求方法5. 请求方法别名6. 浏览器支持情况7. 并发请求 Axios的config的配置信息1.浏览器控制台相关的请求信息&#xff1a;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语言中的结构体:定义、特性与应用

&#x1f525; 个人主页&#xff1a;大耳朵土土垚 目录 结构体的介绍结构体定义结构成员的类型结构体变量的定义和初始化结构体成员的访问结构体传参 结构体的介绍 在C语言中&#xff0c;结构体是一种用户自定义的数据类型&#xff0c;它允许开发者将不同类型的变量组合在一起…...

EDEM-FLUENT耦合报错几大原因总结(持续更新)

写在前面,本篇内容主要是来源于自己做仿真时的个人总结,以及付费请教专业老师。每个人由于工况不一样,所以报错原因千奇百怪,不能一概而论,本篇内容主要是为本专栏读者在报错时提供大致的纠错方向,从而达到少走弯路的效果,debug的过程需要大家一点点试算。问题解答在文 …...

ctfshow sql注入 web234--web241

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

Python的招聘数据分析与可视化管理系统-计算机毕业设计源码55218

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

使用ChatGPT写学术论文的技巧和最佳实践指南

大家好&#xff0c;感谢关注。我是七哥&#xff0c;一个在高校里不务正业&#xff0c;折腾学术科研AI实操的学术人。关于使用ChatGPT等AI学术科研的相关问题可以和作者七哥&#xff08;yida985&#xff09;交流&#xff0c;多多交流&#xff0c;相互成就&#xff0c;共同进步&a…...

多模态图像引导手术导航进展

**摘要&#xff1a;**对多模态图像分割建模、手术方案决策、手术空间位姿标定与跟踪、多模态图像配准、图像融合与显示等多模态图像引导手术导航的关键技术进行总结和分析&#xff0c;提出其进一步发展面临的挑战并展望其未来发展趋势。 **外科手术的发展历程&#xff1a;**从最…...

小程序 全局数据共享 getApp()

在小程序中&#xff0c;可以通过 getApp() 方法获取到小程序全局唯一的App实例 因此在App() 方法中添加全局共享的数据、方法&#xff0c;从而实现页面、组件的数据传值 在 app.js 文件中定义 App({// 全局共享的数据globalData:{token:},// 全局共享的方法setToken(token){//…...

第一次面试的经历(java开发实习生)

面试官的问题 我想问一下你这边有做过什么项目吗?你方便讲一下你做过的那些项目吗&#xff0c;用了什么技术栈&#xff0c;包括你负责开发的内容是什么&#xff1f;&#xff08;项目经验&#xff09;八大基本数据类型是什么&#xff1f;&#xff08;基础&#xff09;你说一下…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

倒装芯片凸点成型工艺

UBM&#xff08;Under Bump Metallization&#xff09;与Bump&#xff08;焊球&#xff09;形成工艺流程。我们可以将整张流程图分为三大阶段来理解&#xff1a; &#x1f527; 一、UBM&#xff08;Under Bump Metallization&#xff09;工艺流程&#xff08;黄色区域&#xff…...

对象回调初步研究

_OBJECT_TYPE结构分析 在介绍什么是对象回调前&#xff0c;首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例&#xff0c;用_OBJECT_TYPE这个结构来解析它&#xff0c;0x80处就是今天要介绍的回调链表&#xff0c;但是先不着急&#xff0c;先把目光…...