分享

文件IO-文件描述符与lseek

 新用户0175WbuX 2022-01-24

  在讲 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,文件描述符,文件表之间的关系,会更加清晰。

  文件IO-文件描述符与lseek

  图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 用法

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多