diff --git a/Makefile b/Makefile index 5d65aa7..c713248 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ SCRIPTS_DIR := $(SUPPORT_DIR)/scripts export KCONFIG_DIR := $(SCRIPTS_DIR)/kconfig .PHONY: all -all: test +all: example .PHONY: everything everything: all report diff --git a/lib/rkgpudev/src/lib.rs b/lib/rkgpudev/src/lib.rs index 3612931..cb9e77c 100644 --- a/lib/rkgpudev/src/lib.rs +++ b/lib/rkgpudev/src/lib.rs @@ -41,9 +41,8 @@ pub use output::*; use rkplat::drivers::virtio::__GPU_DEIVCE; use crate::DIRECTION::{Horizontal, Vertical}; -static mut _EMPTY: [u8; 0] = [0; 0]; static FONT: [u128; 127] = include!("font.txt"); -pub static mut FB: &mut [u8] = unsafe { &mut _EMPTY }; +pub static mut FB: &mut [u8] = &mut []; pub static mut FB_CURSOR: &mut [u32] = &mut [0; 1000]; pub unsafe fn init() { diff --git a/makefiles/report.mk b/makefiles/report.mk index 4ba81bd..a877177 100644 --- a/makefiles/report.mk +++ b/makefiles/report.mk @@ -51,3 +51,6 @@ feasibility-report.bib: $(REPORT_ROOT_DIR)/3_feasibility/feasibility-report.bib feasibility-report.pdf: $(REPORT_ROOT_DIR)/3_feasibility/feasibility-report.tex feasibility-report.bib ../runikraft-report.cls env TEXINPUTS=$(REPORT_ROOT_DIR)/3_feasibility:$$TEXINPUTS $(TEX) $(TEX_FLAGS) $(REPORT_ROOT_DIR)/3_feasibility/feasibility-report.tex + +final-report.pdf: $(REPORT_ROOT_DIR)/5_final/final-report.tex ../runikraft-report.cls + env TEXINPUTS=$(REPORT_ROOT_DIR)/5_final:$$TEXINPUTS $(TEX) $(TEX_FLAGS) $(REPORT_ROOT_DIR)/5_final/final-report.tex diff --git a/report/.gitignore b/report/.gitignore index e8e850c..eb0028b 100644 --- a/report/.gitignore +++ b/report/.gitignore @@ -7,4 +7,4 @@ *.bbl *.run.xml *.toc -!*/assets/* +!/assets/* diff --git a/report/5_final/final-report.tex b/report/5_final/final-report.tex index 37ce736..05a189b 100644 --- a/report/5_final/final-report.tex +++ b/report/5_final/final-report.tex @@ -23,19 +23,35 @@ %\renewcommand{\today}{2022年7月9日} \begin{document} -\title{\bfseries Runikraft小组\quad 结题报告} +\title{\bfseries Runikraft小组\quad 结题报告\thanks{作者按Unicode码位排序,按贡献度排序是张子辰、郭耸霄、陈建绿、吴骏东、蓝俊玮。}} \author{吴骏东\and 张子辰\and 蓝俊玮\and 郭耸霄\and 陈建绿} \date{\today} \maketitle \tableofcontents -\part{调研报告} \section{项目简介} Runikraft 是用Rust语言编写的能在RISC-V架构 + QEMU平台上运行的unikernel。 它基于用C语言实现的Unikraft,在继承Unikraft的高效性和可定制性的同时, 进一步简化了构建系统镜像的流程,加入了RISC-V支持,并且 用Rust语言提供了更强的内核安全保证。 +与RustyHermit和rCore等用Rust实现的操作系统不同, +我们将完全使用Rust的稳定特性编写代码。 + +众所周知,在互联网技术和云计算不断发展的今天,我们身边的虚拟化设备不断增多, +它们中的很多都使用了Unikernel。然而,虚拟化设备和Unikernel也暴露出了很多安全问题。 +对这些安全问题,C语言因其本身的缺陷难辞其咎。 + +Rust 是一门让人人都能写出可靠、高效的软件的语言。\cite{bib:feasibility-7}利用Rust编写Unikernel, +可以充分发挥它相对C语言的高层抽象和内存安全保证。用Rust改写项目已经成为一种解决问题的 +有效手段,如zalando公司从Scala转向Rust的成功故事。\cite{bib:feasibility-3} + +RISC-V是一款自由、开源的ISA,它开启了全新的基于开放标准协作的处理器创新时代。\cite{bib:feasibility-0} +目前基于RISC-V架构的开源处理器有很多,既有标量处理器Rocket, +也有超标量处理器BOOM,还有面向嵌入式领域的Z-scale、PicoRV32等。\cite{bib:feasibility-2} + +因此,我们计划使用Rust语言改写架构和性能方面占优的unikernel——Unikraft。 + \section{项目背景} \subsection{操作系统的架构} 最初的操作系统缺乏明确定义的结构,也就是简单结构。这类系统的设计者希望用 @@ -278,14 +294,6 @@ \subsubsection{RustyHermit}\sectionauthor{陈建绿} RustyHermit 中优化实现了网络栈。它使用 \href{https://github.com/smoltcp-rs/smoltcp}{smoltcp} 作为它的网络栈,使用 \href{https://www.linux-kvm.org/page/Virtio}{Virtio} 作为客户机和主机操作系统之间的接口。 -%将RustyHermit 和 Linux 分别作为客户端运行在基于 Linux 的主机系统上的虚拟机中,以信息的比特数作为自变量,吞吐量/Mbps作为因变量, -%进行测试并绘图,结果如下:\cite{bib:20-linux-kernel} -%\begin{figure}[H] -%\includegraphics[width=\linewidth]{../assets/RustyHermit-1.png} -%\caption{} -%\end{figure} -% -%由结果图可以看出,\textbf{RustyHermit 在信息比特数较小时吞吐量明显比 Linux 更快}。 Sung, Olivier, Lankes and Ravindran\cite{bib:18-intra-unikernel} 提出了一个修改版本的 RustyHermit,该版本利用Intel MPK(Memory Protection Keys)\cite{bib:19-mpk}, @@ -293,8 +301,6 @@ \subsubsection{RustyHermit}\sectionauthor{陈建绿} 包括安全内核代码与不安全内核代码之间的隔离、内核代码与用户代码之间的隔离。 在一组宏基准测试中,带有隔离功能的 unikernel 仅减慢了0.6\%。 -%这篇论文中的内容可以作为我们实现 runikraft 的参考。 - \subsubsection{Rumprun}\sectionauthor{陈建绿} Rumprun unikernel 是在 rump kernels 的基础上开发的。 @@ -386,8 +392,6 @@ \subsubsection{Nanos}\sectionauthor{陈建绿} \item Nanos 并不打算在裸金属上运行,所以开发者努力使其内核尽可能简单。 \end{itemize} -%这也许会对我们的项目有所帮助。 - \subsubsection{Unikraft}\sectionauthor{张子辰} Unikraft是一个比较新的unikernel。它在设计时就充分考虑 了现有的unikernels的优缺点。它在保持unikernel的极简化、 @@ -471,11 +475,7 @@ \subsubsection{Unikraft}\sectionauthor{张子辰} 且缺乏W\^{}X、stack canary等安全特性,unikernel 其实比传统的容器更不安全。 也就是说,攻击者可以利用 unikernel 上的程序的漏洞,控制 unikernel 所在的虚拟机, 进而未经授权访问资源。 -%以 unikernel 的传统应用领域云计算为例, -%如果某个 unikernel 负责处理用户的敏感数据——它通过网络获取用户的数据, -%然后将计算结果通过网络发回,则它一定拥有读用户数据的权限。那么, -%一旦这个 unikernel 存在安全漏洞,攻击者虽然不能控制 unikernel 所在的宿主机, -%但足够窃取用户的数据。 + 所以,不能片面地把安全性与隔离性等同。而且我们不能 片面地认为使用 Rust 这样的安全的程序设计语言就能保证安全, 因为完整的 unikernel 上不只包含安全的系统代码(或者说库代码),还包含可能不安全的用户代码, @@ -484,14 +484,7 @@ \subsubsection{Unikraft}\sectionauthor{张子辰} NCC Group提到了ASLR、分页保护(如W\^{}X政策、内部数据加固、保护页、空页面漏洞等)、 栈保护标志\footnote{它的英文stack canary来自金丝雀曾经被用来检查煤矿的有毒气体的典故\cite{bib:canary}。}、堆加固、标准库加固等安全措施。 -%\begin{itemize} -%\item 地址空间布局随机化 (ASLR)。 -%\item 分页保护:如W\^{}X政策、内部数据加固、保护页、空页面漏洞。 -%\item 栈保护标志(stack canary,典故:金丝雀曾经被用来检查煤矿的有毒气体\cite{bib:canary}):在栈的返回地址后加上一个随机的整型变量(canary),执行\texttt{ret}前检查 canary 是否被修改。 -%\item 堆加固:堆的结构通常是双向链表,链表的元数据(如指针)通常与数据块相邻,堆上的溢出可以改变这些数据块,导致内存的分配、释放算法出错,通常的加固方法是为元数据增加校验值。 -%\item 熵和随机数生成器:通常的 unikernel 缺乏足够产生密码学安全的伪随机数的硬件熵,这导致 unikernel 上生成的伪随机数的质量差,解决方法有使用 CPU 的专用随机数指令(如 x86 的 \texttt{rdrand}) -%\item 标准库加固:如\texttt{printf} 的 \texttt{\%n} 格式符、自定义格式符、\texttt{\_FORTIFY\_SOURCE} 宏。 -%\end{itemize} + 它测试了 rumprun 和 includeOS 两个 unikernels,并发现它们几乎没有实现任何安全特性。 尽管 Unikraft 使用 C 语言实现,但它支持(或计划支持) @@ -500,9 +493,6 @@ \subsubsection{Unikraft}\sectionauthor{张子辰} \href{https://github.com/unikraft/unikraft/pull/421}{ARM BTI}、\href{https://github.com/unikraft/unikraft/pull/191}{KASAN}、\href{https://github.com/unikraft/unikraft/pull/239}{PIE}、True Random Number Generator、 Intel CET、ARM SB等安全特性。\cite{bib:unikraft-secuirty} -%总的来说,Unikraft是我们发现的最好的unikernel项目,所以我们的项目将 -%主要参考它。 - \subsubsection{比较}\sectionauthor{张子辰} 表\ \ref{table:unikernel-compare}\ 比较了我们详细调研的unikernels。因为现有的 资料并没有包含我们需要的所有信息,表中的一些信息是依靠阅读源代码获取的。 @@ -531,8 +521,6 @@ \subsubsection{比较}\sectionauthor{张子辰} \end{longtable} \normalsize -%\subsection{Unikernels面临的问题} - \section{立项依据}\sectionauthor{张子辰} 我们调研的unikernel项目的不足之处可以概括为(并不每个unikernel都有所有缺点): \begin{itemize} @@ -561,9 +549,7 @@ \section{立项依据}\sectionauthor{张子辰} 我们小组计划仿照Unikraft的架构,用Rust语言编写能在RISC-V架构+ QEMU平台上 运行的unikernel——Runikraft。Runikraft的核心代码使用Rust编写,但 -允许用户代码使用任何语言编写 -%——只要它能够被编译成入口为\texttt{\_start}的目标代码 -。Runikraft强调构建系统镜像的简洁,用户只需要 +允许用户代码使用任何语言编写。Runikraft强调构建系统镜像的简洁,用户只需要 修改现有的项目的编译参数就可以构建基于Runikraft的系统镜像,而不必使用 专用的工具链,更不需要重构代码。具体的方法是先将Runikraft编译成静态库, 然后通过修改编译参数,让用户程序不链接标准库,而链接到Runikraft。 @@ -584,16 +570,6 @@ \section{立项依据}\sectionauthor{张子辰} Runikraft的目标是提供比较完整的POSIX兼容层、Linux兼容层和C标准库, 但是考虑到时间有限,这些功能只能部分实现。 -%如果时间允许,我们还会尝试: -%\begin{enumerate} -%\item 支持更多架构,比如目前流行的AMD64和ARMv8; -%\item 支持在裸机上运行,虽然unikernel为云计算诞生,但这并不代表它只适合 -% 云计算领域,事实上,任何专一用途的设备上的系统都可以是unikernel, -% 而且unikernels理论上可以具有比现有的实时系统更高效率; -%\item 支持调试,zos小组\textsuperscript{\ref{ssubsec:x-zos}}曾做过相关研究; -%\item 移植更多库。 -%\end{enumerate} - 我们考虑过改善unikernel的调试,即zos小组的研究, 但是我们发现,QEMU事实上已经支持交互式的虚拟机调试了。 @@ -614,189 +590,6 @@ \section{立项依据}\sectionauthor{张子辰} 在系统架构方面,我们将主要参考Unikraft; 在技术方面,我们将参考RustyHermit、Nanos和Chen and Wu的\textit{rCore Tutorial Book}\cite{bib:rcore-os}。 -\begin{comment} - -\subsection{Unikernel} -Unikernel是专一用途的、单地址空间的轻量操作系统。Unikernels在虚拟机 -上运行时,能够提供比传统的容器更短的启动时间、更高的运行效率和更强的 -隔离性,因rn此unikeels通常被用在云计算领域。\cite{bib:unikernel} - - -\noindent 现有的unikernels普遍存在以下问题:\cite{bib:unikraft} -\begin{itemize} -\item 编译它们并让它们达到高效率需要大量专业的工作,而且这些工作通常 -需要对每个目标应用程序重做。 -\item 它们通常不是POSIX兼容的,需要移植程序和语言环境。 -\end{itemize} - -\ref{subsec:famous-unikernel-projects}\ 小节将简要介绍我们小组详细调研的ClickOS、MirageOS、IncludeOS、Rusty-Hermit、Rumprun -和Unikraft等六个目前仍然在维护的unikernel项目。 -\end{comment} - -\section{前瞻性/重要性分析} - -\subsection{使用先进的工具构建}\sectionauthor{郭耸霄 and 张子辰} - -Rust和RISC-V都是新兴事物,它们都是在吸取旧事物的教训的基础上诞生的, -而且,实践表明,两者都正在经历蓬勃的发展,并正在分别逐步取代旧事物。 -%而unikernel本身也是比较新颖的操作系统结构,它在云计算领域正在逐步取代 -%传统的容器,并且有在嵌入式领域取代传统的嵌入式实时系统的潜能。 -因此,用Rust在RISC-V上开发unikernel顺应了历史的趋势。 - -Rust 是由 Mozilla 研究室 -主导开发的一门现代系统编程语言,自 2015 年 5 月发布 1.0 之后,一直以每 6 周 -一个小版本的开发进度稳定向前推进。语言设计上跟 C++ 一样强调零开销抽象和 RAII。 -拥有极小的运行时和高效的 C 绑定,使其运行效率与 C/C++ 一个级别,非常适合对性能 -要求较高的系统编程领域。利用强大的类型系统和独特的生命周期管理实现了编译期内存管理, -保证内存安全和线程安全的同时使编译后的程序运行速度极快,Rust 还提供函数式编程语言 -的模式匹配和类型推导,让程序写起来更简洁优雅。\cite{bib:2-why-rust} -总地来说,Rust是一门赋予每个人 构建可靠且高效软件能力的语言。\cite{bib:1-rust-lang} -Rust具有高性能、可靠性、生产力三方面的优势。 - -RISC-V是于2010年诞生自加州大学伯克利分校的精简指令集架构, -它的目标是成为一个通用的指令集架构,它能适应包括从最袖珍的嵌入式控制器, -到最快的高性能计算机等各种规模的处理器;它能兼容各种流行的软件栈和编程语言; -它能适应所有实现技术,包括现场可编程门阵列(FPGA) - 、专用集成电路(ASIC) 、全定制芯片,甚至未来的设备技术;它对所有微体系结构样式都有效, -例如微编码或硬连线控制、顺序或乱序执行流水线、单发射或超标量等;它支持广泛的专业化, -成为定制加速器的基础;它是稳定的,基础的指令集架构不应该改变。\cite{bib:risc-v-manual} -与以往的ISA不同,RISC-V是\textit{模块化}的。它的核心是一个名为RV32I的基础ISA, -运行一个完整的软件栈。RV32I是固定的,永远不会改变。这为编译器编写者,操作系统开发人员和汇 -编语言程序员提供了稳定的目标。模块化来源于可选的标准扩展,根据应用程序的需要, -硬件可以包含或不包含这些扩展。这种模块化特性使得RISC-V具有了袖珍化、低能耗的特 -点,而这对于嵌入式应用可能至关重要。RISC-V在设计时考虑了成本、简洁性、性能、 -架构和具体实现的分离、提升空间、 -程序大小和易于编程/编译/链接七个方面的因素。 - -目前的unikernel中,使用/支持两者中的一个的都很少,而根本没有将两者结合者。Runi\-kraft的 -亮点之一就是将两者结合。 - -\subsection{模块化设计}\sectionauthor{张子辰} -目前的大多数unikernel强调“uni-”,它们的设计者认为这样有利于提高效率, -所以系统被设计成了一个整体,这个整体向用户提供能够调用函数。具体的表现就是 -系统的源代码堆在一起,ClickOS、IncludeOS、MirageOS、RustyHermit都有这样 -的问题。系统缺乏明确的功能组件,所以系统必须作为一个整体维护。 - -在Runikraft中,只有极少数平台层的代码被放到了系统的核心组件中,而调度器、 -分配器等组件一律是micro-libraries。这些micro-libraries遵循一套明确 -定义的APIs,同一个系统模块可以有多种实现,用户可以轻松为自己的需求选择合适的系统组件的实现。 -%Rust语言中的trait非常适合这种接口与实现分离的设计。 -从Unikraft给出 -的基准测试数据看,这种模块划分不会降低系统的效率。 - -\section{相关工作} - -\subsection{安全容器}\sectionauthor{蓝俊玮} -《项目背景》中已指出,容器是云计算领域的常用的隔离手段,而由于容器并不是沙盒, -它提供的隔离能力不足以运行潜在的恶意代码,安全容器应运而生。从设计目标看,安全 -容器的隔离能力与unikernels等同。安全容器的目标是能够直接运行ELF二进制文件, -而unikernels通常要求从源代码重新编译。 - -Kata Containers和gVisor都使用Go语言实现。 - -Kata Containers的实现思路是轻量级虚拟机,它是容器向uniKernel的过渡。它的主要特点是\cite{bib:kata}: -\begin{description} -\item[安全] Runs in a dedicated kernel, providing isolation of network, -I/O and memory and can utilize hardware-enforced isolation with virtualization VT extensions. -\item[兼容] Supports industry standards including OCI container format, -Kubernetes CRI interface, as well as legacy virtualization technologies. -\item[高效] Delivers consistent performance as standard Linux containers; -increased isolation without the performance tax of standard virtual machines. -\item[简洁] Eliminates the requirement for nesting containers inside full -blown virtual machines; standard interfaces make it easy to plug in and get started. -\end{description} - -gVisor的实现思路是半虚拟化操作系统,它在用户空间运行,以拦截系统调用的方式为 -应用程序提供服务。它与传统容器的关键区别是没有简单地将应用程序的系统调用重定向 -给宿主机内核,而是实现了大多数内核原语,并基于这些原语实现系统调用。gVisor与unikernel的 -区别是gVisor没有模拟硬件,而只是模拟了一个Linux内核; -unikernel本身是一个运行在虚拟硬件上的操作系统。 -gVisor的特点:\cite{bib:gvisor} -\begin{description} -\item[容器原生安全] By providing each container with its own application kernel, gVisor limits the attack surface of the host. This protection does not limit functionality: gVisor runs unmodified binaries and integrates with container orchestration systems, such as Docker and Kubernetes, and supports features such as volumes and sidecars. - -\item[资源高效] Containers are efficient because workloads of different shapes and sizes can be packed together by sharing host resources. gVisor uses host-native abstractions, such as threads and memory mappings, to co-operate with the host and enable the same resource model as native containers. - -\item[跨平台] Modern infrastructure spans multiple cloud services and data centers, often with a mix of managed services and virtualized or traditional servers. The pluggable platform architecture of gVisor allows it to run anywhere, enabling consistent security policies across multiple environments without having to rearchitect your infrastructure. -\end{description} - -\subsection{嵌入式系统}\sectionauthor{陈建绿} -大部分unikernels只打算在虚拟机上运行,但是IncludeOS和Rumprun支持在嵌入式设备上运行, -往年的ridiculous-includeos小组也做过将unikernel移植到嵌入式设备的研究。 -可见嵌入式设备也是unikernel潜在的应用领域。嵌入式系统的类型丰富多样,这里着重介绍物联网(IoT)系统。 - -目前,物联网操作系统主要分为两大类,一是由传统的嵌入式实时操作系统(RTOS)发展而来, -比如FreeRTOS、LiteOS、RT-Thread;二是由互联网公司的云平台延伸而来, -基于传统操作系统进行“剪裁”和定制的IoT OS,比如Ali OS Things、TencentOS tiny、Win10 IOT。\cite{bib:iot-sys} - -\href{https://github.com/OpenAtomFoundation/TencentOS-tiny}{TencentOS Tiny} 是腾讯 -面向物联网领域开发的实时操作系统,具有低功耗,低资源占用,模块化,安全可靠等特点, -可有效提升物联网终端产品开发效率。 - -TencentOS tiny 提供精简的 RTOS 内核,内核组件可裁剪可配置, -可快速移植到多种主流 MCU (如 STM32 全系列)及模组芯片上。而且, -基于 RTOS 内核提供了丰富的物联网组件,内部集成主流物联网协议栈 -(如CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT等),可助力物联网终端设备及 -业务快速接入腾讯云物联网平台。 - -\href{https://github.com/ms-rtos}{MS-RTOS} (Micro Safe RTOS) 是翼辉信息全新设计的一款面向未来的 -安全实时操作系统,其最大的特点是开创性地在没有 MMU 和资源受限的 MCU(如Cortex-M3)上也能支持多进程与动态装载技术,使得应用与系统能分离开发、独立升级; -MS-RTOS 支持内核空间内存保护(应用程序通过 syscall 访问内核), -使得内核有着非常高的安全性。MS-RTOS 在提供足够丰富功能的同时,保持了高效简洁的实现, -对 ROM、RAM 消耗极低,特别适用于对硬件成本敏感、安全性要求特别高的产品。\cite{bib:ms-rtos} - -\begin{description} -\item[多进程] 允许运行多个进程,进程用户代码工作在 CPU 用户态, -通过系统调用(syscall)访问内核资源,利用 MPU 实现进程地址空间相互隔离。 -\item[动态装载] 驱动与应用程序分离开发,应用与系统独立升级, -应用程序直接在 FLASH 中运行(无需加载到 RAM 执行,节约 RAM,运行速度更快)。 -\item[内核安全] 进程用户代码工作在 CPU 用户态,通过系统调用 -(syscall)进入内核, 保护内核不被进程破坏,利用 MPU 做到进程地址空间相互隔离, -进程影响范围最小化,掉电安全文件系统。 -\end{description} -\subsection{用Rust编写的操作系统} -Rust语言的目标之一就是取代C语言,成为用于系统开发的底层语言。目前已有大量用Rust开发的操作系统:\cite{bib:rust-os-comparison} - -\begin{longtable}{|>{\bfseries}l|p{0.11\linewidth}|p{0.02\linewidth}|p{0.02\linewidth}|p{0.08\linewidth}|p{0.06\linewidth}|p{0.02\linewidth}|p{0.02\linewidth}|p{0.05\linewidth}|p{0.08\linewidth}|p{0.08\linewidth}|} -\caption{用Rust实现的操作系统比较}\\ -\hline -名称&\textbf{架构}&\textbf{纯} \rotatebox[origin=c]{-90}{\textbf{Rust}}&\textbf{活跃}&\textbf{内核架构}&\textbf{目标}&\textbf{用户态}&\rotatebox{-90}{\textbf{GUI}}&\textbf{贡献者数}&\textbf{文件系统}&\textbf{许可}\\\hline -\endfirsthead -\hline -名称&\textbf{架构}&\textbf{纯} \rotatebox[origin=c]{-90}{\textbf{Rust}}&\textbf{活跃}&\textbf{内核架构}&\textbf{目标}&\textbf{用户态}&\rotatebox{-90}{\textbf{GUI}}&\textbf{贡献者数}&\textbf{文件系统}&\textbf{许可}\\\hline -\endhead -redox&x86-32\newline x86-64&是&是&微内核&通用&是&是&50&ZFS\newline RedoxFS&Expat\footnote{自由软件基金会反对把Expat许可证称为“MIT”许可证。}\\\hline -Theseus OS&x86-64\newline ARM WIP&是&是&Safe-language SAS/SPL OS&通用+嵌入式&&是&25&Custom\newline FAT32&Expat\\\hline -Tock&Cortex M&&是&&&&否&40&&APL 2/\newline Expat\\\hline -intermezzOS&x86-64&否&是&?&PoC&否&否&18&无&APL 2/\newline Expat\\\hline -RustOS&x86-32&?&是&无&PoC&否&否&10&无&APL 2/\newline Expat\\\hline -rustboot&x86-32&?&否&无&PoC&否&否&8&无&Expat\\\hline -\end{longtable} - -其中的Redox是一款功能完整的类UNIX操作系统,而不只是操作系统内核,它包含C标准库、 -窗口管理器、浏览器、文本编辑器、图像查看器、终端模拟器等众多软件包。 - -\part{可行性报告} -\section{项目介绍}\sectionauthor{郭耸霄} -我们选择的课题是使用Rust语言改写Unikraft Unikernel。与RustyHermit和rCore等用Rust实现的操作系统不同, -我们将完全使用Rust的稳定特性编写代码。 - -众所周知,在互联网技术和云计算不断发展的今天,我们身边的虚拟化设备不断增多, -它们中的很多都使用了Unikernel。然而,虚拟化设备和Unikernel也暴露出了很多安全问题。 -对这些安全问题,C语言因其本身的缺陷难辞其咎。 - -Rust 是一门让人人都能写出可靠、高效的软件的语言。\cite{bib:feasibility-7}利用Rust编写Unikernel, -可以充分发挥它相对C语言的高层抽象和内存安全保证。用Rust改写项目已经成为一种解决问题的 -有效手段,如zalando公司从Scala转向Rust的成功故事。\cite{bib:feasibility-3} - -RISC-V是一款自由、开源的ISA,它开启了全新的基于开放标准协作的处理器创新时代。\cite{bib:feasibility-0} -目前基于RISC-V架构的开源处理器有很多,既有标量处理器Rocket, -也有超标量处理器BOOM,还有面向嵌入式领域的Z-scale、PicoRV32等。\cite{bib:feasibility-2} - -因此,我们计划使用Rust语言改写架构和性能方面占优的unikernel——Unikraft。 -在这份报告中,我们将首先介绍项目的理论依据——Rust语言适用于系统编程的特性 -及对Unikraft源码的分析和改写思路;随后,我们将介绍使用QEMU进行硬件仿真和 -测试的方法;最后,我们将给出项目的创新点和总体设计。 \section{理论依据} \subsection{Rust语言}\sectionauthor{陈建绿}\vspace*{-4ex} \subsubsection{保证内存安全的方式} @@ -959,8 +752,6 @@ \subsubsection{特权特权模型} 目前 RISC-V 支持的特权指令格式为: -%\begin{table}[H] - %\caption{RISC-V 特权指令} { \small \newcommand{\instbit}[1]{\mbox{\scriptsize #1}} @@ -1743,7 +1534,7 @@ \subsection{启动操作系统} use runikraft as rk; -use rkalloc::RKalloc; +use rkalloc::Alloc; use rkalloc_empty::RKallocEmpty; use rk::plat::time; @@ -1819,198 +1610,643 @@ \subsection{运行和调试} \label{fig:run} \end{figure} -\subsection{QEMU性能测试} -Unikernel以高性能著称,性能也是评价unikernel的重要指标。 -然而遗憾的是,由于我们无法得到RISC-V物理机,而QEMU仿真硬件架构本身 -会带来巨大的性能损失,所以我们很难准确评估我们写的Runikraft的性能。 -为了相对准确的估计Runikraft -的性能,可以测试QEMU仿真本身带来的性能损失, -然后可以用QEMU上的运行时间除以性能损失比\footnote{riscv64时间÷ x86-64时间},估计真实RISC-V -硬件上的运行时间。 - -我们用组员张子辰同学的数据结构课程的大作业 -\href{https://github.com/WCIofQMandRA/huffman_compressor}{Huffman树压缩器}做了基准测试。 -测试结果见表\ \ref{table:test}。 -\begin{longtable}{|c||>{\ttfamily}r|>{\ttfamily}r|>{\ttfamily}r||>{\ttfamily}r|>{\ttfamily}r|>{\ttfamily}r|} -\caption{QEMU基准测试}\label{table:test}\\ -\hline -\multirow{2}{*}{文件}&\multicolumn{3}{c|}{压缩/s}&\multicolumn{3}{c|}{提取/s}\\\cline{2-7} -&\multicolumn{1}{c|}{x86-64}&\multicolumn{1}{c|}{riscv64}&\multicolumn{1}{c||}{损失比}& -\multicolumn{1}{c|}{x86-64}&\multicolumn{1}{c|}{riscv64}&\multicolumn{1}{c|}{损失比}\\\hline -\endfirsthead -test01&0.019& 0.079&4.16&0.009&0.058&6.44\\\hline -test02&0.040& 0.134&3.35&0.011&0.083&7.55\\\hline -test03&1.075& 3.054&2.84&0.316&1.624&5.14\\\hline -test04&1.114& 3.285&2.95&0.272&1.701&6.23\\\hline -test05&0.715& 2.085&2.92&0.201&1.116&5.55\\\hline -test06&5.001&14.121&2.82&1.177&7.018&5.96\\\hline -\end{longtable} +\begin{comment} + +\subsection{Unikernel} +Unikernel是专一用途的、单地址空间的轻量操作系统。Unikernels在虚拟机 +上运行时,能够提供比传统的容器更短的启动时间、更高的运行效率和更强的 +隔离性,因rn此unikeels通常被用在云计算领域。\cite{bib:unikernel} + + +\noindent 现有的unikernels普遍存在以下问题:\cite{bib:unikraft} +\begin{itemize} +\item 编译它们并让它们达到高效率需要大量专业的工作,而且这些工作通常 +需要对每个目标应用程序重做。 +\item 它们通常不是POSIX兼容的,需要移植程序和语言环境。 +\end{itemize} + +\ref{subsec:famous-unikernel-projects}\ 小节将简要介绍我们小组详细调研的ClickOS、MirageOS、IncludeOS、Rusty-Hermit、Rumprun +和Unikraft等六个目前仍然在维护的unikernel项目。 +\end{comment} + +\section{前瞻性/重要性分析} + +\subsection{使用先进的工具构建}\sectionauthor{郭耸霄 and 张子辰} + +Rust和RISC-V都是新兴事物,它们都是在吸取旧事物的教训的基础上诞生的, +而且,实践表明,两者都正在经历蓬勃的发展,并正在分别逐步取代旧事物。 +因此,用Rust在RISC-V上开发unikernel顺应了历史的趋势。 + +Rust 是由 Mozilla 研究室 +主导开发的一门现代系统编程语言,自 2015 年 5 月发布 1.0 之后,一直以每 6 周 +一个小版本的开发进度稳定向前推进。语言设计上跟 C++ 一样强调零开销抽象和 RAII。 +拥有极小的运行时和高效的 C 绑定,使其运行效率与 C/C++ 一个级别,非常适合对性能 +要求较高的系统编程领域。利用强大的类型系统和独特的生命周期管理实现了编译期内存管理, +保证内存安全和线程安全的同时使编译后的程序运行速度极快,Rust 还提供函数式编程语言 +的模式匹配和类型推导,让程序写起来更简洁优雅。\cite{bib:2-why-rust} +总地来说,Rust是一门赋予每个人 构建可靠且高效软件能力的语言。\cite{bib:1-rust-lang} +Rust具有高性能、可靠性、生产力三方面的优势。 + +RISC-V是于2010年诞生自加州大学伯克利分校的精简指令集架构, +它的目标是成为一个通用的指令集架构,它能适应包括从最袖珍的嵌入式控制器, +到最快的高性能计算机等各种规模的处理器;它能兼容各种流行的软件栈和编程语言; +它能适应所有实现技术,包括现场可编程门阵列(FPGA) + 、专用集成电路(ASIC) 、全定制芯片,甚至未来的设备技术;它对所有微体系结构样式都有效, +例如微编码或硬连线控制、顺序或乱序执行流水线、单发射或超标量等;它支持广泛的专业化, +成为定制加速器的基础;它是稳定的,基础的指令集架构不应该改变。\cite{bib:risc-v-manual} +与以往的ISA不同,RISC-V是\textit{模块化}的。它的核心是一个名为RV32I的基础ISA, +运行一个完整的软件栈。RV32I是固定的,永远不会改变。这为编译器编写者,操作系统开发人员和汇 +编语言程序员提供了稳定的目标。模块化来源于可选的标准扩展,根据应用程序的需要, +硬件可以包含或不包含这些扩展。这种模块化特性使得RISC-V具有了袖珍化、低能耗的特 +点,而这对于嵌入式应用可能至关重要。RISC-V在设计时考虑了成本、简洁性、性能、 +架构和具体实现的分离、提升空间、 +程序大小和易于编程/编译/链接七个方面的因素。 -测试脚本是\href{https://github.com/WCIofQMandRA/huffman_compressor/blob/8ab200b656a0e087e27480fcbcae4000a59f6b47/test/benchmark.sh}{test/bencmark.sh}。 -原始记录见我们的仓库的\href{https://github.com/OSH-2022/x-runikraft/tree/eca8a7575be96fb0a4dc311a8c60622c0e9b0aa5/reference/qemu-benchmark.log}{reference/qemu-benchmark.log}。 +目前的unikernel中,使用/支持两者中的一个的都很少,而根本没有将两者结合者。Runi\-kraft的 +亮点之一就是将两者结合。 + +\subsection{模块化设计}\sectionauthor{张子辰} +目前的大多数unikernel强调“uni-”,它们的设计者认为这样有利于提高效率, +所以系统被设计成了一个整体,这个整体向用户提供能够调用函数。具体的表现就是 +系统的源代码堆在一起,ClickOS、IncludeOS、MirageOS、RustyHermit都有这样 +的问题。系统缺乏明确的功能组件,所以系统必须作为一个整体维护。 -从测试结果可以看出,不同类型的程序的损失比并不相同,但是我目前并没有弄清楚 -什么样的程序更容易造成性能损失。由于QEMU无法有效利用处理器的分支预测和高速缓存, -我猜测流水线友好和缓存友好的程序会引起较大的性能损失。考虑到内核的大部分操作不涉及大范围的 -寻址,可以认为系统内核属于会引起较大性能损失的程序,所以我们把QEMU仿真带来的性能损失视为5。 +在Runikraft中,只有极少数平台层的代码被放到了系统的核心组件中,而调度器、 +分配器等组件一律是micro-libraries。这些micro-libraries遵循一套明确 +定义的APIs,同一个系统模块可以有多种实现,用户可以轻松为自己的需求选择合适的系统组件的实现。 +从Unikraft给出的基准测试数据看,这种模块划分不会降低系统的效率。 -\section{创新点} +\subsection{创新点} 与先前用Rust改写FreeRTOS的小组类似,我们的项目偏重工程,没有理论上的创新点。 由于我们了解的所有用Rust写的操作系统都或多或少的使用了unstable的特性,我们用stable特性 编写unikernel也不失为一种技术上的创新。 -\section{概要设计}\sectionauthor{张子辰}\vspace*{-4ex} -\subsection{架构} -Runikraft的架构与Unikraft几乎相同。 +\section{概要设计}\sectionauthor{张子辰} \begin{figure}[!hbt] \centering -\begin{minipage}{0.49\linewidth} -\centering -\includegraphics[width=\linewidth]{../assets/Unikraft-architecture.png} -\caption{Unikraft的架构}\label{fig:unikraft-arch} -\end{minipage} -\begin{minipage}{0.49\linewidth} -\centering -\includegraphics[width=\linewidth]{../assets/Runikraft-architecture.pdf} -\caption{Runikraft的架构}\label{fig:runikraft-arch} -\end{minipage} +\includegraphics[width=\linewidth]{../assets/Runikraft-architecture-impl.pdf} +\caption{Runikraft的架构}\label{fig:runikraft-arch-impl} \end{figure} 平台层将不同的平台封装成通用的\texttt{rkplat} API,它提供与平台/架构密切相关的功能, -比如外设驱动、外中断处理、内存分页、定时器、原子操作、内存屏障,我们只计划支持RISC-V+QEMU virt -一种平台。如果时间不足,我们可以将现成的OpenSBI\cite{bib:feasibility-5}\cite{bib:feasibility-6}封装成\texttt{rkplat} API。 +比如外设驱动、外中断处理、内存分页、定时器、原子操作、内存屏障,我们只支持RISC-V+QEMU virt +一种平台。对于RISC-V架构,OSes运行在supervisor特权模式,它需要运行在machine特权模式的 +OpenSBI\cite{bib:feasibility-5}\cite{bib:feasibility-6}的协助才能关机、设置定时器和 +触发核间中断。 -\texttt{rknetdev}、\texttt{rkblkdev}、\texttt{rktime}、\texttt{rkswrand}、\texttt{rkboot} +\texttt{rkgpudev}、\texttt{rkinputdev}、\texttt{rktime}、\texttt{rkswrand}、\texttt{rkboot} 五个APIs的功能与平台密切相关, 但是为了降低开发和维护难度,它们并没有直接实现,而是在\texttt{rkplat}提供的初级抽象的基础上实现。 -\texttt{rknetdev}和\texttt{rbblkdev}对应图\ \ref{fig:unikraft-arch}\ 的⑦和⑧, -它们分别提供网络设备和块设备的支持。 -%与Unikraft的实现类似,这两个APIs的构造函数的参数接受指向\texttt{trait RKplat}的指针。 -\texttt{rktime}提供获取和修改系统时间的API,\texttt{rkswrand}提供密码学安全的随机数, -\texttt{rkboot}负责完成OS元件的初始化并将控制权转交给用户的代码。 +\texttt{rkgpudev}和\texttt{rbintputdev}分别提供显示设备和输入设备的支持。 +\texttt{rktime}提供获取系统时间的API,\texttt{rkswrand}提供密码学安全的随机数, +\texttt{rkboot}负责完成OS元件的初始化并将控制权交给用户的代码。 \texttt{rksched}和\texttt{rklock}是两个与调度器有关的APIs,前者负责创建、调度、撤销线程,后者负责线程间 -的同步和互斥。Runikraft支持多种调取器,比如,图\ \ref{fig:runikraft-arch}\ 中的RR是时间片轮转调度器、 -Mul-qu是多重队列调取器、EDF是截止日期有限调度器。我们计划先实现RR调度器。当然,为了系统镜像的轻量性, -用户可以不使用任何调取器,这时,\texttt{rksched}和\texttt{rklock}会被编译成空API,创建线程的行为 -将和函数调用相同。这保证了大部分使用仅仅并行加速的程序仍然能正确运行。 +的同步和互斥。Runikraft支持多种调取器,比如,图\ \ref{fig:runikraft-arch-impl}\ 中的 +coop是协作式的FCFS调度器、 +preem是抢占式的RR调度器。当然,为了系统镜像的轻量性,用户可以不使用任何调取器。 -Runikraft选择性地提供线程通信模块,如UNIX风格的信号(\texttt{rksignal}),信箱(\texttt{rkmbox}), +Runikraft选择性地提供线程通信模块,如基于信箱的线程通信(\texttt{rkmpi}), 无锁的环形缓冲队列(\texttt{rkring})。 - -我们目前没有计划区分线程和进程,Runikraft的线程同时具有传统的OSes的线程和进程的特性: +Runikraft不区分线程和进程,它的线程同时具有传统的OSes的线程和进程的特性: 线程之间没有隔离措施,但是线程之间又可以使用进程通信的方法更安全地同步。 \texttt{rkalloc}是分配器API,它的后端可以是\texttt{buddy}、\texttt{tinyalloc}、\texttt{tlsf}、 \texttt{mimalloc}等分配器。 -不过,我们可能只会实现其中的一个。 +目前,我们只实现了其中的\texttt{buddy},即伙伴分配器。 + +Runikraft还提供了一些工具模块,比如时间格式转换工具\texttt{rktimeconv}、 +命令行参数分析工具\texttt{rkargparse}。我们曾经计划实现调试工具\texttt{rkdebug},但我们发现, +Rust的核心库提供的\texttt{assert}和\texttt{debug\_assert}宏已经足够取代Unikraft的\texttt{ukdebug}了, +所以最终没有实现。 -Runikraft还提供了一些工具模块,比如调试工具\texttt{rkdebug}、命令行参数分析工具\texttt{rkparam}。 +\begin{table}[!hbt] +\caption{Unikraft与Runikraft的组件的对应} +\centering +\begin{tabular}{lll} +\hline +Unikraft&Runikraft&功能\\\hline +\texttt{ukalloc}&\texttt{rkalloc}&分配器API\\ +\texttt{ukallocbuddy}&\texttt{rkallocbuddy}&分配器实现——伙伴分配器\\ +\texttt{ukargparse}&\texttt{rkargparse}&kernel arguments parsing\\ +\texttt{ukboot}&\texttt{rkboot}&初始化系统\\ +(无)&\texttt{rkgpudev}&显示设备\\ +\texttt{uklock}&\texttt{rklock}&互斥锁、信号量\\ +\texttt{ukmpi}&\texttt{rkmpi}&基于信箱的通信\\ +\texttt{ukplat}&\texttt{rkplat}&平台相关代码\\ +\texttt{ukring}&\texttt{rkring}&无锁环形缓冲区\\ +\texttt{uksched}&\texttt{rksched}&调度器API\\ +\texttt{ukschedcoop}&\texttt{rkschedcoop}&调度器实现——FCFS\\ +(无)&\texttt{rkschedpreem}&调度器实现——RR\\ +\texttt{ukswrand}&\texttt{rkswrand}&密码学安全的随机数生成器\\ +\texttt{uktimeconv}&\texttt{rktimeconv}&时间格式转换\\ +\hline +\end{tabular} +\end{table} -在 Unikraft 中,文件系统 API vfscore 是 POSIX 兼容层的一部分,我们沿用了它的设 -计,将 \texttt{vfscore} 放在了兼容层。Runikraft计划支持RAM上的临时文件系统和Plan 9 OS的9fps。 +\begin{figure}[!htb] +\centering +\includegraphics[width=\linewidth]{../assets/crates-dependency.pdf} +\caption{Runikraft的crates的依赖关系} +\end{figure} -位于最顶层的是语言标准库,这一层可以帮助Runikraft支持多种语言。 -虽然语言标准库层被画在了兼容层之上,但它其实是直接用OS元件层的APIs实现的,这能避免分层系统 -的低效。 -我们只打算实现Rust标准库和C标准库的部分内容。 +\section{功能组件}\sectionauthor{张子辰} +本节介绍Runikraft的重要crates的设计思想,不展示具体的APIs,也不介绍容易实现的次要组件。 +\subsection[\texttt{rkplat}]{\texttt{rkplat}:平台相关代码} +\texttt{rkplat}主要包含\texttt{console}、\texttt{irq}、 +\texttt{lcpu}、\texttt{thread}和\texttt{time}等模块。 -\subsection{改写方法} +\texttt{console}提供最基本的控制台输入输出,包括Unikraft风格的\texttt{cink}、\texttt{coutk}和Rust +风格的\texttt{print}、\texttt{println}。 -\subsubsection{类型转换} -下面是通常情况下Unikraft使用的C类型对应的Rust类型。 -越先出现的规则优先级越高,即如果一个C类型与多条规则匹配,则按最先出现的规则转换 -成Rust类型,但是如果转换后的类型会引起所有权问题,则放弃这条转换规则,按下一条匹配的 -规则转换。 -\begin{longtable}{|l|l|} -\caption{}\\ -\hline -C类型&Rust类型\\\hline -\endfirsthead -\hline -C类型&Rust类型\\\hline -\endhead -\texttt{long}&\texttt{isize}\\\hline -\texttt{unsigned long}&\texttt{usize}\\\hline -\texttt{\_\_sz}&\texttt{usize}\\\hline -\texttt{\_\_nsec}&\texttt{core::time::Duration}\\\hline -代表错误信息的\texttt{int}&\texttt{Result<(),i32>}\\\hline -取1或0的\texttt{int}&\texttt{bool}\\\hline -\texttt{T[N]}&\texttt{[T;N]}\\\hline -\texttt{void \_\_noreturn}&\texttt{!}\\\hline -\texttt{void*}&(泛型函数)\texttt{*mut T}\\\hline -\texttt{void*}&(泛型函数)\texttt{*const T}\\\hline -\texttt{void*}&\texttt{*const u8}\\\hline -\texttt{void*}&\texttt{*mut u8}\\\hline -\texttt{const char*}&\texttt{\&str}\\\hline -传递结果的\texttt{T*},返回\texttt{int}&\texttt{Result}或\texttt{Optional}\\\hline -传递结果的\texttt{T*},返回\texttt{void}&\texttt{T}\\\hline -\texttt{T*}+\hspace{0cm}长度&\texttt{\&mut [T]}\\\hline -\texttt{const T*}+\hspace{0cm}长度&\texttt{\&[T]}\\\hline -\texttt{T*}&\texttt{\&mut T}\\\hline -\texttt{T*}&\texttt{*mut T}\\\hline -\texttt{const T*}&\texttt{\&T}\\\hline -\texttt{const T*}&\texttt{*const T}\\\hline -整型&长度和符号相同的整型\\\hline -浮点类型&长度相同的浮点类型\\\hline -\end{longtable} +\texttt{irq}是中断子系统。在异常/中断发生后,\texttt{\_\_rkplat\_int\_except\_entry}将 +判断发生的是异常还是中断。对于异常,所有寄存器都会被保存到一段相对不容易被破坏的内存区域,然后 +\texttt{\_\_rkplat\_exception\_handle}会输出异常信息。对于中断,只有\texttt{t*}和\texttt{a*} +寄存器会被保存到当前在栈上,然后控制权会被交给中断子系统的\texttt{\_\_rkplat\_irq\_handle}函数, +后者会调用有平台无关代码事先注册的中断响应函数。 -Unikraft用到了少量双重指针。一般地,C的双重指针\texttt{T**}在C++的含义是\texttt{T \&*}, -所以它对应Rust的\texttt{\&mut *mut T}。但是这种写法比较丑陋,所以我们会尽量通过改写 -函数传参的方式,避免使用指针的引用。 +\texttt{lcpu} 处理逻辑处理器,包括但不限于关闭/开启中断,获取当前的逻辑处理器的编号,唤醒另一个 +逻辑处理器,读取栈指针。 -\subsubsection{面向对象式函数} -所谓“面向对象式函数”就是\ \ref{ssubsec:code-style}\ 提到的第一个参数是 -与库同名的结构体的指针的函数。在Rust中,可以把这些函数放在\texttt{\textbf{impl}}块 -中,第一个参数是\texttt{\textbf{self}}。 +\texttt{thread}提供切换和启动线程的原语\texttt{start(A)}和\texttt{switch(A,B)}。\texttt{start}将执行流 +转到一个从未运行过的线程A。\texttt{switch}将执行流从线程A切换到另外一个调用了\texttt{switch} +或从未执行过的线程B。1. 如果线程B从未执行过,那么执行流将转到B的入口;2. 如果线程B先前调用 +了\texttt{switch},那么执行流将转到B线程的\texttt{switch}函数后的指令,这意味着, +B线程会观察到\texttt{switch}函数返回。无论哪种情况,线程A会被挂起到某一个线程调用\texttt{switch}切换到A。 +\texttt{switch}函数实现了“自主”的线程切换,相比传统的OSes中的需要supervisor协助的线程切换,这种模式减少了 +一次上下文切换,因此更快速地完成线程的切换。%在介绍\texttt{rksched}的设计时,将详细介绍Runikraft的线程模型。 -如\texttt{ukring}的入队操作 -\begin{lstlisting}[language=C] -static __inline int -uk_ring_enqueue(struct uk_ring *br, void *buf) -{ - /* 实现略 */ -} +\texttt{rktime}负责查询时间和设置定时器。 + +总体来说,\texttt{rkplat}提取了多种ISAs的共性,隐藏了不同的ISAs之间的差异,使 Runikraft的 +其他crates的代码能不依赖特定的ISA。 +\subsection[\texttt{rkalloc}]{\texttt{rkalloc}:分配器} +分配器的API位于\texttt{rkalloc} crate,实现之一是\texttt{rkallocbuddy}。 + +\texttt{rkalloc} API兼顾了Rust风格的分配器和C风格的分配器。首先,\texttt{Alloc} trait +的接口与\texttt{alloc::alloc::GlobalAlloc}很接近,在解分配时,必须提供分配时使用的\texttt{size} +和\texttt{align},这能降低实现分配器的难度。其次,\texttt{AllocExt} trait提供与C的\texttt{free} +兼容的解分配函数\texttt{dealloc\_ext(ptr: *mut u8)},在解分配时,只需要提供一个指针。 + +\texttt{rkallocbuddy}是伙伴分配器,它实现了\texttt{AllocExt} trait。尽管我们可以轻松地 +在Internet上找到多种伙伴分配器的C实现\cite{bib:buddy-C-1}\cite{bib:buddy-C-2}\cite{bib:buddy-C-3}和Rust实现\cite{bib:buddy-Rust-1}\cite{bib:buddy-Rust-2}\cite{bib:buddy-Rust-3}, +但是它们都不完全符合Runikraft的要求。 +这是因为Rust的实现以实现\texttt{GlobalAlloc} trait为目标,不支持\texttt{AllocExt}; +一些C的实现虽然非常精彩,但是需要改写成Rust才能使用。所以我们选择自行实现伙伴分配器。 + +首先考虑大小为$2^n$的被管理的内存区域。 + +最小的块的长度是$2^4$ bytes,能容纳2个指针;最大的块的大小为$2^{48}$ bytes,是AMD64支持的最大内存容量。 +用双向链表维护空闲块,链表的结点\texttt{Node}储存在它对应的内存区域的开头。 +用树状bitset维护所有的内存区块的分配情况(元数据): +\begin{itemize} +\item \texttt{[0]}是根结点; +\item \texttt{[i*2+1]}是\texttt{[i]}的左孩子,它对应的内存区域是\texttt{i}二分后的前半段; +\item \texttt{[i*2+2]}是\texttt{[i]}的右孩子,它对应的内存区域是\texttt{i}二分后的后半段; +\item 顺序(order)等于\texttt{k}的结点在这个bitset的索引范围是\texttt{[$2^{n-k}-1$:$2^{n-k+1}-2$]}; +\item 顺序等于\texttt{k}的结点共有$2^{n-k}$个,每个的大小等于$2^k$; +\item 整个bitset的大小等于$2^{n+1}\div16\div8=2^{n-6}$ bytes +\item \texttt{bitset[i]} = 0 表示结点i: + \begin{itemize} + \item i 没有被分配, + \item i 的父结点已经被分配; + \end{itemize} +\item \texttt{bitset[i]} = 1 表示结点i: + \begin{itemize} + \item i 被分配, + \item i 被二分成了两个子结点。 + \end{itemize} +\end{itemize} +初始时,元数据的所有位都是0,如果一块内存i被分配,则i的孩子一定全是0,i和i的祖先一定全是1。 + +元数据被储存在被管理的内存区块的末尾,它们需要占用$2^{n-10}$个最小的块。 + +当内存区域的大小size不是2的幂时,记$n=\lceil\log_2(\mathrm{size})\rceil$, +则可以将内存区域视为大小为$2^n$但末尾的一些 +结点已经被分配的内存区域。 + +\subsection[\texttt{rksched}]{\texttt{rksched}:调度器} +调度器的API位于\texttt{rksched} crate, +实现有\texttt{rkschedcoop}、\texttt{rkschedpreem}。 + +在Runikraft中,调度器由\texttt{Thread}、\texttt{Sched}和\texttt{WaitQ}等三个密切 +配合的结构体实现。\texttt{Thread}保存线程的控制块,\texttt{Sched}按照一定的顺序将CPU时间 +分配给线程,\texttt{WaitQ}维护事件的等待队列。 + +\texttt{Thread}的重要成员有栈顶指针、thread local storage指针、状态(detached、ended、runnable)、 +属性(优先级、时间片长度、截止时间等)、占用的资源上限(CPU时间、内存空间等)和指向控制线程的调度器的 +裸指针。直接使用\texttt{Thread}既不方便也不安全:首先,需要分配线程栈空间(stack)和线程本地存储空间(tls)。 +接着需对tls的低地址调用\texttt{init}(\texttt{unsafe\{*(tls as *mut Thread).init(...)\}}),初始化控制块, +\texttt{init}的tls参数应等于\texttt{tls+size\_of::}。 +之后用\texttt{add\_thread}把线程加入调度器。调度器将周期性地执行线程,直到线程执行完毕或被killed,这时 +必须调用\texttt{exit}。\texttt{exit}函数会invoke调度器,切换到另一个没有退出的线程。然后,调度器会 +调用\texttt{finish},唤醒等待这个线程结束的线程。如果线程是detached,调度器将释放线程的栈空间和thread local +storage,对于joinable线程,创建线程的线程负责释放空间。考虑到\texttt{Thread}的API非常不易用, +Runikraft提供了\texttt{create\_thread}和\texttt{destroy\_thread}两个能够安全地创建和销毁线程的函数。 + +\texttt{Thread}对象自身其实是\texttt{Tailq}的结点,这样可以通过修改指针的方式把线程加入或移出就绪队列。 +不过,\texttt{WaitQ}是Thread的\textit{地址}构成的\texttt{Stailq},而不是\texttt{Thread}自身 +构成的\texttt{Tailq}, +这样做是为了使一个线程能够同时等待多个事件,并且在其中任意一个事件发生时被唤醒。每将线程加入等待队列, +都需要创建\texttt{StailqNode>}对象。为了不使用分配器,这个对象被创建在了栈上, +这意味着,需要将执行栈对象的指针传递出去。这是安全的,因为线程被加入等待队列后,会被立即挂起,栈中的对象 +不会被dropped,当且仅当线程被唤醒,\texttt{StailqNode<...>}对象被移出等待队列后,创建在堆 +上的\texttt{StailqNode<...>}对象才会被dropped。 + +\texttt{Sched}是trait,它的重要公开接口有\texttt{start}、\texttt{yield}、\texttt{add\_thread}、 +\texttt{remove\_thread}、\linebreak\texttt{thread\_blocked}和\texttt{thread\_woken}。在定义\texttt{Sched} +时,我们被Rust的表达能力的缺陷困扰:\texttt{Sched}的\texttt{\_\_set\_next\_sheduler}只适合 +被rkboot调用,\texttt{\_\_workload}只适合被实现Sched trait的结构体调用,可是由于Rust不支持protected +和friend,它们被设置成了公开接口。 + +\texttt{Sched}的重要实现\texttt{Schedcoop}是整个Runikraft中难度最大的部分,因为它需要正确处理 +对称多处理器(SMP)引起的物理层面的数据竞争。\texttt{Schedcoop}的核心函数是\texttt{schedule}, +在不处理SMP时,它的执行流程是 +\begin{lstlisting}[numbers=left,mathescape,language=Rust] +// exit_thread, ready_thread, waiting_thread是Tailq + +current $\leftarrow$ 当前线程 +//放置当前线程 +if current.is_exited() + 把current加入exit_thread +else if current不属于任何一个链表 + 把current加入ready_thread + +//处理退出队列中的线程 +for thread in exit_thread + if thread != current + thread.finish() + if thread.is_detached() + destroy_thread(thread) + +//处理等待队列里的线程 +current_time $\leftarrow$ 当前时间 +sleep_until $\leftarrow$ current_time + 10s +for thread in waiting_thread + if thread.wakeup_time <= current_time + 把thread移出wait_thread + if thread.is_exited() + 把thread加入exit_thread + else + 把thread加入ready_thread + else + sleep_until = min(thread.wakeup_time,sleep_until) + +//切换线程 +if ready_thread非空 + front $\leftarrow$ ready_thread的头结点 + 将front移出ready_thread + if front != current + //rkplat的thread模块的switch原语 + switch(current,front) + return +else + 等待到sleep_until + 转到第5行 \end{lstlisting} -改写为 -\begin{lstlisting}[language=Rust] -impl Ring { - fn enqueue(&mut self, buf: *const T)->Result<(),i32> { - // 实现略 - } -} +它尚且比较简单,不过20\tildechar28行的遍历\texttt{waiting\_thread}的时间复杂度很高,使用 +平衡树可以降低这部分的复杂度。 + +\texttt{schedule}并不是唯一能修改\texttt{ready\_thread}和\texttt{waiting\_thread}的 +函数。\texttt{add\_thread}、\texttt{thread\_blocked}和\texttt{thread\_woken}函数都 +能修改这两个\texttt{Tailq}s。\texttt{thread\_blocked}只会被同步调用,即线程只能阻塞自身, +但是在SMP场景下,\texttt{add\_thread}和\texttt{thread\_woken}会被异步调用,即运行在一个 +硬件线程上的系统线程可以创建和唤醒运行在另外一个硬件线程上的系统线程。自主的线程切换使加锁很困难, +因为在完成调度时,控制权已经到了另外一个线程,在一个线程中加的锁必须在另一个线程中被释放。我们最终 +采用的解决方法是将异步的加入和唤醒请求延迟到\texttt{scheduler}函数执行,异步的\texttt{add\_thread} +和\texttt{thread\_woken}调用只传递操作。在修改后的\texttt{schedule}函数中,在处理了退出队列中的线程 +后,需要依次处理异步加入的线程和异步唤醒的线程。 + +在协作式调度器的基础上,只需要在初始化调度器时注册中断响应函数,并且 +在\texttt{switch}前设置定时器,就可以很轻松地实现抢占式调度器。 + +尽管Unikraft的paper声称自己的API清晰且良定义,但它的线程API却设计得有些混乱。 +Runikraft承袭Unikraft,把调度器的功能分散到了\texttt{Sched}、\texttt{Thread}和\texttt{Waitq} +三个需要密切配合的结构体中。我们不得不谨慎地划分函数的功能边界,这给我们的开发带来了一定的挑战。 + +\subsection[\texttt{rkboot}]{\texttt{rkboot}:系统初始化} +在用户视角下,\texttt{main}函数是程序的入口地址。然而在执行\texttt{main}函数 +前,用户期望设备和系统的元件都已完成初始化而可以直接使用。所以,\texttt{main}不可能是OS的 +入口,在执行\texttt{main}之前,OS一定会执行初始化代码。在Runikraft中,绝大部分初始化由 +\texttt{rkboot}完成,而少数平台相关的初始化由\texttt{rkplat::bootstrap}完成。 + +对于QEMU的\texttt{virt}机器,内核的第一条指令的地址是\texttt{0x80200000},在开始执行内核 +代码时,只有一个硬件线程处于运行状态,而其他的处在挂起状态,\texttt{a0}寄存器保存着当前的硬件线程的ID, +\texttt{a1}寄存器保存着指向展开的设备树(FDT)的指针。此时,栈指针尚未初始化,BSS段也没有被清零。 + +Runikraft开机后执行的第一个函数是\texttt{\_\_runikraft\_start},它清空bss段,初始化异常/中断响应函数, +加载栈指针,然后调用\texttt{\_\_runikraft\_entry\_point},后者初始化硬件线程本地数据,并且把\texttt{a1} +保存到\texttt{DEVICE\_PTR}。至此,平台层的初始化结束。\texttt{\_\_runikraft\_entry\_\linebreak point}把控制 +前交给\texttt{rkboot}前会切换栈,切换前的栈被称为early boot stack,切换后的栈被称为main stack。 +在发生异常时,Runikraft可以在early boot stack上调用异常处理函数。 + +\texttt{rkboot}的入口是\texttt{rkplat\_entry}。它依次初始化全局分配器、 +\texttt{rkplat::irq}、\texttt{rkplat::\linebreak device}、\texttt{rkplat::time}和调度器, +其中分配器和调度器的初始化受\texttt{rkboot}的features的影响,比如在开启\texttt{have\_scheduler} +feature时,rkboot会在每个硬件线程上创建分配器,并且在名为“main”的线程中执行用户的\texttt{main}函数, +如果没有开启这个feature,\texttt{rkboot}将直接调用\texttt{main}。 + +\section{测试系统}\sectionauthor{陈建绿} +测试系统在整个项目中占有重要地位。Runikraft 的模块化设计意味着系统的 +各个功能模块是互不干扰的,每个模块都有自己独立的API。这种模块化设计为测试 +带来极大的便利,我们可以对每个模块进行单独测试,从而可以更快地发现系统设计 +代码中存在的问题并进行修复。 + +我们的测试系统就是基于这种模块化的特性搭建的,我们可以添加任意多的独立的模块测试, +使用命令\texttt{make test}便可以运行所有测试,使用命令\texttt{make TEST\_LIST=} +便可以只运行某个特定的测试,使用起来十分方便。在\texttt{test/}中添加测试程序的步骤如下: + +\begin{enumerate} +\item 创建目录 \texttt{}。 +\item 在该目录下创建 \texttt{Cargo.toml}: + \begin{itemize} + \item package.name = test-。 + \end{itemize} +\item 在\texttt{/src/main.rs}中写测试代码。 +\item 如果你的程序需要特殊的 qemu 命令行参数才能运行,如GPU测试程序需要 + virtio-gpu 设备,创建\texttt{/run\_flags.txt}并把特殊的命令行参数 + 写在此处。 +\end{enumerate} + +\subsection{实现方法} + +最初我们尝试使用 Rust 中提供的集成测试方式进行模块测试\cite{bib:rust-test}, +但stable Rust目前尚不支持在开启\texttt{\#![no\_std]}时的集成测试, +我们只好放弃这种集成测试方式,转而采用其他方式。最终我们采用makefile+sh完成测试。 + +我们在项目根目录下创建了一个名为\texttt{test}的文件夹,来存放各个模块测试。 +按照上面描述的方式添加一个模块测试,其中有\texttt{main.rs}文件,因此便可以按照下面 +的步骤进行运行(\texttt{@testname@}是测试的名称): + +\begin{enumerate} +\item 生成\texttt{@testname@.bin}文件: + \begin{lstlisting} +cd $(TEST_ROOT_DIR)/@testname@ && env RUSTFLAGS="-Clink-arg=-T$(SRC_ROOT_DIR)/linker.ld --cfg __alloc_error_handler --extern __alloc_error_handler=$(MAKE_ROOT_DIR)/liballoc_error_handler.rlib" cargo build --offline +rm $(TEST_ROOT_DIR)/@testname@/Cargo.lock +riscv64-linux-gnu-objcopy --strip-all $(TEST_BUILD_DIR)/test-@testname@ -O binary @testname@.bin + \end{lstlisting} +\item 运行\texttt{@testname@.bin}文件: + \begin{lstlisting} +qemu-system-riscv64 -machine virt -nographic -bios $(MAKE_ROOT_DIR)/opensbi/platform/generic/firmware/fw_jump.bin -kernel @testname@.bin + \end{lstlisting} +\end{enumerate} + +这样就成功执行了一个测试。再利用 shell 脚本不难实现多个模块的测试。 + +\subsection{测试编写} + +在完成测试系统的配置后,我们便可以进行测试的编写了。测试编写的思路大致是: +调用想要测试的模块中的函数,使用\texttt{assert\_eq!}或\texttt{assert!}将 +得到的结果与预期结果进行比较,如果结果一致,说明对应函数编写正确。对模块中的 +每个函数进行测试,这样就完成了对一个模块的测试。 + +最终我们完成了alloc\_buddy0、alloc\_buddy1、global\_alloc0、gpu0、list0、lock0、 +plat\_\linebreak thread\_context0、plat\_time0、rand0、rand1、sched\_coop0、sched\_coop1、 +sched\_preem0、slist0、stailq0、tailq0、timeconv0共17个模块的测试。 + +在测试过程中我们确实找到了一些代码上的问题并进行了修复,测试系统实现得很成功。 + +\section{配置系统}\sectionauthor{陈建绿} +实现配置系统的目的是可以让用户更加方便地按照自己的需求进行某些参数的配置, +比如\texttt{HEAP\_SIZE}、\texttt{STACK\_SIZE}、\texttt{LCPU\_MAXCOUNT}、 +\texttt{PAGE\_SIZE}等, +还可以选择启用或禁用子模块中的features,比如\texttt{rkplat}中的如下所示的features都可以进行选择配置: + +\begin{lstlisting}[language=sh] +[features] + has_smp = [] # 对称多处理器支持 + save_fp = [] # 在线程切换时保存浮点寄存器 + driver_uart = [] # 串口设备驱动 + driver_ns16550 = ["driver_uart"] # ns16550驱动 + driver_virtio = ["volatile", "bitflags"] + driver_virtio_blk = ["driver_virtio"] + driver_virtio_console = ["driver_virtio"] + driver_virtio_gpu = ["driver_virtio"] + driver_virtio_input = ["driver_virtio"] + driver_virtio_net = ["driver_virtio"] + driver_virtio_entropy = ["driver_virtio"] + driver_virtio_all = ["driver_virtio_blk","driver_virtio_console","driver_virtio_gpu","driver_virtio_input","driver_virtio_net","driver_virtio_entropy"] + driver_rtc = [] # 真实时间时钟驱动 + driver_goldfish_rtc = ["driver_rtc"] \end{lstlisting} -\subsubsection{宏} +用户来到项目根目录下,使用\texttt{make menuconfig}便可以开启\texttt{menuconfig}界面进行配置: -用\ \ref{subsec:cond-compile}\ 的方法处理条件编译宏。 -把宏常量转换为静态变量。把仿函数宏转换成内联函数,在必要时使用泛型。 -把用宏实现的模板类型转换成基于泛型的结构体,它们主要是链表这样的容器。 +\begin{figure}[H] +\centering +\includegraphics[width=0.8\linewidth]{../assets/runikraft-menuconfig-1.png} +\caption{} +\label{fig:runikraft-menuconfig-1} +\end{figure} -\subsubsection{编译提示} -Rust的中的\texttt{cold}属性可以充当GCC中的\texttt{\_\_builtin\_expect(\textit{},0)}, -也就是\texttt{unlikely(\textit{})},但是Rust中没有含义是\texttt{likely}的属性。 +配置系统并不是必不可少的,用户可以不使用menuconfig,直接修改\texttt{default\_config.rs}中的非bool +型编译参数,通过标准的cargo的.toml语法开启features。 -GCC的\texttt{\_\_inline}等价于Rust的\texttt{\#[inline]}, -GCC的\texttt{\_\_attribute\_\_((always\_inline))}等价于Rust的\texttt{\#[inline(always)]}, -GCC的\texttt{\_\_attribute\_\_((no\_inline))}等价于Rust的\texttt{\#[inline(never)]}。 +\subsection{实现方法} -一些依赖布局的结构体,如\texttt{\_\_regs}改写成Rust后需要附加\texttt{\#[repr(C)]}属性, -以确保能直接通过偏移量访问结构体成员。 +首先是\texttt{kconfig}的使用,我们参考了Chamberlain\cite{bib:kconfig}的使用方式, +在\texttt{support/scripts}中复用他的\texttt{build.Makefile}和\texttt{objects.Makefile}, +并引入 submodule https://github.com/linux-kdevops/kconfig到\texttt{kconfig}文件夹, +然后在项目根目录下按照 kconfig 的语法编写对应的\texttt{Kconfig}文件: -\part{设计报告} -\section{总体设计} +\begin{lstlisting} +mainmenu "Runikraft/0.1.0 Configuration" + +menu "Memory and CPU Configuration" + config HEAP_SIZE + int "Heap size(MiB)" + default 16 + ... + menu "Limit Configuration" + ... + endmenu + + ... +endmenu + +menu "Library Configuration" + source "lib/rkplat/config" + source "lib/rkboot/config" +endmenu + +... +endmenu +\end{lstlisting} -\section{功能组件} -\subsection[\texttt{rkplat}和\texttt{rkboot}]{\texttt{rkplat}和\texttt{rkboot}:平台支持和系统初始化} -\subsection[\texttt{rkalloc}]{\texttt{rkalloc}:分配器} -\subsection[\texttt{rksched}]{\texttt{rksched}:调度器} -\subsection[\texttt{rklock}、\texttt{rkring}和\texttt{rkmpi}]{\texttt{rklock}、\texttt{rkring}和\texttt{rkmpi}:同步和互斥工具} -\subsection[\texttt{rkswrand}]{\texttt{rkswrand}:随机数生成器} -\subsection[\texttt{rktimeconv}]{\texttt{rktimeconv}:时间格式转换} +其中用\texttt{source "lib/@libname@/config"}引入相应模块中对应的配置菜单。 +比如子模块\texttt{rkplat}的配置菜单如下图所示: + +\begin{figure}[H] +\centering +\includegraphics[width=0.7\linewidth]{../assets/runikraft-menuconfig-2.png} +\caption{} +\label{fig:runikraft-menuconfig-2} +\end{figure} + +这样我们最终的配置结果就会保存到项目根目录中的\texttt{.config}文件中。 + +在得到\texttt{.config}文件之后,接下来便要解决如何根据\texttt{.config}中的内容进行条件编译。 +\texttt{menuconfig}生成的\texttt{.config}文件大致是下面这样: + +\begin{lstlisting}[language={[gnu]make}] +# +# Memory and CPU Configuration +# +CONFIG_HEAP_SIZE=16 +... + +# +# Limit Configuration +# +CONFIG_MEMORY_SIZE=-1 +CONFIG_OPEN_FILES=1024 +... +# end of Limit Configuration +... +# end of Memory and CPU Configuration +... +\end{lstlisting} + +这并不方便处理,因此我们只好采用一些不那么通用的方法来完成文件的读取和配置文件的输出。 + +上面已经提到,Runikraft 中是有两部分配置内容的,一部分是一些全局变量的数值, +另一部分是子模块中features的启用或禁用。 + +全局变量的默认数值存放在一个名为\texttt{default\_config.rs}的文件中, +我们需要根据\linebreak\texttt{.config}的内容来生成一个和\texttt{default\_config.rs} +的格式一样的文件\texttt{\$\{CONFIG\_DIR\}/\linebreak config.rs},然后再执行\texttt{cargo build}时传入环境变量\texttt{RUNIKRAFT\_CONFIG\_FILE=\linebreak"\$\{CONFIG\_DIR\}/config.rs"}, +这样完成了全局变量数值的个性化配置。 -\section{测试系统} +features的启用使用的是\texttt{--features @featurename@}加在\texttt{cargo build}的 +后面的方式(不加在后面默认禁用),于是我们需要根据\texttt{.config}的内容来生成类似于下面这样的一个字符串: -\section{配置系统} +\begin{lstlisting} +--features rkplat/has_smp --features rkplat/driver_uart --features rkplat/driver_ns16550 --features rkplat/driver_virtio --features rkplat/driver_virtio_blk --features rkplat/driver_virtio_console --features rkplat/driver_virtio_gpu --features rkplat/driver_virtio_input --features rkplat/driver_virtio_net --features rkplat/driver_rtc --features rkplat/driver_goldfish_rtc --features rkboot/have_scheduler --features rkboot/sched_coop +\end{lstlisting} + +这样就完成了子模块features的启用或禁用。 + +而上面的两项转换工作我们是用 C++ 程序来实现的。上面已经提到,menuconfig生成的\texttt{.config}文件并不方便处理, +因此我们的 C++ 程序采用的是不那么通用的方式来完成转换的, +详细代码位于\texttt{support/handle\_config.cpp}中,这里不再展示。 + +\section{总结} +经过两个月的不懈努力,我们最终实现了支持对称多处理器的抢占式的 RR 调度器、 +密码学安全的随机数生成器、显示设备驱动、信号量原语、基于信箱的IPC等crates, +我们支持的设备有 ns16550、goldfish-rtc、virtio-blk、virtio-console、 +virtio-rng、virtio-gpu、virtio-input、virtio-net。 +我们还实现了基于 kconfig 的功能定制系统。尽管我们实现的功能与最初的设计仍有差距,但 +已经足以用Runikraft写出有实用价值的程序了。为了展示 Runikraft, +我们写了基于 Runikraft 的数独程序(图\ \ref{fig:sudoku}),它拥有简单的图形界面,使用了 \texttt{rkallocbuddy}、\texttt{rkschedpreem}、\texttt{rklock}、\texttt{rkgpudev}等 crates, +能够比较全面地展示 Runikraft 的功能。在使用 release 配置构建时,数独程序的镜像大小只有 100KiB,由此 +可见,引入图形界面不会显著增大镜像的大小,这对拓宽Unikernels的应用范围有积极意义。 + +\begin{figure}[tbh!] +\centering +\includegraphics[width=0.6\linewidth]{../assets/sudoku} +\caption{数独程序} +\label{fig:sudoku} +\end{figure} + + +Runikraft采用了全新的方式组织系统,系统由独立的crates组成,每个crate都可以独立发布和安装。 +这对目前的自由操作系统的开发和发布有借鉴意义。 + +在Rust 1.59之前,内联汇编不能在stable channel的编译器中使用,使得用stable Rust写操作系统变得不可能。 +在Rust 1.59发布后才开始开发的Runikraft是我们已知的唯一的 +用stable Rust开发的操作系统。Runikraft的成功开发意味着用Rust写操作系统不再是试验性功能, +标志着Rust已经能胜任操作系统的开发。 + +\section{相关工作} + +\subsection{安全容器}\sectionauthor{蓝俊玮} +《项目背景》中已指出,容器是云计算领域的常用的隔离手段,而由于容器并不是沙盒, +它提供的隔离能力不足以运行潜在的恶意代码,安全容器应运而生。从设计目标看,安全 +容器的隔离能力与unikernels等同。安全容器的目标是能够直接运行ELF二进制文件, +而unikernels通常要求从源代码重新编译。 + +Kata Containers和gVisor都使用Go语言实现。 + +Kata Containers的实现思路是轻量级虚拟机,它是容器向uniKernel的过渡。它的主要特点是\cite{bib:kata}: +\begin{description} +\item[安全] Runs in a dedicated kernel, providing isolation of network, +I/O and memory and can utilize hardware-enforced isolation with virtualization VT extensions. +\item[兼容] Supports industry standards including OCI container format, +Kubernetes CRI interface, as well as legacy virtualization technologies. +\item[高效] Delivers consistent performance as standard Linux containers; +increased isolation without the performance tax of standard virtual machines. +\item[简洁] Eliminates the requirement for nesting containers inside full +blown virtual machines; standard interfaces make it easy to plug in and get started. +\end{description} + +gVisor的实现思路是半虚拟化操作系统,它在用户空间运行,以拦截系统调用的方式为 +应用程序提供服务。它与传统容器的关键区别是没有简单地将应用程序的系统调用重定向 +给宿主机内核,而是实现了大多数内核原语,并基于这些原语实现系统调用。gVisor与unikernel的 +区别是gVisor没有模拟硬件,而只是模拟了一个Linux内核; +unikernel本身是一个运行在虚拟硬件上的操作系统。 +gVisor的特点:\cite{bib:gvisor} +\begin{description} +\item[容器原生安全] By providing each container with its own application kernel, gVisor limits the attack surface of the host. This protection does not limit functionality: gVisor runs unmodified binaries and integrates with container orchestration systems, such as Docker and Kubernetes, and supports features such as volumes and sidecars. + +\item[资源高效] Containers are efficient because workloads of different shapes and sizes can be packed together by sharing host resources. gVisor uses host-native abstractions, such as threads and memory mappings, to co-operate with the host and enable the same resource model as native containers. + +\item[跨平台] Modern infrastructure spans multiple cloud services and data centers, often with a mix of managed services and virtualized or traditional servers. The pluggable platform architecture of gVisor allows it to run anywhere, enabling consistent security policies across multiple environments without having to rearchitect your infrastructure. +\end{description} + +\subsection{嵌入式系统}\sectionauthor{陈建绿} +大部分unikernels只打算在虚拟机上运行,但是IncludeOS和Rumprun支持在嵌入式设备上运行, +往年的ridiculous-includeos小组也做过将unikernel移植到嵌入式设备的研究。 +可见嵌入式设备也是unikernel潜在的应用领域。嵌入式系统的类型丰富多样,这里着重介绍物联网(IoT)系统。 + +目前,物联网操作系统主要分为两大类,一是由传统的嵌入式实时操作系统(RTOS)发展而来, +比如FreeRTOS、LiteOS、RT-Thread;二是由互联网公司的云平台延伸而来, +基于传统操作系统进行“剪裁”和定制的IoT OS,比如Ali OS Things、TencentOS tiny、Win10 IOT。\cite{bib:iot-sys} + +\href{https://github.com/OpenAtomFoundation/TencentOS-tiny}{TencentOS Tiny} 是腾讯 +面向物联网领域开发的实时操作系统,具有低功耗,低资源占用,模块化,安全可靠等特点, +可有效提升物联网终端产品开发效率。 + +TencentOS tiny 提供精简的 RTOS 内核,内核组件可裁剪可配置, +可快速移植到多种主流 MCU (如 STM32 全系列)及模组芯片上。而且, +基于 RTOS 内核提供了丰富的物联网组件,内部集成主流物联网协议栈 +(如CoAP/MQTT/TLS/DTLS/LoRaWAN/NB-IoT等),可助力物联网终端设备及 +业务快速接入腾讯云物联网平台。 + +\href{https://github.com/ms-rtos}{MS-RTOS} (Micro Safe RTOS) 是翼辉信息全新设计的一款面向未来的 +安全实时操作系统,其最大的特点是开创性地在没有 MMU 和资源受限的 MCU(如Cortex-M3)上也能支持多进程与动态装载技术,使得应用与系统能分离开发、独立升级; +MS-RTOS 支持内核空间内存保护(应用程序通过 syscall 访问内核), +使得内核有着非常高的安全性。MS-RTOS 在提供足够丰富功能的同时,保持了高效简洁的实现, +对 ROM、RAM 消耗极低,特别适用于对硬件成本敏感、安全性要求特别高的产品。\cite{bib:ms-rtos} + +\begin{description} +\item[多进程] 允许运行多个进程,进程用户代码工作在 CPU 用户态, +通过系统调用(syscall)访问内核资源,利用 MPU 实现进程地址空间相互隔离。 +\item[动态装载] 驱动与应用程序分离开发,应用与系统独立升级, +应用程序直接在 FLASH 中运行(无需加载到 RAM 执行,节约 RAM,运行速度更快)。 +\item[内核安全] 进程用户代码工作在 CPU 用户态,通过系统调用 +(syscall)进入内核, 保护内核不被进程破坏,利用 MPU 做到进程地址空间相互隔离, +进程影响范围最小化,掉电安全文件系统。 +\end{description} +\subsection{用Rust编写的操作系统} +Rust语言的目标之一就是取代C语言,成为用于系统开发的底层语言。目前已有大量用Rust开发的操作系统:\cite{bib:rust-os-comparison} + +\begin{longtable}{|>{\bfseries}l|p{0.11\linewidth}|p{0.02\linewidth}|p{0.02\linewidth}|p{0.08\linewidth}|p{0.06\linewidth}|p{0.02\linewidth}|p{0.02\linewidth}|p{0.05\linewidth}|p{0.08\linewidth}|p{0.08\linewidth}|} +\caption{用Rust实现的操作系统比较}\\ +\hline +名称&\textbf{架构}&\textbf{纯} \rotatebox[origin=c]{-90}{\textbf{Rust}}&\textbf{活跃}&\textbf{内核架构}&\textbf{目标}&\textbf{用户态}&\rotatebox{-90}{\textbf{GUI}}&\textbf{贡献者数}&\textbf{文件系统}&\textbf{许可}\\\hline +\endfirsthead +\hline +名称&\textbf{架构}&\textbf{纯} \rotatebox[origin=c]{-90}{\textbf{Rust}}&\textbf{活跃}&\textbf{内核架构}&\textbf{目标}&\textbf{用户态}&\rotatebox{-90}{\textbf{GUI}}&\textbf{贡献者数}&\textbf{文件系统}&\textbf{许可}\\\hline +\endhead +redox&x86-32\newline x86-64&是&是&微内核&通用&是&是&50&ZFS\newline RedoxFS&MIT\\\hline +Theseus OS&x86-64\newline ARM WIP&是&是&Safe-language SAS/SPL OS&通用+嵌入式&&是&25&Custom\newline FAT32&MIT\\\hline +Tock&Cortex M&&是&&&&否&40&&APL 2/\newline MIT\\\hline +intermezzOS&x86-64&否&是&?&PoC&否&否&18&无&APL 2/\newline MIT\\\hline +RustOS&x86-32&?&是&无&PoC&否&否&10&无&APL 2/\newline MIT\\\hline +rustboot&x86-32&?&否&无&PoC&否&否&8&无&MIT\\\hline +\end{longtable} + +其中的Redox是一款功能完整的类UNIX操作系统,而不只是操作系统内核,它包含C标准库、 +窗口管理器、浏览器、文本编辑器、图像查看器、终端模拟器等众多软件包。 \section*{许可协议}\markboth{许可协议} 本文档以知识共享署名 4.0 国际 (CC BY 4.0)许可证发布。 @@ -2046,6 +2282,11 @@ \section*{许可协议}\markboth{许可协议} \begin{thebibliography}{99} +\bibitem{bib:feasibility-7} Steve Klabink. \textit{The Rust Programming Language}[M]. The Rust Community, 2019. +\bibitem{bib:feasibility-3} 张汉东. \textit{故事:Rust在企业领域的应用}[Z/OL]. 知乎专栏, 2019 (20190404) [2022-07-09] \url{https://web.archive.org/web/20220709104447/https://zhuanlan.zhihu.com/p/61410107} +\bibitem{bib:feasibility-0} \textit{About RISC-V}[Z/OL]. RISC-V International, 2021. \url{https://riscv.org/about/} +\bibitem{bib:feasibility-2} admin. \textit{RISC-V开源指令集架构简介与应用前景分析}[Z/OL]. ScenSmart一站式智能制造平台, 2019 (20191102) [2022-04-15]. \url{http://www.scensmart.com/news/the-introduction-of-risc-v-and-application-prospect-analysis/} + \bibitem{bib:os-concept} Abraham Silberschatz, Peter B. Galvin and Greg Gagne. \textit{操作系统概念}[M]. 郑扣根, 唐杰, 李善平\ 译. 北京: 机械工业出版社, 2021: 54-58. @@ -2065,8 +2306,6 @@ \section*{许可协议}\markboth{许可协议} }[R/OL]. Version 1.0. NCC Group, 2019: 4-10 [2022-03-27]. \url{https://research.nccgroup.com/wp-content/uploads/2020/07/ncc_group-assessing_unikernel_security.pdf} -\bibitem{bib:1-rust-lang} \textit{Rust Programming Language}[G/OL]. [2022-03-26]. \url{https://web.archive.org/web/20220326232949/https://www.rust-lang.org/} -\bibitem{bib:2-why-rust} @程序师视野. \textit{我们为什么要选择小众语言 Rust 来开发软件?}[Z/OL]. 程序师. [2017-06-26]. \url{https://web.archive.org/web/20170626061304/https://www.techug.com/post/why-we-choose-rust-to-dev.html} \bibitem{bib:3-why-rust-pop} Jake Goulding. \textit{What is Rust and why is it so popular?}[Z/OL]. Stack Overflow Blog. 2020 (20200120) [2022-03-24]. \url{https://web.archive.org/web/20220324021421/https://stackoverflow.blog/2020/01/20/what-is-rust-and-why-is-it-so-popular/} \bibitem{bib:4-rust-go-cmp} CharyGao. \textit{也许是最客观、全面的比较 Rust 与 Go:都想把 Rust 也学一下}[Z/OL]. 博客园. 2020 (20201207) [2022-03-29]. \url{https://web.archive.org/web/20220329090604/https://www.cnblogs.com/Chary/p/14097609.html} @@ -2076,7 +2315,6 @@ \section*{许可协议}\markboth{许可协议} \bibitem{bib:7-rust-by-num} Pavan Belagatti.[\textit{Rust by the Numbers: The Rust Programming Language in 2021}[Z/OL]. The New Stack. 2021 (20210512) [2022-03-25]. \url{https://thenewstack.io/rust-by-the-numbers-the-rust-programming-language-in-2021/} \bibitem{bib:8-rust-compatibility} \textit{Rust By Example}[M/OL]. Compatibility [2022-03-26]. \url{https://web.archive.org/web/20220326130141/https://doc.rust-lang.org/rust-by-example/compatibility.html} -\bibitem{bib:risc-v-manual} David Patterson and Andrew Waterman. \textit{RISC-V 手册: 一本开源指令集的指南}[S]. 勾凌睿, 黄成, 刘志刚译. 2018: 13-21 \bibitem{bib:riscv-support} Jeff Tranter. \textit{What is RISC-V and Why is it Important?}[Z/OL]. ICS. 2021 (20210512) [2022-03-27]. \url{https://www.ics.com/blog/what-risc-v-and-why-it-important} \bibitem{bib:riscv-gcc} \textit{Using the GNU Compilers Collections (GCC)}[G/OL]. 2021: RISC-V Options [2022-03-27]. \url{https://gcc.gnu.org/onlinedocs/gcc-11.2.0/gcc/RISC-V-Options.html#RISC-V-Options} \bibitem{bib:riscv-llvm} \textit{Clang command line argument reference: Clang 15.0.0git documentation}[G/OL]. 2022: RISCV [2022-03-27] \url{https://clang.llvm.org/docs/ClangCommandLineReference.html#riscv} @@ -2136,26 +2374,37 @@ \section*{许可协议}\markboth{许可协议} \bibitem{bib:gvisor} \textit{gVisor}[Z/OL]. [2022-04-02]. \url{https://web.archive.org/web/20220402043121/https://gvisor.dev/} +\bibitem{bib:feasibility-1} Paolo Bonzini. \textit{Main Page}[G/OL]. QEMU Wiki, 2020 (20200609) [2022-04-15]. \url{https://wiki.qemu.org/index.php?title=Main_Page&oldid=9546} +\bibitem{bib:feasibility-5} RISC-V Platform Specification Task Group. \textit{RISC-V Supervisor Binary Interface Specification}[S/OL]. GitHub, 2022 (20220322) [2022-04-15]. \url{https://github.com/riscv-non-isa/riscv-sbi-doc/releases/tag/v1.0.0} +\bibitem{bib:feasibility-6} \textit{OpenSBI Version 1.0}[CP/OL]. GitHub, 2021 (20211224) [2022-04-15]. \url{https://github.com/riscv-software-src/opensbi/releases/tag/v1.0} +\bibitem{bib:feasibility-8} 肖猛. \textit{图解 Rust 所有权与生命周期}[M/OL]. 高宪凤. Rust精选. 2021 [2022-04-15]. \url{https://rustmagazine.github.io/rust_magazine_2021/chapter_1/rust_ownership.html} +\bibitem{bib:feasibility-9} hardsinging. \textit{Rust线程安全编程分析}[Z/OL]. 知乎专栏, 2022 (20220312) [2022-07-09]. \url{https://web.archive.org/web/20220709105413/https://zhuanlan.zhihu.com/p/138394529} +\bibitem{bib:feasibility-a} 尹天宇. \textit{RISC-V特权等级与Linux内核的启动}[Z/OL]. 知乎专栏, 2020 (20200823) [2022-07-09]. \url{https://web.archive.org/web/20220709105606/https://zhuanlan.zhihu.com/p/164394603} +\bibitem{bib:feasibility-b} 辣椒油li. \textit{RISC-V基本介绍}[Z/OL]. CSDN, 2022 (20220122) [2022-03-29]. \url{https://web.archive.org/web/20220329183308/https://blog.csdn.net/lijianyi0219/article/details/122634356} +\bibitem{bib:feasibility-c} orangeQWJ. \textit{RISC-V 特权指令结构}[Z/OL]. 博客园, 2022 (20220219) [2022-07-09]. \url{https://web.archive.org/web/20220709110346/https://www.cnblogs.com/orangeQWJ/p/15912780.html} +\bibitem{bib:feasibility-d} shawn. \textit{闲聊RISC-V虚拟化(1)-CPU虚拟化}[Z/OL]. 知乎专栏, 2021 (20210908) [2022-07-09]. \url{https://web.archive.org/web/20220709110755/https://zhuanlan.zhihu.com/p/408197895} + +\bibitem{bib:2-why-rust} @程序师视野. \textit{我们为什么要选择小众语言 Rust 来开发软件?}[Z/OL]. 程序师. [2017-06-26]. \url{https://web.archive.org/web/20170626061304/https://www.techug.com/post/why-we-choose-rust-to-dev.html} +\bibitem{bib:1-rust-lang} \textit{Rust Programming Language}[G/OL]. [2022-03-26]. \url{https://web.archive.org/web/20220326232949/https://www.rust-lang.org/} +\bibitem{bib:risc-v-manual} David Patterson and Andrew Waterman. \textit{RISC-V 手册: 一本开源指令集的指南}[S]. 勾凌睿, 黄成, 刘志刚译. 2018: 13-21 + +\bibitem{bib:feasibility-e} 樊金昊, 左顺, 宁雨亭, 黄业琦, 张俸铭 and 雷思琦. \textit{可行性报告}[R/OL]. GitHub, 2019 (20190409) [2022-04-15]. \url{https://github.com/OSH-2019/x-rust-freertos/blob/2182e559cf5dedb68d0bf028f21298421cbedbb0/docs/feasibility.md} +\bibitem{bib:qemu-virt} \textit{`virt' Generic Virtual Platform (virt)}[G/OL]. QEMU Documentaion, [2022-07-09]. \url{https://web.archive.org/web/20220709110415/https://www.qemu.org/docs/master/system/riscv/virt.html} +\bibitem{bib:qemu7} Marius Nestor. \textit{QEMU 7.0 Released with KVM Support for RISC-V, ARM and OpenRISC Improvements}[N/OL]. 9TO5LINUX, 2022 (20220419) [2022-04-20]. \url{https://web.archive.org/web/20220420092003/https://9to5linux.com/qemu-7-0-released-with-kvm-support-for-risc-v-arm-and-openrisc-improvements} +\bibitem{bib:buddy-C-1} Evan Wallace. \textit{Buddy Memory Allocator}[CP/OL]. GitHub, 2018 (20180212) [2022-07-08]. \url{https://github.com/evanw/buddy-malloc/tree/254531aa101806ee665166920bd4c70e88730eb1} +\bibitem{bib:buddy-C-2} Stanislav Paskalev and Yun Hsiao Wu. \textit{buddy\_alloc}[CP/OL]. GitHub, 2022 (20220102) [2022-07-08]. \url{https://github.com/spaskalev/buddy_alloc/tree/6d6581605ae1254db7cdbc5ee2140d35f0bbac7b} +\bibitem{bib:buddy-C-3} Jinzhou Zhang. \textit{buddy-system}[CP/OL]. GitHub, 2013 (20131211) [2022-07-08]. \url{https://github.com/lotabout/buddy-system/tree/21b9a8edce16ada9622e53c12d4a2a23b92f5749} +\bibitem{bib:buddy-Rust-1} jiegec, Vinay Chandra, Runji Wang, Luo Jia, Richard Chien and +Tom Dohrmann. \textit{buddy\_system\_allocator}[CP/OL]. GitHub, 2022 (20220605) [2022-07-08]. \url{https://github.com/rcore-os/buddy_system_allocator/tree/72c01c53eca8fac39fde29842523ec9d3d965929} +\bibitem{bib:buddy-Rust-2} ARaspiK. \textit{Buddies: A low-level buddy allocator}[CP/OL]. GitHub, 2019 (20190514) [2022-07-08]. \url{https://github.com/araspik/buddies/tree/f607d4c015c0c2bd2ab069a7413045ffdd440b39} +\bibitem{bib:buddy-Rust-3} Eric Kidd and Jethro Beekman. \textit{\texttt{alloc\_buddy\_simple}: A simple ``buddy allocator'' for bare-metal Rust}[CP/OL]. 2016 (20160919) [2022-07-08]. \url{https://github.com/emk/toyos-rs/tree/b29fda8cab38ee51945bd73174ece15481409941/crates/alloc_buddy_simple} +\bibitem{bib:rust-test} 繁星点点尽在你的指尖. \textit{单元测试、集成测试}[G/OL]. +Rust语言圣经(Rust Course), 2022 [2022-07-02]. \url{https://web.archive.org/web/20220702105534/https://course.rs/test/unit-integration-test.html} +\bibitem{bib:kconfig} Luis Chamberlain. \texttt{Adapting Linux kernel kconfig}[CP/OL]. GitHub, 2022 (20220205) [2022-07-01]. \url{https://github.com/mcgrof/init-kconfig/tree/325fc60d21384742d137f9ea5bf66b2478021bce} + \bibitem{bib:iot-sys} 刘于苇. \textit{与嵌入式RTOS大不同,主流物联网操作系统中哪款适合你?}[Z/OL]. 电子工程专辑, 2021 (20210518) [2022-03-12]. \url{https://www.eet-china.com/news/202105180818.html} \bibitem{bib:ms-rtos} ScilogyHunter. \textit{MS-RTOS正式发布啦!!!}[Z/OL]. CSDN, 2020 (20200716) [2022-03-12]. \url{https://blog.csdn.net/ScilogyHunter/article/details/107390947} \bibitem{bib:rust-os-comparison} flosse, wmanley, ticki, et al. \textit{Rust OS comparison}[G/OL]. GitHub, 2022 (20220408) [2022-04-10]. \url{https://github.com/flosse/rust-os-comparison/tree/f3eb1aae4f080fafd81b44254f54e387ac5f6b9d} - -\bibitem{bib:feasibility-0} \textit{About RISC-V}[Z/OL]. RISC-V International, 2021. \url{https://riscv.org/about/} -\bibitem{bib:feasibility-1} -\bibitem{bib:feasibility-2} -\bibitem{bib:feasibility-3} -\bibitem{bib:feasibility-5} -\bibitem{bib:feasibility-6} -\bibitem{bib:feasibility-7} -\bibitem{bib:feasibility-8} -\bibitem{bib:feasibility-9} -\bibitem{bib:feasibility-a} -\bibitem{bib:feasibility-b} -\bibitem{bib:feasibility-c} -\bibitem{bib:feasibility-d} -\bibitem{bib:feasibility-e} -\bibitem{bib:qemu-virt} -\bibitem{bib:qemu7} \end{thebibliography} -\end{document} \ No newline at end of file +\end{document} diff --git a/report/assets/Runikraft-architecture-impl.pdf b/report/assets/Runikraft-architecture-impl.pdf new file mode 100644 index 0000000..904e8a3 Binary files /dev/null and b/report/assets/Runikraft-architecture-impl.pdf differ diff --git a/report/assets/Runikraft-architecture-impl.svg b/report/assets/Runikraft-architecture-impl.svg new file mode 100644 index 0000000..350b9d9 --- /dev/null +++ b/report/assets/Runikraft-architecture-impl.svg @@ -0,0 +1,774 @@ + + + + + + + + + + + + + + + + + + + + + virtio + + + + UART + + + + QEMU + + + RTC + + + 平台层 + + OS元件层 + + + rkgpudev + + + + rkinputdev + + …… + + + + + rkplat + + + + + + rksched + + + + rklock + + + + rkboot + + + + + preem + + + + coop + + + + + 调度器 + + + + rkalloc + + + + + buddy + + + + 分配器 + + + + rktime + + + + rkswrand + + + + + + + + + + + + + + rkargparse + + + + rktimeconv + + 工具 + + + + + + + rkmpi + + + + rkring + + 通信 + + + + …… + + diff --git a/report/assets/Runikraft-architecture.pdf b/report/assets/Runikraft-architecture.pdf new file mode 100644 index 0000000..3c6e8c8 Binary files /dev/null and b/report/assets/Runikraft-architecture.pdf differ diff --git a/report/assets/crates-dependency.pdf b/report/assets/crates-dependency.pdf new file mode 100644 index 0000000..dbfd78f Binary files /dev/null and b/report/assets/crates-dependency.pdf differ diff --git a/report/assets/crates-dependency.svg b/report/assets/crates-dependency.svg new file mode 100644 index 0000000..46f7352 --- /dev/null +++ b/report/assets/crates-dependency.svg @@ -0,0 +1,656 @@ + + + + + + + + + + + + + + rkalloc + + + + rkallocbuddy + + + + rkargparse + + + + rkboot + + + + rkgpudev + + + + rklock + + + + rkmpi + + + + rkplat + + + + rkring + + + + rksched + + + + rkschecoop + + + + rkschedpreem + + + + rkswrand + + + + rktimeconv + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/report/assets/runikraft-menuconfig-1.png b/report/assets/runikraft-menuconfig-1.png new file mode 100644 index 0000000..c94a7aa Binary files /dev/null and b/report/assets/runikraft-menuconfig-1.png differ diff --git a/report/assets/runikraft-menuconfig-2.png b/report/assets/runikraft-menuconfig-2.png new file mode 100644 index 0000000..4b45a7e Binary files /dev/null and b/report/assets/runikraft-menuconfig-2.png differ diff --git a/report/assets/sudoku.png b/report/assets/sudoku.png new file mode 100644 index 0000000..60ef17e Binary files /dev/null and b/report/assets/sudoku.png differ diff --git a/test/README b/test/README index 8f2f76e..1a98434 100644 --- a/test/README +++ b/test/README @@ -1,20 +1,16 @@ 添加测试程序的步骤: 1. 创建目录 . 2. 在该目录下创建 Cargo.toml: - - package.name = test-; - - 目前,dependencies 必须包含 - “rkalloc = {path = "../../lib/rkalloc"}”. + - package.name = test-. 3. 在/src/main.rs中写测试代码. - 4. 如果你的程序需要特殊的 qemu 命令行参数才能运行,如GPU测试程序需要 - virtio-gpu 设备,创建/run_flags.txt并把特殊的命令行参数 + 4. 如果你的程序需要特殊的 qemu 命令行参数才能运行, 如GPU测试程序需要 + virtio-gpu 设备, 创建/run_flags.txt并把特殊的命令行参数 写在此处。 Steps to add a test program: 1. Create directory . 2. Create Cargo.toml under this directory: - - package.name = test-; - - currently, dependencies must contain - ‘rkalloc = {path = "../../lib/rkalloc"}’. + - package.name = test-. 3. Write your test code in /src/main.rs . 4. If your test program needs special qemu command line options to run, i.e. GPU testing code requires a virtio-gpu device, create