UNIX中文件描述符和文件指针

来源:互联网 发布:红牛 副作用 知乎 编辑:程序博客网 时间:2024/05/17 01:00
文件描述符
在C程序中,文件由文件指针或者文件描述符表示。ISO C的标准I/0库函数(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指针,UNIX的I/O函数(open, close, read, write, ioctl)使用文件描述符。下面来说下,文件描述符是如何工作的。
 
文件描述符相当于一个逻辑句柄,而open,close等函数则是将文件或者物理设备与句柄相关联。句柄是一个整数,可以理解为进程特定的文件描述符表的索引。先介绍下面三个概念,后面讲下open、close等操作以后,文件和文件描述符产生什么关系,以及fork后文件描述符的继承等问题。
 
文件描述符表:用户区的一部分,除非通过使用文件描述符的函数,否则程序无法对其进行访问。对进程中每个打开的文件,文件描述符表都包含一个条目。
 
系统文件表:为系统中所有的进程共享。对每个活动的open, 它都包含一个条目。每个系统文件表的条目都包含文件偏移量、访问模式(读、写、or 读-写)以及指向它的文件描述符表的条目计数。
 
内存索引节点表: 对系统中的每个活动的文件(被某个进程打开了),内存中索引节点表都包含一个条目。几个系统文件表条目可能对应于同一个内存索引节点表(不同进程打开同一个文件)。
 
1、举例: 执行myfd = open( "/home/lucy/my.dat", O_RDONLY); 以后,上述3个表的关系原理图如下:
http://keren.blog.51cto.com/
                                                                                  图1
 
系统文件表包含一个偏移量,给出了文件当前的位置。若2个进程同时打开一个文件(如上图A,B)做读操作,每个进程都有自己相对于文件的偏移量,而且读入整个文件是独立于另一个进程的;如果2个进程打开同一个文件做写操作,写操作是相互独立的,每个进程都可以重写另一个进程写入的内容。
 
如果上面进程在open以后又执行了close()函数,操作系统会删除文件描述符表的第四个条目,和系统文件表的对应条目(若指向它的描述符表唯一),并对内存索引节点表条目中的计数减1,如果自减以后变为0,说明没有其他进程链接此文件,将索引节点表条目也删除,而这里进程B也在open这个文件,所以索引节点表条目保留。
 
2、文件描述符的继承
通过fork()创建子进程时,子进程继承父进程环境和上下文的大部分内容的拷贝,其中就包括文件描述符表。注意,两个进程共享文件描述符,所以,文件偏移是相同的。
 
(1)对于父进程在fork()之前打开的文件来说,子进程都会继承,与父进程共享相同的文件偏移量。如下图所示(0-1-2 表示 标准输入-输出-错误):
                                  图2 fork()之前打开my.dat
 
系统文件表位于系统空间中,不会被fork()复制,但是系统文件表中的条目会保存指向它的文件描述符表的计数,fork()时需要对这个计数进行维护,以体现子进程对应的新的文件描述符表也指向它。程序关闭文件时,也是将系统文件表条目内部的计数减一,当计数值减为0时,才将其删除。
 
(2)相反,如果父进程先进程fork,再打开my.dat,这时父子进程关于my.dat 的文件描述符表指向不同的系统文件表条目,也不再共享文件偏移量(fork以后2个进程分别open,在系统文件表中创建2个条目);但是关于标准输入,标准输出,标准错误,父子进程还是共享的。
                      图3   fork()以后打开my.dat
 
 文件指针
系统IO调用有 creat(),open(),read(),write(),close(),ioctl(),接受一个文件描述符,是一个整数,用于索引开放文件的每个进程表(per-process table -of -open -file)

为了确保程序的可移植性应该使用标准IO库调用,如fopen(0,fclose(),fputc(),fseek()等,它们绝大多数中的名字中带有一 个"f"。这些调用都接受一个类型为FILE结构的指针(有时称为流指针)的参数。FILE指针指向一个流结构,在<stdio.h>中定 义。结构的内容根据编译器的不同而有所不同,在UNIX中通常是Open File的每个进程表的一个条目。在典型情况下,它包含了流缓冲区、 所有用于提示缓冲区有多少字节是实际的文件数据的变量以及提示流状态的标志(如ERROR和EOF)等

所以,文件描述符 就是 Open File中的每个进程表的一个偏移量(如"3")。它用于UNIX系统中,用于标识文件。

FILE指针保存了一个FILE结构的地址。FILE结构用于表示 开放的I/O流(如hex 20938).它用于ANSI C标准的IO库调用中,用于标识文件。


缓存
标准I/O,也就是带缓存的I/O采用FILE*,FILE实际上包含了为管理流所需要的所有信息:实际I/O的文件描述符,指向流缓存的指针(标准I/O缓存,由malloc分配,又称为用户态进程空间的缓存,区别于内核所设的缓存),缓存长度,当前在缓存中的字节数,出错标志等,假设流缓存的长度为50字节,把以上的数据写到文件,则只需要2次系统调用(fwrite调用write系统调用),因为先把数据写到流缓存,当其满以后或者调用fflush时才填入内核缓存,所以进行了2次的系统调用write
fflush将流所有未写的数据送入(刷新)到内核(内核缓冲区),fsync将所有内核缓冲区的数据写到文件(磁盘)。

注意:终端的I/O方式略有不同。与终端相关的文件是行缓冲的,而不是完全缓冲的(标准错误除外,它在默认情况下是不缓冲的),对输出来说,行缓冲意味着在缓冲区被填满或遇到一个新行符号之前,行不会被写出。

例:
#include<stdio.h>int main(void){fprintf(stdout,"a");fprintf(stderr,"a has been written!");fprintf(stdout,"b");fprintf(stderr,"b has been written!");fprintf(stdout,"\n");return 0;}

写在标准错误的消息出现在‘a’和'b'之前,因为标准输出是行缓冲的,而标准错误是不缓冲的。

说明:此文章的文件描述符部分来自博客:
http://keren.blog.51cto.com/720558/170822/
原创粉丝点击