3. 深入探究文件 IO

news2024/11/15 22:21:41

3. 深入探究文件 IO

  • 1. Linux 系统如何管理文件
    • 1.1 静态文件与inode
    • 1.2 文件打开时的状态
  • 2. 返回错误处理与errno
    • 2.1 strerror 函数
    • 2.2 perror 函数
  • 3. 空洞文件
  • 4. O_APPEND 和 O_TRUNC
  • 5. 多次打开同一个文件
  • 6. 复制文件描述符
    • 6.1 dup
    • 6.2 dup2
  • 7. 文件共享
    • 7.1 同一个进程中多次调用 open 函数打开同一个文件
    • 7.2 不同进程分别使用 open 函数打开同一个文件
    • 7.3 同一个进程中对文件描述符进行复制
  • 8. 原子操作与竞争冒险
    • 8.1 O_APPEND
    • 8.2 pread 和 pwrite
    • 8.3 创建一个文件
  • 9. fcntl 和 ioctl
    • 9.1 fcntl
    • 9.2 ioctl
  • 10. 截断文件

1. Linux 系统如何管理文件

1.1 静态文件与inode

文件没有被打开的情况下一般都是存放在磁盘中的,并且以一种固定的形式进行存放,这时称为静态文件。文件存储在硬盘上,硬盘的最小存储单元叫做扇区,每个扇区大小是 512 字节, 相当于 0.5k。操作系统读取硬盘的时候不会一个个扇区的读取,而是一次读取多个扇区,也就是一个块, 通常是 4k,也就是 8 个扇区。将磁盘进行分区格式化的时候会分为数据区和 inode 区,inode 区存放 inode 表,该表中存放着 inode 节点,每个节点都是一个结构体,包含着对应文件的属性信息。所以查找文件时,先根据文件名找到对应的 inode 编号,然后找到对应的表,最后查找相关信息,读取数据。
在这里插入图片描述

1.2 文件打开时的状态

调用 open 打开文件的时候,内核会申请一段内存,将数据读取到内存中进行管理,也就是动态文件。对动态文件进行读写操作时,和静态文件不会同步,数据的同步由内核完成,内核会在之后将内存这份动态文件同步到磁盘中。静态文件有多个块,一个块有多个扇区,一个扇区有多个字节,所以对静态文件操作时需要反复读写块,而内存可以直接一个字节一个字节的改动,所以速率较快。
在 Linux 系统中,内核会有一个专门的数据结构管理一个进程,叫做 PCB。该结构体中有一个指针指向了文件描述符表,而文件描述符表中的每一个元素对应文件表,文件表存放着文件的相关信息。

2. 返回错误处理与errno

当发生错误时,操作系统会将错误对应的编号赋值给 errno 变量,每个进程都有一个自己的 errno 全局变量

2.1 strerror 函数

#include <string.h>
char *strerror(int errnum);	// 参数就是对应的errno,返回对应错误编号的字符串描述信息

2.2 perror 函数

#include <stdio.h>
void perror(const char *s);	// 参数可以传递自己想要的信息,然后错误描述就会打印在s之后

3. 空洞文件

lseek 函数还允许文件偏移量超出文件长度,也就是文件末尾还可以向后偏移。比如一个文件只有 4096k,此时在文件头部向后偏移 6000 字节,然后在这里写入数据,那么 4096 ~ 6000 这部分就是空洞。文件空洞部分不会占用任何物理空间,但是空洞文件形成时,逻辑上该文件的大小是包含了空洞部分的大小的。 文件空洞在多线程共同操作文件时有很大作用,可以将文件分段,不同线程在不同空洞部分写入数据。

4. O_APPEND 和 O_TRUNC

O_TRUNC 会将文件原本的内容清除,然后再写入数据,而O_APPEND 是在文件末尾写入数据。

5. 多次打开同一个文件

一个进程内多次打开一个文件,那么会得到多个不同的文件描述符,同理在关闭的时候需要依次关闭对应的文件描述符。而且在内存中不会存在多份动态文件,不同文件描述符对应的读写位置偏移量是相互独立的。因为位置偏移量是相互独立的,所以对不同的文件描述符读写时,是分别进行读写。使用 open 函数打开文件时,默认是覆盖式写入,也就是说当分别进行写入操作时,后续写入数据时会先将文件清空再写入。 不同的文件描述符就对应不同的文件表,而位置偏移量就保存在文件表中,但是文件表中的 inode 指针指向的都是同一个 inode。
同样,多个不同的进程打开同一个文件,在内存中也只是维护一份动态文件,多个进程间共享,有各自独立的文件读写位置偏移量。当文件的引用计数为 0 时,系统会自动关闭文件。

6. 复制文件描述符

在 Linux 系统中,open 得到的文件描述符可以进行复制,新的文件描述符也可以对旧文件描述符指向的文件进行操作,拥有相同的权限。但是新的文件描述符和旧的文件描述符指向的文件表是同一个

6.1 dup

#include <unistd.h>
int dup(int oldfd);	// 成功返回由系统分配的新的文件描述符,失败返回-1
#include <stdio.h>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;

int main()
{
    int fd1=open("./text.txt",O_CREAT|O_RDWR,0777);
    int fd2=dup(fd1);
    write(fd1,"hello ",6);
    write(fd2,"world!",6);
    return 0;
}

在这里插入图片描述

6.2 dup2

#include <unistd.h>
int dup2(int oldfd,int newfd);	// 成功返回newfd,失败返回-1
int main()
{
    int fd1=open("./text.txt",O_CREAT|O_RDWR,0777);
    int fd2=dup2(fd1,9);	// 可以指定文件描述符
    write(fd1,"hello ",6);
    write(fd2,"world!",6);
    cout << fd2<<endl;
    return 0;
}

在这里插入图片描述

7. 文件共享

7.1 同一个进程中多次调用 open 函数打开同一个文件

在这里插入图片描述
会得到同一个文件的不同文件描述符,并且多个文件描述符对应多个不同的文件表,所有的文件表指向同一个 inode 节点

7.2 不同进程分别使用 open 函数打开同一个文件

在这里插入图片描述

7.3 同一个进程中对文件描述符进行复制

在这里插入图片描述

8. 原子操作与竞争冒险

当两个独立的进程对同一个文件进行操作时,因为此时文件是共享的,如果当一个进程的操作未完成时,另一个进程就对文件进行操作就会发生竞争冒险。所以就有了原子操作。原子操作是指一个任务要么不做,要么做完。
O_APPEND、pread() 和 pwrite()、创建文件都是可以实验原子操作的。

8.1 O_APPEND

两个进程都向文件中写入数据,后一个进程会覆盖前一个进程写入的内容,就需要使用该标志

8.2 pread 和 pwrite

这两个函数可传入一个位置偏移量,用于指定文件当前读写的位置偏移量。但是不更新文件表中当前位置偏移量,就是说在当前位置 0 调用这两个函数时如果设置 offset 为1024,然后再调用 lseek 获取当前位置,发现依旧是 0

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

8.3 创建一个文件

如果两个进程都会创建同一个文件,就需要使用 O_EXCL,如果要打开的文件已经存在,就 open 失败,如果不存在,就创建这个文件

9. fcntl 和 ioctl

9.1 fcntl

fcntl 函数可以对一个已经打开的文件描述符执行一系列控制操作

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd,.../* arg */);
/* cmd:操作命令,表示我们将对fd进行什么操作
 * F_DUPFD  或  F_DUPFD_CLOEXEC	:复制文件描述符
 * F_GETFD  或  F_SETFD			:获取/设置文件描述符标志
 * F_GETFL  或  F_SETFL			:获取/设置文件状态标志
 * F_GETOWN 或  F_SETOWN		:获取/设置异步IO所有权
 * F_GETLK  或  F_SETLK			:获取/设置记录锁
 * /
// 第三个参数根据cmd来传入对应的实参
// 返回值是失败返回-1,成功根据cmd有不同的返回值

复制文件描述符

int main()
{
	int fd1=open("./test.txt",O_CREAT|O_RDONLY,0777);
	int fd2=fcntl(fd1,F_DUPFD,0);// 如果传入的第三个参数已经被使用,就返回一个比0大的可使用的文件描述符,否则就返回第三个参数
	cout << fd2 << endl;
	return 0;
}

获取/设置文件状态标志

int main()
{
	int fd1=open("./test.txt",O_CREAT|O_RDWR,0777);
	int flag=fcntl(fd,F_GETFL);
	int fd2=fcntl(fd1,F_SETFL,flag|O_APPEND);
	cout << fd2 << endl;
	return 0;
}

F_GETFL 成功时返回状态标志,F_SETFL 的第三个参数表示需要设置的状态标志。但是文件权限标志(O_RDONLY、 O_WRONLY、 O_RDWR)以及文件创建标志(O_CREAT、O_EXCL、 O_NOCTTY、 O_TRUNC)不能被设置,只有 O_APPEND、 O_ASYNC、O_DIRECT、 O_NOATIME 以及 O_NONBLOCK 这些标志可以被修改

9.2 ioctl

可以认为是文件 IO 操作的杂物箱,一般用于操作特殊文件或硬件外设,比如获取 LCD 相关信息等,这里只是介绍以下

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request,...);
// request 表示向文件描述符请求相应的操作,第三个可变参数根据request设置

10. 截断文件

使用系统调用 truncate() 或 ftruncate()可将普通文件截断为指定字节长度

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);// 使用前必须open,并且有可写权限

先创建两个文件,这时文件大小都是0字节
在这里插入图片描述
向文件中插入数据,改变文件大小

int main()
{
    int fd1=open("./file1",O_RDWR);
    char buffer1[4096]={0};
    char buffer2[2048]={0};
    write(fd1,buffer1,sizeof(buffer1));
    write(open("./file2",O_RDWR),buffer2,sizeof(buffer2));
    return 0;
}

在这里插入图片描述

截断文件,发现文件大小改变了

int main()
{
    int fd1=open("./file1",O_RDWR);
    char buffer1[4096]={0};
    char buffer2[2048]={0};
    write(fd1,buffer1,sizeof(buffer1));
    write(open("./file2",O_RDWR),buffer2,sizeof(buffer2));
    ftruncate(fd1,1024);
    truncate("./file2",2048);
    return 0;
}

在这里插入图片描述

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

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

相关文章

接口测试场景:怎么实现登录之后,需要进行昵称修改?

在接口测试中有一个这样的场景&#xff1a;登录之后&#xff0c;需要进行昵称修改&#xff0c;怎么实现&#xff1f; 首先我们分别看下登录、昵称修改的接口说明&#xff1a; 以上业务中补充一点&#xff0c;昵称修改&#xff0c;还需要添加请求头Authorization传登录获取的to…

Depends 下载

查看某个应用程序和动态库的依赖 属性查看是只支持WIN32 的&#xff0c;查看X64的动态库电脑会卡死 左边框可以查看动态库的依赖&#xff0c;右边 可以查看动态库的导出情况 链接&#xff1a;https://pan.baidu.com/s/1vUFrOuzTO_dfvvkHP0-UiQ 提取码&#xff1a;i09s

2. 流程控制|方法|数组|二维数组|递归

文章目录 流程控制代码块选择结构循环结构跳转控制关键字 方法方法的概述方法的重载Junit单元测试初识全限定类名 Debug 小技巧数组数组的基本概念数组的基本使用数组的声明数组的初始化 JVM内存模型什么是引用数据类型基本数据类型和引用数据类型的区别堆和栈中内容的区别 数组…

yolo系列中的一些评价指标说明

文章目录 一. 混淆矩阵二. 准确度(Accuracy)三. 精确度(Precision)四. 召回率(Recall)五. F1-score六. P-R曲线七. AP八. mAP九. mAP0.5十. mAP[0.5:0.95] 一. 混淆矩阵 TP (True positives)&#xff1a;被正确地划分为正例的个数&#xff0c;即实际为正例且被分类器划分为正例…

ModuleNotFoundError: No module named ‘torch_sparse‘

1、卸载 先把torch-geometric、torch-sparse、torch-scatter、torch-cluster、 torch-spline-conv全部卸载了 pip uninstall torch-geometric torch-scatter torch-sparse torch-cluster torch-spline-conv 2.conda list确定PyTorch的版本&#xff0c;我的是1.10 3、确定下载地…

《斯坦福数据挖掘教程·第三版》读书笔记(英文版)Chapter 3 Finding Similar Items

来源&#xff1a;《斯坦福数据挖掘教程第三版》对应的公开英文书和PPT It is therefore a pleasant surprise to learn of a family of techniques called locality-sensitive hashing, or LSH, that allows us to focus on pairs that are likely to be similar, without hav…

使用bard分析视频内容

11月21日的bard update 更新了分析视频的功能&#xff0c;使用方法如下&#xff1a; 1、打开bard网站。https://bard.google.com/ 2、点击插件。 3、点击YouTube中的 research a topic 选项。 4、输入需要分析的内容&#xff1a; Please analyze how many technologies are in…

五种多目标优化算法(MOGWO、MOLPB、MOJS、NSGA3、MOPSO)求解微电网多目标优化调度(MATLAB代码)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标灰狼优化算法MOGWO 多目标应用&#xff1a;基于多目标灰狼优化算法MOGWO求解微电网多目标优化调度&#xff08;MATLAB代码&#xff09;-CSDN博客 &#xff08;2&#xff09;多目标学习者行为优化算法MOLPB 多目标学习…

win10+ vs2017用cmake编译geos3.5.1

参考教程&#xff1a;使用CMake编译Geos3.5.0_cmake geos-CSDN博客 注意事项&#xff1a; 报错&#xff1a;在使用cmake编译geos-3.5.1的时候&#xff0c;会出现报错&#xff1a; CMake Error at CMakeLists.txt:330 (include): include could not find load file GenerateSou…

map和set的底层结构 --- AVL树的实现(万字图文详解)

AVL树的实现 底层结构1. AVL的概念2. AVL树节点的定义3. AVL树的插入分析 4. AVL树的旋转4.1 新节点插入较高右子树的右侧---右右&#xff1a;左单旋4.2 新节点插入较高左子树的左侧---左左&#xff1a;右单旋4.3 新节点插入较高右子树的左侧---右左&#xff1a;先右单旋再左单…

postman和Jmeter做接口测试的区别(经验之谈)

接口测试的目的 API 测试作为集成测试的一部分&#xff0c;经过被测应用的接口&#xff08;API&#xff09;来确定是否在功能、可靠性、性能和安全方面达到预期的软件测试。因为 API 都没有 GUI 界面&#xff0c;API 测试都是在通信层进行的。 1.建立接口用例集 Postman功能…

redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】

redis数据结构介绍 redis是一个key-value的数据库&#xff0c;key一般是String类型&#xff0c;但是value的类型多种多样。 redis 通用命令 keys : 查看符合模板的所有key &#xff08;keys partten ,匹配表达式支持一些特殊字符 * &#xff1f;&#xff09;del&#xff1a;删…

【可编程串行接口8251A】:用处、内部结构、各引脚的解释、工作方式

8251A的作用 微机内部的数据传送方式为并行方式。 若外设采用串行方式&#xff0c;则微机与外设之间需加串行接口。 串行接口基本功能就是&#xff1a;输入数据时&#xff0c;进行串/并转换&#xff1b;输出数据时&#xff0c;进行并/串转换。Intel8251A是一种可编程的通用同步…

【数据结构】树与二叉树(廿五):树搜索指定数据域的结点(算法FindTarget)

文章目录 5.3.1 树的存储结构5. 左儿子右兄弟链接结构 5.3.2 获取结点的算法1. 获取大儿子、大兄弟结点2. 搜索给定结点的父亲3. 搜索指定数据域的结点a. 算法FindTargetb. 算法解析c. 代码实现a. 使用指向指针的指针b. 直接返回找到的节点 4. 代码整合 5.3.1 树的存储结构 5.…

基于单片机压力传感器MPX4115检测-报警系统proteus仿真+源程序

一、系统方案 1、本设计采用这51单片机作为主控器。 2、MPX4115采集压力值、DS18B20采集温度值送到液晶1602显示。 3、按键设置报警值。 4、蜂鸣器报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /*********************************…

js中map和forEach的区别

forEach只是遍历数组的元素&#xff1b;map映射遍历&#xff0c;除了遍历数组的元素&#xff0c;还会返回一个新的数组。map本身是映射的意思。 若我们平时开发中只是遍历元素的话&#xff0c;用forEach&#xff0c;千万别用map。 注意&#xff1a; 他们俩若遍历的数组元素是基…

暴雷!Shopee佣金再度上调,入驻还需要保证金?—站斧浏览器

近段时间以来&#xff0c;Shopee陆陆续续地上调了多个站点地佣金费率、交易手续费以及FSS&CCB费率等&#xff0c;不仅如此&#xff0c;Shopee还官宣了入驻需要缴纳保证金&#xff01;甚至已入驻商家未交保证金或会被冻结风险&#xff01;这一些列操作让不少Shopee卖家有些措…

1、分布式锁实现原理与最佳实践(一)

在单体的应用开发场景中涉及并发同步时&#xff0c;大家往往采用Synchronized&#xff08;同步&#xff09;或同一个JVM内Lock机制来解决多线程间的同步问题。而在分布式集群工作的开发场景中&#xff0c;就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题&…

linux 账号管理实例一,stdin,passwd复习

需求 账号名称全名次要用户组是否可登录主机密码 myuser1 1st usermygroup1yespasswordmyuser22st usermygroup1yespasswordmyuser33st user无nopassword 第一&#xff1a;用户&#xff0c;和用户组创建&#xff0c;并分配有效用户组&#xff08;初始用户组是passwd里…

Leetcode—45.跳跃游戏II【中等】

2023每日刷题&#xff08;四十&#xff09; Leetcode—45.跳跃游戏II 贪心法思想 实现代码 #define MAX(a, b) (a > b ? (a) : (b))int jump(int* nums, int numsSize) {int start 0;int end 1;int ans 0;int maxStride 0;while(end < numsSize) {maxStride 0;fo…