Linux C:文件描述符、IO重定向、恢复标准输入输出

目录

   

一、文件描述符  

二、IO重定向

三、重定向回终端、伪终端

四、恢复标准输入输出


   

一、文件描述符  

     在Linux中,文件描述符是一个非负整数的数据类型。是FILE结构体中的一个成员属性。 每打开或者新建一个文件时,内核都会返回最小的且未被使用的非负整数,即文件描述符。例如,文件描述符 0,1,2,4,5...已经被该进程使用了,那么再打开一个文件返回的文件描述符就是3,再打开一个新文件就是6。如果文件描述符被关闭,那么文件描述符在下一次可能会重新被打开。

     FILE结构体大致如下

-----------FILE Structure---------
char fbuf[SIZE];
int counter,index..........   一些其它属性
int fd;  //文件描述符

Linux 为每个进程创建了文件描述符表(fd, 文件指针),其中文件指针,指向了系统级的打开文件表,通过该指针可以获取到文件偏移量和i-node指针信息,再通过文件偏移量和i-node指针,可以定位到文件系统中的i-node表,从而找到物理硬盘上的文件。换言之,在操作系统之上,通过进程号和文件描述符就可以定位到具体的文件。这个关系是一对多的,因为一个对象可以被多个指针指向,而一个指针只能指向一个对象。这就意味着,多个进程级的文件描述符可能会指向同一个系统级打开文件表,多个打开文件表项可能指向同一个i-node表。

二、IO重定向

在sh进程中有3个用于终端的IO文件流: stdin(标准输入),stdout(标准输出),stderr(标准错误)。

这3个流实际上指向文件结构体的指针, FILE * stdin , stdout ,stderr;

它们指向的FILE的区别在于它们的文件描述符分别是STDIN_FILENO, STDOUT_FILENO,STDERR_FILENO分别对应的值是0,1,2。标准输入默认来源于键盘,标准输出、标准错误的目标默认是屏幕。改变它们的来源或者目标就叫IO重定向。对应linux的操作符是   "<","1>","2>"。

所以通常程序的缺省情况,fd通常就已经打开了3个,如果想改变输入流或者输出流到文件,那么就要关闭对应的文件描述符。当重新open文件时,系统就会返回最小的且未被使用的文件描述符。

例如在filename.txt写入8878 . ,当关闭文件描述符0时,在打开文件,调用scanf函数就不再从键盘中输入,而把filename.txt中的内容当作输入。

#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
int main(){
  close(0);
  int fd = open("filename.txt",O_RDONLY);
  int item;
  scanf("%d",&item);
  printf("format= %d",item);
}

关于scanf。当FILE结构体中fbuf为空时,才会向内核中发出read系统调用,通过文件描述符为0 和进程号找到对应的文件,把文件数据读到fbuf中。所以哪个文件获取到了文件描述符0,那么标准输入的来源就是这个文件。同理,哪个文件获取到了文件描述符1  (2),那么标准输出(错误)的目标就是这个文件。

三、重定向回终端、伪终端

      终端和伪终端通常包含屏幕和键盘。屏幕输出前,需要通常保存在/dev/ttyX  文件下 ,键盘输入后,输入的内容通常保存在 /dev/pts/# 下。

确定具体文件描述符打开的对应文件

例如在gdb调试代码时

通过   ps  -ef |grep a.out 找到进程号

查询   /proc/[进程号]/fd  中的内容  (需要root账号才可以登录fd目录)

查看到我的伪终端是:

/dev/pts/2

或者用 tty 命令查看伪终端

yu'shhi标准输入重定向回键盘:

 close(0);
 int fd = open("/dev/pts/2",O_RDONLY);

四、恢复标准输入输出

在/dev目录下有/dev/stdin 文件,stdout文件,stderr文件

注意:标准输入文件/dev/stdin是个链接文件!,其他也一样,它们存放的是文件描述符地址,链接文件类似指针,而标准IO文件类似于双重指针。文件描述符在Linux系统中也是一个链接文件。当close([文件描述符时]),其实删除的时文件描述符对应的链接文件。而/dev/stdin固定链接每个 /proc/self/fd/0,当0被删除了之后,/dev/stdin也就链接成了空文件。

也就是说下述代码得到结果并没有重新获得键盘输入

#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
int main(){
  close(0);                                /**文件 /proc/[pid]/fd/0 消失*****/ 
  int fd = open("filename.txt",O_RDONLY);  /**文件 /proc/[pid]/fd/0 链接上了filename.txt */       
  int item;
  scanf("%d",&item);                       /*stdin 找到/proc/[pid]/fd/0 中读数据*/
  close(0);                                /*文件 /proc/[pid]/fd/0消失,/dev/stdin为空链接*/
  //这样做的意图是 /dev/stdin -> /proc/self/0 -> /dev/stdin 吗??? 显然目的不是这样的
  int fd = open("/dev/stdin",O_RDONLY);    /*打开空链接文件,并非是终端文件*/

  scanf("%d",&item); 
  printf("format= %d",item);
}

所以如果要保证代码的通用性,变更stdin链接之前需要把终端文件用新的文件描述符保存起来。

(1)  int fd = dup(oldfd);   ,     int fd= dup2(oldfd,newfd);

当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项
  dup2和dup的区别就是可以用newfd参数指定新描述符的数值。
  APUE用另外一个种方法说明了这个问题:
  实际上,调用dup(oldfd)等效于,fcntl(oldfd, F_DUPFD, 0)
  而调用dup2(oldfd, newfd)等效于,close(oldfd);fcntl(oldfd, F_DUPFD, newfd);

查看如下代码:

#include<fcntl.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <unistd.h>
int main(){
  int fd2=dup(0) ;    //复制文件描述符0对应的表项给fd2 = 3,此时fd2 ,和0 都指向终端
  close(0);           //关闭文件描述符0
  int fd = open("filename.txt",O_RDONLY); //filename.txt获取文件描述符0 
  int item;
  scanf("%d",&item);                //从标准输入0中读数据
   printf("format1= %d\n",item);    //输出filename.txt中读出来的内容
  dup2(fd2,0);                      //复制文件描述符fd2表项给0,此时0重新指向终端,
 // fopen("/dev/stdin", "r+");      
  scanf("%d",&item);                //同时意味着/dev/stdin 也间接指向了终端
  printf("format2= %d\n",item);
}

猜你喜欢

转载自blog.csdn.net/superSmart_Dong/article/details/118531682