FILE结构体与fd文件标识符

来源:互联网 发布:华为p7手机壳淘宝 编辑:程序博客网 时间:2024/05/19 00:12

FILE结构体


我们通常对文件进行操作时,都会使用一些与文件相关的函数,比如:

打开文件:FILE *fopen(const char *path,const char* mode)关闭文件:int fclose(FILEE *fp)读文件:size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream)写入文件:size_t fwrite(const void *ptr,size_t size,size_t nmemb,FILE* stream)

这里提一下,上面这些函数都是通过流的形式来进行的,是和缓存区打交道的东西,下面说到缓冲机制会详细说。


先拿fopen来说,函数的返回值是FILE*,这是一个文件指针类型,
第一个参数是文件的路径,第二个参数是打开的方式。
如下:

“r”:打开只读文件,若不存在,则会报错。“w”:打开只写文件,如果文件内有内容则会被直接清空,若不存在,则会自动创建。“a”:以追加方式打开只写文件,若文件不存在,则会自动创建,如果文件存在,则在内容的最后进行写入。诸如"a+","w+","r+"则都是以可读写的方式,不在赘述“b”:以二进制的方式。例:“ab+” 读写打开一个二进制文件,允许读或在文件末追加数据

说完这个文题,就该正式说下 FILE这个结构体了,FILE这个结构体包含了文件操作的基础属性,其中重要的就是关于这个fd(文件标识符)的东西,因为在底层,操作系统是不会通过什么文件指针来查找到文件,而是通过文件标识符来操作的,所以,fd是FILE结构体很重要的东西。所以我们重点来看这个。

首先,文件标识符以一系列整数,它是从0开始,依次递增的。但是一般的文件标识符都是从3开始的,这是因为系统默认打开三个流(stdin,stdout,stderr)它们的标识符依次为0,1,2,所以正常的文件标识符从3开始,但是当我们比如关掉0后,文件标识符就会先填满0,然后继续向后累加。所以,它的机制是从最低的空位开始。

每个进程在PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表(struct files_struct ),文件描述符就是这个表的索引,文件描述表中每个表项都有一个指向已打开文件的指针,已打开的文件在内核中用file结构体表,文件描述符表中的指针指向file结构体。
如下图:
这里写图片描述

文件描述符与文件指针的区别
文件描述符:在linux系统中打开文件就会获得文件描述符,它是个很小的正整数。每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。文件指针:C语言中使用文件指针做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。

缓冲区

首先,缓冲机制有三种

  1. 行缓冲:遇到\n换行即从缓冲区刷新
  2. 全缓冲:当缓冲区数据填满时刷新
  3. 无缓冲:直接将数据输出,不经过缓冲区

当然缓冲机制是可以更改的,你可以调用setbuf()setvbuf()函数来进行,不过我们重点用下面这个程序来介绍缓冲机制。

#include<stdio.h>#include<errno.h>#include<string.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>int main(){    char* msg = "I am fwrite\n";    char* msg1 = " I am write\n";    printf("I am Printf\n");    fwrite(msg,1,strlen(msg),stdout);    write(1,msg1,strlen(masg1));    pid_t id = fork();    if(id < 0)    {        printf("fork error!");        return;    }    else(id == 0)    {        printf("I am child,pid:%d\n",getpid());     }    else    {        printf("I am father,pid:%d\n",getpid());        sleep(3);    }    return 0;}

运行结果如下图:
这里写图片描述

结果是不是和预料一样,打印了5句话,那么我们试下将运行结果写入一个文件试试。


这里写图片描述
那么这个结果呢?意不意外?为什么5个输出语句,却打印出来了7句。更要注意的是,到底是哪两句多打了。
这里我们就要来分析一下了,先把文章开头没说的知识点说一下,就是对文件的操作大致是两种:

一、流式文件操作:
fopen() 打开流
fclose() 关闭流
fputc() 写一个字符到流中
fgetc() 从流中读一个字符
fseek() 在流中定位到指定的字符
fputs() 写字符串到流
fgets() 从流中读一行或指定个字符
fprintf() 按格式输出到流
fscanf() 从流中按格式读取
feof() 到达文件尾时返回真值
ferror() 发生错误时返回其值
rewind() 复位文件定位器到文件开始处
remove() 删除文件
fread() 从流中读指定个数的字符
fwrite() 向流中写指定个数的字符
tmpfile() 生成一个临时文件流
tmpnam() 生成一个唯一的文件名
这些函数都是先将数据交给缓冲区,然后在进行数据的一系列操作。


二、直接I/O文件操作
open() 打开一个文件并返回它的句柄
close() 关闭一个句柄
lseek() 定位到文件的指定位置
read() 块读文件
write() 块写文件
eof() 测试文件是否结束
filelength() 取得文件长度
rename() 重命名文件
chsize() 改变文件长度
直接的I/O是不经过缓冲区的。


还有一点,当数据直接往显示器输出时,采用行缓冲,而一旦将数据写入文件,则由行缓冲变为全缓冲。(这里的缓冲概念上面提过了,不赘述)

接下来仔细分析程序:

  1. write不经过缓冲区,所以首先被打印出来(虽然程序中先定义printf和fwrite)
  2. 由于printf和fwrite是全缓冲,所以此时这两个输出结果在缓冲区中
  3. fork()之后,产生子进程,代码共享,数据各自持有一份
  4. fork()结束,父子进程各自刷新,都会执行一次缓冲区中的数据

这个经典的例子如果搞明白,想必对这块的知识理解能更加透彻。


以上是个人目前对这块知识浅显的认知,真心希望可以得到更多学习上的指点。

0 0
原创粉丝点击