操作系统案例分析:以 FreeBSD 为例

This is one of assignments on the course “Operating System”.

此篇操作系统案例分析共分为 7 个章节。如下所示:

  1. 历史
  2. 进程与线程
  3. 内存管理及其策略
  4. 设备驱动模型
  5. 文件系统
  6. 收获与体会
  7. 参考文献

I. 历史

FreeBSD 是一个开放源代码的 Unix-like 操作系统。[1] 不同于 Linux,FreeBSD 是派生自 BSD Unix(Berkeley Software Distribution)的(同样派生自 BSD 的操作系统还有 OpenBSD、SunOS、Darwin/macOS 等),自 1993 年 11 月 1 日起,已经诞生了超过 28 年。

FreeBSD 的 1.0 版本是由 Nate Williams、 Rod Grimes 和 Jordan Hubbard 三人,由于不满 386BSD 的发展状况而创立。[2]

自 1993 年 FreeBSD 1.0 版本发布以来,它经历了重大的更新。[1]

  • FreeBSD 2.x 使用 BSD-Lite 4.4 替换了其代码库,并从 NetBSD 引入了可加载内核模块的设计。
  • FreeBSD 3.x 引入了 SMP (Symmetric MultiProcessing),使得利用多 CPU(多核)成为可能。
  • FreeBSD 4.x 带来了 IPv6、USB 2.0 以及超线程技术等多种新技术的支持。
  • FreeBSD 5.x 改写了 Giant Lock 机制,允许不同 CPU 同时进入系统核心。此版本还带来了一系列新 CPU 架构的支持,包括 UltraSPARC、IA-64 (Itanium) 以及 amd64。
  • FreeBSD 6.x 带来了性能监控计数器支持,以及控制多张网卡的能力。
  • FreeBSD 7.x 首次支持如今最为流行的 CPU 架构之一:ARM。此外,它还引入了 tmpfs 以及新的调度机制:ULE 调度。这个版本还带有一个 Linux 2.6 的模拟层,使得在该版本的 FreeBSD 上直接运行 Linux 程序成为可能。
  • FreeBSD 8.x 对于全新的 ZFS 文件系统有了稳定的支持,显着缓解缓冲区溢出和内核空指针问题,可扩展的内核安全框架(MAC Framework)已正式可用。
  • FreeBSD 9.x 升级了 ATA/SATA 驱动并支持 AHCI,并改进了数个支持的文件系统。
  • FreeBSD 10.x 对虚拟化支持进行了大幅强化。更新了 DRM 代码以匹配 Linux 3.8.13,并且允许多个同时 X Server 同时运作,在支持的 CPU 架构上,使用 Clang/LLVM 完全替代了 gcc。
  • FreeBSD 11.x 加入了对 arm64 架构的支持、ZFS 文件系统已更新以实现并行挂载。
  • FreeBSD 12.x 完全支持了对 ext4 文件系统的读写。改善了在 amd64 和 i386 上处理图形驱动程序的方式。
  • FreeBSD 13.x 最显著的变化为其内核现在支持 TLS 版本 1.0 到 1.3 的 TCP 套接字上的传输层安全 (TLS) 数据的内核内成帧和加密。

FreeBSD 的最新版本为 13.1(2022 年 5 月 12 日)。

以下,将会以 FreeBSD 13.1 为例,分析其内核的架构。

FreeBSD 的内核属于宏内核(monolithic kernel)。这意味着相对于微内核,有更多的东西运行在内核态。这其中包括了所有的设备的驱动程序、调度器、虚拟内存、进程间通信、文件系统、以及所有的系统调用。在这种内核架构下,只有用户的应用程序运行于用户态下。

宏内核被设计为单进程的,整个内核共享地址空间。正因如此,所有内核服务都共用一个地址空间,这些内核服务之间的通信相较于微内核就会更为简单。这种共用地址空间的情况下,所有内核服务都可以如同一个用户态程序那样直接调用任意的(内核中的)函数。这种能力使得宏内核能够拥有更加优良的性能以及更为简单的实现。但是这种设计也有它自己的缺陷:内核中任何一个模块的 bug 或不稳定都可能会导致整个系统的崩溃。[3]

FreeBSD 的内核虽然属于宏内核,但是它同时也是模块化的:一些内核模块可以在内核正在运行的时候动态加载。正因如此,FreeBSD 的内核能够动态地加载和卸载内核代码。这种技术的一大利用之处就是可以动态加载新设备的驱动程序,实现类似于 Windows 的即插即用(Plug’n’Play, PnP)功能,在插入如 U 盘的新设备的时候不需要重启系统就可以进行使用。

II. 进程与线程

1. 进程与线程的描述

在 FreeBSD 中,进程和线程都是系统中的任务(task),每个执行线程(thread of execution)都是一个进程。[4] 对于每个进程,都有一个名为 proc 的结构,其中包含所有相关的信息,例如 PID(process ID)、PPID(parent PID)、进程的优先级和进程的状态。有五种不同的统计数据:SIDL、SRUN、SSLEEP、SSTOP 和 SZOMB。函数 fork() 创建 PID 和 PPID 与原始任务不同的实际任务的副本。将所有资源都复制一遍的成本是很高的,因此只有两个进程都使用的重复资源(写时复制,Copy on Write)。这种父-子关系向系统中的进程集引入了层次结构。一个进程可以用另一个程序的内存映像覆盖自己,使用系统调用 execve 将一组参数传递给新创建的映像。通常一个进程通过调用系统调用 exit 来终止,向其父进程发送 8 位退出状态。要是父进程在子进程终止之前终止,由于前文所提到的分层的进程结构,子进程必须绑定到另一个进程,否则子进程需要先行终止。[3]

线程和进程一样,在 FreeBSD 中被描述为任务(task)。但与进程不同的是,线程通常由进程来管理,对于使用者而言通常是接触不到的,这与进程通常可以被使用者所直接管理是相对的,这也是两种任务中的不同之处。

2. 进程调度策略综述

FreeBSD 使用分时调度程序,每一个进程都具有一个优先级。进程优先级会根据包括它使用的 CPU 时间量、它持有或执行所需的内存资源量在内的各种参数定期重新计算。调度程序是一个多级反馈队列,它更倾向于短的作业或 IO 密集型作业。在最底层的队列中,进程以轮转的形式交替执行,直到它们完成并离开系统。

用于表示进程优先级的有两个值:每个进程在开始执行时都会有一个介于 -20 到 20 之间的 nice 值(niceness,友善度),还有一个是介于 0 和 99 之间的实时优先级(realtime-priority)。时间片(timeslice)决定了一个进程在遭到抢占(preempted)之前可以执行多长的时间,在一轮调度中,任何一个进程都不能执行超过其被分配的时间片长度的时间。当所有进程都将时间片用完时,新一轮的调度将会开始,进程再次获得一定量的时间片。反正,若并非所有进程都将时间片用完,已用完时间片的进程就无法在此时执行。这种情况下,通常 IO 密集型的程序可以抢占 CPU 密集型的程序,但 CPU 密集型的程序相较之下能够获得更长的时间片。

一些任务需要更为精确地控制其执行过程,如必须在某时间点之前完成计算、或按照某一特定顺序完成计算,称为实时调度。FreeBSD 的内核对于实时调度实现使用另外一个,不同于普通的使用分时调度的程序的队列。具有实时优先级的进程不受优先级降级的影响,只会被具有相同或更高实时优先级的另一个进程抢占。

FreeBSD 内核还实现了以空闲优先级(idle priority)运行的进程队列。具有空闲优先级的进程仅在实时或时间共享调度队列中没有其他进程可运行时才会运行,并且仅当其空闲优先级等于或大于所有其他可运行的空闲优先级进程时才会运行。

FreeBSD 实现上述调度的方式是透过其 ULE 调度器。下面将分析其 ULE 调度器的工作机理。

3. FreeBSD ULE

FreeBSD 在版本 7.x 后引入了全新的调度机制:ULE 调度。相较于传统的调度机制,ULE 调度有更好的性能。下面简要叙述一下 ULE 调度的运行机制。

3.1 分核心调度(Per-Core Scheduling)

通常情况下,ULE 采用两个运行队列(runqueue)以管理线程:一个队列包含交互式(interactive)线程,另外一个包含批处理(batch)线程。如果目标核心空闲,空闲(idle)队列会被引入,此队列只包含空闲任务(the idle task,即什么都不做,表示 CPU 空闲)。

引入两个队列的目的是赋予交互式的进程更高的优先级。批处理程序通常在没有用户交互的情形下执行,因此调度所带来的延迟便显得没有这么重要。ULE 用一个称为“交互惩罚”(interactivity penalty)的指标来衡量一个线程的“交互性”(interactivity),这个指标介于 0 和 100 之间,计算方式如下:

r <- the time which was spent by the thread in running
s <- the time spent voluntarily sleeping (not including waiting for CPU)

scaling factor = m <- 50

penalty(r, s) => m / (s / r), when s > r
                 m / (r / s) + m, otherwise

此值低于一半(50)表示对应线程相较于其运行的时间,其有更多的时间在自愿让予其他线程运行。反之亦然。ULE 预设为将历史数据保留 5s。过长会导致批处理任务更难检测到;过短会引入不必要的噪声,给分类带来不必要的困扰。

为了给线程分类,ULE 会计算一个分数,由分数 = 交互惩罚 + 友善度定义,低于 30(FreeBSD 13.1 的预设)说明这是一个交互式的线程。反之,则为批处理线程。线程创建时,其交互惩罚的初始值继承自创建它的线程。线程销毁时,其运行数据(即,交互惩罚)将会返回并加到创建它的线程中。这样的机制对创建批处理线程的交互式进程进行了惩罚,以防滥用。

在两个运行队列中,进程们根据优先级进行进一步的排序。在交互式线程的队列中,其优先级即为分数。在批处理进程中,其优先级与其运行时间成反比。

在选择下一个要运行的线程时,ULE 首先在交互式运行队列中进行搜索。 如果存在交互式线程可以被调度,则返回该线程。 如果交互式运行队列为空,ULE 会改为在批处理运行队列中搜索。 如果两个运行队列都是空的,就说明这个核心目前是空闲的,不需要进行调度。

这种机制先让交互式线程运行,然后才是批处理线程。这么看来批处理进程似乎有被饿死(或饥饿,starvation,指线程一直没能被调度器选中运行)的可能。但是根据定义,交互式线程本来就是休眠时间多余执行的时间,这么一来交互式线程总会被执行完,因而饿死也不会发生。

一个线程只能运行有限的时间,称为时间片(timeslice)。与 Linux 的 CFS 调度相异,无论其优先级大小如何,一个线程消耗其时间片的速率是恒定的。但时间片的长度会随当前核心上所运行的线程数量的改变而改变:只有一个线程时,时间片是 10 ticks(78 ms)。随运行线程数量的增加,时间片的长度逐渐减小,直至到达下限 1/127 秒。在 ULE 调度中,完全抢占(full preemption)是禁止的,也就是说只有内核线程才能够抢占其他线程。

3.2 负载均衡

ULE 只会尝试将线程平均地分到每个核上。在 ULE 中,一个核心的“负载”(load)被简单地定义为当前核心上可以运行的线程的数量。与 CFS 不同的是,ULE 不对线程分组,更为倾向于把线程看作一个个独立的实体。

在为新创建或唤醒的线程选择核心时,ULE 使用一种亲和启发式(affinity heuristic)的算法。 如果线程在它上次运行的核心上是缓存亲和(cache affine)的,那么它就会选择在这个核心上运行。 否则,ULE 会在拓扑中查找被认为是亲和的最高级别,直至查找完整个机器的逻辑 CPU。在这种(没找到)的情况下,ULE 首先会试着找到一个最低优先级高于该线程的核心。如果没找到,ULE 会再次尝试,但这次会遍历机器的所有内核。如果还是没找到,ULE 会退化为选择机器上运行线程数最少的内核。

ULE 还定期对线程进行平衡,周期为每 500-1500 毫秒(随机选择的)。 与 CFS 不同,周期性负载均衡仅由核心 0 执行。核心 0 简单地尝试使核心之间的线程数量均匀,如下所示:来自负载最多的核心(称为捐献者)的线程被迁移到负载较少的核心(成为受捐者)。一个核心不能同时成为捐献者或受捐者。负载均衡器反复执行,直到找不到捐献者或受捐者。也就是说每次负载均衡器调用时,一个核心最多可以放弃或接收一个线程。

周期性的负载均衡在 ULE 中的频率要低于 CFS,但每次调用时会计算更多的东西。理由是更好的初始线程调度可以减少对全局负载均衡器的调用。

III. 内存管理及其策略

计算机系统中的物理内存是有限的资源,可以安装的内存量有硬性限制。物理内存不一定是连续的;它可以作为一组不同的地址范围进行访问。此外,不同的 CPU 架构,甚至同一架构的不同实现对如何定义这些地址范围都不尽相同。这些性质使得直接处理物理内存变得相当复杂,为了避免这种复杂性,提出了虚拟内存的概念。[7]

为了对内存进行有效的管理,FreeBSD 将内存空间分为小的块,称为页(page)。页的划分及其大小和运行 FreeBSD 内核的 CPU 架构有关:例如在 amd64 架构上的 FreeBSD 的页大小是 4 KB。页分为虚拟页面和物理页面,它们大小一致。一个物理页面可以映射为一个或多个虚拟页面,这种映射关系由页表(page table)维护,页表负责将进程所使用的虚拟地址转换为物理地址。页表可以是多级的。最低级的页表储存被进程/线程所直接使用的物理地址,而(层次上)高级的页表储存属于低一级页表的页面的地址,层次上最高级的页表的地址储存于寄存器中。当需要获得某一具体虚拟地址所对应的物理地址的时候,再将此过程反向,以获取所需的物理地址。

由于硬件的限制,通常是物理内存大小的限制,使得内核无法用相同的形式来表示所有的页。和 Linux 相似地,FreeBSD 将整个页空间分为三个区域,分别是:ZONE_DMA,用于表示启用了 DMA(DMA-Enabled)的页;ZONE_NORMAL 用于表示常规页;ZONE_HIGHMEM 用于表示并非常驻内核地址空间的页。[3] 内存区域的实际布局取决于硬件,因为并非所有架构都定义了所有区域,并且不同平台对 DMA 的要求也不同。[7]

对内存的分配和释放是内核中最为常见的操作之一。在内存分配与释放这一点上,FreeBSD 与同为 Unix-like 的 Linux 是极为相似的。FreeBSD 的内核并非为每个单独的内存请求分配内存,而是维护有一组空闲列表。每一个空闲列表都表示一组特定大小(大小为 2 的幂)的空闲缓冲区。每个缓冲区都有一个固定长度的头部。当此块缓冲区为空闲时,此头部储存着指向下一块空闲缓冲区的指针。反之(并非空闲),它则会储存指向其所归属的空闲列表的指针。每当请求一定量的空间的时候(例如,经由 malloc() 函数),内核就会计算出可以容纳该量空间的最小缓冲区的大小、从对应空闲列表中移除一块未用的缓冲区、修改该被返回的缓冲区的头部、并返回紧邻头部之后的地址。[5] 如果所请求大小的块恰好没有可用的空闲块,内核将会分割一个更大的块为两块小块,此二块互为伙伴(buddies)。其中一块被分配并返回,另外一块作为空闲块。如果分割后的块还是大于所需块大小,则重复该算法,直到产生的块的大小恰好为所需块的大小。当一个块被释放的时候,内核会检查它的伙伴是否也处于空闲状态,如果是,则进行合并操作并重复该算法直至不能够再合并为止。[6]

物理内存具有易失性,而从磁盘等存储设备中读取页面是将数据放入内存的常见情况。当从存储设备中读取的时候,数据会被放入页面缓存(page cache)中,以防止在后续对同一个处的访问中再次执行代价高昂的存取磁盘操作。类似地,当写入的时候,数据同样是先缓存在页面缓存中,直到它们最终被写回到存储设备上。为了知道一个页面是否被缓存, FreeBSD 使用了一种称为基数树的数据结构(也称压缩前缀树,Radix Trie),它是一种可以快速搜索所需页面的二叉树。缓冲区缓存和页面缓存在 FreeBSD 的实现中被放在一起,因为将两者分开所带来的同步过于复杂。

由于物理内存大小是有限的,而系统中可能启用了磁盘上的交换区(swap)。当系统中存在已启用的交换空间、又发生物理内存不足的情况时,存在于物理内存上的页面就可能遭到换出(swap out),将其存于磁盘上的交换空间中。FreeBSD 使用 LRU(Least Recently Used,最近最少使用)算法来决定哪些页面会被换出。当需要的空间越来越多的时候,甚至可以将整个进程换出。在被换出页面的表的实现中,FreeBSD 使用页面哈希表而不是基数树,因而 FreeBSD 需要在一个双向链表中查找以确定一个页面是否在物理内存中。[3]

在现实中,有可能出现内存被用尽、内核再也没有办法找到足够的内存以继续使得整个系统正常运行。这时,为了使得内存用尽的影响最小化、保证系统剩余部分的正常运行,内核会执行 OOM Killer。OOM Killer 舍小家为大家,它会选择一个任务作为牺牲品,终止其运行,以冀腾出足够的内存供剩下的部分继续正常运作。

IV. 设备驱动模型

1. 综述

在 FreeBSD 中,设备是任何属于现有系统的硬件相关的东西,包括硬盘、显卡、打印机等等。而”驱动程序“就是一个控制这些东西的电脑程序。Unix-like 操作系统中的大多数设备都是通过设备节点访问的,这些设备节点也称为特殊文件,通常位于文件系统层次结构中的 /dev 下。[9] 在 FreeBSD 中,设备驱动有以下的几种:

  • 字符驱动
  • 块驱动(在 FreeBSD 4.x 中被删除)
  • 网络驱动
  • 虚拟驱动(亦作伪驱动)

字符驱动是提供字符流 IO 的驱动程序,或者一个无结构的(原始的,raw)接口,用于输入和输出。

块驱动原先用于控制存储设备,但由于其缓存设计不佳,在 FreeBSD 4.x 中被废弃了。

网络驱动用于驱动网络接口。这类驱动程序在存取时不会用到设备节点,而是使用 socket 系统调用来实现。

虚拟设备的驱动程序在没有任何特定底层硬件的情况下在软件中模拟设备的行为。

2. 可加载内核模块

驱动程序既可以直接编译进系统内核中,也可以动态地加载:这种动态加载进系统的驱动程序就是可加载内核模块(Loadable Kernel Modules,在 FreeBSD 中缩写为 KLD)。

一个 KLD 就是一个内核的子系统,能够在系统启动后进行加载、卸载、启动、停止等等操作。换言之即为在系统运行中时可以动态地向内核添加或者删除功能。显然,此处的功能指的就是驱动设备的能力。每个 KLD 都包含一个模块事件处理程序(module event handler)和一个 DECLARE_MODULE 宏的调用。前者在模块被加载或卸载的时候、或系统关机的时候调用,用于正确地处理这些事件;DECLARE_MODULE 宏的调用则是向内核宣告自己的存在。[8]

3. 动态内核链接工具

kld接口允许系统管理员从运行的系统中动态地添加和删除功能。 这允许设备驱动程序的编写者将他们的新改动加载到运行的内核中, 而不用为了测试新改动而频繁地重启。[10]

kld接口通过下面的特权命令使用:

  • kldload - 加载新内核模块
  • kldunload - 卸载内核模块
  • kldstat - 列举当前加载的模块

4. 访问设备驱动程序

UNIX 提供了一套公共的系统调用供用户的应用程序使用。当用户访问设备节点时,内核的上层将这些调用分发到相应的设备驱动程序。脚本 /dev/MAKEDEV 为你的系统生成了大多数的设备节点,但如果你正在开发你自己的驱动程序,可能需要用 mknod 创建自己的设备节点。[10]

5. 块设备(已废弃)

其他 UNIX 系统支持另一类型的磁盘设备,称为块设备。块设备是内核为它们提供缓冲的磁盘设备。这种缓冲使得块设备几乎没有用,或者说非常不可靠。缓冲会重新安排写操作的次序,使得应用程序丧失了在任何时刻及时知道准确的磁盘内容的能力。这导致对磁盘数据结构(文件系统,数据库等)的可预测的和可靠的崩溃恢复成为不可能。由于写操作被延迟,内核无法向应用程序报告哪个特定的写操作遇到了写错误,这又进一步增加了一致性问题。由于这个原因,真正的应用程序从不依赖于块设备,事实上,几乎所有访问磁盘的应用程序都尽力指定总是使用字符(或"raw",原始的)设备。由于实现将每个磁盘(分区)同具有不同语义的两个设备混为一谈,从而致使相关内核代码极大地复杂化,作为推进磁盘I/O基础结构现代化的一部分,FreeBSD 抛弃了对带缓冲的磁盘设备的支持。[10]

6. 设备启动

在FreeBSD操作系统中,注册按先总线设备后叶设备的顺序进行,设备的启动顺序如下所示:

  1. root_bus
  2. nexus/simplebus
  3. ACPI设备
  4. PCI设备

主要的启动过程为:

  1. Root_bus 检测其子设备
  2. 查找相应的设备驱动
  3. 进一步检测总线上的具体设备
  4. 查找这些设备的驱动程序
  5. 初始化设备、将设备连接到系统中
  6. 注册设备的中断处理函数

系统统中的 root_bus 设备并不执行具体的设备驱动,它实际上是提供给其它设备一个根结点,以便所有的设备都能够通过它联系在一起,形成一个有机的整体。Nexus 设备负责初始化系统资源的大小,同时提供 ACPI 资源链支持。

FreeBSD 的设备驱动管理是按照树节点方法,一层一层往上挂载。[11]

V. 文件系统

本节中,以 FreeBSD 所支持的 Z 文件系统 (ZFS) 为例,分析其结构、磁盘空间的管理、与文件操作有关的系统调用、以及为保证文件系统安全所采取的措施。

1. 综述

ZFS 是一种高级文件系统,旨在解决以前存储子系统软件中发现的主要问题。

最初在 Sun 公司开发,正在进行的开源 ZFS 开发已转移到OpenZFS 项目。

ZFS 有三个主要的设计目标:

  • 数据完整性
  • 池化存储
  • 性能

ZFS 不仅仅是一个文件系统,它与传统的文件系统有着根本的不同。通过结合传统上被分离的卷管理器文件系统的功能,为 ZFS 提供了独特的优势。文件系统现在能够感知磁盘的底层结构。传统文件系统只能在一个磁盘上,不能够横跨多个磁盘。如果有两个磁盘,则必须创建两个单独的文件系统,而不能当一个使。传统的硬件 RAID 配置通过向操作系统提供由多个物理磁盘通过某种形式组织成的单一逻辑磁盘来避免此问题,操作系统在其上建立文件系统,就如同在一个物理磁盘上那样。即使使用 GEOM 提供的软件 RAID 解决方案,位于 RAID 之上的 UFS 文件系统也认为它正在处理单个设备。ZFS 卷管理器和文件系统的组合解决了这个问题,并允许创建共享所有存储空间的文件系统。ZFS 感知物理磁盘结构的一大优势是,当向池中增加新的磁盘时,现有文件系统会自动扩展,扩展的新空间可直接供文件系统使用。ZFS 还可以对每个文件系统应用不同的属性。这使得创建分开的文件系统和数据集而不是单一的单一文件系统非常有用。[12]

2. 结构及空间管理

ZFS 采用一种被称为池化(pooled)存储的方式来组织数据。物理设备,如磁盘等,可以被加入到一个池(pool)中,并且文件系统、卷所需要的从这个池的可用空间中分配。池的空间将会随着新物理设备的加入而增长。[12]

3. 相关系统调用

FreeBSD 支持众多与文件系统相关的系统调用。如下所示,为 FreeBSD 中与文件系统相关的系统调用。

Number Name Short Description Implementation
3 read Read input kern/sys_generic.c
4 write Write output kern/sys_generic.c
5 open Open or create a file for reading, writing or executing kern/vfs_syscalls.c
6 close Delete a descriptor kern/kern_descrip.c
9 link Make a hard file link kern/vfs_syscalls.c
10 unlink Remove directory entry kern/vfs_syscalls.c
15 chmod Change mode of file kern/vfs_syscalls.c
16 chown Change owner and group of a file kern/vfs_syscalls.c
21 mount Mount or dismount a file system kern/vfs_mount.c
22 unmount Mount or dismount a file system kern/vfs_mount.c
33 access Check accessibility of a file kern/vfs_syscalls.c
34 chflags Set file flags kern/vfs_syscalls.c
35 fchflags Set file flags kern/vfs_syscalls.c
36 sync Schedule file system updates kern/vfs_syscalls.c
56 revoke Revoke file access kern/vfs_syscalls.c
57 symlink Make symbolic link to a file kern/vfs_syscalls.c
58 readlink Read value of a symbolic link kern/vfs_syscalls.c
60 umask Set file creation mode mask kern/vfs_syscalls.c
120 readv Read input kern/sys_generic.c
121 writev Write output kern/sys_generic.c
123 fchown Change owner and group of a file kern/vfs_syscalls.c
124 fchmod Change mode of file kern/vfs_syscalls.c
128 rename Change the name of a file kern/vfs_syscalls.c
136 mkdir Make a directory file kern/vfs_syscalls.c
137 rmdir Remove a directory file kern/vfs_syscalls.c
138 utimes Set file access and modification times kern/vfs_syscalls.c
274 lchmod Change mode of file kern/vfs_syscalls.c
289 preadv Read input kern/sys_generic.c
290 pwritev Write output kern/sys_generic.c
378 nmount Mount or dismount a file system kern/vfs_mount.c
391 lchflags Set file flags kern/vfs_syscalls.c
475 pread Read input kern/sys_generic.c
476 pwrite Write output kern/sys_generic.c
489 faccessat Check accessibility of a file kern/vfs_syscalls.c
490 fchmodat Change mode of file kern/vfs_syscalls.c
491 fchownat Change owner and group of a file kern/vfs_syscalls.c
492 fexecve Execute a file kern/kern_exec.c
494 futimesat Set file access and modification kern/vfs_syscalls.c
495 linkat Make a hard file link kern/vfs_syscalls.c
496 mkdirat Make a directory file kern/vfs_syscalls.c
497 mkfifoat Make a fifo file kern/vfs_syscalls.c
498 [freebsd11] mknodat Make a special file node
499 openat Open or create a file for reading, writing or executing kern/vfs_syscalls.c
500 readlinkat Read value of a symbolic link kern/vfs_syscalls.c
501 renameat Change the name of a file kern/vfs_syscalls.c
502 symlinkat Make symbolic link to a file kern/vfs_syscalls.c
503 unlinkat Remove directory entry kern/vfs_syscalls.c
550 fdatasync Synchronise changes to a file kern/vfs_syscalls.c
551 fstat Get file status kern/kern_descrip.c
552 fstatat Get file status kern/vfs_syscalls.c
553 fhstat Access file via file handle kern/vfs_syscalls.c
554 getdirentries Get directory entries in a file system independent format kern/vfs_syscalls.c
555 statfs Get file system statistics kern/vfs_syscalls.c
556 fstatfs Get file system statistics kern/vfs_syscalls.c
557 getfsstat Get list of all mounted file systems kern/vfs_syscalls.c
558 fhstatfs Access file via file handle kern/vfs_syscalls.c
559 mknodat Make a special file node kern/vfs_syscalls.c
564 getfhat Get file handle kern/vfs_syscalls.c
565 fhlink Make a hard file link kern/vfs_syscalls.c
566 fhlinkat Make a hard file link kern/vfs_syscalls.c
567 fhreadlink Read value of a symbolic link kern/vfs_syscalls.c

[13]

4. 文件系统安全性

ZFS 有专门面向数据完整性的设计。在 ZFS 中,所有数据都包含数据的校验和。ZFS 计算校验和并将它们与数据一起写入。稍后读取该数据时,ZFS 会重新计算校验和。如果校验和不匹配,就说明检测到一个或多个数据错误;ZFS 将在相同块、镜像块或奇偶校验块可用时尝试自动纠正错误。

ZFS 所储存的每个块都会计算其校验和。使用的校验和算法是以数据集为单位进行的。每个块在读​​取时都会验证其校验和,这个校验动作是对上层程序透明的,从而允许 ZFS 在不打扰用户的情况下检测损坏。如果读取的数据与预期的校验和不匹配,ZFS 将尝试从任何可用的冗余(如镜像或 RAID-Z)中恢复数据。[12]

VI. 收获与体会

通过这一次的案例分析,给我带来的收获是极多的。不仅对于 FreeBSD 及 Unix 系列系统的历史及特性有了初步的了解,还拓宽了视野,使得接触到的 OS 不仅限于 Windows 和 Linux 系列这些大众的操作系统,能够更好地绕过表象看见本质。同时,在写这次案例分析的过程中,遇到的困难不算少。FreeBSD 作为一个市占率不高的 OS,纵使有着长久的历史,互联网上关于它的资料却是少之又少,中文的资料是更为少的,许多资料仅仅停留在应用的层面。此次案例分析,寻找资料的时间是非常长的,但是也增进了自己对资料的查找以及筛选的能力。

VII. 参考文献

  1. Wikipedia Authors: FreeBSD. [Online] Available from: https://en.wikipedia.org/wiki/FreeBSD, [Accessed 13th May 2022].

  2. FreeBSD Contributers: FreeBSD Handbook. [Online] Available from: https://docs.freebsd.org/en/books/handbook/introduction, [Accessed 14th May 2022].

  3. Bitterling, P., Operating System Kernels. 2010.

  4. M. W. Lucas, Absolute FreeBSD. No Starch Press, 2019.

  5. Harsh Thoriya, Power-of-Two Free Lists Allocators | Kernel Memory Allocators. [Online] Available from: https://www.geeksforgeeks.org/power-of-two-free-lists-allocators-kernal-memory-allocators, [Accessed 15th May 2022].

  6. Linux Kernel Authors, Physical Page Allocation. [Online] Available from: https://www.kernel.org/doc/gorman/html/understand/understand009.html, [Accessed 15th May 2022].

  7. Linux Kernel Authors, Concept Overview. [Online] Available from: https://www.kernel.org/doc/html/latest/admin-guide/mm/concepts.html, [Accessed 15th May 2022].

  8. Joseph Kong, FreeBSD Device Drivers. No Starch Press, 2012.

  9. FreeBSD Contributers: Writing FreeBSD Device Drivers. [Online] Available from: https://docs.freebsd.org/en/books/arch-handbook/driverbasics/, [Accessed 14th May 2022].

  10. FreeBSD Contributers: 编写 FreeBSD 设备驱动程序. [Online] Available from: https://docs.freebsd.org/zh-cn/books/arch-handbook/driverbasics/, [Accessed 23rd May 2022].

  11. hahachenchen789: FreeBSD 设备驱动管理介绍. [Online] Available from: https://blog.csdn.net/hahachenchen789/article/details/69665912, [Accessed 23rd May 2022].

  12. FreeBSD Contributors: The Z File System (ZFS). [Online] Available from: https://docs.freebsd.org/en/books/handbook/zfs/, [Accessed 24th May 2022].

  13. Alfonso Siciliano: FreeBSD System Calls Table. [Online] Available from: https://alfonsosiciliano.gitlab.io/posts/2021-01-02-freebsd-system-calls-table.html, [Accessed 25th May 2022].


Last modified on 2022-05-20