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

625 lines
40 KiB

This file contains ambiguous Unicode characters!

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

<!DOCTYPE HTML>
<html lang="cn" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>🚧 Zircon 用户程序 - 简明 zCore 教程</title>
<!-- 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" class="active"><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="zircon-用户程序"><a class="header" href="#zircon-用户程序">Zircon 用户程序</a></h1>
<h2 id="用户态启动流程"><a class="header" href="#用户态启动流程">用户态启动流程</a></h2>
<h3 id="流程概要"><a class="header" href="#流程概要">流程概要</a></h3>
<p>kernel<br />
-&gt; userboot (decompress bootsvc LZ4 format)<br />
-&gt; bootsvc (可执行文件bin/component_manager)<br />
-&gt; component_manager<br />
-&gt; sh / device_manager</p>
<h3 id="zbizircon-boot-image"><a class="header" href="#zbizircon-boot-image">ZBI(Zircon Boot Image)</a></h3>
<p>ZBI是一种简单的容器格式它内嵌了许多可由引导加载程序 <code>BootLoader</code>传递的项目内容包括硬件特定的信息、提供引导选项的内核“命令行”以及RAM磁盘映像(通常是被压缩的)。<code>ZBI</code>中包含了初始文件系统 <code>bootfs</code>,内核将 <code>ZBI</code> 完整传递给 <code>userboot</code>,由它负责解析并对其它进程提供文件服务。</p>
<h3 id="bootfs"><a class="header" href="#bootfs">bootfs</a></h3>
<p>基本的<code>bootfs</code>映像可满足用户空间程序运行需要的所有依赖:
+ 可执行文件
+ 共享库
+ 数据文件</p>
<p>以上列出的内容还可实现设备驱动或更高级的文件系统,从而能够从存储设备或网络设备上访问读取更多的代码和数据。</p>
<p>在系统自引导结束后,<code>bootfs</code>中的文件就会成为一个挂载在根目录<code>/boot</code>上的只读文件系统树(并由bootsvc提供服务)。随后<code>userboot</code>将从<code>bootfs</code>加载第一个真正意义上的用户程序。</p>
<h3 id="a-hrefhttpsfuchsiadevfuchsia-srcconceptsbootingprogram_loadingzcore程序elf加载与动态链接a"><a class="header" href="#a-hrefhttpsfuchsiadevfuchsia-srcconceptsbootingprogram_loadingzcore程序elf加载与动态链接a"><a href="https://fuchsia.dev/fuchsia-src/concepts/booting/program_loading">zCore程序(ELF加载与动态链接)</a></a></h3>
<p>zCore内核不直接参与正常程序的加载而是提供了一些用户态程序加载时可用的模块。如虚拟内存对象(VMO)、进程(processes)、虚拟地址空间VMAR和线程(threads)。 </p>
<h3 id="elf-格式以及系统应用程序二进制接口system-abi"><a class="header" href="#elf-格式以及系统应用程序二进制接口system-abi">ELF 格式以及系统应用程序二进制接口(system ABI)</a></h3>
<p>标准的zCore用户空间环境提供了动态链接器以及基于ELF的执行环境能够运行ELF格式的格式机器码可执行文件。zCore进程只能通过zCore vDSO使用系统调用。内核采用基于ELF系统常见的程序二进制接口(ABI)提供了vDSO。</p>
<p>具备适当功能的用户空间代码可通过系统调用直接创建进程和加载程序而不用ELF。但是zCore的标准ABI使用了这里所述的ELF。有关ELF文件格式的背景知识如下 </p>
<h3 id="elf文件类型"><a class="header" href="#elf文件类型">ELF文件类型</a></h3>
<p>“ET_REL”代表此ELF文件为可重定位文件</p>
<p>“ET_EXEC“代表ELF文件为可执行文件</p>
<p>“ET_DYN”代表此ELF文件为动态链接库</p>
<p>“ET_CORE”代表此ELF文件是核心转储文件</p>
<h3 id="传统elf程序文件加载"><a class="header" href="#传统elf程序文件加载">传统ELF程序文件加载</a></h3>
<p>可执行链接格式(Executable and Linking Format, ELF)最初由 UNIX 系统实验室开发并发布并成为大多数类Unix系统的通用标准可执行文件格式。在这些系统中内核使用<code>POSIX</code>(可移植操作系统接口)<code>execve API</code>将程序加载与文件系统访问集成在一起。该类系统加载ELF程序的方式会有一些不同但大多遵循以下模式:</p>
<ol>
<li>
<p>内核按照名称加载文件并检查它是ELF还是系统支持的其他类型的文件。</p>
</li>
<li>
<p>内核根据ELF文件的<code>PT_LOAD</code>程序头来映射ELF映像。对于<code>ET_EXEC</code>文件,系统会将程序中的各段(Section)放到<code>p_vaddr</code>中所指定内存中的固定地址。对于<code>ET_DYN</code>文件,系统将加载程序第一个<code>PT_LOAD</code>的基地址,然后根据它们的<code>p_vaddr</code>相对于第一个section的<code>p_vaddr</code>放置后面的section。 通常来说该基地址是通过地址随机化(ASLR)来产生的。</p>
</li>
<li>
<p>若ELF文件中有一个<code>PT_INTERP</code>(Program interpreter)程序头, 它的部分内容(ELF文件中<code>p_offset</code><code>p_filesz</code>给出的一些字节)被当做为一个文件名改文件名用于寻找另一个称为“ELF解释器”的ELF文件。上述这种ELF文件是<code>ET_DYN</code>文件。内核采用同样的方式将该类ELF文件加载但是所加载的地址是自定的。该ELF“解释器”通常指的是被命名为<code>/lib/ld.so.1</code> 或者是 <code>/lib/ld-linux.so.2</code>的ELF动态链接器。</p>
</li>
<li>
<p>内核为初始的线程设置了寄存器和堆栈的内容并在PC寄存器已指向特定程序入口处(Entry Point)的情况下启动线程。 </p>
<ul>
<li>程序入口处(Entry Point)指的是ELF文件头中 <code>e_entry</code>的值,它会根据程序基地址(base address)做相应的调整。如果这是一个带有<code>PT_INTERP</code>的ELF文件则它的入口点不在它本身而是被设置在动态链接器中。</li>
<li>内核通过设置寄存器和堆栈来使得程序能够接收特定的参数环境变量以及其它有实际用途的辅助向量。寄存器和堆栈的设置方法遵循了一种汇编级别的协议方式。若ELF文件运行时依赖动态链接即带有<code>PT_INTERP</code>。则寄存器和堆栈中将包括来自该可执行文件的ELF文件头中的基地址、入口点和程序头部表地址信息这些信息可允许动态链接器在内存中找到该可执行文件的ELF动态链接元数据以实现动态链接。当动态链接启动完成后动态链接器将跳转到该可执行文件的入口点地址。</li>
</ul>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> pub fn sys_process_start(
&amp;self,
proc_handle: HandleValue,
thread_handle: HandleValue,
entry: usize,
stack: usize,
arg1_handle: HandleValue,
arg2: usize,
) -&gt; ZxResult {
info!(&quot;process.start: proc_handle={:?}, thread_handle={:?}, entry={:?}, stack={:?}, arg1_handle={:?}, arg2={:?}&quot;,
proc_handle, thread_handle, entry, stack, arg1_handle, arg2
);
let proc = self.thread.proc();
let process = proc.get_object_with_rights::&lt;Process&gt;(proc_handle, Rights::WRITE)?;
let thread = proc.get_object_with_rights::&lt;Thread&gt;(thread_handle, Rights::WRITE)?;
if !Arc::ptr_eq(&amp;thread.proc(), &amp;process) {
return Err(ZxError::ACCESS_DENIED);
}
let arg1 = if arg1_handle != INVALID_HANDLE {
let arg1 = proc.remove_handle(arg1_handle)?;
if !arg1.rights.contains(Rights::TRANSFER) {
return Err(ZxError::ACCESS_DENIED);
}
Some(arg1)
} else {
None
};
process.start(&amp;thread, entry, stack, arg1, arg2, self.spawn_fn)?;
Ok(())
}
<span class="boring">}
</span></code></pre></pre>
</li>
</ol>
<p>zCore的程序加载受到了传统方式的启发但是有一些不同。在传统模式中需要在加载动态链接器之前加载可执行文件的一个关键原因是动态链接器随机化选择的基地址(base address)不能与<code>ET_EXEC</code>可执行文件使用的固定地址相交。zCore从根本上并不支持<code>ET_EXEC</code>格式ELF文件的固定地址程序加载它只支持位置无关的可执行文件或<a href="https://patchwork.kernel.org/patch/9807325/">PIE</a>(<code>ET_DYN</code>格式的ELF文件)</p>
<h3 id="vmarext-trait实现"><a class="header" href="#vmarext-trait实现">VmarExt trait实现</a></h3>
<p>zCore底层的API不支持文件系统。zCore程序文件的加载通过虚拟内存对象(VMO)以及<code>channel</code>使用的进程间通信机制来完成。</p>
<p>程序的加载基于如下一些前提:
+ 获得一个包含可执行文件的虚拟内存对象VMO的句柄。</p>
<blockquote>
<p>zircon-object\src\util\elf_loader.rs</p>
</blockquote>
<pre><code class="language-shell">fn make_vmo(elf: &amp;ElfFile, ph: ProgramHeader) -&gt; ZxResult&lt;Arc&lt;VmObject&gt;&gt; {
assert_eq!(ph.get_type().unwrap(), Type::Load);
let page_offset = ph.virtual_addr() as usize % PAGE_SIZE;
let pages = pages(ph.mem_size() as usize + page_offset);
let vmo = VmObject::new_paged(pages);
let data = match ph.get_data(&amp;elf).unwrap() {
SegmentData::Undefined(data) =&gt; data,
_ =&gt; return Err(ZxError::INVALID_ARGS),
};
vmo.write(page_offset, data)?;
Ok(vmo)
}
</code></pre>
<ul>
<li>程序执行参数列表。</li>
<li>程序执行环境变量列表。</li>
<li>存在一个初始的句柄列表,每个句柄都有一个句柄信息项。</li>
</ul>
<h3 id="userboot"><a class="header" href="#userboot">USERBOOT</a></h3>
<h4 id="使用userboot的原因"><a class="header" href="#使用userboot的原因">使用userboot的原因</a></h4>
<p>在Zircon中内嵌在ZBI中的<code>RAM磁盘映像</code>通常采用<a href="https://github.com/lz4/lz4">LZ4</a>格式压缩。解压后将继续得到<code>bootfs</code>格式的磁盘镜像。这是一种简单的只读文件系统格式它只列出文件名。且对于每个文件可分别列出它们在BOOTFS映像中的偏移量和大小(这两个值都必须是页面对齐的并且限制在32位)。</p>
<p>由于kernel中没有包含任何可用于解压缩<a href="https://github.com/lz4/lz4">LZ4</a>格式的代码也没有任何用于解析BOOTFS格式的代码。所有这些工作都是由称为<code>userboot</code>的第一个用户空间进程完成的。</p>
<blockquote>
<p>zCore中未找到解压缩bootfs的相关实现<br />
但是能够在scripts/gen-prebuilt.sh中找到ZBI中确实有bootfs的内容<br />
且现有的zCore实现中有关所载入的ZBI方式如下</p>
</blockquote>
<blockquote>
<p>zircon-loader/src/lib.rs</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> // zbi
let zbi_vmo = {
let vmo = VmObject::new_paged(images.zbi.as_ref().len() / PAGE_SIZE + 1);
vmo.write(0, images.zbi.as_ref()).unwrap();
vmo.set_name(&quot;zbi&quot;);
vmo
};
<span class="boring">}
</span></code></pre></pre>
<h4 id="userboot是什么"><a class="header" href="#userboot是什么">userboot是什么</a></h4>
<p>userboot是一个普通的用户空间进程。它只能像任何其他进程一样通过vDSO执行标准的系统调用并受完整vDSO执行制度的约束。</p>
<blockquote>
<p>唯一一个由内核态“不规范地”创建的用户进程</p>
<p>userboot具体实现的功能有</p>
<ul>
<li>
<p>读取channel中的cmdline、handles </p>
</li>
<li>
<p>解析zbi</p>
</li>
<li>
<p>解压BOOTFS </p>
</li>
<li>
<p>选定下一个程序启动 自己充当loader然后“死亡”</p>
</li>
<li>
<p>用“规范的方式”启动下一个程序</p>
</li>
</ul>
</blockquote>
<p>userboot被构建为一个ELF动态共享对象(DSO,dynamic shared object)使用了与vDSO相同的布局。与vDSO一样userboot的ELF映像在编译时就被嵌入到内核中。其简单的布局意味着加载它不需要内核在引导时解析ELF的文件头。内核只需要知道三件事:</p>
<ol>
<li>只读段<code>segment</code>的大小</li>
<li>可执行段<code>segment</code>的大小</li>
<li><code>userboot</code>入口点的地址。</li>
</ol>
<p>这些值在编译时便可从userboot ELF映像中提取并在内核代码中用作常量。</p>
<h4 id="kernel如何启用userboot"><a class="header" href="#kernel如何启用userboot">kernel如何启用userboot</a></h4>
<p>与任何其他进程一样userboot必须从已经映射到其地址空间的vDSO开始这样它才能进行系统调用。内核将userboot和vDSO映射到第一个用户进程然后在userboot的入口处启动它。</p>
<!-- > ! userboot的特殊之处在于它的加载方式。
> ...todo -->
<h4 id="userboot如何在vdso中取得系统调用"><a class="header" href="#userboot如何在vdso中取得系统调用">userboot如何在vDSO中取得系统调用</a></h4>
<p>当内核将<code>userboot</code>映射到第一个用户进程时,会像正常程序那样,在内存中选择一个随机地址进行加载。而在映射<code>userboot</code>的vDSO时并不采用上述随机的方式而是将vDSO映像直接放在内存中<code>userboot</code>的映像之后。这样一来vDSO代码与<code>userboot</code>的偏移量总是固定的。</p>
<p>在编译阶段中系统调用的入口点符号表会从vDSO ELF映像中提取出来随后写入到链接脚本的符号定义中。利用每个符号在vDSO映像中相对固定的偏移地址可在链接脚本提供的<code>_end</code>符号的固定偏移量处定义该符号。通过这种方式userboot代码可以直接调用到放在内存中其映像本身之后的每个确切位置上的vDSO入口点。</p>
<p>相关代码:</p>
<blockquote>
<p>zircon-loader/src/lib.rs</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub fn run_userboot(images: &amp;Images&lt;impl AsRef&lt;[u8]&gt;&gt;, cmdline: &amp;str) -&gt; Arc&lt;Process&gt; {
...
// vdso
let vdso_vmo = {
let elf = ElfFile::new(images.vdso.as_ref()).unwrap();
let vdso_vmo = VmObject::new_paged(images.vdso.as_ref().len() / PAGE_SIZE + 1);
vdso_vmo.write(0, images.vdso.as_ref()).unwrap();
let size = elf.load_segment_size();
let vmar = vmar
.allocate_at(
userboot_size,
size,
VmarFlags::CAN_MAP_RXW | VmarFlags::SPECIFIC,
PAGE_SIZE,
)
.unwrap();
vmar.map_from_elf(&amp;elf, vdso_vmo.clone()).unwrap();
#[cfg(feature = &quot;std&quot;)]
{
let offset = elf
.get_symbol_address(&quot;zcore_syscall_entry&quot;)
.expect(&quot;failed to locate syscall entry&quot;) as usize;
let syscall_entry = &amp;(kernel_hal_unix::syscall_entry as usize).to_ne_bytes();
// fill syscall entry x3
vdso_vmo.write(offset, syscall_entry).unwrap();
vdso_vmo.write(offset + 8, syscall_entry).unwrap();
vdso_vmo.write(offset + 16, syscall_entry).unwrap();
}
vdso_vmo
};
...
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="bootsvc"><a class="header" href="#bootsvc">bootsvc</a></h3>
<p>bootsvc 通常是usermode加载的第一个程序与userboot不同userboot是由内核加载的。bootsvc提供了几种系统服务</p>
<ul>
<li>
<p>包含bootfs/boot内容的文件系统服务初始的bootfs映像包含用户空间系统需要运行的所有内容:</p>
<ul>
<li>可执行文件</li>
<li>共享库和数据文件(包括设备驱动程序或更高级的文件系统的实现)</li>
</ul>
</li>
<li>
<p>从bootfs加载的加载程序服务</p>
</li>
<li>
<p>bin/component_manager</p>
</li>
<li>
<p>sh / device_manager</p>
</li>
</ul>
<h2 id="用户程序的组成"><a class="header" href="#用户程序的组成">用户程序的组成</a></h2>
<blockquote>
<p>内核不直接参与用户程序的加载工作(第一个进程除外)</p>
<p>用户程序强制使用 PIC 和 PIE位置无关代码</p>
<p>内存地址空间组成Program, Stack, vDSO, Dylibs</p>
<p>通过 Channel 传递启动信息和句柄</p>
</blockquote>
<h2 id="系统调用的跳板vdso"><a class="header" href="#系统调用的跳板vdso">系统调用的跳板vDSO</a></h2>
<h4 id="介绍-vdso-的作用"><a class="header" href="#介绍-vdso-的作用">介绍 vDSO 的作用</a></h4>
<p>vDSOvirtual Dynamic Shared ObjectZircon vDSO 是 Zircon 内核访问系统调用的唯一方法(作为系统调用的跳板)。它之所以是虚拟的是因为它不是从文件系统中的ELF文件加载的而是由内核直接提供的vDSO镜像。</p>
<!-- Zircon vDSO是访问Zircon系统调用的唯一手段。vDSO表示虚拟动态共享对象。(动态共享对象是一个术语用于ELF格式的共享库。)它是虚拟的因为它不是从文件系统中的ELF文件加载的。相反vDSO映像由内核直接提供。 -->
<blockquote>
<p>zCore/src/main.rs</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">#[cfg(feature = &quot;zircon&quot;)]
fn main(ramfs_data: &amp;[u8], cmdline: &amp;str) {
use zircon_loader::{run_userboot, Images};
let images = Images::&lt;&amp;[u8]&gt; {
userboot: include_bytes!(&quot;../../prebuilt/zircon/x64/userboot.so&quot;),
vdso: include_bytes!(&quot;../../prebuilt/zircon/x64/libzircon.so&quot;),
zbi: ramfs_data,
};
let _proc = run_userboot(&amp;images, cmdline);
run();
}
</code></pre></pre>
<p>它是一个用户态运行的代码,被封装成<code>prebuilt/zircon/x64/libzircon.so</code>文件。这个.so 文件装载不是放在文件系统中而是由内核提供。它被整合在内核image中。</p>
<p>vDSO映像在编译时嵌入到内核中。内核将它作为只读VMO公开给用户空间。内核启动时会通过计算得到它所在的物理页。当<code>program loader</code>设置了一个新进程后,使该进程能够进行系统调用的唯一方法是:<code>program loader</code>在新进程的第一个线程开始运行之前将vDSO映射到新进程的虚拟地址空间地址随机。因此在启动其他能够进行系统调用的进程的每个进程自己本身都必须能够访问vDSO的VMO。</p>
<blockquote>
<p>zircon-loader/src/lib.rs#line167</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> proc.start(&amp;thread, entry, sp, Some(handle), 0, thread_fn)
.expect(&quot;failed to start main thread&quot;);
proc
<span class="boring">}
</span></code></pre></pre>
<blockquote>
<p>zircon-object/src/task/process.rs#line189</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> thread.start(entry, stack, handle_value as usize, arg2, thread_fn)
<span class="boring">}
</span></code></pre></pre>
<p>vDSO被映射到新进程的同时会将映像的<code>base address</code>通过<code>arg2</code>参数传递给新进程中的第一个线程。通过这个地址可以在内存中找到ELF的文件头该文件头指向可用于查找系统调用符号名的其他ELF程序模块。</p>
<h4 id="如何修改-vdso-源码libzircon将-syscall-改为函数调用"><a class="header" href="#如何修改-vdso-源码libzircon将-syscall-改为函数调用">如何修改 vDSO 源码libzircon将 syscall 改为函数调用</a></h4>
<h5 id="有关代码"><a class="header" href="#有关代码">有关代码</a></h5>
<ul>
<li>参考仓库<a href="https://github.com/PanQL/zircon/blob/master/README.md">README.MD</a>
<blockquote>
<p>···解析代码依赖的compile_commands.json将会随build过程生成到<strong>out</strong>文件夹···</p>
</blockquote>
</li>
</ul>
<h5 id="如何生成imgsvdsozbi"><a class="header" href="#如何生成imgsvdsozbi">如何生成imgs(VDSO,ZBI)</a></h5>
<ol>
<li>
<p>clone Zircon代码仓库从fuchsia官方目录中分离出来的zircon代码</p>
<pre><code class="language-shell">$ git clone https://github.com/PanQL/zircon.git
</code></pre>
</li>
<li>
<p>关于Zircon的编译运行<br />
为了减小仓库体积我们将prebuilt目录进行了大幅调整;因此运行之前请下载google预编译好的clang解压后放到某个权限合适的位置然后在代码的<a href="https://github.com/PanQL/zircon/blob/master/public/gn/toolchain/clang.gni#L16">这个位置</a><strong>绝对目录</strong>修改为对应位置。
clang下载链接:</p>
<ul>
<li><a href="https://cloud.tsinghua.edu.cn/d/7ab1d87feecd4b2cb3d8/">云盘下载链接</a></li>
<li>官方CIPD包下载链接如下
<ul>
<li><a href="https://chrome-infra-packages.appspot.com/p/fuchsia/clang/linux-amd64/+/oEsFSe99FkcDKVxZkAY0MKi6C-yYOan1m-QL45N33W8C">Linux</a></li>
<li><a href="https://chrome-infra-packages.appspot.com/p/fuchsia/clang/mac-amd64/+/Lc64-GTi4kihzkCnW8Vaa80TWTnMpZY0Fy6AqChmqvcC">Mac</a></li>
</ul>
</li>
</ul>
</li>
<li>
<p>当前只支持在Mac OS及Linux x64上进行编译。<br />
默认的<code>make run</code><code>make build</code>是针对x64架构的如果希望编译运行arm架构的zircon那么需要</p>
<ul>
<li>修改out/args.gn中的<code>legacy-image-x64</code><code>legacy-image-arm64</code></li>
<li>重新<code>make build</code></li>
<li><code>make runarm</code></li>
</ul>
</li>
<li>
<p>配合zCore中的有关脚本与补丁文件</p>
<ul>
<li>scripts/gen-prebuilt.sh</li>
<li>scripts/zircon-libos.patch</li>
</ul>
<ul>
<li>https://github.com/PanQL/zircon/blob/master/system/ulib/zircon/syscall-entry.h</li>
<li>https://github.com/PanQL/zircon/blob/master/system/ulib/zircon/syscalls-x86-64.S</li>
<li>zircon-loader/src/lib.rs#line 83-93</li>
</ul>
</li>
</ol>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> #[cfg(feature = &quot;std&quot;)]
{
let offset = elf
.get_symbol_address(&quot;zcore_syscall_entry&quot;)
.expect(&quot;failed to locate syscall entry&quot;) as usize;
let syscall_entry = &amp;(kernel_hal_unix::syscall_entry as usize).to_ne_bytes();
// fill syscall entry x3
vdso_vmo.write(offset, syscall_entry).unwrap();
vdso_vmo.write(offset + 8, syscall_entry).unwrap();
vdso_vmo.write(offset + 16, syscall_entry).unwrap();
}
<span class="boring">}
</span></code></pre></pre>
<!-- 当vsdo 用svc 指令后这时CPU exception进入内核到 expections.S 中的 sync_exception 宏不同ELx sync_exception的参数不一样。然后这个 sync_exception 宏中先做一些现场保存的工作, 然后jump到 arm64_syscall_dispatcher 宏。
进入arm64_syscall_dispatcher宏后 先做一些syscall number检查然后syscall number 跳到 call_wrapper_table 函数表中相应index项的函数中去call_wrapper_table 像一个一维的函数指针的数组syscall number 作indexjump到相应的wrapper syscall function 函数中去)。 -->
<h4 id="加载-vdso-时修改-vdso-代码段填入跳转地址"><a class="header" href="#加载-vdso-时修改-vdso-代码段填入跳转地址">加载 vDSO 时修改 vDSO 代码段,填入跳转地址</a></h4>
<h2 id="第一个用户程序userboot"><a class="header" href="#第一个用户程序userboot">第一个用户程序userboot</a></h2>
<blockquote>
<p>实现 zircon-loader 中的 run_userboot 函数</p>
<p>能够进入用户态并在第一个系统调用时跳转回来</p>
</blockquote>
<h4 id="从bootfs加载第一个真正意义上的用户程序"><a class="header" href="#从bootfs加载第一个真正意义上的用户程序"><code>bootfs</code>加载第一个真正意义上的用户程序。</a></h4>
<p>主要相关代码:</p>
<blockquote>
<p>zircon-loader/src/lib.rs
zircon-object/src/util/elf_loader.rs</p>
</blockquote>
<p><code>userboot</code>解压完毕<code>ZBI</code>中的<code>bootfs</code>后,<code>userboot</code>将继续从<code>bootfs</code>载入程序文件运行。</p>
<p>Zircon中具体的实现流程如下</p>
<ol>
<li>
<p><code>userboot</code>检查从内核接收到的环境字符串,这些字符串代表了一定的内核命令行。</p>
<blockquote>
<p>zircon-loader/src/main.rs</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">#[async_std::main]
async fn main() {
kernel_hal_unix::init();
init_logger();
let opt = Opt::from_args();
let images = open_images(&amp;opt.prebuilt_path).expect(&quot;failed to read file&quot;);
let proc: Arc&lt;dyn KernelObject&gt; = run_userboot(&amp;images, &amp;opt.cmdline);
drop(images);
proc.wait_signal(Signal::USER_SIGNAL_0).await;
}
</code></pre></pre>
<p>在Zircon中</p>
<ul>
<li>若该字符串内容为<code>userboot=file</code>,那么该<code>file</code>将作为第一个真正的用户进程加载。</li>
<li>若没有这样的选项,则<code>userboot</code>将选择的默认文为<code>bin/bootsvc</code>。该文件可在<code>bootfs</code>中找到。</li>
</ul>
<p>而在zCore的实现中</p>
<ul>
<li>..</li>
</ul>
</li>
<li>
<p>为了加载上述文件userboot实现了一个功能齐全的ELF程序加载器
<code>zircon_object::util::elf_loader::load_from_elf</code></p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> // userboot
let (entry, userboot_size) = {
let elf = ElfFile::new(images.userboot.as_ref()).unwrap();
let size = elf.load_segment_size();
let vmar = vmar
.allocate(None, size, VmarFlags::CAN_MAP_RXW, PAGE_SIZE)
.unwrap();
vmar.load_from_elf(&amp;elf).unwrap();
(vmar.addr() + elf.header.pt2.entry_point() as usize, size)
};
<span class="boring">}
</span></code></pre></pre>
</li>
<li>
<p>然后userboot以随机地址加载vDSO。它使用标准约定启动新进程并给它传递一个channel句柄和vDSO基址。
<code>zircon_object::util::elf_loader::map_from_elf</code></p>
</li>
</ol>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch04-00-userspace.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="ch04-02-context-switch.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="ch04-00-userspace.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="ch04-02-context-switch.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</body>
</html>