目录
概述
什么是 Tilck?
Tilck
是一个教育用的单内核 x86 内核,旨在在二进制级别上与 Linux 兼容。项目的小规模和简单设计使其成为在内核模式下进行实践的完美平台,同时保留了在 Linux 内核上运行完全相同的用户模式位的能力。这在教育性内核领域是一个罕见的特性。因此,为 Tilck 构建程序只需要来自 bootlin.com 的 i686-musl
工具链。Tilck 不需要拥有自己一套定制编写的应用程序,这与大多数教育性内核不同。它只是运行主流 Linux 程序,如 BusyBox 套件。
虽然 Linux 兼容性和单内核设计从操作系统研究的角度来看可能似乎是一种限制,但另一方面,这种设计使整个项目在未来更接近实际应用,相比之下,将预先存在的软件移植到其他内核上可能需要付出巨大的努力。此外,没有什么能阻止 Tilck 实现自定义的非 Linux 系统调用,有意识的应用程序可以利用这些调用。
未来计划
从长远来看,取决于项目的成功程度,Tilck
可能会适用于需要完全确定性和超低延迟系统的嵌入式系统。如果运气好的话,Tilck
可能能够填补嵌入式 Linux 和典型的实时操作系统(如 FreeRTOS 或 Zephyr)之间的空白。无论如何,在某个时候它将被移植到 ARM
系列,并可能被调整以在无 MMU 的 CPU 上运行。Tilck 非常适合这一点,因为消耗极少的 RAM 一直是 Tilck 设计的一个关键点。事实上,该内核今天可以在只有 3 MB 内存的 i686 QEMU 机器上启动和运行。当然,这在 x86 上是毫无意义的,但在 ARM Cortex-R 上就不再是这样了。
除此之外,添加基本的网络和存储支持也在计划之中,尽管细节尚未确定。网络支持可能限于 UDP + IP(至少在开始时),并且可在有限的网卡上使用。存储也是如此:并非所有类型的块设备都将得到支持,内核中将实现少数文件系统(可能只有 fat32 和 ext2)。将考虑对 FUSE 文件系统的支持。
项目的一个主要里程碑将是为特定的 SoC(如 Raspberry Pi 3 或 4)支持网络和存储,但这可能只有在 Tilck 被移植到 ARM64 之后才能实现。
Tilck 不是什么?
-
一个试图重写和/或替代 Linux 内核的尝试。Tilck 是一个完全不同的内核,它只是与 Linux 有部分兼容性,以便利用其程序和工具链。此外,这有助于验证其正确性:如果一个程序在 Linux 上正常工作,它也必须以相同的方式在 Tilck 上工作(减去未实现的功能)。但是,让相当数量的 Linux 程序在其上运行只是一个起点:随着时间的推移,Tilck 将以不同的方式发展,并且也将拥有自己独特的一系列功能。
-
一个适用于桌面操作系统的内核。Tilck 上没有 X 服务器运行,这不仅是因为这需要大量额外的功能,而且因为朝这个方向发展完全超出了项目的目标。 另请参阅:https://github.com/vvaltchev/tilck/discussions/81
Tilck 对比 Linux
Tilck 与 Linux 有本质区别,它不旨在针对多用户服务器或桌面机器,因为那样做毫无意义:Linux 之所以庞大复杂,并非因为实现糟糕,而是因为它提供了大量功能,以及这些功能所需的内在复杂性。换句话说,Linux 很棒,因为它解决了特定的问题。Tilck 会提供更少的功能,以换取:
- 更简单的代码(差距很大)
- 更小的二进制大小
- 极其确定性的行为
- 超低延迟
- 更容易的开发和测试
- 额外的稳健性
总之,尽管目前这仍是一个教育项目,但在编写时已考虑到这些目标,并且拥有一个雄心勃勃的测试基础设施,试图达到近乎企业级的水平(参见测试)。
功能
Tilck 是一个可抢占的单体(但具有编译时模块)*NIX 内核,在 x86 上实现了约 100 个 Linux 系统调用(通过 int 0x80
和 sysenter
)。从核心来说,该内核并非以 x86 为中心,尽管目前只在 x86 上运行。所有特定于架构的内容都被隔离。因此,大部分内核代码已经可以为任何架构编译,并可用于内核的单元测试。
硬件支持
尽管内核使用了相当多的传统硬件,如用于 IRQ 的 8259 PIC、用于系统计时器的传统 8254 PIT、用于串行通信的传统 16550 UART、8042 键盘控制器、8237 ISA DMA 和声霸卡 16 声卡(仅 QEMU),但它也支持一些近期硬件特性,如 SSE、AVX 和 AVX2 浮点指令、PAT、i686 sysenter、PCI Express 设备枚举(通过 ECAM),最重要的是,通过 ACPICA 支持 ACPI。目前 ACPI 用于接收电源按钮事件、重启或关闭机器,以及读取机器电池的当前参数(当通过 ACPI 控制方法实现时)。
关于物理硬件的评论
从一开始,该操作系统就通过 USB 启动在物理硬件上进行定期测试(参见下面的注释)。测试机器包括实际的 i686 机器、仅有 BIOS 固件的较旧 x86_64 机器、带有 UEFI+CSM 的较新 x86_64 机器,以及最新的纯 UEFI 机器。长期以来,Tilck 的开发严格遵守以下规则:如果无法在真实硬件上测试,就不要在 Tilck 中实现。直到最近,这一规则才略有放宽,以便尝试 SB16。未来可能会有少数其他驱动程序只在虚拟机上进行测试:开发它们的理由是为操作系统带来教育价值,而且为它们建立的基础设施将被用于同类的其他驱动程序。但是,这永远不会成为常规做法。Tilck 旨在在真实硬件上工作,在那里可能发生各种奇怪的事情。在那里保持可靠性对 Tilck 的成功至关重要。
文件系统
Tilck 拥有一个简单但功能齐全(包括软硬链接、文件空洞、内存映射等)的 ramfs 实现,一个简约的 devfs 实现,对 FAT16 和 FAT32(用于 initrd)的只读支持,允许文件的内存映射,以及一个 sysfs 实现,用于提供 ACPI 命名空间的完整视图、所有 PCI(e) 设备列表和 Tilck 的编译时配置。显然,为了同时处理多个文件系统,Tilck 也有一个简单的 VFS 实现。注意:Tilck 目前还不支持块设备,所以一切都在内存中。
进程和信号
虽然 Tilck 内部使用线程的概念,但多线程目前并未暴露给用户空间(当然,内核线程是存在的)。fork()
和 vfork()
都得到了正确实现,fork 的进程使用了写时复制。waitpid()
系统调用得到了完全实现(这意味着进程组等)。对 POSIX 信号的支持是部分的:支持使用 rt_sigaction()
接口的自定义信号处理程序,但大多数 SA_* 标志不被支持,处理程序也不能相互中断。rt_sigprocmask()
、sys_rt_sigpending()
、sys_rt_sigsuspend()
按预期工作,特殊信号如 SIGSTOP、SIGCONT 和 SIGCHLD 也是如此。更多详情,请参见 syscalls 文档。
这个领域有一个有趣的特性值得特别提及:尽管用户空间缺乏多线程,Tilck 通过 set_thread_area()
完全支持 TLS(线程本地存储),因为 libmusl
需要它,即使是经典的单线程进程。
I/O
除了经典的 read()
和 write()
系统调用,Tilck 还通过 readv()
和 writev()
支持矢量 I/O。此外,还支持非阻塞 I/O、select()
和 poll()
。幸运的是,到目前为止还没有程序需要 epoll
:-)
控制台
Tilck 拥有一个支持 Linux 控制台超过 90% 功能的控制台。它在文本模式和帧缓冲模式下以相同的方式工作(使用抽象层)。实现如此强大的控制台的努力是由让 Vim 在 Tilck 上流畅运行(包括语法高亮等)的目标驱动的。虽然这样的事情与"适当的"内核开发关系不大,但能够在像 Tilck 这样简单的内核上运行 Vim 这样的"庞然大物"本身就是一个重大成就,因为它表明 Tilck 可以正确运行具有相当复杂性的程序。
用户空间应用程序
Tilck 可以运行相当多的控制台应用程序,如 BusyBox 套件、Vim、TinyCC、Micropython、Lua,以及帧缓冲应用程序如 fbDOOM(仅供娱乐),尽管这完全超出了项目的范围。查看项目的 [wiki 页面] 以获取更多关于可以在 Tilck 上运行的软件信息。
启动 Tilck
Tilck 的引导加载程序
Tilck
配备了一个交互式引导加载程序,可在传统 BIOS 和 UEFI 系统上工作。引导加载程序允许用户选择所需的视频模式、内核文件本身,并编辑内核的命令行。
第三方引导加载程序
Tilck
可以被任何支持multiboot 1.0
的引导加载程序加载。例如,qemu的内置引导加载程序可以完美地运行Tilck
:
qemu-system-i386 -kernel ./build/tilck -initrd ./build/fatpart
实际上,这种引导内核的方式在系统测试中被使用。它的快捷方式是:
./build/run_multiboot_qemu
Grub支持
Tilck
可以很容易地通过GRUB引导。只需编辑你的/etc/grub.d/40_custom
文件(或创建一个新文件),添加一个如下的条目:
menuentry "Tilck" {
multiboot <TILCK构建目录路径>/tilck
module --nounzip <TILCK构建目录路径>/fatpart
boot
}
之后,以root身份运行update-grub
并重启你的机器即可。
文档和使用指南
项目的主要文档可以在docs/
目录中找到。然而,Tilck的wiki可以用来浏览这些文档文件,并附加了许多额外内容,如截图。下面是一个快速的入门指南,重点介绍最常见的场景。
构建Tilck
该项目支持相当多的构建配置和自定义选项,但使用其默认配置构建可以用几个步骤描述。构建Tilck唯一真正的要求是拥有一个Linux x86_64主机系统或Microsoft的WSL
。步骤如下:
- 进入项目根目录。
- 构建工具链(仅第一次):
./scripts/build_toolchain
- 编译内核并准备可引导镜像:
make
此时,build
目录中将有一个名为tilck.img
的镜像文件。在这个时候尝试Tilck
最简单的方法是运行:./build/run_qemu
。
在物理硬件上运行
tilck.img
镜像当然也可以在物理机上引导,无论是UEFI系统还是传统系统。只需用dd
将镜像文件刷入U盘并重启机器即可。
其他配置
要了解更多关于如何构建和配置Tilck的信息,请查看docs/
目录中的构建指南。
测试Tilck
Tilck在同一个仓库中拥有单元测试、内核自测试、系统测试(使用系统调用接口)和自动交互式系统测试(通过QEMU的监视器模拟真实用户输入),完全集成到其构建系统中。除此之外,还有完整的代码覆盖支持和用于生成HTML报告的有用脚本(参见[覆盖率]指南)。最后,Tilck与Azure Pipelines完全集成,通过多种配置的构建和测试运行来验证每个推送的分支。内核的覆盖率数据也上传到CodeCov。以下是运行Tilck大部分测试的一些基本说明。要了解全部内容,请阅读测试文档。
运行Tilck的测试
运行Tilck的测试非常简单:只需要在机器上安装python 3
。对于自测试和经典的系统测试,运行:
<构建目录>/st/run_all_tests -c
要运行单元测试,则:
-
安装googletest库(一次性):
./scripts/build_toolchain -s build_gtest build_gmock
-
构建单元测试:
make gtests
-
运行它们:
<构建目录>/gtests
要了解更多关于Tilck测试的一般信息,以及如何运行其交互式系统测试,请阅读测试文档。
调试Tilck
使用QEMU
的集成GDB
服务器,可以像调试普通进程一样用GDB调试Tilck内核。只是在发生上下文切换时会变得棘手,但GDB无法帮助解决这个问题。要用GDB调试,请按以下步骤操作:
-
(可选)准备Tilck的调试版本,以获得更好的调试体验。
-
运行Tilck的VM:
./build/run_qemu
,但停留在引导加载程序阶段。 -
在另一个终端中运行:
gdb ./build/tilck_unstripped
。 -
在GDB中运行:
target remote :1234
连接到QEMU的gdb服务器。 -
使用类似
break kmain
的命令设置一个或多个断点。 -
输入
c
让执行继续,并在引导加载程序中按ENTER引导操作系统。
为了使调试体验更好,Tilck附带了一组GDB脚本(见other/gdb_scripts
)。有了它们,列出系统上的所有任务、任何给定进程当前打开的句柄等操作变得非常简单。要了解如何利用这些GDB脚本以及与调试Tilck项目相关的其他内容,请查看调试文档。
Tilck的调试面板
在虚拟机中使用GDB调试Tilck非常方便,但在其他情况下(例如在真实硬件上运行Tilck)我们没有GDB支持。此外,即使内核在虚拟机中运行,有些功能直接从内核本身暴露出来比通过GDB脚本更方便。向用户空间暴露内核信息的一种方式是使用sysfs
,但对于所有情况来说这并不一定是最便捷的方式(尽管如此,Tilck确实有sysfs实现),特别是当出于调试目的需要与内核本身进行交互时。为了帮助解决这些情况,Tilck内部引入了一个调试面板。它最初类似于Linux的Magic SysRq,后来演变成一种带有调试信息和用户进程跟踪功能的TUI应用程序。将来,它还将支持一些适当的调试功能。要了解更多信息,请查看调试文档。
关于用户体验的评论
Tilck在一个方面与许多开源项目明显不同:它真正关注用户体验(这里的"用户"指的是"开发者")。它不是那种构建和配置极其复杂的超酷低级项目;也不是需要在主机上安装200个东西的项目。构建这样的项目可能需要几个小时甚至几天的努力(想想特殊配置,例如使用交叉编译器构建)。相比之下,Tilck的设计目标是即使对于只有基本Linux知识的新手来说也能轻松构建和测试。它有一个复杂的脚本来构建自己的工具链,可以在所有主要的Linux发行版上运行,还有一个功能强大的基于CMake的构建系统。Tilck的构建会生成一个可以直接在QEMU中测试或写入USB存储设备的镜像。(在某种程度上,这类似于buildroot
项目对Linux所做的事情,但要简单得多。)最后,该项目还包括在QEMU上以各种配置运行Tilck的脚本(BIOS引导、UEFI引导、使用QEMU的-kernel
选项直接(多重)引导等)。
动机
拥有上述功能的原因是为用户和潜在贡献者提供真正良好的体验,避免任何形式的挫折感。希望即使是最有经验的工程师也能享受零努力的体验。但这不仅仅是为了减少挫折感。它还旨在_不吓跑_那些可能只是好奇想了解这个项目,并可能渴望为它编写一个简单程序和/或在他们的分支中添加几个printk()
的学生和初级开发者。希望这些仅仅是"玩玩"Tilck的人中,有些人实际上可能想为其开发做出贡献。
总之,即使项目本身的某些部分可能相当复杂,至少构建和运行其测试必须是任何人都能做到的事情。
常见问题
以下是一些常见问题列表。 这个列表并不全面,而且会随时间变化。 如需查看有关Tilck的完整问题列表,请查看讨论区的问答页面。