可以设想,在一个进程中,存在着一个大数组(记录了打开的文件),这个数组的索引号便是 open 函数返回的整数,而这个数组的每一项,记录了打开的文件干系信息。
我们知道,在操作系统中,是通过进程掌握块(PCB)来描述进程信息和干系资源的。实际上在 linux 中,它便是一个巨大的构造体,在 linux 0.11 中,便是 task_struct 构造体。为了便于学习,我把其它用不着的部分先删除了。(代码来源于 linux 0.11,由于我们是初学者,学习这个版本的源码足够了。)
从下面的PCB构造体中,确实存在着这么一个数组 filp[NR_OPEN]。

struct task_struct { long pid; // 进程号 // ... 其它字段 // 文件描述符标志位,有关该标志位,往后再说。 unsigned long close_on_exec; // 数组索引号便是文件描述符。NR_OPEN 的值在 linux 0.11 中被定义为 20 struct file filp[NR_OPEN]; // ... 其它字段};1234567891011
那么 struct file 长啥样呢?我们知道它记录了文件的干系信息,还记得open函数的几个参数吗?有 flag, 它记录了文件状态,比如O_RDONLY,O_WRONLY,O_APPEND等等?有 mode,文件的权限位。可以预测,这个struct file 也记录着这些属性。下面是 linux 0.11 源码中的数据构造。
struct file { unsigned short f_mode; // 文件权限位 unsigned short f_flags; // 文件状态位 unsigned short f_count; // 引用计数 struct m_inode f_inode; // 文件存在磁盘上的哪个位置等等其它信息由这个字段来阐明 off_t f_pos; // 当前偏移量};1234567
用下面这张图来描述 PCB,文件描述符,文件表之间的关系,会更加清晰。
图1 PCB,文件描述符与文件表
上图中的 flip 数组的索引号,便是文件描述符。数组中的每个元素是一个指向了 struct file 类型(文件表)的指针,struct file(文件表)中记录了当前打开的文件的主要信息,个中根据 f_inode 成员就可以找到磁盘上的文件(实际由 f_inode 到磁盘文件没有图中那样大略,要稍稍繁芜点,不过为相识释问题,大家暂时可以这样理解,后面到文件系统的时候会深入)。相信到这里,你已经理清了描述符,文件表之间的关系了。在上图中,可以看到本进程中有三个文件描述符指向了同一个文件表,那么这个文件表中的引用计数一定为 3。用 C 措辞来讲便是 flip[0] == flip[1] && flip[1] == flip[2]。而第二个文件表只有一个指针指向了它,它的引用计数便是1.在调用 close(fd) 函数的时候,实际上做了两步:
--flip[fd]->count; flip[fd] = NULL;12
当 count == 0 的时候,才真正的关闭文件。
有没有可能人为的让两个不同的描述符指向同一个文件表?答案是完备可能,这没什么好奇怪的,函数 dup 和 dup2 便是干这事的。dup2(int oldfd, int newfd) 的函数做的事情大概就向下面这个样子:
int dup2(int oldfd, int newfd) { // ... close(newfd); flip[newfd] = flip[oldfd]; flip[newfd]->count++; //... return 0;}12345678
而 dup 函数只接管一个参数 oldfd,它返回系统分配的新描述符值。
并非所有的操作系统实现办法都如图 1 中所示,在 linux 0.11 中采取的是数组,当然,完备可以采取链表来实现,这取决于不同操作系统的实现办法,万变不离其宗。
2. lseek 函数函数原型// off_t 可以理解成 intoff_t lseek(int fd, off_t offset, int whence);12
lseek 函数,便是改变 flip[fd] 指向的 struct file 这个构造中的 f_pos 成员的。
当用 open 函数打开一个文件的时候,该偏移量 f_pos 被默认指定为 0。
如果 whence 即是 SEEK_SET,则 f_pos = offset(offset 只能是正数)如果 whence 即是 SEEK_CUR, 则 f_pos = f_pos + offset(offset 可正可负)如果 whence 即是 SEEK_whence,则 f_pos = 文件长度 + offset (offset 可正可负)3. 指定 f_pos 后有什么影响?如果一个文件中的内容是 hello world,当f_pos==6的时候,实行 read 函数将从字母w开始读取,实行write 也会从 w处开始写数据。
4. 示例目录树.|-lseekdemo.c|-test123test 文件
hello world1lseekdemo.c
#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>int main() { int fd = open(\"大众test\公众, O_RDONLY); if (fd < 0) { perror(\"大众open\公众); return 1; } char buf[64] = {0}; lseek(fd, 6, SEEK_SET); read(fd, buf, 64); printf(\公众%s\n\"大众, buf); return 0;}123456789101112131415161718
编译后实行,屏幕会打印 world.
5. 总结理解文件描述符是什么知道 struct file 构造体理解 lseek 事理节制 lseek 用法