在讲 lseek 前,先讲讲上一节文件描述符这个遗留的问题。 1. 文件描述符 在上一节中,我们已经知道 open 函数返回一个整数,它在本进程中唯一的标识了一个文件。那么,它到底是怎样标识的呢? 可以设想,在一个进程中,存在着一个大数组(记录了打开的文件),这个数组的索引号就是 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]; // ... 其它字段 }; 那么 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; // 当前偏移量 }; 用下面这张图来描述 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; 当 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; } 而 dup 函数只接受一个参数 oldfd,它返回系统分配的新描述符值。 并非所有的操作系统实现方式都如图 1 中所示,在 linux 0.11 中采用的是数组,当然,完全可以采用链表来实现,这取决于不同操作系统的实现方式,万变不离其宗。 2. lseek 函数函数原型 // off_t 可以理解成 int off_t lseek(int fd, off_t offset, int whence); 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 |-test test 文件 hello world 1 lseekdemo.c #include #include #include #include #include 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 ", buf); return 0; } 编译后执行,屏幕会打印 world. 5. 总结理解文件描述符是什么知道 struct file 结构体理解 lseek 原理掌握 lseek 用法 |
|
来自: 新用户0175WbuX > 《待分类》