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*作为标准库的一部分,其接口在不同的平台上是一致的,从而提高了代码的跨平台性。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Upload-labs 靶场通关解析(上)

前言 文件上传漏洞是一种常见的网络安全漏洞&#xff0c;存在于许多Web应用程序中。攻击者利用这个漏洞可以上传恶意文件到目标服务器&#xff0c;从而执行各种恶意操作&#xff0c;如执行恶意代码、获取敏感信息、控制服务器等。 文件上传漏洞的原理是&#xff0c;Web应用程…

(✌)粤嵌—2024/5/7—除自身以外数组的乘积

代码实现&#xff1a; /*** Note: The returned array must be malloced, assume caller calls free().*/ int* productExceptSelf(int *nums, int numsSize, int *returnSize) {// 左乘积int l[numsSize];l[0] 1;for (int i 1; i < numsSize; i) {l[i] l[i - 1] * nums[…

PyQt6--Python桌面开发(1.安装配置环境)

一.PyQt6简介 PyQt&#xff1a;PyQt是一个功能强大且成熟的GUI框架&#xff0c;基于Qt库。它提供了丰富的组件、布局和主题选项&#xff0c;以及强大的功能和灵活性。PyQt的优点是它具有现代化的外观和丰富的功能&#xff0c;适用于复杂的GUI应用程序。然而&#xff0c;由于Py…

VMware导入ova/ovf虚拟机文件

1.文件-打开-ova文件 2.为新虚拟机起名称 3.等待导入 4.导入完成&#xff0c;可以开始使用 参考链接&#xff1a;VMware导入ova/ovf虚拟机文件

数仓分层——ODS、DW、ADS

一、什么是数仓分层 数据仓库分层是一种组织和管理数据仓库的结构化方法&#xff0c;它将数据仓库划分为不同的层次或级别&#xff0c;每个层次具有特定的功能和目的。这种分层方法有助于管理数据仓库中的数据流程、数据处理和数据访问&#xff0c;并提供一种清晰的结构来支持…

ArrayList线程安全问题解决方案

jdk8 Stream API的出现大大简化了我们对于集合元素的处理代码&#xff0c;对于串行流来说&#xff0c;无需考虑线程安全问题&#xff1b;但是&#xff0c;对于并行流来说&#xff0c;由于它是以多线程的方式并行处理同一个集合中的数据元素的&#xff0c;因此&#xff0c;存在着…

[Android]国内流行的应用市场

国内应用市场的特点 由于Google Play商店服务国内不可用&#xff0c;有许多其它的应用商店充当Android应用的分发渠道。这些商店通常由中国的主要科技公司运营&#xff0c;每个商店都有自己的运营策略和用户基础。 全球移动供应商市场份额&#xff1a;https://gs.statcounter…

我独自升级崛起PC下载安装教程 我独自升级崛起PC下载教程

《我独自升级&#xff1a;崛起》这款游戏灵感源自热门网络漫画《我独自升级》&#xff0c;是一款深度浸入式RPG游戏。它不仅呈献给玩家一个情节错综复杂、引人入胜的故事线&#xff0c;让玩家能紧随主角步伐&#xff0c;亲历其成长的点点滴滴&#xff0c;还自豪地展示了琳琅满目…

geojson文件规格

geojson文件示例&#xff0c; {"type": "FeatureCollection","features": [{"type": "Feature","geometry": {"type": "Point","coordinates": [102.0, 0.5]},"properties&q…

RT-DETR-20240507周更说明|更新Inner-IoU、Focal-IoU、Focaler-IoU等数十种IoU计算方式

RT-DETR改进专栏|包含主干、模块、注意力、损失函数等改进 专栏介绍 本专栏包含模块、卷积、检测头、损失等深度学习前沿改进,目前已有改进点70&#xff01;每周更新。 20240507更新说明&#xff1a; ⭐⭐ 更新CIoU、DIoU、MDPIoU、GIoU、EIoU、SIoU、ShapeIou、PowerfulIoU、…

聚乙烯醇(PVA)纳米纤维膜的性能介绍

聚乙烯醇&#xff08;PVA&#xff09;纳米纤维膜是一种由聚乙烯醇&#xff08;PVA&#xff09;分子组成的纳米级薄膜。这种材料具有许多优良的性能和应用前景&#xff0c;具体如下&#xff1a; 制备工艺&#xff1a;聚乙烯醇纳米纤维膜可以通过静电纺丝等方法制备。具体步骤包括…

网络知识点之—QoS

QoS&#xff08;Quality of Service&#xff0c;服务质量&#xff09;指一个网络能够利用各种基础技术&#xff0c;为指定的网络通信提供更好的服务能力&#xff0c;是网络的一种安全机制&#xff0c; 是用来解决网络延迟和阻塞等问题的一种技术。QoS的保证对于容量有限的网络来…

5000A信号发生器使用方法

背景 gnss工作需要使用的5000A&#xff0c;所以做成文档&#xff0c;用于其他员工学习。 下载星历数据 https://cddis.nasa.gov/archive/gnss/data/daily/2024/brdc/ 修改daily中的年份&#xff0c;就可以获取相关截至时间的星历数据 brcd数据格式 第一行记录了卫星的PRN号&a…

科创板门槛升级!解析中国量子企业的上市之路与国际比拼

4月30日晚&#xff0c;中国证监会于发布了修订后的《科创属性评价指引&#xff08;试行&#xff09;》&#xff08;以下简称“新指引”&#xff09;&#xff0c;该指引自发布日起正式生效。本次修订对原有指引中的部分标准进行了调整&#xff0c;具体如下&#xff1a; 1&#x…

远程桌面连接不上,远程桌面连接不上的专业解决策略

在信息技术领域&#xff0c;远程桌面连接是一种非常重要的工具&#xff0c;它允许用户从任何地点、任何时间访问和操作远程计算机。然而&#xff0c;当远程桌面连接出现问题时&#xff0c;可能会严重影响工作效率。以下是一些可能导致远程桌面连接不上的原因以及相应的解决方案…

干货分享-策划人都在用的活动策划网站

职场上&#xff0c;学会借力&#xff0c;学会‘抄’&#xff0c;比辛辛苦苦做老黄牛&#xff0c;更能事倍功半&#xff0c;不仅自己省事省力&#xff0c;还能获得更多升职加薪的机会。 那么&#xff0c;职场新人如何快速的写出一份领导满意的方案&#xff1f; 今天分享的‘抄…

深度学习之基于Vgg19预训练卷积神经网络图像风格迁移系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 在数字艺术和图像处理领域&#xff0c;图像风格迁移技术一直备受关注。该技术可以将一幅图像的内容和…

动态内存开辟(下)

前言 动态内存开辟以及柔性数组的介绍 一、 几个经典的笔试题 1. 题目一 void Getmemory(char*p) {p (char*)malloc(100); } int main() {char* str NULL;Getmemory(str);strcpy(str, "hello world");printf(str);return 0; } 这段代码我们可以发现两个很明显…

解决VScode -正在本地下载 VS Code 服务器

不知道怎么回事再次连接服务器的时候一直卡在这里了&#xff0c;查看输出信息发现一直卡在下载处&#xff0c;报错信息如图1&#xff0c;输出信息如图2。 1.报错信息 图1 报错信息 图2 输出信息 2.尝试 【已解决】设置SSH主机&#xff1a;VS Code-正在本地下载 VS Code 服务器…

Linux 第二十二章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…