|
|
<!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,然后再逐步替换底层实现,
|
|
|
"移植"回裸机环境中运行。因此我们更关注系统的整体设计,从高层视角看待 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>第五层,是硬件抽象层(HAL,Hardware 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>其中,VDSO(Virtual 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(&self) -> KoID;
|
|
|
/// 获取对象类型名
|
|
|
fn type_name(&self) -> &str;
|
|
|
/// 获取对象名称
|
|
|
fn name(&self) -> String;
|
|
|
/// 设置对象名称
|
|
|
fn set_name(&self, name: &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<DummyObjectInner>,
|
|
|
}
|
|
|
|
|
|
/// `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 = "0.7"
|
|
|
</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() -> Arc<Self> {
|
|
|
Arc::new(DummyObject {
|
|
|
id: Self::new_koid(),
|
|
|
inner: Default::default(),
|
|
|
})
|
|
|
}
|
|
|
|
|
|
/// 生成一个唯一的 ID
|
|
|
fn new_koid() -> 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<Self></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(&self) -> KoID {
|
|
|
self.id
|
|
|
}
|
|
|
fn type_name(&self) -> &str {
|
|
|
"DummyObject"
|
|
|
}
|
|
|
fn name(&self) -> String {
|
|
|
self.inner.lock().name.clone()
|
|
|
}
|
|
|
fn set_name(&self, name: &str) {
|
|
|
self.inner.lock().name = String::from(name);
|
|
|
}
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p>到此为止,我们已经迈出了万里长征第一步,实现了一个最简单的功能。有实现,就要有测试!即使最简单的代码也要保证它的行为符合我们预期。
|
|
|
只有对现有代码进行充分测试,在未来做添加和修改的时候,我们才有信心不会把事情搞砸。俗话讲"万丈高楼平地起",把地基打好才能盖摩天大楼。</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(), "DummyObject");
|
|
|
assert_eq!(o1.name(), "");
|
|
|
o1.set_name("object1");
|
|
|
assert_eq!(o1.name(), "object1");
|
|
|
}
|
|
|
}
|
|
|
</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<dyn KernelObject></code> 转换成具体类型的结构 <code>Arc<T> 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<DummyObject*>(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<T: KernelObject>(object: Arc<dyn KernelObject>) -> Arc<T> {
|
|
|
object.downcast::<T>().unwrap()
|
|
|
}
|
|
|
fn downcast_v2<T: KernelObject>(object: Arc<dyn KernelObject>) -> Arc<T> {
|
|
|
let object: Arc<dyn Any + Send + Sync + 'static> = object;
|
|
|
object.downcast::<T>().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 = "1.2.0", 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<dyn KernelObject> = dummy;
|
|
|
let _result: Arc<DummyObject> = object.downcast_arc::<DummyObject>().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<KObjectBaseInner>,
|
|
|
}
|
|
|
|
|
|
/// `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() -> Self {
|
|
|
KObjectBase {
|
|
|
id: Self::new_koid(),
|
|
|
inner: Default::default(),
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
impl KObjectBase {
|
|
|
/// 生成一个唯一的 ID
|
|
|
fn new_koid() -> KoID {...}
|
|
|
/// 获取对象名称
|
|
|
pub fn name(&self) -> String {...}
|
|
|
/// 设置对象名称
|
|
|
pub fn set_name(&self, name: &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 )*) => {
|
|
|
// 为对象实现 KernelObject trait,方法直接转发到内部 struct
|
|
|
impl KernelObject for $class {
|
|
|
fn id(&self) -> KoID {
|
|
|
// 直接访问内部的 pub 属性
|
|
|
self.base.id
|
|
|
}
|
|
|
fn type_name(&self) -> &str {
|
|
|
// 用 stringify! 宏将输入转成字符串
|
|
|
stringify!($class)
|
|
|
}
|
|
|
// 注意宏里面的类型要写完整路径,例如:alloc::string::String
|
|
|
fn name(&self) -> alloc::string::String {
|
|
|
self.base.name()
|
|
|
}
|
|
|
fn set_name(&self, name: &str){
|
|
|
// 直接访问内部的 pub 方法
|
|
|
self.base.set_name(name)
|
|
|
}
|
|
|
// 可以传入任意数量的函数,覆盖 trait 的默认实现
|
|
|
$( $fn )*
|
|
|
}
|
|
|
// 为对象实现 Debug trait
|
|
|
impl core::fmt::Debug for $class {
|
|
|
fn fmt(
|
|
|
&self,
|
|
|
f: &mut core::fmt::Formatter<'_>,
|
|
|
) -> core::result::Result<(), core::fmt::Error> {
|
|
|
// 输出对象类型、ID 和名称
|
|
|
f.debug_tuple(&stringify!($class))
|
|
|
.field(&self.id())
|
|
|
.field(&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() -> Arc<Self> {
|
|
|
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<dyn KernelObject> = dummy;
|
|
|
assert_eq!(object.type_name(), "DummyObject");
|
|
|
assert_eq!(object.name(), "");
|
|
|
object.set_name("dummy");
|
|
|
assert_eq!(object.name(), "dummy");
|
|
|
assert_eq!(
|
|
|
format!("{:?}", object),
|
|
|
format!("DummyObject({}, \"dummy\")", object.id())
|
|
|
);
|
|
|
let _result: Arc<DummyObject> = object.downcast_arc::<DummyObject>().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>内核对象的“<a href="https://fuchsia.dev/docs/concepts/kernel/rights">权限</a>”指定允许对内核对象进行哪些操作。权限与句柄相关联,并传达对关联句柄或与句柄关联的对象执行操作的特权。单个进程可能对具有不同权限的同一个内核对象有两个不同的句柄。</p>
|
|
|
<h2 id="句柄"><a class="header" href="#句柄">句柄</a></h2>
|
|
|
<p>句柄是允许用户程序引用内核对象引用的一种内核结构,它可以被认为是与特定内核对象的会话或连接。</p>
|
|
|
<p>通常情况下,多个进程通过不同的句柄同时访问同一个对象。对象可能有多个句柄(在一个或多个进程中)引用它们。但单个句柄只能绑定到单个进程或绑定到内核。</p>
|
|
|
<p>当句柄绑定到内核时,我们说它是“在传输中”('in-transit')。</p>
|
|
|
<p>在用户模式下,句柄只是某个系统调用返回的特定数字。只有“不在传输中”的句柄对用户模式可见。</p>
|
|
|
<p>代表句柄的整数只对其所属的那个进程有意义。另一个进程中的相同数字可能不会映射到任何句柄,或者它可能映射到指向完全不同的内核对象的句柄。</p>
|
|
|
<p>句柄的整数值是任何 32 位数字,但对应于<strong>ZX_HANDLE_INVALID</strong>的值将始终为 0。除此之外,有效句柄的整数值将始终具有句柄集的两个最低有效位. 可以使用<strong>ZX_HANDLE_FIXED_BITS_MASK</strong>访问代表这些位的掩码。</p>
|
|
|
<p>句柄可以从一个进程移动到另一个进程,方法是将它们写入通道(使用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_write"><code>channel_write()</code></a>),或者使用 <a href="https://fuchsia.dev/docs/reference/syscalls/process_start"><code>process_start()</code></a>传递一个句柄作为新进程中第一个线程的参数。对于几乎所有的对象,当最后一个打开的引用对象的句柄关闭时,对象要么被销毁,要么被置于可能无法撤消的最终状态。</p>
|
|
|
<p>在 <code>Cargo.toml</code> 中加入 <code>bitflags</code> 库:</p>
|
|
|
<pre><code class="language-rust noplaypen">[dependencies]
|
|
|
bitflags = "1.2"
|
|
|
</code></pre>
|
|
|
<p>在 object 模块下定义两个子模块:</p>
|
|
|
<pre><code class="language-rust noplaypen">// src/object/mod.rs
|
|
|
mod handle;
|
|
|
mod rights;
|
|
|
|
|
|
pub use self::handle::*;
|
|
|
pub use self::rights::*;
|
|
|
</code></pre>
|
|
|
<p>定义权限:</p>
|
|
|
<pre><code class="language-rust noplaypen">// src/object/rights.rs
|
|
|
use bitflags::bitflags;
|
|
|
|
|
|
bitflags! {
|
|
|
/// 句柄权限
|
|
|
pub struct Rights: u32 {
|
|
|
const DUPLICATE = 1 << 0;
|
|
|
const TRANSFER = 1 << 1;
|
|
|
const READ = 1 << 2;
|
|
|
const WRITE = 1 << 3;
|
|
|
const EXECUTE = 1 << 4;
|
|
|
const MAP = 1 << 5;
|
|
|
const GET_PROPERTY = 1 << 6;
|
|
|
const SET_PROPERTY = 1 << 7;
|
|
|
const ENUMERATE = 1 << 8;
|
|
|
const DESTROY = 1 << 9;
|
|
|
const SET_POLICY = 1 << 10;
|
|
|
const GET_POLICY = 1 << 11;
|
|
|
const SIGNAL = 1 << 12;
|
|
|
const SIGNAL_PEER = 1 << 13;
|
|
|
const WAIT = 1 << 14;
|
|
|
const INSPECT = 1 << 15;
|
|
|
const MANAGE_JOB = 1 << 16;
|
|
|
const MANAGE_PROCESS = 1 << 17;
|
|
|
const MANAGE_THREAD = 1 << 18;
|
|
|
const APPLY_PROFILE = 1 << 19;
|
|
|
const SAME_RIGHTS = 1 << 31;
|
|
|
|
|
|
const BASIC = Self::TRANSFER.bits | Self::DUPLICATE.bits | Self::WAIT.bits | Self::INSPECT.bits;
|
|
|
const IO = Self::READ.bits | Self::WRITE.bits;
|
|
|
|
|
|
const DEFAULT_CHANNEL = Self::BASIC.bits & !Self::DUPLICATE.bits | Self::IO.bits | Self::SIGNAL.bits | Self::SIGNAL_PEER.bits;
|
|
|
/// GET_PROPERTY | SET_PROPERTY
|
|
|
const PROPERTY = Self::GET_PROPERTY.bits | Self::SET_PROPERTY.bits;
|
|
|
/// BASIC | IO | PROPERTY | ENUMERATE | DESTROY | SIGNAL | MANAGE_PROCESS | MANAGE_THREAD
|
|
|
const DEFAULT_PROCESS = Self::BASIC.bits | Self::IO.bits | Self::PROPERTY.bits | Self::ENUMERATE.bits | Self::DESTROY.bits | Self::SIGNAL.bits | Self::MANAGE_PROCESS.bits | Self::MANAGE_THREAD.bits;
|
|
|
}
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p>定义句柄:</p>
|
|
|
<pre><code class="language-rust noplaypen">// src/object/handle.rs
|
|
|
use super::{KernelObject, Rights};
|
|
|
use alloc::sync::Arc;
|
|
|
|
|
|
/// 内核对象句柄
|
|
|
#[derive(Clone)]
|
|
|
pub struct Handle {
|
|
|
pub object: Arc<dyn KernelObject>,
|
|
|
pub rights: Rights,
|
|
|
}
|
|
|
|
|
|
impl Handle {
|
|
|
/// 创建一个新句柄
|
|
|
pub fn new(object: Arc<dyn KernelObject>, rights: Rights) -> Self {
|
|
|
Handle { object, rights }
|
|
|
}
|
|
|
}
|
|
|
</code></pre>
|
|
|
<h2 id="存储内核对象句柄"><a class="header" href="#存储内核对象句柄">存储内核对象句柄</a></h2>
|
|
|
<blockquote>
|
|
|
<p>添加成员变量 handles: BTreeMap<HandleValue, Handle></p>
|
|
|
<p>实现 create,add_handle,remove_handle 函数</p>
|
|
|
</blockquote>
|
|
|
<p>使用上一节的方法,实现一个空的 Process 对象:</p>
|
|
|
<pre><code class="language-rust noplaypen">// src/task/process.rs
|
|
|
/// 进程对象
|
|
|
pub struct Process {
|
|
|
base: KObjectBase,
|
|
|
inner: Mutex<ProcessInner>,
|
|
|
}
|
|
|
|
|
|
impl_kobject!(Process);
|
|
|
|
|
|
struct ProcessInner {
|
|
|
handles: BTreeMap<HandleValue, Handle>,
|
|
|
}
|
|
|
|
|
|
pub type HandleValue = u32;
|
|
|
|
|
|
impl Process {
|
|
|
/// 创建一个新的进程对象
|
|
|
pub fn new() -> Arc<Self> {
|
|
|
Arc::new(Process {
|
|
|
base: KObjectBase::default(),
|
|
|
inner: Mutex::new(ProcessInner {
|
|
|
handles: BTreeMap::default(),
|
|
|
}),
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
</code></pre>
|
|
|
<p>插入、删除句柄函数:</p>
|
|
|
<pre><code class="language-rust noplaypen">// src/task/process.rs
|
|
|
impl Process {
|
|
|
/// 添加一个新的对象句柄
|
|
|
pub fn add_handle(&self, handle: Handle) -> HandleValue {
|
|
|
let mut inner = self.inner.lock();
|
|
|
let value = (0 as HandleValue..)
|
|
|
.find(|idx| !inner.handles.contains_key(idx))
|
|
|
.unwrap();
|
|
|
inner.handles.insert(value, handle);
|
|
|
value
|
|
|
}
|
|
|
|
|
|
/// 删除一个对象句柄
|
|
|
pub fn remove_handle(&self, handle_value: HandleValue) {
|
|
|
self.inner.lock().handles.remove(&handle_value);
|
|
|
}
|
|
|
}
|
|
|
</code></pre>
|
|
|
<h2 id="定义内核错误及-result-类型"><a class="header" href="#定义内核错误及-result-类型">定义内核错误及 <code>Result</code> 类型</a></h2>
|
|
|
<pre><code class="language-rust noplaypen">// src/error.rs
|
|
|
/// Zircon statuses are signed 32 bit integers. The space of values is
|
|
|
/// divided as follows:
|
|
|
/// - The zero value is for the OK status.
|
|
|
/// - Negative values are defined by the system, in this file.
|
|
|
/// - Positive values are reserved for protocol-specific error values,
|
|
|
/// and will never be defined by the system.
|
|
|
#[allow(non_camel_case_types, dead_code)]
|
|
|
#[repr(i32)]
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
pub enum ZxError {
|
|
|
OK = 0,
|
|
|
|
|
|
// ======= Internal failures =======
|
|
|
/// The system encountered an otherwise unspecified error
|
|
|
/// while performing the operation.
|
|
|
INTERNAL = -1,
|
|
|
|
|
|
/// The operation is not implemented, supported,
|
|
|
/// or enabled.
|
|
|
NOT_SUPPORTED = -2,
|
|
|
|
|
|
// ......
|
|
|
|
|
|
/// Connection was aborted.
|
|
|
CONNECTION_ABORTED = -76,
|
|
|
}
|
|
|
</code></pre>
|
|
|
<pre><code class="language-rust noplaypen">// src/error.rs
|
|
|
///
|
|
|
pub type ZxResult<T> = Result<T, ZxError>;
|
|
|
</code></pre>
|
|
|
<h2 id="根据句柄查找内核对象"><a class="header" href="#根据句柄查找内核对象">根据句柄查找内核对象</a></h2>
|
|
|
<blockquote>
|
|
|
<p>实现 get_object_with_rights 等其它相关函数</p>
|
|
|
<p>实现 handle 单元测试</p>
|
|
|
</blockquote>
|
|
|
<pre><code class="language-rust noplaypen">// src/task/process.rs
|
|
|
impl Process {
|
|
|
/// 根据句柄值查找内核对象,并检查权限
|
|
|
pub fn get_object_with_rights<T: KernelObject>(
|
|
|
&self,
|
|
|
handle_value: HandleValue,
|
|
|
desired_rights: Rights,
|
|
|
) -> ZxResult<Arc<T>> {
|
|
|
let handle = self
|
|
|
.inner
|
|
|
.lock()
|
|
|
.handles
|
|
|
.get(&handle_value)
|
|
|
.ok_or(ZxError::BAD_HANDLE)?
|
|
|
.clone();
|
|
|
// check type before rights
|
|
|
let object = handle
|
|
|
.object
|
|
|
.downcast_arc::<T>()
|
|
|
.map_err(|_| ZxError::WRONG_TYPE)?;
|
|
|
if !handle.rights.contains(desired_rights) {
|
|
|
return Err(ZxError::ACCESS_DENIED);
|
|
|
}
|
|
|
Ok(object)
|
|
|
}
|
|
|
}
|
|
|
</code></pre>
|
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="对象传送器channel-对象"><a class="header" href="#对象传送器channel-对象">对象传送器:Channel 对象</a></h1>
|
|
|
<h2 id="概要"><a class="header" href="#概要">概要</a></h2>
|
|
|
<p>通道(Channel)是由一定数量的字节数据和一定数量的句柄组成的双向消息传输。</p>
|
|
|
<h2 id="描述"><a class="header" href="#描述">描述</a></h2>
|
|
|
<p>通道有两个端点(endpoints)。从逻辑上讲,每个端点都维护要读取的有序消息队列。写入一个端点会将消息排入另一个端点的队列中。当端点的最后一个句柄关闭时,该端点队列中的未读消息将被销毁。因为销毁消息会关闭消息包含的所有句柄,关闭通道端点可能会产生递归效果(例如,通道包含一条消息,它包含一个通道,它包含一条消息,等等)。</p>
|
|
|
<p>关闭通道的最后一个句柄对先前写入该通道的消息的生命周期没有影响。这为通道提供了“即发即忘”的语义。</p>
|
|
|
<p>一条消息由一定数量的数据和一定数量的句柄组成。调用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_write"><code>channel_write()</code></a>使一条消息入队,调用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_read"><code>channel_read()</code></a> 使一条消息出列(如果有队列)。线程可以阻塞,直到消息通过<a href="https://fuchsia.dev/docs/reference/syscalls/object_wait_one"><code>object_wait_one()</code></a>或其他等待机制挂起。</p>
|
|
|
<p>或者,调用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_call"><code>channel_call()</code></a>在通道的一个方向上将消息入队,等待相应的响应,然后将响应消息出队。在调用模式(call mode)下,相应的响应通过消息的前 4 个字节标识,称为事务 ID(transaction ID)。内核使用<a href="https://fuchsia.dev/docs/reference/syscalls/channel_call"><code>channel_call()</code></a>,为消息提供唯一的事务 ID.</p>
|
|
|
<p>通过通道发送消息的过程有两个步骤。第一步是原子地将数据写入通道并将消息中所有句柄的所有权移到此通道中。此操作始终消耗句柄:在调用结束时,所有句柄要么全部在通道中,要么全部丢弃。第二步操作,通道读取(channel read),与第一步类似:成功后,下一条消息中的所有句柄都被原子地移动到接收进程的句柄表中。失败时,通道将保留所有权,然后它们将被删除。</p>
|
|
|
<p>与许多其他内核对象类型不同,通道是不可复制的。因此,只有一个句柄与通道端点相关联,持有该句柄的进程被视为所有者(owner)。只有所有者可以读取或写入消息或将通道端点发送到另一个进程。</p>
|
|
|
<p>当通道端点的所有权从一个进程转移到另一个进程时,即使消息正在进行写入,消息也不会被重新排序或截断。转移事件之前的消息属于以前的所有者,转移之后的消息属于新的所有者。如果在传输端点时,正在进行消息读取,则之前描述的所有权转移方式同样适用。</p>
|
|
|
<p>即使最后剩余的句柄被剥夺了<strong>DUPLICATE</strong>权限,也不为其他内核对象提供上述顺序保证。</p>
|
|
|
<h2 id="创建一对内核对象"><a class="header" href="#创建一对内核对象">创建一对内核对象</a></h2>
|
|
|
<h2 id="创建通道"><a class="header" href="#创建通道">创建通道</a></h2>
|
|
|
<p>创建 Channel 将返回两个句柄,一个指向对象的每个端点。</p>
|
|
|
<blockquote>
|
|
|
<p>实现 Channel::create</p>
|
|
|
<p>讲一下互相持有对方 Weak 指针的目的,这里有不可避免的 unsafe</p>
|
|
|
</blockquote>
|
|
|
<h2 id="实现数据传输"><a class="header" href="#实现数据传输">实现数据传输</a></h2>
|
|
|
<p>当句柄被写入通道时,它们会从发送进程中删除。当从通道读取带有句柄的消息时,句柄被添加到接收进程中。在这两个事件之间,句柄继续存在(确保它们所指的对象继续存在),除非它们写入的通道的末端关闭——此时发送到该端点的消息被丢弃并且它们包含的任何句柄都已关闭。</p>
|
|
|
<blockquote>
|
|
|
<p>实现 read, write 函数,read_write 单元测试</p>
|
|
|
</blockquote>
|
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="任务管理"><a class="header" href="#任务管理">任务管理</a></h1>
|
|
|
<p>本章我们来实现第一类内核对象:任务管理(Tasks)。</p>
|
|
|
<p>任务对象主要包括:线程 <code>Thread</code>,进程 <code>Process</code>,作业 <code>Job</code>。以及一些辅助性的对象,例如负责暂停任务执行的 <code>SuspendToken</code> 和负责处理异常的 <code>Exception</code>。</p>
|
|
|
<p>为了能够真实表现线程对象的行为,我们使用 Rust async 运行时 <a href="https://docs.rs/async-std/1.6.2/async_std/index.html"><code>async_std</code></a> 中的<strong>用户态协程</strong>来模拟<strong>内核线程</strong>。
|
|
|
这样就可以在用户态的单元测试中检验实现的正确性。
|
|
|
考虑到未来这个 OS 会跑在裸机环境中,将会有不同的内核线程的实现,我们创建一个特殊的<strong>硬件抽象层(Hardware Abstraction Layer,HAL)</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="描述-1"><a class="header" href="#描述-1">描述</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_VMO,NEW_CHANNEL,NEW_EVENT,NEW_EVENTPAIR,NEW_PORT,NEW_SOCKET,NEW_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>状态转移:创建 -> 运行 -> 暂停 -> 退出,最好有个状态机的图</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_state,write_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 中有两个跟内存管理有关的对象 VMO(Virtual 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<dyn VMObjectTrait>,
|
|
|
inner: Mutex<VmObjectInner>,
|
|
|
}
|
|
|
|
|
|
impl_kobject!(VmObject);
|
|
|
|
|
|
#[derive(Default)]
|
|
|
struct VmObjectInner {
|
|
|
parent: Weak<VmObject>,
|
|
|
children: Vec<Weak<VmObject>>,
|
|
|
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(&self, offset: usize, buf: &mut [u8]) -> ZxResult;
|
|
|
|
|
|
/// Write memory from `buf` to VMO at `offset`.
|
|
|
fn write(&self, offset: usize, buf: &[u8]) -> ZxResult;
|
|
|
|
|
|
/// Resets the range of bytes in the VMO from `offset` to `offset+len` to 0.
|
|
|
fn zero(&self, offset: usize, len: usize) -> ZxResult;
|
|
|
|
|
|
/// Get the length of VMO.
|
|
|
fn len(&self) -> usize;
|
|
|
|
|
|
/// Set the length of VMO.
|
|
|
fn set_len(&self, len: usize) -> ZxResult;
|
|
|
|
|
|
/// Commit a page.
|
|
|
fn commit_page(&self, page_idx: usize, flags: MMUFlags) -> ZxResult<PhysAddr>;
|
|
|
|
|
|
/// 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(
|
|
|
&self,
|
|
|
f: &mut dyn FnMut(&mut dyn FnMut(usize, MMUFlags) -> ZxResult<PhysAddr>) -> ZxResult,
|
|
|
) -> ZxResult;
|
|
|
|
|
|
/// Commit allocating physical memory.
|
|
|
fn commit(&self, offset: usize, len: usize) -> ZxResult;
|
|
|
|
|
|
/// Decommit allocated physical memory.
|
|
|
fn decommit(&self, offset: usize, len: usize) -> ZxResult;
|
|
|
|
|
|
/// Create a child VMO.
|
|
|
fn create_child(&self, offset: usize, len: usize) -> ZxResult<Arc<dyn VMObjectTrait>>;
|
|
|
|
|
|
/// Append a mapping to the VMO's mapping list.
|
|
|
fn append_mapping(&self, _mapping: Weak<VmMapping>) {}
|
|
|
|
|
|
/// Remove a mapping from the VMO's mapping list.
|
|
|
fn remove_mapping(&self, _mapping: Weak<VmMapping>) {}
|
|
|
|
|
|
/// Complete the VmoInfo.
|
|
|
fn complete_info(&self, info: &mut VmoInfo);
|
|
|
|
|
|
/// Get the cache policy.
|
|
|
fn cache_policy(&self) -> CachePolicy;
|
|
|
|
|
|
/// Set the cache policy.
|
|
|
fn set_cache_policy(&self, policy: CachePolicy) -> ZxResult;
|
|
|
|
|
|
/// Count committed pages of the VMO.
|
|
|
fn committed_pages_in_range(&self, start_idx: usize, end_idx: usize) -> usize;
|
|
|
|
|
|
/// Pin the given range of the VMO.
|
|
|
fn pin(&self, _offset: usize, _len: usize) -> ZxResult {
|
|
|
Err(ZxError::NOT_SUPPORTED)
|
|
|
}
|
|
|
|
|
|
/// Unpin the given range of the VMO.
|
|
|
fn unpin(&self, _offset: usize, _len: usize) -> ZxResult {
|
|
|
Err(ZxError::NOT_SUPPORTED)
|
|
|
}
|
|
|
|
|
|
/// Returns true if the object is backed by a contiguous range of physical memory.
|
|
|
fn is_contiguous(&self) -> bool {
|
|
|
false
|
|
|
}
|
|
|
|
|
|
/// Returns true if the object is backed by RAM.
|
|
|
fn is_paged(&self) -> bool {
|
|
|
false
|
|
|
}
|
|
|
}
|
|
|
<span class="boring">}
|
|
|
</span></code></pre></pre>
|
|
|
<p><code>read()</code> 和 <code>write()</code> 用于读和写,<code>zero()</code> 用于清空一段内存。<br />
|
|
|
比较特别的是:<code>fn commit_page(&self, page_idx: usize, flags: MMUFlags) -> ZxResult<PhysAddr>;</code>,<code>fn commit(&self, offset: usize, len: usize) -> ZxResult;</code> 和 <code>fn commit(&self, offset: usize, len: usize) -> 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) -> Arc<Self> {
|
|
|
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) -> Arc<Self> {
|
|
|
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) -> Arc<Self> {
|
|
|
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) -> ZxResult<Arc<Self>> {
|
|
|
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: &Arc<Self>, 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: &Arc<Self>,
|
|
|
resizable: bool,
|
|
|
offset: usize,
|
|
|
len: usize,
|
|
|
) -> ZxResult<Arc<Self>> {
|
|
|
// Create child VmObject
|
|
|
let base = KObjectBase::with_name(&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(&child);
|
|
|
Ok(child)
|
|
|
}
|
|
|
|
|
|
/// Add child to the list
|
|
|
fn add_child(&self, child: &Arc<VmObject>) {
|
|
|
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() -> File {
|
|
|
let dir = tempdir().expect("failed to create pmem dir");
|
|
|
let path = dir.path().join("pmem");
|
|
|
|
|
|
// 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 = "macos")]
|
|
|
std::mem::forget(dir);
|
|
|
|
|
|
let file = OpenOptions::new()
|
|
|
.read(true)
|
|
|
.write(true)
|
|
|
.create(true)
|
|
|
.open(&path)
|
|
|
.expect("failed to create pmem file");
|
|
|
file.set_len(PMEM_SIZE as u64)
|
|
|
.expect("failed to resize file");
|
|
|
trace!("create pmem file: path={:?}, size={:#x}", 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 = "macos")]
|
|
|
let prot = if prot & 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!(
|
|
|
"mmap file: fd={}, offset={:#x}, len={:#x}, vaddr={:#x}, prot={:#b}",
|
|
|
fd,
|
|
|
offset,
|
|
|
len,
|
|
|
vaddr,
|
|
|
prot,
|
|
|
);
|
|
|
assert_eq!(ret, vaddr, "failed to mmap: {:?}", 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 = "hal_pmem_read"]
|
|
|
pub fn pmem_read(paddr: PhysAddr, buf: &mut [u8]) {
|
|
|
trace!("pmem read: paddr={:#x}, len={:#x}", paddr, buf.len());
|
|
|
assert!(paddr + buf.len() <= 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 = "hal_pmem_write"]
|
|
|
pub fn pmem_write(paddr: PhysAddr, buf: &[u8]) {
|
|
|
trace!("pmem write: paddr={:#x}, len={:#x}", paddr, buf.len());
|
|
|
assert!(paddr + buf.len() <= 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<()>,
|
|
|
inner: Mutex<VMObjectPhysicalInner>,
|
|
|
}
|
|
|
|
|
|
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(&self, offset: usize, buf: &mut [u8]) -> ZxResult {
|
|
|
let _ = self.data_lock.lock(); // 先获取锁
|
|
|
assert!(offset + buf.len() <= 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<dyn VMObjectTrait>,
|
|
|
/// The offset from parent.
|
|
|
offset: usize,
|
|
|
/// The size in bytes.
|
|
|
size: usize,
|
|
|
}
|
|
|
|
|
|
impl VMObjectSlice {
|
|
|
pub fn new(parent: Arc<dyn VMObjectTrait>, offset: usize, size: usize) -> Arc<Self> {
|
|
|
Arc::new(VMObjectSlice {
|
|
|
parent,
|
|
|
offset,
|
|
|
size,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
fn check_range(&self, offset: usize, len: usize) -> ZxResult {
|
|
|
if offset + len >= 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(&self, offset: usize, buf: &mut [u8]) -> 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 = "weak"]
|
|
|
#[export_name = "hal_frame_alloc"]
|
|
|
pub fn alloc() -> Option<Self> {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_frame_alloc_contiguous"]
|
|
|
pub fn alloc_contiguous_base(_size: usize, _align_log2: usize) -> Option<PhysAddr> {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
|
|
|
pub fn alloc_contiguous(size: usize, align_log2: usize) -> Vec<Self> {
|
|
|
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() -> Option<Self> {
|
|
|
Self::alloc().map(|f| {
|
|
|
pmem_zero(f.addr(), PAGE_SIZE);
|
|
|
f
|
|
|
})
|
|
|
}
|
|
|
|
|
|
pub fn alloc_contiguous_zeroed(size: usize, align_log2: usize) -> Vec<Self> {
|
|
|
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(&self) -> PhysAddr {
|
|
|
self.paddr
|
|
|
}
|
|
|
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_zero_frame_paddr"]
|
|
|
pub fn zero_frame_addr() -> PhysAddr {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Drop for PhysFrame {
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_frame_dealloc"]
|
|
|
fn drop(&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<VecDeque<usize>> =
|
|
|
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 = "hal_frame_alloc"]
|
|
|
pub fn alloc() -> Option<Self> {
|
|
|
let ret = AVAILABLE_FRAMES
|
|
|
.lock()
|
|
|
.unwrap()
|
|
|
.pop_front()
|
|
|
.map(|paddr| PhysFrame { paddr });
|
|
|
trace!("frame alloc: {:?}", ret);
|
|
|
ret
|
|
|
}
|
|
|
#[export_name = "hal_zero_frame_paddr"]
|
|
|
pub fn zero_frame_addr() -> PhysAddr {
|
|
|
0
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Drop for PhysFrame {
|
|
|
#[export_name = "hal_frame_dealloc"]
|
|
|
fn drop(&mut self) {
|
|
|
trace!("frame dealloc: {:?}", 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(&self) -> usize {
|
|
|
self.end - self.begin
|
|
|
}
|
|
|
pub fn is_full(&self) -> bool {
|
|
|
self.len() == (1usize << self.block_size_log2)
|
|
|
}
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
self.len() == 0
|
|
|
}
|
|
|
pub fn origin_begin(&self) -> usize {
|
|
|
(self.block << self.block_size_log2) + self.begin
|
|
|
}
|
|
|
pub fn origin_end(&self) -> usize {
|
|
|
(self.block << self.block_size_log2) + self.end
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl Iterator for BlockIter {
|
|
|
type Item = BlockRange;
|
|
|
|
|
|
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
|
|
if self.begin >= self.end {
|
|
|
return None;
|
|
|
}
|
|
|
let block_size_log2 = self.block_size_log2;
|
|
|
let block_size = 1usize << 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<VMObjectPagedInner>,
|
|
|
}
|
|
|
|
|
|
/// The mutable part of `VMObjectPaged`.
|
|
|
#[derive(Default)]
|
|
|
struct VMObjectPagedInner {
|
|
|
/// Physical frames of this VMO.
|
|
|
frames: Vec<PhysFrame>,
|
|
|
/// Cache Policy
|
|
|
cache_policy: CachePolicy,
|
|
|
/// Is contiguous
|
|
|
contiguous: bool,
|
|
|
/// Sum of pin_count
|
|
|
pin_count: usize,
|
|
|
/// All mappings to this VMO.
|
|
|
mappings: Vec<Weak<VmMapping>>,
|
|
|
}
|
|
|
<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) -> Arc<Self> {
|
|
|
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) -> ZxResult<Arc<Self>> {
|
|
|
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(
|
|
|
&mut self,
|
|
|
offset: usize,
|
|
|
buf_len: usize,
|
|
|
mut f: impl FnMut(PhysAddr, Range<usize>),
|
|
|
) {
|
|
|
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(&self, offset: usize, buf: &mut [u8]) -> 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, &mut buf[buf_range]);
|
|
|
});
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
fn write(&self, offset: usize, buf: &[u8]) -> 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, &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(&self, page_idx: usize, _flags: MMUFlags) -> ZxResult<PhysAddr> {
|
|
|
let inner = self.inner.lock();
|
|
|
Ok(inner.frames[page_idx].addr())
|
|
|
}
|
|
|
|
|
|
fn commit_pages_with(
|
|
|
&self,
|
|
|
f: &mut dyn FnMut(&mut dyn FnMut(usize, MMUFlags) -> ZxResult<PhysAddr>) -> ZxResult,
|
|
|
) -> ZxResult {
|
|
|
let inner = self.inner.lock();
|
|
|
f(&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(&self, offset: usize, len: usize) -> ZxResult<Arc<dyn VMObjectTrait>> {
|
|
|
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(&mut self, offset: usize, len: usize) -> ZxResult<Arc<VMObjectPaged>> {
|
|
|
// 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 = "hal_frame_copy"]
|
|
|
pub fn frame_copy(src: PhysAddr, target: PhysAddr) {
|
|
|
trace!("frame_copy: {:#x} <- {:#x}", target, src);
|
|
|
assert!(src + PAGE_SIZE <= PMEM_SIZE && target + PAGE_SIZE <= 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 = "hal_pmem_zero"]
|
|
|
pub fn pmem_zero(paddr: PhysAddr, len: usize) {
|
|
|
trace!("pmem_zero: addr={:#x}, len={:#x}", paddr, len);
|
|
|
assert!(paddr + len <= 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>定义 VmAddressRange,VmMapping</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<Arc<VmAddressRegion>>,
|
|
|
page_table: Arc<Mutex<dyn PageTableTrait>>,
|
|
|
/// If inner is None, this region is destroyed, all operations are invalid.
|
|
|
inner: Mutex<Option<VmarInner>>,
|
|
|
}
|
|
|
|
|
|
#[derive(Default)]
|
|
|
struct VmarInner {
|
|
|
children: Vec<Arc<VmAddressRegion>>,
|
|
|
mappings: Vec<Arc<VmMapping>>,
|
|
|
}
|
|
|
<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() -> Arc<Self> {
|
|
|
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 << 47) - 4096 - USER_ASPACE_BASE;
|
|
|
|
|
|
impl VmAddressRegion {
|
|
|
/// Create a kernel root VMAR.
|
|
|
pub fn new_kernel() -> Arc<Self> {
|
|
|
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<VmObject>,
|
|
|
page_table: Arc<Mutex<dyn PageTableTrait>>,
|
|
|
inner: Mutex<VmMappingInner>,
|
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
struct VmMappingInner {
|
|
|
/// The actual flags used in the mapping of each page
|
|
|
flags: Vec<MMUFlags>,
|
|
|
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: &Arc<Self>) -> ZxResult {
|
|
|
self.vmo.commit_pages_with(&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(&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 = "weak"]
|
|
|
#[export_name = "hal_pt_current"]
|
|
|
pub fn current() -> Self {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
|
|
|
/// Create a new `PageTable`.
|
|
|
#[allow(clippy::new_without_default)]
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_pt_new"]
|
|
|
pub fn new() -> Self {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl PageTableTrait for PageTable {
|
|
|
/// Map the page of `vaddr` to the frame of `paddr` with `flags`.
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_pt_map"]
|
|
|
fn map(&mut self, _vaddr: VirtAddr, _paddr: PhysAddr, _flags: MMUFlags) -> Result<()> {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
/// Unmap the page of `vaddr`.
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_pt_unmap"]
|
|
|
fn unmap(&mut self, _vaddr: VirtAddr) -> Result<()> {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
/// Change the `flags` of the page of `vaddr`.
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_pt_protect"]
|
|
|
fn protect(&mut self, _vaddr: VirtAddr, _flags: MMUFlags) -> Result<()> {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
/// Query the physical address which the page of `vaddr` maps to.
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_pt_query"]
|
|
|
fn query(&mut self, _vaddr: VirtAddr) -> Result<PhysAddr> {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
/// Get the physical address of root page table.
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_pt_table_phys"]
|
|
|
fn table_phys(&self) -> PhysAddr {
|
|
|
self.table_phys
|
|
|
}
|
|
|
|
|
|
/// Activate this page table
|
|
|
#[cfg(target_arch = "riscv64")]
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_pt_activate"]
|
|
|
fn activate(&self) {
|
|
|
unimplemented!()
|
|
|
}
|
|
|
|
|
|
#[linkage = "weak"]
|
|
|
#[export_name = "hal_pt_unmap_cont"]
|
|
|
fn unmap_cont(&mut self, vaddr: VirtAddr, pages: usize) -> Result<()> {
|
|
|
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 = "hal_pt_map"]
|
|
|
fn map(&mut self, vaddr: VirtAddr, paddr: PhysAddr, flags: MMUFlags) -> Result<()> {
|
|
|
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 = "hal_pt_unmap"]
|
|
|
fn unmap(&mut self, vaddr: VirtAddr) -> Result<()> {
|
|
|
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: &Arc<Self>) -> ZxResult {
|
|
|
self.vmo.commit_pages_with(&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("failed to map");
|
|
|
}
|
|
|
Ok(())
|
|
|
})
|
|
|
}
|
|
|
|
|
|
fn unmap(&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("failed to unmap")
|
|
|
}
|
|
|
}
|
|
|
<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>
|
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="zircon-用户程序"><a class="header" href="#zircon-用户程序">Zircon 用户程序</a></h1>
|
|
|
<h2 id="用户态启动流程"><a class="header" href="#用户态启动流程">用户态启动流程</a></h2>
|
|
|
<blockquote>
|
|
|
<p>kernel -> userboot -> bootsvc -> component_manager -> sh / device_manager</p>
|
|
|
<p>ZBI 与 bootfs:ZBI 中包含初始文件系统 bootfs,内核将 ZBI 完整传递给 userboot,由它负责解析并对其它进程提供文件服务</p>
|
|
|
</blockquote>
|
|
|
<h2 id="用户程序的组成"><a class="header" href="#用户程序的组成">用户程序的组成</a></h2>
|
|
|
<blockquote>
|
|
|
<p>内核不直接参与用户程序的加载工作(第一个进程除外)</p>
|
|
|
<p>用户程序强制使用 PIC 和 PIE(位置无关代码)</p>
|
|
|
<p>内存地址空间组成:Program, Stack, vDSO, Dylibs</p>
|
|
|
<p>通过 Channel 传递启动信息和句柄</p>
|
|
|
</blockquote>
|
|
|
<h2 id="加载-elf-文件"><a class="header" href="#加载-elf-文件">加载 ELF 文件</a></h2>
|
|
|
<blockquote>
|
|
|
<p>简单介绍 ELF 文件的组成结构</p>
|
|
|
<p>实现 VmarExt::load_from_elf 函数</p>
|
|
|
</blockquote>
|
|
|
<h2 id="系统调用的跳板vdso"><a class="header" href="#系统调用的跳板vdso">系统调用的跳板:vDSO</a></h2>
|
|
|
<blockquote>
|
|
|
<p>介绍 vDSO 的作用</p>
|
|
|
<p>如何修改 vDSO 源码(libzircon)将 syscall 改为函数调用</p>
|
|
|
<p>加载 vDSO 时修改 vDSO 代码段,填入跳转地址</p>
|
|
|
</blockquote>
|
|
|
<h2 id="第一个用户程序userboot"><a class="header" href="#第一个用户程序userboot">第一个用户程序:userboot</a></h2>
|
|
|
<blockquote>
|
|
|
<p>实现 zircon-loader 中的 run_userboot 函数</p>
|
|
|
<p>能够进入用户态并在第一个系统调用时跳转回来</p>
|
|
|
</blockquote>
|
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="上下文切换"><a class="header" href="#上下文切换">上下文切换</a></h1>
|
|
|
<blockquote>
|
|
|
<p>本节介绍 trapframe-rs 中 <a href="https://github.com/rcore-os/trapframe-rs/blob/master/src/arch/x86_64/fncall.rs">fncall.rs</a> 的魔法实现</p>
|
|
|
</blockquote>
|
|
|
<h2 id="保存和恢复通用寄存器"><a class="header" href="#保存和恢复通用寄存器">保存和恢复通用寄存器</a></h2>
|
|
|
<blockquote>
|
|
|
<p>定义 UserContext 结构体</p>
|
|
|
<p>保存 callee-saved 寄存器到栈上,恢复 UserContext 寄存器,进入用户态,反之亦然</p>
|
|
|
</blockquote>
|
|
|
<h2 id="找回内核上下文线程局部存储-与-fs-寄存器"><a class="header" href="#找回内核上下文线程局部存储-与-fs-寄存器">找回内核上下文:线程局部存储 与 FS 寄存器</a></h2>
|
|
|
<blockquote>
|
|
|
<p>在用户程序跳转回内核代码的那一刻,如何在不破坏用户寄存器的情况下切换回内核栈?</p>
|
|
|
<p>进入用户态前,将内核栈指针保存在内核 glibc 的 TLS 区域中。为此我们需要查看 glibc 源码,找到一个空闲位置。</p>
|
|
|
<p>Linux 和 macOS 下如何分别通过系统调用设置 fsbase / gsbase</p>
|
|
|
</blockquote>
|
|
|
<h2 id="测试"><a class="header" href="#测试">测试</a></h2>
|
|
|
<blockquote>
|
|
|
<p>编写单元测试验证上述过程</p>
|
|
|
</blockquote>
|
|
|
<h2 id="macos-的麻烦动态二进制修改"><a class="header" href="#macos-的麻烦动态二进制修改">macOS 的麻烦:动态二进制修改</a></h2>
|
|
|
<blockquote>
|
|
|
<p>由于 macOS 用户程序无法修改 fs 寄存器,当运行相关指令时会访问非法内存地址触发段错误。</p>
|
|
|
<p>我们需要实现段错误信号处理函数,并在其中动态修改用户程序指令,将 fs 改为 gs。</p>
|
|
|
</blockquote>
|
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="系统调用"><a class="header" href="#系统调用">系统调用</a></h1>
|
|
|
<h2 id="获取系统调用参数"><a class="header" href="#获取系统调用参数">获取系统调用参数</a></h2>
|
|
|
<blockquote>
|
|
|
<p>从寄存器中获取参数</p>
|
|
|
</blockquote>
|
|
|
<h2 id="系统调用上下文与处理函数"><a class="header" href="#系统调用上下文与处理函数">系统调用上下文与处理函数</a></h2>
|
|
|
<blockquote>
|
|
|
<p>定义 Syscall 结构体,实现 syscall 函数</p>
|
|
|
</blockquote>
|
|
|
<h2 id="实现第一个系统调用"><a class="header" href="#实现第一个系统调用">实现第一个系统调用</a></h2>
|
|
|
<blockquote>
|
|
|
<p>实现 sys_channel_read 和 sys_debuglog_write</p>
|
|
|
</blockquote>
|
|
|
<div style="break-before: page; page-break-before: always;"></div><h1 id="信号和等待"><a class="header" href="#信号和等待">信号和等待</a></h1>
|
|
|
<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_unix,Host文件系统,其中载入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() -> 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 pthread,glibc中,其中的首个结构是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>"No, UML works only on x86 and x86_64."</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>
|