3.文件IO-文件描述符,文件表与lseek

文件描述符

在上一篇博客当中,我们知道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数组的索引号,就是 文件描述符;数组中的每个元素都是一个指向了struct file类型( 文件表)的 指针;

struct file(文件表)中记录了当前打开的文件的重要信息,其中根据 f_inode 成员就可以找到磁盘上的文件;

在上图中,可以看到本进程中有三个文件描述符指向了同一个struct file(文件表),那么这个文件表中的引用计数必然为 3;

而第二个文件表只有一个指针指向了它,它的引用计数就是1;

在调用 close(fd) 函数的时候,实际上做了两步:
--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;
}

猜你喜欢

转载自blog.csdn.net/Regemc/article/details/80670023