UNIX IO---再谈文件描述符

http://keren.blog.51cto.com/720558/170822/


在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

后续会加入程序进行完善性说明,程序灵感来自:《UNIX环境高级编程》第三章和第8章

程序:

#include <unistd.h>
#include <stdio.h>


int glob = 6;
char buf[] = "a write to stdout\n";


int main()
{
int var;
pid_t pid;
var = 88;
int n = sizeof(buf);
if ( write( STDOUT_FILENO,buf, n - 1 ) != n-1 )
printf( "write error \n");

printf( "before fork\n");
if ( ( pid = fork()) < 0)
printf( "fork error\n");
else if (( 0 == pid ))
{
glob++;
var++;
printf( "I am child\n");
}
else 
{
printf( "child, you are big\n" );
sleep(10);
}

printf( "pid = %d, glob = %d, var = %d\n", getpid(), glob, var );
exit(0);
}


执行结果:

G2DBT1:/wxp#./fork
a write to stdout
before fork
child, you are big
I am child
pid = 23003200, glob = 7, var = 89
pid = 27852918, glob = 6, var = 88
G2DBT1:/wxp#./fork > result
G2DBT1:/wxp#vi result
"result" 7 lines, 142 characters 
a write to stdout
before fork
I am child
pid = 23265392, glob = 7, var = 89
before fork
child, you are big
pid = 23789746, glob = 6, var = 88


执行结果不一致的说明:

第一种输出:

特点:标准输出为终端

父进程先返回然后把CPU时间让给子进程执行


第二种输出:

特点:标准输出重定向为文件

先执行子进程,再执行父进程

当进程终止的时候,会将缓存里面的内容写入到文件里。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值