Linux--基础IO(文件描述符fd)

目录

1.回顾一下文件

2.理解文件

下面就是系统调用的文件操作

文件描述符fd,fd的本质是什么?

读写文件与内核级缓存区的关系

据上理论我们就可以知道:open在干什么

3.理解Linux一切皆文件 

 4.C语言中的FILE*


1.回顾一下文件

先来段代码回顾C文件接口

写文件

#include <stdio.h>
#include <string.h>
int main()
{FILE* fp = fopen("myfile", "w");if (!fp) {printf("fopen error!\n");}const char* msg = "hello\n";int count = 5;while (count--) {fwrite(msg, strlen(msg), 1, fp);}fclose(fp);return 0;
}
读文件
#include <stdio.h>
#include <string.h>
int main()
{FILE* fp = fopen("myfile", "r");if (!fp) {printf("fopen error!\n");}char buf[1024];const char* msg = "hello bit!\n";while (1) {//注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明size_t s = fread(buf, 1, strlen(msg), fp);if (s > 0) {buf[s] = 0;printf("%s", buf);}if (feof(fp)) {break;}}fclose(fp);return 0;
}
输出信息到显示器,你有哪些方法
#include <stdio.h>
#include <string.h>
int main()
{const char* msg = "hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello printf\n");fprintf(stdout, "hello fprintf\n");return 0;
}
stdin & stdout & stderr
C 默认会打开三个输入输出流,分别是 stdin, stdout, stderr。
三个流的类型都是 FILE*, fopen 返回值类型,文件指针。

打开文件时,需要指定一个打开模式,它决定了程序对文件的访问方式。常见的打开模式包括:

  • 只读模式:只允许读取文件内容。
  • 只写模式:只允许写入文件内容,通常会清空文件原有内容。
  • 追加模式:在文件末尾添加内容,而不会覆盖原有内容。
  • 读写模式:既允许读取也允许写入文件内容。

例如在w(只写)操作下:1.如果文件不存在,就在当前的路径下,新建指定的文件 2.文件存在,默认打开文件的时候,就会把文件清空!

这个特性与Linux中的输出重定向(>)是非常相似的,所有输出重定向一定是文件操作!


2.理解文件

结合之前所学的,我们基本知道:文件=属性+内容

1.打开文件,本质其实是进程打开文件。2.文件没有被打开的时候,存放在硬盘中。3.进程是可以一次性打开很多文件的。4.系统中可以存在很多的进程。

因此,在很多情况下,OS内部一定存在大量被打开的文件,OS肯定是需要把它们管理起来的。我们类比进程,OS通过先描述,再组织的方式,用PCB将每个进程都管理起来,那么每打开一个文件,在OS内部,一定存在对应描述文件的结构体,类似PCB,将每个文件都管理起来。以上就是我们目前对文件的全部认识了。


a.操作文件,本质:是将文件的相关属性加载到内存中,并为程序提供对文件的访问接口,以便进行后续的读写操作。这一过程涉及多个步骤和概念,需要深入理解才能确保文件的正确访问和数据的完整性。是进程和文件的关系。

b.文件储存在硬盘中(硬盘是外设,是硬件),向文件中写入本质是向硬件中写入,OS是硬件的管理者,用户是没有权限直接写入的,用户必须要通过OS写入,OS必须为我们提供系统调用(OS不相信任何人),因此我们在c/c++/(其它语言)对文件操作的函数,其实都是对系统调用接口的封装!


下面就是系统调用的文件操作

open():此系统调用用于打开或创建文件。它接受文件路径、打开标志(如只读、只写、读写等)和文件权限等参数,并返回一个文件描述符,该描述符用于后续的文件操作。如果文件成功打开,则返回的文件描述符是一个非负整数;否则,返回-1表示错误。

  • pathname参数指定了要打开或创建的文件的路径名。
  • flags参数用于设置打开文件的方式和属性,比如是否读写、是否创建新文件、是否追加等。常见的flags包括O_RDONLY(只读打开)、O_WRONLY(只写打开)、O_RDWR(读写打开)、O_CREAT(如果文件不存在则创建)、O_TRUNC(如果文件存在并且以写方式打开,则将其长度截断为0),O_APPEND(每次写入都追加到文件的末尾)等。这些标志可以通过按位或运算符(|)组合使用。这里的参数并不是表示int类型,而是32个比特位,用比特位来进行标志位的传递(位图)。这是OS设计很多系统调用接口的常用方法(下面是个简单的示例)

运行结果:这就实现了向一个参数传递多种标记位的功能

  • mode参数仅在创建新文件时使用,用于指定新文件的权限。它通常与flags中的O_CREAT一起使用,以设置新文件的访问权限。

我们先来使用一下第一个函数:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT);if (fd < 0){perror("open");return 1;}
}
  1. O_WRONLY:表示文件以只写模式打开。
  2. O_CREAT:表示没有这个文件,就创建这个文件

运行结果:log.txt被成功创建(我们可以看到这个文件的权限是有问题的,是乱码),这是因为在Linux中新建一个文件,就要告诉OS这个文件的其实权限是什么 ,这就是第三个参数,mode做的事情

我们加上权限

运行结果:这时就符合我们权限要求了。

        但是,请注意,文件的实际权限可能受到运行程序的用户的umask(用户文件创建掩码)的影响。umask是一个权限位掩码,它会从open函数中的mode参数中“减去”相应的权限位,以确定新文件的实际权限。

        例如,如果umask设置为0002,那么新文件的权限将会是0666 & ~0002,即0664,意味着所有者有读/写权限,组用户和其他用户只有读权限。这个权限就是0664了

为了取消umask的影响,OS为我们提供了系统调用接口,在程序运行的时候可以动态的调整当前进程的umask值

修改后代码:

运行结果:这时候就符合预期了。


向文件写入:

关闭文件:

eg:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{int fd = open("log.txt", O_WRONLY | O_APPEND);if (fd < 0){perror("open");return 1;}const char* m = "hello!\n";write(fd, m, strlen(m));close(fd);return 0;
}

运行结果:成功的写入了

我们重新写入一些内容:

重新编译运行后:

结果为什么是这样的呢?为什么老的内容没有被清空,而是接着从头开始覆盖呢?如果想写入的时候,清空原来文件的内容,在打开文件的时候就要加上O_TRUNC了。

再重新编译运行:发现达到我们的预期了

如果想在文件末尾追加,不覆盖再打开文件的时候,加上O_APPEND。(就不做演示了)


 在Linux系统中,当open函数成功打开一个文件或设备时,它返回的文件描述符是一个正整数。这个正整数的起始值是从3开始的。

为什么是从3开始呢?这主要基于以下几个原因:

标准输入、输出和错误流:在Unix和Linux系统中,进程默认拥有三个打开的文件描述符:

  • 0:标准输入(stdin)键盘
  • 1:标准输出(stdout)显示器
  • 2:标准错误(stderr)显示器
    这三个文件描述符是进程创建时自动打开的,用于基本的输入、输出和错误处理。

因此,我们是可以直接向1里面输入,就直接打印在显示器上:

运行结果:

我们知道这些整数都表示文件描述符(fd),为什么往这些整数里写,就可以向文件里写呢?


文件描述符fd,fd的本质是什么?

文件储存在磁盘中,OS需要从磁盘中获取。

   struct file是内核中用于表示已打开文件的结构体。当进程打开一个文件时,内核会创建一个file结构体实例来描述该文件。我们还知道文件=内容+属性,文件的属性会用来初始化struct file结构体

   文件内核级缓存:Linux内核为了提高文件系统的读写性能,会使用一部分内存作为缓存区。struct file中有一个指针指向内核级缓存,文件的内容会写到内核级缓存中,然后再根据需要从缓存中写入磁盘或从磁盘读取到缓存中。这种缓存机制可以减少CPU上下文的切换和堆栈调用,提高磁盘访问效率。

        我们知道进程是可以打开多个文件的,进程和文件的关系是 1:N的。为了能够支持进程和文件产生关联,

操作系统在自己的PCB(task_struct)中存在一个属性struct files_struct* files,他会指向struct files_struct.

   struct files_struct中存在一个指针数组struct file* fd_array,其元素是指向file结构体的指针。每个file结构体代表内核中一个已打开的文件对象。文件描述符(fd)就是该数组的下标,进程可以通过这个下标找到文件的描述信息,进而操作文件。因此fd的本质就是:文件映射关系的数组的下标


读写文件与内核级缓存区的关系

写文件与内核级缓存的关系

当进程执行写文件操作时,数据并不会直接写入磁盘,而是首先写入内核的缓存区。这种缓存机制被称为“回写缓存”。内核会将写请求缓存起来,等待合适的时机(例如缓存区满或定时器触发)再将数据一并写入后端磁盘。这样做的好处是减少了磁盘的写操作次数,提高了写入性能,并且减少了CPU的上下文切换和堆栈调用开销。

读文件与内核级缓存的关系

对于读文件操作,当进程需要读取文件数据时,内核会首先检查缓存区中是否已经有该数据。如果缓存区中已经存在所需数据(这通常是由于之前对该数据的读取或写入操作已经将数据缓存到内存中),则内核会直接从缓存中读取数据,而无需从磁盘读取。这种直接从缓存中读取数据的机制称为“缓存命中”,它可以显著提高读取性能。

从这里我们就可以知道:wirte,read函数,本质是拷贝函数


据上理论我们就可以知道:open在干什么

1.创建file 

2.开辟文件缓冲区的空间,加载文件数据(延后)

3.查看进程的文件描述符

4.file地址,填入对应的表下标中

5.返回下标


3.理解Linux一切皆文件 

通过上面的例子我们可以知道:硬件也是文件,

标准输入、输出和错误流:在Unix和Linux系统中,0:标准输入(stdin)键盘

1:标准输出(stdout)显示器 ,2:标准错误(stderr)显示器

        我们都知道,每个文件被打开都有对应的struct file进行管理。通过struct file,Linux内核能够以一种统一的方式处理各种不同类型的文件和设备。无论是磁盘文件、网络套接字、字符设备还是块设备,都可以通过相同的接口和数据结构来表示和操作。这种设计使得Linux系统具有高度的可扩展性和灵活性,能够支持各种不同类型的文件系统和设备驱动程序。

  struct file结构体在Linux内核中用于表示一个打开的文件或设备。它包含了一些通用的字段,如文件描述符、文件操作函数指针、文件类型等。这些字段允许内核以统一的方式处理不同类型的文件和设备,尽管它们的底层实现和行为可能完全不同。

        具体来说,struct file中的f_op字段是一个指向file_operations结构体的指针,该结构体包含了一组用于操作文件的函数指针。不同的文件系统或设备驱动程序可以提供自己的file_operations结构体实例,并在其中定义自己的文件操作函数。当用户空间程序通过系统调用来操作文件时,内核会根据struct file中的f_op字段来确定应该调用哪个函数来执行相应的操作。

        这种设计使得Linux内核能够以统一的方式处理各种不同类型的文件和设备,而无需关心它们的底层实现。例如,无论是磁盘文件、网络套接字还是字符设备,它们都可以通过相同的read()write()系统调用来进行读写操作。内核会根据struct file中的f_op字段来确定应该调用哪个函数来执行这些操作,从而实现多态性的效果。

struct file的角度来看,Linux的“一切皆文件”体现在将所有类型的资源(包括文件、设备、套接字等)都抽象为文件,并通过统一的struct file数据结构来表示和操作这些资源。这种设计简化了内核和用户空间之间的交互,提高了系统的可扩展性和可管理性。

        从底层角度来看,Linux的“一切皆文件”体现在其文件系统的架构和内核如何处理各种资源的方式上。通过VFS(虚拟文件系统)、文件描述符、设备驱动等机制,Linux将各种资源抽象为文件,并通过统一的接口来管理和访问它们,从而简化了系统设计和编程接口的复杂性。

补充:虚拟文件系统(VFS)

  • Linux内核通过虚拟文件系统(VFS)这一层来抽象各种实际文件系统(如EXT4、XFS、Btrfs等)的共性,使得上层应用或用户可以通过统一的接口来访问这些文件系统。
  • VFS提供了一个通用的文件系统接口,使得内核可以透明地支持多种文件系统,而不必关心底层文件系统的具体实现。

 4.C语言中的FILE*

        通过前面的学习我们知道:在Linux中,系统访问文件或设备时主要依赖文件描述符(fd)来进行操作。文件描述符(fd)是进程内部用于唯一标识(在Linux中,系统访问文件的时候,只认文件描述符fd)打开的文件或设备的非负整数,它允许系统以一种统一和抽象的方式来处理各种不同类型的文件和设备。通过系统调用和文件描述符表(fd),进程可以与文件或设备进行交互,并执行各种操作。

为什么C语言函数可以直接调用这些操作呢?

        原因在于C语言标准库已经为我们封装(一定封装了fd!!)了底层系统调用的细节。

  FILE*类型,它使得C语言程序员能够以一种更直观、更便捷的方式来进行文件操作。

  FILE*是C语言标准库中的一个结构体类型的指针,指向一个结构体(_IO_FILE),这个结构体包含了进行文件操作所需的所有信息,比如文件描述符、缓冲区、错误标志等。

        当你使用C语言标准库函数(如fopen()fclose()fread()fwrite()等)来打开、关闭、读写文件时,这些函数会在内部处理文件描述符。具体来说,当你调用fopen()打开一个文件时,它会调用系统调用来获取一个文件描述符,并将这个文件描述符以及其他相关信息封装在一个_IO_FILE结构体中,然后返回一个指向这个结构体的指针(即FILE*。后续的文件操作(如读写)都会通过这个FILE*指针来间接地访问和操作文件描述符。

        所有的C语言上的文件操作函数,本质底层都是对系统调用的封装。

eg:_fileno 是 glibc 中的一个内部成员,用于存储与 FILE * 关联的文件描述符


 

C语言为什么要用FILE*进行封装?

FILE*作为标准库的一部分,其接口在不同的平台上是一致的,从而提高了代码的跨平台性。

每个平台下使用的库不一样,但是用的函数是一样的,这样就具有跨平台性了。

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

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

相关文章

数据结构——链表(精简易懂版)

文章目录 链表概述链表的实现链表的节点&#xff08;单个积木&#xff09;链表的构建直接构建尾插法构建头插法构建 链表的插入 总结 链表概述 1&#xff0c;链表&#xff08;Linked List&#xff09;是一种常见的数据结构&#xff0c;用于存储一系列元素。它由一系列节点&…

Mysql查询语句(一)简单查询和简单条件查询

MySQL的所有语句中&#xff0c;我们日常用的最多的其实就是查询语句。因此这篇文章主要介绍查询语句中的一些基础语法。 目录 简单查询 简单条件查询 简单查询 最简单的查询语句的语法如下所示&#xff1a; SELECT * FROM student; 它的语法解析如下&#xff1a; SELECT关…

学习笔记:【QC】Android Q qmi扩展nvReadItem/nvWriteItem

一、qmi初始化 流程图 初始化流程: 1、主入口&#xff1a; vendor/qcom/proprietary/qcril-hal/qcrild/qcrild/rild.c int main(int argc, char **argv) { const RIL_RadioFunctions *(*rilInit)(const struct RIL_Env *, int, char **); rilInit RIL_Init; funcs rilInit…

122. Kafka问题与解决实践

文章目录 前言顺序问题1. 为什么要保证消息的顺序&#xff1f;2.如何保证消息顺序&#xff1f;3.出现意外4.解决过程 消息积压1. 消息体过大2. 路由规则不合理3. 批量操作引起的连锁反应4. 表过大 主键冲突数据库主从延迟重复消费多环境消费问题后记 前言 假如有家公司是做餐饮…

无法添加以供审核,提交以供审核时遇到意外错误。如果问题仍然存在,请联系我们

遇到问题&#xff1a; 无法添加以供审核 要开始审核流程&#xff0c;必须提供以下项目&#xff1a; 提交以供审核时遇到意外错误。如果问题仍然存在&#xff0c;请联系我们。 解决办法&#xff1a; 修改备案号为小写&#xff0c; 例如&#xff1a;京ICP备2023013223号-2A 改…

自然语言(NLP)

It’s time for us to learn how to analyse natural language documents, using Natural Language Processing (NLP). We’ll be focusing on the Hugging Face ecosystem, especially the Transformers library, and the vast collection of pretrained NLP models. Our proj…

运动控制“MC_MoveVelocity“功能块详细应用介绍

1、运动控制单位u/s介绍 运动控制单位[u/s]介绍-CSDN博客文章浏览阅读91次。运动控制很多手册上会写这样的单位,这里的u是英文单词unit的缩写,也就是单位的意思,所以这里的单位不是微米/秒,也不是毫米/秒,这里是一个泛指,当我们的单位选择脉冲时,它就是脉冲/秒,也就是…

【排序算法】第四章:归并排序(近万字讲解,通俗易懂)

归并排序 归并排序本质就是一种思想&#xff0c;在很多题目都可以用到 一、归并排序的原理 归并排序&#xff08;MergeSort&#xff09; 是建立在归并操作上的一种有效的排序算法&#xff0c;采用分治法排序&#xff0c;分为分解、合并两个步骤。 分解&#xff1a;将数组分割…

GESP一级考试笔记(C++)

考纲 GESP C 一级考纲 一、计算机基础知识 二、变量 1.变量的声明 想要使用变量&#xff0c;必须先做“声明”&#xff0c;也就是告诉计算机要用到的数据叫什么名字。变量声明的标准语法可以写成&#xff1a;数据类型 变量名; #include <iostream> using namespace s…

速览Coinbase 2024Q1 财报重点:业务全面开花,净利润达11.8亿美元

作者&#xff1a;范佳宝&#xff0c;Odaily 星球日报 近期&#xff0c;Coinbase 发布了其 2024 年第一季度财报。 报告显示&#xff0c;Coinbase 第一季度营收为 16.4 亿美元&#xff0c;高于分析师平均预期的 13.4 亿美元&#xff1b;净利润为 11.8 亿美元&#xff0c;合每股…

PyCharm怎么安装Comate与使用示范

目录 简单介绍Comate 安装步骤详解 Comate使用示范详解 使用总结 简单介绍Comate Baidu Comate智能编码助手是一款基于文心大模型打造的编码辅助工具&#xff0c;具备多重优势&#xff0c;包括代码智能、应用场景丰富、创造价值高、广泛应用等。它能帮助开发者提升编码效率…

LeetCode算法题:8.字符串转换整数 (atoi)

请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串并丢弃无用的前导空格检查下一个字符&#xff08;假设还未到字符末…

Java:内存模型

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、Java内存模型出现的背景 二、什么是Java内存模型 三、Java内存模型的底层实现 总结 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一…

linux系统 虚拟机的安装详细步骤

window&#xff1a; (1) 个人&#xff1a;win7 win10 win11 winxp (2)服务器&#xff1a;windows server2003 2008 2013 linux&#xff1a; (1)centos7 5 6 8 (2)redhat (3)ubuntu (4)kali 什么是linux: 主要是基于命令来完成各种操作&#xff0c;类似于DO…

【黑客不回去做的三件事情】

作为黑客不会去做的三件事 黑客在认知上和普通人有什么区别&#xff1f;什么行为是自己觉得很正常&#xff0c; 但是大部分黑客并不会这么做的。今天来聊聊作为黑客&#xff0c;有哪些日常生活中不会去做的事&#xff0c; 第一&#xff0c;黑客不会用同一个密码&#xff0c;并…

开源模型应用落地-CodeQwen模型小试-探索更多使用场景(三)

一、前言 代码专家模型是基于人工智能的先进技术&#xff0c;它能够自动分析和理解大量的代码库&#xff0c;并从中学习常见的编码模式和最佳实践。这种模型可以提供准确而高效的代码建议&#xff0c;帮助开发人员在编写代码时避免常见的错误和陷阱。 通过学习代码专家模型&…

vivado Zynq UltraScale+ MPSoC 比特流设置

Zynq UltraScale MPSoC 比特流设置 下表所示 Zynq UltraScale MPSoC 器件的器件配置设置可搭配 set_property <Setting> <Value> [current_design] Vivado 工具 Tcl 命令一起使用。

KDTree空间搜索算法学习

目录 KDTree&#xff08;K-Dimensional Tree&#xff09;原理步骤空间索引建立例子[^1]回溯搜索例子[^2] 相关包案例[^3]数据KDTree 识别轨道衔接出行轨道衔接单车骑行范围分析结果保存 KDTree&#xff08;K-Dimensional Tree&#xff09;原理 将需要匹配的 K 维空间点建立 K …

java10基础(this super关键字 重写 final关键字 多态 抽象类)

目录 一. this和super关键字 1. this关键字 2. super关键字 二. 重写 三. final关键字 四. 多态 五. 抽象类 1. 抽象方法 2. 抽象类 3. 面向抽象设计 一. this和super关键字 1. this关键字 this 当前对象的引用 this.属性 this.方法名() this() -- 调用构造函数 …

VALSE 2024 Workshop报告分享┆Open-Sora Plan视频生成开源计划——进展与不足

2024年视觉与学习青年学者研讨会&#xff08;VALSE 2024&#xff09;于5月5日到7日在重庆悦来国际会议中心举行。本公众号将全方位地对会议的热点进行报道&#xff0c;方便广大读者跟踪和了解人工智能的前沿理论和技术。欢迎广大读者对文章进行关注、阅读和转发。文章是对报告人…