目录
通过对open函数的学习,我们知道了文件描述符就是一个小整数。
1.fd0、1、2
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#define FILE_NAME(number) "log.txt"#number
//宏参数,转换为字符串,两个字符串有自动连接特性
//
int main()
{
printf("stdin->fd:%d\n",stdin->_fileno);
printf("stdout->fd:%d\n",stdout->_fileno);
printf("stderr->fd:%d\n",stderr->_fileno);
umask(0);
int fd0=open(FILE_NAME(1),O_WRONLY | O_CREAT |O_APPEND,0666);
int fd1=open(FILE_NAME(2),O_WRONLY | O_CREAT |O_APPEND,0666);
int fd2=open(FILE_NAME(3),O_WRONLY | O_CREAT |O_APPEND,0666);
int fd3=open(FILE_NAME(4),O_WRONLY | O_CREAT |O_APPEND,0666);
int fd4=open(FILE_NAME(5),O_WRONLY | O_CREAT |O_APPEND,0666);
printf("FILE1 fd:%d\n",fd0);
printf("FILE2 fd:%d\n",fd1);
printf("FILE3 fd:%d\n",fd2);
printf("FILE4 fd:%d\n",fd3);
printf("FILE5 fd:%d\n",fd4);
close(fd0);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
return 0;
}
stdin,stdout,stderr(FILE*类型对象)里封装了文件描述符。
标准输入、标准输出、标准错误默认占据每个进程的文件描述符表的0,1,2,由该进程创建文件的fd只能从3开始编号。
0,1,2对应的物理设备一般是:键盘,显示器,显示器。重定向后0,1,2可以对应其他文件。
2.文件描述符
44struct files_struct {
48 atomic_t count;
49 struct fdtable *fdt;
50 struct fdtable fdtab;
54 spinlock_t file_lock ____cacheline_aligned_in_smp;
55 int next_fd;
56 struct embedded_fd_set close_on_exec_init;
57 struct embedded_fd_set open_fds_init;
58 struct file * fd_array[NR_OPEN_DEFAULT];//指针数组,指向文件描述符表
59};
FILE 是C语言文件结构定义,file是操作系统内核的文件结构体。
操作系统为了管理被打开的文件,为文件创建对应的 内核数据结构 标志文件struct file{},file里包含了文件的大部分属性,以链式结构管理。
1.进程调用open()打开文件,会先初始化文件的file对象,把磁盘中的文件加载到内存。
2.在进程的文件描述符表fd_array[]找到最小未使用下标,在该下标位置填入file指针。数组下标就是文件描述符。给open返回文件描述符。
3.访问文件也通过文件描述符经过进程task_struct,找到文件描述符表里找。
分配规则
文件描述符表的数组从下标0开始,找到当前未被使用的最小下标。
例:如果close(0),取消指向,打开文件的文件描述符就会匹配到0。
3.重定向
为什么没向显示器打印fd1?
文件操作以文件标识符为依据指定文件,系统默认0为标准输入,1为标准输出,2为标准错误。
printf/fprintf默认向stdout打印,stdout里封装的文件描述符是1。
所以printf/fprintf向标识符为1的文件打印。但当前进程的标识符1的指向已经变了,log.txt的fd为1,所以向文件打印。
强制刷新stdout的缓冲区,才能成功打印到文件,否则不能成功写入到文件。
了解缓冲区后的更多理解:
磁盘文件和显示器文件的缓冲区刷新策略不同。数据写到stdout的缓冲区里了,但是因为刷新策略是全缓冲,数据一直在stdout的缓冲区,没到内核缓冲区。
进程结束时刷新内核缓冲区,而数据没有写入到内核缓冲区。
所以只能强制调fflush去刷新。fflush先将stdout缓冲区数据写入到内核缓冲区(write()),再调系统接口强制刷新到外设(磁盘)。
3.1 重定向的本质
上层接口使用的fd不变,在内核中更改fd对应的指针(struct file*)。
常用的重定向指令:
1.>:输出
2.>>:追加
3.< :输入
3.2 dup2系统调用
dup2是一个更方便的重定向接口。
功能:newfd是oldfd的拷贝,拷贝oldfd。把oldfd下标存的内容拷贝给newfd。
例1:追加重定向
方法一:先close(1),再打开文件,打开文件的文件描述符为1
-
不强制刷新:只有write的数据被写入
只写入了helloworld,原因:write直接将数据写入到内核缓冲区,fprintf/printf写入到stdout缓冲区,再根据刷新策略决定是否刷新到内核缓冲区。刷新策略是全缓冲,也就是没刷新到内核。也没强制刷新,只能等进程结束刷新内核缓冲区。现在内核只有helloworld。
-
强制刷新:观察到调用位置不同,结果不同
后三行追加,虽然先调用的fprintf和printf,但是write的数据先到内核缓冲区,后调的强制刷新。
改变调用fflush的位置后,printf/fprintf的缓冲区数据先到内核缓冲区且先同步到外设。write等进程结束再同步到外设。
方法二:dup2
调用dup2重定向,再也不用自己调强制刷新了。但是还是顺序问题,等进程结束才帮忙刷新stdout的缓冲区。