概述
目录
一、文件描述符
二、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= %dn",item); //输出filename.txt中读出来的内容
dup2(fd2,0); //复制文件描述符fd2表项给0,此时0重新指向终端,
// fopen("/dev/stdin", "r+");
scanf("%d",&item); //同时意味着/dev/stdin 也间接指向了终端
printf("format2= %dn",item);
}
最后
以上就是鳗鱼小蘑菇为你收集整理的Linux C:文件描述符、IO重定向、恢复标准输入输出的全部内容,希望文章能够帮你解决Linux C:文件描述符、IO重定向、恢复标准输入输出所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复