1.4: File system

xv6 文件系统

xv6 文件系统提供了两种类型的文件:数据文件和目录。

数据文件:包含未解释的字节数组。

  • 目录:包含指向数据文件和其他目录的命名引用。

  • 目录形成一棵树,起点是一个称为 根目录(/)的特殊目录。例如,路径 /a/b/c 表示在根目录/下的a目录中的 b 目录中的文件或目录 c。

如果路径不以 / 开头,则会相对于调用进程的当前目录进行解析,可以使用 chdir 系统调用更改当前目录。例如,以下两段代码打开同一个文件(假设相关目录均存在):

chdir("/a");
chdir("b");
open("c", O_RDONLY);

open("/a/b/c", O_RDONLY);
  • 第一段代码将当前目录更改为 /a/b。

  • 第二段代码既没有引用也没有更改当前目录。

创建新文件和目录

以下系统调用用于创建新文件和目录:

  • mkdir:创建新目录。

  • open(带有 O_CREATE 标志):创建新的数据文件。

  • mknod:创建新的设备文件。

示例代码:

mkdir("/dir");
fd = open("/dir/file", O_CREATE | O_WRONLY);
close(fd);
mknod("/console", 1, 1);
  • mknod 创建一个引用设备的特殊文件。

  • 设备文件关联有主设备号和次设备号(mknod 的两个参数),用于唯一标识内核设备。

  • 当进程随后打开设备文件时,内核将read和write系统调用重定向到内核设备的实现,而不是传递给文件系统。

文件名与文件本身

文件的名称与文件本身是分离的。

  • 同一个底层文件(称为inode)可以有多个名称(称为链接)。

  • 每个链接由目录中的一个条目组成,该条目包含一个文件名和对inode的引用。

inode 包含以下元数据:

  • 文件类型(文件、目录或设备)。

  • 文件长度。

  • 文件内容在磁盘上的位置。

  • 文件的链接数量。

fstat 系统调用

fstat系统调用从文件描述符引用的inode中检索信息,返回一个struct stat结构:

如下

#define T_DIR 1 // 目录
#define T_FILE 2 // 文件
#define T_DEVICE 3 // 设备

struct stat {
    int dev;       // 文件系统的磁盘设备
    uint ino;      // inode 编号
    short type;    // 文件类型
    short nlink;   // 文件链接数量
    uint64 size;   // 文件大小(字节)
};

文件操作的用户级程序

Unix 提供了可以从 shell 调用的文件操作工具(作为用户级程序),例如:

  • mkdir:创建目录。

  • ln:创建链接。

  • rm:删除文件。 这种设计允许用户通过添加新的用户级程序来扩展命令行接口。虽然在现代看来这是显而易见的,但当时许多系统将这些命令直接集成到 shell 或内核中。

特例:cd 命令

  • cd 是一个内置于 shell 的命令(见 user/sh.c:161)。

  • cd 必须更改 shell 本身的当前工作目录。如果 cd 作为常规命令运行,那么 shell 将 fork 一个子进程,子进程运行 cd 并更改其工作目录,但父进程(即 shell)的工作目录不会改变。这使得必须将 cd 实现为内置命令。

见 user/sh.c:161

  // Read and run input commands.
  while(getcmd(buf, sizeof(buf)) >= 0){
    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
      // Chdir must be called by the parent, not the child.
      buf[strlen(buf)-1] = 0;  // chop \n
      if(chdir(buf+3) < 0)
        fprintf(2, "cannot cd %s\n", buf+3);
      continue;
    }

以下是对代码的解析

while(getcmd(buf, sizeof(buf)) >= 0){
  • getcmd(buf, sizeof(buf)):从用户输入中读取命令,并将其存储到 buf 中。

  • 如果读取成功(返回值 >= 0),进入循环处理命令

if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
  • 检查是否为 cd 命令

buf[strlen(buf)-1] = 0;  // chop \n

处理换行符

  • 用户输入通常以换行符 (\n) 结尾。

  • 这行代码将换行符替换为字符串结束符 \0,清理用户输入。

if(chdir(buf+3) < 0)
    fprintf(2, "cannot cd %s\n", buf+3);
  • chdir(buf+3):

    • buf+3 是跳过 "cd " 的部分,即用户输入的路径参数。

    • 调用 chdir 系统调用尝试将工作目录更改为 buf+3 指定的路径。 错误处理:

  • 如果 chdir 返回值小于 0,说明目录更改失败。

  • 使用 fprintf输出错误消息到文件描述符2(标准错误输出),告知用户无法进入指定目录。