文件系统 | 系统调用之读写文件

打开文件的最基本的目的是为了对文件进行访问,而访问中最基本的操作是读和写。文件的内容被保存在磁盘块,但是这些磁盘块极有可能是不连续的而且往往是动态变化的。而访问文件时我们往往又需要将文件看作一个连续的字节流,要解决这样的矛盾,核心在于地址空间(Address Space)的引入。

地址空间的目的就是将其属主对象在磁盘或其他介质中的(可能不连续的)数据以页面为单位连续地呈现出来。根据需要,这些页面可以,也可以不在系统内存,称为页面缓存,对属主对象的读写操作可以在页面缓存中进行,只是在需要时按照属主对象特定的操作语义从磁盘(或其他介质)读,或像磁盘(或其他介质)写。

简而言之就是维护了文件页和物理页的关系,并决定它们该在什么时候读出和写回,这里已经是我们整个IO链条的最上层部分了。

数据结构

尽管在上文已经贴过了address_space及其操作表的结构,但是这里还是再粘贴一次:

struct address_space {
  struct inode    *host; // inode或块设备,属主
  struct xarray    i_pages; // 缓存页们
  struct rw_semaphore  invalidate_lock; 
  // 保护(页面缓存内容)和(文件偏移与磁盘块映射关系)的一致性
  // 它还用于通过内存映射阻止对页面缓存内容的修改。
  gfp_t      gfp_mask;
  atomic_t    i_mmap_writable; // 地址空间中共享内存映射的数目
#ifdef CONFIG_READ_ONLY_THP_FOR_FS
  /* number of thp, only for non-shmem files */
  atomic_t    nr_thps;
#endif
  struct rb_root_cached  i_mmap; // 私有和共享映射树根
  struct rw_semaphore  i_mmap_rwsem; // 保护 i_mmap
  unsigned long    nrpages; // 地址空间中页面总数
  pgoff_t      writeback_index; // 写回开始处
  const struct address_space_operations *a_ops; // 地址空间操作表
  unsigned long    flags; // 错误标记
  errseq_t    wb_err; // 最近一个错误
  spinlock_t    private_lock;  // 保护地址空间私有链表的锁
  struct list_head  private_list; // 地址空间私有链表
  void      *private_data;
} __attribute__((aligned(sizeof(long)))) __randomize_layout;

文件地址空间被分割为一个个以页面大小为单元的数据块,这些数据块(页)被组织成一个多叉树,被称为基(radix)树,树中所有叶子节点为一个个页面结构(struct page),表示用于缓存该文件的每一个页,叶子层最左端的第一个页面保存着该文件的前4096字节(若页大小为4K),接下来的页面保存着第二个4K字节,以此类推。在5.15的内核版本中,这棵树被组织为一棵红黑树:

struct rb_root_cached {
  struct rb_root rb_root;
  struct rb_node *rb_leftmost;
};

这已经是内存管理的知识了,我们目前主要关注的是内存管理和文件管理打交道的那一部分。

和address_space相关的操作方法都在a_ops表中(昨天也贴过了,但是还是再贴一遍):

struct address_space_operations {
  /* 更新内存到磁盘,调用前page已是最新,第一个参数为指向待写内存页描述符指针,第二个参数为指向控制回写行为结构的指针*/
  int (*writepage)(struct page *page, struct writeback_control *wbc);
  /* 读取文件file数据到内存page */
  int (*readpage)(struct file *, struct page *);


  /* 回写多个脏页时调用,第一个参数为指向地址空间的指针,第二个参数指向回写控制结构,
  如果同步模式为WBC_SYNC_ALL,回写控制中会指定要写出页面的范围 */
  /* Write back some dirty pages from this mapping. */
  int (*writepages)(struct address_space *, struct writeback_control *);


  /* Set a page dirty.  Return true if this dirtied it 标记传入page为脏*/
  int (*set_page_dirty)(struct page *page);


  /*
   * Reads in the requested pages. Unlike ->readpage(), this is
   * PURELY used for read-ahead!. 用于预读场合,读取多个页面到地址空间
   */
  int (*readpages)(struct file *filp, struct address_space *mapping,
      struct list_head *pages, unsigned nr_pages);
  void (*readahead)(struct readahead_control *);


 /*  要求具体文件系统准备将指定偏移和长度的数据写入到文件 
  *   VFS通过调用write_begin告诉具体文件系统,准备写文件字节pos~pos+len到给定页面
  *   具体文件系统要确保本次操作能完成,必要时分配空间,非覆写时从磁盘读取数据到页面等
  *   第一个参数为指向文件的指针,第二个参数为指向地址空间的指针,第三个第四个参数如上介绍
  *   第五个参数为flag,第六个参数为page指针数组,如果为NULL,则需要具体文件系统分配,并通过它返回线程安全的页面
  *   第七个参数为由具体文件系统解释的数据结构
  */
  int (*write_begin)(struct file *, struct address_space *mapping,
        loff_t pos, unsigned len, unsigned flags,
        struct page **pagep, void **fsdata);
 /*   在成功调用write_begin,并完成数据复制后必须调用,
  *   VFS通过调用write_end告诉具体文件系统,文件字节pos~pos+len数据已被复制到页面,现在可以提交磁盘
  *   具体文件系统要确保本次操作能完成,必要时分配空间,非覆写时从磁盘读取数据到页面等
  *   第一个参数为指向文件的指针,第二个参数为指向地址空间的指针,第三个第四个参数如上介绍
  *   第五个参数为已写字节数,第六个参数为指向页面的指针
  *   第七个参数为由具体文件系统解释的数据结构
  *   具体文件系统需要负责解锁页面,释放引用计数,并更新i_size。
  *   失败返回负的错误码;否则返回能够被复制到页面缓存页面的字节数(<=copied)
  */
  int (*write_end)(struct file *, struct address_space *mapping,
        loff_t pos, unsigned len, unsigned copied,
        struct page *page, void *fsdata);


  /* Unfortunately this kludge is needed for FIBMAP. Don't use it 
  *  将文件中逻辑扇区编号映射为物理扇区编号
  */
  sector_t (*bmap)(struct address_space *, sector_t);
  /* 使某个页面全部或部分失效,用在截断文件时 */
  void (*invalidatepage) (struct page *, unsigned int, unsigned int);
  /* 用于释放页面 */
  int (*releasepage) (struct page *, gfp_t);
  void (*freepage)(struct page *);
  ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter);
  /*
   * migrate the contents of a page to the specified target. If
   * migrate_mode is MIGRATE_ASYNC, it must not block.
   * 将页面内容移动到指定的目标
   */
  int (*migratepage) (struct address_space *,
      struct page *, struct page *, enum migrate_mode);
  bool (*isolate_page)(struct page *, isolate_mode_t);
  void (*putback_page)(struct page *);
  /* 在释放一个页面之前被调用,回写一个脏页 */
  int (*launder_page) (struct page *);
  /* 在处理缓冲IO请求时,被调用以判断要读取的这部分数据在页面中是否为最新的 */
  int (*is_partially_uptodate) (struct page *, unsigned long,
          unsigned long);
  void (*is_dirty_writeback) (struct page *, bool *, bool *);
  int (*error_remove_page)(struct address_space *, struct page *);


  /* swapfile support */
  int (*swap_activate)(struct swap_info_struct *sis, struct file *file,
        sector_t *span);
  void (*swap_deactivate)(struct file *file);
};

读写操作是在内核IO控制块的控制下进行的,实际上这样的结构是为了支持异步IO(Asychronous IO,AIO),实现用户空间和内核之间的异步,对应系统调用是io_setup, io_submit, io_cancel, io_destory等,在f2fs的读写流程中,vfs向f2fs会传入一个内核控制块及io向量

struct kiocb {
  struct file    *ki_filp;


  /* The 'ki_filp' pointer is shared in a union for aio */
  randomized_struct_fields_start


  loff_t      ki_pos; // 当前进行io操作的文件位置
  void (*ki_complete)(struct kiocb *iocb, long ret, long ret2);
  void      *private; // 可以被文件系统层自由使用
  int      ki_flags; // kiocb描述符标志位
  u16      ki_hint;
  u16      ki_ioprio; /* See linux/ioprio.h */
  union {
    unsigned int    ki_cookie; /* for ->iopoll */
    struct wait_page_queue  *ki_waitq; /* for async buffered IO */
  };


  randomized_struct_fields_end
};
---iov_iter是对iovec的迭代器
struct iov_iter {
  u8 iter_type; // 迭代器类型 判断union是啥
  bool nofault;
  bool data_source;
  size_t iov_offset; // iov指针指向的第一个iovec结构体中数据的第一个字节偏移
  size_t count; // iov数组存放个数(有几个缓冲区段)
  union {
    const struct iovec *iov;
    const struct kvec *kvec;
    const struct bio_vec *bvec;
    struct xarray *xarray;
    struct pipe_inode_info *pipe;
  };
  union {
    unsigned long nr_segs;
    struct {
      unsigned int head;
      unsigned int start_head;
    };
    loff_t xarray_start;
  };
};
--- 一个iovec就是一个用户空间缓冲区地址+长度(==一段用户缓冲区范围)
struct iovec
{
  void __user *iov_base;  /* BSD uses caddr_t (1003.1g requires void *) */
  __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};

read系统调用

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
  return ksys_read(fd, buf, count); // 从fd 读 count 到 buf里面
}
---ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
  struct fd f = fdget_pos(fd); // 根据fd取出file
  ssize_t ret = -EBADF;


  if (f.file) {
    loff_t pos, *ppos = file_ppos(f.file); // 获得当前读写位置
    if (ppos) {
      pos = *ppos;
      ppos = &pos;
    }
    ret = vfs_read(f.file, buf, count, ppos); // 读,从file的ppos开始读count到buf里面
    if (ret >= 0 && ppos)
      f.file->f_pos = pos; // 修改当前读写位置
    fdput_pos(f); // 引用计数
  }
  return ret;
}
---
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
  ssize_t ret;


  if (!(file->f_mode & FMODE_READ))
    return -EBADF;
  if (!(file->f_mode & FMODE_CAN_READ))
    return -EINVAL;
  if (unlikely(!access_ok(buf, count))) // 确认缓冲区可访问
    return -EFAULT;


  ret = rw_verify_area(READ, file, pos, count); // 检查锁冲突
  if (ret)
    return ret;
  if (count > MAX_RW_COUNT)
    count =  MAX_RW_COUNT;


  if (file->f_op->read) // 调用f_op中的方法
    ret = file->f_op->read(file, buf, count, pos);
  else if (file->f_op->read_iter) // 没有read,但有read_iter,eg. f2fs
    ret = new_sync_read(file, buf, count, pos);
  else
    ret = -EINVAL;
  if (ret > 0) {
    fsnotify_access(file);
    add_rchar(current, ret);
  }
  inc_syscr(current);
  return ret;
}
---
static ssize_t new_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
  struct iovec iov = { .iov_base = buf, .iov_len = len };
  struct kiocb kiocb;
  struct iov_iter iter;
  ssize_t ret;


  init_sync_kiocb(&kiocb, filp);
  kiocb.ki_pos = (ppos ? *ppos : 0);
  iov_iter_init(&iter, READ, &iov, 1, len);


  ret = call_read_iter(filp, &kiocb, &iter); // == file->f_op->read_iter(kio, iter);
  BUG_ON(ret == -EIOCBQUEUED);
  if (ppos)
    *ppos = kiocb.ki_pos;
  return ret;
}
---iov_iter进行初始化:
void iov_iter_init(struct iov_iter *i, unsigned int direction,
      const struct iovec *iov, unsigned long nr_segs,
      size_t count)
{
  WARN_ON(direction & ~(READ | WRITE));
  *i = (struct iov_iter) {
    .iter_type = ITER_IOVEC,
    .nofault = false,
    .data_source = direction,
    .iov = iov,
    .nr_segs = nr_segs,
    .iov_offset = 0,
    .count = count
  };
}
---kiocb初始化:
static inline void init_sync_kiocb(struct kiocb *kiocb, struct file *filp)
{
  *kiocb = (struct kiocb) {
    .ki_filp = filp,
    .ki_flags = iocb_flags(filp),
    .ki_hint = ki_hint_validate(file_write_hint(filp)),
    .ki_ioprio = get_current_ioprio(),
  };
}

最终,配置好的kiocb和iov_iter被派发到f2fs等具体操作系统中执行。

write系统调用

了解read系统调用之后,write系统调用是十分相似的过程。

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
    size_t, count)
{
  return ksys_write(fd, buf, count);
}
---
ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
{
  struct fd f = fdget_pos(fd);
  ssize_t ret = -EBADF;


  if (f.file) {
    loff_t pos, *ppos = file_ppos(f.file); // 获取文件读写位置
    if (ppos) {
      pos = *ppos;
      ppos = &pos;
    }
    ret = vfs_write(f.file, buf, count, ppos); // 调用vfs层write
    if (ret >= 0 && ppos)
      f.file->f_pos = pos; // 更新文件读写位置
    fdput_pos(f);
  }


  return ret;
}
---
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
  ssize_t ret;


  if (!(file->f_mode & FMODE_WRITE))
    return -EBADF;
  if (!(file->f_mode & FMODE_CAN_WRITE))
    return -EINVAL;
  if (unlikely(!access_ok(buf, count)))
    return -EFAULT;


  ret = rw_verify_area(WRITE, file, pos, count);
  if (ret)
    return ret;
  if (count > MAX_RW_COUNT)
    count =  MAX_RW_COUNT;
  file_start_write(file);
  if (file->f_op->write)
    ret = file->f_op->write(file, buf, count, pos); // 如果定义了write
  else if (file->f_op->write_iter)
    ret = new_sync_write(file, buf, count, pos); // 定义write_iter
  else
    ret = -EINVAL;
  if (ret > 0) {
    fsnotify_modify(file);
    add_wchar(current, ret);
  }
  inc_syscw(current);
  file_end_write(file);
  return ret;
}
---
static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
  struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
  struct kiocb kiocb;
  struct iov_iter iter;
  ssize_t ret;


  init_sync_kiocb(&kiocb, filp);
  kiocb.ki_pos = (ppos ? *ppos : 0);
  iov_iter_init(&iter, WRITE, &iov, 1, len); // 写


  ret = call_write_iter(filp, &kiocb, &iter);
  BUG_ON(ret == -EIOCBQUEUED);
  if (ret > 0 && ppos)
    *ppos = kiocb.ki_pos;
  return ret;
}

可见在vfs层,读写流程几乎一模一样,主要功能是将kiocb和iov_iter下发到具体文件系统执行。后面我们将以F2FS为具体文件系统继续分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值