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/ch03-04-vmar.html

497 lines
26 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>🚧 虚拟内存VMAR 对象 - 简明 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" class="active"><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="虚拟内存vmar-对象"><a class="header" href="#虚拟内存vmar-对象">虚拟内存VMAR 对象</a></h1>
<h2 id="vmar-简介"><a class="header" href="#vmar-简介">VMAR 简介</a></h2>
<p>虚拟内存地址区域Virtual Memory Address Regions VMARs为管理进程的地址空间提供了一种抽象。在进程创建时将Root VMAR 的句柄提供给进程创建者。该句柄指的是跨越整个地址空间的 VMAR。这个空间可以通过<a href="https://fuchsia.dev/docs/reference/syscalls/vmar_map"><code>zx_vmar_map()</code></a><a href="https://fuchsia.dev/docs/reference/syscalls/vmar_allocate"><code>zx_vmar_allocate()</code></a>接口来划分 。 <a href="https://fuchsia.dev/docs/reference/syscalls/vmar_allocate"><code>zx_vmar_allocate()</code></a>可用于生成新的 VMAR称为子区域或子区域可用于将地址空间的各个部分组合在一起。</p>
<h2 id="实现-vmar-对象框架"><a class="header" href="#实现-vmar-对象框架">实现 VMAR 对象框架</a></h2>
<blockquote>
<p>定义 VmAddressRangeVmMapping</p>
<p>实现 create_child, map, unmap, destroy 函数,并做单元测试验证地址空间分配</p>
</blockquote>
<h3 id="vmaddressregion"><a class="header" href="#vmaddressregion">VmAddressRegion</a></h3>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub struct VmAddressRegion {
flags: VmarFlags,
base: KObjectBase,
addr: VirtAddr,
size: usize,
parent: Option&lt;Arc&lt;VmAddressRegion&gt;&gt;,
page_table: Arc&lt;Mutex&lt;dyn PageTableTrait&gt;&gt;,
/// If inner is None, this region is destroyed, all operations are invalid.
inner: Mutex&lt;Option&lt;VmarInner&gt;&gt;,
}
#[derive(Default)]
struct VmarInner {
children: Vec&lt;Arc&lt;VmAddressRegion&gt;&gt;,
mappings: Vec&lt;Arc&lt;VmMapping&gt;&gt;,
}
<span class="boring">}
</span></code></pre></pre>
<p>构造一个根节点 VMAR这个 VMAR 是每个进程都拥有的。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VmAddressRegion {
/// Create a new root VMAR.
pub fn new_root() -&gt; Arc&lt;Self&gt; {
let (addr, size) = {
use core::sync::atomic::*;
static VMAR_ID: AtomicUsize = AtomicUsize::new(0);
let i = VMAR_ID.fetch_add(1, Ordering::SeqCst);
(0x2_0000_0000 + 0x100_0000_0000 * i, 0x100_0000_0000)
};
Arc::new(VmAddressRegion {
flags: VmarFlags::ROOT_FLAGS,
base: KObjectBase::new(),
addr,
size,
parent: None,
page_table: Arc::new(Mutex::new(kernel_hal::PageTable::new())), //hal PageTable
inner: Mutex::new(Some(VmarInner::default())),
})
}
}
<span class="boring">}
</span></code></pre></pre>
<p>我们的内核同样需要一个根 VMAR</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// The base of kernel address space
/// In x86 fuchsia this is 0xffff_ff80_0000_0000 instead
pub const KERNEL_ASPACE_BASE: u64 = 0xffff_ff02_0000_0000;
/// The size of kernel address space
pub const KERNEL_ASPACE_SIZE: u64 = 0x0000_0080_0000_0000;
/// The base of user address space
pub const USER_ASPACE_BASE: u64 = 0;
// pub const USER_ASPACE_BASE: u64 = 0x0000_0000_0100_0000;
/// The size of user address space
pub const USER_ASPACE_SIZE: u64 = (1u64 &lt;&lt; 47) - 4096 - USER_ASPACE_BASE;
impl VmAddressRegion {
/// Create a kernel root VMAR.
pub fn new_kernel() -&gt; Arc&lt;Self&gt; {
let kernel_vmar_base = KERNEL_ASPACE_BASE as usize;
let kernel_vmar_size = KERNEL_ASPACE_SIZE as usize;
Arc::new(VmAddressRegion {
flags: VmarFlags::ROOT_FLAGS,
base: KObjectBase::new(),
addr: kernel_vmar_base,
size: kernel_vmar_size,
parent: None,
page_table: Arc::new(Mutex::new(kernel_hal::PageTable::new())),
inner: Mutex::new(Some(VmarInner::default())),
})
}
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="vmaddressmapping"><a class="header" href="#vmaddressmapping">VmAddressMapping</a></h3>
<p>VmAddressMapping 用于建立 VMO 和 VMAR 之间的映射。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// Virtual Memory Mapping
pub struct VmMapping {
/// The permission limitation of the vmar
permissions: MMUFlags,
vmo: Arc&lt;VmObject&gt;,
page_table: Arc&lt;Mutex&lt;dyn PageTableTrait&gt;&gt;,
inner: Mutex&lt;VmMappingInner&gt;,
}
#[derive(Debug, Clone)]
struct VmMappingInner {
/// The actual flags used in the mapping of each page
flags: Vec&lt;MMUFlags&gt;,
addr: VirtAddr,
size: usize,
vmo_offset: usize,
}
<span class="boring">}
</span></code></pre></pre>
<p>map 和 unmap 实现内存映射和解映射</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VmMapping {
/// Map range and commit.
/// Commit pages to vmo, and map those to frames in page_table.
/// Temporarily used for development. A standard procedure for
/// vmo is: create_vmo, op_range(commit), map
fn map(self: &amp;Arc&lt;Self&gt;) -&gt; ZxResult {
self.vmo.commit_pages_with(&amp;mut |commit| {
let inner = self.inner.lock();
let mut page_table = self.page_table.lock();
let page_num = inner.size / PAGE_SIZE;
let vmo_offset = inner.vmo_offset / PAGE_SIZE;
for i in 0..page_num {
let paddr = commit(vmo_offset + i, inner.flags[i])?;
//通过 PageTableTrait 的 hal_pt_map 进行页表映射
//调用 kernel-hal的方法进行映射
}
Ok(())
})
}
fn unmap(&amp;self) {
let inner = self.inner.lock();
let pages = inner.size / PAGE_SIZE;
// TODO inner.vmo_offset unused?
// 调用 kernel-hal的方法进行解映射
}
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="hal用-mmap-模拟页表"><a class="header" href="#hal用-mmap-模拟页表">HAL用 mmap 模拟页表</a></h2>
<blockquote>
<p>实现页表接口 map, unmap, protect</p>
</blockquote>
<p>在 kernel-hal 中定义了一个页表和这个页表具有的方法。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>/// Page Table
#[repr(C)]
pub struct PageTable {
table_phys: PhysAddr,
}
impl PageTable {
/// Get current page table
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_current&quot;]
pub fn current() -&gt; Self {
unimplemented!()
}
/// Create a new `PageTable`.
#[allow(clippy::new_without_default)]
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_new&quot;]
pub fn new() -&gt; Self {
unimplemented!()
}
}
impl PageTableTrait for PageTable {
/// Map the page of `vaddr` to the frame of `paddr` with `flags`.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_map&quot;]
fn map(&amp;mut self, _vaddr: VirtAddr, _paddr: PhysAddr, _flags: MMUFlags) -&gt; Result&lt;()&gt; {
unimplemented!()
}
/// Unmap the page of `vaddr`.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_unmap&quot;]
fn unmap(&amp;mut self, _vaddr: VirtAddr) -&gt; Result&lt;()&gt; {
unimplemented!()
}
/// Change the `flags` of the page of `vaddr`.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_protect&quot;]
fn protect(&amp;mut self, _vaddr: VirtAddr, _flags: MMUFlags) -&gt; Result&lt;()&gt; {
unimplemented!()
}
/// Query the physical address which the page of `vaddr` maps to.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_query&quot;]
fn query(&amp;mut self, _vaddr: VirtAddr) -&gt; Result&lt;PhysAddr&gt; {
unimplemented!()
}
/// Get the physical address of root page table.
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_table_phys&quot;]
fn table_phys(&amp;self) -&gt; PhysAddr {
self.table_phys
}
/// Activate this page table
#[cfg(target_arch = &quot;riscv64&quot;)]
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_activate&quot;]
fn activate(&amp;self) {
unimplemented!()
}
#[linkage = &quot;weak&quot;]
#[export_name = &quot;hal_pt_unmap_cont&quot;]
fn unmap_cont(&amp;mut self, vaddr: VirtAddr, pages: usize) -&gt; Result&lt;()&gt; {
for i in 0..pages {
self.unmap(vaddr + i * PAGE_SIZE)?;
}
Ok(())
}
}
<span class="boring">}
</span></code></pre></pre>
<p>在 kernel-hal-unix 中实现了 PageTableTrait在 map 中调用了 mmap。</p>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl PageTableTrait for PageTable {
/// Map the page of `vaddr` to the frame of `paddr` with `flags`.
#[export_name = &quot;hal_pt_map&quot;]
fn map(&amp;mut self, vaddr: VirtAddr, paddr: PhysAddr, flags: MMUFlags) -&gt; Result&lt;()&gt; {
debug_assert!(page_aligned(vaddr));
debug_assert!(page_aligned(paddr));
let prot = flags.to_mmap_prot();
mmap(FRAME_FILE.as_raw_fd(), paddr, PAGE_SIZE, vaddr, prot);
Ok(())
}
/// Unmap the page of `vaddr`.
#[export_name = &quot;hal_pt_unmap&quot;]
fn unmap(&amp;mut self, vaddr: VirtAddr) -&gt; Result&lt;()&gt; {
self.unmap_cont(vaddr, 1)
}
}
<span class="boring">}
</span></code></pre></pre>
<h2 id="实现内存映射"><a class="header" href="#实现内存映射">实现内存映射</a></h2>
<blockquote>
<p>用 HAL 实现上面 VMAR 留空的部分,并做单元测试验证内存映射</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2018">
<span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>impl VmMapping {
/// Map range and commit.
/// Commit pages to vmo, and map those to frames in page_table.
/// Temporarily used for development. A standard procedure for
/// vmo is: create_vmo, op_range(commit), map
fn map(self: &amp;Arc&lt;Self&gt;) -&gt; ZxResult {
self.vmo.commit_pages_with(&amp;mut |commit| {
let inner = self.inner.lock();
let mut page_table = self.page_table.lock();
let page_num = inner.size / PAGE_SIZE;
let vmo_offset = inner.vmo_offset / PAGE_SIZE;
for i in 0..page_num {
let paddr = commit(vmo_offset + i, inner.flags[i])?;
//通过 PageTableTrait 的 hal_pt_map 进行页表映射
page_table
.map(inner.addr + i * PAGE_SIZE, paddr, inner.flags[i])
.expect(&quot;failed to map&quot;);
}
Ok(())
})
}
fn unmap(&amp;self) {
let inner = self.inner.lock();
let pages = inner.size / PAGE_SIZE;
// TODO inner.vmo_offset unused?
self.page_table
.lock()
.unmap_cont(inner.addr, pages)
.expect(&quot;failed to unmap&quot;)
}
}
<span class="boring">}
</span></code></pre></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch03-03-vmo-paged.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-00-userspace.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="ch03-03-vmo-paged.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-00-userspace.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>