【Linux学习】文件描述符重定向缓冲区

目录

九.文件描述符

        9.1 文件描述符概念

        9.2 文件描述符的分配规则

        9.3 重定向

        9.3.1 常见的重定向操作

        9.3.2 重定向的原理

        9.4 缓冲区

        9.4.1 缓冲区概念

        9.4.2 缓冲区刷新策略

        9.4.3 C语言的缓冲区在哪里?


九.文件描述符

        9.1 文件描述符概念

在上一篇讲到基础IO时,我们说到open函数在打开一个文件后有一个int类型的返回值.我们将之称为文件描述符. 那么到底什么是文件描述符?它有什么作用呢?

概念:

文件描述符(File Descriptor,简称fd),是一个用于标识和操作文件或输入/输出设备的整数。在Unix-like操作系统中,包括Linux,文件描述符是对打开文件或I/O设备的引用.每个进程都有一个文件描述符表,它是一个数组,其中包含了该进程打开的文件或I/O设备的引用

也就是说:每一个进程都会维护一个独立的文件描述符表(File Descriptor Table)用来管理自己打开的文件

这个文件描述符表存储在哪里?

这里我们可以结合之前的知识猜到,文件描述表应该存储在进程的PCB结构中.

事实上,也确实如此.如上图所示,在进程对应的的task_struct结构中会存在一个files指针指向指向一个files_struct结构体

每个进程都会有单独维护的files_struct结构体,其专门用于跟踪对应进程与文件相关的信息.

files_struct有两个地方来管理所有打开的文件结构,即有两个数组来管理所有打开的文件结构

  • files_struct结构体中会包含一个struct fdtable *fdt,其指向fdtable(文件描述符表),fdtable同样也是一个结构体,其中包含** struct file fd,这个数组存储了每个文件描述符对应的文件指针
  • files_struct结构体中同样直接包含了一个fd_array用来直接存储了每个文件描述符对应的文件指针

在这两个数组中都储存着指向file结构体的指针,也就是说进程打开的文件在内核中会被描述为struct file结构体的实例,这个结构体包含了有关打开文件的各种信息,包括文件的状态、位置、访问模式等

总的来说,通过这种方式,内核能够更灵活地管理文件,而进程则通过文件描述符引用这些文件实例,而不必直接关心底层的文件结构和操作.这提供了一种高层次的抽象,简化了用户空间程序对文件的操作

在这里我们可以通过一步一步讲解fwrite()的运行过程,自上而下的梳理一遍

当我们在调用了fwrite()时,首先需要接收一个FILE *指针,表示要写入的文件的FILE结构,而对应的FILE结构体中存有文件描述符(fd),这个时候fwrite()会底层调用write(fd,.....),来执行操作系统内部的write(),它能找到进程的task_struct,再通过里面的files指针再找到files_struct,而files_struct中的fd_array[fd]便指向打开的文件的file结构体,这时候便内存文件便找到了,也就可以后续操作.

注意:

  • 文件的fd实际上是fd_array数组的对应下标
  • C语言中的FILE结构体和内核中的file结构体是两个不同的概念,需要区分

        9.2 文件描述符的分配规则

在上文讲到基础IO时,我们讲到C语言的程序会默认打开三个流,分别是stdin,stdout,stderr.

这时候结合Linux系统的设计哲学:一切皆文件,我们可以知道,在C语言编写的程序运行后,会默认打开对应的三个文件

这时候,我们可以编写下面这个简单的程序来验证.

也就是说fd_array数组当中下标为0、1、2的位置已经被占用了,之后打开的文件都是重3之后开始

那么如果我们在进行文件操作之前将默认打开的文件关掉会怎么样呢?

这里我们将下标为1的文件先关掉进行验证,给程序先加上这样一句代码:

close(0);

这时候我们再此运行观察

这时我们可以得出 文件描述符的分配规则:

顺序分配: 一般情况下,文件描述符会按照顺序分配,即从最小的未使用的整数开始分配

        9.3 重定向

9.3.1 常见的重定向操作

重定向是指将一个文件描述符与另一个文件描述符或文件关联起来,从而改变输入或输出的方向。在 Unix-like 操作系统中,包括 Linux,重定向是一种强大的机制,允许在命令行中灵活地管理输入和输出。

以下是常见的重定向操作符:

1.>:输出重定向

        示例:command > output.txt

        描述:将命令的标准输出重定向到文件 output.txt 中。如果文件不存在,则会创建文件;如果文件已存在,将会覆盖其中的内容。

2.>>:追加输出重定向

        示例:command >> output.txt

        描述:将命令的标准输出追加到文件 output.txt 中。如果文件不存在,则会创建文件;如果文件已存在,内容将会被追加到文件末尾。

3.<:输入重定向

        示例:command < input.txt

        描述:将文件 input.txt 中的内容作为命令的标准输入。命令将读取文件的内容而不是从键盘读取。

4.|:管道

        示例:command1 | command2

        描述:将 command1 的标准输出通过管道传递给 command2 的标准输入。这使得两个命令可以协作处理数据。

5.2>:错误输出重定向

        示例:command 2> error.txt

        描述:将命令的错误输出重定向到文件 error.txt 中。类似于 >,但是针对错误输出。

6.&>&>>:合并输出和错误输出重定向

        示例:command &> output_and_error.txtcommand &>> output_and_error.txt

        描述:将命令的标准输出和错误输出合并,并重定向到文件 output_and_error.txt 中。

9.3.2 重定向的原理

重定向的本质是通过操作系统提供的文件描述符机制,动态地改变进程的输入和输出源

dup2:

 #include<unistd.h>

 int dup2(int oldfd, int newfd);

函数返的作用:dup2的作用是将 newfd 指向的文件描述符关闭,然后将 oldfd 复制到 newfd,使得它们指向同一个打开的文件、套接字或管道。如果 newfd已经打开,dup2 会首先关闭它

这个系统调用的主要应用场景之一是在重定向中,将一个文件描述符重定向到另一个文件描述符。例如,在 shell 命令中,使用 > 这样的操作符进行输出重定向时,实际上就是使用了dup2

函数返回值: dup2如果调用成功,返回newfd,否则返回-1。

使用dup2时,我们需要注意以下两点:

  • 如果oldfd不是有效的文件描述符,则dup2调用失败,并且此时文件描述符为newfd的文件没有被关闭。
  • 如果oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值,则dup2不做任何操作,并返回newfd。

借助dup2(),我们可以试着模拟一下重定向的过程

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {int fd = open("log.txt", O_CREAT | O_RDWR,0666);if (fd < 0) {perror("open");return 1;}close(1);dup2(fd, 1);for (;;) {char buf[1024] = { 0 };ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0) {perror("read");break;}printf("%s", buf);fflush(stdout);}return 0;
}

通过这样一个简单的程序我们便可以将标准输出(文件描述符1)重定向到一个文件("log.txt"),并不断地从标准输入(文件描述符0)读取数据,然后将其写入到文件中.

        9.4 缓冲区

9.4.1 缓冲区概念

缓冲区(Buffer)是计算机系统中常见的概念,它是一块用于临时存储数据的内存区域。缓冲区在各种计算机应用中都起着关键作用,从文件输入输出到网络通信,都涉及到对数据的缓冲和管理

下面是缓冲区的常见作用:

  1. 提高效率: 缓冲区的存在可以减少对底层资源的频繁访问,从而提高数据传输的效率。通过按块读写数据,而不是逐个字节进行操作,可以减少读写次数,降低系统开销

  2. 平滑数据流: 缓冲区可以平滑数据流,使得数据以块的形式传输。这对于文件输入输出、网络通信等场景特别有用,有助于减少频繁的小规模数据传输,提高整体性能

  3. 优化磁盘和网络访问: 在文件系统中,文件缓冲区可以优化磁盘访问,减少磁盘I/O的次数。在网络通信中,网络缓冲区可以优化数据在网络上传输的效率

  4. 应对不同速度的设备: 缓冲区可以协调不同速度的设备之间的数据传输,确保数据能够以适当的速率流动,避免了生产者和消费者之间的速度不匹配问题

  5. 提高用户体验: 输入缓冲区允许用户按块输入数据,提高了用户体验。输出缓冲区允许程序按块输出数据,减少对屏幕或文件的频繁写入,提高了响应速度

  6. 支持流式处理: 缓冲区使得数据能够以流式的方式进行处理,而不是一次性处理整个数据集。这对于处理大数据或实时数据流非常重要

  7. 适应不同工作负载: 缓冲区的存在使得系统能够更好地适应不同的工作负载。它可以根据需要动态调整缓冲策略,以满足不同场景下的性能需求

  8. 错误处理: 缓冲区可以提供一定程度的错误处理机制。例如,在网络通信中,如果无法立即发送所有数据,缓冲区可以保存部分数据,等待合适的时机重新发送

9.4.2 缓冲区刷新策略

C语言中一般有以下几种缓冲区刷新策略:

行缓冲(Line Buffering):在行缓冲模式下,缓冲区在遇到换行符 \n 时自动刷新。也就是说,当遇到换行符时,缓冲区中的数据会被立即写入文件

全缓冲(Fully Buffered):在全缓冲模式下,缓冲区满时会触发刷新,此时缓冲区中的数据才会被写入文件

手动刷新缓冲区:使用 fflush() 函数手动刷新缓冲区。这对于确保数据在特定时刻被写入文件很有用

关闭文件时刷新:当文件关闭时,C库会自动刷新缓冲区


一般而言,行缓冲的设备文件 --- 显示器

全缓冲的设备文件 --- 磁盘文件

极端情况,你是可以自定义规则的 --- 这时候我们可以自己使用fflush()来手动刷新

但所有的设备,永远倾向于全缓冲 --> 缓冲区满了再刷新 --> 需要更少次数的IO操作 -->更少次数的外设访问(相当于提高了整机效率).

有同学可能有疑问,比如10行数据,每一行有100个字节,虽然10行最后再一起刷新,只进行了一次的外设访问,但是数据量很多啊,1000个字节,而按行刷新虽然刷新了10次,但每次数据量少啊,那为什么外设访问次数越少越好呢?

这是因为和外部设备IO的时候,数据量的大小不是主要矛盾,你和外设预备IO的过程是最耗费时间的.


9.4.3 C语言的缓冲区在哪里?

缓冲区在哪里?我们所说的这个缓冲区是由操作系统维护还是语言维护?

为了回答这个问题,我们可以先感受下面这段代码

#include<stdio.h>  
#include<fcntl.h>  
#include<sys/stat.h>  
#include<sys/types.h>  
#include<unistd.h>  
#include<string.h>
int main()
{//C语言提供的接口    printf("hello,printf\n");fprintf(stdout, "hello,fprintf\n");const char* s = "hello,fputs\n";fputs(s, stdout);//系统接口    const char* ss = "hello,write\n";write(1, ss, strlen(s));//创建子进程fork();
}

当我们把代码编译运行后:

此时当我们将程序的输出 重定向到另外的文件时

 这时候我们可以先预测一下输出的结果

最后我们会惊讶的发现一个现象:所有C语言提供的函数都输出了两次 .系统接口的只输出了一次

这时候我们改一下我们原本的代码,在write()与fork()之间加上一句fflush()

这时候再进行一次上述操作

这次我们发现结果又发生了变化,这是为什么呢?


这时候我们得回到最初的问题:缓冲区是由操作系统维护还是语言维护?

这时大家也许会猜到 我们所说的这个 缓冲区其实是由C语言自己维护的

在源代码中,你可以找到stdio.h 头文件以及其中包含的FILE结构体的定义,而FILE结构体里面不仅封装了fd,而且包含了该文件对应的语言层的缓冲区结构_IO_FILE。

_IO_FILE的内部结构大致如下:

/* In glibc, FILE is a typedef, defined in FILE struct and definedelsewhere.  */
struct _IO_FILE {int _flags;           /* High-order word is _IO_MAGIC; rest is flags. *//* The following pointers correspond to the C++ streambuf protocol. */char *_IO_read_ptr;   /* Current read pointer */char *_IO_read_end;   /* End of get area. */char *_IO_read_base;  /* Start of putback+get area. */char *_IO_write_base; /* Start of put area. */char *_IO_write_ptr;  /* Current put pointer. */char *_IO_write_end;  /* End of put area. */char *_IO_buf_base;   /* Start of reserve area. */char *_IO_buf_end;    /* End of reserve area. *//* The following fields are used to support backing up and undo. */char *_IO_save_base; /* Pointer to start of non-current get area. */char *_IO_backup_base;/* Pointer to first valid character of backup area */char *_IO_save_end; /* Pointer to end of non-current get area. */struct _IO_marker *_markers;struct _IO_FILE *_chain;int _fileno;int _flags2;__off_t _old_offset; /* This used to be _offset but it's too small.  *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];_IO_lock_t *_lock;__off64_t _offset;/* Wide character stream stuff.  */struct _IO_codecvt *_codecvt;struct _IO_wide_data *_wide_data;struct _IO_FILE *_freeres_list;void *_freeres_buf;size_t __pad5;int _mode;/* Make sure we don't get into trouble again.  */char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};

回到我们的最初的代码

我们直接运行程序是向显示器中打印,采用的是行刷新策略,而我们重定向到文件中,向文件中打印,便成了全缓冲策略.

1.如果是向显示器中打印,那么采用的是行刷新策略,那么最后执行fork的时候,所有的数据都已经刷新完成了,此时再执行fork就没有意义了.

2.如果对程序进行了重定向,即此时要向文件中打印,此时刷新策略便隐式的变成了全缓冲

遇到\n换行符便不会再触发刷新.

fork的时候,一定是函数已经执行完了,但是数据还没有刷新! 这些数据在当前进程对应的C标准库中的缓冲区里. 而这些数据是父进程的.

fork之后子进程和父进程执行return 0前,父进程和子进程实际上是共享相同的缓冲区的.但是,当子进程执行 return 0 后,由于任一方进程退出时触发了关闭文件时刷新策略,此时可能触发了对标准输出缓冲区的刷新.这时,写时复制拷贝机制会起作用,确保子进程得到自己的缓冲区副本,从而避免对父进程缓冲区的影响。这样就有了两份数据,然后分别输出到文件中。

所以就出现了C语言标准库输出的函数打印了两次,而系统接口打印了一次。

因为系统接口是直接写入到了文件中,而不用经过缓冲区。


而在我们更改之后的代码中,fflush已经强制将缓冲区的内容刷新了出来,此时缓冲区已经是空的了,然后再执行fork,父子进程缓冲区都是空的,所以也没有数据刷新了,这样才各自只打印了一条语句

此时,更加让我们确信了一个事实:缓冲区一定不是操作系统提供的!而是由C语言标准库提供的!因为如果是操作系统提供的,那么这个系统接口也应该输出两次,而不是只有一次

注意:

缓冲区的概念并不是特定于 C 语言,而是一种广泛应用于计算机科学和编程的概念。缓冲区是一块用于临时存储数据的内存区域,其目的是提高数据传输的效率。在不同的编程语言和操作系统中,都存在类似的概念

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

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

相关文章

【C++】: unordered_map的使用

1、概念 key 键值的类型。unordered_map中的每个元素都是由其键值唯一标识的。 T 映射值的类型。unordered_map中的每个元素都用来存储一些数据作为其映射值。 Hash 一种一元函数对象类型&#xff0c;它接受一个key类型的对象作为参数&#xff0c;并根据该对象返回size_t类型…

Flask SocketIO 实现动态绘图

Flask-SocketIO 是基于 Flask 的一个扩展&#xff0c;用于简化在 Flask 应用中集成 WebSocket 功能。WebSocket 是一种在客户端和服务器之间实现实时双向通信的协议&#xff0c;常用于实现实时性要求较高的应用&#xff0c;如聊天应用、实时通知等&#xff0c;使得开发者可以更…

java系列:什么是SSH?什么是SSM?SSH框架和SSM框架的区别

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 什么是SSH&#xff1f;什么是SSM&#xff1f;SSH框架和SSM框架的区别 前言一、什么是SSH&#xff1f;1.1 Struts2具体工作流程&#xff1a;Struts2的缺点&#xff1a; 1.2 Sp…

【Linux】firewall防火墙配置-解决Zookeeper未授权访问漏洞

背景&#xff1a; zookeeper未授权访问漏洞&#xff0c;进行限制访问&#xff0c;采用防火墙访问策略 配置步骤&#xff1a; ##查看firewall配置清单 firewall-cmd --list-all ##查到为关闭态&#xff0c;启动防火墙 systemctl start firewalld ## 添加端口&#xff0c;这里…

Python入门06布尔值

目录 1 什么是布尔值2 怎么生成布尔值3 在控制程序中使用布尔值4 数据过滤、排序和其他高级操作总结 1 什么是布尔值 首先我们要学习一下布尔值的定义&#xff0c;布尔值是一种数据类型&#xff0c;它只有两个可能的值&#xff1a;True&#xff08;真&#xff09;或 False&…

rabbitmq消息队列实验

实验目的&#xff1a;实现异步通信 实验条件&#xff1a; 主机名 IP地址 组件 test1 20.0.0.10 rabbitmq服务 test2 20.0.0.20 rabbitmq服务 test3 20.0.0.30 rabbitmq服务 实验步骤&#xff1a; 1、安装rabbitmq服务 2、erlang进入命令行&#xff0c;查看版本 …

瑜伽学习零基础入门,各种瑜伽教学方法全集

一、教程描述 练习瑜伽的好处多多&#xff0c;能够保证平衡健康的身体基础&#xff0c;提升气质、塑造形体、陶冶情操&#xff0c;等等。本套教程是瑜伽的组合教程&#xff0c;共由33套视频教程组合而成&#xff0c;包含了塑身纤体&#xff0c;速效瘦身&#xff0c;四季养生&a…

双通道 H 桥 5V 4A驱动芯片

SS6951A 为电机一体化应用提供一种双通道集成电机驱动方案。SS6951A 有两路 H 桥驱动&#xff0c;每个 H 桥可提供最大峰值电流 4.0A&#xff0c;可驱动两个刷式直流电机&#xff0c;或者一个双极步进电机&#xff0c;或者螺线管或者其它感性负载。双极步进电机可以以整步、2 细…

字节大佬整理测试用例编写规范

目录 1.1目的 1.2使用范围 二 测试用例编写原则 2.1系统性 2.2连贯性 2.3全面性 2.4正确性 2.5符合正常业务惯例 2.6仿真性 2.7容错性&#xff08;健壮性&#xff09; 三 测试用例设计方法 3.1 等价类划分法&#xff1a; 3.2 边界值分析法&#xff1a; 3.3 因果图…

Nginx基线检查

扩展知识: Nginx主配置文件:/etc/nginx/nginx.conf 这是Nginx的主要配置文件,用于配置全局的设置、HTTP块、事件处理、邮件等内容。 打开并编辑配置文件 vim /etc/nginx/nginx.conf 一、关于禁止显示服务器版本号和操作系统版本信息: 简介: 在错误页面和响应头中显示…

多路转接<select>和<poll>使用手册

select int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); 参数说明 返回值 返回值>0 表示成功返回可访问的文件描述符个数&#xff0c;返回值0 表示标识等待时间到期返回值<0 表示出现错误…

[蓝桥杯习题]———位运算、判断二进制1个数

⭐Hello!这里是欧_aita的博客。 ⭐今日语录&#xff1a;行动胜过一切。 ⭐个人主页&#xff1a;欧_aita ψ(._. )>⭐个人专栏&#xff1a; 数据结构与算法&#xff08;内含蓝桥杯习题&#xff09; MySQL数据库 位运算 位运算位运算的定义简单运用 实战刷题题目思路代码实现声…

Apipost推出IDEA插件,代码写完直接调试

IDEA是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它可以帮助开发人员更加高效地编写、调试和部署软件应用程序。我们在编写完接口代码后需要进行接口调试等操作&#xff0c;一般需要打开额外的调试工具。 今天给大家介绍一款IDEA插件&#xff1a;Api…

嵌入式数据传输及存储的C语言实现

各种类型的数据传输和存储就涉及到大小端的问题&#xff0c;首先要简单说下芯片的大小端问题&#xff0c;这里主要讨论Cortex-M内核。 M内核支持大端或者小端&#xff0c;实际应用中大部分内核都是小端。以STM32为例&#xff0c;全部都是小端&#xff0c;而且是芯片设计之初就固…

如何使用JMeter测试https请求

HTTP与HTTPS略有不同&#xff0c;所以第一次使用JMeter测试https请求时遇到了问题&#xff0c;百度一番后找到解决方法&#xff1a;加载证书。 下面内容主要记录这次操作&#xff0c;便于后续参考&#xff1a; 操作浏览器&#xff1a;谷歌 &#xff08;1&#xff09;下载被测…

速达软件全系产品 RCE漏洞复现

0x01 产品简介 速达软件是中小企业管理软件第一品牌和行业领导者,是128万家企业用户忠实的选择。14年来速达致力于进销存软件、ERP软件、财务软件、CRM软件等管理软件的研发和服务。 0x02 漏洞概述 速达软件全系产品存在任意文件上传漏洞&#xff0c;未经身份认证得攻击者可以…

推荐几款免费的智能AI伪原创工具

在当今信息快速传播的时代&#xff0c;创作者们常常为了在激烈的竞争中脱颖而出而苦苦挣扎&#xff0c;而其中的一项挑战就是创作出独具创意和独特性的内容。然而&#xff0c;时间有限的现实让很多人望而却步。在这个背景下&#xff0c;免费在线伪原创工具成为了创作者们的得力…

初始Redis(入门篇)

目录 什么是Redis Redis特性 速度快 丰富的功能 客户端语言多 持久化 主从复制 Redis可以做什么 缓存 排行榜系统 计数器应用 消息队列系统 Redis安装 centos7安装 Redis重要文件 Redis的使用 Redis通用命令 set get keys exists del expire 什么是Redi…

基于Java+SpringBoot+Vue3+Uniapp+TypeScript(有视频教程)前后端分离的求职招聘小程序

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

机器人AGV小车避障传感器测距

一、A22超声波传感器 该模块是基于机器人自动控制应用而设计的超声波避障传感器&#xff0c;针对目前市场上对于超声波传感器模组盲区大、测量角度大、响应时间长、安装适配性差等问题而着重设计。 具备了盲区小、测量角度小、响应时间短、过滤同频干扰、体积小、安装适配性高…