Skip to content

file.c

TIP

为涉及文件描述符的系统调用提供支持函数。

#include "types.h"
#include "riscv.h"
#include "defs.h"
#include "param.h"
#include "fs.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "file.h"
#include "stat.h"
#include "proc.h"

TIP

设备驱动表,devsw[i] 包含主设备号 i 的驱动函数

struct devsw devsw[NDEV];

TIP

全局文件表 (ftable)。 这是系统中所有打开文件的池。 ftable.lock 保护该表,确保并发访问的安全性。 ftable.file 是一个包含 NFILEstruct file 的数组。

struct {
  struct spinlock lock;
  struct file file[NFILE];
} ftable;

TIP

初始化全局文件表锁

void
fileinit(void)
{
  initlock(&ftable.lock, "ftable");
}

TIP

分配一个文件结构体 (struct file)。 这是在内核中打开一个新文件的第一步。 它会在全局文件表 ftable 中查找一个未被使用的 struct file。 @return: 成功时返回一个指向 struct file 的指针,失败时返回 0。

struct file*
filealloc(void)
{
  struct file *f;

  acquire(&ftable.lock);
  for(f = ftable.file; f < ftable.file + NFILE; f++){
    if(f->ref == 0){
      f->ref = 1;
      release(&ftable.lock);
      return f;
    }
  }
  release(&ftable.lock);
  return 0;
}

TIP

增加文件 f 的引用计数。 当一个文件描述符被复制时(例如 dup()fork()),会调用此函数。 这允许多个文件描述符指向同一个 struct file。 @param f: 要增加引用计数的文件结构体。 @return: 返回指向同一个文件结构体的指针 f

struct file*
filedup(struct file *f)
{
  acquire(&ftable.lock);
  if(f->ref < 1)
    panic("filedup");
  f->ref++;
  release(&ftable.lock);
  return f;
}

TIP

关闭文件 f。 这会减少文件的引用计数。如果引用计数降至 0, 则会释放底层的资源(如 inode 或管道)。 @param f: 要关闭的文件结构体。

void
fileclose(struct file *f)
{
  struct file ff;

  acquire(&ftable.lock);
  if(f->ref < 1)
    panic("fileclose");
  if(--f->ref > 0){
    release(&ftable.lock);
    return;
  }

TIP

如果引用计数为 0,则这是最后一个引用,需要真正关闭文件

  ff = *f;
  f->ref = 0;
  f->type = FD_NONE;
  release(&ftable.lock);

  if(ff.type == FD_PIPE){

TIP

如果是管道文件,关闭管道

    pipeclose(ff.pipe, ff.writable);
  } else if(ff.type == FD_INODE || ff.type == FD_DEVICE){

TIP

如果是 inode 或设备文件,释放 inode begin_op/end_op 用于确保 inode 操作的原子性

    begin_op();
    iput(ff.ip);
    end_op();
  }
}

TIP

获取文件 f 的状态信息 (元数据)。 @param f: 要获取状态的文件。 @param addr: 指向用户空间 struct stat 的地址,用于存储结果。 @return: 成功时返回 0,失败时返回 -1。

int
filestat(struct file *f, uint64 addr)
{
  struct proc *p = myproc();
  struct stat st;
  
  if(f->type == FD_INODE || f->type == FD_DEVICE){
    ilock(f->ip);
    stati(f->ip, &st);
    iunlock(f->ip);

TIP

将状态信息复制到用户空间

    if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0)
      return -1;
    return 0;
  }
  return -1;
}

TIP

从文件 f 读取数据。 @param f: 要读取的文件。 @param addr: 存储读取数据的用户空间缓冲区的地址。 @param n: 要读取的字节数。 @return: 返回读取的字节数;如果出错则返回 -1。

int
fileread(struct file *f, uint64 addr, int n)
{
  int r = 0;

  if(f->readable == 0)
    return -1;

  if(f->type == FD_PIPE){
    r = piperead(f->pipe, addr, n);
  } else if(f->type == FD_DEVICE){
    if(f->major < 0 || f->major >= NDEV || !devsw[f->major].read)
      return -1;
    r = devsw[f->major].read(1, addr, n);
  } else if(f->type == FD_INODE){
    ilock(f->ip);

TIP

从 inode 读取数据,并更新文件偏移量

    if((r = readi(f->ip, 1, addr, f->off, n)) > 0)
      f->off += r;
    iunlock(f->ip);
  } else {
    panic("fileread");
  }

  return r;
}

TIP

向文件 f 写入数据。 @param f: 要写入的文件。 @param addr: 包含要写入数据的用户空间缓冲区的地址。 @param n: 要写入的字节数。 @return: 返回写入的字节数;如果出错则返回 -1。

int
filewrite(struct file *f, uint64 addr, int n)
{
  int r, ret = 0;

  if(f->writable == 0)
    return -1;

  if(f->type == FD_PIPE){
    ret = pipewrite(f->pipe, addr, n);
  } else if(f->type == FD_DEVICE){
    if(f->major < 0 || f->major >= NDEV || !devsw[f->major].write)
      return -1;
    ret = devsw[f->major].write(1, addr, n);
  } else if(f->type == FD_INODE){

TIP

为了避免超出单个日志事务的大小,分块写入。 MAXOPBLOCKS 限制了单个事务中可以包含的块数。

    int max = ((MAXOPBLOCKS-1-1-2) / 2) * BSIZE;
    int i = 0;
    while(i < n){
      int n1 = n - i;
      if(n1 > max)
        n1 = max;

      begin_op();
      ilock(f->ip);

TIP

向 inode 写入数据,并更新文件偏移量

      if ((r = writei(f->ip, 1, addr + i, f->off, n1)) > 0)
        f->off += r;
      iunlock(f->ip);
      end_op();

      if(r != n1){

TIP

writei 发生错误或写入的字节数少于预期

        break;
      }
      i += r;
    }
    ret = (i == n ? n : -1);
  } else {
    panic("filewrite");
  }

  return ret;
}