You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
zCore-Tutorial/print.html

1051 lines
67 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE HTML>
<html lang="cn" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>简明 zCore 教程</title>
<meta name="robots" content="noindex" />
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded affix "><a href="index.html">简明 zCore 教程</a></li><li class="chapter-item expanded affix "><a href="zcore-intro.html">🚧 zCore 整体结构和设计模式</a></li><li class="chapter-item expanded affix "><a href="fuchsia.html">🚧 Fuchsia OS 和 Zircon 微内核</a></li><li class="chapter-item expanded "><a href="ch01-00-object.html"><strong aria-hidden="true">1.</strong> 内核对象</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch01-01-kernel-object.html"><strong aria-hidden="true">1.1.</strong> ✅ 初识内核对象</a></li><li class="chapter-item expanded "><a href="ch01-02-process-object.html"><strong aria-hidden="true">1.2.</strong> 🚧 对象管理器Process 对象</a></li><li class="chapter-item expanded "><a href="ch01-03-channel-object.html"><strong aria-hidden="true">1.3.</strong> 🚧 对象传送器Channel 对象</a></li></ol></li><li class="chapter-item expanded "><a href="ch02-00-task.html"><strong aria-hidden="true">2.</strong> 任务管理</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch02-01-zircon-task.html"><strong aria-hidden="true">2.1.</strong> 🚧 Zircon 任务管理体系</a></li><li class="chapter-item expanded "><a href="ch02-02-process-job-object.html"><strong aria-hidden="true">2.2.</strong> 🚧 进程管理Process 与 Job 对象</a></li><li class="chapter-item expanded "><a href="ch02-03-thread-object.html"><strong aria-hidden="true">2.3.</strong> 🚧 线程管理Thread 对象</a></li></ol></li><li class="chapter-item expanded "><a href="ch03-00-memory.html"><strong aria-hidden="true">3.</strong> 内存管理</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch03-01-zircon-memory.html"><strong aria-hidden="true">3.1.</strong> 🚧 Zircon 内存管理模型</a></li><li class="chapter-item expanded "><a href="ch03-02-vmo.html"><strong aria-hidden="true">3.2.</strong> 🚧 物理内存VMO 对象</a></li><li class="chapter-item expanded "><a href="ch03-03-vmo-paged.html"><strong aria-hidden="true">3.3.</strong> 🚧 物理内存:按页分配的 VMO</a></li><li class="chapter-item expanded "><a href="ch03-04-vmar.html"><strong aria-hidden="true">3.4.</strong> 🚧 虚拟内存VMAR 对象</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-userspace.html"><strong aria-hidden="true">4.</strong> 用户程序</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch04-01-user-program.html"><strong aria-hidden="true">4.1.</strong> 🚧 Zircon 用户程序</a></li><li class="chapter-item expanded "><a href="ch04-02-context-switch.html"><strong aria-hidden="true">4.2.</strong> 🚧 上下文切换</a></li><li class="chapter-item expanded "><a href="ch04-03-syscall.html"><strong aria-hidden="true">4.3.</strong> 🚧 系统调用</a></li></ol></li><li class="chapter-item expanded "><a href="ch05-00-signal-and-waiting.html"><strong aria-hidden="true">5.</strong> 信号和等待</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch05-01-wait-signal.html"><strong aria-hidden="true">5.1.</strong> 🚧 等待内核对象的信号</a></li><li class="chapter-item expanded "><a href="ch05-02-port-object.html"><strong aria-hidden="true">5.2.</strong> 🚧 同时等待多个信号Port 对象</a></li><li class="chapter-item expanded "><a href="ch05-03-more-signal-objects.html"><strong aria-hidden="true">5.3.</strong> 🚧 实现更多EventPair, Timer 对象</a></li><li class="chapter-item expanded "><a href="ch05-04-futex-object.html"><strong aria-hidden="true">5.4.</strong> 🚧 用户态同步互斥Futex 对象</a></li></ol></li></ol> </div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">简明 zCore 教程</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/rcore-os/zCore-Tutorial" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="简明-zcore-教程"><a class="header" href="#简明-zcore-教程">简明 zCore 教程</a></h1>
<h2 id="自己动手山寨操作系统自顶向下方法"><a class="header" href="#自己动手山寨操作系统自顶向下方法">自己动手山寨操作系统:自顶向下方法</a></h2>
<p>zCore 是用 Rust 语言重写的 Zircon 微内核,它是 Google 正在开发的 Fuchsia OS 中的底层内核。</p>
<p>本教程基于 zCore 的真实开发历史,还原其开发过程。带领读者一步一步用 Rust 实现自己的 Zircon 内核,最终能够运行原生的 shell 程序。
在此过程中我们将体会 Zircon 微内核的设计理念,感受如何用 Rust 语言以一种现代的方式编写系统软件,在项目中实现理论与实践的融合。</p>
<p>与传统操作系统开发不同的是zCore 使用一种自顶向下的方法:首先基于宿主系统已有的功能,在用户态实现一个能够工作的 libOS然后再逐步替换底层实现
&quot;移植&quot;回裸机环境中运行。因此我们更关注系统的整体设计,从高层视角看待 OS 如何为用户提供服务,而不纠结于底层硬件细节。</p>
<p>鉴于此,本教程假设读者了解操作系统基本概念和原理,具有常用的 Linux 系统使用经验,并且会使用 Rust 语言编写简单程序。
如果读者不熟悉操作系统和 Rust 语言,希望以自底向上方法从零构建操作系统,<a href="https://rcore-os.github.io/rCore-Tutorial-deploy/">rCore Tutorial</a> 可能是更好的选择。</p>
<p>如果你准备好了,让我们开始吧!</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="zcore-整体结构和设计模式"><a class="header" href="#zcore-整体结构和设计模式">zCore 整体结构和设计模式</a></h1>
<p>首先,从 <a href="https://github.com/rcore-os/zCore/wiki/files/wrj-thesis.pdf">Rust语言操作系统的设计与实现,王润基本科毕设论文,2019</a><a href="https://github.com/rcore-os/zCore/wiki/files/pql-thesis.pdf">zCore操作系统内核的设计与实现,潘庆霖本科毕设论文,2020</a> 可以了解到从 rCore 的设计到 zCore 的设计过程的全貌。</p>
<h2 id="zcore-的整体结构"><a class="header" href="#zcore-的整体结构">zCore 的整体结构</a></h2>
<p><a href="https://github.com/rcore-os/zCore">zCore</a> 的整体结构/项目设计图如下:</p>
<p><img src="zcore-intro/structure.svg" alt="img" /></p>
<p>zCore的设计主要有两个出发点</p>
<ul>
<li>内核对象的封装:将内核对象代码封装为一个库,保证可重用</li>
<li>硬件接口的设计使硬件与内核对象的设计相对独立只向上提供统一、抽象的API接口</li>
</ul>
<p>项目设计从上到下,上层更远离硬件,下层更接近硬件。</p>
<p>zCore 设计的顶层是上层操作系统,比如 zCore、rCore、Zircon LibOS 和 Linux LibOS。在项目架构中各版本的操作系统有部分公用代码。与 zCore 微内核设计实现相关的部分则主要是图中左侧蓝色线部分。</p>
<p>第二层,是 ELF 程序加载层ELF Program Loader包括 zircon-loader 和 linux-loader其中封装了初始化内核对象、部分硬件相关的初始化、设定系统调用接口、运行首个用户态程序等逻辑并形成一个库函数。zCore 在顶层通过调用 zircon-loader 库中的初始化逻辑,进入第一个用户态程序执行。</p>
<p>第三层是系统调用实现层Syscall Implementation包括 zircon-syscall 和 linux-syscall这一层将所有的系统调用处理例程封装为一个系统调用库供上方操作系统使用。</p>
<p>第四层,利用硬件抽象层提供的虚拟硬件 API 进行内核对象Kernel Objects的实现并且基于实现的各类内核对象实现第三层各个系统调用接口所需要的具体处理例程。</p>
<p>第五层是硬件抽象层HALHardware Abstraction Layer这里对应的是 kernel-hal 模块。kernel-hal 将向上提供所有操作硬件需要的接口,从而使得硬件环境对上层操作系统透明化。</p>
<p>第六层,是对直接操作硬件的代码进行一层封装,对应模块为 kernel-hal-bare 和 kernel-hal-unix。kernel-hal 系列库仅仅负责接口定义,即将底层硬件/宿主操作系统的操作翻译为上层操作系统可以使用的形式。在这里kernel-hal-bare 负责翻译裸机的硬件功能,而 kernel-hal-unix 则负责翻译类 Unix 系统的系统调用。</p>
<p>最底层是底层运行环境,包括 Bare Metal裸机Linux / macOS 操作系统。Bare Metal可以认为是硬件架构上的寄存器等硬件接口。</p>
<h2 id="zcore-内核组件"><a class="header" href="#zcore-内核组件">zCore 内核组件</a></h2>
<p>zCore 内核运行时组件层次概况如下:</p>
<p><img src="zcore-intro/image-20200805123801306.png" alt="image-20200805123801306" /></p>
<p>在zCore启动过程中会初始化物理页帧分配器、堆分配器、线程调度器等各个组成部分。并委托 zircon-­loader 进行内核对象的初始化创建过程,然后进入用户态的启动过程开始执行。每当用户态触发系统调用进入内核态,系统调用处理例程将会通过已实现的内核对象的功能来对服务请求进行处理;而对应的内核对象的内部实现所需要的各种底层操作,则是通过 HAL 层接口由各个内核组件负责提供。</p>
<p>其中VDSOVirtual dynamic shared object是一个映射到用户空间的 so 文件,可以在不陷入内核的情况下执行一些简单的系统调用。在设计中,所有中断都需要经过 VDSO 拦截进行处理,因此重写 VDSO 便可以实现自定义的对下层系统调用syscall的支持。Executor 是 zCore 中基于 Rust 的 <code>async</code> 机制的协程调度器。</p>
<p>在HAL接口层的设计上还借助了 Rust 的能够指定函数链接过程的特性。即,在 kernel-­hal 中规定了所有可供 zircon­-object 库及 zircon-­syscall 库调用的虚拟硬件接口,以函数 API 的形式给出,但是内部均为未实现状态,并设置函数为弱引用链接状态。在 kernel­-hal-­bare 中才给出裸机环境下的硬件接口具体实现,编译 zCore 项目时、链接的过程中将会替换/覆盖 kernel-­hal 中未实现的同名接口,从而达到能够在编译时灵活选择 HAL 层的效果。</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="fuchsia-os-和-zircon-微内核"><a class="header" href="#fuchsia-os-和-zircon-微内核">Fuchsia OS 和 Zircon 微内核</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="内核对象"><a class="header" href="#内核对象">内核对象</a></h1>
<p>Zircon 是一个基于内核对象的系统。系统的功能被划分到若干组内核对象中。</p>
<p>作为一切的开始,本章首先构造了一个内核对象框架,作为后面实现的基础。
然后我们实现第一个内核对象 —— <code>Process</code>,它是所有对象的容器,也是将来我们操作对象的入口点。
最后会实现一个稍微复杂但是极其重要的对象 <code>Channel</code>它是进程间通信IPC的基础设施也是传送对象的唯一管道。</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="初识内核对象"><a class="header" href="#初识内核对象">初识内核对象</a></h1>
<h2 id="内核对象简介"><a class="header" href="#内核对象简介">内核对象简介</a></h2>
<p>在动手编写我们的代码之前,需要首先进行调研和学习,对目标对象有一个全面系统的了解。
而了解一个项目设计的最好方式就是阅读官方提供的手册和文档。</p>
<p>让我们先来阅读一下 Fuchsia 官方文档:<a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/objects.md">内核对象</a>。这个链接是社区翻译的中文版,已经有些年头了。如果读者能够科学上网,推荐直接阅读<a href="https://fuchsia.dev/fuchsia-src/reference/kernel_objects/objects">官方英文版</a></p>
<p>通过阅读文档,我们了解到与内核对象相关的三个重要概念:<strong>对象Object句柄Handle权限Rights</strong>。它们在 Zircon 内核中的角色和关系如下图所示:</p>
<p><img src="img/ch01-01-kernel-object.png" alt="" /></p>
<p>简单来说:</p>
<ul>
<li>Zircon是一个基于对象的内核内核资源被抽象封装在不同的 <strong>对象</strong> 中。</li>
<li>用户程序通过 <strong>句柄</strong> 与内核交互。句柄是对某一对象的引用,并且附加了特定的 <strong>权限</strong></li>
<li>对象通过 <strong>引用计数</strong> 管理生命周期。对于大多数对象,当指向它的最后一个句柄关闭时,对象随之销毁,或<a href="https://fuchsia.dev/fuchsia-src/concepts/kernel/concepts#handles_and_rights">进入无法挽回的最终状态</a></li>
</ul>
<p>此外在内核对象的文档中,还列举了一些<a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/objects.md#%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E5%8F%AF%E7%94%A8%E7%9A%84%E5%86%85%E6%A0%B8%E5%AF%B9%E8%B1%A1">常用对象</a>。点击链接进去就能查看到这个对象的<a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/objects/channel.md">具体描述</a>,在页面最下方还列举了与这个对象相关的<a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/objects/channel.md#%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8">全部系统调用</a>
进一步查看系统调用的 <a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/syscalls/channel_read.md#%E6%A6%82%E8%A6%81">API 定义</a>,以及它的<a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/syscalls/channel_read.md#%E6%8F%8F%E8%BF%B0">行为描述</a>,我们就能更深入地了解用户程序操作内核对象的一些细节:</p>
<ul>
<li>
<p>创建:每一种内核对象都存在一个系统调用来创建它,例如 <a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/syscalls/channel_create.md"><code>zx_channel_create</code></a>
创建对象时一般需要传入一个参数选项 <code>options</code>,若创建成功则内核会将一个新句柄写入用户指定的内存中。</p>
</li>
<li>
<p>使用:获得对象句柄后可以通过若干系统调用对它进行操作,例如 <a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/syscalls/channel_write.md"><code>zx_channel_write</code></a>
这类系统调用一般需要传入句柄 <code>handle</code> 作为第一个参数,内核首先对其进行检查,如果句柄非法或者对象类型与系统调用不匹配就会报错。
接下来内核会检查句柄的权限是否满足操作的要求,例如 <code>write</code> 操作一般要求句柄具有 <code>WRITE</code> 权限,如果权限不满足就会继续报错。</p>
</li>
<li>
<p>关闭:当用户程序不再使用对象时,会调用 <a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/syscalls/handle_close.md"><code>zx_handle_close</code></a> 关闭句柄。当用户进程退出时,仍处于打开状态的句柄也都会自动关闭。</p>
</li>
</ul>
<p>我们还发现,有一类 Object 系统调用是对所有内核对象都适用的。
这表明所有内核对象都有一些公共属性,例如 ID、名称等等。每一种内核对象也会有自己特有的属性。</p>
<p>其中一些 Object 系统调用和信号相关。Zircon 每个内核对象都附带有 32 个 <strong><a href="https://github.com/zhangpf/fuchsia-docs-zh-CN/blob/master/zircon/docs/signals.md">信号Signals</a></strong>,它们代表了不同类型的事件。
与传统 Unix 系统的信号不同,它不能异步地打断用户程序运行,而只能由用户程序主动地阻塞等待在某个对象的某些信号上面。
信号是 Zircon 内核中很重要的机制,不过这部分在前期不会涉及,我们留到第五章再具体实现。</p>
<p>以上我们了解了 Zircon 内核对象的相关概念和使用方式。接下来在这一节中,我们将用 Rust 实现内核对象的基本框架,以方便后续快速实现各种具体类型的内核对象。
从传统面向对象语言的视角看,我们只是在实现一个基类。但由于 Rust 语言模型的限制,这件事情需要用到一些特殊的技巧。</p>
<h2 id="建立项目"><a class="header" href="#建立项目">建立项目</a></h2>
<p>首先我们需要安装 Rust 工具链。在 Linux 或 macOS 系统下,只需要用一个命令下载安装 rustup 即可:</p>
<pre><code class="language-sh">$ curl https://sh.rustup.rs -sSf | sh
</code></pre>
<p>具体安装方法可以参考<a href="https://kaisery.github.io/trpl-zh-cn/ch01-01-installation.html">官方文档</a></p>
<p>接下来我们用 cargo 创建一个 Rust 库项目:</p>
<pre><code class="language-sh">$ cargo new --lib zcore
$ cd zcore
</code></pre>
<p>我们将在这个 crate 中实现所有的内核对象以库lib而不是可执行文件bin的形式组织代码后面我们会依赖单元测试保证代码的正确性。</p>
<p>由于我们会用到一些不稳定unstable的语言特性需要使用 nightly 版本的工具链。在项目根目录下创建一个 <code>rust-toolchain</code> 文件,指明使用的工具链版本:</p>
<pre><code class="language-sh">{{#include ../../code/ch01-01/rust-toolchain}}
</code></pre>
<p>这个程序库目前是在你的 Linux 或 macOS 上运行,但有朝一日它会成为一个真正的 OS 在裸机上运行。
为此我们需要移除对标准库的依赖,使其成为一个不依赖当前 OS 功能的库。在 <code>lib.rs</code> 的第一行添加声明:</p>
<pre><code class="language-rust noplaypen">// src/lib.rs
#![no_std]
extern crate alloc;
</code></pre>
<p>现在我们可以尝试运行一下自带的单元测试,编译器可能会自动下载并安装工具链:</p>
<pre><code class="language-sh">$ cargo test
Finished test [unoptimized + debuginfo] target(s) in 0.52s
Running target/debug/deps/zcore-dc6d43637bc5df7a
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
</code></pre>
<h2 id="实现-kernelobject-接口"><a class="header" href="#实现-kernelobject-接口">实现 KernelObject 接口</a></h2>
<p>所有的内核对象有一系列共同的属性和方法,我们称这些方法为对象的公共<strong>接口Interface</strong>
同一种方法在不同类型的对象中可能会有不同的行为,在面向对象语言中我们称其为<strong>多态Polymorphism</strong></p>
<p>Rust 是一门部分面向对象的语言,我们通常用它的 trait 实现接口和多态。</p>
<p>首先创建一个 <code>KernelObject</code> trait 作为内核对象的公共接口:</p>
<pre><code class="language-rust noplaypen">use alloc::string::String;
// src/object/mod.rs
/// 内核对象公共接口
pub trait KernelObject: Send + Sync {
/// 获取对象 ID
fn id(&amp;self) -&gt; KoID;
/// 获取对象类型名
fn type_name(&amp;self) -&gt; &amp;str;
/// 获取对象名称
fn name(&amp;self) -&gt; String;
/// 设置对象名称
fn set_name(&amp;self, name: &amp;str);
}
/// 对象 ID 类型
pub type KoID = u64;
</code></pre>
<p>这里的 <a href="https://kaisery.github.io/trpl-zh-cn/ch16-04-extensible-concurrency-sync-and-send.html"><code>Send + Sync</code></a> 是一个约束所有 <code>KernelObject</code> 都要满足的前提条件,即它必须是一个<strong>并发对象</strong>
所谓并发对象指的是<strong>可以安全地被多线程共享访问</strong>。事实上我们的内核本身就是一个共享地址空间的多线程程序,在裸机上每个 CPU 核都可以被视为一个并发执行的线程。
由于内核对象可能被多个线程同时访问,因此它必须是并发对象。</p>
<h2 id="实现一个空对象"><a class="header" href="#实现一个空对象">实现一个空对象</a></h2>
<p>接下来我们实现一个最简单的空对象 <code>DummyObject</code>,并为它实现 <code>KernelObject</code> 接口:</p>
<pre><code class="language-rust noplaypen">// src/object/object.rs
use spin::Mutex;
/// 空对象
#[derive(Debug)]
pub struct DummyObject {
id: KoID,
inner: Mutex&lt;DummyObjectInner&gt;,
}
/// `DummyObject` 的内部可变部分
#[derive(Default, Debug)]
struct DummyObjectInner {
name: String,
}
</code></pre>
<p>这里我们采用一种<a href="https://kaisery.github.io/trpl-zh-cn/ch15-05-interior-mutability.html"><strong>内部可变性</strong></a>的设计模式:将对象的所有可变的部分封装到一个内部对象 <code>DummyObjectInner</code> 中,并在原对象中用自旋锁 <a href="https://docs.rs/spin/0.5.2/spin/struct.Mutex.html"><code>Mutex</code></a> 把它包起来,剩下的其它字段都是不可变的。
<code>Mutex</code> 会用最简单的方式帮我们处理好并发访问问题:如果有其他人正在访问,我就在这里死等。
数据被 <code>Mutex</code> 包起来之后需要首先使用 <a href="https://docs.rs/spin/0.5.2/spin/struct.Mutex.html#method.lock"><code>lock()</code></a> 拿到锁之后才能访问。此时并发访问已经安全,因此被包起来的结构自动具有了 <code>Send + Sync</code> 特性。</p>
<p>使用自旋锁引入了新的依赖库 <a href="https://docs.rs/spin/0.5.2/spin/index.html"><code>spin</code></a> ,需要在 <code>Cargo.toml</code> 中加入以下声明:</p>
<pre><code class="language-toml">[dependencies]
spin = &quot;0.7&quot;
</code></pre>
<p>然后我们为新对象实现构造函数:</p>
<pre><code class="language-rust noplaypen">// src/object/object.rs
use alloc::sync::Arc;
use core::sync::atomic::*;
impl DummyObject {
/// 创建一个新 `DummyObject`
pub fn new() -&gt; Arc&lt;Self&gt; {
Arc::new(DummyObject {
id: Self::new_koid(),
inner: Default::default(),
})
}
/// 生成一个唯一的 ID
fn new_koid() -&gt; KoID {
static NEXT_KOID: AtomicU64 = AtomicU64::new(1024);
NEXT_KOID.fetch_add(1, Ordering::SeqCst)
}
}
</code></pre>
<p>根据文档描述,每个内核对象都有唯一的 ID。为此我们需要实现一个全局的 ID 分配方法。这里采用的方法是用一个静态变量存放下一个待分配 ID 值,每次分配就原子地 +1。
ID 类型使用 <code>u64</code>,保证了数值空间足够大,在有生之年都不用担心溢出问题。在 Zircon 中 ID 从 1024 开始分配1024 以下保留作内核内部使用。</p>
<p>另外注意这里 <code>new</code> 函数返回类型不是 <code>Self</code> 而是 <code>Arc&lt;Self&gt;</code>,这是为了以后方便而做的统一约定。</p>
<p>最后我们为它实现 <code>KernelObject</code> 接口:</p>
<pre><code class="language-rust noplaypen">// src/object/object.rs
impl KernelObject for DummyObject {
fn id(&amp;self) -&gt; KoID {
self.id
}
fn type_name(&amp;self) -&gt; &amp;str {
&quot;DummyObject&quot;
}
fn name(&amp;self) -&gt; String {
self.inner.lock().name.clone()
}
fn set_name(&amp;self, name: &amp;str) {
self.inner.lock().name = String::from(name);
}
}
</code></pre>
<p>到此为止,我们已经迈出了万里长征第一步,实现了一个最简单的功能。有实现,就要有测试!即使最简单的代码也要保证它的行为符合我们预期。
只有对现有代码进行充分测试,在未来做添加和修改的时候,我们才有信心不会把事情搞砸。俗话讲&quot;万丈高楼平地起&quot;,把地基打好才能盖摩天大楼。</p>
<p>为了证明上面代码的正确性,我们写一个简单的单元测试,替换掉自带的 <code>it_works</code> 函数:</p>
<pre><code class="language-rust noplaypen">// src/object/object.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dummy_object() {
let o1 = DummyObject::new();
let o2 = DummyObject::new();
assert_ne!(o1.id(), o2.id());
assert_eq!(o1.type_name(), &quot;DummyObject&quot;);
assert_eq!(o1.name(), &quot;&quot;);
o1.set_name(&quot;object1&quot;);
assert_eq!(o1.name(), &quot;object1&quot;);
}
}
</code></pre>
<pre><code class="language-sh">$ cargo test
Finished test [unoptimized + debuginfo] target(s) in 0.53s
Running target/debug/deps/zcore-ae1be84852989b13
running 1 test
test tests::dummy_object ... ok
</code></pre>
<p>大功告成!让我们用 <code>cargo fmt</code> 命令格式化一下代码,然后记得 <code>git commit</code> 及时保存进展。</p>
<h2 id="实现接口到具体类型的向下转换"><a class="header" href="#实现接口到具体类型的向下转换">实现接口到具体类型的向下转换</a></h2>
<p>在系统调用中,用户进程会传入一个内核对象的句柄,然后内核会根据系统调用的类型,尝试将其转换成特定类型的对象。
于是这里产生了一个很重要的需求:将接口 <code>Arc&lt;dyn KernelObject&gt;</code> 转换成具体类型的结构 <code>Arc&lt;T&gt; where T: KernelObject</code>
这种操作在面向对象语言中称为<strong>向下转换downcast</strong></p>
<p>在大部分编程语言中,向下转换都是一件非常轻松的事情。例如在 C/C++ 中,我们可以这样写:</p>
<pre><code class="language-c++">struct KernelObject {...};
struct DummyObject: KernelObject {...};
KernelObject *base = ...;
// C 风格:强制类型转换
DummyObject *dummy = (DummyObject*)(base);
// C++ 风格:动态类型转换
DummyObject *dummy = dynamic_cast&lt;DummyObject*&gt;(base);
</code></pre>
<p>但在 Rust 中,由于其 trait 模型的限制,向下转换并不是一件容易的事情。
虽然标准库中提供了 <a href="https://doc.rust-lang.org/std/any/"><code>Any</code></a> trait部分实现了动态类型的功能但实际操作起来却困难重重。
不信邪的同学可以自己折腾一下:</p>
<pre><pre class="playground"><code class="language-rust editable edition2018"><span class="boring">use std::any::Any;
</span><span class="boring">use std::sync::Arc;
</span><span class="boring">fn main() {}
</span>
trait KernelObject: Any + Send + Sync {}
fn downcast_v1&lt;T: KernelObject&gt;(object: Arc&lt;dyn KernelObject&gt;) -&gt; Arc&lt;T&gt; {
object.downcast::&lt;T&gt;().unwrap()
}
fn downcast_v2&lt;T: KernelObject&gt;(object: Arc&lt;dyn KernelObject&gt;) -&gt; Arc&lt;T&gt; {
let object: Arc&lt;dyn Any + Send + Sync + 'static&gt; = object;
object.downcast::&lt;T&gt;().unwrap()
}
</code></pre></pre>
<p>当然这个问题也困扰了 Rust 社区中的很多人。目前已经有人提出了一套不错的解决方案,就是我们接下来要引入的 <a href="https://docs.rs/downcast-rs/1.2.0/downcast_rs/index.html"><code>downcast-rs</code></a> 库:</p>
<pre><code class="language-toml">[dependencies]
downcast-rs = { version = &quot;1.2.0&quot;, default-features = false }
</code></pre>
<p>(题外话:这个库原来是不支持 <code>no_std</code>zCore 有这个需求,于是就顺便帮他实现了一把)</p>
<p>按照它文档的描述,我们要为自己的接口实现向下转换,只需以下修改:</p>
<pre><code class="language-rust noplaypen">// src/object/mod.rs
use core::fmt::Debug;
use downcast_rs::{impl_downcast, DowncastSync};
pub trait KernelObject: DowncastSync + Debug {...}
impl_downcast!(sync KernelObject);
</code></pre>
<p>其中 <code>DowncastSync</code> 代替了原来的 <code>Send + Sync</code><code>Debug</code> 用于出错时输出调试信息。
<code>impl_downcast!</code> 宏用来帮我们自动生成转换函数,然后就可以用 <code>downcast_arc</code> 来对 <code>Arc</code> 做向下转换了。我们直接来测试一把:</p>
<pre><code class="language-rust noplaypen">// src/object/object.rs
#[test]
fn downcast() {
let dummy = DummyObject::new();
let object: Arc&lt;dyn KernelObject&gt; = dummy;
let _result: Arc&lt;DummyObject&gt; = object.downcast_arc::&lt;DummyObject&gt;().unwrap();
}
</code></pre>
<pre><code class="language-sh">$ cargo test
Finished test [unoptimized + debuginfo] target(s) in 0.47s
Running target/debug/deps/zcore-ae1be84852989b13
running 2 tests
test object::downcast ... ok
test object::tests::dummy_object ... ok
</code></pre>
<h2 id="模拟继承用宏自动生成接口实现代码"><a class="header" href="#模拟继承用宏自动生成接口实现代码">模拟继承:用宏自动生成接口实现代码</a></h2>
<p>上面我们已经完整实现了一个内核对象,代码看起来很简洁。但当我们要实现更多对象的时候,就会发现一个问题:
这些对象拥有一些公共属性,接口方法也有共同的实现。
在传统 OOP 语言中,我们通常使用 <strong>继承inheritance</strong> 来复用这些公共代码:子类 B 可以继承父类 A然后自动拥有父类的所有字段和方法。</p>
<p>继承是一个很强大的功能,但在长期实践中人们也逐渐发现了它的弊端。有兴趣的读者可以看一看知乎上的探讨:<a href="https://www.zhihu.com/question/20275578/answer/26577791"><em>面向对象编程的弊端是什么?</em></a>
经典著作《设计模式》中就鼓励大家<strong>使用组合代替继承</strong>。而一些现代的编程语言,如 Go 和 Rust甚至直接抛弃了继承。在 Rust 中,通常使用组合结构和 <a href="https://kaisery.github.io/trpl-zh-cn/ch15-02-deref.html"><code>Deref</code></a> trait 来部分模拟继承。</p>
<blockquote>
<p>继承野蛮trait 文明。 —— 某 Rust 爱好者</p>
</blockquote>
<p>接下来我们模仿 <code>downcast-rs</code> 库的做法,使用一种基于宏的代码生成方案,来实现 <code>KernelObject</code> 的继承。
当然这只是抛砖引玉,如果读者自己实现了,或者了解到社区中有更好的解决方案,也欢迎指出。</p>
<p>具体做法是这样的:</p>
<ul>
<li>使用一个 struct 来提供所有的公共属性和方法,作为所有子类的第一个成员。</li>
<li>为子类实现 trait 接口,所有方法直接委托给内部 struct 完成。这部分使用宏来自动生成模板代码。</li>
</ul>
<p>而所谓的内部 struct其实就是我们上面实现的 <code>DummyObject</code>。为了更好地体现它的功能,我们给他改个名叫 <code>KObjectBase</code></p>
<pre><code class="language-rust noplaypen">// src/object/mod.rs
/// 内核对象核心结构
pub struct KObjectBase {
/// 对象 ID
pub id: KoID,
inner: Mutex&lt;KObjectBaseInner&gt;,
}
/// `KObjectBase` 的内部可变部分
#[derive(Default)]
struct KObjectBaseInner {
name: String,
}
</code></pre>
<p>接下来我们把它的构造函数改为实现 <code>Default</code> trait并且公共属性和方法都指定为 <code>pub</code></p>
<pre><code class="language-rust noplaypen">// src/object/mod.rs
impl Default for KObjectBase {
/// 创建一个新 `KObjectBase`
fn default() -&gt; Self {
KObjectBase {
id: Self::new_koid(),
inner: Default::default(),
}
}
}
impl KObjectBase {
/// 生成一个唯一的 ID
fn new_koid() -&gt; KoID {...}
/// 获取对象名称
pub fn name(&amp;self) -&gt; String {...}
/// 设置对象名称
pub fn set_name(&amp;self, name: &amp;str) {...}
}
</code></pre>
<p>最后来写一个魔法的宏!</p>
<pre><code class="language-rust noplaypen">// src/object/mod.rs
/// 为内核对象 struct 自动实现 `KernelObject` trait 的宏。
#[macro_export] // 导出宏,可在 crate 外部使用
macro_rules! impl_kobject {
// 匹配类型名,并可以提供函数覆盖默认实现
($class:ident $( $fn:tt )*) =&gt; {
// 为对象实现 KernelObject trait方法直接转发到内部 struct
impl KernelObject for $class {
fn id(&amp;self) -&gt; KoID {
// 直接访问内部的 pub 属性
self.base.id
}
fn type_name(&amp;self) -&gt; &amp;str {
// 用 stringify! 宏将输入转成字符串
stringify!($class)
}
// 注意宏里面的类型要写完整路径例如alloc::string::String
fn name(&amp;self) -&gt; alloc::string::String {
self.base.name()
}
fn set_name(&amp;self, name: &amp;str){
// 直接访问内部的 pub 方法
self.base.set_name(name)
}
// 可以传入任意数量的函数,覆盖 trait 的默认实现
$( $fn )*
}
// 为对象实现 Debug trait
impl core::fmt::Debug for $class {
fn fmt(
&amp;self,
f: &amp;mut core::fmt::Formatter&lt;'_&gt;,
) -&gt; core::result::Result&lt;(), core::fmt::Error&gt; {
// 输出对象类型、ID 和名称
f.debug_tuple(&amp;stringify!($class))
.field(&amp;self.id())
.field(&amp;self.name())
.finish()
}
}
};
}
</code></pre>
<p>轮子已经造好了!让我们看看如何用它方便地实现一个内核对象,仍以 <code>DummyObject</code> 为例:</p>
<pre><code class="language-rust noplaypen">// src/object/mod.rs
/// 空对象
pub struct DummyObject {
// 其中必须包含一个名为 `base` 的 `KObjectBase`
base: KObjectBase,
}
// 使用刚才的宏,声明其为内核对象,自动生成必要的代码
impl_kobject!(DummyObject);
impl DummyObject {
/// 创建一个新 `DummyObject`
pub fn new() -&gt; Arc&lt;Self&gt; {
Arc::new(DummyObject {
base: KObjectBase::default(),
})
}
}
</code></pre>
<p>是不是方便了很多?最后按照惯例,用单元测试检验实现的正确性:</p>
<pre><code class="language-rust noplaypen">// src/object/mod.rs
#[test]
fn impl_kobject() {
use alloc::format;
let dummy = DummyObject::new();
let object: Arc&lt;dyn KernelObject&gt; = dummy;
assert_eq!(object.type_name(), &quot;DummyObject&quot;);
assert_eq!(object.name(), &quot;&quot;);
object.set_name(&quot;dummy&quot;);
assert_eq!(object.name(), &quot;dummy&quot;);
assert_eq!(
format!(&quot;{:?}&quot;, object),
format!(&quot;DummyObject({}, \&quot;dummy\&quot;)&quot;, object.id())
);
let _result: Arc&lt;DummyObject&gt; = object.downcast_arc::&lt;DummyObject&gt;().unwrap();
}
</code></pre>
<p>有兴趣的读者可以继续探索使用功能更强大的 <a href="https://doc.rust-lang.org/proc_macro/index.html"><strong>过程宏proc_macro</strong></a>,进一步简化实现新内核对象所需的模板代码。
如果能把上面的代码块缩小成下面这两行,就更加完美了:</p>
<pre><code class="language-rust noplaypen">#[KernelObject]
pub struct DummyObject;
</code></pre>
<h2 id="总结"><a class="header" href="#总结">总结</a></h2>
<p>在这一节中我们用 Rust 语言实现了 Zircon 最核心的<strong>内核对象</strong>概念。在此过程中涉及到 Rust 的一系列语言特性和设计模式:</p>
<ul>
<li>使用 <strong>trait</strong> 实现接口</li>
<li>使用 <strong>内部可变性</strong> 模式实现并发对象</li>
<li>基于社区解决方案实现 trait 到 struct 的 <strong>向下转换</strong></li>
<li>使用组合模拟继承,并使用 <strong></strong> 实现模板代码的自动生成</li>
</ul>
<p>由于 Rust 独特的<a href="https://kaisery.github.io/trpl-zh-cn/ch17-00-oop.html">面向对象编程特性</a>,我们在实现内核对象的过程中遇到了一定的挑战。
不过万事开头难,解决这些问题为整个项目打下了坚实基础,后面实现新的内核对象就会变得简单很多。</p>
<p>在下一节中,我们将介绍内核对象相关的另外两个概念:句柄和权限,并实现内核对象的存储和访问。</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="对象管理器process-对象"><a class="header" href="#对象管理器process-对象">对象管理器Process 对象</a></h1>
<h2 id="句柄和权限"><a class="header" href="#句柄和权限">句柄和权限</a></h2>
<blockquote>
<p>介绍并实现 HandleRights</p>
</blockquote>
<p><code>Cargo.toml</code> 中加入 <code>bitflags</code> 库:</p>
<pre><code class="language-rust noplaypen">[dependencies]
bitflags = &quot;1.2&quot;
</code></pre>
<p>在 object 模块下定义两个子模块:</p>
<pre><code class="language-rust noplaypen">// src/object/mod.rs
mod handle;
mod rights;
pub use self::handle::*;
pub use self::rights::*;
</code></pre>
<p>定义权限:</p>
<pre><code class="language-rust noplaypen">// src/object/rights.rs
use bitflags::bitflags;
bitflags! {
/// 句柄权限
pub struct Rights: u32 {
const DUPLICATE = 1 &lt;&lt; 0;
const TRANSFER = 1 &lt;&lt; 1;
const READ = 1 &lt;&lt; 2;
const WRITE = 1 &lt;&lt; 3;
const EXECUTE = 1 &lt;&lt; 4;
const MAP = 1 &lt;&lt; 5;
const GET_PROPERTY = 1 &lt;&lt; 6;
const SET_PROPERTY = 1 &lt;&lt; 7;
const ENUMERATE = 1 &lt;&lt; 8;
const DESTROY = 1 &lt;&lt; 9;
const SET_POLICY = 1 &lt;&lt; 10;
const GET_POLICY = 1 &lt;&lt; 11;
const SIGNAL = 1 &lt;&lt; 12;
const SIGNAL_PEER = 1 &lt;&lt; 13;
const WAIT = 1 &lt;&lt; 14;
const INSPECT = 1 &lt;&lt; 15;
const MANAGE_JOB = 1 &lt;&lt; 16;
const MANAGE_PROCESS = 1 &lt;&lt; 17;
const MANAGE_THREAD = 1 &lt;&lt; 18;
const APPLY_PROFILE = 1 &lt;&lt; 19;
const SAME_RIGHTS = 1 &lt;&lt; 31;
const BASIC = Self::TRANSFER.bits | Self::DUPLICATE.bits | Self::WAIT.bits | Self::INSPECT.bits;
const IO = Self::READ.bits | Self::WRITE.bits;
const DEFAULT_CHANNEL = Self::BASIC.bits &amp; !Self::DUPLICATE.bits | Self::IO.bits | Self::SIGNAL.bits | Self::SIGNAL_PEER.bits;
}
}
</code></pre>
<p>定义句柄:</p>
<pre><code class="language-rust noplaypen">// src/object/handle.rs
use super::{KernelObject, Rights};
use alloc::sync::Arc;
/// 内核对象句柄
#[derive(Clone)]
pub struct Handle {
pub object: Arc&lt;dyn KernelObject&gt;,
pub rights: Rights,
}
impl Handle {
/// 创建一个新句柄
pub fn new(object: Arc&lt;dyn KernelObject&gt;, rights: Rights) -&gt; Self {
Handle { object, rights }
}
}
</code></pre>
<h2 id="存储内核对象句柄"><a class="header" href="#存储内核对象句柄">存储内核对象句柄</a></h2>
<blockquote>
<p>添加成员变量 handles: BTreeMap&lt;HandleValue, Handle&gt;</p>
<p>实现 createadd_handleremove_handle 函数</p>
</blockquote>
<p>使用上一节的方法,实现一个空的 Process 对象:</p>
<pre><code class="language-rust noplaypen">// src/task/process.rs
/// 进程对象
pub struct Process {
base: KObjectBase,
inner: Mutex&lt;ProcessInner&gt;,
}
impl_kobject!(Process);
struct ProcessInner {
handles: BTreeMap&lt;HandleValue, Handle&gt;,
}
pub type HandleValue = u32;
impl Process {
/// 创建一个新的进程对象
pub fn new() -&gt; Arc&lt;Self&gt; {
Arc::new(Process {
base: KObjectBase::default(),
inner: Mutex::new(ProcessInner {
handles: BTreeMap::default(),
}),
})
}
}
</code></pre>
<p>插入、删除句柄函数:</p>
<pre><code class="language-rust noplaypen">// src/task/process.rs
impl Process {
/// 添加一个新的对象句柄
pub fn add_handle(&amp;self, handle: Handle) -&gt; HandleValue {
let mut inner = self.inner.lock();
let value = (0 as HandleValue..)
.find(|idx| !inner.handles.contains_key(idx))
.unwrap();
inner.handles.insert(value, handle);
value
}
/// 删除一个对象句柄
pub fn remove_handle(&amp;self, handle_value: HandleValue) {
self.inner.lock().handles.remove(&amp;handle_value);
}
}
</code></pre>
<h2 id="定义内核错误及-result-类型"><a class="header" href="#定义内核错误及-result-类型">定义内核错误及 <code>Result</code> 类型</a></h2>
<pre><code class="language-rust noplaypen">// src/error.rs
/// Zircon statuses are signed 32 bit integers. The space of values is
/// divided as follows:
/// - The zero value is for the OK status.
/// - Negative values are defined by the system, in this file.
/// - Positive values are reserved for protocol-specific error values,
/// and will never be defined by the system.
#[allow(non_camel_case_types, dead_code)]
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum ZxError {
OK = 0,
// ======= Internal failures =======
/// The system encountered an otherwise unspecified error
/// while performing the operation.
INTERNAL = -1,
/// The operation is not implemented, supported,
/// or enabled.
NOT_SUPPORTED = -2,
// ......
/// Connection was aborted.
CONNECTION_ABORTED = -76,
}
</code></pre>
<pre><code class="language-rust noplaypen">// src/error.rs
///
pub type ZxResult&lt;T&gt; = Result&lt;T, ZxError&gt;;
</code></pre>
<h2 id="根据句柄查找内核对象"><a class="header" href="#根据句柄查找内核对象">根据句柄查找内核对象</a></h2>
<blockquote>
<p>实现 get_object_with_rights 等其它相关函数</p>
<p>实现 handle 单元测试</p>
</blockquote>
<pre><code class="language-rust noplaypen">// src/task/process.rs
impl Process {
/// 根据句柄值查找内核对象,并检查权限
pub fn get_object_with_rights&lt;T: KernelObject&gt;(
&amp;self,
handle_value: HandleValue,
desired_rights: Rights,
) -&gt; ZxResult&lt;Arc&lt;T&gt;&gt; {
let handle = self
.inner
.lock()
.handles
.get(&amp;handle_value)
.ok_or(ZxError::BAD_HANDLE)?
.clone();
// check type before rights
let object = handle
.object
.downcast_arc::&lt;T&gt;()
.map_err(|_| ZxError::WRONG_TYPE)?;
if !handle.rights.contains(desired_rights) {
return Err(ZxError::ACCESS_DENIED);
}
Ok(object)
}
}
</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="对象传送器channel-对象"><a class="header" href="#对象传送器channel-对象">对象传送器Channel 对象</a></h1>
<h2 id="创建一对内核对象"><a class="header" href="#创建一对内核对象">创建一对内核对象</a></h2>
<blockquote>
<p>实现 Channel::create</p>
<p>讲一下互相持有对方 Weak 指针的目的,这里有不可避免的 unsafe</p>
</blockquote>
<h2 id="实现数据传输"><a class="header" href="#实现数据传输">实现数据传输</a></h2>
<blockquote>
<p>实现 read, write 函数read_write 单元测试</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="任务管理"><a class="header" href="#任务管理">任务管理</a></h1>
<p>本章我们来实现第一类内核对象任务管理Tasks</p>
<p>任务对象主要包括:线程 <code>Thread</code>,进程 <code>Process</code>,作业 <code>Job</code>。以及一些辅助性的对象,例如负责暂停任务执行的 <code>SuspendToken</code> 和负责处理异常的 <code>Exception</code></p>
<p>为了能够真实表现线程对象的行为,我们使用 Rust async 运行时 <a href="https://docs.rs/async-std/1.6.2/async_std/index.html"><code>async_std</code></a> 中的<strong>用户态协程</strong>来模拟<strong>内核线程</strong>
这样就可以在用户态的单元测试中检验实现的正确性。
考虑到未来这个 OS 会跑在裸机环境中,将会有不同的内核线程的实现,我们创建一个特殊的<strong>硬件抽象层Hardware Abstraction LayerHAL</strong>,来屏蔽底层平台的差异,对上提供一个统一的接口。
这个 HAL 的接口未来会根据需要进行扩充。</p>
<p>本章中我们只会实现运行一个程序所必需的最小功能子集,剩下的部分则留到跑起用户程序之后再按需实现。</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="zircon-任务管理体系"><a class="header" href="#zircon-任务管理体系">Zircon 任务管理体系</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="进程管理process-与-job-对象"><a class="header" href="#进程管理process-与-job-对象">进程管理Process 与 Job 对象</a></h1>
<h2 id="process-与-job"><a class="header" href="#process-与-job">Process 与 Job</a></h2>
<blockquote>
<p>介绍 Process 与 Job 的整体设计</p>
<p>实现 Process 和 Job 对象的基本框架,支持树状结构</p>
</blockquote>
<h2 id="job-policy-策略"><a class="header" href="#job-policy-策略">Job Policy 策略</a></h2>
<blockquote>
<p>实现 JobPolicy</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="线程管理thread-对象"><a class="header" href="#线程管理thread-对象">线程管理Thread 对象</a></h1>
<h2 id="线程状态"><a class="header" href="#线程状态">线程状态</a></h2>
<blockquote>
<p>状态转移:创建 -&gt; 运行 -&gt; 暂停 -&gt; 退出,最好有个状态机的图</p>
<p>实现 ThreadState最好能加一个单元测试来验证转移过程</p>
</blockquote>
<h2 id="线程寄存器上下文"><a class="header" href="#线程寄存器上下文">线程寄存器上下文</a></h2>
<blockquote>
<p>定义 ThreadState实现 read_statewrite_state</p>
</blockquote>
<h2 id="async-运行时和-hal-硬件抽象层"><a class="header" href="#async-运行时和-hal-硬件抽象层">Async 运行时和 HAL 硬件抽象层</a></h2>
<blockquote>
<p>简单介绍 async-std 的异步机制</p>
<p>介绍 HAL 的实现方法:弱链接</p>
<p>实现 hal_thread_spawn</p>
</blockquote>
<h2 id="线程启动"><a class="header" href="#线程启动">线程启动</a></h2>
<blockquote>
<p>将 HAL 接入 Thread::start编写单元测试验证能启动多线程</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="内存管理"><a class="header" href="#内存管理">内存管理</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="zircon-内存管理模型"><a class="header" href="#zircon-内存管理模型">Zircon 内存管理模型</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="物理内存vmo-对象"><a class="header" href="#物理内存vmo-对象">物理内存VMO 对象</a></h1>
<h2 id="vmo-简介"><a class="header" href="#vmo-简介">VMO 简介</a></h2>
<blockquote>
<p>根据文档梳理 VMO 的主要特性</p>
</blockquote>
<h2 id="实现-vmo-对象框架"><a class="header" href="#实现-vmo-对象框架">实现 VMO 对象框架</a></h2>
<blockquote>
<p>实现 VmObject 结构,其中定义 VmObjectTrait 接口,并提供三个具体实现 Paged, Physical, Slice</p>
</blockquote>
<h2 id="hal用文件模拟物理内存"><a class="header" href="#hal用文件模拟物理内存">HAL用文件模拟物理内存</a></h2>
<blockquote>
<p>初步介绍 mmap引出用文件模拟物理内存的思想</p>
<p>创建文件并用 mmap 线性映射到进程地址空间</p>
<p>实现 pmem_read, pmem_write</p>
</blockquote>
<h2 id="实现物理内存-vmo"><a class="header" href="#实现物理内存-vmo">实现物理内存 VMO</a></h2>
<blockquote>
<p>用 HAL 实现 VmObjectPhysical 的方法,并做单元测试</p>
</blockquote>
<h2 id="实现切片-vmo"><a class="header" href="#实现切片-vmo">实现切片 VMO</a></h2>
<blockquote>
<p>实现 VmObjectSlice并做单元测试</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="物理内存按页分配的-vmo"><a class="header" href="#物理内存按页分配的-vmo">物理内存:按页分配的 VMO</a></h1>
<h2 id="简介"><a class="header" href="#简介">简介</a></h2>
<blockquote>
<p>说明一下Zircon 的官方实现中为了高效支持写时复制,使用了复杂精巧的树状数据结构,但它同时也引入了复杂性和各种 Bug。
我们在这里只实现一个简单版本,完整实现留给读者自行探索。</p>
<p>介绍 commit 操作的意义和作用</p>
</blockquote>
<h2 id="hal物理内存管理"><a class="header" href="#hal物理内存管理">HAL物理内存管理</a></h2>
<blockquote>
<p>在 HAL 中实现 PhysFrame 和最简单的分配器</p>
</blockquote>
<h2 id="辅助结构blockrange-迭代器"><a class="header" href="#辅助结构blockrange-迭代器">辅助结构BlockRange 迭代器</a></h2>
<blockquote>
<p>实现 BlockRange</p>
</blockquote>
<h2 id="实现按页分配的-vmo"><a class="header" href="#实现按页分配的-vmo">实现按页分配的 VMO</a></h2>
<blockquote>
<p>实现 for_each_page, commit, read, write 函数</p>
</blockquote>
<h2 id="vmo-复制"><a class="header" href="#vmo-复制">VMO 复制</a></h2>
<blockquote>
<p>实现 create_child 函数</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="虚拟内存vmar-对象"><a class="header" href="#虚拟内存vmar-对象">虚拟内存VMAR 对象</a></h1>
<h2 id="vmar-简介"><a class="header" href="#vmar-简介">VMAR 简介</a></h2>
<h2 id="实现-vmar-对象框架"><a class="header" href="#实现-vmar-对象框架">实现 VMAR 对象框架</a></h2>
<blockquote>
<p>定义 VmAddressRangeVmMapping</p>
<p>实现 create_child, map, unmap, destroy 函数,并做单元测试验证地址空间分配</p>
</blockquote>
<h2 id="hal用-mmap-模拟页表"><a class="header" href="#hal用-mmap-模拟页表">HAL用 mmap 模拟页表</a></h2>
<blockquote>
<p>实现页表接口 map, unmap, protect</p>
</blockquote>
<h2 id="实现内存映射"><a class="header" href="#实现内存映射">实现内存映射</a></h2>
<blockquote>
<p>用 HAL 实现上面 VMAR 留空的部分,并做单元测试验证内存映射</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="用户程序"><a class="header" href="#用户程序">用户程序</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="zircon-用户程序"><a class="header" href="#zircon-用户程序">Zircon 用户程序</a></h1>
<h2 id="用户态启动流程"><a class="header" href="#用户态启动流程">用户态启动流程</a></h2>
<blockquote>
<p>kernel -&gt; userboot -&gt; bootsvc -&gt; component_manager -&gt; sh / device_manager</p>
<p>ZBI 与 bootfsZBI 中包含初始文件系统 bootfs内核将 ZBI 完整传递给 userboot由它负责解析并对其它进程提供文件服务</p>
</blockquote>
<h2 id="用户程序的组成"><a class="header" href="#用户程序的组成">用户程序的组成</a></h2>
<blockquote>
<p>内核不直接参与用户程序的加载工作(第一个进程除外)</p>
<p>用户程序强制使用 PIC 和 PIE位置无关代码</p>
<p>内存地址空间组成Program, Stack, vDSO, Dylibs</p>
<p>通过 Channel 传递启动信息和句柄</p>
</blockquote>
<h2 id="加载-elf-文件"><a class="header" href="#加载-elf-文件">加载 ELF 文件</a></h2>
<blockquote>
<p>简单介绍 ELF 文件的组成结构</p>
<p>实现 VmarExt::load_from_elf 函数</p>
</blockquote>
<h2 id="系统调用的跳板vdso"><a class="header" href="#系统调用的跳板vdso">系统调用的跳板vDSO</a></h2>
<blockquote>
<p>介绍 vDSO 的作用</p>
<p>如何修改 vDSO 源码libzircon将 syscall 改为函数调用</p>
<p>加载 vDSO 时修改 vDSO 代码段,填入跳转地址</p>
</blockquote>
<h2 id="第一个用户程序userboot"><a class="header" href="#第一个用户程序userboot">第一个用户程序userboot</a></h2>
<blockquote>
<p>实现 zircon-loader 中的 run_userboot 函数</p>
<p>能够进入用户态并在第一个系统调用时跳转回来</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="上下文切换"><a class="header" href="#上下文切换">上下文切换</a></h1>
<blockquote>
<p>本节介绍 trapframe-rs 中 <a href="https://github.com/rcore-os/trapframe-rs/blob/master/src/arch/x86_64/fncall.rs">fncall.rs</a> 的魔法实现</p>
</blockquote>
<h2 id="保存和恢复通用寄存器"><a class="header" href="#保存和恢复通用寄存器">保存和恢复通用寄存器</a></h2>
<blockquote>
<p>定义 UserContext 结构体</p>
<p>保存 callee-saved 寄存器到栈上,恢复 UserContext 寄存器,进入用户态,反之亦然</p>
</blockquote>
<h2 id="找回内核上下文线程局部存储-与-fs-寄存器"><a class="header" href="#找回内核上下文线程局部存储-与-fs-寄存器">找回内核上下文:线程局部存储 与 FS 寄存器</a></h2>
<blockquote>
<p>在用户程序跳转回内核代码的那一刻,如何在不破坏用户寄存器的情况下切换回内核栈?</p>
<p>进入用户态前,将内核栈指针保存在内核 glibc 的 TLS 区域中。为此我们需要查看 glibc 源码,找到一个空闲位置。</p>
<p>Linux 和 macOS 下如何分别通过系统调用设置 fsbase / gsbase</p>
</blockquote>
<h2 id="测试"><a class="header" href="#测试">测试</a></h2>
<blockquote>
<p>编写单元测试验证上述过程</p>
</blockquote>
<h2 id="macos-的麻烦动态二进制修改"><a class="header" href="#macos-的麻烦动态二进制修改">macOS 的麻烦:动态二进制修改</a></h2>
<blockquote>
<p>由于 macOS 用户程序无法修改 fs 寄存器,当运行相关指令时会访问非法内存地址触发段错误。</p>
<p>我们需要实现段错误信号处理函数,并在其中动态修改用户程序指令,将 fs 改为 gs。</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="系统调用"><a class="header" href="#系统调用">系统调用</a></h1>
<h2 id="获取系统调用参数"><a class="header" href="#获取系统调用参数">获取系统调用参数</a></h2>
<blockquote>
<p>从寄存器中获取参数</p>
</blockquote>
<h2 id="系统调用上下文与处理函数"><a class="header" href="#系统调用上下文与处理函数">系统调用上下文与处理函数</a></h2>
<blockquote>
<p>定义 Syscall 结构体,实现 syscall 函数</p>
</blockquote>
<h2 id="实现第一个系统调用"><a class="header" href="#实现第一个系统调用">实现第一个系统调用</a></h2>
<blockquote>
<p>实现 sys_channel_read 和 sys_debuglog_write</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="信号和等待"><a class="header" href="#信号和等待">信号和等待</a></h1>
<div style="break-before: page; page-break-before: always;"></div><h1 id="等待内核对象的信号"><a class="header" href="#等待内核对象的信号">等待内核对象的信号</a></h1>
<h2 id="信号与等待机制简介"><a class="header" href="#信号与等待机制简介">信号与等待机制简介</a></h2>
<h2 id="在内核对象中加入信号"><a class="header" href="#在内核对象中加入信号">在内核对象中加入信号</a></h2>
<blockquote>
<p>定义 Signal 结构体</p>
<p>在 KObjectBase 中加入 signal 和 callbacks 变量,实现 signal 系列函数,并做单元测试</p>
</blockquote>
<h2 id="实现信号等待-future"><a class="header" href="#实现信号等待-future">实现信号等待 Future</a></h2>
<blockquote>
<p>实现 wait_signal 函数,并做单元测试</p>
</blockquote>
<h2 id="利用-select-组合子实现多对象等待"><a class="header" href="#利用-select-组合子实现多对象等待">利用 select 组合子实现多对象等待</a></h2>
<blockquote>
<p>实现 wait_signal_many 函数,并做单元测试</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="同时等待多个信号port-对象"><a class="header" href="#同时等待多个信号port-对象">同时等待多个信号Port 对象</a></h1>
<h2 id="port-对象简介"><a class="header" href="#port-对象简介">Port 对象简介</a></h2>
<blockquote>
<p>同时提及一下 Linux 的 epoll 机制作为对比</p>
</blockquote>
<h2 id="实现-port-对象框架"><a class="header" href="#实现-port-对象框架">实现 Port 对象框架</a></h2>
<blockquote>
<p>定义 Port 和 PortPacket 结构体</p>
</blockquote>
<h2 id="实现事件推送和等待"><a class="header" href="#实现事件推送和等待">实现事件推送和等待</a></h2>
<blockquote>
<p>实现 KernelObject::send_signal_to_port 和 Port::wait 函数,并做单元测试</p>
</blockquote>
<div style="break-before: page; page-break-before: always;"></div><h1 id="实现更多eventpair-timer-对象"><a class="header" href="#实现更多eventpair-timer-对象">实现更多EventPair, Timer 对象</a></h1>
<h2 id="event-对象"><a class="header" href="#event-对象">Event 对象</a></h2>
<h2 id="eventpair-对象"><a class="header" href="#eventpair-对象">EventPair 对象</a></h2>
<h2 id="hal定时器"><a class="header" href="#hal定时器">HAL定时器</a></h2>
<blockquote>
<p>实现 timer_now, timer_set在此基础上实现 SleepFuture</p>
</blockquote>
<h2 id="timer-对象"><a class="header" href="#timer-对象">Timer 对象</a></h2>
<div style="break-before: page; page-break-before: always;"></div><h1 id="用户态同步互斥futex-对象"><a class="header" href="#用户态同步互斥futex-对象">用户态同步互斥Futex 对象</a></h1>
<h2 id="futex-机制简介"><a class="header" href="#futex-机制简介">Futex 机制简介</a></h2>
<blockquote>
<p>Futex 是现代 OS 中用户态同步互斥的唯一底层设施</p>
<p>为什么快:利用共享内存中的原子变量,避免进入内核</p>
</blockquote>
<h2 id="实现基础元语wait-和-wake"><a class="header" href="#实现基础元语wait-和-wake">实现基础元语wait 和 wake</a></h2>
<blockquote>
<p>实现 wait 和 wake 函数,并做单元测试</p>
</blockquote>
<h2 id="实现高级操作"><a class="header" href="#实现高级操作">实现高级操作</a></h2>
<blockquote>
<p>实现 Zircon 中定义的复杂 API</p>
</blockquote>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</nav>
</div>
<script type="text/javascript">
window.playground_copyable = true;
</script>
<script src="ace.js" type="text/javascript" charset="utf-8"></script>
<script src="editor.js" type="text/javascript" charset="utf-8"></script>
<script src="mode-rust.js" type="text/javascript" charset="utf-8"></script>
<script src="theme-dawn.js" type="text/javascript" charset="utf-8"></script>
<script src="theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
<script type="text/javascript">
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
</body>
</html>