之前谈到open函数的返回值fd是一个小整数,每次我们读写操作时,发现fd总是从3开始。
OS必然同时打开多个进程,每个进程都进行文件读写操作,他们的fd是如何分配的呢?
关于什么是重定向,之前我们理解是将内容从一个地方输入到另一个地方,本文将会详细探讨。
上文文件系统调用接口的read和write都涉及到缓冲区,这个缓冲区是什么?是OS的吗?
详细阅读本文,能帮大家理清这些问题。
文件是由进程打开的,一个进程可以打开多个文件,也就是说!
在内核中,时刻存在大量被打开的文件 ,他们的关系是 进程:文件=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文件描述符就是一个特定的数组下标,通过这个下标,就能找到对应的文件!
在我们的设备中,有网卡,显示器,键盘,磁盘等外设。
他们有自己的一套读写方式,不同的设备读写方式不同。
打开文件时,fd会被分配用来管理文件。
struct_file的方法将统一的函数指针传给驱动设备的read\write ,像read_keyboard,write_keyboard这种就是每个设备的特殊方式,驱动直接管理外设,有点多态的感觉。
OS看待各个不同的设备也就是普通的struct_file ,不管是显示器,还是键盘,都是read 和write的原始函数指针。所以OS并不关心底层的差异!Linux下一切皆文件!
示例:
我们在进程中,以w的方式打开三个文件。文件不存在就创建。
打印出他们的fd
1 #include
2 #include 3 #include 4 #include 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
2 #include 3 #include 4 #include 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 } ~
结论!
对原本的程序在写入后添加fflush刷新后就能得到写入的内容
常见Linux下的echo指令
echo "hollow" > txt 这个>的方式我们就叫重定向,把本应该输出到显示器的hellow输出到文件里。
类似的 >> 是文件追加重定向。将打开文件的时候,不会清空,是将内容追加到末尾 。
这里就探讨一下重定向的本质。
常见 < 输入重定向,将内容输入到指定文件。
>>追加重定向
>输出重定向
#include
#include
#include
#include
#include
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上,这一过程就叫做输出重定向!
1号文件和2号文件的输出都是显示器,它们的区别是什么?
#include
#include 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输出内容
这样的操作就将标准输出和标准错误分开。
在我们的工程中,许多操作都需要将错误和正常的信息得到,那么标准错误的方式就能清晰可见。
将标准错误和标准输出的内容合到一个文件
演示
对于原先的重定向,先关闭1号文件,再打开新文件的操作,来将fd_array[]上的文件替换。
这是一个繁琐的工作!
Linux下提供dup2文件简化这一操作
通过man 手册查看dup2的信息
作用是将oldfd 覆盖到newfd 让newfd内容替换成oldfd的
必要时候可以关闭fd文件
演示:
1 #include
2 #include 3 #include 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的使用是相对简单
下面我们还有一个问题,关于缓冲区??为什么在上述的例子刷新缓冲区就能得到显示内容!
在下一篇文章,将介绍缓冲区。