Linux文件描述符|重定向

之前谈到open函数的返回值fd是一个小整数,每次我们读写操作时,发现fd总是从3开始。

OS必然同时打开多个进程,每个进程都进行文件读写操作,他们的fd是如何分配的呢?

关于什么是重定向,之前我们理解是将内容从一个地方输入到另一个地方,本文将会详细探讨。

上文文件系统调用接口的read和write都涉及到缓冲区,这个缓冲区是什么?是OS的吗?
详细阅读本文,能帮大家理清这些问题。

文件描述符fd

文件是由进程打开的,一个进程可以打开多个文件,也就是说!

在内核中,时刻存在大量被打开的文件 ,他们的关系是  进程:文件=1:n   n>=1

OS对大量文件的管理是先描述再组织,先利用结构体struct file{属性+内容} 的描述方式,再通过数据结构链式链接,对文件的管理就是对这一数据结构的增删查改。

进程如何与文件建立关系?

进程被运行时,OS会将磁盘上的数据和代码加载到内存上,然后通过task_struct建立mm_struct虚拟地址,虚拟地址和物理地址被映射起来。

在task_struct中还有struct files_struct *files文件指针,file文件指针里面有一个fd_array[]的数组,数组的下标从0 1 2 3 .....递增,在某个数组的下标没有被分配时指向NULL,如果被分配指向被打开的文件结构。这一个数组的映射关系就是我们说的fd文件描述符。

本质上,fd文件描述符就是一个特定的数组下标,通过这个下标,就能找到对应的文件!

如何理解Linux下一切皆文件!

在我们的设备中,有网卡,显示器,键盘,磁盘等外设。

他们有自己的一套读写方式,不同的设备读写方式不同。

打开文件时,fd会被分配用来管理文件。

struct_file的方法将统一的函数指针传给驱动设备的read\write ,像read_keyboard,write_keyboard这种就是每个设备的特殊方式,驱动直接管理外设,有点多态的感觉。

OS看待各个不同的设备也就是普通的struct_file ,不管是显示器,还是键盘,都是read 和write的原始函数指针。所以OS并不关心底层的差异!Linux下一切皆文件!

fd的分配规则
 

示例:

我们在进程中,以w的方式打开三个文件。文件不存在就创建。

打印出他们的fd

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/stat.h>
  4 #include<fcntl.h>
  5 
  6 int main()
  7 {
  8 
  9   int fd_1=open("log_1.txt",O_WRONLY|O_CREAT|O_TRUNC);
 10   int fd_2=open("log_2.txt",O_WRONLY|O_CREAT|O_TRUNC);
 11   int fd_3=open("log_3.txt",O_WRONLY|O_CREAT|O_TRUNC);
 12   printf("fd_1:%d\n",fd_1);
 13   printf("fd_2:%d\n",fd_2);
 14   printf("fd_3:%d\n",fd_3);                                                                                 
 15 
 16   close(fd_1);
 17   close(fd_2);
 18   close(fd_3);
 19   return 0;
 20 }

fd 从3 开始往后递增。

猜测:fd总是从最小的没有被使用的fd_arrary[]中填充,修改代码,将0号文件关闭。

 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/stat.h>
  4 #include<fcntl.h>
  5 
  6 int main()
  7 {
  8   close(0);
  9   int fd_1=open("log_1.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
 10   int fd_2=open("log_2.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
 11   int fd_3=open("log_3.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);                                                 
 12   printf("fd_1:%d\n",fd_1);
 13   printf("fd_2:%d\n",fd_2);
 14   printf("fd_3:%d\n",fd_3);
 15 
 16   close(fd_1);
 17   close(fd_2);
 18   close(fd_3);
 19   return 0;
 20 }
~

结论!

files_struct数组当中,找到当前没有被使用的 最小的一个下标,作为新的文件描述符。

一个进程的运行,会打开三个fd文件 分别是
0号——键盘输入
1号——显示器输出
2号——错误输出
将一号文件关闭,是否就不能在显示器观察到fd?

关闭一号文件后,果然在显示器得不到理应出现的结果!
分析:
将系统的1号文件关闭后,一号文件被更换为我们的log_1.txt。
显示的内容是否被更新到了log_1.txt文件上。cat输出
内容依旧没有显示
对原本的程序在写入后添加fflush刷新后
就能得到写入的内容
这里刷新缓冲区后就能得到结果!原因在后续将详细分析

重定向

常见Linux下的echo指令

echo "hollow" > txt  这个>的方式我们就叫重定向,把本应该输出到显示器的hellow输出到文件里。

类似的  >>  是文件追加重定向。将打开文件的时候,不会清空,是将内容追加到末尾  。

这里就探讨一下重定向的本质。

常见 < 输入重定向,将内容输入到指定文件。

>>追加重定向

>输出重定向


重定向的本质

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
 close(1);
 int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
 if(fd < 0){
 perror("open");
 return 1;
 }
 printf("fd: %d\n", fd);
 fflush(stdout);
 
 close(fd);
 exit(0);
}

先关闭1号文件。1号文件是输出显示器,内容就不会往显示器输出了。打开myfile文件,根据fd的分配,内容会往一号文件里输入。

这一过程叫做重定向。

cat 将内容输出。

结果:
进程被运行时会创建log.txt文件,但是不会输出内容!cat重定向的时候得到内容打开文件成功。

描述这一过程,1号文件被关闭,其它文件被打开,将fd_arry[]上原本的1号文件替换,所有的内容原本输出到显示器上的都被输出到文件myfile上,这一过程就叫做输出重定向!


2号文件 "标准错误"

标准错误场景

1号文件和2号文件的输出都是显示器,它们的区别是什么?
 

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
	//stdout
	printf("hello printf 1\n");
	fprintf(stdout, "hello fprintf 1\n");
	fputs("hello fputs 1\n", stdout);
	//stderr
	fprintf(stderr, "hello fprintf 2\n");
	fputs("hello puts 2\n", stderr);
	perror("hello perror 2"); //stderr                                                                                                                                               
	//cout 
	cout << "hello cout 1" << endl;
	//cerr
	cerr << "hello cerr 2" << endl;
	return 0;
}

我们的代码分别往 1号标准输出 和标准错误打印字符串,得到的结果是一致的!

将文件内容利用重定向log.tx文件中,结果在显示器显示的只有err2的内容。

cat也能正常打印出1号文件的内容

如果想要得到2号文件的内容,那么我们需要重定向2号文件到1号中

log.tx文件重定向到1号文件, log_txt重定向到2号文件

分别cat输出内容

这样的操作就将标准输出和标准错误分开。

在我们的工程中,许多操作都需要将错误和正常的信息得到,那么标准错误的方式就能清晰可见。

2>&1

将标准错误和标准输出的内容合到一个文件

演示

dup2

对于原先的重定向,先关闭1号文件,再打开新文件的操作,来将fd_array[]上的文件替换。

这是一个繁琐的工作!

Linux下提供dup2文件简化这一操作

通过man 手册查看dup2的信息

作用是将oldfd 覆盖到newfd 让newfd内容替换成oldfd的

必要时候可以关闭fd文件

演示:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <fcntl.h>
  4 int main() {
  5   int fd = open("log.txt", O_CREAT | O_WRONLY|O_TRUNC,0666);                                                                                                                                                  
  6   if (fd < 0)
  7   {
  8    perror("open");
  9    return 1;
 10   }
 11   close(1);
 12   dup2(fd, 1);
 13   printf("hellow Linux!");
 14   return 0;
 15 }
~


dup2的使用是相对简单


总结

  • fd的文件分配规则是从fd_array数组中寻找没有被分配的最小编号。
  • Linux下一切皆文件是哲学的说法。OS统一看待各种外设,直接访问的就是文件的读写。只有底层的差异!
  • 重定向的本质是替换fd数组中某个文件的编号。例如,重定向输出。将1号文件替换。
  • 标准错误的使用场景是分离出错误信息。
  • dup2的为了简化close重定向的一个简化方式

下面我们还有一个问题,关于缓冲区??为什么在上述的例子刷新缓冲区就能得到显示内容!

在下一篇文章,将介绍缓冲区。

  • 49
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深度搜索

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值