文章目录
- 什么是文件描述符fd?
- 文件描述符的分配规则
- 重定向的原理
什么是文件描述符fd?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME(n) "tag.txt"#n
int main()
{
int fd1 = open(FILE_NAME(1), O_RDWR | O_CREAT | O_TRUNC, 0666);
int fd2 = open(FILE_NAME(2), O_RDWR | O_CREAT | O_TRUNC, 0666);
int fd3 = open(FILE_NAME(3), O_RDWR | O_CREAT | O_TRUNC, 0666);
int fd4 = open(FILE_NAME(4), O_RDWR | O_CREAT | O_TRUNC, 0666);
printf("fd1=%d\n", fd1);
printf("fd2=%d\n", fd2);
printf("fd3=%d\n", fd3);
printf("fd4=%d\n", fd4);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
return 0;
}
可以看到,创建了四个文件,文件描述符分别是3、4、5、6,那么前面的数字去哪里了呢?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME(n) "tag.txt"#n
int main()
{
printf("fd1=%d\n", stdin->_fileno);
printf("fd2=%d\n", stdout->_fileno);
printf("fd3=%d\n", stderr->_fileno);
return 0;
}
其实文件描述符的0、1、2被标准输入、标准输出和标准错误给占用了。
一个系统中,会打开非常非常多的文件,这些文件OS也是需要进行管理的,而管理的方法就是先描述再组织,将所有文件共有的属性描述成结构体struct file __rcu
,而在进程的PCB里有一个指针struct files_struct *files
指向结构体struct files_struct
,该结构体中有一个指针数组struct file __rcu * fd_array[NR_OPEN_DEFAULT];
属于当前进程的所有文件的结构体指针都在该数组中,而该数组的下表就是文件描述符fd!
stdin、stdout、stderr默认占了该数组的前三个位置,而后面打开的文件则会依次填入该数组,所以fd会以0、1、2、3、4……的方式给出。
文件描述符的分配规则
我们每次用open打开文件,总是会用close来关闭文件。open会给我们返回fd,也就是新打开的文件的结构体指针已经填入到了那个数组中,那么close来关闭文件也就是将该数组对应下标的指针给释放。那么stdin、stdout、stderr是否可以关闭呢?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME(n) "tag.txt"#n
int main()
{
close(0);
close(2);
int fd1 = open(FILE_NAME(1), O_RDWR | O_CREAT | O_TRUNC, 0666);
int fd2 = open(FILE_NAME(2), O_RDWR | O_CREAT | O_TRUNC, 0666);
printf("fd1=%d\n", fd1);
printf("fd2=%d\n", fd2);
close(fd1);
close(fd2);
return 0;
}
可以发现,当关闭了stdin、stderr对应的0和2后,我们新打开的文件分配的fd就变成了0和2,这也就说明了,fd的分配规则是从数组的0下标开始依次遍历,该位置上无文件则分配,有文件则跳过。
重定向的原理
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME(n) "tag.txt"#n
int main()
{
close(1);
int fd = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_TRUNC, 0666);
const char *s = "hello world!\n";
write(1, s, strlen(s));
close(fd);
return 0;
}
该程序如果未关闭文件描述符1的话,也就是未关闭stdout的话,正常是应该往显示器上输出hello world!,但是关闭之后,hello world!就写入到了文件tag.txt1中!
原因是:关闭文件描述符1也就是将那个数组下标为1的指针关闭,而此时又open了一个文件,所以该文件分配的文件描述符就是1,但是write函数就是往1里面写,也就将本来写入到stdout里的内容写入到了tag.txt1中。
所以,输出重定向的原理就是关闭1,再让新打开的文件的文件描述符分配上1;
输入重定向的原理就是关闭0,再让新打开的文件的文件描述符分配上0。
但是这样的操作太过繁琐,于是就有一个函数,一步到位:dup2。
#include <unistd.h>
int dup2(int oldfd, int newfd);
参数:
newfd是oldfd的拷贝,也就是将newfd位置上的指针变为oldfd位置上的指针。
返回值:
成功返回newfd
失败返回-1
于是上面的函数就可以改为:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME(n) "tag.txt"#n
int main()
{
int fd = open(FILE_NAME(1), O_WRONLY | O_CREAT | O_TRUNC, 0666);
dup2(fd, 1);
const char *s = "hello world!\n";
write(1, s, strlen(s));
close(fd);
return 0;
}