文件IO基础

一、文件描述符

        调用 open 函数会有一个返回值,该返回值就是一个文件描述符( file descriptor),这说明文件描述符是一个 非负整数;对于 Linux 内核而言,所有打开的文件都会通过文件描述符进行索引。
 当调用 open 函数打开一个现有文件或创建一个新文件时,内核会向进程返回一个文件描述符,用于指代被打开的文件,所有执行 IO 操作的系统调用都是通过文件描述符来索引到对应的文件
        

        一个进程能打开的文件是有限的,所以对进程来说,文件描述符也是有限的,文件描述符是从 0 开始分配的,譬如说进程中第一个被打开的文件对应的文件描述符是 0、第二个文件是 1、第三个文件是 2、第 4 个文件是 3……以此类推,所以由此可知,文件描述符数字最大值为 1023(0~1023)。每一个被打开的文件在同一个进程中都有一个唯一的文件描述符,不会重复,如果文件被关闭后,它对应的文件描述符将会被释放,那么这个文件描述符将可以再次分配给其它打开的文件、与对应的文件绑定起来。        

       当我们在程序中,调用 open 函数打开文件的时候,分配的文件描述符一般都是从 3 开始,这里大家可能要问了,上面不是说从 0 开始的吗,确实是如此,但是 0 1 2 这三个文件描述符已经默认被系统占用 了,分别分配给了系统标准输入(0 )、标准输出( 1 )以及标准错误( 2
(注:标准输入一般对应的是键盘,可以理解为 0 便是打开键盘对应的设备文件时所得到的文件描述符;标 准输出一般指的是 LCD 显示器,可以理解为 1 便是打开 LCD 设备对应的设备文件时所得到的文件描述符; 而标准错误一般指的也是 LCD 显示器。)

二、open打开文件

Linux 系统中要操作一个文件,需要先打开该文件,得到文件描述符,然后再对文件进行相应的读写操作(或其他操作),最后在关闭该文件;open 函数用于打已经存在的文件之外,
还可以创建一个新的文件,函数原型如下所示:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

从上图可知,open函数有两种原型,但C语言又不支持重载,所以open应为可变参函数:

所以由此可知,在应用程序中调用 open 函数即可传入 2 个参数( pathname flags )、也可传入 3 个参数(pathname flags mode ),但是第三个参数 mode 需要在第二个参数 flags 满足条件时才会有效。 open 函数的第 3 个参数只有在使用 O_CREAT O_TMPFILE 标志 时才有效。
参数说明:
pathname 字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信 息,譬如:"./src_file" (当前目录下的 src_file 文件)、 "/home/dengtao/hello.c" 等;如果 pathname 是一个符号链接,会对其进行解引用。
flags 调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,这些标志使 用宏定义进行描述,都是常量,open 函数提供了非常多的标志,我们传入 flags 参数时既可以单独使用某一 个标志,也可以通过位或运算(|)将多个标志进行组合。比如O_RDONLY O_WRONLY
mode 此参数用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT O_TMPFILE 标志 时才有效(O_TMPFILE 标志用于创建一个临时文件)
在实际编程中,我们可以直接使用 Linux 中 已经定义好的宏,不同的宏定义表示不同的权限,如下所示:

三、Linux如何管理文件

1、静态文件与inode

        文件在没有被打开的情况下一般都是存放在磁盘中的,譬如电脑硬盘、移动硬盘、U 盘等外部存储设备,文件存放在磁盘文件系统中,并且以一种固定的形式进行存放,我们把他们称为静态文件。
        文件储存在硬盘上,硬盘的最小存储单位叫做“扇区”(Sector ),每个扇区储存 512 字节(相当于 0.5KB ), 操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次 性读取一个“块”(block )。这种由多个扇区组成的“块”,是文件存取的最小单位。“块”的大小,最常见的是 4KB ,即连续八个 sector 组成一个 block
        所以由此可以知道,静态文件对应的数据都是存储在磁盘设备不同的“块”中,我们的磁盘在进行分区、 格式化的时候会将其分为两个区域,一个是数据区,用于存储文件中的数据;另一个是 inode 区,用于存放 inode table(inode 表),inode table 中存放的是一个一个的 inode(也称为 inode节点),不同的 inode 就可以表示不同的文件,每一个文件都必须对应一个 inode,inode 实质上是一个结构体,这个结构体中有很多的元素,不同的元素记录了文件了不同信息,譬如文件字节大小、文件所有者、文件对应的读/写/执行权限、文件时间戳(创建时间、更新时间等)、文件类型、文件数据存储的 block(块)位置等等信息。
Linux 系统中,内核会为每个进程设置一个专门的数据结构用于管理该进 程,譬如用于记录进程的状态信息、运行特征等,我们把这个称为进程控制块(Process control block ,缩写 PCB)。
PCB 数据结构体中有一个指针指向了文件描述符表( File descriptors ),文件描述符表中的每一个元素 索引到对应的文件表(File table ),文件表也是一个数据结构体,其中记录了很多文件相关的信息,譬如文 件状态标志、引用计数、当前文件的读写偏移量以及 i-node 指针(指向该文件对应的 inode )等,进程打开 的所有文件对应的文件描述符都记录在文件描述符表中,每一个文件描述符都会指向一个对应的文件表, 其示意图如下所示:
系统内部会将这个过程分为三步:
1) 系统找到这个文件名所对应的 inode 编号(do_file_open函数内执行);
2) 通过 inode 编号从 inode table 中找到对应的 inode 结构体;
3) 根据 inode 结构体中记录的信息,确定文件数据所在的 block ,并读出数据。

2、文件打开时的状态

当我们调用 open 函数去打开文件的时候,内核会申请一段内存(一段缓冲区),并且将静态文件的数据内容从磁盘这些存储设备中读取到内存中进行管理、缓存(也把内存中的这份文件数据叫做动态文件、内核缓冲区)。打开文件后,以后对这个文件的读写操作,都是针对内存中这一份动态文件进行相关的操作, 而并不是针对磁盘中存放的静态文件。

3、多次打开同一个文件

  • 一个进程内多次 open 打开同一个文件,那么会得到多个不同的文件描述符 fd,同理在关闭文件的时候也需要调用 close 依次关闭各个文件描述符。
  • 一个进程内多次 open 打开同一个文件,在内存中并不会存在多份动态文件。
  • 一个进程内多次 open 打开同一个文件,不同文件描述符所对应的读写位置偏移量是相互独立的。

4、原子操作与竞争冒险

操作共享资源的两个进程(或线程),其操作之后的所得到的结果往往是不可预期的,因为每个进程(或线程)去操作文件的顺序是不可预期的,即这些进程获得 CPU 使用权的先后顺序是不可预期的,完全由操作系统调配,这就是所谓的竞争状态。既然存在竞争状态,那么该如何规避或消除这种状态呢?可以执行 原子操作
所谓原子操作,是有多步操作组成的一个操作,原子操作要么一步也不执行,一旦执行,必须要执行完所有 步骤,不可能只执行所有步骤中的一个子集。

(1)O_APPEND 实现原子操作

进程 A 和进程 B 都对同一个文件进行追加写操作,导致进程 A 写入 的数据覆盖了进程 B 写入的数据,解决办法就是将“先定位到文件末尾,然后写”这两个步骤组成一个原 子操作即可,那如何使其变成一个原子操作呢?答案就是 O_APPEND 标志。
open 函数的 flags 参数中包含了 O_APPEND 标志,每次执行 write 写入操作时都会将文件当前写位置偏移量移动到文件末尾,然后再写入数据,这里“移动当前写位置偏移量到文件末尾、写入数据”这两个操作步骤就组成了一个原子操作,加入 O_APPEND 标志后,不管怎么写入数据 都会是从文件末尾写,这样就不会导致出现“进程 A 写入的数据覆盖了进程 B 写入的数据”这种情况了。

(2)pread()pwrite()

pread() pwrite() 都是系统调用,与 read() write() 函数的作用一样,用于读取和写入数据。区别在于, pread()和 pwrite() 可用于实现原子操作,调用 pread 函数或 pwrite 函数可传入一个位置偏移量 offset 参数, 用于指定文件当前读或写的位置偏移量,所以调用 pread 相当于调用 lseek 后再调用 read ;同理,调用 pwrite 相当于调用 lseek 后再调用 write 。所以可知,使用 pread pwrite 函数不需要使用 lseek 来调整当前位置偏 移量,并会将“移动当前位置偏移量、读或写”这两步操作组成一个原子操作。

(3)创建一个文件

O_EXCL 可以用于测试一个文件是否存在,如果不存在则创建此文件,如果存在则返回错误,这使得测试和创建两者成为一个原子操作。

5、fcntrl函数

fcntl() 函数可以对一个已经打开的文件描述符执行一系列控制操作,譬如复制一个文件描述符(与 dup 、 dup2 作用相同)、获取 / 设置文件描述符标志、获取 / 设置文件状态标志等,类似于一个多功能文件描述符管理工具箱。

6、ioctrl函数

ioctl() 可以认为是一个文件 IO 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件 或硬件外设。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用Postgres的JSONB数据类型进行高效查询

文章目录 解决方案1. 创建包含JSONB列的表2. 插入JSON数据3. 使用GIN索引加速查询4. 执行高效的JSONB查询 示例代码解释 PostgreSQL的JSONB数据类型提供了一种灵活的方式来存储和查询JSON格式的数据。JSONB不仅允许你在PostgreSQL数据库中存储JSON文档&#xff0c;而且还对这些…

Java程序生成可执行的exe文件 详细图文教程

1.Java编辑器&#xff0c;如&#xff1a;idea、eclipse等&#xff0c;下载地址&#xff1a;IntelliJ IDEA: The Capable & Ergonomic Java IDE by JetBrainshttps://www.jetbrains.com/idea/2.exe4j&#xff0c;下载地址&#xff1a;ej-technologies - Java APM, Java Prof…

【创建型模式】工厂方法模式

一、简单工厂模式 1.1 简单工厂模式概述 简单工厂模式又叫做静态工厂方法模式。 目的&#xff1a;定义一个用于创建对象的接口。实质&#xff1a;由一个工厂类根据传入的参数&#xff0c;动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。 简单工厂模式…

Next.js在浏览器中访问env环境变量报错解决方案

报错的代码 .env.local DB_HOSTlocalhost DB_USERmyuser DB_PASSmypasswordenv.js export default function Env() {const dbHost process.env.DB_HOSTconst dbUser process.env.DB_USERconst dbPass process.env.DB_PASSreturn (<div><p>DB Host: {dbHost}<…

LeetCode 11.盛最多谁的容器

目录 题目描述 方法一 双指针 思路&#xff1a; 代码&#xff1a; 题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的…

0418WeCross搭建 + Caliper测试TPS

1. 基本信息 虚拟机名称&#xff1a;Pure-Ununtu18.04 WeCross位置&#xff1a;/root/wecross-demo 2. 搭建并启动WeCross 参考官方指导文档 https://wecross.readthedocs.io/zh-cn/v1.2.0/docs/tutorial/demo/demo.html 访问WeCross网页管理平台 http://localhost:8250/s/…

C语言——字符函数与字符串函数

正文开始&#xff1a;在编程过程中&#xff0c;我们经常要处理字符和字符串&#xff0c;为了方便操作字符和字符串&#xff0c;C语⾔标准库中提供了 一系列库函数&#xff0c;接下来我们就学习⼀下这些函数。 1. 字符分类函数 C语⾔中有⼀系列的函数是专门做字符分类的&#…

代码随想录算法训练营第二天 | 977. 有序数组的平方 | 209. 长度最小的子数组 | 59. 螺旋矩阵 II

977. 有序数组的平方 int* sortedSquares(int* nums, int numsSize, int* returnSize) {int left 0, right numsSize-1;*returnSize numsSize;int *array (int *)malloc(sizeof(int) * numsSize);for (int i numsSize-1; i > 0; i--) {if (nums[left]*nums[left] > …

视觉位置识别与多模态导航规划

前言 机器人感知决策是机器人移动的前提&#xff0c;机器人需要对周围环境实现理解&#xff0c;而周围环境通常由静态环境与动态环境构成。机器人在初始状态或者重启时需要确定当前所处的位置&#xff0c;然后根据用户的指令或意图&#xff0c;开展相应移动或抓取操作。通过视觉…

广告归因窗口期,解决多渠道买量抢激活难题

App想要持续拿量且提高用户转化量&#xff0c;就免不了在多个广告平台上投放广告。 这种买量行为带来一个常见问题&#xff1a;同一用户可能在不同平台上看到同一应用的广告&#xff0c;当这个用户成功转化时&#xff0c;应该将此转化归因于哪个渠道呢&#xff1f;又该如何评估…

C语言如何使⽤指针?

一、问题 指针变量在初始化以后就可以使⽤和参与操作了&#xff0c;那么就要⽤到对指针变量最常⽤的两个操作符——> * 和 &#xff06; 。 二、解答 这⾥⼜要提到始终贯穿着指针的⼀个符号“ * ”&#xff0c;但是这⾥的“ * ”是作为指针运算符使⽤的&#xff0c;叫做取内…

一份超详细的鸿蒙开发面经分享!上百道鸿蒙经典面试题整理~

鸿蒙&#xff08;HarmonyOS&#xff09;作为华为公司自主研发的全场景分布式操作系统&#xff0c;受到了广泛关注。 在面试中&#xff0c;面试官往往会关注申请人的技术能力、项目经验以及解决问题的能力。 下面是一些关于鸿蒙开发具有3年工作经验的面试题及其相关问答&#…