Skip to content

sysfile.c

TIP

文件系统相关的系统调用。 此文件中的函数是用户空间程序和内核文件系统之间的接口。 它们主要负责: 1. 从用户空间获取和验证系统调用参数(例如文件描述符、路径名、缓冲区地址)。 2. 调用 file.c 和 fs.c 中更底层的函数来执行实际的文件操作。 3. 处理文件描述符的分配和释放。 4. 管理文件系统操作的原子性(通过 begin_op/end_op)。

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

TIP

从当前进程的系统调用参数中获取第 n 个整数作为文件描述符。 验证该文件描述符是否有效,并返回对应的 struct file 指针。 @param n: 系统调用参数的索引。 @param pfd: (可选) 一个整型指针,用于存储文件描述符的值。 @param pf: (可选) 一个文件指针的指针,用于存储找到的 struct file。 @return: 成功返回0,失败返回-1。

static int
argfd(int n, int *pfd, struct file **pf)
{
  int fd;
  struct file *f;

TIP

从用户空间获取整数参数

  argint(n, &fd);

TIP

检查文件描述符是否越界,或者对应的文件指针是否为空

  if(fd < 0 || fd >= NOFILE || (f=myproc()->ofile[fd]) == 0)
    return -1;

TIP

如果提供了pfd指针,则将fd写入

  if(pfd)
    *pfd = fd;

TIP

如果提供了pf指针,则将文件指针写入

  if(pf)
    *pf = f;
  return 0;
}

TIP

为给定的文件在当前进程中分配一个未使用的文件描述符。 将 struct file 指针存入进程的文件描述符表中。 @param f: 需要分配文件描述符的文件结构体指针。 @return: 成功则返回新的文件描述符,失败则返回-1。

static int
fdalloc(struct file *f)
{
  int fd;
  struct proc *p = myproc();

TIP

遍历进程的文件描述符表,查找空闲位置

  for(fd = 0; fd < NOFILE; fd++){
    if(p->ofile[fd] == 0){
      p->ofile[fd] = f;
      return fd;
    }
  }
  return -1;
}

TIP

sys_dup 系统调用:复制一个已有的文件描述符。 创建一个新的文件描述符,它与旧的描述符指向同一个打开的文件。 @return: 成功则返回新的文件描述符,失败则返回-1。

uint64
sys_dup(void)
{
  struct file *f;
  int fd;

TIP

获取第一个参数(即要复制的文件描述符)对应的文件结构

  if(argfd(0, 0, &f) < 0)
    return -1;

TIP

为该文件分配一个新的文件描述符

  if((fd=fdalloc(f)) < 0)
    return -1;

TIP

增加文件的引用计数,因为现在有两个描述符指向它

  filedup(f);
  return fd;
}

TIP

sys_read 系统调用:从文件中读取数据。 @return: 成功则返回读取的字节数,失败则返回-1。

uint64
sys_read(void)
{
  struct file *f;
  int n;
  uint64 p;

TIP

获取参数:用户缓冲区地址(p)和要读取的字节数(n)

  argaddr(1, &p);
  argint(2, &n);

TIP

获取文件描述符对应的文件结构

  if(argfd(0, 0, &f) < 0)
    return -1;

TIP

调用底层文件读取函数

  return fileread(f, p, n);
}

TIP

sys_write 系统调用:向文件中写入数据。 @return: 成功则返回写入的字节数,失败则返回-1。

uint64
sys_write(void)
{
  struct file *f;
  int n;
  uint64 p;
  

TIP

获取参数:用户缓冲区地址(p)和要写入的字节数(n)

  argaddr(1, &p);
  argint(2, &n);

TIP

获取文件描述符对应的文件结构

  if(argfd(0, 0, &f) < 0)
    return -1;

TIP

调用底层文件写入函数

  return filewrite(f, p, n);
}

TIP

sys_close 系统调用:关闭一个文件描述符。 @return: 成功返回0,失败返回-1。

uint64
sys_close(void)
{
  int fd;
  struct file *f;

TIP

获取文件描述符和对应的文件结构

  if(argfd(0, &fd, &f) < 0)
    return -1;

TIP

从进程的文件表中移除该文件描述符

  myproc()->ofile[fd] = 0;

TIP

关闭文件(这会减少引用计数,如果计数为0,则会释放 inode)

  fileclose(f);
  return 0;
}

TIP

sys_fstat 系统调用:获取文件的状态信息。 @return: 成功返回0,失败返回-1。

uint64
sys_fstat(void)
{
  struct file *f;
  uint64 st;

TIP

获取用户空间的 stat 结构体地址

  argaddr(1, &st);

TIP

获取文件描述符对应的文件结构

  if(argfd(0, 0, &f) < 0)
    return -1;

TIP

调用底层函数获取文件状态

  return filestat(f, st);
}

TIP

sys_link 系统调用:为文件创建一个新的硬链接。 @return: 成功返回0,失败返回-1。

uint64
sys_link(void)
{
  char name[DIRSIZ], new[MAXPATH], old[MAXPATH];
  struct inode *dp, *ip;

TIP

获取旧路径和新路径

  if(argstr(0, old, MAXPATH) < 0 || argstr(1, new, MAXPATH) < 0)
    return -1;

  begin_op();
  if((ip = namei(old)) == 0){
    end_op();
    return -1;
  }

  ilock(ip);
  if(ip->type == T_DIR){
    iunlockput(ip);
    end_op();
    return -1;
  }

  ip->nlink++;
  iupdate(ip);
  iunlock(ip);

  if((dp = nameiparent(new, name)) == 0)
    goto bad;
  ilock(dp);

TIP

检查设备号是否一致,并在父目录中创建目录项

  if(dp->dev != ip->dev || dirlink(dp, name, ip->inum) < 0){
    iunlockput(dp);
    goto bad;
  }
  iunlockput(dp);
  iput(ip);

  end_op();

  return 0;

bad:
  ilock(ip);
  ip->nlink--;
  iupdate(ip);
  iunlockput(ip);
  end_op();
  return -1;
}

TIP

检查目录 dp 是否为空(除了 "." 和 "..")。

static int
isdirempty(struct inode *dp)
{
  int off;
  struct dirent de;

TIP

遍历目录中的所有目录项 跳过前两个条目,即 "." 和 ".."

  for(off=2*sizeof(de); off<dp->size; off+=sizeof(de)){

TIP

读取目录项

    if(readi(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de))
      panic("isdirempty: readi");

TIP

如果 inode 号不为0,说明目录项有效,目录不为空

    if(de.inum != 0)
      return 0;
  }
  return 1;
}

TIP

sys_unlink 系统调用:删除一个文件链接。 如果文件的链接数降为0,则释放文件数据和 inode。 @return: 成功返回0,失败返回-1。

uint64
sys_unlink(void)
{
  struct inode *ip, *dp;
  struct dirent de;
  char name[DIRSIZ], path[MAXPATH];
  uint off;

  if(argstr(0, path, MAXPATH) < 0)
    return -1;

  begin_op();
  if((dp = nameiparent(path, name)) == 0){
    end_op();
    return -1;
  }

  ilock(dp);

TIP

不允许删除 "." 或 ".."

  if(namecmp(name, ".") == 0 || namecmp(name, "..") == 0)
    goto bad_unlink;

  if((ip = dirlookup(dp, name, &off)) == 0)
    goto bad_unlink;
  ilock(ip);

  if(ip->nlink < 1)
    panic("unlink: nlink < 1");

TIP

如果是目录,必须为空才能删除

  if(ip->type == T_DIR && !isdirempty(ip)){
    iunlockput(ip);
    goto bad_unlink;
  }

TIP

从父目录中删除该目录项(通过将目录项清零)

  memset(&de, 0, sizeof(de));
  if(writei(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de))
    panic("unlink: writei");
  if(ip->type == T_DIR){
    dp->nlink--;
    iupdate(dp);
  }
  iunlockput(dp);

  ip->nlink--;
  iupdate(ip);
  iunlockput(ip);

  end_op();

  return 0;

bad_unlink:
  iunlockput(dp);
  end_op();
  return -1;
}

TIP

通用函数:在指定路径创建一个新的 inode(文件、目录或设备)。 @param path: 创建路径。 @param type: inode 类型 (T_FILE, T_DIR, T_DEVICE)。 @param major: (设备文件)主设备号。 @param minor: (设备文件)次设备号。 @return: 成功则返回锁定的 inode 指针,失败则返回0。

static struct inode*
create(char *path, short type, short major, short minor)
{
  struct inode *ip, *dp;
  char name[DIRSIZ];

TIP

查找父目录 inode 和最后一级的文件名

  if((dp = nameiparent(path, name)) == 0)
    return 0;

  ilock(dp);

TIP

检查文件是否已存在

  if((ip = dirlookup(dp, name, 0)) != 0){
    iunlockput(dp);
    ilock(ip);

TIP

如果是创建文件,并且已存在同名文件或设备,则可以返回现有inode(用于O_CREATE)

    if(type == T_FILE && (ip->type == T_FILE || ip->type == T_DEVICE))
      return ip;
    iunlockput(ip);
    return 0;
  }

TIP

分配一个新的 inode

  if((ip = ialloc(dp->dev, type)) == 0){
    iunlockput(dp);
    return 0;
  }

TIP

初始化新的 inode

  ilock(ip);
  ip->major = major;
  ip->minor = minor;
  ip->nlink = 1;
  iupdate(ip);

  if(type == T_DIR){

TIP

在新目录中创建 "." 和 ".." 条目 注意:"." 的 nlink 不增加,以避免循环引用计数问题

    if(dirlink(ip, ".", ip->inum) < 0 || dirlink(ip, "..", dp->inum) < 0)
      goto fail_create;
  }

TIP

在父目录中创建指向新 inode 的链接

  if(dirlink(dp, name, ip->inum) < 0)
    goto fail_create;

      if(type == T_DIR){

TIP

成功创建后,父目录的链接数加一(因为 "..")

    dp->nlink++;
    iupdate(dp);
  }

  iunlockput(dp);

  return ip;

 fail_create:

TIP

创建失败,回滚操作 清理新分配的 inode

  ip->nlink = 0;
  iupdate(ip);
  iunlockput(ip);
  iunlockput(dp);
  return 0;
}

TIP

sys_open 系统调用:打开或创建一个文件。 @return: 成功则返回新的文件描述符,失败则返回-1。

uint64
sys_open(void)
{
  char path[MAXPATH];
  int fd, omode;
  struct file *f;
  struct inode *ip;
  int n;

TIP

获取路径和打开模式

  argint(1, &omode);
  if((n = argstr(0, path, MAXPATH)) < 0)
    return -1;

  begin_op();

  if(omode & O_CREATE){
    ip = create(path, T_FILE, 0, 0);
    if(ip == 0){
      end_op();
      return -1;
    }
  } else {
    if((ip = namei(path)) == 0){
      end_op();
      return -1;
    }
    ilock(ip);

TIP

目录只能以只读方式打开

    if(ip->type == T_DIR && omode != O_RDONLY){
      iunlockput(ip);
      end_op();
      return -1;
    }
  }

TIP

检查设备文件的主设备号是否有效

  if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){
    iunlockput(ip);
    end_op();
    return -1;
  }

TIP

分配文件结构和文件描述符

  if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
    if(f)
      fileclose(f);
    iunlockput(ip);
    end_op();
    return -1;
  }

TIP

初始化文件结构

  if(ip->type == T_DEVICE){
    f->type = FD_DEVICE;
    f->major = ip->major;
  } else {
    f->type = FD_INODE;
    f->off = 0;
  }
  f->ip = ip;
  f->readable = !(omode & O_WRONLY);
  f->writable = (omode & O_WRONLY) || (omode & O_RDWR);

TIP

如果指定 O_TRUNC,则截断文件(清空文件内容)

  if((omode & O_TRUNC) && ip->type == T_FILE){
    itrunc(ip);
  }

  iunlock(ip);
  end_op();

  return fd;
}

TIP

sys_mkdir 系统调用:创建一个新目录。 @return: 成功返回0,失败返回-1。

uint64
sys_mkdir(void)
{
  char path[MAXPATH];
  struct inode *ip;

  begin_op();

TIP

调用 create 函数创建目录类型的 inode

  if(argstr(0, path, MAXPATH) < 0 || (ip = create(path, T_DIR, 0, 0)) == 0){
    end_op();
    return -1;
  }
  iunlockput(ip);
  end_op();
  return 0;
}

TIP

sys_mknod 系统调用:创建一个设备文件(特殊文件)。 @return: 成功返回0,失败返回-1。

uint64
sys_mknod(void)
{
  struct inode *ip;
  char path[MAXPATH];
  int major, minor;

  begin_op();
  argint(1, &major);
  argint(2, &minor);

TIP

调用 create 函数创建设备类型的 inode

  if((argstr(0, path, MAXPATH)) < 0 ||
     (ip = create(path, T_DEVICE, major, minor)) == 0){
    end_op();
    return -1;
  }
  iunlockput(ip);
  end_op();
  return 0;
}

TIP

sys_chdir 系统调用:改变当前进程的工作目录。 @return: 成功返回0,失败返回-1。

uint64
sys_chdir(void)
{
  char path[MAXPATH];
  struct inode *ip;
  struct proc *p = myproc();
  
  begin_op();

TIP

查找新目录的 inode

  if(argstr(0, path, MAXPATH) < 0 || (ip = namei(path)) == 0){
    end_op();
    return -1;
  }
  ilock(ip);

TIP

必须是一个目录

  if(ip->type != T_DIR){
    iunlockput(ip);
    end_op();
    return -1;
  }
  iunlock(ip);
  iput(p->cwd);
  end_op();
  p->cwd = ip;
  return 0;
}

TIP

sys_exec 系统调用:加载并执行一个新程序,替换当前进程的内存映像。 @return: 成功时不返回,失败则返回-1。

uint64
sys_exec(void)
{
  char path[MAXPATH], *argv[MAXARG];
  int i;
  uint64 uargv, uarg;

TIP

获取可执行文件路径和参数列表的用户空间地址

  argaddr(1, &uargv);
  if(argstr(0, path, MAXPATH) < 0) {
    return -1;
  }
  

TIP

将参数列表从用户空间拷贝到内核空间

  memset(argv, 0, sizeof(argv));
  for(i=0;; i++){
    if(i >= NELEM(argv)){
      goto bad_exec;
    }

TIP

从用户空间获取 argv[i] 的指针

    if(fetchaddr(uargv+sizeof(uint64)*i, (uint64*)&uarg) < 0){
      goto bad_exec;
    }
    if(uarg == 0){
      argv[i] = 0;
      break;
    }
    argv[i] = kalloc();
    if(argv[i] == 0)
      goto bad_exec;

TIP

从用户空间拷贝参数字符串

    if(fetchstr(uarg, argv[i], PGSIZE) < 0)
      goto bad_exec;
  }

TIP

调用 exec 执行程序

  int ret = exec(path, argv);

TIP

如果 exec 成功,它不会返回。如果返回,说明发生了错误。 释放为参数列表分配的内存。

  for(i = 0; i < NELEM(argv) && argv[i] != 0; i++)
    kfree(argv[i]);

  return ret;

 bad_exec:
  for(i = 0; i < NELEM(argv) && argv[i] != 0; i++)
    kfree(argv[i]);
  return -1;
}

TIP

sys_pipe 系统调用:创建一个管道,用于进程间通信。 @return: 成功返回0,失败返回-1。

uint64
sys_pipe(void)
{
  uint64 fdarray;
  struct file *rf, *wf;
  int fd0, fd1;
  struct proc *p = myproc();

  argaddr(0, &fdarray);

TIP

分配一个管道,得到读端和写端的文件结构

  if(pipealloc(&rf, &wf) < 0)
    return -1;
  fd0 = -1;

TIP

为读端和写端分配文件描述符

  if((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0){

TIP

如果分配失败,清理已分配的资源

    if(fd0 >= 0)
      p->ofile[fd0] = 0;
    fileclose(rf);
    fileclose(wf);
    return -1;
  }

TIP

将两个文件描述符(读端 fd0 和写端 fd1)拷贝回用户空间

  if(copyout(p->pagetable, fdarray, (char*)&fd0, sizeof(fd0)) < 0 ||
     copyout(p->pagetable, fdarray+sizeof(fd0), (char *)&fd1, sizeof(fd1)) < 0){

TIP

如果拷贝失败,清理资源

    p->ofile[fd0] = 0;
    p->ofile[fd1] = 0;
    fileclose(rf);
    fileclose(wf);
    return -1;
  }
  return 0;
}