首页 > 其他分享 >理解重定向的具体原理:文件描述符表与操作流程

理解重定向的具体原理:文件描述符表与操作流程

时间:2024-10-12 11:52:01浏览次数:9  
标签:文件 重定向 描述符 fd 缓冲区 表与 hello

首先回忆一下我们讲操作系统概念时,画的一张图 系统调用接口和库函数的关系,一目了然。 所以,可以认为, f# 系列的函数,都是对系统调用的封装,方便二次开发 也就是说   fopen fclose fread fwrite 都是 C 标准库当中的函数,我们称之为库函数( libc )。 而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

 

 

文件描述符表

通过对 open 函数的学习,我们知道了文件描述符就是一个小整数

每个进程在操作系统中都有一个与之关联的文件描述符表(File Descriptor Table)。这个表是一个数组,数组的每个元素都是一个指向打开文件的引用。文件描述符(File Descriptor,简称fd)就是这个数组的索引,通常是非负整数。

在Linux系统中,文件描述符表的前三个索引默认对应以下标准文件:

  • 0:标准输入(Standard Input,stdin
  • 1:标准输出(Standard Output,stdout
  • 2:标准错误(Standard Error,stderr

当一个进程打开一个新的文件时,操作系统会在文件描述符表中找到最小的未被使用的索引,将其分配给新打开的文件。

而现在知道,文件描述符就是从 0 开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file 结构体。表示一个已经打开的文件对象。而进程执行 open 系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表 files_struct, 该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

 重定向

如果我们关闭标准输出(文件描述符1),会发生什么呢?请看以下代码:

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

int main() {
    close(1);
    int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    close(fd);
    return 0;
}

运行后,我们会发现本应输出到显示器的内容被写入到了文件myfile中,且文件描述符fd的值为1。这种现象称为输出重定向。常见的重定向符号有>>><等。

重定向的本质

重定向的本质是改变文件描述符的指向。可以使用dup2系统调用来实现。其函数原型如下:

#include <unistd.h>
int dup2(int oldfd, int newfd);
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
 int fd = open("./log", O_CREAT | O_RDWR);
 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;
}

 

重定向的本质是改变文件描述符在文件描述符表中的指向,使得原本指向某个文件或设备的文件描述符指向其他的文件或设备。

以输出重定向为例,当我们执行命令command > output.txt时,实际上是将标准输出(文件描述符1)重定向到了output.txt文件。

 具体流程如下:

  • 打开目标文件:进程调用open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644)打开output.txt文件,获得一个新的文件描述符,假设为fd_new

  • 保存标准输出(可选):如果需要恢复标准输出,可以在重定向前保存标准输出的文件描述符。

  • 重定向标准输出:使用dup2(fd_new, 1)系统调用,将fd_new复制到文件描述符1的位置。此时,文件描述符1指向output.txt,而原来的fd_new被关闭。

  • 关闭原文件描述符:如果fd_new不等于1,dup2会自动关闭fd_new,无需手动关闭。

  • 执行命令:进程继续执行,所有写入标准输出(stdout)的内容都会写入到output.txt文件中。

所以也就是说dup2对文件描述符的拷贝本质上是文件描述符下标所对应内容的拷贝

缓冲区

来段代码在研究一下
#include <stdio.h>
#include <string.h>
int main()
{
 const char *msg0="hello printf\n";
 const char *msg1="hello fwrite\n";
 const char *msg2="hello write\n";
 printf("%s", msg0);
 fwrite(msg1, strlen(msg0), 1, stdout);
 write(1, msg2, strlen(msg2));
 fork();
 return 0;
运行出结果:
hello printf
hello fwrite
hello write
但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了
hello write
hello printf
hello fwrite
hello printf
hello fwrite
我们发现 printf 和 fwrite (库函数)都输出了 2 次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关! 一般 C 库函数写入文件时是全缓冲的,而写入显示器是行缓冲。printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后。但是进程退出之后,会统一刷新,写入文件当中。但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。write 没有变化,说明没有所谓的缓冲

 

所以我们可以得出以下内容:

缓冲区的类型

根据缓冲策略,C标准库中的缓冲区可以分为以下三种类型:

  1. 全缓冲(Fully Buffered)

    • 只有当缓冲区满时,数据才会被真正写入文件或设备。
    • 适用于文件等块设备。
  2. 行缓冲(Line Buffered)

    • 当检测到换行符(\n)时,缓冲区的数据会被刷新。
    • 适用于交互式设备,如终端(显示器、键盘)。
  3. 无缓冲(Unbuffered)

    • 数据不会经过缓冲区,直接写入文件或设备。
    • 适用于stderr等需要立即输出的场景。

库函数的缓冲机制

C标准库中的输入输出函数,如printffprintffwrite等,都是基于FILE结构体实现的。这些函数在底层使用了用户级的缓冲区,以提高I/O效率。

FILE结构体与缓冲区

FILE结构体是C标准库中用于表示文件流的类型。它的定义在stdio.hlibio.h中,包含了与缓冲区相关的成员。

struct _IO_FILE {
    int _flags;        /* 标志位 */
    char* _IO_read_ptr;   /* 当前读取位置的指针 */
    char* _IO_read_end;   /* 读取缓冲区的结束位置 */
    char* _IO_read_base;  /* 读取缓冲区的起始位置 */
    char* _IO_write_base; /* 写入缓冲区的起始位置 */
    char* _IO_write_ptr;  /* 当前写入位置的指针 */
    char* _IO_write_end;  /* 写入缓冲区的结束位置 */
    /* 其他成员 */
};

这些缓冲区成员用于暂存数据,直到满足刷新条件才进行实际的I/O操作。 

缓冲区的存在主要有以下原因:

  • 减少系统调用次数:每次系统调用都会有一定的开销。通过使用缓冲区,可以将多次小的I/O操作合并为一次大的I/O操作。
  • 提高磁盘和网络I/O效率:磁盘和网络设备的I/O操作通常比内存慢得多。缓冲区可以协调不同速度的设备之间的数据传输。

 

标签:文件,重定向,描述符,fd,缓冲区,表与,hello
From: https://blog.csdn.net/2301_77754590/article/details/142871993

相关文章

  • 【图像识别】用于对象识别的良好彩色图像描述符/功能(Matlab实现)
    ......
  • 套接字和文件描述符的区别
    node_sockstat_sockets_used和node_filefd_allocated是两个不同的系统资源使用指标,分别用于监控套接字和文件描述符的使用情况。它们的具体区别如下:1.node_sockstat_sockets_used:已使用的套接字数量描述:这个指标表示当前系统中已使用的套接字数量,包括所有网络连接使用的套......
  • 博客搭建之路:Netlify将url重定向到小写问题
    Netlify将url重定向到小写问题hexo版本5.0.2npm版本6.14.7next版本7.8.0前两天将博客从vercel改为托管到Netlify上,本来运行的挺流畅的。但是今天我看一篇博客的评论时突然发现,虽然有评论但是文章开头的评论数显示的是0这里的评论系统使用的是Valine我记得之前是好......
  • linux 命令行中重定向的使用
    问题1:>output.txt&什么意思?在Linux中,command>output.txt&是将命令的标准输出重定向到文件并将该命令放入后台执行的组合操作。各部分解释:command>output.txt:将命令的标准输出(stdout)重定向到output.txt文件中。&:将命令放入后台执行,使得你可以继续在终端执行其他......
  • USB协议详解第11讲(USB描述符-总结)
    描述符回顾总结1.其实所有的描述符都是USB设备用来描述自己属性及用途的,所以必须在设备端实现对应的描述符,主机会在枚举此设备的时候根据设备实现的描述符去确定设备到底是一个什么样的设备、设备需要的总线资源、和设备的通讯方式等等。2.每一个USB设备只有一个设备描述符,主要......
  • bp 开放重定向
    检查:https://www.bugbountyhunter.com/vulnerability/?type=open_redirect检查:https://portswigger.net/web-security/ssrf#bypassing-ssrf-filters-via-open-redirection检查:https://portswigger.net/web-security/dom-based/open-redirection/lab-dom-open-redirectionLab:......
  • 【STC15】实现printf()重定向的相关问题
    本文前提:读者已经知道如何用STC15实现串口重定向的基础知识(大体思路和代码大意)。如果不知道,请移步:《STC15单片机-串口打印》:https://blog.csdn.net/weixin_46251230/article/details/126679956问题1:uint8_t 数字增长显示错误/*Privatevariables-------------------------......
  • 使用dup2实现一个简单的命令重定向
    以下是一个echo命令的的重定向的简单实现,大家可以把他改成main函数在执行,一样的,然后我们需要关注的部分就是代码中标有注释的代码块#include"command.h"int_echo(char*argv[]){ intargc=0; for(char**ptr=argv;*ptr!=NULL;ptr++) argc++; if(argc<2||......
  • Linux文件IO(七)-复制文件描述符
    在Linux系统中,open返回得到的文件描述符fd可以进行复制,复制成功之后可以得到一个新的文件描述符,使用新的文件描述符和旧的文件描述符都可以对文件进行IO操作,复制得到的文件描述符和旧的文件描述符拥有相同的权限,譬如使用旧的文件描述符对文件有读写权限,那么新的文件描述......
  • Python——列表与函数的关系
    目录1.问题描述2.与Python其他序列类型比较3.与C语言比较4.结论1.问题描述Python中为方便对列表进行修改,规定列表传入函数后进行的修改是直接针对原列表所在地址的,即在函数中对传入的列表的修改是全局的、永久的。示例代码如下:deffun(list):list[0][0]=18lis......