Linux操作系统——重定向与缓冲区

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

1.理解一下struct file内核对象

上一篇文章(文件详解)我们一直在谈,一个文件要被访问就必须要先被打开,打开之前就必须要先把文件加载到内存,同时呢我们的操作系统为了管理文件也会为我们的文件创建相对应的struct file对象,那么这个struct file对象里面应该有什么?

其实struct file里面最核心的两个:一个叫做内容,另一个叫做属性。

因为我们前面说了文件= 内容+属性。所以一个文件被打开之后最重要的要么是内容,要么是属性。

如果我们要读一个文件,那么读文件一定是由进程来读的,其中进程的PCB中包含了一个文件描述符表指针指向struct file * fd_array[]数组的一个指针,正常情况下标准输入,标准输出,标准错误三个流是默认被打开的,也就是对应着文件描述符0,1,2,所以新打开的文件一般是把新打开创建的struct file的地址填到文件描述符为3的空间里,然后将该文件描述符作为返回值传递给上层,上层拿到了文件描述符为3的文件就可以对该文件进行一系列操作了。如果我们要对文件进行读数据,首先要将文件加载至内存,如果要对文件进行写数据时我们不能在磁盘中对文件进行写入,而是要把文件加载到缓冲区(内存空间)才能进行写入操作。所以说无论读写都要先把文件加载到文件缓冲区中!

那么我们在应用层进行数据的读写本质是什么呢?其实本质是将内核缓冲区中的数据进行来回拷贝!

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

下面我们看一看Linux内核源代码中关于文件的结构体字段的描述

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

2.文件描述符fd的分配规则

下面我们先看一下以写的方式打开文件如果不存在就创建文件的方式进行代码测试:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行后:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

既然我们说默认文件描述符为0,1,2的文件流默认是打开的,那么我们直接使用文件描述符为0的文件对其进行操作即可验证这一说法,然后改写代码为:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行之后:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

既然我们可以对标准输入进行读,那么自然也可以从标准输出进行写操作,下面我们将代码修改:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行后:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

读了abcdefg,同时写入了abcdefg.

进程默认已经打开了0,1,2,我们可以直接使用0,1,2进行数据访问!

下面我们在进行一些测试:

我直接把文件描述符为0的标准输入给关了

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行之后:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

发现我们以写的方式打开的新的文件的文件描述符不再是3了,而是变成了0,一个该现象并不足以说明问题,下面我们把2号标准错误流给改了,看是否打开的新文件的文件描述符会发生变化。

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行结果:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

我们发现果然,文件描述符变成了2,这说明了什么问题呢?

当我们打开一个新的文件时,会从文件描述符表中从上往下扫描,寻找文件描述符最小且没有被使用的文件描述符分配为为新的文件的文件描述符。

那么下面我们把文件描述符为1的标准输出流给关了之后会发生什么现象呢?

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行之后:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

我们发现什么都没有???原因就在于我们原本printf是要将内容打印到显示上的,但是我们在代码中已经把文件描述符为1的标准输出流给关闭了,所以没有打印出来。

如果我们再把代码改成这样呢?

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

然后运行之后,再查看一下log.txt文件中的内容:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

怎么我们要打印的内容在log.txt文件里面去了?

其实很容易理解,根据我们上面的结论,文件描述符为1的标准输出流关闭了,所以log.txt打开时会被分配最小的没有被使用的文件描述符,也就是1,所以当前情况下log.txt的文件描述符为1,而这些内容会写到log.txt里面原因就在于这个过程叫做输出重定向

3.重定向

printf只认stdout,而stdout只认_fileno=1也就是文件描述符为1所指向的文件,如果文件描述符为1指向的是显示器那就往显示器上打,如果文件描述符为1指向的是log.txt文件,那就往log.txt上打,所以说重定向的本质,其实就是修改特定文件fd的下表中的内容(也就是打开文件的地址)。

下面我们再使用一个与printf比较相似的fprintf来测试:

先看一下fprintf的手册:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

编写如下代码:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行之后:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

下面我们把打开方式改一下:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行之后:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

所以我们就很容易的实现了追加重定向,每次运行都是在log.txt后面进行追加。

下面我们再测试一个输入重定向:

查看一下fread的相关手册

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

修改代码为如下:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行结果:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

我们发现原本应该要从键盘上读取数据,最后是从log.txt的文件上进行读取的。

所以上面就说明了,上层fd不变,底层fd指向的内容在改变。

关于上述的重定向我们感觉太麻烦了,因为它还需要我们关闭相对应文件描述符指向的文件,有没有一种方法是直接把新打开的文件的地址拷贝到对应文件描述符的内容里面去完成重定向呢?也就是通过文件描述符级别的数组内容的拷贝!

答案是有的,一下就是那个接口:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

这个接口的用法是将oldfd的内容拷贝到,newfd上,也就是最后两处的文件描述符都是指向oldfd所指向的内容,而这会导致两个文件描述符如何关闭的问题,其实这个问题是文件结构体中会存在一个引用计数,也就是有几个指针指向该文件,如果有多个指针就对计数那个字段进行数量控制,如果有一个指向该文件的文件描述符关闭了,那么该字段就减减,直到减到0才关闭。下面我们以输出重定向为例来测试一下dup2这个接口:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

运行结果:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

这就叫做重定向。

那么我们之前在命令行里面的重定向是怎么用的呢?

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

这就是我们在命令行中使用的重定向,那么命令行中的重定向与我们前面用c语言写的文件重定向之间有什么关系呢?

任何命令行在执行的时候都是bash的子进程,我们输入的命令行都叫做命令行字符串,所以我们要先做的是对命令行字符串进行解析,首先要识别的是,一旦发现有一个大于(>) ,两个大于(>>),还是一个小于(<) 这样的符号之后紧接着我们立马就能判定出来,这条指令是需要有重定向功能的,一旦识别出需要重定向我们就可以判断出来,前半部分是我们要执行的指令,后半部分是需要重定向到的目标文件,进程在进行程序替换之前会先进行重定向工作,所以未来要执行这条指令时就可以把原本打到显示器中的内容打到文件中去。

为了说明程序替换会不会影响重定向功能,下面我们修改一下代码来进行测试,以下是我们用c语言模拟实现的一个mybash的代码:

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

#define NUM 1024
#define SIZE 64
#define SEP " "
//#define Debug 1

//redir
#define NoneRedir   0
#define OutputRedir 1
#define AppendRedir 2
#define InputRedir  3

int redir = NoneRedir;
char *filename = NULL;

char cwd[1024];
char enval[1024]; // for test
int lastcode = 0;

char *homepath()
{
    char *home = getenv("HOME");
    if(home) return home;
    else return (char*)".";
}

const char *getUsername()
{
    const char *name = getenv("USER");
    if(name) return name;
    else return "none";
}
const char *getHostname()
{
    const char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "none";
}
const char *getCwd()
{
    const char *cwd = getenv("PWD");
    if(cwd) return cwd;
    else return "none";
}
int getUserCommand(char *command, int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());
    char *r = fgets(command, num, stdin); // 最终你还是会输入\n
    if(r == NULL) return -1;
    // "abcd\n" "\n"
    command[strlen(command) - 1] = '\0'; // 有没有可能越界?不会
    return strlen(command);
}

void commandSplit(char *in, char *out[])
{
    int argc = 0;
    out[argc++] = strtok(in, SEP);
    while( out[argc++] = strtok(NULL, SEP));

#ifdef Debug
    for(int i = 0; out[i]; i++)
    {
        printf("%d:%s\n", i, out[i]);
    }
#endif
}

int execute(char *argv[])
{
    pid_t id = fork();
    if(id < 0) return -1;
    else if(id == 0) //child
    {
        int fd = 0;
        if(redir == InputRedir)
        {
            fd = open(filename, O_RDONLY); 
            dup2(fd, 0);
        }
        else if(redir == OutputRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if(redir == AppendRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
        }
        else
        {
            //do nothing
        }
        // exec command
        execvp(argv[0], argv); // cd ..
        exit(1);
    }
    else // father
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0){
            lastcode = WEXITSTATUS(status);
        }
    }

    return 0;
}

void cd(const char *path)
{
    chdir(path);
    char tmp[1024];
    getcwd(tmp, sizeof(tmp));
    sprintf(cwd, "PWD=%s", tmp); // bug
    putenv(cwd);
}

// 什么叫做内键命令: 内建命令就是bash自己执行的,类似于自己内部的一个函数!
// 1->yes, 0->no, -1->err
int doBuildin(char *argv[])
{
    if(strcmp(argv[0], "cd") == 0)
    {
        char *path = NULL;
        if(argv[1] == NULL) path=homepath();
        else path = argv[1];
        cd(path);
        return 1;
    }
    else if(strcmp(argv[0], "export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(enval, argv[1]);
        putenv(enval); // ???
        return 1;
    }
    else if(strcmp(argv[0], "echo") == 0)
    {
        if(argv[1] == NULL){
            printf("\n");
            return 1;
        }
        if(*(argv[1]) == '$' && strlen(argv[1]) > 1){ 
            char *val = argv[1]+1; // $PATH $?
            if(strcmp(val, "?") == 0)
            {
                printf("%d\n", lastcode);
                lastcode = 0;
            }
            else{
                const char *enval = getenv(val);
                if(enval) printf("%s\n", enval);
                else printf("\n");
            }
            return 1;
        }
        else {
            printf("%s\n", argv[1]);
            return 1;
        }
    }
    else if(0){}

    return 0;
}

#define SkipSpace(pos) do{ while(isspace(*pos)) pos++; }while(0)

void checkRedir(char usercommand[], int len)
{
    // ls -a -l > log.txt
    // ls -a -l >> log.txt
    char *end = usercommand + len - 1;
    char *start = usercommand;
    while(end>start)
    {
        if(*end == '>')
        {
            if(*(end-1) == '>')
            {
                *(end-1) = '\0';
                filename = end+1;
                SkipSpace(filename);
                redir = AppendRedir;
                break;
            }
            else
            {
                *end = '\0';
                filename = end+1;
                SkipSpace(filename);
                redir = OutputRedir;
                break;
            }
        }
        else if(*end == '<')
        {
            *end = '\0';
            filename = end+1;
            SkipSpace(filename); // 如果有空格,就跳过
            redir = InputRedir;
            break;
        }
        else
        {
            end--;
        }
    }
}
int main()
{
    while(1){
        redir = NoneRedir;
        filename = NULL;
        char usercommand[NUM];
        char *argv[SIZE];
        // 1. 打印提示符&&获取用户命令字符串获取成功
        int n = getUserCommand(usercommand, sizeof(usercommand));
        if(n <= 0) continue;
        // "ls -a -l > log.txt" -> 判断 -> "ls -a -l"  redir_type   "log.txt"
        // 1.1 检测是否发生了重定向
        checkRedir(usercommand, strlen(usercommand));
        // 2. 分割字符串
        // "ls -a -l" -> "ls" "-a" "-l"
        commandSplit(usercommand, argv);
        // 3. check build-in command
        n = doBuildin(argv);
        if(n) continue;
        // 4. 执行对应的命令
        execute(argv);
    }
}

其中实现我们平常用>,<,>>来进行重定向的重要代码是:

int execute(char *argv[])
{
    pid_t id = fork();
    if(id < 0) return -1;
    else if(id == 0) //child
    {
       
        int fd = 0;
        if(redir == InputRedir)
        {
            fd = open(filename, O_RDONLY); 
            dup2(fd, 0);
        }
        else if(redir == OutputRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if(redir == AppendRedir)
        {
            fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
        }
        else
        {
            //do nothing
        }
        // exec command
        execvp(argv[0], argv); // cd ..
        exit(1);
    }
    else // father
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0){
            lastcode = WEXITSTATUS(status);
        }
    }

    return 0;
}
#define SkipSpace(pos) do{ while(isspace(*pos)) pos++; }while(0)

void checkRedir(char usercommand[], int len)
{
    // ls -a -l > log.txt
    // ls -a -l >> log.txt
    char *end = usercommand + len - 1;
    char *start = usercommand;
    while(end>start)
    {
        if(*end == '>')
        {
            if(*(end-1) == '>')
            {
                *(end-1) = '\0';
                filename = end+1;
                SkipSpace(filename);
                redir = AppendRedir;
                break;
            }
            else
            {
                *end = '\0';
                filename = end+1;
                SkipSpace(filename);
                redir = OutputRedir;
                break;
            }
        }
        else if(*end == '<')
        {
            *end = '\0';
            filename = end+1;
            SkipSpace(filename); // 如果有空格,就跳过
            redir = InputRedir;
            break;
        }
        else
        {
            end--;
        }
    }
}

运行之后:

Linux操作系统——重定向与缓冲区,初学者必看:Linux操作系统入门,linux,运维,服务器

我们发现我们模拟实现的一个bash命令行解释器实现了重定向功能。

其实这些都不是很大的问题,问题比较大的是,程序替换会不会影响曾经的重定向呢?答案是不会。因为程序替换并没有创建新进程,而重定向只是在内核的层面上将文件描述符表数组对应的下标的内容进行了拷贝,而不会影响在内存中进行的程序替换,所以说这两者并不会互相影响,各自做各自的事文章来源地址https://www.toymoban.com/news/detail-792666.html

到了这里,关于Linux操作系统——重定向与缓冲区的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【linux基础I/O(二)】文件系统讲解以及文件缓冲区的概念

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:Linux从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学更多操作系统知识   🔝🔝 对于文件来讲,有打开的在内存中 的文件,也有没有打开的在磁盘上 文件,上一篇文章讲解的是前者,本篇 文章将带大家了解后

    2024年01月19日
    浏览(56)
  • [Linux]理解文件系统!动静态库详细制作使用!(缓冲区、inode、软硬链接、动静态库)

            hello,大家好,这里是bang___bang_,今天来谈谈的文件系统知识,包含有缓冲区、inode、软硬链接、动静态库。本篇旨在分享记录知识,如有需要,希望能有所帮助。 目录 1️⃣缓冲区 🍙缓冲区的意义 🍙常见缓冲区刷新策略 🍙缓冲区位置猜想 🍥现象猜测 🍥现象解

    2024年02月13日
    浏览(50)
  • 【Linux】理解缓冲区

    我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关! C接口的函数被打印了两次系统接口前后只是打印了一次:和fork函数有关,fork会创建子进程。在创建子进程的时候,数据会被处理成两份,父子进程发生写时拷

    2024年01月23日
    浏览(54)
  • 【Linux】文件缓冲区

    提到文件缓冲区这个概念我们好像并不陌生,但是我们对于这个概念好像又是模糊的存在脑海中,之间我们在介绍c语言文件操作已经简单的提过这个概念,今天我们不妨深入理解什么是文件缓冲区 通过自己实现库中的一些文件操作函数更加深入的理解文件缓冲区 自定义实现

    2024年02月10日
    浏览(58)
  • 【Linux】深入理解缓冲区

    目录 什么是缓冲区 为什么要有缓冲区 缓冲区刷新策略 缓冲区在哪里  手动设计一个用户层缓冲区 缓冲区本质上一块内存区域,用来保存临时数据。 缓冲区在各种计算任务中都广泛应用,包括输入/输出操作、网络通信、图像处理、音频处理等。 这块内存区域是由 谁提供的

    2024年02月15日
    浏览(64)
  • Linux之缓冲区的理解

    目录 一、问题引入 二、缓冲区 1、什么是缓冲区 2、刷新策略 3、缓冲区由谁提供 4、重看问题 三、缓冲区的简单实现 我们先来看看下面的代码:我们使用了C语言接口和系统调用接口来进行文件操作。在代码的最后,我们还使用fork函数创建了一个子进程。  代码运行结果如

    2024年02月03日
    浏览(50)
  • 【Linux】深入理解文件缓冲区

    问题引入 首先看一段代码: 运行代码,结果如下: 如果此时将输出结果重定向一下: 会发现 printf 、 fwrite 都打印了两次。 究其原因,就要谈到缓冲区和缓冲区刷新策略的概念了。 如何理解缓冲区 假设你在青岛,你要从网上买一件商品,商家所在地是北京。你不会跑去北

    2024年02月11日
    浏览(57)
  • 浅谈linux缓冲区的认识!

    今天来为大家分享一波关于缓冲区的知识!那么既然我们要谈缓冲区,那么就得从是什么?为什么?有什么作用这几个方面来谈论一下缓冲区!然后再通过一些代码来更加深刻的理解缓冲区的知识! 从最简单的理解方面来,我们可以将缓冲区理解成一块内存!那么这块内存是

    2024年02月05日
    浏览(55)
  • [Linux打怪升级之路]-缓冲区

    前言 作者 : 小蜗牛向前冲 名言 : 我可以接受失败,但我不能接受放弃    如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正  本期学习目标:认识什么是缓冲区,缓冲区在哪里,模拟实现一个简单的缓

    2024年02月07日
    浏览(49)
  • 【Linux】基础IO----理解缓冲区

    作者:დ旧言~ 座右铭:松树千年终是朽,槿花一日自为荣。 目标:理解缓冲区 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安! 专栏选自:Linux初阶 望小伙伴们点赞👍收藏✨加关注哟💕💕 缓冲区大家其实不陌生,像我们使用的 VS2019 编译器这里就有缓冲区,那它

    2024年04月13日
    浏览(47)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包