前面对于文件有了基本认知,那么基于前面认知本篇总结的是文件重定向的含义极其本质

文件内核对象

首先理解一下file内核对象什么,回顾一下下面这张图

在这里插入图片描述
站在用户的角度,对于文件操作有这些诸如readwrite这些系统调用,也有这些系统调用进行了一定的封装后诞生的C语言库函数,这些系统调用的内部会在内存中进行一系列操作

首先,在进程运行起来后首先会创建出进程的PCB,这是一定的,其次,进程的PCB中有这么一块专门的区域名字叫作fd_array[],它的本质是一个指针数组,它会指向一个一个结构体,而每一个结构体中存储的是关于这个文件信息这个结构体叫做struct file,而前面所说的文件描述符fd,本质上就是这个指针数组下标操作系统默认会为用户打开三个文件,分别是标准输入标准输出标准错误,关于这三个文件的用处就是本文要重点解析内容,也是理解文件重定向所必须要理解的内容核心

这个file结构就是用来管理这些系统调用中需要的一些操作,以读写函数为例假设现在用户使用了读的系统调用,那么落实到内存中,进程的PCB就会根据在系统调用中这个fd索引找到对应的文件结构体,进而就能找到这个文件的信息,而接下来关于这个文件的一系列操作,都要借助文件缓冲区来帮助,还是以读这个系统调用为例,当现在需要进行读取数据时候,由冯诺依曼体系可以知道CPU不会和外设打交道,也就是说在磁盘中的文件信息是不能直接CPU进行交互的,因此要首先加载到内存中,因此就会加载到文件缓冲区中,而此时进程就会从文件缓冲区读取需要信息,进而给用户或者是其他函数一个反馈

数据也是一个道理,但是不管是写数据还是数据,由于冯诺依曼体系原因,是一定要先加载到内存中去的,无论读写,都需要加载到文件缓冲区

因此可以得到一个观点:应用层对于数据的读写,本质上是将内核缓冲区的数据进行来回拷贝

由此结束了关于file内核对象的理解,下面开始进行下一个模块的理解

fd分配问题

来看示例代码

void testfd0()
{
    char buffer[1024];
    ssize_t s = read(0, buffer, 1024);
    buffer[s - 1] = 0;
    printf("%sn", buffer);
}

调用了系统调用接口,从标准输入流中读取信息,再进行输出

从中可以得出一个结论:进程默认打开了012三个文件,用户可以直接使用012进行数据的访问

// fd分配原则
void testfd1()
{
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    printf("fd -> %dn", fd);
    close(fd);
}

输出:

[test@VM-16-11-centos File]$ ./myfile 
fd -> 3

这是符合预期的,因为系统默认打开三个文件,分别占用0,1,2这三个位置,那如果把这三个文件关掉一个呢?

void testfd2()
{
    close(0);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    printf("fd -> %dn", fd);
    close(fd);
}

输出:

[test@VM-16-11-centos File]$ ./myfile 
fd -> 0

由此可以推测出,文件描述符分配规则是:寻找最小的,没有被使用的数据位置分配指定打开文件

重定向的现象

关于什么重定向,用下面的demo来做一个示范

// 重定向
void redir1()
{
    close(1);
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if(fd < 0)
    {
        perror("open failn");
        exit(1);
    }
    printf("fd-> %dn", fd);
    printf("stdout->fd: %dn", stdout->_fileno);
    fflush(stdout);
    close(fd);
}

运行结果是,在屏幕没有任何信息,但在log.txt中居然出现了输出信息

在这里插入图片描述
那么这是为什么呢?从代码的目的来看,代码一开始就关闭标准输出流文件,其次又打开了一个新的fd文件,那么根据前面fd
分配规则这个打开fd下标就是1,因为标准输出的下标1,这是可以确定

其次,代码的下一步打印了一些信息,但是这些信息都被打印到了文件中,但是理想状态下,信息应该打印显示器上,这说明了此时printf这个函数的打印发生了一些变化,原本它的打印目标显示器,现在却打印到了文件中,而从输出的第二条信息也能看出,现在要打印一下stdout的文件描述符多少,此时打印出来的结果1,而我们打开的文件的下标也是1,那么可不可以说,这个新打开的文件描述符取代了原来的stdout呢?

答案确实是这样,与其说stdout在系统中只认文件描述符,不如说它只认1,在打印的时候它只会找1,哪里有1它就打印到哪里,在正常的逻辑中,stdout对应的文件描述符1,而1这个文件描述符会在进程启动时候自动打开标准输出文件,因此在进程执行printf这样的函数的时候,就会把信息打印到显示器上,这样就能让我们看到显示器上的内容,这是符合预期的

但是上面的示例代码中,却有了一个偷梁换柱感觉的操作,把原本文件描述符为1的文件换成了log.txt,此时再打印的时候,进程只会机械性的去寻找文件描述符为1的文件,因此就忽略了这个文件本身其实已经不是它了,而这恰恰也证明了在之前就一直输出的一个观点:Linux下一切皆文件,不管是显示器还是键盘还是网卡等等外部设备操作系统都有自己方法能把它变成内存中的一部分,这个方法前面也有提及,就是VFS技术

用下面的这张图来说明一下刚才上面代码的一系列操作原理

在这里插入图片描述
重定向的本质,就是修改特征文件fd的下标内容

上层的fd不变,变化的是底层fd指向的内容,也就是所谓文件描述符级别的数组内容的拷贝

那这样的写法还是太奇怪了,每次都要把一个文件关闭打开一个新的文件,作为系统理应给操作者提供这样替换文件描述符的系统调用,事实上也确实提供了这样的系统调用

dup2

DUP(2)                                                      Linux Programmer's Manual                                                     DUP(2)

NAME
       dup, dup2, dup3 - duplicate a file descriptor

SYNOPSIS
       #include <unistd.h>

       int dup(int oldfd);
       int dup2(int oldfd, int newfd);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <fcntl.h>              /* Obtain O_* constant definitions */
       #include <unistd.h>

       int dup3(int oldfd, int newfd, int flags);

DESCRIPTION
       These system calls create a copy of the file descriptor oldfd.

       dup() uses the lowest-numbered unused descriptor for the new descriptor.

       dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:

       *  If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.

       *  If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.

       After  a  successful  return from one of these system calls, the old and new file descriptors may be used interchangeably.  They refer to
       the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset  is  modi‐
       fied by using lseek(2) on one of the descriptors, the offset is also changed for the other.

       The  two  descriptors do not share file descriptor flags (the close-on-exec flag).  The close-on-exec flag (FD_CLOEXEC; see fcntl(2)) for
       the duplicate descriptor is off.

简单来说,就是用oldfd替换newfd,保留下来的是oldfd,那么上面的代码就可以被改良成这样:

void redir2()
{
    int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
    if(fd < 0)
    {
        perror("open failn");
        exit(1);
    }
    dup2(fd, 1);
    printf("fd-> %dn", fd);
    printf("stdout->fd: %dn", stdout->_fileno);
    fflush(stdout);
    close(fd);
}

显然这是可行的

重定向的使用

重定向其实并不陌生,在之前的学习中已经用过重定向,只是那是还没有建立起来一个基础的概念,先看下面的指令演示

[test@VM-16-11-centos File]$ echo "hello linux" > log.txt 
[test@VM-16-11-centos File]$ echo "hello linux"
hello linux

这段指令的含义就是,把hello linux重定向到log.txt中,其实这样的操作符还有下面的这几种,一一进行介绍

>

这个操作符表示的是输出重定向,意思就是把内容输出到某个文件中,有些类似于以w方式打开一个文件并进行写入

>>

这个操作符表示的是追加重定向,意思就是把内容追加输出到某个文件中,有些类似于append方式进行写入

<

这个操作符表示的是输入重定向,表示把原来的内容输入输入到某个文件中,相当于是替换了标准输入流的文件

标准输出和标准错误

前面的知识已经足以理解为什么要有标准输入和标准输出,但是还有一个问题有待解决,标准错误的意义是什么呢?难道标准输出的信息还不够吗?

答案是确实不够,在对于大型项目时候,会有很多的输出信息,这些输出信息有些是正常信息,有些是异常信息,而对于开发者来说他们需要的是错误信息,因此对于如何获取错误信息就显得至关重要,于是标准错误流信息就诞生了,对于正常来说可能没有太多的感觉,但是实际上,没有感觉的原因是因为标准错误和标准输出的文件对象都是显示器,而实际上这是可以被替换的,基于这个原理可以做出下面的测试代码

void teststderr()
{
    printf("this is normal messagen");
    perror("this is error messagen");
}
[test@VM-16-11-centos File]$ make
gcc -o myfile myfile.c -std=c99
[test@VM-16-11-centos File]$ ./myfile 1>out.txt 2>error.txt

利用上面的原理可以写出这样的测试代码,把正确信息存储到一个文件中,把错误信息存储到另外一个文件中,这样就能知道哪里是错误哪里是正确

关于其他重定向

  1. 1>&amp;2意思是把标准输出重定向到标准错误
  2. 2>&amp;1意思是把标准错误输出重定向到标准输出

原文地址:https://blog.csdn.net/qq_73899585/article/details/134699494

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_38974.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注