Linux中的文件描述符和重定向

这篇具有很好参考价值的文章主要介绍了Linux中的文件描述符和重定向。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

文件描述符概念

我们在使用系统调用进行文件操作的时候
image.png
image.png
这里的open返回值就是一个文件描述符简称fd。
文件描述符其实就是一个从3开始的小整数,文件描述符是小整数的原因是因为文件描述符实际是文件描述符表这个数组的下标。
image.png
为什么从3开始,是因为系统默认打开了三个文件,stdin,stdout,stderr。

分配规则

进程打开文件之后给文件分配文件描述符的规则是:从小到大,按顺序寻找最小的没有被占用的fd。
代码演示:

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



int main()
{
    close(0);
    int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC, 0666);
    
    printf("fd:%d\n",fd);

    close(fd);
    return 0;
}

image.png
因为操作系统默认打开的三个文件,所以我们关闭一个之后再打开文件,此时我们的文件描述符就会被分配成0.同理如果关闭0 和 2 那么此时就会给fd分配最小的0.

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



int main()
{
    // close(0);
    close(1);
    int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC, 0666);
    
    printf("fd:%d\n",fd);

    close(fd);
    return 0;
}

但是当我们1关闭也就是stdout封装的文件描述符,此时运行程序会发现什么都没有输出。因为printf默认是向stdout里输出的,从系统调用的角度看就是向文件描述符为1的文件里输出的。但是此时我们打开的文件log.txt的文件描述符是1,所以理论上打印的内容应该在log.txt里面
image.png
但是实际并没有,这是因为字符串在向普通文件中写入和向显示器中写入的时候,缓冲区的刷新策略不同。所以我们需要手动刷新缓冲区。

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



int main()
{
    // close(0);
    close(1);
    int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC, 0666);
    
    printf("fd:%d\n",fd);
    fflush(stdout);
    close(fd);
    return 0;
}

image.png
刷新文件缓冲区之后字符串成功写入了文件中
刷新缓冲区的时候参数是stdout,因为stdout封装的文件描述符就是1,而此时1描述的文件是log.txt所以数据就被刷新到文件中了。

重定向

上面这种情况,本来要向stdout输出后来却输出到了文件log.txt中,这种特性就是输出重定向。

重定向的本质

上层用的fd不变,在内核中更改fd在文件描述符表中对应的struct file*指针。

重定向的类型
  1. 输出重定向 >
  2. 追加重定向 >>
  3. 输入重定向 <

在shell命令行中可以直接使用重定向,下面是演示:
image.png
输出重定向,将原本要输出到显示器的内容重定向到了test.txt文件之中
image.png
追加重定向
image.png
输出重定向

重定向的原理

image.png
这是不进行重定向时我们打开的文件被文件描述符3所指向。此时我们进行输出重定向,原理实际就是将内核数据结构files_struct 中指向myfile的这个指针拷贝到下标为1处,也就是标准输出的文件描述符处。使得文件描述符1指向的文件就是新打开的myfile。
image.png

重定向的实现

通过使用close关闭文件,再open打开文件的方式来实现重定向不仅麻烦而且不好控制,所以我们可以使用一个系统调用dup2()
image.png
image.png
dup2的返回值是文件描述符
image.png
由图可知dup2的功能是将oldfd文件描述符对应的文件指针拷贝到了newfd所对应的位置并覆盖了原来newfd对应的文件指针,如果有需要会先关闭newfd所对应的文件。
也就是如果我们要是实现输出重定向,需要使用我们打开的文件的文件指针去覆盖掉stdout的文件描述符所在的位置。
代码演示:

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



int main()
{
    // close(0);
    //close(1);
    int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC, 0666);
   
    dup2(fd,1);
    printf("fd:%d\n",fd);
    fflush(stdout);
    close(fd);
    return 0;
}

image.png
此时新打开文件的fd还是3,但是我们已经完成了重定向,将本来应该打印到显示器上的数据打印到了文件中,因为dup2实现重定向的原理就是用文件描述符表中下标为3的内容覆盖了下标为1的内容实现输出重定向。

代码实现shell支持重定向

下面我们手写一个shell让它支持重定向功能。
首先输入一般是以下几种情况:
ls -a -l > log.txt
ls -a -l >> log.txt
cat < log.txt
所以我们首先要做的就是输入检测,将输入的字符串以中间的重定向符号为分割,将重定向符号置为\0,然后去掉多余空格剩下的两部分分别是指令操作和文件名。
然后在子进程内部就可以按照重定型符号以不同的方式打开文件,然后进行重定向,最后使用程序替换执行指令即可。

#include<stdio.h>
#include<unistd.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<errno.h>


#define RMSPACE(start) do{ while(isspace(*start)) ++start; }while(0)

enum Redir 
{
    NONE_REDIR = 0,
    INPUT_REDIR,
    OUT_REDIR,
    APPEND_REDIR
};

char* FileName = NULL;
int RedirWay = NONE_REDIR;

char GetsChar[1024];
char* Str[64];
void StrPart(char* s)
{
    char* start = s;
    char* end = s + strlen(s);
    while(start < end)
    {
        if(*start == '>')
        {
            *start = '\0';
            start++;
            RedirWay = OUT_REDIR;
            if(*start == '>')
            {
                start++;
                RedirWay = APPEND_REDIR;
            }
            RMSPACE(start);
            FileName = start;
            break;
        }
        else if(*start == '<')
        {
            *start = '\0';
            start++;
            RMSPACE(start);
            FileName = start;
            RedirWay = INPUT_REDIR;
            break;
        }
        else 
        {
            start++;
        }
    }
}


int main()
{
    while(1)
    {
        //每次循环重置重定向方式和文件名
        RedirWay = NONE_REDIR;
        FileName = NULL;
        printf("[%s@%s 路径]$",getenv("LOGNAME"),getenv("HOSTNAME"));
        //读取整行输入的指令
        char* s = fgets(GetsChar, sizeof(GetsChar) - 1,stdin);
        assert(s);
        
        //将读入的\n置为0
        GetsChar[strlen(s) - 1] = 0;

        //将字符串按重定向符分割
        StrPart(s);


        //将读入的字符串按空格分割
        Str[0] = strtok(GetsChar," ");
        int i = 1;

        //对ls命令添加上颜色
        if(Str[0] != NULL && (strcmp(Str[0],"ls") == 0))
        {
            Str[i++] = (char*)"--color=auto";
        }
        //将字符串分割连同最后结尾NULL一同输入Str数组
        while((Str[i++] = strtok(NULL," ")));
        
        //内建命令cd
        //不用子进程使用进程替换来执行,而是使用系统调用来完成的命令就叫做内建命令
        if(Str[0] != NULL && (strcmp(Str[0],"cd") == 0))
        {
            if(Str[1] == NULL)
            {
                char* path = getenv("HOME");
                chdir(path);
            }
            else 
            {
                chdir(Str[1]);
            }
            continue;
        }
#ifdef TEST
        for(int i = 0;Str[i]; i++)
        {
            printf("%d:%s\n",i,Str[i]);
        }
#endif
        
        pid_t id = fork();
        assert(id != -1);

        //子进程
        if(id == 0)
        {
            switch(RedirWay)
            {
                case NONE_REDIR:
                    break;
                case INPUT_REDIR:
                    {
                        int fd = open(FileName,O_RDONLY);
                        if(fd < 0)
                        {
                            perror("open");
                            exit(errno);
                        }
                        dup2(fd,0);
                    }
                    break;
                case OUT_REDIR:
                case APPEND_REDIR:
                    {
                        int flags = O_WRONLY | O_CREAT;
                        if(RedirWay == OUT_REDIR)
                        {
                            flags |= O_TRUNC;
                        }
                        else 
                        {
                            flags |= O_APPEND;
                        }
                        int fd = open(FileName, flags, 0666);
                        if(fd < 0) 
                        {
                            perror("open");
                            exit(errno);
                        }
                        dup2(fd,1);
                    }
                    break;
                default:
                    puts("The RedirWay is bug");
                    break;
            }
            
            execvp(Str[0],Str);
            exit(1);
        }
        
        waitpid(id,NULL,0);
    }

    return 0;
}

当我们的使用子进程进行重定向的时候,内核数据结构中,创建子进程不会再拷贝一份文件对象而是直接让子进程的文件描述符表内保存和父进程同样的地址,指向的是同一份文件对象。
所以子进程进行重定向不会影响到父进程,这也是进程独立性的体现
image.png
子进程进行重定向也是修改的子进程的files_struct 的数据结构,并且这里采用了引用计数,比如此时两个进程都打开了文件标准输出,如果子进程关闭了标准输出,此时其实标准输出子进程是没有资格关闭的,子进程关闭的意思是子进程现在不用标准输出了,此时标准输出的引用计数-1,当引用计数减为0的时候操作系统才会真正去关闭标准输出。
子进程进行程序替换是不会影响到内核数据结构的,进程替换只是将其他程序的代码和数据替换子进程的代码和数据,不会修改到内核数据结构
image.png
父子进程在没有进行重定向的时候,父子进程打开的文件时共享的,也就是父进程打开的文件在子进程里面可以继续进行读写访问。

如何理解Linux下一切皆文件

image.png
首先计算机底层是以冯诺依曼体系结构联系组成的硬件,每种硬件都有对应的驱动程序驱动程序肯定有对应硬件的读写函数,方便操作系统与硬件进行IO。而操作系统管理硬件的方式就是先描述再组织。
但是不同的硬件他们的驱动程序肯定是不同的,读写方法也是不同的,如何统一的描述硬件呢?
操作系统再描述硬件的结构体中将硬件也看做是文件,硬件的各种属性就是文件属性,然后设置了两个函数指针,他们分别指向了硬件的读写驱动函数。
所以站在struct file 的角度上来看所有的硬件设备和文件都是统一的结构体对象,所以Linux下一切皆文件
这种在操作系统层看来使用统一的方法(函数)调用,既可以调度文件也可以调度硬件设别,在上层看来调用的是同一个函数,这就是多态思想。
上面的实现是C语言形式的多态实现。
优点就是,摒弃了底层的设备差别,以统一的方式进行操作

如何证明上面的说法是正确的呢?
在Linux源码中,task_struct结构体中保存了一个结构体指针struct files_struct* files;
image.png
struct files_struct 结构体就是进程组织文件的结构体,内部包含了文件描述符表。
image.png
文件描述符表是一个数组,一般如果是虚拟机,文件描述符表最大是32,但是可以进行扩展,云服务器进行了修改应该最大是100000或者是65536.
这里的struct file结构体就是操作系统为了管理文件和硬件设备描述出来的内核结构体。
image.png
struct file里面的 struct file_operations结构体内部保存的就是操作文件(硬件设备等)所有的函数指针,操作系统只需要初始化完成这些函数指针,就不需要关注操作的到底是硬件还是文件。
image.png文章来源地址https://www.toymoban.com/news/detail-409336.html

到了这里,关于Linux中的文件描述符和重定向的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Shell编程 管道和重定向 | 基本概念及其相关应用

    在Linux中,管道和重定向是非常有用的工具,用于处理命令的输入和输出。它们允许你将多个命令组合在一起,将命令的输出发送到文件或从文件中读取输入。以下是有关Linux管道和重定向的详细介绍,并附带了丰富的示例: 管道符号 | 用于将一个命令的输出传递给另一个命

    2024年01月18日
    浏览(8)
  • 【Linux】文件描述符与重定向操作

    【Linux】文件描述符与重定向操作

    收录于【Linux】文件系统 专栏 对于Linux下文件的写入与读取,以及文件原理还有疑惑的可以看看上一篇文章浅谈文件原理与操作。 目录 系列文章 再谈文件描述符 IO函数的本质 一切皆文件 文件重定向 原理 系统接口 🍧上一篇文章中,我们就提到了 open 的返回值即 fd ,又称

    2024年02月09日
    浏览(11)
  • 【Linux】基础IO(一) :文件描述符,文件流指针,重定向

    【Linux】基础IO(一) :文件描述符,文件流指针,重定向

    🍎 作者: 阿润菜菜 📖 专栏: Linux系统编程 是不是只有C/C++有文件操作呢?python、java、go等文件接口操作的方法是不太一样的,那如何理解这种现象?有没有统一的视角去看待所有的语言文件操作呢?—我们今天从系统视角去理解 ---- 实际都是通过系统调用来访问 文件=内

    2024年01月18日
    浏览(13)
  • <Linux> 基础IO(文件操作、文件描述符fd、重定向)

    <Linux> 基础IO(文件操作、文件描述符fd、重定向)

    1、空文件也要在磁盘占用 我们创建的文件,虽然里面并没有存放数据,但是文件属性也是数据,即便你创建一个空文件,也要占据磁盘空间 2、文件 = 文件内容 + 文件属性 文件内容就是真正写入的内容,文件属性就是文件名、文件大小、文件的权限、拥有者所属组…… 3、文

    2024年02月03日
    浏览(27)
  • 『Linux』文件描述符及重定向——为何说Linux下,一切皆文件?

    『Linux』文件描述符及重定向——为何说Linux下,一切皆文件?

    🌸作者简介: 花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。 🌸 专栏简介:本文收录于 Linux从入门到精通 ,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

    2024年02月12日
    浏览(13)
  • Linux学习之系统默认打开的文件描述符、重定向

    Linux学习之系统默认打开的文件描述符、重定向

    一个进程默认会打开标准输入、标准输出、错误输出三个文件描述符。可以在 /proc/PID/fd 里边可以看到打开文件的描述符,PID需要改成具体的 pid ,比如可以使用 A终端 输入 vim proctest 之后按下回车键。 打开一个vim编辑窗口。 再打开一个 B终端 ,输入 ps -aux | grep \\\'vim\\\' 查找一下

    2024年02月13日
    浏览(11)
  • 【Linux】基础IO_文件描述符与重定向

    【Linux】基础IO_文件描述符与重定向

    环境:centos7.6,腾讯云服务器 Linux文章都放在了专栏:【 Linux 】欢迎支持订阅 相关文章推荐: 【Linux】冯.诺依曼体系结构与操作系统 【C/进阶】如何对文件进行读写(含二进制)操作? 【Linux】基础IO_文件操作 在前文中学习了open函数,我们知道 open函数的返回值就是文件描

    2024年02月03日
    浏览(10)
  • 一文吃透 SpringMVC 中的转发和重定向

    一文吃透 SpringMVC 中的转发和重定向

    ✅作者简介:2022年 博客新星 第八 。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏:SSM 框架从入门到精通 ✨特色专栏:国学周更-心性养成之路 🥭本文内容:一文吃透 SpringM

    2024年02月01日
    浏览(8)
  • 【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向

    【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向

    作者:დ旧言~ 座右铭:松树千年终是朽,槿花一日自为荣。 目标:了解在Linux下的系统文件IO,知道什么是文件描述符,什么是重定向 毒鸡汤:白日莫闲过,青春不再来。 专栏选自:Linux初阶 望小伙伴们点赞👍收藏✨加关注哟💕💕 最早我们在C语言中学习关于如何用代码

    2024年04月14日
    浏览(9)
  • [Linux]基础IO详解(系统文件I/O接口、文件描述符、理解重定向)

    [Linux]基础IO详解(系统文件I/O接口、文件描述符、理解重定向)

            hello,大家好,这里是bang___bang_ ,今天和大家谈谈Linux中的基础IO,包含内容有对应的系统文件I/O接口,文件描述符,理解重定向。    目录 1️⃣初识文件 2️⃣ 系统文件I/O接口 🍙open 🍙write 🍙read 🍙close 3️⃣文件描述符 🍙012 🍙内核中文件描述符的探究 🍙分配

    2024年02月12日
    浏览(9)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包