fd(文件描述符)与FILE结构体

2019-07-14 09:30发布

文件描述符fd
通过对open函数的学习我们现在知道了文件描述符就是一个小整数,从0开始。是文件指针数组的下标。
0&1&2 #include 2 #include 3 #include 4 #include 5 #include 6 7 int main(){ 8 char buf[1024]; 9 printf("fd "); 10 ssize_t s =read(0,buf,sizeof(buf)); 11 if(s>0){ 12 buf[s]=0; 13 printf("fd1 "); 14 write(1,buf,strlen(buf)); 15 printf("fd2 "); 16 write(2,buf,strlen(buf)); 17 printf("fd3 "); 18 write(3,buf,strlen(buf)); 19 } 20 return 0; 21 } 22 执行的结果如下:
这里写图片描述
linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。然而3不是缺省打开的文件描述符。
0、1、2对应的物理设备一般是:键盘,显示器,显示器
文件描述符是如何找到对应的文件的
这里写图片描述
Linux下大尅文件就会获得文件描述符,它是一个很小的整数,每个进程在PCB中保存着一份文件描述符表,文件描述符就是这个表的索引,,每个表项都有一个指向已打开文件的指针,文件指针,C语言中使用文件指针作为IO的句柄。文件指针指向进程用户区中一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符,而文件描述符就是文件描述符表的一个索引,因此从某种意义上来说,文件指针就是句柄的句柄(在Windows系统上文件描述符被称为文件句柄)。
每个进程都有一个PCB,每一个PCB中都包含一个file指针,它指向一个文件结构体,结构体中包含了一个文件指针数组,每个元素都包含了一个指向打开文件的指针!,所以文件描述符就是该文件指针数组的下标,因此,我们只需要知道文件描述符,就可以通过文件描述符去找到指针指向的文件。
文件描述符的分配规则
在file_struct结构体中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
文件描述符也是一种资源,这种分配规则可以更好的利用资源。 文件描述符的有效范围是 0 到 OPEN_MAX。一般来说,每个进程最多可以打开 64 个文件(0 — 63)。对于 FreeBSD 5.2.1、Mac OS X 10.3 和 Solaris 9 来说,每个进程最多可以打开文件的多少取决于系统内存的大小,int 的大小,以及系统管理员设定的限制。Linux 2.4.22 强制规定最多不能超过 1,048,576 。
1)当没有关闭默认文件描述符时,新文件的文件描述符等于3。
2)当关闭了0时,新文件的文件描述符时0;
3)关闭使用close(0)
重定向
概念:就是各种请求重新定个方向转到其他位置。
分类:输出重定向(>)、输入重定向(<)、追加重定向(>>) #include 2 #include 3 #include 4 #include 5 #include 6 7 int main(){ 8 close(1); 9 int fd=open("myfile",O_WRONLY|O_CREAT,0644); 10 if(fd<0){ 11 perror("open"); 12 return 1; 13 } 14 printf("fd:%d ",fd); 15 fflush(stdout); 16 17 close(fd); 18 exit(0); 19 } ~ 根据分配原则,新打开的文件接收到的文件描述符是1,但是结果是咋样呢?
这里写图片描述
我们发现本来输出到显示器上的内容,输出到了文件myfile中,其中fd=1,这种现象叫做输出重定向。
那输出重定向的本质是什么?
这里写图片描述
printf是c库当中的IO函数,一般往stdout中输出,但是stdout底层访问文件的时候,找的换是fd:1,但是此时,fd:1下标所表示内容,已经变成了myfile的地址,不再是显示器的文件的地址,所以,输出任何消息都会往文件中写入,进而完成输出重定向。
FILE结构体
1、FILE是在stdio.h定义的保存文件流信息的一个结构体类型。
2、本质上访问文件都是通过fd访问的,库函数封装了fd,所以,FILE结构体内部肯定封装了fd。
3.fork缓冲问题。
//行缓冲:比如写入显示器中,输入的数先进入行缓冲区,当满足条件时在进一步输出到屏幕。
//全缓冲;比如写入文件中。
//无缓冲
①使用三种操作 方式(库函数、系统调用)往显示器上打印数据。
1 #include 2 #include 3 4 int main(){ 5 const char *msg0="hello printf "; 6 const char *msg1="hello fwrite "; 7 const char *msg2="hello write "; 8 9 printf("%s",msg0); 10 fwrite(msg1,strlen(msg0),1,stdout); 11 write(1,msg2,strlen(msg2)); 12 13 fork(); 14 return 0; 15 } 16 ~ 运行结果如下所示:
这里写图片描述 ②但是如果对进程实现输出重定向呢?我们发现结果变成了:
这里写图片描述
我们发现printf和fwrite(库函数)都输出了2次,而write只输出了一次(系统调用)为什么呢?这与fork有关。
为什么会出现这种情况呢?不应该都是出现两次吗?
答:当进程输出重定向到新文件时,数据的缓冲方式由行缓冲变为了全缓冲,printf与fwrite(库函数自带缓冲区)先进入了全缓冲区,不会立即被刷新;write是系统调用,没有缓冲区,直接输出到了屏幕,当下一步执行fork()函数时,子进程的执行起点为fork之后,但此时父子数据会发生写时拷贝,所以当父进程全缓冲区准备刷新的时候,子进程也有了同样的一份数据,随机产生两份数据。那为什么没有write数据呢?因为写时拷贝,只有在写入时,数据才被复制。
所以当父进程全缓冲区准备刷新的时候,子进程也有了同样的一份数据,随机产生两份数据。那为什么子没有write数据呢?因为写时拷贝,只有在写入时,数据才被复制。
一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲的。 printf、fwrite库函数会自带缓冲区(进度条例子就可以说明)当发生重定向到普通文件时,数据的缓冲方式由行缓冲变为了全缓冲。
而我们放在缓冲区的数据,就不会立即被刷新,甚至fork之后。
但是进程退出后,会统一刷新,写入文件当中。
但是fork的时候,父子进程数据会写时拷贝,所以当你父进程准备刷新的时候,子进程也有了同样的一份数据,随即产生两份数据。
write没有变化,说明没有所谓的缓冲。
FILE结构体 #ifndef _FILE_DEFINED struct _iobuf { char *_ptr; //文件输入的下一个位置     int _cnt; //当前缓冲区的相对位置     char *_base; //指基础位置(即是文件的其始位置)     int _flag; //文件标志     int _file; //文件的有效性验证     int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取     int _bufsiz; //缓冲区大小     char *_tmpfname; //临时文件名 }; typedef struct _iobuf FILE; 上面结构体中的 _file 实际上是一个描述符,作为进入打开文件表索引的整数。
就是我们说的文件描述符。