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.

3481 lines
192 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><li class="chapter-item expanded "><a href="ch06-00-hal.html"><strong aria-hidden="true">6.</strong> 硬件抽象层</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch06-01-zcore-hal-unix.html"><strong aria-hidden="true">6.1.</strong> ✅ UNIX硬件抽象层</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><strong>对象Object</strong> 具备属性和行为的客体。客体之间可有各种联系。从简单的<strong>整数</strong>到复杂的<strong>操作系统进程</strong>等都可看做对象,它不仅仅表示具体的事物,还能表示抽象的规则、计划或事件。</li>
<li><strong>句柄Handle</strong>标识对象的符号也可看成是一种指向对象的变量也可称为标识符、引用、ID等</li>
<li><strong>权限Rights</strong>:是指对象的访问者被允许在对象上执行的操作,即对象的访问权限。当对象访问者打开对象的句柄,该句柄具有对其对象的访问权限的某种组合。</li>
</ul>
<p>对于Zircon与对象、句柄、权限的关系可简单地表述为</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 值,每次分配就原子地 加<code>1</code>
ID 类型使用 <code>u64</code>,保证了数值空间足够大,在有生之年都不用担心溢出问题。在 Zircon 中 ID 从 1024 开始分配1024 以下保留作内核内部使用。</p>
<p>另外注意这里 <code>new</code> 函数返回类型不是 <code>Self</code> 而是 <code>Arc&lt;Self&gt;</code>,这是的<a href="https://doc.rust-lang.org/std/sync/struct.Arc.html"> <code>Arc</code> </a>为了以后方便并行处理而做的统一约定。</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>按照<a href="https://docs.rs/downcast-rs/1.2.0/downcast_rs/index.html"><code>downcast-rs</code></a> 文档的描述,我们要为自己的接口实现向下转换,只需以下修改:</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`
#[allow(dead_code)]
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><h4 id="对象管理器process-对象"><a class="header" href="#对象管理器process-对象">对象管理器Process 对象</a></h4>
<h2 id="句柄操作内核对象的桥梁"><a class="header" href="#句柄操作内核对象的桥梁">句柄——操作内核对象的桥梁</a></h2>
<p>在1.1中我们用Rust语言实现了一个最核心的内核对象在本小节我们将逐步了解与内核对象相关的三个重要概念中的其他两个<strong>句柄Handle和权限Rights</strong></p>
<p>句柄是允许用户程序引用内核对象引用的一种内核结构,它可以被认为是与特定内核对象的会话或连接。</p>
<p>通常情况下,多个进程通过不同的句柄同时访问同一个对象。对象可能有多个句柄(在一个或多个进程中)引用它们。但单个句柄只能绑定到单个进程或绑定到内核。</p>
<h3 id="定义句柄"><a class="header" href="#定义句柄">定义句柄</a></h3>
<p>在 object 模块下定义一个子模块:</p>
<pre><code class="language-rust noplaypen">// src/object/mod.rs
mod handle;
pub use self::handle::*;
</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,
}
</code></pre>
<p>一个Handle包含object和right两个字段object是实现了<code>KernelObject</code>Trait的内核对象Rights是该句柄的权限我们将在下面提到它。</p>
<p>Arc<T>是一个可以在多线程上使用的引用计数类型,这个计数会随着 <code>Arc&lt;T&gt;</code> 的创建或复制而增加,并当 <code>Arc&lt;T&gt;</code> 生命周期结束被回收时减少。当这个计数变为零之后,这个计数变量本身以及被引用的变量都会从堆上被回收。</p>
<p>我们为什么要在这里使用Arc智能指针呢</p>
<p>绝大多数内核对象的析构都发生在句柄数量为 0 时也就是最后一个指向内核对象的Handle被关闭该对象也随之消亡抑或进入一种无法撤销的最终状态。很明显这与Arc<T>天然的契合。</p>
<h2 id="控制句柄的权限rights"><a class="header" href="#控制句柄的权限rights">控制句柄的权限——Rights</a></h2>
<p>上文的Handle中有一个字段是rights也就是句柄的权限。顾名思义权限规定该句柄对引用的对象可以进行何种操作。</p>
<p>当不同的权限和同一个对象绑定在一起时,也就形成了不同的句柄。</p>
<h3 id="定义权限"><a class="header" href="#定义权限">定义权限</a></h3>
<p>在 object 模块下定义一个子模块:</p>
<pre><code class="language-rust noplaypen">// src/object/mod.rs
mod rights;
pub use self::rights::*;
</code></pre>
<p>权限就是u32的一个数字</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;
...
}
</code></pre>
<p><a href="https://docs.rs/bitflags/1.2.1/bitflags/"><strong>bitflags</strong></a> 是一个 Rust 中常用来比特标志位的 crate 。它提供了 一个 <code>bitflags!</code> 宏,如上面的代码段所展示的那样,借助 <code>bitflags!</code> 宏我们将一个 <code>u32</code> 的 rights 包装为一个 <code>Rights</code> 结构体。注意,在使用之前我们需要引入该 crate 的依赖:</p>
<pre><code class="language-rust noplaypen"><span class="boring">Cargo.toml
</span>
[dependencies]
bitflags = &quot;1.2&quot;
</code></pre>
<p>定义好权限之后,我们回到句柄相关方法的实现。</p>
<p>首先是最简单的部分创建一个handle很显然我们需要提供两个参数分别是句柄关联的内核对象和句柄的权限。</p>
<pre><code class="language-rust noplaypen">impl Handle {
/// 创建一个新句柄
pub fn new(object: Arc&lt;dyn KernelObject&gt;, rights: Rights) -&gt; Self {
Handle { object, rights }
}
}
</code></pre>
<h3 id="测试"><a class="header" href="#测试">测试</a></h3>
<p>好啦,让我们来测试一下!</p>
<pre><code class="language-rust noplaypen">#[cfg(test)]
mod tests {
use super::*;
use crate::object::DummyObject;
#[test]
fn new_obj_handle() {
let obj = DummyObject::new();
let handle1 = Handle::new(obj.clone(), Rights::BASIC);
}
}
</code></pre>
<h2 id="句柄存储的载体process"><a class="header" href="#句柄存储的载体process">句柄存储的载体——Process</a></h2>
<p>实现完了句柄之后,我们开始考虑,句柄是存储在哪里的呢?</p>
<p>通过前面的讲解很明显Process拥有内核对象句柄也就是说句柄存储在Process中所以我们先来实现一个Process</p>
<h3 id="实现空的process对象"><a class="header" href="#实现空的process对象">实现空的process对象</a></h3>
<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;
</code></pre>
<p>handles使用BTreeMap存储的key是HandleValuevalue就是句柄。通过HandleValue实现对句柄的增删操作。HandleValue实际上就是u32类型是别名。</p>
<p>把内部对象ProcessInner用自旋锁Mutex包起来保证了互斥访问因为Mutex会帮我们处理好并发问题这一点已经在1.1节中详细说明。</p>
<p>接下来我们实现创建一个Process的方法</p>
<pre><code class="language-rust noplaypen">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>
<h4 id="单元测试"><a class="header" href="#单元测试">单元测试</a></h4>
<p>我们已经实现了创建一个Process的方法下面我们写一个单元测试</p>
<pre><code class="language-rust noplaypen">#[test]
fn new_proc() {
let proc = Process::new();
assert_eq!(proc.type_name(), &quot;Process&quot;);
assert_eq!(proc.name(), &quot;&quot;);
proc.set_name(&quot;proc1&quot;);
assert_eq!(proc.name(), &quot;proc1&quot;);
assert_eq!(
format!(&quot;{:?}&quot;, proc),
format!(&quot;Process({}, \&quot;proc1\&quot;)&quot;, proc.id())
);
let obj: Arc&lt;dyn KernelObject&gt; = proc;
assert_eq!(obj.type_name(), &quot;Process&quot;);
assert_eq!(obj.name(), &quot;proc1&quot;);
obj.set_name(&quot;proc2&quot;);
assert_eq!(obj.name(), &quot;proc2&quot;);
assert_eq!(
format!(&quot;{:?}&quot;, obj),
format!(&quot;Process({}, \&quot;proc2\&quot;)&quot;, obj.id())
);
}
</code></pre>
<h3 id="process相关方法"><a class="header" href="#process相关方法">Process相关方法</a></h3>
<h4 id="插入句柄"><a class="header" href="#插入句柄">插入句柄</a></h4>
<p>在Process中添加一个新的handle返回值是一个handleValue也就是u32</p>
<pre><code class="language-rust noplaypen">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();
// 插入BTreeMap
inner.handles.insert(value, handle);
value
}
</code></pre>
<h4 id="移除句柄"><a class="header" href="#移除句柄">移除句柄</a></h4>
<p>删除Process中的一个句柄</p>
<pre><code class="language-rust noplaypen">pub fn remove_handle(&amp;self, handle_value: HandleValue) {
self.inner.lock().handles.remove(&amp;handle_value);
}
</code></pre>
<h4 id="根据句柄查找内核对象"><a class="header" href="#根据句柄查找内核对象">根据句柄查找内核对象</a></h4>
<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>
<h4 id="zxresult"><a class="header" href="#zxresult">ZxResult</a></h4>
<p>ZxResult是表示Zircon状态的i32值值空间划分如下</p>
<ul>
<li>0:ok</li>
<li>负值:由系统定义(也就是这个文件)</li>
<li>正值:被保留,用于协议特定的错误值,永远不会被系统定义。</li>
</ul>
<pre><code class="language-rust noplaypen">pub type ZxResult&lt;T&gt; = Result&lt;T, ZxError&gt;;
#[allow(non_camel_case_types, dead_code)]
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum ZxError {
OK = 0,
...
/// 一个不指向handle的特定的handle value
BAD_HANDLE = -11,
/// 操作主体对于执行这个操作来说是错误的类型
/// 例如: 尝试执行 message_read 在 thread handle.
WRONG_TYPE = -12,
// 权限检查错误
// 调用者没有执行该操作的权限
ACCESS_DENIED = -30,
}
</code></pre>
<p>ZxResult<T>相当于Result&lt;T, ZxError&gt;,也就相当于我们自己定义了一种错误。</p>
<h3 id="单元测试-1"><a class="header" href="#单元测试-1">单元测试</a></h3>
<p>目前为止我们已经实现了Process最基础的方法下面我们来运行一个单元测试</p>
<pre><code class="language-rust noplaypen">fn proc_handle() {
let proc = Process::new();
let handle = Handle::new(proc.clone(), Rights::DEFAULT_PROCESS);
let handle_value = proc.add_handle(handle);
let object1: Arc&lt;Process&gt; = proc
.get_object_with_rights(handle_value, Rights::DEFAULT_PROCESS)
.expect(&quot;failed to get object&quot;);
assert!(Arc::ptr_eq(&amp;object1, &amp;proc));
proc.remove_handle(handle_value);
}
</code></pre>
<h2 id="总结-1"><a class="header" href="#总结-1">总结</a></h2>
<p>在这一节中我们实现了内核对象的两个重要的概念句柄Handle和权限Rights同时实现了句柄存储的载体——Process并且实现了Process的基本方法这将是我们继续探索zCore的基础。</p>
<p>在下一节中我们将介绍内核对象的传输器——管道Channel</p>
<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>
<p>通道Channel是由一定数量的字节数据和一定数量的句柄组成的双向消息传输。</p>
<h2 id="用于ipc的内核对象"><a class="header" href="#用于ipc的内核对象">用于IPC的内核对象</a></h2>
<p>Zircon中用于IPC的内核对象主要有Channel、Socket和FIFO。这里我们主要介绍一下前两个。</p>
<blockquote>
<p><strong>进程间通信</strong><strong>IPC</strong><em>Inter-Process Communication</em>),指至少两个进程或线程间传送数据或信号的一些技术或方法。进程是计算机系统分配资源的最小单位(进程是分配资源最小的单位,而线程是调度的最小单位,线程共用进程资源)。每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。举一个典型的例子,使用进程间通信的两个应用可以被分类为客户端和服务器,客户端进程请求数据,服务端回复客户端的数据请求。有一些应用本身既是服务器又是客户端,这在分布式计算中,时常可以见到。这些进程可以运行在同一计算机上或网络连接的不同计算机上。</p>
</blockquote>
<p><code>Socket</code><code>Channel</code>都是双向和双端的IPC相关的<code>Object</code>。创建<code>Socket</code><code>Channel</code>将返回两个不同的<code>Handle</code>,分别指向<code>Socket</code><code>Channel</code>的两端。与channel的不同之处在于socket仅能传输数据而不移动句柄而channel可以传递句柄。</p>
<ul>
<li><code>Socket</code>是面向流的对象,可以通过它读取或写入以一个或多个字节为单位的数据。</li>
<li><code>Channel</code>是面向数据包的对象并限制消息的大小最多为64K如果有改变可能会更小以及最多1024个<code>Handle</code>挂载到同一消息上(如果有改变,同样可能会更小)。</li>
</ul>
<p><code>Handle</code>被写入到<code>Channel</code>中时,在发送端<code>Process</code>中将会移除这些<code>Handle</code>。同时携带<code>Handle</code>的消息从<code>Channel</code>中被读取时,该<code>Handle</code>也将被加入到接收端<code>Process</code>中。在这两个时间点之间时,<code>Handle</code>将同时存在于两端(以保证它们指向的<code>Object</code>继续存在而不被销毁),除非<code>Channel</code>写入方向一端被关闭,这种情况下,指向该端点的正在发送的消息将被丢弃,并且它们包含的任何句柄都将被关闭。</p>
<h2 id="channel"><a class="header" href="#channel">Channel</a></h2>
<p>Channel是唯一一个能传递handle的IPC其他只能传递消息。通道有两个端点<code>endpoints</code>,对于代码实现来说,<strong>通道是虚拟的,我们实际上是用通道的两个端点来描述一个通道</strong>。两个端点各自要维护一个消息队列,在一个端点写消息,实际上是把消息写入<strong>另一个端点</strong>的消息队列队尾;在一个端点读消息,实际上是从<strong>当前端点</strong>的消息队列的队头读出一个消息。</p>
<p>消息通常含有<code>data</code><code>handles</code>两部分,我们这里将消息封装为<code>MessagePacket</code>结构体,结构体中含有上述两个字段:</p>
<pre><code class="language-rust noplaypen">#[derive(Default)]
pub struct MessagePacket {
/// message packet携带的数据data
pub data: Vec&lt;u8&gt;,
/// message packet携带的句柄Handle
pub handles: Vec&lt;Handle&gt;,
}
</code></pre>
<h3 id="实现空的channel对象"><a class="header" href="#实现空的channel对象">实现空的Channel对象</a></h3>
<p><code>src</code>目录下创建一个<code>ipc</code>目录,在<code>ipc</code>模块下定义一个子模块<code>channel</code></p>
<pre><code class="language-rust noplaypen">// src/ipc/mod.rs
use super::*;
mod channel;
pub use self::channel::*;
</code></pre>
<p><code>ipc.rs</code>中引入<code>crate</code></p>
<pre><code class="language-rust noplaypen">// src/ipc/channel.rs
use {
super::*,
crate::error::*,
crate::object::*,
alloc::collections::VecDeque,
alloc::sync::{Arc, Weak},
spin::Mutex,
};
</code></pre>
<p>把在上面提到的<code>MessagePacket</code>结构体添加到该文件中。</p>
<p>下面我们添加Channel结构体</p>
<pre><code class="language-rust noplaypen">// src/ipc/channel.rs
pub struct Channel {
base: KObjectBase,
peer: Weak&lt;Channel&gt;,
recv_queue: Mutex&lt;VecDeque&lt;T&gt;&gt;,
}
type T = MessagePacket;
</code></pre>
<p><code>peer</code>代表当前端点所在管道的另一个端点,两端的结构体分别持有对方的<code>Weak</code>引用,并且两端的结构体将分别通过<code>Arc</code>引用作为内核对象而被内核中的其他数据结构引用这一部分我们将在创建Channel实例时提到。</p>
<p><code>recv_queue</code>代表当前端点维护的消息队列,它使用<code>VecDeque</code>来存放<code>MessagePacket</code>,可以通过<code>pop_front()</code><code>push_back</code>等方法在队头弹出数据和在队尾压入数据。</p>
<p>用使用宏自动实现 <code>KernelObject</code> trait 使用channel类型名并添加两个函数。</p>
<pre><code class="language-rust noplaypen">impl_kobject!(Channel
fn peer(&amp;self) -&gt; ZxResult&lt;Arc&lt;dyn KernelObject&gt;&gt; {
let peer = self.peer.upgrade().ok_or(ZxError::PEER_CLOSED)?;
Ok(peer)
}
fn related_koid(&amp;self) -&gt; KoID {
self.peer.upgrade().map(|p| p.id()).unwrap_or(0)
}
);
</code></pre>
<h3 id="实现创建channel的方法"><a class="header" href="#实现创建channel的方法">实现创建Channel的方法</a></h3>
<p>下面我们来实现创建一个<code>Channel</code>的方法:</p>
<pre><code class="language-rust noplaypen">impl Channel {
#[allow(unsafe_code)]
pub fn create() -&gt; (Arc&lt;Self&gt;, Arc&lt;Self&gt;) {
let mut channel0 = Arc::new(Channel {
base: KObjectBase::default(),
peer: Weak::default(),
recv_queue: Default::default(),
});
let channel1 = Arc::new(Channel {
base: KObjectBase::default(),
peer: Arc::downgrade(&amp;channel0),
recv_queue: Default::default(),
});
// no other reference of `channel0`
unsafe {
Arc::get_mut_unchecked(&amp;mut channel0).peer = Arc::downgrade(&amp;channel1);
}
(channel0, channel1)
}
</code></pre>
<p>该方法的返回值是两端点结构体Channel<code>Arc</code>引用,这将作为内核对象被内核中的其他数据结构引用。两个端点互相持有对方<code>Weak</code>指针这是因为一个端点无需引用计数为0只要<code>strong_count</code>为0就可以被清理掉即使另一个端点指向它。</p>
<blockquote>
<p>rust 语言并没有提供垃圾回收 (GC, Garbage Collection ) 的功能, 不过它提供了最简单的引用计数包装类型 <code>Rc</code>,这种引用计数功能也是早期 GC 常用的方法, 但是引用计数不能解决循环引用。那么如何 fix 这个循环引用呢?答案是 <code>Weak</code> 指针,只增加引用逻辑,不共享所有权,即不增加 strong reference count。由于 <code>Weak</code> 指针指向的对象可能析构了,所以不能直接解引用,要模式匹配,再 upgrade。</p>
</blockquote>
<p>下面我们来分析一下这个<code>unsafe</code>代码块:</p>
<pre><code class="language-rust noplaypen">unsafe {
Arc::get_mut_unchecked(&amp;mut channel0).peer = Arc::downgrade(&amp;channel1);
}
</code></pre>
<p>由于两端的结构体将分别通过 <code>Arc</code> 引用,作为内核对象而被内核中的其他数据结构使用。因此,在同时初始化两端的同时,将必须对某一端的 Arc 指针进行获取可变引用的操作,即<code>get_mut_unchecked</code>接口。当 <code>Arc</code> 指针的引用计数不为 <code>1</code> 时,这一接口是非常不安全的,但是在当前情境下,我们使用这一接口进行<code>IPC</code> 对象的初始化,安全性是可以保证的。</p>
<h3 id="单元测试-2"><a class="header" href="#单元测试-2">单元测试</a></h3>
<p>下面我们写一个单元测试,来验证我们写的<code>create</code>方法:</p>
<pre><code class="language-rust noplaypen">#[test]
fn test_basics() {
let (end0, end1) = Channel::create();
assert!(Arc::ptr_eq(
&amp;end0.peer().unwrap().downcast_arc().unwrap(),
&amp;end1
));
assert_eq!(end0.related_koid(), end1.id());
drop(end1);
assert_eq!(end0.peer().unwrap_err(), ZxError::PEER_CLOSED);
assert_eq!(end0.related_koid(), 0);
}
</code></pre>
<h3 id="实现数据传输"><a class="header" href="#实现数据传输">实现数据传输</a></h3>
<p>Channel中的数据传输可以理解为<code>MessagePacket</code>在两个端点之间的传输,那么谁可以读写消息呢?</p>
<p>有一个句柄与通道端点相关联持有该句柄的进程被视为所有者owner。所以是持有与通道端点关联句柄的进程可以读取或写入消息或将通道端点发送到另一个进程。</p>
<p><code>MessagePacket</code>被写入通道时,它们会从发送进程中删除。当从通道读取<code>MessagePacket</code>时,<code>MessagePacket</code>的句柄被添加到接收进程中。</p>
<h4 id="read"><a class="header" href="#read">read</a></h4>
<p>获取当前端点的<code>recv_queue</code>,从队头中读取一条消息,如果能读取到消息,返回<code>Ok</code>,否则返回错误信息。</p>
<pre><code class="language-rust noplaypen">pub fn read(&amp;self) -&gt; ZxResult&lt;T&gt; {
let mut recv_queue = self.recv_queue.lock();
if let Some(_msg) = recv_queue.front() {
let msg = recv_queue.pop_front().unwrap();
return Ok(msg);
}
if self.peer_closed() {
Err(ZxError::PEER_CLOSED)
} else {
Err(ZxError::SHOULD_WAIT)
}
}
</code></pre>
<h4 id="write"><a class="header" href="#write">write</a></h4>
<p>先获取当前端点对应的另一个端点的<code>Weak</code>指针,通过<code>upgrade</code>接口升级为<code>Arc</code>指针,从而获取到对应的结构体对象。在它的<code>recv_queue</code>队尾push一个<code>MessagePacket</code></p>
<pre><code class="language-rust noplaypen">pub fn write(&amp;self, msg: T) -&gt; ZxResult {
let peer = self.peer.upgrade().ok_or(ZxError::PEER_CLOSED)?;
peer.push_general(msg);
Ok(())
}
fn push_general(&amp;self, msg: T) {
let mut send_queue = self.recv_queue.lock();
send_queue.push_back(msg);
}
</code></pre>
<h3 id="单元测试-3"><a class="header" href="#单元测试-3">单元测试</a></h3>
<p>下面我们写一个单元测试,验证我们上面写的<code>read</code><code>write</code>两个方法:</p>
<pre><code class="language-rust noplaypen">#[test]
fn read_write() {
let (channel0, channel1) = Channel::create();
// write a message to each other
channel0
.write(MessagePacket {
data: Vec::from(&quot;hello 1&quot;),
handles: Vec::new(),
})
.unwrap();
channel1
.write(MessagePacket {
data: Vec::from(&quot;hello 0&quot;),
handles: Vec::new(),
})
.unwrap();
// read message should success
let recv_msg = channel1.read().unwrap();
assert_eq!(recv_msg.data.as_slice(), b&quot;hello 1&quot;);
assert!(recv_msg.handles.is_empty());
let recv_msg = channel0.read().unwrap();
assert_eq!(recv_msg.data.as_slice(), b&quot;hello 0&quot;);
assert!(recv_msg.handles.is_empty());
// read more message should fail.
assert_eq!(channel0.read().err(), Some(ZxError::SHOULD_WAIT));
assert_eq!(channel1.read().err(), Some(ZxError::SHOULD_WAIT));
}
</code></pre>
<h2 id="总结-2"><a class="header" href="#总结-2">总结</a></h2>
<p>在这一节中我们实现了唯一一个可以传递句柄的对象传输器——Channel我们先了解的Zircon中主要的IPC内核对象再介绍了Channel如何创建和实现read和write函数的细节。</p>
<p>本章我们学习了中最核心的几个内核对象,在下一章中,我们将学习<code>Zircon</code>的任务管理体系和进程、线程管理的对象。</p>
<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>
<p>线程Thread表示包含进程Proess所拥有的地址空间中的多个执行控制流CPU寄存器堆栈等。进程属于作业Job作业定义了各种资源限制。作业一直由父级作业parent Jobs拥有一直到根作业Root Job为止根作业是内核在启动时创建并传递给<a href="https://fuchsia.dev/docs/concepts/booting/userboot"><code>userboot</code>(第一个开始执行的用户进程)</a></p>
<p>如果没有作业句柄Job Handle则进程中的线程无法创建另一个进程或另一个作业。</p>
<p><a href="https://fuchsia.dev/docs/concepts/booting/program_loading">程序加载</a>由内核层以上的用户空间工具和协议提供。</p>
<p>一些相关的系统调用:</p>
<p><a href="https://fuchsia.dev/docs/reference/syscalls/process_create"><code>zx_process_create()</code></a>, <a href="https://fuchsia.dev/docs/reference/syscalls/process_start"><code>zx_process_start()</code></a>, <a href="https://fuchsia.dev/docs/reference/syscalls/thread_create"><code>zx_thread_create()</code></a>, <a href="https://fuchsia.dev/docs/reference/syscalls/thread_start"><code>zx_thread_start()</code></a></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="进程管理process-与-job-对象"><a class="header" href="#进程管理process-与-job-对象">进程管理Process 与 Job 对象</a></h1>
<blockquote>
<p>介绍 Process 与 Job 的整体设计</p>
<p>实现 Process 和 Job 对象的基本框架,支持树状结构</p>
</blockquote>
<h2 id="作业job"><a class="header" href="#作业job">作业Job</a></h2>
<h3 id="概要-1"><a class="header" href="#概要-1">概要</a></h3>
<p>作业是一组进程可能还包括其他作业。作业用于跟踪执行内核操作的特权即使用各种选项进行各种syscall以及跟踪和限制基本资源例如内存CPU的消耗。每个进程都属于一个作业。作业也可以嵌套并且除根作业外的每个作业都属于一个作业。</p>
<h3 id="描述"><a class="header" href="#描述">描述</a></h3>
<p>作业是包含以下内容的对象:</p>
<ul>
<li>对父作业的引用</li>
<li>一组子作业(每个子作业的父作业既是这个作业)</li>
<li>一组成员进程</li>
<li>一套策略Policy</li>
</ul>
<p>由多个进程组成的“应用程序”可作为单个实体,被作业基于一套策略进行控制。</p>
<h3 id="作业策略job-policy"><a class="header" href="#作业策略job-policy">作业策略Job Policy</a></h3>
<p><a href="https://fuchsia.dev/fuchsia-src/concepts/settings/policy/policy_concepts?hl=en">策略policy</a> 可在Kernel运行时动态修改系统的各种配置setting。作业策略主要涉及作业安全性和资源使用的条件Condition限制。</p>
<h4 id="策略的行为policyaction"><a class="header" href="#策略的行为policyaction">策略的行为PolicyAction</a></h4>
<p>策略的行为包括:</p>
<ul>
<li>Allow 允许条件</li>
<li>Deny 拒绝条件</li>
<li>AllowException 通过 debugt port 生成异常,异常处理完毕后可恢复执行且运行条件</li>
<li>DenyException 通过 debugt port 生成异常,异常处理完毕后可恢复执行</li>
<li>Kill 杀死进程</li>
</ul>
<h4 id="应用策略时的条件-policycondition"><a class="header" href="#应用策略时的条件-policycondition">应用策略时的条件 PolicyCondition</a></h4>
<p>应用策略时的条件包括:</p>
<ul>
<li>BadHandle 此作业下的某个进程正在尝试发出带有无效句柄的syscall。在这种情况下<code>PolicyAction::Allow</code>并且<code>PolicyAction::Deny</code>是等效的如果syscall返回它将始终返回错误ZX_ERR_BAD_HANDLE。</li>
<li>WrongObject此作业下的某个进程正在尝试发出带有不支持该操作的句柄的syscall。</li>
<li>VmarWx此作业下的进程正在尝试映射具有写执行访问权限的地址区域。</li>
<li>NewAny代表上述所有ZX_NEW条件的特殊条件例如NEW_VMONEW_CHANNELNEW_EVENTNEW_EVENTPAIRNEW_PORTNEW_SOCKETNEW_FIFO和任何将来的ZX_NEW策略。这将包括不需要父对象来创建的所有新内核对象。</li>
<li>NewVMO此作业下的某个进程正在尝试创建新的vm对象。</li>
<li>NewChannel此作业下的某个进程正在尝试创建新通道。</li>
<li>NewEvent此作业下的一个进程正在尝试创建一个新事件。</li>
<li>NewEventPair此作业下的某个进程正在尝试创建新的事件对。</li>
<li>NewPort此作业下的进程正在尝试创建新端口。</li>
<li>NewSocket此作业下的进程正在尝试创建新的套接字。</li>
<li>NewFIFO此工作下的一个进程正在尝试创建一个新的FIFO。</li>
<li>NewTimer此作业下的某个进程正在尝试创建新的计时器。</li>
<li>NewProcess此作业下的进程正在尝试创建新进程。</li>
<li>NewProfile此作业下的一个进程正在尝试创建新的配置文件。</li>
<li>AmbientMarkVMOExec此作业下的某个进程正在尝试使用带有ZX_HANDLE_INVALID的zx_vmo_replace_as_executable作为第二个参数而不是有效的ZX_RSRC_KIND_VMEX。</li>
</ul>
<h2 id="进程process"><a class="header" href="#进程process">进程Process</a></h2>
<p>进程是传统意义上程序的一个运行实例,包含一组指令和数据,这些指令将由一个或多个线程执行,并拥有一组资源。在具体实现上,进程包括如下内容:</p>
<ul>
<li>Handles :大部分是进程用到的资源对象的句柄</li>
<li>Virtual Memory Address Regions进程所在的内存地址空间</li>
<li>Threads进程包含的线程组</li>
</ul>
<p>进程包含在作业Job的管理范畴之中。从资源和权限限制以及生命周期控制的角度来看允许将由多个进程组成的应用程序视为一个实体即作业</p>
<h3 id="生命周期lifetime"><a class="header" href="#生命周期lifetime">生命周期(lifetime)</a></h3>
<p>进程有自己的生命周期,从开始创建到直到被强制终止或程序退出为止。可通过调用<code>Process::create()</code>创建一个进程,并调用<code>Process::start()</code>开始执行 。该进程在以下情况下停止执行:</p>
<ul>
<li>最后一个线程终止或退出</li>
<li>进程调用 <code>Process::exit()</code></li>
<li>父作业parent job终止了该过程</li>
<li>父作业parent job被销毁destroied</li>
</ul>
<p>注:<code>Process::start()</code>不能被调用两次。新线程不能被添加到已启动的进程。</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="线程管理thread-对象"><a class="header" href="#线程管理thread-对象">线程管理Thread 对象</a></h1>
<p>线程对象是代表分时CPU的执行上下文的一种结构。线程对象与特定的进程对象相关联该进程对象为线程对象执行中涉及的I/O和计算提供提供必要的内存和其他对象的句柄。</p>
<h2 id="生命期"><a class="header" href="#生命期">生命期</a></h2>
<p>线程是通过调用Thread::create()创建的但只有在调用Thread::create()或Process::start()时才开始执行。这两个系统调用的参数都是要执行的初始例程的入口。</p>
<p>传递给Process::start()的线程应该是在一个进程上开始执行的第一个线程。</p>
<p>下列情况都可导致一个线程终止执行:</p>
<ul>
<li>通过调用 <code>CurrentThread::exit()</code></li>
<li>当父进程终止时</li>
<li>通过调用 <code>Task::kill()</code></li>
<li>在生成没有处理程序或处理程序决定终止线程的异常之后。</li>
</ul>
<p>从入口例程返回并不终止执行。入口点的最后一个动作应该是调用CurrentThread::exit()。</p>
<p>关闭一个线程的最后一个句柄并不终止执行。为了强行杀死一个没有可用句柄的线程可以使用KernelObject::get_child()来获得该线程的句柄。但这种方法是非常不可取的。杀死一个正在执行的线程可能会使进程处于损坏的状态。</p>
<p>本地线程总是分离的(<em>detached</em>。也就是说不需要join()操作来做一个干净的终止clean termination。但一些内核之上的运行系统如C11或POSIX可能需要线程被连接be joined</p>
<h2 id="信号"><a class="header" href="#信号">信号</a></h2>
<p>线程提供以下信号:</p>
<ul>
<li>THREAD_TERMINATED</li>
<li>THREAD_SUSPENDED</li>
<li>THREAD_RUNNING</li>
</ul>
<p>当一个线程启动执行时THREAD_RUNNING被设定。当它被暂停时THREAD_RUNNING被取消THREAD_SUSPENDED被设定。当线程恢复时THREAD_SUSPENDED被取消THREAD_RUNNING被设定。当线程终止时THREAD_RUNNING和THREAD_SUSPENDED都被置位THREAD_TERMINATED也被置位。</p>
<p>注意信号经过“或”运算后进入KernelObject::wait_signal()函数系列所保持的状态 ,因此当它们返回时,你可能会看到所要求的信号的任何组合。</p>
<h2 id="线程状态threadstate"><a class="header" href="#线程状态threadstate">线程状态(ThreadState)</a></h2>
<blockquote>
<p>状态转移:创建 -&gt; 运行 -&gt; 暂停 -&gt; 退出,最好有个状态机的图</p>
<p>实现 ThreadState最好能加一个单元测试来验证转移过程</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub enum ThreadState {
New, \\该线程已经创建,但还没有开始运行
Running, \\该线程正在正常运行用户代码
Suspended, \\由于zx_task_suspend()而暂停
Blocked, \\在一个系统调用中或处理一个异常而阻塞
Dying, \\线程正在被终止的过程中,但还没有停止运行
Dead, \\该线程已停止运行
BlockedException, \\该线程在一个异常中被阻塞
BlockedSleeping, \\该线程在zx_nanosleep()中被阻塞
BlockedFutex, \\该线程在zx_futex_wait()中被阻塞
BlockedPort, \\该线程在zx_port_wait()中被被阻塞
BlockedChannel, \\该线程在zx_channel_call()中被阻塞
BlockedWaitOne, \\该线程在zx_object_wait_one()中被阻塞
BlockedWaitMany, \\该线程在zx_object_wait_many()中被阻塞
BlockedInterrupt, \\该线程在zx_interrupt_wait()中被阻塞
BlockedPager, \\被Pager阻塞 (目前没用到???
}
<span class="boring">}
</span></code></pre></pre>
<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>
<p>Zircon 中有两个跟内存管理有关的对象 VMOVirtual Memory Object和 VMAR Virtual Memory Address Region。VMO 主要负责管理物理内存页面VMAR 主要负责进程虚拟内存管理。当创建一个进程的时候,需要使用到内存的时候,都需要创建一个 VMO然后将这个 VMO map 到 VMAR上面。</p>
<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>
<p>虚拟拟内存对象Virtual Memory Objects VMO代表一组物理内存页面或 潜在的页面(将根据需要延迟创建/填充)。</p>
<p>它们可以通过 <a href="https://fuchsia.dev/docs/reference/syscalls/vmar_map"><code>zx_vmar_map()</code></a>被映射到一个进程Process的地址空间也可通过 <a href="https://fuchsia.dev/docs/reference/syscalls/vmar_unmap"><code>zx_vmar_unmap()</code></a>来解除映射。可以使用<a href="https://fuchsia.dev/docs/reference/syscalls/vmar_protect"><code>zx_vmar_protect()</code></a>来调整映射页面的权限。</p>
<p>也可以直接使用<a href="https://fuchsia.dev/docs/reference/syscalls/vmo_read"><code>zx_vmo_read()</code></a>来读取VMO和通过使用 <a href="https://fuchsia.dev/docs/reference/syscalls/vmo_write"><code>zx_vmo_write()</code></a>来写入 VMO。因此通过诸如“创建 VMO将数据集写入其中然后将其交给另一个进程使用”等一次性one-shot )操作,可以避免将它们映射到地址空间的开销。</p>
<h2 id="实现-vmo-对象框架"><a class="header" href="#实现-vmo-对象框架">实现 VMO 对象框架</a></h2>
<blockquote>
<p>实现 VmObject 结构,其中定义 VmObjectTrait 接口,并提供三个具体实现 Paged, Physical, Slice</p>
</blockquote>
<p>VmObject 结构体</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// vm/vmo/mod.rs
pub struct VmObject {
base: KObjectBase,
resizable: bool,
trait_: Arc&lt;dyn VMObjectTrait&gt;,
inner: Mutex&lt;VmObjectInner&gt;,
}
impl_kobject!(VmObject);
#[derive(Default)]
struct VmObjectInner {
parent: Weak&lt;VmObject&gt;,
children: Vec&lt;Weak&lt;VmObject&gt;&gt;,
mapping_count: usize,
content_size: usize,
}
<span class="boring">}
</span></code></pre></pre>
<p><code>trait_</code> 指向实现了 VMObjectTrait 的对象,它由三个具体实现,分别是 VMObjectPage, VMObjectPhysical, VMObjectSlice。VMObjectPaged 是按页分配内存VMObjectSlice 主要用于共享内存VMObjectPhysical 在 zCore-Tutorial 中暂时不会使用到。<br />
<code>mapping_count</code> 表示这个 VmObject 被 map 到几个 VMAR 中。<br />
<code>content_size</code> 是分配的物理内存的大小。<br />
VmObjectTrait 定义了一组 VMObject* 共有的方法</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub trait VMObjectTrait: Sync + Send {
/// Read memory to `buf` from VMO at `offset`.
fn read(&amp;self, offset: usize, buf: &amp;mut [u8]) -&gt; ZxResult;
/// Write memory from `buf` to VMO at `offset`.
fn write(&amp;self, offset: usize, buf: &amp;[u8]) -&gt; ZxResult;
/// Resets the range of bytes in the VMO from `offset` to `offset+len` to 0.
fn zero(&amp;self, offset: usize, len: usize) -&gt; ZxResult;
/// Get the length of VMO.
fn len(&amp;self) -&gt; usize;
/// Set the length of VMO.
fn set_len(&amp;self, len: usize) -&gt; ZxResult;
/// Commit a page.
fn commit_page(&amp;self, page_idx: usize, flags: MMUFlags) -&gt; ZxResult&lt;PhysAddr&gt;;
/// Commit pages with an external function f.
/// the vmo is internally locked before it calls f,
/// allowing `VmMapping` to avoid deadlock
fn commit_pages_with(
&amp;self,
f: &amp;mut dyn FnMut(&amp;mut dyn FnMut(usize, MMUFlags) -&gt; ZxResult&lt;PhysAddr&gt;) -&gt; ZxResult,
) -&gt; ZxResult;
/// Commit allocating physical memory.
fn commit(&amp;self, offset: usize, len: usize) -&gt; ZxResult;
/// Decommit allocated physical memory.
fn decommit(&amp;self, offset: usize, len: usize) -&gt; ZxResult;
/// Create a child VMO.
fn create_child(&amp;self, offset: usize, len: usize) -&gt; ZxResult&lt;Arc&lt;dyn VMObjectTrait&gt;&gt;;
/// Append a mapping to the VMO's mapping list.
fn append_mapping(&amp;self, _mapping: Weak&lt;VmMapping&gt;) {}
/// Remove a mapping from the VMO's mapping list.
fn remove_mapping(&amp;self, _mapping: Weak&lt;VmMapping&gt;) {}
/// Complete the VmoInfo.
fn complete_info(&amp;self, info: &amp;mut VmoInfo);
/// Get the cache policy.
fn cache_policy(&amp;self) -&gt; CachePolicy;
/// Set the cache policy.
fn set_cache_policy(&amp;self, policy: CachePolicy) -&gt; ZxResult;
/// Count committed pages of the VMO.
fn committed_pages_in_range(&amp;self, start_idx: usize, end_idx: usize) -&gt; usize;
/// Pin the given range of the VMO.
fn pin(&amp;self, _offset: usize, _len: usize) -&gt; ZxResult {
Err(ZxError::NOT_SUPPORTED)
}
/// Unpin the given range of the VMO.
fn unpin(&amp;self, _offset: usize, _len: usize) -&gt; ZxResult {
Err(ZxError::NOT_SUPPORTED)
}
/// Returns true if the object is backed by a contiguous range of physical memory.
fn is_contiguous(&amp;self) -&gt; bool {
false
}
/// Returns true if the object is backed by RAM.
fn is_paged(&amp;self) -&gt; bool {
false
}
}
<span class="boring">}
</span></code></pre></pre>
<p><code>read()</code><code>write()</code> 用于读和写,<code>zero()</code> 用于清空一段内存。<br />
比较特别的是:<code>fn commit_page(&amp;self, page_idx: usize, flags: MMUFlags) -&gt; ZxResult&lt;PhysAddr&gt;;</code><code>fn commit(&amp;self, offset: usize, len: usize) -&gt; ZxResult;</code><code>fn commit(&amp;self, offset: usize, len: usize) -&gt; ZxResult;</code> 主要用于分配物理内存,因为一些内存分配策略,物理内存并不一定是马上分配的,所以需要 commit 来分配一块内存。<br />
<code>pin</code><code>unpin</code> 在这里主要用于增加和减少引用计数。<br />
VmObject 实现了不同的 new 方法,它们之间的差别在于实现 trait_ 的对象不同。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VmObject {
/// Create a new VMO backing on physical memory allocated in pages.
pub fn new_paged(pages: usize) -&gt; Arc&lt;Self&gt; {
Self::new_paged_with_resizable(false, pages)
}
/// Create a new VMO, which can be resizable, backing on physical memory allocated in pages.
pub fn new_paged_with_resizable(resizable: bool, pages: usize) -&gt; Arc&lt;Self&gt; {
let base = KObjectBase::new();
Arc::new(VmObject {
resizable,
trait_: VMObjectPaged::new(pages),
inner: Mutex::new(VmObjectInner::default()),
base,
})
}
/// Create a new VMO representing a piece of contiguous physical memory.
pub fn new_physical(paddr: PhysAddr, pages: usize) -&gt; Arc&lt;Self&gt; {
Arc::new(VmObject {
base: KObjectBase::new(),
resizable: false,
trait_: VMObjectPhysical::new(paddr, pages),
inner: Mutex::new(VmObjectInner::default()),
})
}
/// Create a VM object referring to a specific contiguous range of physical frame.
pub fn new_contiguous(pages: usize, align_log2: usize) -&gt; ZxResult&lt;Arc&lt;Self&gt;&gt; {
let vmo = Arc::new(VmObject {
base: KObjectBase::new(),
resizable: false,
trait_: VMObjectPaged::new_contiguous(pages, align_log2)?,
inner: Mutex::new(VmObjectInner::default()),
});
Ok(vmo)
}
}
<span class="boring">}
</span></code></pre></pre>
<p>通过 <code>pub fn create_child(self: &amp;Arc&lt;Self&gt;, resizable: bool, offset: usize, len: usize)</code> 可以创建一个 VMObject 的快照副本。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VmObject {
/// Create a child VMO.
pub fn create_child(
self: &amp;Arc&lt;Self&gt;,
resizable: bool,
offset: usize,
len: usize,
) -&gt; ZxResult&lt;Arc&lt;Self&gt;&gt; {
// Create child VmObject
let base = KObjectBase::with_name(&amp;self.base.name());
let trait_ = self.trait_.create_child(offset, len)?;
let child = Arc::new(VmObject {
base,
resizable,
trait_,
inner: Mutex::new(VmObjectInner {
parent: Arc::downgrade(self),
..VmObjectInner::default()
}),
});
// Add child VmObject to this VmObject
self.add_child(&amp;child);
Ok(child)
}
/// Add child to the list
fn add_child(&amp;self, child: &amp;Arc&lt;VmObject&gt;) {
let mut inner = self.inner.lock();
// 判断这个 child VmObject 是否还是存在,通过获取子对象的强引用数来判断
inner.children.retain(|x| x.strong_count() != 0);
// downgrade 将 Arc 转为 Weak
inner.children.push(Arc::downgrade(child));
}
}
<span class="boring">}
</span></code></pre></pre>
<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>
<h3 id="mmap"><a class="header" href="#mmap">mmap</a></h3>
<p>mmap是一种内存映射文件的方法将一个文件或者其它对象映射到进程的地址空间实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址一一对应的关系。实现这样的映射关系后进程就可以采用指针的方式读写操作这一段内存而系统会自动回写脏页面到对应的文件磁盘上即完成了对文件的操作而不必再调用read,write等系统调用函数。相反内核空间对这段区域的修改也直接反映用户空间从而可以实现不同进程间的文件共享。因此新建一个文件然后调用 mmap其实就相当于分配了一块物理内存因此我们可以用文件来模拟物理内存。
<img src="img/mmap.png" alt="mmap.png" /></p>
<h3 id="分配地址空间"><a class="header" href="#分配地址空间">分配地址空间</a></h3>
<p>创建一个文件用于 mmap 系统调用。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>fn create_pmem_file() -&gt; File {
let dir = tempdir().expect(&quot;failed to create pmem dir&quot;);
let path = dir.path().join(&quot;pmem&quot;);
// workaround on macOS to avoid permission denied.
// see https://jiege.ch/software/2020/02/07/macos-mmap-exec/ for analysis on this problem.
#[cfg(target_os = &quot;macos&quot;)]
std::mem::forget(dir);
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&amp;path)
.expect(&quot;failed to create pmem file&quot;);
file.set_len(PMEM_SIZE as u64)
.expect(&quot;failed to resize file&quot;);
trace!(&quot;create pmem file: path={:?}, size={:#x}&quot;, path, PMEM_SIZE);
let prot = libc::PROT_READ | libc::PROT_WRITE;
// 调用 mmap (这个不是系统调用)进行文件和内存之间的双向映射
mmap(file.as_raw_fd(), 0, PMEM_SIZE, phys_to_virt(0), prot);
file
}
<span class="boring">}
</span></code></pre></pre>
<p>mmap:</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// Mmap frame file `fd` to `vaddr`.
fn mmap(fd: libc::c_int, offset: usize, len: usize, vaddr: VirtAddr, prot: libc::c_int) {
// 根据不同的操作系统去修改权限
// workaround on macOS to write text section.
#[cfg(target_os = &quot;macos&quot;)]
let prot = if prot &amp; libc::PROT_EXEC != 0 {
prot | libc::PROT_WRITE
} else {
prot
};
// 调用 mmap 系统调用ret 为返回值
let ret = unsafe {
let flags = libc::MAP_SHARED | libc::MAP_FIXED;
libc::mmap(vaddr as _, len, prot, flags, fd, offset as _)
} as usize;
trace!(
&quot;mmap file: fd={}, offset={:#x}, len={:#x}, vaddr={:#x}, prot={:#b}&quot;,
fd,
offset,
len,
vaddr,
prot,
);
assert_eq!(ret, vaddr, &quot;failed to mmap: {:?}&quot;, Error::last_os_error());
}
<span class="boring">}
</span></code></pre></pre>
<p>最后创建一个全局变量保存这个分配的内存</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>lazy_static! {
static ref FRAME_FILE: File = create_pmem_file();
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="pmem_read-和-pmem_write"><a class="header" href="#pmem_read-和-pmem_write">pmem_read 和 pmem_write</a></h3>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// Read physical memory from `paddr` to `buf`.
#[export_name = &quot;hal_pmem_read&quot;]
pub fn pmem_read(paddr: PhysAddr, buf: &amp;mut [u8]) {
trace!(&quot;pmem read: paddr={:#x}, len={:#x}&quot;, paddr, buf.len());
assert!(paddr + buf.len() &lt;= PMEM_SIZE);
ensure_mmap_pmem();
unsafe {
(phys_to_virt(paddr) as *const u8).copy_to_nonoverlapping(buf.as_mut_ptr(), buf.len());
}
}
/// Write physical memory to `paddr` from `buf`.
#[export_name = &quot;hal_pmem_write&quot;]
pub fn pmem_write(paddr: PhysAddr, buf: &amp;[u8]) {
trace!(&quot;pmem write: paddr={:#x}, len={:#x}&quot;, paddr, buf.len());
assert!(paddr + buf.len() &lt;= PMEM_SIZE);
ensure_mmap_pmem();
unsafe {
buf.as_ptr()
.copy_to_nonoverlapping(phys_to_virt(paddr) as _, buf.len());
}
}
/// Ensure physical memory are mmapped and accessible.
fn ensure_mmap_pmem() {
FRAME_FILE.as_raw_fd();
}
<span class="boring">}
</span></code></pre></pre>
<p><code>ensure_mmap_pmem()</code> 确保物理内存已经映射<br />
<code>copy_to_nonoverlapping(self, dst *mut T, count: usize)</code> 将 self 的字节序列拷贝到 dst 中source 和 destination 是不互相重叠的。<code>(phys_to_virt(paddr) as *const u8).copy_to_nonoverlapping(buf.as_mut_ptr(), buf.len());</code> 通过 <code>phys_to_virt(paddr)</code> 将 paddr 加上 PMEM_BASE 转为虚拟地址,然后将里面的字节拷贝到 buf 里面。</p>
<h2 id="实现物理内存-vmo"><a class="header" href="#实现物理内存-vmo">实现物理内存 VMO</a></h2>
<blockquote>
<p>用 HAL 实现 VmObjectPhysical 的方法,并做单元测试
物理内存 VMO 结构体:</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct VMObjectPhysical {
paddr: PhysAddr,
pages: usize,
/// Lock this when access physical memory.
data_lock: Mutex&lt;()&gt;,
inner: Mutex&lt;VMObjectPhysicalInner&gt;,
}
struct VMObjectPhysicalInner {
cache_policy: CachePolicy,
}
<span class="boring">}
</span></code></pre></pre>
<p>这里比较奇怪的是 data_lock 这个字段,这个字段里 Mutex 的泛型类型是一个 unit type其实相当于它是没有“值”的它只是起到一个锁的作用。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VMObjectTrait for VMObjectPhysical {
fn read(&amp;self, offset: usize, buf: &amp;mut [u8]) -&gt; ZxResult {
let _ = self.data_lock.lock(); // 先获取锁
assert!(offset + buf.len() &lt;= self.len());
kernel_hal::pmem_read(self.paddr + offset, buf); // 对一块物理内存进行读
Ok(())
}
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="实现切片-vmo"><a class="header" href="#实现切片-vmo">实现切片 VMO</a></h2>
<blockquote>
<p>实现 VmObjectSlice并做单元测试
VMObjectSlice 中的 parent 用于指向一个实际的 VMO 对象比如VMObjectPaged这样通过 VMObjectSlice 就可以实现对 VMObjectPaged 的共享。</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct VMObjectSlice {
/// Parent node.
parent: Arc&lt;dyn VMObjectTrait&gt;,
/// The offset from parent.
offset: usize,
/// The size in bytes.
size: usize,
}
impl VMObjectSlice {
pub fn new(parent: Arc&lt;dyn VMObjectTrait&gt;, offset: usize, size: usize) -&gt; Arc&lt;Self&gt; {
Arc::new(VMObjectSlice {
parent,
offset,
size,
})
}
fn check_range(&amp;self, offset: usize, len: usize) -&gt; ZxResult {
if offset + len &gt;= self.size {
return Err(ZxError::OUT_OF_RANGE);
}
Ok(())
}
}
<span class="boring">}
</span></code></pre></pre>
<p>VMObjectSlice 实现的读写,第一步是 <code>check_range</code> ,第二步是调用 parent 中的读写方法。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VMObjectTrait for VMObjectSlice {
fn read(&amp;self, offset: usize, buf: &amp;mut [u8]) -&gt; ZxResult {
self.check_range(offset, buf.len())?;
self.parent.read(offset + self.offset, buf)
}
}
<span class="boring">}
</span></code></pre></pre>
<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>
<p>commit_page 和 commit_pages_with 函数的作用:用于检查物理页帧是否已经分配。</p>
<h2 id="hal物理内存管理"><a class="header" href="#hal物理内存管理">HAL物理内存管理</a></h2>
<blockquote>
<p>在 HAL 中实现 PhysFrame 和最简单的分配器</p>
</blockquote>
<h3 id="kernel-hal"><a class="header" href="#kernel-hal">kernel-hal</a></h3>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[repr(C)]
pub struct PhysFrame {
// paddr 物理地址
paddr: PhysAddr,
}
impl PhysFrame {
// 分配物理页帧
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_frame_alloc&quot;]
pub fn alloc() -&gt; Option&lt;Self&gt; {
unimplemented!()
}
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_frame_alloc_contiguous&quot;]
pub fn alloc_contiguous_base(_size: usize, _align_log2: usize) -&gt; Option&lt;PhysAddr&gt; {
unimplemented!()
}
pub fn alloc_contiguous(size: usize, align_log2: usize) -&gt; Vec&lt;Self&gt; {
PhysFrame::alloc_contiguous_base(size, align_log2).map_or(Vec::new(), |base| {
(0..size)
.map(|i| PhysFrame {
paddr: base + i * PAGE_SIZE,
})
.collect()
})
}
pub fn alloc_zeroed() -&gt; Option&lt;Self&gt; {
Self::alloc().map(|f| {
pmem_zero(f.addr(), PAGE_SIZE);
f
})
}
pub fn alloc_contiguous_zeroed(size: usize, align_log2: usize) -&gt; Vec&lt;Self&gt; {
PhysFrame::alloc_contiguous_base(size, align_log2).map_or(Vec::new(), |base| {
pmem_zero(base, size * PAGE_SIZE);
(0..size)
.map(|i| PhysFrame {
paddr: base + i * PAGE_SIZE,
})
.collect()
})
}
pub fn addr(&amp;self) -&gt; PhysAddr {
self.paddr
}
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_zero_frame_paddr&quot;]
pub fn zero_frame_addr() -&gt; PhysAddr {
unimplemented!()
}
}
impl Drop for PhysFrame {
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_frame_dealloc&quot;]
fn drop(&amp;mut self) {
unimplemented!()
}
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="kernel-hal-unix"><a class="header" href="#kernel-hal-unix">kernel-hal-unix</a></h3>
<p>通过下面的代码可以构造一个页帧号。<code>(PAGE_SIZE..PMEM_SIZE).step_by(PAGE_SIZE).collect()</code> 可以每隔 PAGE_SIZE 生成一个页帧的开始位置。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>lazy_static! {
static ref AVAILABLE_FRAMES: Mutex&lt;VecDeque&lt;usize&gt;&gt; =
Mutex::new((PAGE_SIZE..PMEM_SIZE).step_by(PAGE_SIZE).collect());
}
<span class="boring">}
</span></code></pre></pre>
<p>分配一块物理页帧就是从 AVAILABLE_FRAMES 中通过 pop_front 弹出一个页号</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl PhysFrame {
#[export_name = &quot;hal_frame_alloc&quot;]
pub fn alloc() -&gt; Option&lt;Self&gt; {
let ret = AVAILABLE_FRAMES
.lock()
.unwrap()
.pop_front()
.map(|paddr| PhysFrame { paddr });
trace!(&quot;frame alloc: {:?}&quot;, ret);
ret
}
#[export_name = &quot;hal_zero_frame_paddr&quot;]
pub fn zero_frame_addr() -&gt; PhysAddr {
0
}
}
impl Drop for PhysFrame {
#[export_name = &quot;hal_frame_dealloc&quot;]
fn drop(&amp;mut self) {
trace!(&quot;frame dealloc: {:?}&quot;, self);
AVAILABLE_FRAMES.lock().unwrap().push_back(self.paddr);
}
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="辅助结构blockrange-迭代器"><a class="header" href="#辅助结构blockrange-迭代器">辅助结构BlockRange 迭代器</a></h2>
<blockquote>
<p>实现 BlockRange</p>
</blockquote>
<p>在按页分配内存的 VMObjectPaged 的读和写的方法中会使用到一个 BlockIter 迭代器。BlockIter 主要用于将一段内存分块,每次返回这一块的信息也就是 BlockRange。</p>
<h3 id="blockiter"><a class="header" href="#blockiter">BlockIter</a></h3>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[derive(Debug, Eq, PartialEq)]
pub struct BlockRange {
pub block: usize,
pub begin: usize, // 块内地址开始位置
pub end: usize, // 块内地址结束位置
pub block_size_log2: u8,
}
/// Given a range and iterate sub-range for each block
pub struct BlockIter {
pub begin: usize,
pub end: usize,
pub block_size_log2: u8,
}
<span class="boring">}
</span></code></pre></pre>
<p>block_size_log2 是 log 以2为底 block size, 比如block size 大小为4096则 block_size_log2 为 12。block 是块编号。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl BlockRange {
pub fn len(&amp;self) -&gt; usize {
self.end - self.begin
}
pub fn is_full(&amp;self) -&gt; bool {
self.len() == (1usize &lt;&lt; self.block_size_log2)
}
pub fn is_empty(&amp;self) -&gt; bool {
self.len() == 0
}
pub fn origin_begin(&amp;self) -&gt; usize {
(self.block &lt;&lt; self.block_size_log2) + self.begin
}
pub fn origin_end(&amp;self) -&gt; usize {
(self.block &lt;&lt; self.block_size_log2) + self.end
}
}
impl Iterator for BlockIter {
type Item = BlockRange;
fn next(&amp;mut self) -&gt; Option&lt;&lt;Self as Iterator&gt;::Item&gt; {
if self.begin &gt;= self.end {
return None;
}
let block_size_log2 = self.block_size_log2;
let block_size = 1usize &lt;&lt; self.block_size_log2;
let block = self.begin / block_size;
let begin = self.begin % block_size;
// 只有最后一块需要计算块内最后的地址,其他的直接返回块的大小
let end = if block == self.end / block_size {
self.end % block_size
} else {
block_size
};
self.begin += end - begin;
Some(BlockRange {
block,
begin,
end,
block_size_log2,
})
}
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="实现按页分配的-vmo"><a class="header" href="#实现按页分配的-vmo">实现按页分配的 VMO</a></h2>
<blockquote>
<p>实现 for_each_page, commit, read, write 函数</p>
</blockquote>
<p>按页分配的 VMO 结构体如下:</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct VMObjectPaged {
inner: Mutex&lt;VMObjectPagedInner&gt;,
}
/// The mutable part of `VMObjectPaged`.
#[derive(Default)]
struct VMObjectPagedInner {
/// Physical frames of this VMO.
frames: Vec&lt;PhysFrame&gt;,
/// Cache Policy
cache_policy: CachePolicy,
/// Is contiguous
contiguous: bool,
/// Sum of pin_count
pin_count: usize,
/// All mappings to this VMO.
mappings: Vec&lt;Weak&lt;VmMapping&gt;&gt;,
}
<span class="boring">}
</span></code></pre></pre>
<p>VMObjectPage 有两个 new 方法</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VMObjectPaged {
/// Create a new VMO backing on physical memory allocated in pages.
pub fn new(pages: usize) -&gt; Arc&lt;Self&gt; {
let mut frames = Vec::new();
frames.resize_with(pages, || PhysFrame::alloc_zeroed().unwrap()); // 分配 pages 个页帧号,并将这些页帧号的内存清零
Arc::new(VMObjectPaged {
inner: Mutex::new(VMObjectPagedInner {
frames,
..Default::default()
}),
})
}
/// Create a list of contiguous pages
pub fn new_contiguous(pages: usize, align_log2: usize) -&gt; ZxResult&lt;Arc&lt;Self&gt;&gt; {
let frames = PhysFrame::alloc_contiguous_zeroed(pages, align_log2 - PAGE_SIZE_LOG2);
if frames.is_empty() {
return Err(ZxError::NO_MEMORY);
}
Ok(Arc::new(VMObjectPaged {
inner: Mutex::new(VMObjectPagedInner {
frames,
contiguous: true,
..Default::default()
}),
}))
}
}
<span class="boring">}
</span></code></pre></pre>
<p>VMObjectPaged 的读和写用到了一个非常重要的函数 for_each_page 。首先它先构造了一个 BlockIter 迭代器,然后调用传入的函数进行读或者写。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VMObjectPagedInner {
/// Helper function to split range into sub-ranges within pages.
///
/// ```text
/// VMO range:
/// |----|----|----|----|----|
///
/// buf:
/// [====len====]
/// |--offset--|
///
/// sub-ranges:
/// [===]
/// [====]
/// [==]
/// ```
///
/// `f` is a function to process in-page ranges.
/// It takes 2 arguments:
/// * `paddr`: the start physical address of the in-page range.
/// * `buf_range`: the range in view of the input buffer.
fn for_each_page(
&amp;mut self,
offset: usize,
buf_len: usize,
mut f: impl FnMut(PhysAddr, Range&lt;usize&gt;),
) {
let iter = BlockIter {
begin: offset,
end: offset + buf_len,
block_size_log2: 12,
};
for block in iter {
// 获取这一块开始的物理地址
let paddr = self.frames[block.block].addr();
// 这块物理地址的范围
let buf_range = block.origin_begin() - offset..block.origin_end() - offset;
f(paddr + block.begin, buf_range);
}
}
}
<span class="boring">}
</span></code></pre></pre>
<p>read 和 write 函数,一个传入的是 <code>kernel_hal::pmem_read</code> ,另外一个是 <code>kernel_hal::pmem_write</code></p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VMObjectTrait for VMObjectPaged {
fn read(&amp;self, offset: usize, buf: &amp;mut [u8]) -&gt; ZxResult {
let mut inner = self.inner.lock();
if inner.cache_policy != CachePolicy::Cached {
return Err(ZxError::BAD_STATE);
}
inner.for_each_page(offset, buf.len(), |paddr, buf_range| {
kernel_hal::pmem_read(paddr, &amp;mut buf[buf_range]);
});
Ok(())
}
fn write(&amp;self, offset: usize, buf: &amp;[u8]) -&gt; ZxResult {
let mut inner = self.inner.lock();
if inner.cache_policy != CachePolicy::Cached {
return Err(ZxError::BAD_STATE);
}
inner.for_each_page(offset, buf.len(), |paddr, buf_range| {
kernel_hal::pmem_write(paddr, &amp;buf[buf_range]);
});
Ok(())
}
}
<span class="boring">}
</span></code></pre></pre>
<p>commit 函数</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VMObjectTrait for VMObjectPaged {
fn commit_page(&amp;self, page_idx: usize, _flags: MMUFlags) -&gt; ZxResult&lt;PhysAddr&gt; {
let inner = self.inner.lock();
Ok(inner.frames[page_idx].addr())
}
fn commit_pages_with(
&amp;self,
f: &amp;mut dyn FnMut(&amp;mut dyn FnMut(usize, MMUFlags) -&gt; ZxResult&lt;PhysAddr&gt;) -&gt; ZxResult,
) -&gt; ZxResult {
let inner = self.inner.lock();
f(&amp;mut |page_idx, _| Ok(inner.frames[page_idx].addr()))
}
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="vmo-复制"><a class="header" href="#vmo-复制">VMO 复制</a></h2>
<blockquote>
<p>实现 create_child 函数</p>
</blockquote>
<p>create_child 是将原 VMObjectPaged 的内容拷贝一份</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// object/vm/vmo/paged.rs
impl VMObjectTrait for VMObjectPaged {
fn create_child(&amp;self, offset: usize, len: usize) -&gt; ZxResult&lt;Arc&lt;dyn VMObjectTrait&gt;&gt; {
assert!(page_aligned(offset));
assert!(page_aligned(len));
let mut inner = self.inner.lock();
let child = inner.create_child(offset, len)?;
Ok(child)
}
/// Create a snapshot child VMO.
fn create_child(&amp;mut self, offset: usize, len: usize) -&gt; ZxResult&lt;Arc&lt;VMObjectPaged&gt;&gt; {
// clone contiguous vmo is no longer permitted
// https://fuchsia.googlesource.com/fuchsia/+/e6b4c6751bbdc9ed2795e81b8211ea294f139a45
if self.contiguous {
return Err(ZxError::INVALID_ARGS);
}
if self.cache_policy != CachePolicy::Cached || self.pin_count != 0 {
return Err(ZxError::BAD_STATE);
}
let mut frames = Vec::with_capacity(pages(len));
for _ in 0..pages(len) {
frames.push(PhysFrame::alloc().ok_or(ZxError::NO_MEMORY)?);
}
for (i, frame) in frames.iter().enumerate() {
if let Some(src_frame) = self.frames.get(pages(offset) + i) {
kernel_hal::frame_copy(src_frame.addr(), frame.addr())
} else {
kernel_hal::pmem_zero(frame.addr(), PAGE_SIZE);
}
}
// create child VMO
let child = Arc::new(VMObjectPaged {
inner: Mutex::new(VMObjectPagedInner {
frames,
..Default::default()
}),
});
Ok(child)
}
}
// kernel-hal-unix/sr/lib.rs
/// Copy content of `src` frame to `target` frame
#[export_name = &quot;hal_frame_copy&quot;]
pub fn frame_copy(src: PhysAddr, target: PhysAddr) {
trace!(&quot;frame_copy: {:#x} &lt;- {:#x}&quot;, target, src);
assert!(src + PAGE_SIZE &lt;= PMEM_SIZE &amp;&amp; target + PAGE_SIZE &lt;= PMEM_SIZE);
ensure_mmap_pmem();
unsafe {
let buf = phys_to_virt(src) as *const u8;
buf.copy_to_nonoverlapping(phys_to_virt(target) as _, PAGE_SIZE);
}
}
/// Zero physical memory at `[paddr, paddr + len)`
#[export_name = &quot;hal_pmem_zero&quot;]
pub fn pmem_zero(paddr: PhysAddr, len: usize) {
trace!(&quot;pmem_zero: addr={:#x}, len={:#x}&quot;, paddr, len);
assert!(paddr + len &lt;= PMEM_SIZE);
ensure_mmap_pmem();
unsafe {
core::ptr::write_bytes(phys_to_virt(paddr) as *mut u8, 0, len);
}
}
<span class="boring">}
</span></code></pre></pre>
<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>
<p>虚拟内存地址区域Virtual Memory Address Regions VMARs为管理进程的地址空间提供了一种抽象。在进程创建时将Root VMAR 的句柄提供给进程创建者。该句柄指的是跨越整个地址空间的 VMAR。这个空间可以通过<a href="https://fuchsia.dev/docs/reference/syscalls/vmar_map"><code>zx_vmar_map()</code></a><a href="https://fuchsia.dev/docs/reference/syscalls/vmar_allocate"><code>zx_vmar_allocate()</code></a>接口来划分 。 <a href="https://fuchsia.dev/docs/reference/syscalls/vmar_allocate"><code>zx_vmar_allocate()</code></a>可用于生成新的 VMAR称为子区域或子区域可用于将地址空间的各个部分组合在一起。</p>
<h2 id="实现-vmar-对象框架"><a class="header" href="#实现-vmar-对象框架">实现 VMAR 对象框架</a></h2>
<blockquote>
<p>定义 VmAddressRangeVmMapping</p>
<p>实现 create_child, map, unmap, destroy 函数,并做单元测试验证地址空间分配</p>
</blockquote>
<h3 id="vmaddressregion"><a class="header" href="#vmaddressregion">VmAddressRegion</a></h3>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct VmAddressRegion {
flags: VmarFlags,
base: KObjectBase,
addr: VirtAddr,
size: usize,
parent: Option&lt;Arc&lt;VmAddressRegion&gt;&gt;,
page_table: Arc&lt;Mutex&lt;dyn PageTableTrait&gt;&gt;,
/// If inner is None, this region is destroyed, all operations are invalid.
inner: Mutex&lt;Option&lt;VmarInner&gt;&gt;,
}
#[derive(Default)]
struct VmarInner {
children: Vec&lt;Arc&lt;VmAddressRegion&gt;&gt;,
mappings: Vec&lt;Arc&lt;VmMapping&gt;&gt;,
}
<span class="boring">}
</span></code></pre></pre>
<p>构造一个根节点 VMAR这个 VMAR 是每个进程都拥有的。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VmAddressRegion {
/// Create a new root VMAR.
pub fn new_root() -&gt; Arc&lt;Self&gt; {
let (addr, size) = {
use core::sync::atomic::*;
static VMAR_ID: AtomicUsize = AtomicUsize::new(0);
let i = VMAR_ID.fetch_add(1, Ordering::SeqCst);
(0x2_0000_0000 + 0x100_0000_0000 * i, 0x100_0000_0000)
};
Arc::new(VmAddressRegion {
flags: VmarFlags::ROOT_FLAGS,
base: KObjectBase::new(),
addr,
size,
parent: None,
page_table: Arc::new(Mutex::new(kernel_hal::PageTable::new())), //hal PageTable
inner: Mutex::new(Some(VmarInner::default())),
})
}
}
<span class="boring">}
</span></code></pre></pre>
<p>我们的内核同样需要一个根 VMAR</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// The base of kernel address space
/// In x86 fuchsia this is 0xffff_ff80_0000_0000 instead
pub const KERNEL_ASPACE_BASE: u64 = 0xffff_ff02_0000_0000;
/// The size of kernel address space
pub const KERNEL_ASPACE_SIZE: u64 = 0x0000_0080_0000_0000;
/// The base of user address space
pub const USER_ASPACE_BASE: u64 = 0;
// pub const USER_ASPACE_BASE: u64 = 0x0000_0000_0100_0000;
/// The size of user address space
pub const USER_ASPACE_SIZE: u64 = (1u64 &lt;&lt; 47) - 4096 - USER_ASPACE_BASE;
impl VmAddressRegion {
/// Create a kernel root VMAR.
pub fn new_kernel() -&gt; Arc&lt;Self&gt; {
let kernel_vmar_base = KERNEL_ASPACE_BASE as usize;
let kernel_vmar_size = KERNEL_ASPACE_SIZE as usize;
Arc::new(VmAddressRegion {
flags: VmarFlags::ROOT_FLAGS,
base: KObjectBase::new(),
addr: kernel_vmar_base,
size: kernel_vmar_size,
parent: None,
page_table: Arc::new(Mutex::new(kernel_hal::PageTable::new())),
inner: Mutex::new(Some(VmarInner::default())),
})
}
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="vmaddressmapping"><a class="header" href="#vmaddressmapping">VmAddressMapping</a></h3>
<p>VmAddressMapping 用于建立 VMO 和 VMAR 之间的映射。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// Virtual Memory Mapping
pub struct VmMapping {
/// The permission limitation of the vmar
permissions: MMUFlags,
vmo: Arc&lt;VmObject&gt;,
page_table: Arc&lt;Mutex&lt;dyn PageTableTrait&gt;&gt;,
inner: Mutex&lt;VmMappingInner&gt;,
}
#[derive(Debug, Clone)]
struct VmMappingInner {
/// The actual flags used in the mapping of each page
flags: Vec&lt;MMUFlags&gt;,
addr: VirtAddr,
size: usize,
vmo_offset: usize,
}
<span class="boring">}
</span></code></pre></pre>
<p>map 和 unmap 实现内存映射和解映射</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VmMapping {
/// Map range and commit.
/// Commit pages to vmo, and map those to frames in page_table.
/// Temporarily used for development. A standard procedure for
/// vmo is: create_vmo, op_range(commit), map
fn map(self: &amp;Arc&lt;Self&gt;) -&gt; ZxResult {
self.vmo.commit_pages_with(&amp;mut |commit| {
let inner = self.inner.lock();
let mut page_table = self.page_table.lock();
let page_num = inner.size / PAGE_SIZE;
let vmo_offset = inner.vmo_offset / PAGE_SIZE;
for i in 0..page_num {
let paddr = commit(vmo_offset + i, inner.flags[i])?;
//通过 PageTableTrait 的 hal_pt_map 进行页表映射
//调用 kernel-hal的方法进行映射
}
Ok(())
})
}
fn unmap(&amp;self) {
let inner = self.inner.lock();
let pages = inner.size / PAGE_SIZE;
// TODO inner.vmo_offset unused?
// 调用 kernel-hal的方法进行解映射
}
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="hal用-mmap-模拟页表"><a class="header" href="#hal用-mmap-模拟页表">HAL用 mmap 模拟页表</a></h2>
<blockquote>
<p>实现页表接口 map, unmap, protect</p>
</blockquote>
<p>在 kernel-hal 中定义了一个页表和这个页表具有的方法。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// Page Table
#[repr(C)]
pub struct PageTable {
table_phys: PhysAddr,
}
impl PageTable {
/// Get current page table
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_current&quot;]
pub fn current() -&gt; Self {
unimplemented!()
}
/// Create a new `PageTable`.
#[allow(clippy::new_without_default)]
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_new&quot;]
pub fn new() -&gt; Self {
unimplemented!()
}
}
impl PageTableTrait for PageTable {
/// Map the page of `vaddr` to the frame of `paddr` with `flags`.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_map&quot;]
fn map(&amp;mut self, _vaddr: VirtAddr, _paddr: PhysAddr, _flags: MMUFlags) -&gt; Result&lt;()&gt; {
unimplemented!()
}
/// Unmap the page of `vaddr`.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_unmap&quot;]
fn unmap(&amp;mut self, _vaddr: VirtAddr) -&gt; Result&lt;()&gt; {
unimplemented!()
}
/// Change the `flags` of the page of `vaddr`.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_protect&quot;]
fn protect(&amp;mut self, _vaddr: VirtAddr, _flags: MMUFlags) -&gt; Result&lt;()&gt; {
unimplemented!()
}
/// Query the physical address which the page of `vaddr` maps to.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_query&quot;]
fn query(&amp;mut self, _vaddr: VirtAddr) -&gt; Result&lt;PhysAddr&gt; {
unimplemented!()
}
/// Get the physical address of root page table.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_table_phys&quot;]
fn table_phys(&amp;self) -&gt; PhysAddr {
self.table_phys
}
/// Activate this page table
#[cfg(target_arch = &quot;riscv64&quot;)]
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_activate&quot;]
fn activate(&amp;self) {
unimplemented!()
}
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_unmap_cont&quot;]
fn unmap_cont(&amp;mut self, vaddr: VirtAddr, pages: usize) -&gt; Result&lt;()&gt; {
for i in 0..pages {
self.unmap(vaddr + i * PAGE_SIZE)?;
}
Ok(())
}
}
<span class="boring">}
</span></code></pre></pre>
<p>在 kernel-hal-unix 中实现了 PageTableTrait在 map 中调用了 mmap。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl PageTableTrait for PageTable {
/// Map the page of `vaddr` to the frame of `paddr` with `flags`.
#[export_name = &quot;hal_pt_map&quot;]
fn map(&amp;mut self, vaddr: VirtAddr, paddr: PhysAddr, flags: MMUFlags) -&gt; Result&lt;()&gt; {
debug_assert!(page_aligned(vaddr));
debug_assert!(page_aligned(paddr));
let prot = flags.to_mmap_prot();
mmap(FRAME_FILE.as_raw_fd(), paddr, PAGE_SIZE, vaddr, prot);
Ok(())
}
/// Unmap the page of `vaddr`.
#[export_name = &quot;hal_pt_unmap&quot;]
fn unmap(&amp;mut self, vaddr: VirtAddr) -&gt; Result&lt;()&gt; {
self.unmap_cont(vaddr, 1)
}
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="实现内存映射"><a class="header" href="#实现内存映射">实现内存映射</a></h2>
<blockquote>
<p>用 HAL 实现上面 VMAR 留空的部分,并做单元测试验证内存映射</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VmMapping {
/// Map range and commit.
/// Commit pages to vmo, and map those to frames in page_table.
/// Temporarily used for development. A standard procedure for
/// vmo is: create_vmo, op_range(commit), map
fn map(self: &amp;Arc&lt;Self&gt;) -&gt; ZxResult {
self.vmo.commit_pages_with(&amp;mut |commit| {
let inner = self.inner.lock();
let mut page_table = self.page_table.lock();
let page_num = inner.size / PAGE_SIZE;
let vmo_offset = inner.vmo_offset / PAGE_SIZE;
for i in 0..page_num {
let paddr = commit(vmo_offset + i, inner.flags[i])?;
//通过 PageTableTrait 的 hal_pt_map 进行页表映射
page_table
.map(inner.addr + i * PAGE_SIZE, paddr, inner.flags[i])
.expect(&quot;failed to map&quot;);
}
Ok(())
})
}
fn unmap(&amp;self) {
let inner = self.inner.lock();
let pages = inner.size / PAGE_SIZE;
// TODO inner.vmo_offset unused?
self.page_table
.lock()
.unmap_cont(inner.addr, pages)
.expect(&quot;failed to unmap&quot;)
}
}
<span class="boring">}
</span></code></pre></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="用户程序"><a class="header" href="#用户程序">用户程序</a></h1>
<p>zCore采用的是微内核的设计风格。微内核设计的一个复杂问题是”如何引导初始用户空间进程“。通常这是通过让内核实现最小版本的文件系统读取和程序加载来实现的引导。</p>
<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>
<h3 id="流程概要"><a class="header" href="#流程概要">流程概要</a></h3>
<p>kernel<br />
-&gt; userboot (decompress bootsvc LZ4 format)<br />
-&gt; bootsvc (可执行文件bin/component_manager)<br />
-&gt; component_manager<br />
-&gt; sh / device_manager</p>
<h3 id="zbizircon-boot-image"><a class="header" href="#zbizircon-boot-image">ZBI(Zircon Boot Image)</a></h3>
<p>ZBI是一种简单的容器格式它内嵌了许多可由引导加载程序 <code>BootLoader</code>传递的项目内容包括硬件特定的信息、提供引导选项的内核“命令行”以及RAM磁盘映像(通常是被压缩的)。<code>ZBI</code>中包含了初始文件系统 <code>bootfs</code>,内核将 <code>ZBI</code> 完整传递给 <code>userboot</code>,由它负责解析并对其它进程提供文件服务。</p>
<h3 id="bootfs"><a class="header" href="#bootfs">bootfs</a></h3>
<p>基本的<code>bootfs</code>映像可满足用户空间程序运行需要的所有依赖:
+ 可执行文件
+ 共享库
+ 数据文件</p>
<p>以上列出的内容还可实现设备驱动或更高级的文件系统,从而能够从存储设备或网络设备上访问读取更多的代码和数据。</p>
<p>在系统自引导结束后,<code>bootfs</code>中的文件就会成为一个挂载在根目录<code>/boot</code>上的只读文件系统树(并由bootsvc提供服务)。随后<code>userboot</code>将从<code>bootfs</code>加载第一个真正意义上的用户程序。</p>
<h3 id="a-hrefhttpsfuchsiadevfuchsia-srcconceptsbootingprogram_loadingzcore程序elf加载与动态链接a"><a class="header" href="#a-hrefhttpsfuchsiadevfuchsia-srcconceptsbootingprogram_loadingzcore程序elf加载与动态链接a"><a href="https://fuchsia.dev/fuchsia-src/concepts/booting/program_loading">zCore程序(ELF加载与动态链接)</a></a></h3>
<p>zCore内核不直接参与正常程序的加载而是提供了一些用户态程序加载时可用的模块。如虚拟内存对象(VMO)、进程(processes)、虚拟地址空间VMAR和线程(threads)。 </p>
<h3 id="elf-格式以及系统应用程序二进制接口system-abi"><a class="header" href="#elf-格式以及系统应用程序二进制接口system-abi">ELF 格式以及系统应用程序二进制接口(system ABI)</a></h3>
<p>标准的zCore用户空间环境提供了动态链接器以及基于ELF的执行环境能够运行ELF格式的格式机器码可执行文件。zCore进程只能通过zCore vDSO使用系统调用。内核采用基于ELF系统常见的程序二进制接口(ABI)提供了vDSO。</p>
<p>具备适当功能的用户空间代码可通过系统调用直接创建进程和加载程序而不用ELF。但是zCore的标准ABI使用了这里所述的ELF。有关ELF文件格式的背景知识如下 </p>
<h3 id="elf文件类型"><a class="header" href="#elf文件类型">ELF文件类型</a></h3>
<p>“ET_REL”代表此ELF文件为可重定位文件</p>
<p>“ET_EXEC“代表ELF文件为可执行文件</p>
<p>“ET_DYN”代表此ELF文件为动态链接库</p>
<p>“ET_CORE”代表此ELF文件是核心转储文件</p>
<h3 id="传统elf程序文件加载"><a class="header" href="#传统elf程序文件加载">传统ELF程序文件加载</a></h3>
<p>可执行链接格式(Executable and Linking Format, ELF)最初由 UNIX 系统实验室开发并发布并成为大多数类Unix系统的通用标准可执行文件格式。在这些系统中内核使用<code>POSIX</code>(可移植操作系统接口)<code>execve API</code>将程序加载与文件系统访问集成在一起。该类系统加载ELF程序的方式会有一些不同但大多遵循以下模式:</p>
<ol>
<li>
<p>内核按照名称加载文件并检查它是ELF还是系统支持的其他类型的文件。</p>
</li>
<li>
<p>内核根据ELF文件的<code>PT_LOAD</code>程序头来映射ELF映像。对于<code>ET_EXEC</code>文件,系统会将程序中的各段(Section)放到<code>p_vaddr</code>中所指定内存中的固定地址。对于<code>ET_DYN</code>文件,系统将加载程序第一个<code>PT_LOAD</code>的基地址,然后根据它们的<code>p_vaddr</code>相对于第一个section的<code>p_vaddr</code>放置后面的section。 通常来说该基地址是通过地址随机化(ASLR)来产生的。</p>
</li>
<li>
<p>若ELF文件中有一个<code>PT_INTERP</code>(Program interpreter)程序头, 它的部分内容(ELF文件中<code>p_offset</code><code>p_filesz</code>给出的一些字节)被当做为一个文件名改文件名用于寻找另一个称为“ELF解释器”的ELF文件。上述这种ELF文件是<code>ET_DYN</code>文件。内核采用同样的方式将该类ELF文件加载但是所加载的地址是自定的。该ELF“解释器”通常指的是被命名为<code>/lib/ld.so.1</code> 或者是 <code>/lib/ld-linux.so.2</code>的ELF动态链接器。</p>
</li>
<li>
<p>内核为初始的线程设置了寄存器和堆栈的内容并在PC寄存器已指向特定程序入口处(Entry Point)的情况下启动线程。 </p>
<ul>
<li>程序入口处(Entry Point)指的是ELF文件头中 <code>e_entry</code>的值,它会根据程序基地址(base address)做相应的调整。如果这是一个带有<code>PT_INTERP</code>的ELF文件则它的入口点不在它本身而是被设置在动态链接器中。</li>
<li>内核通过设置寄存器和堆栈来使得程序能够接收特定的参数环境变量以及其它有实际用途的辅助向量。寄存器和堆栈的设置方法遵循了一种汇编级别的协议方式。若ELF文件运行时依赖动态链接即带有<code>PT_INTERP</code>。则寄存器和堆栈中将包括来自该可执行文件的ELF文件头中的基地址、入口点和程序头部表地址信息这些信息可允许动态链接器在内存中找到该可执行文件的ELF动态链接元数据以实现动态链接。当动态链接启动完成后动态链接器将跳转到该可执行文件的入口点地址。</li>
</ul>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> pub fn sys_process_start(
&amp;self,
proc_handle: HandleValue,
thread_handle: HandleValue,
entry: usize,
stack: usize,
arg1_handle: HandleValue,
arg2: usize,
) -&gt; ZxResult {
info!(&quot;process.start: proc_handle={:?}, thread_handle={:?}, entry={:?}, stack={:?}, arg1_handle={:?}, arg2={:?}&quot;,
proc_handle, thread_handle, entry, stack, arg1_handle, arg2
);
let proc = self.thread.proc();
let process = proc.get_object_with_rights::&lt;Process&gt;(proc_handle, Rights::WRITE)?;
let thread = proc.get_object_with_rights::&lt;Thread&gt;(thread_handle, Rights::WRITE)?;
if !Arc::ptr_eq(&amp;thread.proc(), &amp;process) {
return Err(ZxError::ACCESS_DENIED);
}
let arg1 = if arg1_handle != INVALID_HANDLE {
let arg1 = proc.remove_handle(arg1_handle)?;
if !arg1.rights.contains(Rights::TRANSFER) {
return Err(ZxError::ACCESS_DENIED);
}
Some(arg1)
} else {
None
};
process.start(&amp;thread, entry, stack, arg1, arg2, self.spawn_fn)?;
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
</li>
</ol>
<p>zCore的程序加载受到了传统方式的启发但是有一些不同。在传统模式中需要在加载动态链接器之前加载可执行文件的一个关键原因是动态链接器随机化选择的基地址(base address)不能与<code>ET_EXEC</code>可执行文件使用的固定地址相交。zCore从根本上并不支持<code>ET_EXEC</code>格式ELF文件的固定地址程序加载它只支持位置无关的可执行文件或<a href="https://patchwork.kernel.org/patch/9807325/">PIE</a>(<code>ET_DYN</code>格式的ELF文件)</p>
<h3 id="vmarext-trait实现"><a class="header" href="#vmarext-trait实现">VmarExt trait实现</a></h3>
<p>zCore底层的API不支持文件系统。zCore程序文件的加载通过虚拟内存对象(VMO)以及<code>channel</code>使用的进程间通信机制来完成。</p>
<p>程序的加载基于如下一些前提:
+ 获得一个包含可执行文件的虚拟内存对象VMO的句柄。</p>
<blockquote>
<p>zircon-object\src\util\elf_loader.rs</p>
</blockquote>
<pre><code class="language-shell">fn make_vmo(elf: &amp;ElfFile, ph: ProgramHeader) -&gt; ZxResult&lt;Arc&lt;VmObject&gt;&gt; {
assert_eq!(ph.get_type().unwrap(), Type::Load);
let page_offset = ph.virtual_addr() as usize % PAGE_SIZE;
let pages = pages(ph.mem_size() as usize + page_offset);
let vmo = VmObject::new_paged(pages);
let data = match ph.get_data(&amp;elf).unwrap() {
SegmentData::Undefined(data) =&gt; data,
_ =&gt; return Err(ZxError::INVALID_ARGS),
};
vmo.write(page_offset, data)?;
Ok(vmo)
}
</code></pre>
<ul>
<li>程序执行参数列表。</li>
<li>程序执行环境变量列表。</li>
<li>存在一个初始的句柄列表,每个句柄都有一个句柄信息项。</li>
</ul>
<h3 id="userboot"><a class="header" href="#userboot">USERBOOT</a></h3>
<h4 id="使用userboot的原因"><a class="header" href="#使用userboot的原因">使用userboot的原因</a></h4>
<p>在Zircon中内嵌在ZBI中的<code>RAM磁盘映像</code>通常采用<a href="https://github.com/lz4/lz4">LZ4</a>格式压缩。解压后将继续得到<code>bootfs</code>格式的磁盘镜像。这是一种简单的只读文件系统格式它只列出文件名。且对于每个文件可分别列出它们在BOOTFS映像中的偏移量和大小(这两个值都必须是页面对齐的并且限制在32位)。</p>
<p>由于kernel中没有包含任何可用于解压缩<a href="https://github.com/lz4/lz4">LZ4</a>格式的代码也没有任何用于解析BOOTFS格式的代码。所有这些工作都是由称为<code>userboot</code>的第一个用户空间进程完成的。</p>
<blockquote>
<p>zCore中未找到解压缩bootfs的相关实现<br />
但是能够在scripts/gen-prebuilt.sh中找到ZBI中确实有bootfs的内容<br />
且现有的zCore实现中有关所载入的ZBI方式如下</p>
</blockquote>
<blockquote>
<p>zircon-loader/src/lib.rs</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> // zbi
let zbi_vmo = {
let vmo = VmObject::new_paged(images.zbi.as_ref().len() / PAGE_SIZE + 1);
vmo.write(0, images.zbi.as_ref()).unwrap();
vmo.set_name(&quot;zbi&quot;);
vmo
};
<span class="boring">}
</span></code></pre></pre>
<h4 id="userboot是什么"><a class="header" href="#userboot是什么">userboot是什么</a></h4>
<p>userboot是一个普通的用户空间进程。它只能像任何其他进程一样通过vDSO执行标准的系统调用并受完整vDSO执行制度的约束。</p>
<blockquote>
<p>唯一一个由内核态“不规范地”创建的用户进程</p>
<p>userboot具体实现的功能有</p>
<ul>
<li>
<p>读取channel中的cmdline、handles </p>
</li>
<li>
<p>解析zbi</p>
</li>
<li>
<p>解压BOOTFS </p>
</li>
<li>
<p>选定下一个程序启动 自己充当loader然后“死亡”</p>
</li>
<li>
<p>用“规范的方式”启动下一个程序</p>
</li>
</ul>
</blockquote>
<p>userboot被构建为一个ELF动态共享对象(DSO,dynamic shared object)使用了与vDSO相同的布局。与vDSO一样userboot的ELF映像在编译时就被嵌入到内核中。其简单的布局意味着加载它不需要内核在引导时解析ELF的文件头。内核只需要知道三件事:</p>
<ol>
<li>只读段<code>segment</code>的大小</li>
<li>可执行段<code>segment</code>的大小</li>
<li><code>userboot</code>入口点的地址。</li>
</ol>
<p>这些值在编译时便可从userboot ELF映像中提取并在内核代码中用作常量。</p>
<h4 id="kernel如何启用userboot"><a class="header" href="#kernel如何启用userboot">kernel如何启用userboot</a></h4>
<p>与任何其他进程一样userboot必须从已经映射到其地址空间的vDSO开始这样它才能进行系统调用。内核将userboot和vDSO映射到第一个用户进程然后在userboot的入口处启动它。</p>
<!-- > ! userboot的特殊之处在于它的加载方式。
> ...todo -->
<h4 id="userboot如何在vdso中取得系统调用"><a class="header" href="#userboot如何在vdso中取得系统调用">userboot如何在vDSO中取得系统调用</a></h4>
<p>当内核将<code>userboot</code>映射到第一个用户进程时,会像正常程序那样,在内存中选择一个随机地址进行加载。而在映射<code>userboot</code>的vDSO时并不采用上述随机的方式而是将vDSO映像直接放在内存中<code>userboot</code>的映像之后。这样一来vDSO代码与<code>userboot</code>的偏移量总是固定的。</p>
<p>在编译阶段中系统调用的入口点符号表会从vDSO ELF映像中提取出来随后写入到链接脚本的符号定义中。利用每个符号在vDSO映像中相对固定的偏移地址可在链接脚本提供的<code>_end</code>符号的固定偏移量处定义该符号。通过这种方式userboot代码可以直接调用到放在内存中其映像本身之后的每个确切位置上的vDSO入口点。</p>
<p>相关代码:</p>
<blockquote>
<p>zircon-loader/src/lib.rs</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub fn run_userboot(images: &amp;Images&lt;impl AsRef&lt;[u8]&gt;&gt;, cmdline: &amp;str) -&gt; Arc&lt;Process&gt; {
...
// vdso
let vdso_vmo = {
let elf = ElfFile::new(images.vdso.as_ref()).unwrap();
let vdso_vmo = VmObject::new_paged(images.vdso.as_ref().len() / PAGE_SIZE + 1);
vdso_vmo.write(0, images.vdso.as_ref()).unwrap();
let size = elf.load_segment_size();
let vmar = vmar
.allocate_at(
userboot_size,
size,
VmarFlags::CAN_MAP_RXW | VmarFlags::SPECIFIC,
PAGE_SIZE,
)
.unwrap();
vmar.map_from_elf(&amp;elf, vdso_vmo.clone()).unwrap();
#[cfg(feature = &quot;std&quot;)]
{
let offset = elf
.get_symbol_address(&quot;zcore_syscall_entry&quot;)
.expect(&quot;failed to locate syscall entry&quot;) as usize;
let syscall_entry = &amp;(kernel_hal_unix::syscall_entry as usize).to_ne_bytes();
// fill syscall entry x3
vdso_vmo.write(offset, syscall_entry).unwrap();
vdso_vmo.write(offset + 8, syscall_entry).unwrap();
vdso_vmo.write(offset + 16, syscall_entry).unwrap();
}
vdso_vmo
};
...
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="bootsvc"><a class="header" href="#bootsvc">bootsvc</a></h3>
<p>bootsvc 通常是usermode加载的第一个程序与userboot不同userboot是由内核加载的。bootsvc提供了几种系统服务</p>
<ul>
<li>
<p>包含bootfs/boot内容的文件系统服务初始的bootfs映像包含用户空间系统需要运行的所有内容:</p>
<ul>
<li>可执行文件</li>
<li>共享库和数据文件(包括设备驱动程序或更高级的文件系统的实现)</li>
</ul>
</li>
<li>
<p>从bootfs加载的加载程序服务</p>
</li>
<li>
<p>bin/component_manager</p>
</li>
<li>
<p>sh / device_manager</p>
</li>
</ul>
<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="系统调用的跳板vdso"><a class="header" href="#系统调用的跳板vdso">系统调用的跳板vDSO</a></h2>
<h4 id="介绍-vdso-的作用"><a class="header" href="#介绍-vdso-的作用">介绍 vDSO 的作用</a></h4>
<p>vDSOvirtual Dynamic Shared ObjectZircon vDSO 是 Zircon 内核访问系统调用的唯一方法(作为系统调用的跳板)。它之所以是虚拟的是因为它不是从文件系统中的ELF文件加载的而是由内核直接提供的vDSO镜像。</p>
<!-- Zircon vDSO是访问Zircon系统调用的唯一手段。vDSO表示虚拟动态共享对象。(动态共享对象是一个术语用于ELF格式的共享库。)它是虚拟的因为它不是从文件系统中的ELF文件加载的。相反vDSO映像由内核直接提供。 -->
<blockquote>
<p>zCore/src/main.rs</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">#[cfg(feature = &quot;zircon&quot;)]
fn main(ramfs_data: &amp;[u8], cmdline: &amp;str) {
use zircon_loader::{run_userboot, Images};
let images = Images::&lt;&amp;[u8]&gt; {
userboot: include_bytes!(&quot;../../prebuilt/zircon/x64/userboot.so&quot;),
vdso: include_bytes!(&quot;../../prebuilt/zircon/x64/libzircon.so&quot;),
zbi: ramfs_data,
};
let _proc = run_userboot(&amp;images, cmdline);
run();
}
</code></pre></pre>
<p>它是一个用户态运行的代码,被封装成<code>prebuilt/zircon/x64/libzircon.so</code>文件。这个.so 文件装载不是放在文件系统中而是由内核提供。它被整合在内核image中。</p>
<p>vDSO映像在编译时嵌入到内核中。内核将它作为只读VMO公开给用户空间。内核启动时会通过计算得到它所在的物理页。当<code>program loader</code>设置了一个新进程后,使该进程能够进行系统调用的唯一方法是:<code>program loader</code>在新进程的第一个线程开始运行之前将vDSO映射到新进程的虚拟地址空间地址随机。因此在启动其他能够进行系统调用的进程的每个进程自己本身都必须能够访问vDSO的VMO。</p>
<blockquote>
<p>zircon-loader/src/lib.rs#line167</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> proc.start(&amp;thread, entry, sp, Some(handle), 0, thread_fn)
.expect(&quot;failed to start main thread&quot;);
proc
<span class="boring">}
</span></code></pre></pre>
<blockquote>
<p>zircon-object/src/task/process.rs#line189</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> thread.start(entry, stack, handle_value as usize, arg2, thread_fn)
<span class="boring">}
</span></code></pre></pre>
<p>vDSO被映射到新进程的同时会将映像的<code>base address</code>通过<code>arg2</code>参数传递给新进程中的第一个线程。通过这个地址可以在内存中找到ELF的文件头该文件头指向可用于查找系统调用符号名的其他ELF程序模块。</p>
<h4 id="如何修改-vdso-源码libzircon将-syscall-改为函数调用"><a class="header" href="#如何修改-vdso-源码libzircon将-syscall-改为函数调用">如何修改 vDSO 源码libzircon将 syscall 改为函数调用</a></h4>
<h5 id="有关代码"><a class="header" href="#有关代码">有关代码</a></h5>
<ul>
<li>参考仓库<a href="https://github.com/PanQL/zircon/blob/master/README.md">README.MD</a>
<blockquote>
<p>···解析代码依赖的compile_commands.json将会随build过程生成到<strong>out</strong>文件夹···</p>
</blockquote>
</li>
</ul>
<h5 id="如何生成imgsvdsozbi"><a class="header" href="#如何生成imgsvdsozbi">如何生成imgs(VDSO,ZBI)</a></h5>
<ol>
<li>
<p>clone Zircon代码仓库从fuchsia官方目录中分离出来的zircon代码</p>
<pre><code class="language-shell">$ git clone https://github.com/PanQL/zircon.git
</code></pre>
</li>
<li>
<p>关于Zircon的编译运行<br />
为了减小仓库体积我们将prebuilt目录进行了大幅调整;因此运行之前请下载google预编译好的clang解压后放到某个权限合适的位置然后在代码的<a href="https://github.com/PanQL/zircon/blob/master/public/gn/toolchain/clang.gni#L16">这个位置</a><strong>绝对目录</strong>修改为对应位置。
clang下载链接:</p>
<ul>
<li><a href="https://cloud.tsinghua.edu.cn/d/7ab1d87feecd4b2cb3d8/">云盘下载链接</a></li>
<li>官方CIPD包下载链接如下
<ul>
<li><a href="https://chrome-infra-packages.appspot.com/p/fuchsia/clang/linux-amd64/+/oEsFSe99FkcDKVxZkAY0MKi6C-yYOan1m-QL45N33W8C">Linux</a></li>
<li><a href="https://chrome-infra-packages.appspot.com/p/fuchsia/clang/mac-amd64/+/Lc64-GTi4kihzkCnW8Vaa80TWTnMpZY0Fy6AqChmqvcC">Mac</a></li>
</ul>
</li>
</ul>
</li>
<li>
<p>当前只支持在Mac OS及Linux x64上进行编译。<br />
默认的<code>make run</code><code>make build</code>是针对x64架构的如果希望编译运行arm架构的zircon那么需要</p>
<ul>
<li>修改out/args.gn中的<code>legacy-image-x64</code><code>legacy-image-arm64</code></li>
<li>重新<code>make build</code></li>
<li><code>make runarm</code></li>
</ul>
</li>
<li>
<p>配合zCore中的有关脚本与补丁文件</p>
<ul>
<li>scripts/gen-prebuilt.sh</li>
<li>scripts/zircon-libos.patch</li>
</ul>
<ul>
<li>https://github.com/PanQL/zircon/blob/master/system/ulib/zircon/syscall-entry.h</li>
<li>https://github.com/PanQL/zircon/blob/master/system/ulib/zircon/syscalls-x86-64.S</li>
<li>zircon-loader/src/lib.rs#line 83-93</li>
</ul>
</li>
</ol>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> #[cfg(feature = &quot;std&quot;)]
{
let offset = elf
.get_symbol_address(&quot;zcore_syscall_entry&quot;)
.expect(&quot;failed to locate syscall entry&quot;) as usize;
let syscall_entry = &amp;(kernel_hal_unix::syscall_entry as usize).to_ne_bytes();
// fill syscall entry x3
vdso_vmo.write(offset, syscall_entry).unwrap();
vdso_vmo.write(offset + 8, syscall_entry).unwrap();
vdso_vmo.write(offset + 16, syscall_entry).unwrap();
}
<span class="boring">}
</span></code></pre></pre>
<!-- 当vsdo 用svc 指令后这时CPU exception进入内核到 expections.S 中的 sync_exception 宏不同ELx sync_exception的参数不一样。然后这个 sync_exception 宏中先做一些现场保存的工作, 然后jump到 arm64_syscall_dispatcher 宏。
进入arm64_syscall_dispatcher宏后 先做一些syscall number检查然后syscall number 跳到 call_wrapper_table 函数表中相应index项的函数中去call_wrapper_table 像一个一维的函数指针的数组syscall number 作indexjump到相应的wrapper syscall function 函数中去)。 -->
<h4 id="加载-vdso-时修改-vdso-代码段填入跳转地址"><a class="header" href="#加载-vdso-时修改-vdso-代码段填入跳转地址">加载 vDSO 时修改 vDSO 代码段,填入跳转地址</a></h4>
<h2 id="第一个用户程序userboot"><a class="header" href="#第一个用户程序userboot">第一个用户程序userboot</a></h2>
<blockquote>
<p>实现 zircon-loader 中的 run_userboot 函数</p>
<p>能够进入用户态并在第一个系统调用时跳转回来</p>
</blockquote>
<h4 id="从bootfs加载第一个真正意义上的用户程序"><a class="header" href="#从bootfs加载第一个真正意义上的用户程序"><code>bootfs</code>加载第一个真正意义上的用户程序。</a></h4>
<p>主要相关代码:</p>
<blockquote>
<p>zircon-loader/src/lib.rs
zircon-object/src/util/elf_loader.rs</p>
</blockquote>
<p><code>userboot</code>解压完毕<code>ZBI</code>中的<code>bootfs</code>后,<code>userboot</code>将继续从<code>bootfs</code>载入程序文件运行。</p>
<p>Zircon中具体的实现流程如下</p>
<ol>
<li>
<p><code>userboot</code>检查从内核接收到的环境字符串,这些字符串代表了一定的内核命令行。</p>
<blockquote>
<p>zircon-loader/src/main.rs</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">#[async_std::main]
async fn main() {
kernel_hal_unix::init();
init_logger();
let opt = Opt::from_args();
let images = open_images(&amp;opt.prebuilt_path).expect(&quot;failed to read file&quot;);
let proc: Arc&lt;dyn KernelObject&gt; = run_userboot(&amp;images, &amp;opt.cmdline);
drop(images);
proc.wait_signal(Signal::USER_SIGNAL_0).await;
}
</code></pre></pre>
<p>在Zircon中</p>
<ul>
<li>若该字符串内容为<code>userboot=file</code>,那么该<code>file</code>将作为第一个真正的用户进程加载。</li>
<li>若没有这样的选项,则<code>userboot</code>将选择的默认文为<code>bin/bootsvc</code>。该文件可在<code>bootfs</code>中找到。</li>
</ul>
<p>而在zCore的实现中</p>
<ul>
<li>..</li>
</ul>
</li>
<li>
<p>为了加载上述文件userboot实现了一个功能齐全的ELF程序加载器
<code>zircon_object::util::elf_loader::load_from_elf</code></p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> // userboot
let (entry, userboot_size) = {
let elf = ElfFile::new(images.userboot.as_ref()).unwrap();
let size = elf.load_segment_size();
let vmar = vmar
.allocate(None, size, VmarFlags::CAN_MAP_RXW, PAGE_SIZE)
.unwrap();
vmar.load_from_elf(&amp;elf).unwrap();
(vmar.addr() + elf.header.pt2.entry_point() as usize, size)
};
<span class="boring">}
</span></code></pre></pre>
</li>
<li>
<p>然后userboot以随机地址加载vDSO。它使用标准约定启动新进程并给它传递一个channel句柄和vDSO基址。
<code>zircon_object::util::elf_loader::map_from_elf</code></p>
</li>
</ol>
<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>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct UserContext {
pub general: GeneralRegs,
pub trap_num: usize,
pub error_code: usize,
}
<span class="boring">}
</span></code></pre></pre>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct GeneralRegs {
pub rax: usize,
pub rbx: usize,
pub rcx: usize,
pub rdx: usize,
pub rsi: usize,
pub rdi: usize,
pub rbp: usize,
pub rsp: usize,
pub r8: usize,
pub r9: usize,
pub r10: usize,
pub r11: usize,
pub r12: usize,
pub r13: usize,
pub r14: usize,
pub r15: usize,
pub rip: usize,
pub rflags: usize,
pub fsbase: usize,
pub gsbase: usize,
}
<span class="boring">}
</span></code></pre></pre>
<p><code>Usercontext</code>保存了用户执行的上下文包括跳转到用户态之后程序的第一条指令的地址如果程序首次从内核态进入用户态执行则rip指向用户进程的第一条指令的地址。</p>
<blockquote>
<p>保存 callee-saved 寄存器到栈上,恢复 UserContext 寄存器,进入用户态,反之亦然</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>syscall_fn_return:
<span class="boring"> save callee-saved registers
</span> push r15
push r14
push r13
push r12
push rbp
push rbx
push rdi
SAVE_KERNEL_STACK
mov rsp, rdi
POP_USER_FSBASE
<span class="boring"> pop trap frame (struct GeneralRegs)
</span> pop rax
pop rbx
pop rcx
pop rdx
pop rsi
pop rdi
pop rbp
pop r8 # skip rsp
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
pop r11 # r11 = rip. FIXME: don't overwrite r11!
popfq # pop rflags
mov rsp, [rsp - 8*11] # restore rsp
jmp r11 # restore rip
<span class="boring">}
</span></code></pre></pre>
<p>弹出的寄存器恰好对应了GeneralRegs的结构通过在rust的unsafe代码块中调用<code>syscall_fn_return</code>函数,并且传递<code>Usercontext</code>结构体的指针到rdi中可以创造出程序进入用户态的运行环境。</p>
<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="测试-1"><a class="header" href="#测试-1">测试</a></h2>
<blockquote>
<p>编写单元测试验证上述过程</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[cfg(test)]
mod tests {
use crate::*;
#[cfg(target_os = &quot;macos&quot;)]
global_asm!(&quot;.set _dump_registers, dump_registers&quot;);
// Mock user program to dump registers at stack.
global_asm!(
r#&quot;
dump_registers:
push r15
push r14
push r13
push r12
push r11
push r10
push r9
push r8
push rsp
push rbp
push rdi
push rsi
push rdx
push rcx
push rbx
push rax
add rax, 10
add rbx, 10
add rcx, 10
add rdx, 10
add rsi, 10
add rdi, 10
add rbp, 10
add r8, 10
add r9, 10
add r10, 10
add r11, 10
add r12, 10
add r13, 10
add r14, 10
add r15, 10
call syscall_fn_entry
&quot;#
);
#[test]
fn run_fncall() {
extern &quot;sysv64&quot; {
fn dump_registers();
}
let mut stack = [0u8; 0x1000];
let mut cx = UserContext {
general: GeneralRegs {
rax: 0,
rbx: 1,
rcx: 2,
rdx: 3,
rsi: 4,
rdi: 5,
rbp: 6,
rsp: stack.as_mut_ptr() as usize + 0x1000,
r8: 8,
r9: 9,
r10: 10,
r11: 11,
r12: 12,
r13: 13,
r14: 14,
r15: 15,
rip: dump_registers as usize,
rflags: 0,
fsbase: 0, // don't set to non-zero garbage value
gsbase: 0,
},
trap_num: 0,
error_code: 0,
};
cx.run_fncall();
// check restored registers
let general = unsafe { *(cx.general.rsp as *const GeneralRegs) };
assert_eq!(
general,
GeneralRegs {
rax: 0,
rbx: 1,
rcx: 2,
rdx: 3,
rsi: 4,
rdi: 5,
rbp: 6,
// skip rsp
r8: 8,
r9: 9,
r10: 10,
// skip r11
r12: 12,
r13: 13,
r14: 14,
r15: 15,
..general
}
);
// check saved registers
assert_eq!(
cx.general,
GeneralRegs {
rax: 10,
rbx: 11,
rcx: 12,
rdx: 13,
rsi: 14,
rdi: 15,
rbp: 16,
// skip rsp
r8: 18,
r9: 19,
r10: 20,
// skip r11
r12: 22,
r13: 23,
r14: 24,
r15: 25,
..cx.general
}
);
assert_eq!(cx.trap_num, 0x100);
assert_eq!(cx.error_code, 0);
}
}
<span class="boring">}
</span></code></pre></pre>
<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="zircon-系统调用"><a class="header" href="#zircon-系统调用">Zircon 系统调用</a></h1>
<blockquote>
<p>目录位于<code>zCore/zircon-syscall</code></p>
</blockquote>
<p>从userboot运行起来到实现调用syscall的简要函数调用流程如下</p>
<ol>
<li>run_userboot -&gt;</li>
<li>proc.start -&gt;</li>
<li>thread_fn -&gt;</li>
<li>new_thread -&gt; </li>
<li>handle_syscall -&gt; </li>
<li>syscall -&gt;</li>
<li>sys_handle_close() (举例某一具体的syscall运行该syscall可用于实现<code>close a handle</code>的功能)</li>
</ol>
<h2 id="获取系统调用参数"><a class="header" href="#获取系统调用参数">获取系统调用参数</a></h2>
<p>从寄存器中获取参数</p>
<blockquote>
<p>不同的计算机体系结构获得参数的方式不同</p>
<p>以下区分<code>x86_64</code>以及<code>aarch64</code></p>
</blockquote>
<p>调用syscall需要从寄存器收集两种参数<br />
+ <code>num</code> : 系统调用号<br />
+ <code>args</code> : 具体某一系统调用的参数</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>async fn handle_syscall(thread: &amp;CurrentThread, regs: &amp;mut GeneralRegs) {
#[cfg(target_arch = &quot;x86_64&quot;)]
let num = regs.rax as u32;
#[cfg(target_arch = &quot;aarch64&quot;)]
let num = regs.x16 as u32;
// LibOS: Function call ABI
#[cfg(feature = &quot;std&quot;)]
#[cfg(target_arch = &quot;x86_64&quot;)]
let args = unsafe {
let a6 = (regs.rsp as *const usize).read();
let a7 = (regs.rsp as *const usize).add(1).read();
[
regs.rdi, regs.rsi, regs.rdx, regs.rcx, regs.r8, regs.r9, a6, a7,
]
};
// RealOS: Zircon syscall ABI
#[cfg(not(feature = &quot;std&quot;))]
#[cfg(target_arch = &quot;x86_64&quot;)]
let args = [
regs.rdi, regs.rsi, regs.rdx, regs.r10, regs.r8, regs.r9, regs.r12, regs.r13,
];
// ARM64
#[cfg(target_arch = &quot;aarch64&quot;)]
let args = [
regs.x0, regs.x1, regs.x2, regs.x3, regs.x4, regs.x5, regs.x6, regs.x7,
];
let mut syscall = Syscall {
regs,
thread,
thread_fn,
};
let ret = syscall.syscall(num, args).await as usize;
#[cfg(target_arch = &quot;x86_64&quot;)]
{
syscall.regs.rax = ret;
}
#[cfg(target_arch = &quot;aarch64&quot;)]
{
syscall.regs.x0 = ret;
}
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="系统调用上下文与处理函数"><a class="header" href="#系统调用上下文与处理函数">系统调用上下文与处理函数</a></h2>
<h3 id="定义-syscall-结构体"><a class="header" href="#定义-syscall-结构体">定义 Syscall 结构体</a></h3>
<p>保存上下文信息</p>
<blockquote>
<p>zCore/zircon-syscall/src/lib.rs#L52</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// 系统调用的结构(存储关于创建系统调用的信息)
pub struct Syscall&lt;'a&gt; {
/// store the regs statues
pub regs: &amp;'a mut GeneralRegs,
/// the thread making a syscall
pub thread: &amp;'a CurrentThread,
/// new thread function
pub thread_fn: ThreadFn,
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="实现-syscall-函数"><a class="header" href="#实现-syscall-函数">实现 syscall 函数</a></h3>
<blockquote>
<p>zCore/zircon-syscall/src/lib.rs#L59</p>
</blockquote>
<ol>
<li>检查系统调用号<code>sys_type</code>是否合法</li>
<li>获取传递给具体某一系统调用的参数<code>args</code></li>
<li>若syscall函数输入的系统调用号合法则进一步根据系统调用号匹配具体系统调用处理函数</li>
<li>传入对应系统调用所需的参数,并运行之</li>
<li>检查系统调用的返回值<code>ret</code>是否符合预期 </li>
</ol>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> pub async fn syscall(&amp;mut self, num: u32, args: [usize; 8]) -&gt; isize {
...
// 1. 检查系统调用号`sys_type`是否合法
let sys_type = match Sys::try_from(num) {
Ok(t) =&gt; t,
Err(_) =&gt; {
error!(&quot;invalid syscall number: {}&quot;, num);
return ZxError::INVALID_ARGS as _;
}
};
...
// 2. 获取传递给具体系统调用参数
let [a0, a1, a2, a3, a4, a5, a6, a7] = args;
// 3. 若syscall函数输入的系统调用号合法
// 则进一步根据系统调用号匹配具体系统调用处理函数
let ret = match sys_type {
// 4. 传入对应系统调用所需的参数,并运行之
Sys::HANDLE_CLOSE =&gt; self.sys_handle_close(a0 as _),
Sys::HANDLE_CLOSE_MANY =&gt; self.sys_handle_close_many(a0.into(), a1 as _),
Sys::HANDLE_DUPLICATE =&gt; self.sys_handle_duplicate(a0 as _, a1 as _, a2.into()),
Sys::HANDLE_REPLACE =&gt; self.sys_handle_replace(a0 as _, a1 as _, a2.into()),
...
// 更多系统调用匹配的分支
Sys::CLOCK_GET =&gt; self.sys_clock_get(a0 as _, a1.into()),
Sys::CLOCK_READ =&gt; self.sys_clock_read(a0 as _, a1.into()),
Sys::CLOCK_ADJUST =&gt; self.sys_clock_adjust(a0 as _, a1 as _, a2 as _),
Sys::CLOCK_UPDATE =&gt; self.sys_clock_update(a0 as _, a1 as _, a2.into()),
Sys::TIMER_CREATE =&gt; self.sys_timer_create(a0 as _, a1 as _, a2.into()),
...
};
...
// 5. 检查系统调用的返回值`ret`是否符合预期
match ret {
Ok(_) =&gt; 0,
Err(err) =&gt; err as isize,
}
}
<span class="boring">}
</span></code></pre></pre>
<p>系统调用号匹配信息位于</p>
<blockquote>
<p>zCore/zircon-syscall/src/consts.rs#L8</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub enum SyscallType {
BTI_CREATE = 0,
BTI_PIN = 1,
BTI_RELEASE_QUARANTINE = 2,
CHANNEL_CREATE = 3,
CHANNEL_READ = 4,
CHANNEL_READ_ETC = 5,
CHANNEL_WRITE = 6,
CHANNEL_WRITE_ETC = 7,
CHANNEL_CALL_NORETRY = 8,
CHANNEL_CALL_FINISH = 9,
CLOCK_GET = 10,
CLOCK_ADJUST = 11,
CLOCK_GET_MONOTONIC_VIA_KERNEL = 12,
...
VMO_CREATE_CONTIGUOUS = 165,
VMO_CREATE_PHYSICAL = 166,
COUNT = 167,
FUTEX_WAKE_HANDLE_CLOSE_THREAD_EXIT = 200,
VMAR_UNMAP_HANDLE_CLOSE_THREAD_EXIT = 201,
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="简单实现一个系统调用处理函数sys_clock_adjust为例"><a class="header" href="#简单实现一个系统调用处理函数sys_clock_adjust为例">简单实现一个系统调用处理函数(<code>sys_clock_adjust</code>为例)</a></h3>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> pub fn sys_clock_adjust(&amp;self, resource: HandleValue, clock_id: u32, offset: u64) -&gt; ZxResult {
// 1. 记录log信息info!()
info!(
&quot;clock.adjust: resource={:#x?}, id={:#x}, offset={:#x}&quot;,
resource, clock_id, offset
);
// 2. 检查参数合法性(需要归纳出每个系统调用的参数值的范围)
// missing now
// 3. 获取当前进程对象
let proc = self.thread.proc();
// 4. 根据句柄从进程中获取对象
proc.get_object::&lt;Resource&gt;(resource)?
.validate(ResourceKind::ROOT)?;
match clock_id {
ZX_CLOCK_MONOTONIC =&gt; Err(ZxError::ACCESS_DENIED),
// 5. 调用内河对象API执行具体功能
ZX_CLOCK_UTC =&gt; {
UTC_OFFSET.store(offset, Ordering::Relaxed);
Ok(())
}
_ =&gt; Err(ZxError::INVALID_ARGS),
}
}
<span class="boring">}
</span></code></pre></pre>
<blockquote>
<p>一个现有不完全的系统调用实现<code>sys_clock_get</code></p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> /// Acquire the current time.
///
/// + Returns the current time of clock_id via `time`.
/// + Returns whether `clock_id` was valid.
pub fn sys_clock_get(&amp;self, clock_id: u32, mut time: UserOutPtr&lt;u64&gt;) -&gt; ZxResult {
// 记录log信息info!()
info!(&quot;clock.get: id={}&quot;, clock_id);
// 检查参数合法性
// miss
match clock_id {
ZX_CLOCK_MONOTONIC =&gt; {
time.write(timer_now().as_nanos() as u64)?;
Ok(())
}
ZX_CLOCK_UTC =&gt; {
time.write(timer_now().as_nanos() as u64 + UTC_OFFSET.load(Ordering::Relaxed))?;
Ok(())
}
ZX_CLOCK_THREAD =&gt; {
time.write(self.thread.get_time())?;
Ok(())
}
_ =&gt; Err(ZxError::NOT_SUPPORTED),
}
}
<span class="boring">}
</span></code></pre></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="信号和等待"><a class="header" href="#信号和等待">信号和等待</a></h1>
<h2 id="信号-1"><a class="header" href="#信号-1">信号</a></h2>
<p>对象可能有多达 32 个信号(由 zx_signals <em>t 类型和 ZX</em> <strong>SIGNAL</strong> 定义表示),它们表示有关其当前状态的一条信息。例如,通道和套接字可能是 READABLE 或 WRITABLE 的。进程或线程可能会被终止。等等。</p>
<p>线程可以等待信号在一个或多个对象上变为活动状态。</p>
<h2 id="等待"><a class="header" href="#等待">等待</a></h2>
<p>线程可用于<a href="https://fuchsia.dev/docs/reference/syscalls/object_wait_one"><code>zx_object_wait_one()</code></a> 等待单个句柄上的信号处于活动状态或 <a href="https://fuchsia.dev/docs/reference/syscalls/object_wait_many"><code>zx_object_wait_many()</code></a>等待多个句柄上的信号。两个调用都允许超时,即使没有信号挂起,它们也会返回。</p>
<p>超时可能会偏离指定的截止时间,具体取决于计时器的余量。</p>
<p>如果线程要等待大量句柄使用端口Port会更有效它是一个对象其他对象可能会绑定到这样的对象当信号在它们上被断言时端口会收到一个包含信息的数据包关于未决信号。</p>
<h2 id="事件与事件对"><a class="header" href="#事件与事件对">事件与事件对</a></h2>
<p>事件Event是最简单的对象除了它的活动信号集合之外没有其他状态。</p>
<p>事件对Event Pair是可以相互发出信号的一对事件中的一个。事件对的一个有用属性是当一对的一侧消失时它的所有句柄都已关闭PEER_CLOSED 信号在另一侧被断言。</p>
<p>见:<a href="https://fuchsia.dev/docs/reference/syscalls/event_create"><code>zx_event_create()</code></a>, 和<a href="https://fuchsia.dev/docs/reference/syscalls/eventpair_create"><code>zx_eventpair_create()</code></a></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>
<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>
<p>Futexes 是内核原语与用户空间原子操作一起使用以实现高效的同步原语如Mutexes Condition Variables等它只需要在竞争情况contended case下才进行系统调用。通常它们实现在标准库中。</p>
<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>
<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="zcore-的用户态运行支持"><a class="header" href="#zcore-的用户态运行支持">zCore 的用户态运行支持</a></h1>
<p>libos 版 zCore简称uzCore 的开发与裸机版 zCore 简称bzCore同步进行,两个版本的 zCore 共用除了HAL 层之外的所有代码。为了支持 uzCore 的正常运行,zCore 在地址空间划分方面对 Zircon /Linux的原有设计进行了一定的修改,并为此对 Fuchsia 的源码进行了简单的修改、重新编译;另外,uzCore 需要的硬件相关层(HAL)将完全由宿主 OS 提供支持,一个合理的 HAL 层接口划分也是为支持 uzCore做出的重要考虑。</p>
<h2 id="hal-层接口设计"><a class="header" href="#hal-层接口设计">HAL 层接口设计</a></h2>
<p>HAL 层的设计是在bzCore 和 uzCore 的开发过程中逐渐演进形成的,在开发过程中将硬件实现相关的接口,比如页表、物理内存分配等进行封装,暴露给上层的内核对象层使用。在 kernel­-hal 模块中,给出空的弱链接实现,由 bzCore 或 uzCore 的开发者对相应的接口进行相应的实现,并用设定函数链接名称的方式,替换掉预设的弱链接的空函数。在整个开发过程中,不断对 HAL 层提出需求并实现,目前形成了第一版 HAL 层接口,在设计上能够满足现有的内核对象实现所需要的功能。</p>
<p>对内核对象层而言,所依赖的硬件环境不再是真实硬件环境中能够看到的物理内存、CPU、MMU 等,而是 HAL 暴露给上层的一整套接口。这一点从设计上来说,是 zCore 与 Zircon 存在差异的一点。Zircon 将 x86_64 、ARM64 的硬件架构进行底层封装,但是没有给出一套统一的硬件 API 供上层的内核对象直接使用,在部分内核对象的实现中,仍然需要通过宏等手段对代码进行条件编译,从而支持同时面向两套硬件架构进行开发。而在 zCore 的内核对象层实现中,可以完全不考虑底层硬件接口的实现,使一套内核对象的模块代码可以同时在 bzCore和 uzCore 上运行,之后如果 zCore 进一步支持 RISC-V 64 架构(已初步实现),只需要新增一套 HAL的实现,无需修改上层代码。下面将列出目前的uzCore的HAL层即kernel-hal-unix的接口。</p>
<h3 id="hal接口名称----功能描述"><a class="header" href="#hal接口名称----功能描述"><strong>HAL接口名称  功能描述</strong></a></h3>
<ul>
<li>线程相关
<ul>
<li>hal_thread_spawn Thread::spawn创建一个新线程并加入调度</li>
<li>hal_thread_set_tid Thread::set_tid  设定当前线程的 id</li>
<li>hal_thread_get_tid Thread::get_tid  获取当前线程的 id</li>
</ul>
</li>
<li>future
<ul>
<li>yield_now暂时让出 CPU回到async runtime中</li>
<li>sleep_until 休眠直到定时到达</li>
<li>YieldFuture 放弃执行的future</li>
<li>SleepFuture 睡眠且等待被唤醒的future</li>
<li>SerialFuture 通过serial_read获得字符的future</li>
</ul>
</li>
<li>上下文切换相关
<ul>
<li>VectorRegs  x86相关</li>
<li>hal_context_run context_run 进入“用户态”运行</li>
</ul>
</li>
<li>用户指针相关
<ul>
<li>UserPtr  对用户指针的操作:读/写/解引用/访问数组/访问字符串</li>
<li>IoVec 非连续buffer集合Vec结构读/写</li>
</ul>
</li>
<li>页表相关
<ul>
<li>hal_pt_currentPageTable::current  获取当前页表</li>
<li>hal_pt_newPageTable::new  新建一个页表</li>
<li>hal_pt_map PageTable::map  将一个物理页帧映射到一个虚拟地址中</li>
<li>hal_pt_unmap PageTable::unmap  解映射某个虚拟地址</li>
<li>hal_pt_protect PageTable::protect 修改vaddr对应的页表项的flags</li>
<li>hal_pt_query PageTable::query  查询某个虚拟地址对应的页表项状态</li>
<li>hal_pt_table_phys PageTable::table_phys  获取对应页表的根目录表物理地址</li>
<li>hal_pt_activate PageTable::activate 激活当前页表</li>
<li>PageTable::map_many  同时映射多个物理页帧到连续虚拟内存空间</li>
<li>PageTable::map_cont  同时映射连续的多个物理页帧到虚拟内存空间</li>
<li>hal_pt_unmap_cont PageTable::unmap_cont  解映射某个虚拟地址开始的一片范围</li>
<li>MMUFlags  页表项的属性位</li>
</ul>
</li>
<li>物理页帧相关
<ul>
<li>hal_frame_alloc PhysFrame::alloc  分配一个物理页帧</li>
<li>hal_frame_alloc_contiguous PhysFrame::alloc_contiguous_base 分配一块连续的物理内存</li>
<li>PhysFrame::addr 返回物理页帧对应的物理地址</li>
<li>PhysFrame::alloc_contiguous  分配一块连续的物理内存</li>
<li>PhysFrame::zero_frame_addr  返回零页的物理地址一个特殊页内容永远为全0</li>
<li>PhysFrame::drop  Drop trait 回收该物理页帧</li>
<li>hal_pmem_read pmem_read  读取某特定物理页帧的内容到缓冲区</li>
<li>hal_pmem_write pmem_write  将缓冲区中的内容写入某特定物理页帧</li>
<li>hal_frame_copy frame_copy  复制物理页帧的内容</li>
<li>hal_frame_zero frame_zero_in_range 物理页帧清零</li>
<li>hal_frame_flush frame_flush将物理页帧的数据从 Cache 刷回内存</li>
</ul>
</li>
<li>基本I/O外设
<ul>
<li>hal_serial_read serial_read 字符串输入</li>
<li>hal_serial_write serial_write 字符串输出</li>
<li>hal_timer_now timer_now 获取当前时间</li>
<li>hal_timer_set timer_set 设置一个时钟当到达deadline时会调用 callback 函数</li>
<li>hal_timer_set_next timer_set_next 设置下一个时钟</li>
<li>hal_timer_tick timer_tick当时钟中断产生时会调用的时钟函数触发所有已到时间的 callback</li>
</ul>
</li>
<li>中断处理
<ul>
<li>hal_irq_handle handle 中断处理例程</li>
<li>hal_ioapic_set_handle set_ioapic_handle x86相关对高级中断控制器设置处理例程</li>
<li>hal_irq_add_handle add_handle 对某中断添加中断处理例程</li>
<li>hal_ioapic_reset_handle reset_ioapic_handle 重置级中断控制器并设置处理例程</li>
<li>hal_irq_remove_handle remove_handle 移除某中断的中断处理例程</li>
<li>hal_irq_allocate_block allocate_block 给某中断分配连续区域</li>
<li>hal_irq_free_block free_block 给某中断释放连续区域</li>
<li>hal_irq_overwrite_handler overwrite_handler 覆盖某中断的中断处理例程</li>
<li>hal_irq_enable enable 使能某中断</li>
<li>hal_irq_disable disable 屏蔽某中断</li>
<li>hal_irq_maxinstr maxinstr x86相关获得IOAPIC的maxinstr???</li>
<li>hal_irq_configure configure 对某中断进行配置???</li>
<li>hal_irq_isvalid is_valid 查询某中断是否有效</li>
</ul>
</li>
<li>硬件平台相关
<ul>
<li>hal_vdso_constants vdso_constants 得到平台相关常量参数
<ul>
<li>struct VdsoConstants  平台相关常量</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>max_num_cpus features dcache_line_size ticks_per_second  ticks_to_mono_numerator ticks_to_mono_denominator physmem version_string_len  version_string</p>
<pre><code>* fetch_fault_vaddr fetch_fault_vaddr 取得出错的地址 ???好像缺了hal_*
* fetch_trap_num fetch_trap_num 取得中断号
* hal_pc_firmware_tables pc_firmware_tables x86相关取得`acpi_rsdp` 和 `smbios` 的物理地址
* hal_acpi_table get_acpi_table 得到acpi table
* hal_outpd outpd x86相关对IO Port进行写访问
* hal_inpd inpd x86相关对IO Port进行读访问
* hal_apic_local_id apic_local_id 得到本地(local) APIC ID
* fill_random 产生随机数并写入到buffer中
</code></pre>
<p>在上述“线程相关”的列表中,列出了 HAL 层的部分接口设计,覆盖线程调度方面。在线程调度方面,Thread 结构体相关的接口主要用于将一个线程加入调度等基本操作。在 zCore 的相关实现中,线程调度的各接口使用 naive­-executor 给出的接口以及 trapframe­ 给出的接口来进行实现,二者都是我们为裸机环境的协程调度与上下文切换所封装的 Rust 库。uzCore 中,线程调度的相关接口依赖于 Rust 的用户态协程支持以及 uzCore 开发者实现的用户态上下文切换。</p>
<p>在内存管理方面,HAL 层将内存管理分为页表操作与物理页帧管理两方面,并以此设计接口。在 zCore 实现中,物理页帧的分配与回收由于需要设计物理页帧分配器,且可分配范围大小与内核刚启动时的内存探测密切相关,我们将其直接在总控模块 zCore 中进行实现。而在 uzCore 中,页表对应操作依赖 mmap 进行模拟,物理页帧的相关操作则直接使用用户态物理内存分配器进行模拟。</p>
<p>在 Zircon 的设计中,内存的初始状态应该设置为全 0,为了在内核对象层满足该要求,我们为 HAL 层设计了零页接口,要求 HAL 层保留一个内容为全 0 的物理页帧,供上层使用。上层负责保证该零页内容不被修改。</p>
<h2 id="修改-vdso"><a class="header" href="#修改-vdso">修改 VDSO</a></h2>
<p>VDSO 是由内核提供、并只读映射到用户态的动态链接库,以函数接口形式提供系统调用接口。原始的 VDSO 中将会最终使用 syscall 指令从用户态进入内核态。但在 uzCore 环境下,内核和用户程序都运行在用户态,因此需要将 syscall 指令修改为函数调用,也就是将 sysall 指令修改为 call 指令。为此我们修改了 VDSO 汇编代码,将其中的 syscall 替换为 call提供给 uzCore 使用。在 uzCore 内核初始化环节中,向其中填入 call 指令要跳转的目标地址,重定向到内核中处理 syscall 的特定函数,从而实现模拟系统调用的效果。</p>
<h2 id="调整地址空间范围"><a class="header" href="#调整地址空间范围">调整地址空间范围</a></h2>
<p>在 uzCore 中,使用 mmap 来模拟页表,所有进程共用一个 64 位地址空间。因此,从地址空间范围这一角度来说,运行在 uzCore 上的用户程序所在的用户进程地址空间无法像 Zircon 要求的一样大。对于这一点,我们在为每一个用户进程设置地址空间时,手动进行分配,规定每一个用户进程地址空间的大小为 0x100_0000_0000,从 0x2_0000_0000 开始依次排布。0x0 开始至 0x2_0000_0000 规定为 uzCore 内核所在地址空间,不用于 mmap。图 3.3给出了 uzCore 在运行时若干个用户进程的地址空间分布。</p>
<p>与 uzCore 兼容,zCore 对于用户进程的地址空间划分也遵循同样的设计,但在裸机环境下,一定程度上摆脱了限制,能够将不同用户地址空间分隔在不同的页表中。如图 3.4所示,zCore 中将三个用户进程的地址空间在不同的页表中映射,但是为了兼容 uzCore 的运行,每一个用户进程地址空间中用户程序能够真正访问到的部分都仅有 0x100_0000_0000 大小。</p>
<h2 id="libos源代码分析记录"><a class="header" href="#libos源代码分析记录">LibOS源代码分析记录</a></h2>
<h3 id="zcore-on-riscv64的libos支持"><a class="header" href="#zcore-on-riscv64的libos支持">zCore on riscv64的LibOS支持</a></h3>
<ul>
<li>LibOS unix模式的入口在linux-loader main.rs:main()</li>
</ul>
<p>初始化包括kernel_hal_unixHost文件系统其中载入elf应用程序的过程与zcore bare模式一样</p>
<p>重点工作应该在kernel_hal_unix中的<strong>内核态与用户态互相切换</strong>的处理。</p>
<p>kernel_hal_unix初始化主要包括了构建Segmentation Fault时SIGSEGV信号的处理函数当代码尝试使用fs寄存器时会触发信号</p>
<ul>
<li>为什么要注册这个信号处理函数呢?</li>
</ul>
<p>根据wrj的说明由于 macOS 用户程序无法修改 fs 寄存器当运行相关指令时会访问非法内存地址触发Segmentation Fault。故实现段错误信号处理函数并在其中动态修改用户程序指令将 fs 改为 gs</p>
<p>kernel_hal_unix还构造了<strong>进入用户态</strong>所需的run_fncall() -&gt; syscall_fn_return()</p>
<p>而用户程序需要调用syscall_fn_entry()来<strong>返回内核态</strong></p>
<p>Linux-x86_64平台运行时用户态和内核态之间的切换运用了 fs base 寄存器;</p>
<ul>
<li>Linux 和 macOS 下如何分别通过系统调用设置 fsbase / gsbase 。</li>
</ul>
<p>这个转换过程调用到了trapframe库x86_64和aarch64有对应实现而riscv则需要自己手动实现</p>
<ul>
<li>关于fs寄存器</li>
</ul>
<p>查找了下fs寄存器一般会用于寻址TLS每个线程有它自己的fs base地址</p>
<p>fs寄存器被glibc定义为存放tls信息结构体tcbhead_t就是用来描述tls</p>
<p>进入用户态前,将内核栈指针保存在内核 glibc 的 TLS 区域中。</p>
<p>可参考一个运行时程序的代码转换工具:<a href="https://github.com/DynamoRIO/dynamorio/issues/1568#issuecomment-239819506?fileGuid=VMAPV7ERl7HbpNqg">https://github.com/DynamoRIO/dynamorio/issues/1568#issuecomment-239819506</a></p>
<ul>
<li><strong>LibOS内核态与用户态的切换</strong></li>
</ul>
<p>Linux x86_64中fs寄存器是用户态程序无法设置的只能通过系统调用进行设置</p>
<p>例如clone系统调用通过arch_prctl来设置fs寄存器指向的struct pthreadglibc中其中的首个结构是tcbhead_t</p>
<p>计算tls结构体偏移</p>
<p>经过试验x86_64平台int型4节指针类型8节无符号长整型8节</p>
<p>riscv64平台int型 4节指针类型8节无符号长整型8节</p>
<p>计算tls偏移量时注意下在musl中aarch64和riscv64架构有#define TLS_ABOVE_TP而x86_64无此定义</p>
<ul>
<li>关于Linux user mode (UML)</li>
</ul>
<p>&quot;No, UML works only on x86 and x86_64.&quot;</p>
<p><a href="https://sourceforge.net/p/user-mode-linux/mailman/message/32782012/?fileGuid=VMAPV7ERl7HbpNqg">https://sourceforge.net/p/user-mode-linux/mailman/message/32782012/</a></p>
</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>