文章目录
- ● 文件描述符
- 一 、Linux 系统对文件的管理(要知道)
- 二 、什么是文件描述符 fd ?
- 三 、再探文件被管理过程(重要)
- 四 、文件描述符 0 、1、2
- 1. 文件描述符的分配原则
- 2. 提前认识三个默认打开的文件
- ● 重定向原理(重要)
- 一 、重定向现象
- 二 、深入剖析重定向现象(重要)
- 1 . 重定向的本质是什么 ???
- 2 . 各种重定向实现原理
- 2.1 dup2 系统调用(理解 , 掌握)
- 2.2 输出重定向
- 2.3 输入重定向
- 2.4 追加重定向
- 3 . 重定向的原理(面试考)
- ● 正式谈三个默认打开的文件
- 一 、stderr
- 二 、重定向的使用(重要)(重点讲解标准错误)
- 1 . 重定向的完整写法
- 2 . 将错误信息和正常信息隔离(重要)
- 3 . 将错误信息和正常信息打印在一起
- 总结
● 文件描述符
篇章一中笔者提到了文件描述符的概念 , 本节就详细对其进行介绍 !
对以下系统调用中会涉及文件描述符的概念 :
write(int fd, ....) - write to a file descriptor - 文件描述符
read( int fd, .... ) - read from a file descriptor - 文件描述符
close(int fd) - close a file descriptor - 文件描述符
一 、Linux 系统对文件的管理(要知道)
之前第一篇章提到操作文件 , 就是进程操作文件 ! 那么考虑一个问题 ???
在系统中一切皆文件 , 那么文件多了 , 要不要被管理呢 ???
答 : 文件要被管理 ! 那怎么管理呢 ??? 先描述 , 再组织 !
既然要描述 , 那么就会有数据结构吧 !
以下便是具体描述 :
二 、什么是文件描述符 fd ?
上面也提到了文件被管理是通过文件描述符表管理的 , 那这个文件描述符表是个什么呢 ???
文件描述符表是一个数组 !
文件描述符的本质就是数组的下标 ! , 即 : fd 的本质就是数组的下标 !
flies_struct
这个数据结构中包含一个结构体指针数组 , 这个数组就是文件描述符表 !
- 内核源代码中的 files_struct
struct files_struct {
....
....
....
....
....
....
//其中包含一个结构体指针数组
struct file __rcu * fd_array[NR_OPEN_DEFAULT]; // 文件描述符表
};
每打开一个文件 , 就会有一个文件描述符来描述该文件 , 即 : 就会有一个指针指向该文件 !
三 、再探文件被管理过程(重要)
总结文件被管理的过程(面试可能考) :
记住一个图 :
所以 , 还可以得出一个结论 :
对文件内容的任何操作 , 必须先把文件的内容加载到内核对应的文件缓冲区内 , 避免频繁 I/O , 提高效率 !
四 、文件描述符 0 、1、2
有了以上的理解 , 现在便好介绍 0 , 1 , 2 了 .
笔者之前提过 , 系统会为每个文件分配一个文件描述符 , 每一个描述符对应一个文件 !
1. 文件描述符的分配原则
因为文件描述符是数组的下标 , 所以会有以下原则 :
找到 files_struct 数组中没有被使用的最小的下标 , 作为当前文件的描述符 !
注意 : 是最小的下标 !!!
/ fd 分配原则
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
//close(0);
//int fd = open("log.txt" , O_CREAT | O_WRONLY | O_TRUNC , 0666);
//printf("fd : %d\n",fd); ********** 打印 0
//close(2);
//int fd = open("log.txt" , O_CREAT | O_WRONLY | O_TRUNC , 0666);
//printf("fd : %d\n",fd); ********** 打印 2
int fd = open("log.txt" , O_CREAT | O_WRONLY | O_TRUNC , 0666);
printf("fd : %d\n",fd); ********** 打印 3
return 0;
}
2. 提前认识三个默认打开的文件
之前笔者也讲过 , 系统会自动打开三个默认文件 !
这三个默认文件 , 分别对应 :
键盘 --- 文件描述符 0
显示器 --- 文件描述符 1
显示器 --- 文件描述符 2
观察一个现象
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("进入第一次输入 :\n");
int a = 0;
scanf("%d",&a);
printf("%d\n",a);
close(0);
printf("进入第二次输入 :\n");
int b = 10;
scanf("%d",&b);
printf("%d\n",b);
return 0;
}
所以 , 上面完全可以印证文件描述符 0 对应的就是键盘文件(C语言 — stdin ) !
其余 , 学者可以自行验证 !
● 重定向原理(重要)
我们之前经常用 > , 这个就是重定向 , 那么其原理真的了解吗 ?
重定向分类 :
- 输出重定向
- 输入重定向
- 追加重定向
一 、重定向现象
给出以下代码 : 你会发现什么现象 ??
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
// 演示输出重定向现象 , 即 1 -- 对于 stdout
// 关闭 1 对于的文件 , 即 : 关闭显示器文件
close(1);
int fd = open("redir.txt" , O_CREAT|O_WRONLY | O_APPEND , 0666);
//打印 , 默认打印到显示器 , 即 : 只会向 1 设备文件中打印 ; 但当关闭 stdout 文件后 , 会打印到 redir.txt 中
printf("I am file , defalut printf to stdout\n");
return 0;
}
*************** 关闭 1 , 不会在显示器上打印了 , 而是打印在了 redir.txt 中 **************
二 、深入剖析重定向现象(重要)
对以上的现象 , 为什么关闭了 1 对应的文件 , 不会打印在显示器 , 却打印到了其它文件了呢 ??
1 . 重定向的本质是什么 ???
通过以上讲解的 , 现象的剖析 , 便可得到 :
重定向的本质 : 改变文件描述符的指针指向 !!!!
2 . 各种重定向实现原理
2.1 dup2 系统调用(理解 , 掌握)
这里介绍一个系统调用 :
dup, dup2, dup3 - duplicate a file descriptor , 复制一个文件描述符
int dup2(int oldfd, int newfd);
描述 : 使用新的 newfd 这个文件描述符 , 形成 oldfd 这个文件描述符的拷贝 !
简单理解 : 它会将 oldfd 所指向的文件复制到 newfd,新的会指向旧的内容 !
2.2 输出重定向
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
// ls > xxx , 往 xxx 写入
int main()
{
int fd = open("dir.txt" , O_CREAT | O_WRONLY | O_TRUNC , 0666);
// old , new , 新的指向旧的
dup2(fd , 1); // 1 本身指向的是显示器文件 , 在 C语言中为 : stdout , 现在指向 fd
printf("Hello newfile\n");
const char* str = "dir success!\n";
write(1 , str , strlen(str));
close(fd);
return 0;
}
通过我们手动编写的原理 , 这里就可以知道为什么 > 符号可以用来新建文件了 . 因为 open 文件时会 O_CREAT , 文件不存在 , 就创建 !
2.3 输入重定向
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
// ls < xxx , 从 xxx 读取内容
int main()
{
int fd = open("dir.txt" , O_RDONLY);
dup2(fd , 0); // 0 原来指向键盘文件 , 默认从键盘上读取内容, 现在指向 dir.txt
char buff[200];
memset(buff , 0 , sizeof(buff));
//读 fd 文件
while(1)
{
// 从 0 指向的设备文件中读
ssize_t red = read(0 , buff , sizeof(buff)-1); // 不读 \0 l
if(red > 0)
{
buff[red] = 0;
printf("%s\n",buff);
}
if(red == 0 )
{
break;
}
}
return 0;
}
以上就做到了 , 本来是从键盘读取 , 现在是在新的文件中读取 !
2.4 追加重定向
这里实现一个追加输出重定向 .
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
/ 追加输出重定向
// ls >> xxx , 往 xxx 写入
int main()
{
int fd = open("dir.txt" , O_CREAT | O_WRONLY | O_APPEND , 0666);
// old , new , 新的指向旧的
dup2(fd , 1); // 1 本身指向的是显示器文件 , 在 C语言中为 : stdout , 现在指向 fd
printf("Hello newfile\n");
const char* str = "dir success!\n";
write(1 , str , strlen(str));
close(fd);
return 0;
}
这里就可以发现 , 可以追加写入到 dir.txt 这个文件中了 !
3 . 重定向的原理(面试考)
通过以上各个重定向的编写 , 我们可以发现一个共同点 , 那就是每个重定向都是 : 打开方式 + dup2 来进行的 !
面试总结 :
● 正式谈三个默认打开的文件
在之前的篇章笔者一直在铺垫三个默认打开的文件 , 但是没有正式讲解 , 这里便给出 !
通过文件描述符的学习 , 我们这里就可以知道了 , 以下 :
- 键盘文件对应文件描述符 0
- 显示器文件(stdout) 对应文件描述符 1
- 显示器文件(stderr) 对应文件描述符 2
所以 , 默认情况下 , 0 , 1 , 2 是被占用的 , 我们之后打开的文件默认是从文件描述符 3 开始的 !
那对于 stderr 这个显示器文件一直没有介绍 , 以下对其详细解释 !
一 、stderr
stderr 是显示器文件 , 那么和 stdout 的显示器文件到底有什么区别呢 ??
- stdout 是显示器文件 , 一般显示我们正常的程序信息 .
- stderr 是显示器文件 , 一般显示我们错误的程序信息 .
- 二者本质都是指向同一个硬件 , 但是二者所对应的文件描述符不同 !
描述符不同 , 目的是让我们用重定向进行正常信息和错误信息的隔离 !!!
二 、重定向的使用(重要)(重点讲解标准错误)
1 . 重定向的完整写法
假如这里需要把可执行程序 a.out 里面的内容重定向到文件 log.txt 中 .
我们平时写的 x > log.txt 的完整写法是 :
a.out 1 > log.txt
// 解释
意思就是 : 把 a.out 里面的 1 文件描述符对应的内容重定向到 log.txt 中 .
这里看一段代码理解 :
#include <iostream>
#include <cstdio>
int main()
{
//输出 , 默认是在显示器输出 , 1 对应的文件 ,stdout(C语言) , cout(C++)
printf("Hello C!\n");
std::cout << "Hello C++!" << std::endl;
//输出错误信息 , 默认也是在显示器输出 , 1 对应文件 ,stderr , cerr
const char* str = "Hello C err!\n";
fprintf(stderr , "%s" , str);
std::cerr << "Hello C++ Err!" << std::endl;
return 0;
}
- 编译后执行 , 不进行重定向 !
-
编译后执行 , 进行重定向 ! (不用完整写法)
-
编译后执行 , 进行重定向 ! (用完整写法)
所以 , 重定向时 , 可以指定文件描述符 , 这是最完整的写法 !!!
2 . 将错误信息和正常信息隔离(重要)
重定向完整写法的最大应用就是将二者进行隔离 , 这样方便程序员去查看错误信息 !
./mydir 1>log.normal 2>log.err
所以 , 要清楚
文件描述符 1 - > 对应的是显示器文件 , stdout / cout , 即 : 一般打印我们程序程序信息 !
文件描述符 2 - > 对于的是显示器文件 , stderr / cerr , 即 : 一般打印我们程序的错误信息 !
通过 重定向 可以将不同的信息打印到不同文件 ! 这是常做的 !!!!
3 . 将错误信息和正常信息打印在一起
当我们需要将二者信息同时都重定向到一个文件该怎么做 ???
可能有的人会这样做 :
.mydir 1>log.normal 2>log.normal // 这个有问题吗 ????????
上面的做法是有问题的 , 因为 > 底层是用的系统调用 open 呀 , open 的打开文件方式是用了 O_TRUNC 的 , 也就意味着会先清空在写入 , 所以 , 非常错误 !!!
正确做法
./mydir 1>log.normal 2>&1
2 > &1 的意思就是 : 再把 2 里面的内容添加到 1 里面 , 不清空 !