[Linux] 逐层深入理解文件系统 (2)—— 文件重定向

news/2024/10/16 19:26:56 标签: linux, 运维, 服务器

标题:[Linux] 逐层深入理解文件系统 (2)—— 文件重定向

个人主页@水墨不写bug

(图片来源于网络)

目录

一、文件的读取和写入

 二、文件重定向的本质

1.手动模拟重定向的过程——把标准输出重定向到redir.txt

2.重定向函数dup2

3.命令行的重定向指令操作 

四、缓冲区的刷新策略


正文开始:

一、文件的读取和写入

        在深入理解文件系统(1)中,我们深入了解了文件的打开与关闭:操作系统通过文件描述符fd来对文件进行管理操作。此外,我们知道了文件的系统调用级别的打开方式,但是我们还没有了解如何读写文件的内容。

文件的写入同样有对应的系统调用:

 参数列表:

        fd:想要写入的文件的对应的文件描述符(对打开文件后会得到对应的文件描述符)

        *buf:这是一个我们定义的数组,是一个缓冲区。

        count:写入的字节数

返回值:

        成功,返回写入的字节数;失败,返回-1,并设置错误码。

文件的读取的系统调用:

参数列表:

        fd:想要读取的文件的对应的文件描述符

        *buf:把文件的内容读取到这个数组中。

        count:读取的字节数。

返回值:

        成功,返回读取的字节数;失败,返回-1,设置错误码。

 二、文件重定向的本质

        文件重定向的本质是在内核中改变文件描述符表的特定下标,和上层的语言层面的无关。

1.手动模拟重定向的过程——把标准输出重定向到redir.txt

        1)使用系统调用close关闭  标准输出(fd==1的文件)

        2)使用系统调用open打开redir.txt文件

        这个时候向标准输出打印信息就会被重定向到redir.txt文件中。

底层原理:

        首先我们关闭了标准输出(文件fd==1的文件),文件描述符标的下标1的这位置就被空出来了,由于文件描述符的分配规则是从小到大来分配的,当我们打开一个新的文件(redir.txt)这个文件的fd就会被分配为1。

        我们向标准输出写入,本质是向文件描述符表下标1的位置对应的内核级缓冲区写入,由于redir.txt分配到了1这个位置,所以向原来的标准输出的缓冲区写入,就是向redir.txt的缓冲区写入。

        这样就实现了文件的重定向。

 关闭标准输出后打开redir.txt:


2.重定向函数dup2

函数原型 :

简单总结:

        函数作用:让newfd对应的内容成为oldfd对应的内容的拷贝,本质是文件描述符下标所对应的内容的拷贝,并在结束的时候首先关闭newfd。

注意:

        1)oldfd不是一个有效的fd,则调用失败, newfd不会被关闭。

        2)oldfd是有效的fd,但是newfd和oldfd对应同一个文件,dup2函数不做任何事。

         dup2系统调用函数的意义在于可以让我们的重定向操作更加方便比如如果我们还是想要把标准输出重定向到redir.txt文件,那么只需要调用一个系统调用函数即可:

int fd1 = open("redir.txt",O_CREAT | O_WDONLY);

dup2(fd1,1);//这样就实现了把标准输出重定向到fd1

3.命令行的重定向指令操作 

       我们写出这样的一个代码:

#include<stdio.h>
int main()
{
    fprintf(stdout,"hello stdout\n");
    fprintf(stderr,"hello stderr\n");
    
    return 0;
}

        编译完运行:

         发现标准输出和标准错误都是显示器,这符合预期。


        对一个项目,我们可以把运行结果重定向到不同的日志文件中,方便后续维护:

        上面的操作是把运行结果(向显示器输出重定向到两个文件中):

        标准输出重定向到ok.txt;

        标准错误重定向到err.txt。 

       


如果不加fd直接重定向,会仅仅把标准输出重定向:

         标准错误仍被打印到显示器。

       


如果想把标准输出和标准错误重定向到一个文件,需要:


四、缓冲区的刷新策略

        在之前,我们知道在文件IO中 缓冲区存在两个,一个是C语言层面的缓冲区,一个是系统内核级缓冲区,但是我们并不了解缓冲区究竟是什么。

        是什么:缓冲区就是一段连续的内存空间。

        为什么:将系统调用和与硬件交互解耦,将C语言函数调用与系统调用解耦;提高刷新效率,从而在整体上提高IO效率,为用户提供高效的IO体验。

        怎么办:这就需要谈到不同的刷新策略问题了。

        对于不同的文件,缓冲区的刷新策略不同。常见刷新策略有如下几种:

                1)立刻刷新。比如调用fflush(stdout)(立刻刷新语言缓冲区)   ,   fsync(fd)(立刻刷新系统缓冲区)等。

                2)行刷新。显示器,因为显示器需要照顾用户查看习惯。

                3)  全缓冲,缓冲区写满才刷新。比如普通文件。

此外,对于特殊情况也会进行缓冲区刷新:

                a)进程退出,系统自动刷新缓冲区

在了解了缓冲区刷新策略之后,我们看看下面这样的场景:

#include<stdio.h>
#include<string.h>
#include<unistd.h>

int main()
{
    
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");

    const char* msg = "hello write\n";
    write(1,msg,strlen(msg));

    return 0;
}

         编译成功运行结果:

当我们重定向到log.txt,并打印出来:

         发现顺序不一样了,原因在于缓冲区向文件写入时刷新策略发生了变化:

        对一次是向显示器写入,刷新策略是按行刷新,由于三次打印有带有换行符,所以是按照代码的顺序打印输出。

        第二次是向文件写入,策略是全缓冲,所以尽管带有换行符,前两次写入都是暂时写入到了C语言级别的缓冲区内了,没有立刻被刷新到内核级缓冲区内。但是write是系统调用,直接刷新到了内核级缓冲区,所以最先被写入文件,后来在进程结束前,语言级缓冲区刷新到内核级,再被刷新到磁盘的文件中。

 这个场景还有一个变式:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>

int main()
{
    printf("hello printf\n");
    fprintf(stdout,"hello fprintf\n");

    const char* msg = "hello write\n";
    write(1,msg,strlen(msg));

    fork();
    return 0;
}

运行后直接输出到显示器:

        在意料之内。

但是重定向到log.txt文件并cat打印出来:

        发现了不对劲,其实造成这样的现象本质也是缓冲区刷新策略的变化导致的。

        write是系统调用,会直接刷新到内核级缓冲区。但是这个时候前两个语句已经被执行了,但是数据还存在于语言缓冲区内。这个时候fork创建了一个子进程,子进程的数据和父进程相同,在进程结束之前,两个进程都刷新了自己的语言级缓冲区,导致前两个打印语句被执行了两次! 


完~

未经作者同意禁止转载


http://www.niftyadmin.cn/n/5708429.html

相关文章

C++ : STL容器之vector剖析

STL容器之vector剖析 一、构造函数与赋值&#xff08;一&#xff09;默认构造&#xff08;二&#xff09;拷贝构造&#xff08;三&#xff09;几个相同值构造&#xff08;四&#xff09;迭代器构造&#xff08;五&#xff09;initializer_list 构造&#xff08;六&#xff09;赋…

在wpf 中 用mvvm 的方式 绑定 鼠标事件

在 wpf中, 如果遇到控件的 MouseEnter MouseLeave 属性时, 往往会因为有参数object sender, System.Windows.Input.MouseEventArgs e 很多人选择直接生成属性在后台, 破坏了MVVM, 这其实是不必要的. 我们完全可以用 xmlns:i“http://schemas.microsoft.com/xaml/behaviors” 完…

【C语言】数据类型

C语言常用数据类型 int 整型 4字节float 浮点型 4字节double 双精度浮点类型 8字节char 字符型 1字节构造类型&#xff1a;数组&#xff08;多个相同类型的变量组合&#xff09;构造类型&#xff1a;结构体&#xff08;多个不同类型的变量组合&#xff09; #include <stdi…

机器学习:opencv--人脸检测以及微笑检测

目录 前言 一、人脸检测的原理 1.特征提取 2.分类器 二、代码实现 1.图片预处理 2.加载分类器 3.进行人脸识别 4.标注人脸及显示 三、微笑检测 前言 人脸检测是计算机视觉中的一个重要任务&#xff0c;旨在自动识别图像或视频中的人脸。它可以用于多种应用&#xff0…

mysql学习教程,从入门到精通,SQL导入数据(44)

1.SQL 导出数据 以下是一个关于如何使用 SQL 导出数据的示例。这个示例将涵盖从一个关系数据库管理系统&#xff08;如 MySQL&#xff09;中导出数据到 CSV 文件的基本步骤。 1.1、前提条件 你已经安装并配置好了 MySQL 数据库。你有访问数据库的权限。你知道要导出的表名。…

微信小程序中的文件查看方法

获得后缀名判断类型,如果是图片用ex.previewImage(),如果是视频,用uni.previewMedia(),如果是word文档这些的,用 uni.downloadFile来下载资源后用 uni.saveFile来保存到本地,uni.openDocument来打开新的网页,如果打不开的话则返回说到PC端去打开 const lookFile (url) > {l…

MySQL中FIND_IN_SET(),IN()和LIKE区别

在 MySQL 中&#xff0c; FIND_IN_SET() 和 LIKE 都可以用于字符串的匹配查找&#xff0c;但它们有以下不同&#xff1a; 一、语法及功能 1. FIND_IN_SET(str,strlist) &#xff1a; 用于在以逗号分隔的字符串列表中查找特定字符串&#xff0c;并返回其位置。如果未找到则返…

DOS时代渐行渐远,而国产新型平台,却在悄然换代

可能就是有点儿念旧吧&#xff0c;没事干的时候&#xff0c;就爱把那些DOS年代的经典软件找出来摆弄摆弄。 虽说它们现在都是老古董了&#xff0c;但在咱们70后、80后的心里头&#xff0c;这些当年闪闪发光的软件宝贝&#xff0c;还是有着独一无二的位置&#xff0c;谁也替代不…