YSOS-rust lab6

YSOS-lab6

1. 实验要求

  1. 了解文件系统的概念、作用。
  2. 实现块设备、磁盘、分区、文件系统的抽象。
  3. 了解 ATA 硬盘的工作原理、实现基本的读写驱动。
  4. 实现 FAT16 文件系统的读取和只读文件访问。

2. 实验过程

milestone1:mbr模块

完成[相关代码](# 实现mbr模块)后,可运行pkg/storage/src/partition/mbr/entry.rs中的test,检验MbrPartition::parse的实现

运行指令cargo test --package ysos_storage --lib -- partition::mbr::entry::tests::partition_test --exact --show-output

image-20240527214539833

milestone2:磁盘识别

image-20240528091737135

完成[相关代码](# 实现命令发送与磁盘识别)后,可在YSOS启动后看到磁盘打开的日志

milestone3:磁盘读写

image-20240531212855561

milestone4:BPB

运行cargo test --package ysos_storage --lib -- fs::fat16::bpb::tests::test_fat16_bpb_2 --exact --show-output,测试Fat16Bpb 的字段定义是否正确

image-20240531231056443

image-20240531231123236

milestone5:DirEntry

运行cargo test --package ysos_storage --lib -- fs::fat16::direntry::tests --show-output,检验DirEntry实现是否正确

image-20240601090504474

milestone6:文件系统接入操作系统

完成[相关代码](# 实现文件系统)后,可实现milestone6

image-20240606154544006

探索linux操作系统

  1. procfs

    /proc 中,你可以找到一系列的文件和文件夹,探索他们并回答如下问题:

    • 解释 /proc 下的数字目录代表什么,其内部存在什么内容?

      表示当前正在运行的进程,目录名即为进程的 PID,其内部包括进程的各种信息,包括进程对应的可执行文件为exe,进程的统计信息为stat

    • /proc/cpuinfo/proc/meminfo 存储了哪些信息?

      /proc/cpuinfo列出了系统上 CPU 的信息,包括型号、速度和核心数量。

      /proc/meminfo列出了当前的内存使用情况

    • /proc/loadavg/proc/uptime 存储了哪些信息?

      /proc/loadavg 文件包含了系统负载平均值的统计信息。五个字段分别是:最近1分钟的系统负载平均值,最近5分钟的系统负载平均值,最近15分钟的系统负载平均值,正在运行的进程数/总进程数,最后一个创建或终止的进程的 PID

      /proc/uptime 文件包含了系统启动以来的运行时间和空闲时间,这个文件有两个字段,都以秒为单位,分别是系统启动以来的总运行时间和系统所有CPU核心的总空闲时间

    • 尝试读取 /proc/interrupts 文件,你能够从中获取到什么信息?

      系统中中断的统计信息,详细列出了各个 CPU 接收到的各种中断的次数,以及每个中断的来源

      image-20240607084753513

    • 尝试读取 /proc/self/status 文件,你能够从中获取到什么信息?

      /proc/self/status 文件提供了当前进程的状态信息。self 是一个特殊的符号链接,指向读取该文件的进程的目录,因此 /proc/self/status 实际上是 /proc/[pid]/status,其中 [pid] 是当前进程的进程 ID。

    • 尝试读取 /proc/self/smaps 文件,你能够从中获取到什么信息?

      /proc/self/smaps 文件提供了当前进程的内存映射和每个映射区域的详细内存使用情况。包括以下信息:

      • 该内存映射区域的起始地址和结束地址。
      • 该内存映射区域的权限(读、写、执行、共享/私有)
      • 映射文件的偏移量。
      • 设备号(主设备号和次设备号)
      • 文件的 inode 号
      • 文件路径
    • 结合搜索,回答 echo 1 > /proc/sys/net/ipv4/ip_forward 有什么用?尝试据此命令,从系统调用角度,解释 “一切皆文件” 的优势。

      这是一个在 Linux 系统中用来启用 IP 转发的命令。这条命令的作用是将内核参数 ip_forward 设置为 1,从而允许操作系统在不同的网络接口之间转发 IP 数据包。

      启用 IP 转发后,Linux 设备可以作为一个路由器,转发来自一个网络接口的数据包到另一个网络接口。这对于配置网络桥接、NAT 以及创建 VPN 等应用非常重要。

      从系统调用角度看,由于一切资源都可以视为文件进行操作,系统调用的种类和数量得以减少,简化了系统接口。

  2. devfs

    Linux 将设备也作为“文件”,默认挂载于 /dev 目录下,探索他们并回答如下问题:

    • /dev/null/dev/zero/dev/random/dev/urandom 分别有什么作用?

      • null 设备文件是一个“黑洞”,写入到它的所有数据都会被丢弃。读取这个文件永远返回 EOF

        使用场景包括

        • 丢弃输出: 将不需要的输出重定向到 /dev/null 以防止其显示在屏幕上。例如,command > /dev/null 2>&1 将命令 command 的标准输出和标准错误输出都丢弃。
        • 作为输入: 使用 /dev/null 作为命令或程序的输入,避免需要实际文件。例如,cat /dev/null 将输出空内容。
      • zero 设备文件提供无限量的零字节。当读取这个文件时,会返回连续的零字节(0x00)。

      使用场景包括

      • 初始化文件: 可以用来初始化文件内容为零。例如,dd if=/dev/zero of=emptyfile bs=1M count=1 创建一个大小为 1MB 的全零文件。

      • 虚拟内存: 在一些虚拟内存操作或需要大块零字节数据的情况下使用。

      • random 设备文件生成高质量的随机数据。这些数据来源于系统中收集的环境噪声,因此熵值较高,但数据生成可能较慢。

        使用场景包括

        • 密码学应用: 由于其高熵特性,适用于生成需要高安全性的随机数,如密钥、密码等。
        • 随机数生成: 适合需要高质量随机数的场景,但要注意可能会因为熵不足导致读取变慢。
      • urandom 设备文件生成伪随机数据,使用的是加密算法对内核熵池的数据进行扩展。虽然不如 /dev/random 的数据质量高,但速度更快,不会阻塞。

        使用场景包括

        • 常规随机数生成: 适用于大多数非密码学应用,如生成随机文件名、随机测试数据等。
        • 性能要求较高的随机数生成: 在要求高性能但不要求极高安全性的情况下使用。
    • 尝试运行 head /dev/kmsg 并观察输出,结合搜索引擎,解释这一文件的作用。

      用于查看内核日志消息。具体来说,/dev/kmsg 是一个字符设备文件,它提供了内核消息缓冲区的内容。使用 head /dev/kmsg 可以从这个缓冲区的开头读取并显示一些消息。head 命令默认显示文件的前 10 行内容。

    • /dev/sdX/dev/sdX1 (X 为一个字母,1 为数字)是什么?有什么区别?如果你正在使用的 Linux 系统中不存在这样的文件,请找到功能类似的文件,并解释。

      可以找到例如/dev/sda/dev/sda1这样的文件,用于表示硬盘和硬盘上的分区。

      /dev/sda:

      • 表示整个硬盘设备(或存储设备)。
      • “sda” 中的 “sd” 代表 SCSI 磁盘设备,“a” 代表第一个设备。如果有多个 SCSI 磁盘设备,它们会依次命名为 sdb、sdc 等。
      • 不包含任何分区信息,只是表示整个物理存储设备。

      /dev/sda1:

      • 表示硬盘上的第一个分区。
      • “sda1” 中的 “1” 代表第一个分区。同理,第二个分区会是 sda2,第三个是 sda3,以此类推。
      • 这个设备文件包含了特定分区的信息,可以直接挂载并使用该分区上的文件系统。
    • /dev/ttyX/dev/loopX/dev/srX 分别代表什么设备?

      • /dev/ttyXtty 代表 “teletypewriter”。/dev/ttyX 表示终端设备,其中 X 是一个数字,表示具体的终端。例如,/dev/tty1 表示第一个虚拟控制台。这些设备通常用于与用户交互的终端会话。

      • /dev/loopXloop 设备是用于创建虚拟块设备的设备文件。/dev/loopX 表示环回设备,其中 X 是一个数字,表示具体的环回设备。例如,/dev/loop0 是第一个环回设备。环回设备可以将一个普通文件当作块设备来使用,这在创建和测试磁盘镜像或挂载文件系统时非常有用。

      • /dev/srXsr 代表 SCSI CD-ROM 设备。/dev/srX 表示 SCSI 光盘驱动器设备,其中 X 是一个数字,表示具体的光盘驱动器。例如,/dev/sr0 是第一个 SCSI 光盘驱动器。这些设备通常用于光盘驱动器,如 CD、DVD 驱动器。

    • 列出 /dev/disk 下的目录,尝试列出其中的“软连接”,这样的设计有什么好处?

      /dev/disk 目录下通常包含与磁盘和存储设备相关的设备文件。

      通常 /dev/disk 目录下会有以下几个子目录:

      • /dev/disk/by-id: 按设备ID列出设备。
      • /dev/disk/by-path: 按设备路径列出设备。
      • /dev/disk/by-uuid: 按设备UUID列出设备。
      • /dev/disk/by-label: 按设备标签列出设备。

      每个子目录中的条目一般都是指向实际设备文件的软连接。

      这些软连接有助于系统管理员更方便、稳定地管理和引用存储设备,避免设备名变化带来的潜在问题。

    • 尝试运行 lsblk 命令,根据你的输出,解释其中的内容。

      lsblk 用于列出有关块设备的信息,如硬盘、SSD、光盘驱动器等。

      默认情况下,lsblk 命令列出系统中所有的块设备,包括硬盘、分区、光驱等。

      lsblk 以树形结构显示块设备和它们之间的关系,例如硬盘和它的分区。

      可以通过指定选项来显示更多详细信息,如设备的大小、类型、文件系统、挂载点等。

  3. tmpfs

    在 Linux 中 /dev/shm/run 或者 /var/run 目录下,存储了一个特殊的文件系统,它是一个内存文件系统,探索它并回答如下问题:

    • 列出这些目录,尝试找到扩展名为 pid 的文件。应用程序如何利用它们确保某个程序只运行一个实例?

      程序在启动时可以查看/run目录下是否存在/run/进程名.pid文件,如果存在则说明程序已经运行了一个实例,否则可以创建该文件标记程序已经运行了一个示例,避免重复创建实例

    • 列出这些目录,尝试找到扩展名为 lock 的文件。应用程序如何利用它们确保某个资源只被一个程序访问?

      当应用程序需要访问某个资源时,它首先会尝试在/run目录下创建一个扩展名为 lock 的锁文件。通常这个锁文件的命名与所要锁定的资源相关,以便容易识别。如果锁文件已经存在,其他应用程序就无法访问所需资源

    • 列出这些目录,尝试找到扩展名为 socksocket 的文件。应用程序如何利用它们实现进程间通信?

      .sock.socket文件通常表示Unix域套接字,这是进程间通信(Inter-Process Communication, IPC)的一种机制。Unix域套接字允许同一主机上的不同进程进行通信,具有高效、低延迟的特点。

      需要通信的程序只需要创建套接字,连接到套接字文件,可以通过套接字文件进行通信

    • tmpfs 的存在对于操作系统有什么作用?尝试从性能、安全性、系统稳定性几方面进行回答。

      tmpfs 是一种基于内存的文件系统,它的主要作用是将临时文件存储在内存中,而不是磁盘上。

      • 性能:

        • 快速存取,由于 tmpfs 是基于内存的文件系统,读写操作的速度非常快,通常比磁盘操作快得多。这可以显著提升使用临时文件的应用程序的性能,例如编译器、Web服务器缓存等。

        • 减少I/O瓶颈,使用 tmpfs 可以减少磁盘I/O操作,避免磁盘成为系统的性能瓶颈。这对于高I/O密集型的任务尤其有益。

        • 系统可以将常用数据放入 tmpfs,以便快速访问,从而提高整体系统的响应速度。

      • 安全性:

        • tmpfs 中的数据在系统重启或文件系统卸载时会消失。这种特性可以防止敏感数据在磁盘上长期存储,减少数据泄露的风险。

        • 由于 tmpfs 仅存储在内存中,不会写入磁盘,防止了恶意软件或攻击者从磁盘中提取临时数据,从而增强数据的安全性。

        • 可以为不同的用户或进程创建独立的 tmpfs 文件系统,确保临时文件的隔离,避免数据泄露和权限问题。

      • 系统稳定性:可减少磁盘磨损,避免磁盘空间耗尽

  4. 在完全手动安装一个 Linux 操作系统时,我们常常会将待安装的磁盘(分区)格式化后,使用 mount 挂载于 /mnt 目录下。之后,可以使用 chroot 切换根目录,在“新的操作系统”中进行安装后期的工作。

    然而在 chroot /mnt 之前,还需要进行一些额外的挂载操作:

    1
    2
    3
    4
    mount proc /mnt/proc -t proc -o nosuid,noexec,nodev
    mount sys /mnt/sys -t sysfs -o nosuid,noexec,nodev,ro
    mount udev /mnt/dev -t devtmpfs -o mode=0755,nosuid
    ...

    尝试解释上述举例的的挂载命令,思考为什么需要这样的挂载操作?如果不进行这些操作,在 chroot 之后会失去哪些能力?


    在进行 chroot 切换根目录之前,进行这些额外的挂载操作是为了确保新的操作系统环境中具备必要的文件系统和设备节点,从而让系统在 chroot 之后可以正常运行和进行各种管理操作。

    如果不挂载 proc 文件系统:

    • pstop 等命令将无法正常工作,因为它们依赖于 /proc 中的信息来显示进程状态。
    • 很多系统管理工具也依赖于 /proc,例如 sysctl

    sysfs 文件系统提供内核对象的视图,主要用于显示设备信息。这个文件系统在 /sys 目录下挂载,可以显示和管理系统设备和相关内核参数。

    如果不挂载 sys 文件系统:

    • 系统硬件信息将不可访问,导致某些设备驱动和系统管理工具无法正常工作。
    • 设备管理和配置工具(如 udevadm)将无法正常操作。

    devtmpfs 文件系统是一个专门用于设备节点的伪文件系统,它自动创建和管理设备节点。这个文件系统挂载在 /dev 目录下,提供对系统设备的访问。

    如果不挂载 dev 文件系统:

    • 设备节点将无法访问,导致许多基本的设备操作(如磁盘访问、网络接口等)无法进行。
    • 系统中的许多设备相关工具和命令(如 lsblkfdisk)将无法正常使用。

思考题

  1. 为什么在 pkg/storage/lib.rs 中声明了 #![cfg_attr(not(test), no_std)],它有什么作用?哪些因素导致了 kernel 中进行单元测试是一个相对困难的事情?


    这是一个条件编译指令,它的作用是在非测试环境下禁用标准库(std

    cfg_attr(condition, attribute) 是一个条件编译宏,它的作用是如果 condition 为真,则应用 attribute

    not(test) 是一个编译条件,它在非测试环境下为真。也就是说,当你运行 cargo buildcargo build --release 时,这个条件为真;但当你运行 cargo test 时,这个条件为假

    no_std 是一个属性,它告诉编译器不要链接标准库(std

    单元测试相对困难,是因为测试一小部分代码就必须要编译整个内核,如果代码有很多部分未完成,是很难通过编译的。另外,测试代码是在宿主机上运行,而不是在qemu中运行,运行环境不同,可能会出现错误

  2. 留意 MbrTable 的类型声明,为什么需要泛型参数 T 满足 BlockDevice<B> + Clone?为什么需要 PhantomData<B> 作为 MbrTable 的成员?在 PartitionTable trait 中,为什么需要 Self: Sized 约束?


    • BlockDevice<B> trait 让 MbrTable 可以使用其中的方法与 T 类型的实例进行交互,确保它们符合块设备的行为。Clone trait确保 MbrTable 可以在需要时安全地克隆 T 类型的实例

    • PhantomData 是一个零大小类型,用于表示某个类型参数被使用,尽管在编译时没有实际的值关联到这个类型参数。这在泛型编程中非常有用,尤其是在需要在类型系统中反映一些类型信息,但在运行时并不需要持有该类型的实际值。

      PhantomData<B> 告诉编译器 MbrTable 与类型 B 相关联,即使 MbrTable 的字段中没有实际存储 B 类型的值。这对于保证类型系统的正确性和完整性非常重要。

      PhantomData<B> 可以帮助 Rust 的借用检查器理解 MbrTableB 类型的生命周期和所有权关系。例如,如果 B 包含一些生命周期参数,PhantomData<B> 可以让编译器追踪这些生命周期。

    • Self: Sized 约束在 trait 中的使用主要是为了确保实现该 trait 的类型是已知大小的(即编译时大小已知),否则代码fn parse(inner: T) -> Result<Self>; 会出现报错

  3. AtaDrive 为了实现 MbrTable,如何保证了自身可以实现 Clone?对于分离 AtaBusAtaDrive 的实现,你认为这样的设计有什么好处?


    • #[derive(Clone)] 宏会自动为标记了该宏的结构体实现 Clone trait, Rust 编译器会自动生成一个 Clone trait 的实现。

      具体来说,AtaDrive结构体中的u8, u32, Box<str>都实现了Clone trait。

      #[derive(Clone)] 宏会为 AtaDrive 结构体生成一个 clone 方法。这个 clone 方法会依次调用 busdriveblocksmodelserial 字段各自的 clone 方法。由于这些字段的类型都已经实现了 Clone trait,编译器可以确保生成的 clone 方法是有效的。

    • 代码更加模块化,易于维护。通过分离 AtaBusAtaDrive,可以在不修改总线实现的情况下,复用 AtaDrive 实现。这对于支持多种总线协议(如 IDE、SATA 等)非常有用。当需要添加新的驱动器类型或新的总线类型时,只需添加新的类而不必修改现有的类。

  4. 结合本次实验中的抽象和代码框架,简单解释和讨论如下写法的异同:

    1. 函数声明:

      • fn f<T: Foo>(f: T) -> usize
      • fn f(f: impl Foo) -> usize
      • fn f(f: &dyn Foo) -> usize

      fn f<T: Foo>(f: T) -> usizefn f(f: impl Foo) -> usize 都使用了编译时的泛型和单态化,提供高效的性能,但增加了代码尺寸。前者显式定义泛型,后者使用impl Trait简化语法。

      fn f(f: &dyn Foo) -> usize 使用了特性对象和动态派发,带来了运行时的灵活性和较小的代码尺寸,但有运行时开销。

    2. 结构体声明:

      • struct S<T: Foo> { f: T }
      • struct S { f: Box<dyn Foo> }

      struct S<T: Foo> { f: T } 使用了编译时的泛型和单态化,提供高效的性能,但增加了代码尺寸。适用于需要高性能和编译时确定类型的场景。

      struct S { f: Box<dyn Foo> } 使用了特性对象和动态派发,带来了运行时的灵活性和较小的代码尺寸,但有运行时开销和堆分配开销。适用于需要在运行时处理多态性和动态类型的场景。

  5. 文件系统硬链接和软链接的区别是什么?Windows 中的 “快捷方式” 和 Linux 中的软链接有什么异同?


    硬链接

    1. 硬链接是文件系统中多个文件名指向同一个文件数据块。它们共享相同的 inode 号。
    2. 删除任意一个硬链接不会影响其他硬链接和文件的实际数据。文件只有在所有硬链接被删除后,文件数据才会被释放。
    3. 硬链接不能跨文件系统(不同分区),且不能对目录创建硬链接(在大多数文件系统中)。
    4. 硬链接之间是完全一致的,修改任何一个硬链接内容都会反映在所有硬链接中。

    软链接

    1. 软链接是一个独立的文件,包含指向目标文件或目录路径的引用。它们有自己的 inode 号。
    2. 如果删除了目标文件,软链接会变成一个“悬空”链接(指向无效路径),但软链接本身仍存在。
    3. 软链接可以跨文件系统(不同分区),并且可以指向目录。
    4. 软链接是一个不同的文件,与目标文件的修改和删除有一定的独立性。

    Windows 中的快捷方式与 Linux 中的软链接

    1. Windows 快捷方式
      • 快捷方式是一个特殊的文件(.lnk 文件),它包含了目标文件或程序的位置、图标、快捷键等元数据。
      • 快捷方式的功能主要依赖于 Windows 操作系统,其他操作系统可能无法识别或处理这些快捷方式文件。
    2. Linux 软链接
      • 软链接是一个符号链接文件,指向目标文件或目录路径。
      • 独立于操作系统,符号链接的机制是文件系统级别的,许多类 Unix 系统都支持。
  6. 日志文件系统(如 NTFS)与传统的非日志文件系统(如 FAT)在设计和实现上有哪些不同?在系统异常崩溃后,它的恢复机制、恢复速度有什么区别?


    数据结构和元数据管理

    NTFS使用B树结构来管理文件和目录,提高了查找速度和存储效率。每个文件和目录都有一套详细的元数据,包括安全属性、时间戳、文件权限等。支持压缩、加密、磁盘配额等高级功能。

    FAT使用简单的链表结构来管理文件和目录,随着文件系统的增长,查找效率可能降低。元数据相对简单,主要包括文件名、大小、创建时间等。不支持高级功能,安全性和灵活性较低。

    日志记录

    NTFS采用日志记录机制,在执行写操作前先将操作记录在日志中。日志记录包括元数据和文件数据的修改,确保系统在崩溃后可以回滚未完成的操作,维持文件系统的一致性。

    FAT不使用日志记录机制,直接在磁盘上执行写操作。缺乏事务管理和回滚机制,系统崩溃时数据可能处于不一致状态。

    恢复机制

    NTFS具有更强大的恢复机制。操作系统可以使用日志中的信息来回滚未完成的操作,以确保文件系统的一致性。

    FAT文件系统没有事务日志的概念,因此在系统异常崩溃时,可能会导致文件系统处于不一致的状态。FAT文件系统通常通过扫描文件系统来检测和修复损坏的文件系统结构,这需要一些时间,并且不如NTFS那样可靠、快速。

加分项

😋 你的操作系统拥有了直接读取文件系统的能力,不妨将加载用户态应用的工作从 bootloader 手中接过:

  • 重新修改 spawn 函数的实现,接受一个文件路径。
  • 使用 read_all 加载对应的文件。
  • 将文件内容传入 elf_spawn
  • 在操作系统初始化后,使用文件路径生成 Shell 程序。
  • 修改对应的系统调用,将 Spawn 调用的参数从应用名称改为路径。
  • 赋予你的 Shell 从磁盘启动用户程序的能力!

image-20240606222746434

3. 关键代码

实现mbr模块

pkg/storage/src/partition/mbr/entry.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
impl MbrPartition {
/// Parse a partition entry from the given data.
pub fn parse(data: &[u8; 16]) -> MbrPartition {
MbrPartition {
data: data.to_owned(),
}
}

// FIXME: define other fields in the MbrPartition
// - use `define_field!` macro
// - ensure you can pass the tests
// - you may change the field names if you want
//
// NOTE: some fields are not aligned with byte.
// define your functions to extract values:
//
// 0x02 - 0x03 begin sector & begin cylinder
// 0x06 - 0x07 end sector & end cylinder
define_field!(u8, 0x00, status);
define_field!(u8, 0x01, begin_head);
// 0x02 - 0x03 begin sector & begin cylinder
define_field!(u8, 0x04, partition_type);
define_field!(u8, 0x05, end_head);
// 0x06 - 0x07 end sector & end cylinder
define_field!(u32, 0x08, begin_lba);
define_field!(u32, 0x0c, total_lba);

// an example of how to define a field
// move your mouse on the `define_field!` to see the docs
//define_field!(u8, 0x00, status);

pub fn is_active(&self) -> bool {
self.status() & 0x80 != 0
}

pub fn is_extended(&self) -> bool {
self.partition_type() == 0x05
}

pub fn begin_sector(&self) -> u8 {
self.data[2] & 0x3f
}
pub fn begin_cylinder(&self) -> u16 {
(self.data[2] as u16 & 0xc0) << 2 | (self.data[3] as u16)
}
pub fn end_sector(&self) -> u8 {
self.data[6] & 0x3f
}
pub fn end_cylinder(&self) -> u16 {
(self.data[6] as u16 & 0xc0) << 2 | (self.data[7] as u16)
}
}

至此,可实现[milestone1](# milestone1:mbr模块)

实现命令发送与磁盘识别

pkg/kernel/src/main.rs

1
2
3
4
5
pub fn kernel_main(boot_info: &'static boot::BootInfo) -> ! {
//...
let s = AtaDrive::open(0, 0).unwrap();
//...
}

实现open函数

pkg/kernel/src/drivers/ata/mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
impl AtaDrive {
pub fn open(bus: u8, drive: u8) -> Option<Self> {
trace!("Opening drive {}@{}...", bus, drive);

// we only support PATA drives
if let Ok(AtaDeviceType::Pata(res)) = BUSES[bus as usize].lock().identify_drive(drive) {
let buf = res.map(u16::to_be_bytes).concat();
/* FIXME: get the serial from buf */
let serial = String::from_utf8_lossy(&buf[20..40]).trim().into();
/* FIXME: get the model from buf */
let model = String::from_utf8_lossy(&buf[54..94]).trim().into();
/* FIXME: get the block count from buf */
let blocks = u32::from_be_bytes(buf[120..124].try_into().unwrap()).rotate_left(16);
let ata_drive = Self {
bus,
drive,
model,
serial,
blocks,
};
info!("Drive {} opened", ata_drive);
Some(ata_drive)
} else {
warn!("Drive {}@{} is not a PATA drive", bus, drive);
None
}
}
}

use storage::{Block512, BlockDevice, DeviceError};

impl BlockDevice<Block512> for AtaDrive {
fn block_count(&self) -> storage::Result<usize> {
// FIXME: return the block count
Ok(self.blocks as usize)
}

fn read_block(&self, offset: usize, block: &mut Block512) -> storage::Result<()> {
// FIXME: read the block
// - use `BUSES` and `self` to get bus
// - use `read_pio` to get data
BUSES[self.bus as usize].lock().read_pio(self.drive, offset as u32, block.as_mut_u8_slice())
}

fn write_block(&self, offset: usize, block: &Block512) -> storage::Result<()> {
// FIXME: write the block
// - use `BUSES` and `self` to get bus
// - use `write_pio` to write data
BUSES[self.bus as usize].lock().write_pio(self.drive, offset as u32, block.as_u8_slice())
}
}

identify_drive

pkg/kernel/src/drivers/ata/bus.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
impl AtaBus {
//...
fn write_command(&mut self, drive: u8, block: u32, cmd: AtaCommand) -> storage::Result<()> {
let mut bytes = block.to_le_bytes(); // a trick to convert u32 to [u8; 4]
unsafe {
// just 1 sector for current implementation
self.sector_count.write(1);

// FIXME: store the LBA28 address into four 8-bit registers
// - read the documentation for more information
// - enable LBA28 mode by setting the drive register
bytes[3].set_bit(4, drive > 0);
bytes[3].set_bit(5, true);
bytes[3].set_bit(6, true);
bytes[3].set_bit(7, true);
self.lba_low.write(bytes[0]);
self.lba_mid.write(bytes[1]);
self.lba_high.write(bytes[2]);
self.drive.write(bytes[3]);

// FIXME: write the command register (cmd as u8)
self.command.write(cmd as u8);

}

if self.status().is_empty() {
// unknown drive
return Err(storage::DeviceError::UnknownDevice.into());
}

// FIXME: poll for the status to be not BUSY
self.poll(AtaStatus::BUSY, false);


if self.is_error() {
warn!("ATA error: {:?} command error", cmd);
self.debug();
return Err(storage::DeviceError::InvalidOperation.into());
}

// FIXME: poll for the status to be not BUSY and DATA_REQUEST_READY
self.poll(AtaStatus::BUSY | AtaStatus::DATA_REQUEST_READY, true);

Ok(())
}

/// Identifies the drive at the given `drive` number (0 or 1).
///
/// reference: https://wiki.osdev.org/ATA_PIO_Mode#IDENTIFY_command
pub(super) fn identify_drive(&mut self, drive: u8) -> storage::Result<AtaDeviceType> {
info!("Identifying drive {}", drive);

// FIXME: use `AtaCommand::IdentifyDevice` to identify the drive
// - call `write_command` with `drive` and `0` as the block number
// - if the status is empty, return `AtaDeviceType::None`
// - else return `DeviceError::Unknown` as `FsError`
if self.write_command(drive, 0, AtaCommand::IdentifyDevice).is_err(){
unsafe{
if self.alternate_status.read() == 0{
return Ok(AtaDeviceType::None);
}else{
return Err(storage::DeviceError::UnknownDevice.into());
}
}
}
// FIXME: poll for the status to be not BUSY
self.poll(AtaStatus::BUSY, false);

Ok(match (self.cylinder_low(), self.cylinder_high()) {
// we only support PATA drives
(0x00, 0x00) => AtaDeviceType::Pata(Box::new([0u16; 256].map(|_| self.read_data()))),
// ignore the data as we don't support following types
(0x14, 0xEB) => AtaDeviceType::PataPi,
(0x3C, 0xC3) => AtaDeviceType::Sata,
(0x69, 0x96) => AtaDeviceType::SataPi,
_ => AtaDeviceType::None,
})
}
//...
}

pkg/storage/src/partition/mbr/mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
impl<T, B> PartitionTable<T, B> for MbrTable<T, B>
where
T: BlockDevice<B> + Clone,
B: BlockTrait,
{
fn parse(inner: T) -> Result<Self> {
//...
for i in 0..4 {
partitions.push(
// FIXME: parse the mbr partition from the buffer
// - just ignore other fields for mbr
MbrPartition::parse(&buffer[0x1be + (i * 16)..0x1be + (i * 16) + 16].try_into().unwrap())
);

if partitions[i].is_active() {
trace!("Partition {}: {:#?}", i, partitions[i]);
}
}

//...
}
}

pkg/storage/src/common/block.rs

1
2
3
4
5
6
7
8
9
10
impl<const SIZE: usize> Block<SIZE> {
//...
pub fn as_u8_slice(&self) -> &[u8; SIZE] {
&self.contents
}

pub fn as_mut_u8_slice(&mut self) -> &mut [u8; SIZE] {
&mut self.contents
}
}

至此,可实现[milestone2](# milestone2:磁盘识别)

实现磁盘读写

pkg/kernel/src/drivers/ata/bus.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
impl AtaBus {
pub(super) fn read_pio(
&mut self,
drive: u8,
block: u32,
buf: &mut [u8],
) -> storage::Result<()> {
self.write_command(drive, block, AtaCommand::ReadPio)?;

// FIXME: read the data from the data port into the buffer
// - use `buf.chunks_mut(2)`
// - use `self.read_data()`
// - ! pay attention to data endianness
for chunk in buf.chunks_mut(2){
let data = self.read_data().to_le_bytes();
chunk.clone_from_slice(&data);
}

//...
}
pub(super) fn write_pio(&mut self, drive: u8, block: u32, buf: &[u8]) -> storage::Result<()> {
self.write_command(drive, block, AtaCommand::WritePio)?;

// FIXME: write the data from the buffer into the data port
// - use `buf.chunks(2)`
// - use `self.write_data()`
// - ! pay attention to data endianness
for chunk in buf.chunks(2){
let data = u16::from_le_bytes(chunk.try_into().unwrap());
self.write_data(data);
}
//...
}
}

pkg/kernel/src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
pub fn open_drive(){
let drive = AtaDrive::open(0, 0).unwrap();
let mbrtab = MbrTable::parse(drive)
.expect("Failed to parse MBR");
let parts = mbrtab.partitions().expect("Failed to get partitions");
let mut i = 0;
for p in parts{
let offset = p.offset;
let size = p.size;
info!("Found partition#{} at offset {} with size {}", i, offset, size);
i += 1;
}
}

至此,可实现[milestone3](# milestone3:磁盘读写)

实现BPB

pkg/storage/src/fs/fat16/bpb.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
impl Fat16Bpb {
//...
// FIXME: define all the fields in the BPB
// - use `define_field!` macro
// - ensure you can pass the tests
// - you may change the field names if you want
define_field!([u8; 8], 0x03, oem_name);
define_field!(u16, 0x0b, bytes_per_sector);
define_field!(u8, 0x0d, sectors_per_cluster);
define_field!(u16, 0x0e, reserved_sector_count);
define_field!(u8, 0x10, fat_count);
define_field!(u16, 0x11, root_entries_count);
define_field!(u16, 0x13, total_sectors_16);
define_field!(u8, 0x15, media_descriptor);
define_field!(u16, 0x16, sectors_per_fat);
define_field!(u16, 0x18, sectors_per_track);
define_field!(u16, 0x1a, track_count);
define_field!(u32, 0x1c, hidden_sectors);
define_field!(u32, 0x20, total_sectors_32);
define_field!(u8, 0x24, drive_number);
define_field!(u8, 0x25, reserved_flags);
define_field!(u8, 0x26, boot_signature);
define_field!(u32, 0x27, volume_id);
define_field!([u8; 11], 0x2b, volume_label);
define_field!([u8; 8], 0x36, system_identifier);
define_field!(u16, 0x1fe, trail);
}

至此,可实现[milestone4](# milestone4:BPB)

实现DirEntry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
impl DirEntry {
pub const LEN: usize = 0x20;

pub fn is_read_only(&self) -> bool {
self.attributes.contains(Attributes::READ_ONLY)
}

pub fn is_hidden(&self) -> bool {
self.attributes.contains(Attributes::HIDDEN)
}

pub fn is_system(&self) -> bool {
self.attributes.contains(Attributes::SYSTEM)
}

pub fn is_volume_id(&self) -> bool {
self.attributes.contains(Attributes::VOLUME_ID)
}

pub fn is_directory(&self) -> bool {
self.attributes.contains(Attributes::DIRECTORY)
}

pub fn is_archive(&self) -> bool {
self.attributes.contains(Attributes::ARCHIVE)
}

pub fn is_long_name(&self) -> bool {
self.attributes.contains(Attributes::LFN)
}

pub fn is_eod(&self) -> bool {
self.filename.is_eod()
}

pub fn is_unused(&self) -> bool {
self.filename.is_unused()
}

pub fn is_valid(&self) -> bool {
!self.is_eod() && !self.is_unused()
}

pub fn filename(&self) -> String {
// NOTE: ignore the long file name in FAT16 for lab
if self.is_valid() && !self.is_long_name() {
format!("{}", self.filename)
} else {
String::from("unknown")
}
}

/// For Standard 8.3 format
///
/// reference: https://osdev.org/FAT#Standard_8.3_format
pub fn parse(data: &[u8]) -> Result<DirEntry> {
let filename = ShortFileName::new(&data[..11]);

// FIXME: parse the rest of the fields
// - ensure you can pass the test
// - you may need `prase_datetime` function
let attributes = Attributes::from_bits_truncate(data[11]);
let mut time = u32::from_le_bytes([data[14], data[15], data[16], data[17]]);
let created_time = prase_datetime(time);
time = u32::from_le_bytes([0, 0, data[18], data[19]]);
let accessed_time = prase_datetime(time);
let cluster = (data[27] as u32) << 8
| (data[26] as u32)
| (data[21] as u32) << 24
| (data[20] as u32) << 16;
time = u32::from_le_bytes([data[22], data[23], data[24], data[25]]);
let modified_time = prase_datetime(time);
let size = u32::from_le_bytes([data[28], data[29], data[30], data[31]]);

Ok(DirEntry {
filename,
modified_time,
created_time,
accessed_time,
cluster: Cluster(cluster),
attributes,
size,
})
}

pub fn as_meta(&self) -> Metadata {
self.into()
}
}

fn prase_datetime(time: u32) -> FsTime {
// FIXME: parse the year, month, day, hour, min, sec from time
let year = 1980 + (time >> 25) as i32;
let month = (time >> 21) & 0x0f;
let day = (time >> 16) & 0x1f;
let hour = (time >> 11) & 0x1f;
let min = (time >> 5) & 0x3f;
let sec = (time & 0x1f) << 1;

if let Single(time) = Utc.with_ymd_and_hms(year, month, day, hour, min, sec) {
time
} else {
DateTime::from_timestamp_millis(0).unwrap()
}
}

impl ShortFileName {
//...
pub fn parse(name: &str) -> Result<ShortFileName> {
// FIXME: implement the parse function
// use `FilenameError` and into `FsError`
// use different error types for following conditions:
//
// - use 0x20 ' ' for right padding
// - check if the filename is empty
// - check if the name & ext are too long
// - period `.` means the start of the file extension
// - check if the period is misplaced (after 8 characters)
// - check if the filename contains invalid characters:
// [0x00..=0x1F, 0x20, 0x22, 0x2A, 0x2B, 0x2C, 0x2F, 0x3A,
// 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, 0x7C]
let mut sfn = ShortFileName {
name: [0x20; 8],
ext: [0x20; 3],
};
let mut idx = 0;
let mut seen_dot = false;
for ch in name.bytes() {
match ch {
0x00..=0x1F
| 0x20
| 0x22
| 0x2A
| 0x2B
| 0x2C
| 0x2F
| 0x3A
| 0x3B
| 0x3C
| 0x3D
| 0x3E
| 0x3F
| 0x5B
| 0x5C
| 0x5D
| 0x7C =>{
return Err(FsError::FileNameError(FilenameError::InvalidCharacter));
}
b'.' => {
if (1..=8).contains(&idx) {
seen_dot = true;
idx = 8;
} else {
return Err(FsError::FileNameError(FilenameError::MisplacedPeriod));
}
}
_ => {
let ch = ch.to_ascii_uppercase();
// trace!("Char: '{}', at: {}", ch as char, idx);
if seen_dot {
if (8..11).contains(&idx) {
sfn.ext[idx - 8] = ch;
} else {
return Err(FsError::FileNameError(FilenameError::NameTooLong));
}
} else if idx < 8 {
sfn.name[idx] = ch;
} else {
return Err(FsError::FileNameError(FilenameError::NameTooLong));
}
idx += 1;
}
}
}
if idx == 0 {
return Err(FsError::FileNameError(FilenameError::FilenameEmpty));
}
Ok(sfn)
}
}

实现文件系统

Fat16impl

pkg/storage/src/fs/fat16/impls.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
use super::*;
use crate::alloc::string::ToString;
impl Fat16Impl {
pub fn new(inner: impl BlockDevice<Block512>) -> Self {
let mut block = Block::default();
let block_size = Block512::size();

inner.read_block(0, &mut block).unwrap();
let bpb = Fat16Bpb::new(block.as_ref()).unwrap();

trace!("Loading Fat16 Volume: {:#?}", bpb);

// HINT: FirstDataSector = BPB_ResvdSecCnt + (BPB_NumFATs * FATSz) + RootDirSectors;
let fat_start = bpb.reserved_sector_count() as usize;
/* FIXME: get the size of root dir from bpb */
let root_dir_size = (bpb.root_entries_count() as usize * DirEntry::LEN + block_size - 1) / block_size;
/* FIXME: calculate the first root dir sector */
let first_root_dir_sector = fat_start + (bpb.fat_count() as usize * bpb.sectors_per_fat() as usize);
let first_data_sector = first_root_dir_sector + root_dir_size;

Self {
bpb,
inner: Box::new(inner),
fat_start,
first_data_sector,
first_root_dir_sector,
}
}
//将 cluster: &Cluster 转换为 sector
pub fn cluster_to_sector(&self, cluster: &Cluster) -> usize {
match *cluster {
Cluster::ROOT_DIR => self.first_root_dir_sector,
Cluster(c) => {
// FIXME: calculate the first sector of the cluster
// HINT: FirstSectorofCluster = ((N – 2) * BPB_SecPerClus) + FirstDataSector;
((c - 2) as usize * self.bpb.sectors_per_cluster() as usize) + self.first_data_sector
}
}
}

// FIXME: YOU NEED TO IMPLEMENT THE FILE SYSTEM OPERATIONS HERE
// - read the FAT and get next cluster
//根据当前 cluster: &Cluster,利用 FAT 表,获取下一个 cluster
pub fn next_cluster(&self, cluster: Cluster) -> Result<Cluster> {
let fat_offset = (cluster.0 * 2) as usize;
let cur_fat_sector = self.fat_start + fat_offset / Block512::size();
let offset = fat_offset % Block512::size();

let mut block = Block::default();
self.inner.read_block(cur_fat_sector, &mut block).unwrap();

let fat_entry = u16::from_le_bytes(block[offset..=offset + 1].try_into().unwrap_or([0; 2]));
match fat_entry {
0xFFF7 => Err(FsError::BadCluster), // Bad cluster
0xFFF8..=0xFFFF => Err(FsError::EndOfFile), // There is no next cluster
f => Ok(Cluster(f as u32)), // Seems legit
}
}
// - traverse the cluster chain and read the data
//遍历文件夹 dir: &Directory,获取其中文件信息
pub fn iterate_dir<F>(&self, dir: &Directory, mut func: F) -> Result<()>
where
F: FnMut(&DirEntry),
{
if let Some(entry) = &dir.entry {
trace!("Iterating directory: {}", entry.filename());
}

let mut current_cluster = Some(dir.cluster);
let mut dir_sector_num = self.cluster_to_sector(&dir.cluster);
let dir_size = match dir.cluster {
Cluster::ROOT_DIR => self.first_data_sector - self.first_root_dir_sector,
_ => self.bpb.sectors_per_cluster() as usize,
};
trace!("Directory size: {}", dir_size);

let mut block = Block::default();
while let Some(cluster) = current_cluster {
for sector in dir_sector_num..dir_sector_num + dir_size {
self.inner.read_block(sector, &mut block).unwrap();
for entry in 0..Block512::size() / DirEntry::LEN {
let start = entry * DirEntry::LEN;
let end = (entry + 1) * DirEntry::LEN;

let dir_entry ; // !
match DirEntry::parse(&block[start..end]){
Ok(entry) => dir_entry = entry,
Err(e) => {
return Err(e);
}
}


if dir_entry.is_eod() {
return Ok(());
} else if dir_entry.is_valid() && !dir_entry.is_long_name() {
func(&dir_entry);
}
}
}
current_cluster = if cluster != Cluster::ROOT_DIR {
match self.next_cluster(cluster) {
Ok(n) => {
dir_sector_num = self.cluster_to_sector(&n);
Some(n)
}
_ => None,
}
} else {
None
}
}
Ok(())
}
//根据当前文件夹 dir: &Directory 信息,获取名字为 name: &str 的 DirEntry
pub fn find_directory_entry(
&self,
dir: &Directory,
name: &str,
) -> Result<DirEntry> {
let match_name ;
match ShortFileName::parse(name){
Ok(name) => match_name = name,
Err(e) => return Err(e)
}

let mut current_cluster = Some(dir.cluster);
let mut dir_sector_num = self.cluster_to_sector(&dir.cluster);
let dir_size = match dir.cluster {
Cluster::ROOT_DIR => self.first_data_sector - self.first_root_dir_sector,
_ => self.bpb.sectors_per_cluster() as usize,
};
while let Some(cluster) = current_cluster {
for sector in dir_sector_num..dir_sector_num + dir_size {
match self.find_entry_in_sector(&match_name, sector) {
Err(FsError::NotInSector) => continue,
x => return x,
}
}
current_cluster = if cluster != Cluster::ROOT_DIR {
match self.next_cluster(cluster) {
Ok(n) => {
dir_sector_num = self.cluster_to_sector(&n);
Some(n)
}
_ => None,
}
} else {
None
}
}
Err(FsError::FileNotFound)
}
fn find_entry_in_sector(
&self,
match_name: &ShortFileName,
sector: usize,
) -> Result<DirEntry> {
let mut block = Block::default();
self.inner.read_block(sector, &mut block).unwrap();

for entry in 0..Block512::size() / DirEntry::LEN {
let start = entry * DirEntry::LEN;
let end = (entry + 1) * DirEntry::LEN;
let dir_entry =
DirEntry::parse(&block[start..end]).map_err(|_| FsError::InvalidOperation)?;
// trace!("Matching {} to {}...", dir_entry.filename(), match_name);
if dir_entry.is_eod() {
// Can quit early
return Err(FsError::FileNotFound);
} else if dir_entry.filename.matches(match_name) {
// Found it
return Ok(dir_entry);
};
}
Err(FsError::NotInSector)
}
// - parse the path
pub fn resolve_path(&self, root_path: &str) -> Option<Directory> {
let mut path = root_path.to_owned();
let mut root = fat16::Fat16Impl::root_dir();

while let Some(pos) = path.find('/') {
let dir = path[..pos].to_owned();

let tmp = self.find_directory_entry(&root, dir.as_str());
let tmp = if tmp.is_err() {
warn!("File not found: {}, {:?}", root_path, tmp);
return None;
}else{
tmp.unwrap()
};
let tmp = if tmp.is_directory(){
Ok(Directory::from_entry(tmp))
}else{
Err(FsError::NotADirectory)
};
if tmp.is_err() {
warn!("Directory not found: {}, {:?}", root_path, tmp);
return None;
}

root = tmp.unwrap();

path = path[pos + 1..].to_string();
trace!("Resolving path: {}", path);

if path.is_empty() {
break;
}
}

Some(root)
}
// - open the root directory
pub fn root_dir() -> Directory {
Directory::new(Cluster::ROOT_DIR)
}
// - ...
// - finally, implement the FileSystem trait for Fat16 with `self.handle`
}

impl FileSystem for Fat16 {
fn read_dir(&self, path: &str) -> Result<Box<dyn Iterator<Item = Metadata> + Send>> {
// FIXME: read dir and return an iterator for all entries
let root = match self.handle.resolve_path(path) {
Some(root) => root,
None => return Err(FsError::FileNotFound),
};
let mut ret: Vec<Metadata> = Vec::new();
if let Err(err) = self.handle.iterate_dir(&root, |entry| {
ret.push(entry.try_into().unwrap());
}) {
warn!("{:#?}",err);
}
Ok(Box::new(ret.into_iter()))

}

fn open_file(&self, path: &str) -> Result<FileHandle> {
// FIXME: open file and return a file handle
trace!("{}",path);
let path = path.to_owned();
let pos = path.rfind('/');
let root;
let filename;
if pos.is_none() {

root = fat16::Fat16Impl::root_dir();
filename = path.as_str();
}else
{
let pos = pos.unwrap();

trace!("Root: {}, Filename: {}", &path[..=pos], &path[pos + 1..]);

let rootu = self.handle.resolve_path(&path[..=pos]);
filename = &path[pos + 1..];
if rootu.is_none() {
return Err(FsError::FileNotFound);
}
root = rootu.unwrap();
}



//
trace!("Try open file: {}", filename);
let dir_entry = self.handle.find_directory_entry(&root, filename)?;

if dir_entry.is_directory() {
return Err(FsError::NotAFile);
}

// let file = File {
// start_cluster: dir_entry.cluster,
// length: dir_entry.size,
// mode,
// entry: dir_entry,
// }
let file = File::new(self.handle.clone(), dir_entry);

trace!("Opened file: {:#?}", &file);
//debug!("{:#?}",self.metadata(&path).unwrap());
let file_handle = FileHandle::new(self.metadata("APP/").unwrap(), Box::new(file));
Ok(file_handle)
}

fn metadata(&self, root_path: &str) -> Result<Metadata> {
// FIXME: read metadata of the file / dir
let mut path = root_path.to_owned();
let mut root = fat16::Fat16Impl::root_dir();
let mut ret:Result<Metadata> = Err(FsError::FileNotFound);
// if path.rfind('/').is_none() {
// ret = Ok((&root.entry.unwrap()).try_into().unwrap());
// return ret;
// };
while let Some(pos) = path.find('/') {
let dir = path[..pos].to_owned();

let tmp = self.handle.find_directory_entry(&root, dir.as_str());
let tmp = if tmp.is_err() {
warn!("File not found: {}, {:?}", root_path, tmp);
return Err(FsError::FileNotFound);
}else{
tmp.unwrap()
};
ret = Ok((&tmp).try_into().unwrap());
let tmp = if tmp.is_directory(){
Ok(Directory::from_entry(tmp))
}else{
Err(FsError::FileNotFound)
};
if tmp.is_err() {
warn!("Directory not found: {}, {:?}", root_path, tmp);
return Err(FsError::FileNotFound);
}

root = tmp.unwrap();

path = path[pos + 1..].to_string();
trace!("Resolving path: {}", path);

if path.is_empty() {
break;
}
}

ret
}

fn exists(&self, root_path: &str) -> Result<bool> {
// FIXME: check if the file / dir exists
let mut path = root_path.to_owned();
let mut root = fat16::Fat16Impl::root_dir();

while let Some(pos) = path.find('/') {
let dir = path[..pos].to_owned();

let tmp = self.handle.find_directory_entry(&root, dir.as_str());
let tmp = if tmp.is_err() {
return Ok(false);
}else{
tmp.unwrap()
};
let tmp = if tmp.is_directory(){
Ok(Directory::from_entry(tmp))
}else{
Err(FsError::NotADirectory)
};

path = path[pos + 1..].to_string();
trace!("Resolving path: {}", path);

if path.is_empty() {
break;
}
if tmp.is_err() {
//warn!("Directory not found: {}, {:?}", root_path, tmp);
return Ok(false);
}
root = tmp.unwrap();
}

Ok(true)
}
}

文件读取的逻辑

pkg/storage/src/fs/fat16/file.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
impl Read for File {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
// FIXME: read file content from disk
// CAUTION: file length / buffer size / offset
//
// - `self.offset` is the current offset in the file in bytes
// - use `self.handle` to read the blocks
// - use `self.entry` to get the file's cluster
// - use `self.handle.cluster_to_sector` to convert cluster to sector
// - update `self.offset` after reading
// - update `self.cluster` with FAT if necessary
trace!("at read");
if buf.len() < self.length() as usize {
warn!("buffer too small");
return Err(FsError::InvalidOperation);
}

let mut length = self.length() as usize;
let mut block = Block512::default();

for i in 0..=self.length() as usize / Block512::size() {
let sector = self.handle.cluster_to_sector(&self.entry.cluster);
self.handle.inner.read_block(sector + i, &mut block).unwrap();
if length > Block512::size() {
buf[i * Block512::size()..(i + 1) * Block512::size()].copy_from_slice(block.as_u8_slice());
length -= Block512::size();
} else {
buf[i * Block512::size()..i * Block512::size() + length].copy_from_slice(&block[..length]);
break;
}
}
Ok(self.length() as usize)
}
}

ls和cat的底层实现

pkg/kernel/src/drivers/filesystem.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
pub fn ls(root_path: &str) {
let mut path = root_path.to_string();
if !path.ends_with("/"){
path = path + "/";
}
let root_path = path.as_str();
let iter = match get_rootfs().read_dir(root_path) {
Ok(iter) => iter,
Err(err) => {
warn!("{:?}", err);
return;
}
};

// FIXME: format and print the file metadata
// - use `for meta in iter` to iterate over the entries
// - use `crate::humanized_size_short` for file size
// - add '/' to the end of directory names
// - format the date as you liket
// - do not forget to print the table header
println!("|name |type |size |created |modified |accessed |");
println!("|----------------+---------+----------+-----------------------+-----------------------+-----------------------|");
for meta in iter{
let create_time = match meta.created{
Some(time) => time.to_string(),
None => "unknown".to_string(),
};
let modified_time = match meta.modified{
Some(time) => time.to_string(),
None => "unknown".to_string(),
};
let accessed_time = match meta.accessed{
Some(time) => time.to_string(),
None => "unknown".to_string(),
};
let entry_type = match meta.entry_type{
FileType::File => "file",
FileType::Directory => "directory",
};
let (size, units) = humanized_size(meta.len as u64);
let len = format!("{:.2}{:3}",size, units);
println!("|{:16}|{:9}|{:10}|{:23}|{:23}|{:23}|", meta.name, entry_type, len, create_time, modified_time, accessed_time);
}
}

pub fn try_get_file(path: &str) -> Result<FileHandle> {
get_rootfs().open_file(path)//FileHandle.file就是Box::new(file)
}

pub fn read_file(path: &str, buf: &mut [u8]) -> Result<usize> {
//info!("{}", path);
let file = try_get_file(path);
//info!("file: {:#?}", file);
let mut file = match file{
Ok(file) => file,
Err(err) => {
warn!("{:?}", err);
return Err(err);
}
};
//info!("before read");
let ret = file.read(buf);
// info!("after read");
// info!("{:#?}",ret);
ret
}

接入操作系统

pkg/kernel/src/lib.rs

1
2
3
4
5
pub fn init(boot_info: &'static BootInfo) {
//...
filesystem::init();
//...
}

系统调用侧

pkg/syscall/src/lib.rs

1
2
3
4
5
6
7
pub enum Syscall {
//...
ListDir = 114,
Cat = 514,
//...
}

pkg/kernel/src/interrupt/syscall/service.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pub fn sys_list_dir(args: &SyscallArgs){
let root = unsafe {
core::str::from_utf8_unchecked(core::slice::from_raw_parts(
args.arg0 as *const u8,
args.arg1,
))
};
crate::drivers::filesystem::ls(root);
}
pub fn sys_cat(args: &SyscallArgs) -> usize{
let root = unsafe {
core::str::from_utf8_unchecked(core::slice::from_raw_parts(
args.arg0 as *const u8,
args.arg1,
))
};
let mut buf;
unsafe{
buf = core::slice::from_raw_parts_mut(args.arg2 as *mut u8, 4096);
};
let ret = read_file(root, &mut buf);
match ret{
Ok(len) => len,
Err(e) => {
warn!("{:#?}",e);
0
}
}
}

pkg/lib/src/syscall.rs

1
2
3
4
5
6
7
8
#[inline(always)]
pub fn sys_list_dir(root: &str) {
syscall!(Syscall::ListDir, root.as_ptr() as u64, root.len() as u64);
}
#[inline(always)]
pub fn sys_cat(root: &str, buf: &mut [u8])-> usize {
syscall!(Syscall::Cat, root.as_ptr() as u64, root.len() as u64, buf.as_ptr() as u64)
}

在用户shell中加入ls,cat和cd

pkg/app/shell/src/main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
static mut CUR_DIR:String = String::new();
fn main() -> isize {
// println!("To test the abilily to fork, YSOS will run app `fork` first");
// sys_wait_pid(sys_spawn("fork"));
// println!("Successfully exited app `fork`, YSOS will run shell next");
unsafe{
CUR_DIR = String::from("/");
}
loop {
print!("\x1b[32m\x1b[1mCJL@YSOS\x1b[0m:\x1b[34m\x1b[1m{}\x1b[0m# ", unsafe {
CUR_DIR.as_str()
});
//...
}
0
}
fn run(s: &str) -> bool{
let v:Vec<&str> = s.split(" ").collect();
if v.len() == 0{
return false;
}else if v[0] == "run"{
let mut i = 1;
while i < v.len(){
if v[i].len() != 0{
let pid = sys_spawn(v[i]);
if pid == 0{
println!("Run: unknown process {}", v[i]);
}else{
sys_wait_pid(pid);
}
return true;
}
i += 1;
}
println!("Help: run `the name of app`");
println!("For example, `run hello` will start an app named `hello`");
println!("You can view the app list with `app`");
true
}else if v[0] == "ls"{
let dist;
if v.len() == 1{
unsafe{
dist = CUR_DIR.as_str();
}
}else{
dist = v[1];
}
myls::myls(dist);
true
}else if v[0] == "cat"{
if v.len() == 1{
println!("Help: cat `the name of file`");
false
}else{
mycat::mycat(v[1]);
true
}
}else if v[0] == "cd"{
let target = if v.len() == 1{
"/"
}else{
v[1]
};
mycd::mycd(target);
true
}else{
false
}
}


pkg/app/shell/src/myls.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use lib::{string::{String, ToString}, sys_list_dir};
use mycd::get_path;

use crate::mycd;

pub fn myls(dist: &str){
let goto:String;
//获取用户输入为dist

//得到前往的目录路径
goto = get_path(&dist.to_string());

sys_list_dir(&goto);
}

pkg/app/shell/src/mycd.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use lib::{string::{String, ToString}, vec::Vec};

use crate::CUR_DIR;


pub fn mycd(dist: &str){
let goto:String;
//获取用户输入为dist

//得到前往的目录路径
goto = get_path(&dist.to_string());

unsafe{
CUR_DIR = goto.clone();
}
}
pub fn get_path(dist: &String) -> String {
// 根据输入的dist路径,返回一个字符串,表示前往的目录
// dist可以是相对路径,也可以是绝对路径
// 仅支持linux路径
let tar = split_path(dist);
//考虑第一个字符是不是`/`
if dist.chars().next().unwrap() != '/' {
//cur当前的目录
//let cur = String::from(env::current_dir().unwrap().to_str().unwrap());
let mut vcur = split_path(unsafe { &CUR_DIR });
vcur.extend(tar);
vcur = formatting_path(&vcur);
join_path(&vcur)
} else {
let tar = formatting_path(&tar);
join_path(&tar)
}
}
fn split_path(path: &String) -> Vec<String> {
//将路径字符串分解为数组
let mut tmps: String = String::from("");
let mut ret: Vec<String> = Vec::new();
for ch in path.chars() {
if ch == '/' {
if tmps.len() != 0 {
let add = tmps.clone();
ret.push(add);
}
tmps.clear();
} else {
tmps.push(ch);
}
}
if tmps.len() != 0 {
let add = tmps.clone();
ret.push(add);
}
ret
}
fn formatting_path(path: &Vec<String>) -> Vec<String> {
//处理路径中的`.`和`..`
let mut ret: Vec<String> = Vec::new();
for p in path {
if p == ".." {
if ret.len() != 0 {
ret.pop();
}
} else if p != "." {
let add = p.clone();
ret.push(add);
}
}
ret
}
fn join_path(path: &Vec<String>) -> String {
//将表示路径的字符串数组重新组合为字符串路径
let mut ret = String::from("");
for s in path {
ret += "/";
ret += s;
}
match ret.len() {
0 => String::from("/"),
_ => ret,
}
}

pkg/app/shell/src/mycat.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use lib::{print, println, string::{String, ToString}, sys_cat, sys_list_dir};
use mycd::get_path;

use crate::mycd;

pub fn mycat(dist: &str){
let goto:String;
//获取用户输入为dist

//得到前往的目录路径
goto = get_path(&dist.to_string());

let mut buf = [0u8; 4096];
let len = sys_cat(&goto, &mut buf);
for i in 0..len{
print!("{}", buf[i] as char);
}
println!();
}

至此,可实现[milestone6](# milestone6:文件系统接入操作系统)

从文件系统启动应用

实现对大文件的读取

pkg/storage/src/fs/fat16/file.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
impl Read for File{
fn read_all(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
// FIXME: read data into the buffer
// - extend the buffer if it's not big enough
// - break if the read returns 0 or Err
// - update the length of the buffer if data was read
//let mut data = vec![0u8; self.length() as usize];
buf.resize(self.length(), 0u8);
let mut length = self.length() as usize;
let mut block = Block512::default();

for i in 0..=self.length() as usize / Block512::size() {
let sector = self.handle.cluster_to_sector(&self.entry.cluster);
self.handle.inner.read_block(sector + i, &mut block).unwrap();
if length > Block512::size() {
buf[i * Block512::size()..(i + 1) * Block512::size()].copy_from_slice(block.as_u8_slice());
length -= Block512::size();
} else {
buf[i * Block512::size()..i * Block512::size() + length].copy_from_slice(&block[..length]);
break;
}
}
Ok(buf.len())
}
}

pkg/kernel/src/drivers/filesystem.rs

1
2
3
4
5
6
pub fn read_all_file(path: &str) -> Result<Vec<u8>> {
let mut file = try_get_file(path)?;
let mut buf = Vec::new();
file.read_all(&mut buf)?;
Ok(buf)
}

从文件启动进程

pkg/syscall/src/lib.rs

1
2
3
4
5
pub enum Syscall {
//...
SpawnFile = 1919,
//...
}

pkg/kernel/src/interrupt/syscall/service.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub fn sys_spawn_file(args: &SyscallArgs) -> usize{
let buf: &[u8];
unsafe{
buf = core::slice::from_raw_parts(args.arg0 as *const u8, args.arg1 as usize);
}
let path;
unsafe {
path = core::str::from_utf8_unchecked(buf);
};
//debug!("name is {}",name);
let res = proc::spawn_from_file(path);
match res {
Some(pid) => pid.0 as usize,
None => 0,
}
}

pkg/kernel/src/proc/mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub fn spawn_from_file(path: &str) -> Option<ProcessId> {
let buf = match read_all_file(path){
Ok(buf) => buf,
Err(_) => return None
};
let elf = match xmas_elf::ElfFile::new(&buf){
Ok(elf) => elf,
Err(_) => return None
};
let name = match path.rfind("/"){
Some(pos) => &path[pos+1..],
None => path
};
elf_spawn(name.to_string(), &elf)
}

用户侧

pkg/app/shell/src/myrun.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use lib::{print, println, string::{String, ToString}, sys_cat, sys_list_dir, sys_spawn_file, sys_wait_pid};
use mycd::get_path;

use crate::mycd;

pub fn myrun(dist: &str){
let path:String = get_path(&dist.to_string());
let pid = sys_spawn_file(path.as_str());
//println!("{}",pid);
if pid == 0{
println!("Error: failed to run {}", dist);
}else{
sys_wait_pid(pid);
}
}

至此,可实现[加分项](# 加分项)

4. 实验结果

image-20240606154544006

5. 总结

熟悉了磁盘IO的工作原理和文件系统的实现,实现了一个更好用的shell


YSOS-rust lab6
https://blog.algorithmpark.xyz/2024/06/07/YSOS/lab6/index/
作者
CJL
发布于
2024年6月7日
更新于
2024年7月15日
许可协议