【Linux篇】文件描述符背后的秘密:让你的代码更加高效
从零开始理解文件描述符:程序员的必备技能
- 一. 系统文件I/O
- 1.1 写文件
- 1.2 open()
- 1.3 文件描述符
- 1.3.1 0&1&2
- 1.3.2 文件描述符分配原则
- 1.4 重定向(重点)
- 1.4.1 基本概念
- 1.4.2 原理解释
- 1.4.3 dup2()系统调用
- 二. 最后
在操作系统和编程的世界里,文件描述符是一个经常被提及但却容易被忽视的概念。无论你是在处理文件输入输出,还是与网络进行数据交换,文件描述符都在背后默默地支撑着你的代码。然而,很多开发者对它的理解往往停留在表面,甚至不清楚它到底是如何工作的。本文将带你深入探索文件描述符的真正含义,帮助你掌握这一关键概念,并揭示它如何影响程序的性能和效率。通过简单易懂的解释,你将学会如何高效地使用文件描述符,从而提升你的编程技巧,并更好地理解操作系统的运作原理。
💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!
👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对Linux OS感兴趣的朋友,让我们一起进步!
一. 系统文件I/O
1.1 写文件
下面将展示用C语言库函数来打开文件,下面会用到C语言提供的库函数fwrite()
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
功能:函数 fwrite() 将 nmemb 个数据项写入由 stream 指针指向的流中,每个数据项的长度为 size 字节,这些数据项从 ptr 所指向的位置获取。
返回值:fwrite() 返回已写入的数据项数量。注意:数据项并不是返回字节数,返回的是指定类型准确写入的个数。
示例代码:
#include
#include
int main()
{
FILE *fp = fopen("myfile", "w");
if(!fp){
printf("fopen error!
");
}
const char *msg = "hello bit!
";
int count = 5;
while(count--){
fwrite(msg, strlen(msg), 1, fp);
}
fclose(fp);
return 0;
}
该示例会将msg所指向的常量字符串写入fp中,其实就是fd所指向的文件。
通过系统调用来进行写文件 write()
ssize_t write(int fd, const void *buf, size_t count);
功能:write() 函数将从 buf 起始位置开始的最多 count 字节数据写入由文件描述符 fd 指定的文件中。
返回值:操作成功时,返回写入的字节数。操作失败时,返回 -1,并设置 errno 以指示错误原因。
示例代码:
#include
#include
#include
#include
#include
#include
int main()
{
umask(0);
int fd = open("myfile", O_WRONLY|O_CREAT, 0644);
if(fd < 0){
perror("open");
return 1;
}
int count = 5;
const char *msg = "hello bit!
";
int len = strlen(msg);
while(count--){
write(fd, msg, len);// msg:缓冲区⾸地址, len: 本次读取,期望写
⼊多少个字节的数据。 返回值:实际写了多少字节数据
}
close(fd);
return 0;
}
上述代码简单让大家知道如何使用接口,以及功能、返回值等,下面将重点介绍open系统调用。
1.2 open()
open 系统调用的语法如下:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
- pathname:要打开的文件的路径。可以是绝对路径或相对路径。
- flags:文件打开的标志,指定如何打开文件(比如只读、只写、追加等)通过 | 运算来传递标志。常见的标志有:
O_RDONLY:只读模式。
O_WRONLY:只写模式。
O_RDWR:读写模式。
O_CREAT:如果文件不存在,则创建文件。
O_TRUNC:如果文件已存在且以写入模式打开,则将文件截断为零长度(清空文件内容)。
O_APPEND:以追加模式打开文件。
- mode:文件的权限设置,仅在使用 O_CREAT 标志时有效。通常是文件的读写权限,如 0644(即所有者读写,其他人只读)。
返回值:
- 如果成功,open 返回一个文件描述符(通常是一个非负整数),它代表了与文件的连接。
- 如果失败,返回 -1,并将 errno 设置为具体的错误代码。
下面是一个简单的例子,展示如何使用 open 打开一个文件:
#include
#include
#include
int main() {
int fd = open("example.txt", O_RDWR | O_CREAT, 0644);
if (fd == -1) {
perror("Error opening file");
return 1;
}
// 使用文件描述符(fd)进行读写操作...
close(fd); // 使用完毕后关闭文件
return 0;
}
小结:
open 是文件操作中至关重要的一个系统调用,它提供了对文件的访问权限并返回一个文件描述符。理解和正确使用 open 是掌握文件操作的第一步,尤其在进行底层文件处理时,能帮助开发者精确地控制文件的打开方式和访问权限。
1.3 文件描述符
本质:它是一个整数,fd本质就是一个数组下标,数组里面存的内容(指针)就是指向打开文件具体先关属性结构体。
1.3.1 0&1&2
Linux进程会默认打开3个缺省的文件描述符,分别是标准输入0,标准输出1,标准错误2,一般对应的设备是键盘,显示器,显示器。
示例代码:
#include
int main()
{
printf("stdin: %d
",stdin->_fileno);
printf("stdout: %d
",stdout->_fileno);
printf("stderr: %d
",stderr->_fileno);
return 0;
}
输出:
0
1
2
将上述图形象化:每打开一个文件,OS会在内存中创建对应的数据结构来描述文件,就是上述的file结构体,该结构体保存了文件很多属性,如读写位置,文件内核缓冲区,不同设备读写方法存在于该struct files_operation{} 结构体中。执行系统调用open,必须将进程与文件关联起来,通过什么关联呢?指针!!!task_struct{}存在files指针,指向该进程全部打开的文件描述表,通过fd就可以找到对应的文件,为什么???因为文件描述表file* fd_array[]里面存在指向打开文件对象的file*指针,就可以将进程与文件相关连起来咯。
1.3.2 文件描述符分配原则
示例代码:
#include
#include
#include
#include
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d
", fd);
close(fd);
return 0;
}
输出:
fd:3
如果关闭默认打开的标准输入流0呢???
#include
#include
#include
#include
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d
", fd);
close(fd);
return 0;
}
输出:
fd:0
⽂件描述符的分配规则:在files_struct数组当中,找到当前没有被使⽤的最⼩的⼀个下标,作为新的⽂件描述符。假如都使用过,则新的fd依次递增,如0,1,2都被正确的打开了,此时再打开文件log.txt 此时该文件的描述符是3,后面还有的话,依次递增。
1.4 重定向(重点)
1.4.1 基本概念
重定向(Redirection)是操作系统中将输入输出流从默认的终端或文件描述符重定向到其他位置的操作。在 Unix 和类 Unix 系统中,标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)是三个常见的文件描述符,通过重定向,可以改变这些流的来源和去向。
1.4.2 原理解释
使用代码解释:
示例代码:
#include
#include
#include
#include
#include
int main()
{
close(1);
int fd = open("myfile", O_WRONLY|O_CREAT, 0644);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d
", fd);
fflush(stdout);
close(fd);
exit(0);
}
发现应该输出到显示器的内容,被输出到myfile文件中,fd = 1,这种现象叫输出重定向。常见输出重定向 >, >>, <
原理图(如下):
此时标准输出指向的不再是显示器,而是myfile,所以写入的内容不再往显示器输入,而是写入新文件中。
上述代码是通过手动关闭文件描述符,有没有系统调用完成一样的功能??有的下面将介绍一下系统调用dup2()
1.4.3 dup2()系统调用
dup2 是 Unix/Linux 系统中的一个系统调用,用于复制一个文件描述符,并将其关联到另一个指定的文件描述符。它的作用是将一个文件描述符的行为复制到另一个文件描述符上,这样可以实现文件描述符的重定向。这个系统调用通常用于进程的输入输出重定向,或者在程序中改变标准输入、输出或错误输出的目标。
- dup2原型:
int dup2(int oldfd, int newfd);
- oldfd:原始的文件描述符,表示要复制的文件描述符。
- newfd:目标文件描述符,表示要复制 oldfd 的行为到 newfd。如果 newfd 已经打开,则 dup2 会先关闭它,再将
oldfd 复制到 newfd。
返回值:
- 如果成功,返回新的文件描述符 newfd。
- 如果失败,返回 -1,并设置 errno 来指示错误。
功能与使用场景:
- dup2 的主要功能是将一个打开的文件描述符复制到另一个文件描述符上。它通常用于将程序的标准输入、输出或错误输出重定向到文件或其他设备。
常见用法:
标准输出重定向: 假设我们要将程序的输出重定向到文件,而不是打印到终端上。可以使用 dup2 将标准输出(文件描述符 1)重定向到文件。
#include
#include
#include
int main() {
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
return 1;
}
// 将标准输出重定向到文件
if (dup2(fd, 1) == -1) { // 1 是标准输出的文件描述符
perror("dup2");
return 1;
}
// 之后的 printf 将写入到 output.txt,而不是终端
printf("This will be written to output.txt
");
close(fd);
return 0;
}
这段代码首先打开一个文件 output.txt,然后使用 dup2 将标准输出(文件描述符 1)重定向到该文件。之后的所有标准输出(如 printf)都会写入到 output.txt 中,而不是打印到终端。原理与上述一致,不再重复。
小结:
dup2是一种非常有用的系统调用,特别是在需要进行输入输出重定向的场景中。它使得进程可以灵活地改变标准输入输出的目标,常用于程序中日志记录、调试、输出重定向等场合。了解 dup2 的工作原理,可以让你在编写与文件操作相关的程序时,更加高效和灵活。
Linux一切皆文件原理:
该图准确讲述的是在Linux等操作系统中,文件操作是如何通过结构体和函数指针实现的,以及不同设备如何通过不同的文件操作结构体来实现对文件的读写访问。可以达到屏蔽底层设备的差异。
二. 最后
本文深入介绍了文件描述符的概念及其在操作系统中的应用,特别是在系统文件I/O操作中的重要性。我们从 文件写入 开始,介绍了通过 C 语言的 fwrite() 和系统调用 write() 写入文件的基本方法。接着,详细讲解了 open() 系统调用的使用及其返回的文件描述符。文件描述符不仅仅是一个数字,它在内存中通过指针关联着文件属性。通过了解 标准输入(0)、标准输出(1)、标准错误(2),读者可以更清楚地理解文件描述符的分配和管理原理。文章重点介绍了 文件重定向,尤其是如何通过 dup2() 系统调用实现标准输入输出的重定向,深入揭示了文件描述符在实际应用中的灵活性和重要性。掌握这些技能,能显著提升对底层操作系统机制的理解和编程能力。
路虽远,行则将至;事虽难,做则必成
亲爱的读者们,下一篇文章再会!!! color{Red}亲爱的读者们,下一篇文章再会!!! 亲爱的读者们,下一篇文章再会!!!