文件描述符
在上一篇博客当中,我们知道open函数会返回一个整数,它在本进程中唯一标识了一个文件;
在一个进程中,存在着一个大数组,记录了打开的文件;这个数组的索引就是open函数返回的整数,这个索引就是文件描述符;文件描述符指向的struct file类型记录了与打开的文件相关的信息;
在操作系统当中,是通过进程控制块(PCB)来描述进程信息和相关资源的;
实际上在Linux中,PCB就是一个巨大的结构体,即task_struct结构体;
struct task_struct {
long pid; //进程号
....
//文件描述符标志位
unsigned long close_on_exec;
//数组的每一项都是文件描述符;NR_OPEN的值在linux 0.11 中被定义为20
struct file * filp[NR_OPEN];
....
};
上面的PCB结构体中,有一个struct file类型的数组 filp[NR_OPEN];
//struct file记录了文件的相关信息
struct file {
unsigned short f_mode; //文件权限位
unsigned short f_flags; //文件状态位,只读,只写等等
unsigned short f_count; //引用计数
struct m_inode * f_inode; //文件存在磁盘上的哪个位置等等其它信息由这个字段来解释
off_t f_pos; //当前偏移量
};
下图就是,PCB,文件描述符和文件表之间的关系
--flip[fd]->count;
flip[fd] = NULL;
当 count == 0 的时候,才真正的关闭文件;
有没有可能人为的让两个不同的文件描述符指向同一个struct file(文件表)?答案是完全可能,这没什么好奇怪的,函数 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 中采用的是数组,当然,完全可以采用链表来实现,这取决于不同操作系统的实现方式,万变不离其宗;
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 可正可负)
如果一个文件中的内容是 hello world,当f_pos==6的时候,执行 read 函数将从字母w开始读取,执行write 也会从 w 处开始写数据;也就是说,改变了这个f_pos的值,所有的读写都会受影响;
示例:
#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); //屏幕会打印world.
return 0;
}