文件描述符与文件描述符的分配规则

目录

一、文件描述符及其在系统中的管理

二、进程默认打开的文件描述符

三、磁盘文件与内存文件的区别

四、文件描述符的分配规则

1. 文件描述符的起始分配

2. 关闭文件描述符对分配的影响

3. 总结分配规则

五、总结


一、文件描述符及其在系统中的管理

        当你打开文件或设备时,操作系统会为你分配一个被称为“文件描述符”的标识符。想象一下,你身处一个巨大的图书馆,这里存放着无数的书籍(文件)。为了方便管理和查找,每本书都会有一个唯一的编号(文件描述符)。当你想阅读某本书时,只需要告诉图书管理员这个编号,他就能迅速找到对应的书籍。

        在计算机系统中,情况也是如此。每个进程都可以打开多个文件,而系统中存在大量进程,这就意味着系统中任何时刻都可能有无数打开的文件。操作系统需要一种高效的方式来管理这些文件。为此,它使用了一种名为 struct file 的结构体来表示每个打开的文件,并将这些结构体以双链表的形式连接起来。这样,对文件的管理就转化为对这个双链表的增删查改等操作。

        为了区分不同进程所打开的文件,操作系统需要建立进程和文件之间的对应关系。当一个程序运行起来时,操作系统会将其代码和数据加载到内存,并为其创建相关的数据结构,如 task_structmm_struct 和页表等。task_struct 中有一个指针,指向一个名为 files_struct 的结构体。在这个结构体中,有一个名为 fd_array 的指针数组,这个数组的下标就是文件描述符。

        例如,当进程打开 log.txt 文件时,操作系统会将该文件从磁盘加载到内存,形成一个 struct file。这个结构体会被添加到文件双链表中,并且其地址会被存储到 fd_array 数组的下标为 3 的位置。这样,当进程需要对这个文件进行操作时,只需要使用文件描述符 3,就能找到对应的文件信息。

        值得注意的是,当程序执行写操作时,数据并非直接落盘,而是先进入内核缓冲区。系统会定期将缓冲区内容批量写入磁盘,这种延迟写入机制极大提升了I/O效率。这也是为什么突然断电可能导致数据丢失的原因。这类似于我们在写笔记时,先在草稿纸上记录内容,稍后再将草稿纸上的内容整理到正式的笔记本上。

二、进程默认打开的文件描述符

当一个进程被创建时,它会自动打开三个文件描述符,它们分别是:

  • 0号描述符:标准输入(stdin),默认指向键盘。

  • 1号描述符:标准输出(stdout),默认指向显示器。

  • 2号描述符:标准错误(stderr),也指向显示器。

🌵为什么进程创建时默认打开0、1、2三个文件描述符?

        在电脑的世界里,一个程序运行起来就被叫做一个进程。这个进程要想和外面的世界交流,比如获取输入、吐出结果,就得通过一些特别的渠道,这些渠道在电脑系统里被称作“文件描述符”,就像是一个个编号的小管道。

        0、1、2这三个文件描述符就是专门为进程准备的最基本的交流渠道:

0号文件描述符,就好比是这个进程的“嘴巴”,它默认连接到键盘,意思就是当进程需要“吃”数据的时候,它可以直接从键盘这个“饭碗”里获取,比如我们用键盘输入文字,程序就能通过0号管道把文字读进去。

1号文件描述符,就像是进程的“嘴巴”吐出东西的那面,它默认连接到显示器,每当进程想把结果或者信息展示给用户的时候,就通过这个1号管道,把信息送到显示器上,用户就能在屏幕上看到,比如程序运行后显示的结果。

2号文件描述符,相当于进程专门设置的“垃圾出口”,它也连接到显示器,但主要是用来吐出那些“哎呀,我出错了”之类的信息。这样,正常的输出信息和出错的信息就可以分开,用户能更容易地知道哪里出了问题。

        电脑系统把这三个小管道默认打开,是为了让进程一出生就有能力顺畅地和外界交流,就像一个人一出生就有嘴巴能吃能说,眼睛能看一样,这是为了让进程能够快速、方便地工作。

三、磁盘文件与内存文件的区别

        磁盘文件和内存文件之间的关系类似程序和进程的关系。磁盘文件就像一本存储在书架上的书(程序),当你需要阅读时,将其取下来并打开(加载到内存)就变成了内存文件(进程)。

        磁盘文件主要由两部分构成:文件内容(你存储的数据,如文字、图片等)和文件属性(元信息,如文件名、大小、创建时间等)。当磁盘文件被加载到内存时,系统首先会加载文件的属性信息。只有当你需要对文件内容进行读取、写入等操作时,文件的数据才会被延后加载到内存中。这类似于你买了一本书,一开始可能只是快速浏览封面和目录(元信息),只有在需要深入阅读时,才会翻到具体章节(数据内容)。

四、文件描述符的分配规则

1. 文件描述符的起始分配

我们通过一个代码示例来说明文件描述符的分配规则。假设连续打开五个文件:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main()
{
	umask(0);

	int fd1 = open("log1.txt", O_RDONLY | O_CREAT, 0666);
	int fd2 = open("log2.txt", O_RDONLY | O_CREAT, 0666);
	int fd3 = open("log3.txt", O_RDONLY | O_CREAT, 0666);
	int fd4 = open("log4.txt", O_RDONLY | O_CREAT, 0666);
	int fd5 = open("log5.txt", O_RDONLY | O_CREAT, 0666);

	printf("fd1:%d\n", fd1);
	printf("fd2:%d\n", fd2);
	printf("fd3:%d\n", fd3);
	printf("fd4:%d\n", fd4);
	printf("fd5:%d\n", fd5);

	return 0;
}

        运行结果显示五个文件描述符从 3 开始连续递增。这是因为进程创建时已经默认打开了标准输入流(0)、标准输出流(1)和标准错误流(2)。因此,fd_array 数组的 0、1、2 位置已经被占用,所以后续的文件描述符只能从 3 开始分配。

2. 关闭文件描述符对分配的影响

如果我们先关闭文件描述符 0,再打开五个文件:

close(0);

此时,第一个打开的文件获取到的文件描述符将是 0,而后续打开的文件描述符仍然从 3 开始依次递增。这是因为文件描述符的分配总是从最小的未被使用的 fd_array 数组下标开始。

若我们在打开五个文件前,关闭文件描述符 0 和 2:

close(0);
close(2);

那么前两个打开的文件获取到的文件描述符将分别是 0 和 2,之后打开的文件描述符从 3 开始依次递增。这表明文件描述符的分配会优先使用最小的未被使用的下标,即使这些下标之前曾被关闭。

3. 总结分配规则

文件描述符的分配遵循以下规则:

  • 如果有未被使用的最小 fd_array 数组下标,则使用该下标作为新的文件描述符。

  • 例如,当 0、1、2 被占用时,文件描述符从 3 开始分配。

  • 如果关闭了一个文件描述符(如 0 或 2),后续打开文件时会优先使用被关闭的最小下标。

这一规则确保了文件描述符的分配是尽可能紧凑和高效的。

五、总结

        文件描述符是操作系统管理打开文件的关键机制。进程运行时,默认打开的 0、1、2 文件描述符分别对应标准输入、标准输出和标准错误流。通过 fd_array 数组,操作系统将文件描述符与具体的 struct file 结构体关联起来,从而实现对文件的高效操作。

### 文件描述符文件流指针的区别联系 #### 一、基本概念 文件描述符(File Descriptor, FD)是一个非负整数,由操作系统分配给每个打开文件或设备。它是进程文件描述符表中的索引,用于标识当前进程中已打开文件资源[^2]。 文件流指针(`FILE *`)则是 C/C++ 编程语言中的一种数据结构,通常用于高级 I/O 操作。它封装了底层的文件描述符以及其他元信息(如缓冲区状态),提供了更友好的接口供开发者使用[^5]。 --- #### 二、主要区别 1. **层次不同** - 文件描述符属于操作系统层面的概念,直接对应于内核维护的文件对象。 - 文件流指针则位于应用层,是对文件描述符的一次封装,便于程序员进行更高层次的操作[^4]。 2. **功能范围** - 文件描述符仅支持低级操作,例如 `read()`、`write()`、`close()` 等系统调用。 - 文件流指针除了能够完成类似的读写操作外,还额外支持格式化输入输出(如 `fprintf()`、`fscanf()`)、错误处理机制以及自动缓冲管理等功能[^5]。 3. **实现方式** - 文件描述符本质上只是一个整数值,在 Linux 或其他类 Unix 系统下代表某个特定的文件或者网络连接等资源。 - 而 FILE 结构体内部包含了指向实际文件描述符的字段(比如 `_fileno` 成员变量)。这意味着可以通过访问该成员获取到关联的基础文件描述符[^5]。 4. **跨平台特性** - 使用文件描述符编写程序具有良好的可移植性,因为几乎所有现代操作系统都遵循 POSIX 标准实现了统一 API 集合。 - 对应地,尽管标准库定义了一致的行为模式,但由于具体实现细节可能有所差异,因此基于文件流开发的应用可能会遇到兼容性问题[^3]。 --- #### 三、两者之间的关系 虽然它们分属不同的抽象级别,但在某些情况下可以互相转换: - 在 C 库函数的帮助下,可以从现有的文件流创建一个新的独立副本并获得相应的新文件描述符;反之亦然——利用 fdopen 函数可以把原始裸露出来的数字型句柄重新包装成易于使用的 stream object 形式[^5]。 下面展示一段简单的例子说明如何在这两种形式之间切换: ```c #include <stdio.h> #include <unistd.h> int main() { int fd = fileno(stdin); // 获取 stdin 的文件描述符 printf("Standard Input File Descriptor: %d\n", fd); FILE* fp = fdopen(1, "w"); // 将 stdout (file descriptor '1') 转换回 file pointer fprintf(fp, "This is written via a FILE pointer.\n"); fclose(fp); return 0; } ``` 此代码片段先是从预设的标准输入流提取出它的基础编号表示法即文件描述符,接着又演示怎样把另一个方向上的标准化输出端口映射回去形成新的高级别 IO 接口实例。 --- ### 总结 综上所述,文件描述符文件流指针各有优劣之处:前者更加贴近硬件本质且效率较高但灵活性较差;后者虽牺牲了一些性能却极大地简化了复杂场景下的编码工作量并且增强了安全性保障措施。根据项目需求合理选用合适的工具才是最重要的原则之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南风与鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值