2rust-dynamic-allocation.html 43.9 KB
Newer Older
Y
Yifan Wu 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122


<!DOCTYPE html>
<html class="writer-html5" lang="zh-CN" >
<head>
  <meta charset="utf-8">
  
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  
  <title>Rust 中的动态内存分配 &mdash; rCore-Tutorial-Book-v3 0.1 文档</title>
  

  
  <link rel="stylesheet" href="../_static/css/theme.css" type="text/css" />
  <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />

  
  
  
  

  
  <!--[if lt IE 9]>
    <script src="../_static/js/html5shiv.min.js"></script>
  <![endif]-->
  
    
      <script type="text/javascript" id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
        <script src="../_static/jquery.js"></script>
        <script src="../_static/underscore.js"></script>
        <script src="../_static/doctools.js"></script>
        <script src="../_static/language_data.js"></script>
        <script kind="utterances">

    var commentsRunWhenDOMLoaded = cb => {
    if (document.readyState != 'loading') {
        cb()
    } else if (document.addEventListener) {
        document.addEventListener('DOMContentLoaded', cb)
    } else {
        document.attachEvent('onreadystatechange', function() {
        if (document.readyState == 'complete') cb()
        })
    }
}

var addUtterances = () => {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = "https://utteranc.es/client.js";
    script.async = "async";

    script.setAttribute("repo", "rcore-os/rCore-Tutorial-Book-v3");
    script.setAttribute("issue-term", "pathname");
    script.setAttribute("theme", "github-light");
    script.setAttribute("label", "comments");
    script.setAttribute("crossorigin", "anonymous");

    sections = document.querySelectorAll("div.section");
    if (sections !== null) {
        section = sections[sections.length-1];
        section.appendChild(script);
    }
}
commentsRunWhenDOMLoaded(addUtterances);
</script>
        <script src="../_static/translations.js"></script>
        <script async="async" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?config=TeX-AMS-MML_HTMLorMML"></script>
    
    <script type="text/javascript" src="../_static/js/theme.js"></script>

    
    <link rel="index" title="索引" href="../genindex.html" />
    <link rel="search" title="搜索" href="../search.html" />
    <link rel="next" title="实现 SV39 多级页表机制" href="3sv39-implementation.html" />
    <link rel="prev" title="地址空间" href="1address-space.html" /> 
</head>

<body class="wy-body-for-nav">

   
  <div class="wy-grid-for-nav">
    
    <nav data-toggle="wy-nav-shift" class="wy-nav-side">
      <div class="wy-side-scroll">
        <div class="wy-side-nav-search" >
          

          
            <a href="../index.html" class="icon icon-home" alt="Documentation Home"> rCore-Tutorial-Book-v3
          

          
          </a>

          
            
            
          

          
<div role="search">
  <form id="rtd-search-form" class="wy-form" action="../search.html" method="get">
    <input type="text" name="q" placeholder="Search docs" />
    <input type="hidden" name="check_keywords" value="yes" />
    <input type="hidden" name="area" value="default" />
  </form>
</div>

          
        </div>

        
        <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
          
            
            
              
            
            
              <p class="caption"><span class="caption-text">Part1 - Just do it!</span></p>
<ul class="current">
Y
Yifan Wu 已提交
123
<li class="toctree-l1"><a class="reference internal" href="../chapter0/index.html">第零章:操作系统概述</a></li>
Y
Yifan Wu 已提交
124 125
<li class="toctree-l1"><a class="reference internal" href="../chapter1/index.html">第一章:RV64 裸机应用</a></li>
<li class="toctree-l1"><a class="reference internal" href="../chapter2/index.html">第二章:批处理系统</a></li>
Y
Yifan Wu 已提交
126
<li class="toctree-l1"><a class="reference internal" href="../chapter3/index.html">第三章:多道程序与分时多任务</a></li>
Y
Yifan Wu 已提交
127 128
<li class="toctree-l1 current"><a class="reference internal" href="index.html">第四章:地址空间(施工中)</a><ul class="current">
<li class="toctree-l2"><a class="reference internal" href="1address-space.html">地址空间</a></li>
Y
Yifan Wu 已提交
129 130 131 132 133 134
<li class="toctree-l2 current"><a class="current reference internal" href="#">Rust 中的动态内存分配</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#id1">静态与动态内存分配</a></li>
<li class="toctree-l3"><a class="reference internal" href="#id2">Rust 中的堆数据结构</a></li>
<li class="toctree-l3"><a class="reference internal" href="#id4">在内核中支持动态内存分配</a></li>
</ul>
</li>
Y
Yifan Wu 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
<li class="toctree-l2"><a class="reference internal" href="3sv39-implementation.html">实现 SV39 多级页表机制</a></li>
<li class="toctree-l2"><a class="reference internal" href="4kernel-app-spaces.html">内核与应用的地址空间</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="../chapter5/index.html">第五章:进程及重要系统调用</a></li>
<li class="toctree-l1"><a class="reference internal" href="../chapter6/index.html">第六章:文件描述符与进程间通信</a></li>
<li class="toctree-l1"><a class="reference internal" href="../chapter7/index.html">第七章:数据持久化存储</a></li>
</ul>
<p class="caption"><span class="caption-text">Part2 - Do it better!</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../chapter8/index.html">第八章:阻塞(暂定)</a></li>
</ul>
<p class="caption"><span class="caption-text">附录</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../appendix-a/index.html">附录 A:Rust 快速入门</a></li>
<li class="toctree-l1"><a class="reference internal" href="../appendix-b/index.html">附录 B:常见工具的使用方法</a></li>
<li class="toctree-l1"><a class="reference internal" href="../appendix-c/index.html">附录 C:深入机器模式:RustSBI</a></li>
<li class="toctree-l1"><a class="reference internal" href="../terminology.html">术语中英文对照表</a></li>
</ul>
<p class="caption"><span class="caption-text">开发注记</span></p>
<ul>
Y
Yifan Wu 已提交
156
<li class="toctree-l1"><a class="reference internal" href="../setup-sphinx.html">修改和构建本项目</a></li>
Y
Yifan Wu 已提交
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
<li class="toctree-l1"><a class="reference internal" href="../rest-example.html">reStructuredText 基本语法</a></li>
</ul>

            
          
        </div>
        
      </div>
    </nav>

    <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">

      
      <nav class="wy-nav-top" aria-label="top navigation">
        
          <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
          <a href="../index.html">rCore-Tutorial-Book-v3</a>
        
      </nav>


      <div class="wy-nav-content">
        
        <div class="rst-content">
        
          















<div role="navigation" aria-label="breadcrumbs navigation">

  <ul class="wy-breadcrumbs">
    
      <li><a href="../index.html" class="icon icon-home"></a> &raquo;</li>
        
          <li><a href="index.html">第四章:地址空间(施工中)</a> &raquo;</li>
        
      <li>Rust 中的动态内存分配</li>
    
    
      <li class="wy-breadcrumbs-aside">
        
            
            <a href="../_sources/chapter4/2rust-dynamic-allocation.rst.txt" rel="nofollow"> View page source</a>
          
        
      </li>
    
  </ul>

  
  <hr/>
</div>
          <div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
           <div itemprop="articleBody">
            
  <div class="section" id="rust">
<h1>Rust 中的动态内存分配<a class="headerlink" href="#rust" title="永久链接至标题"></a></h1>
Y
Yifan Wu 已提交
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
<p>到目前为止,如果将我们的内核也看成一个应用,那么其中所有的变量都是被静态分配在内存中的。为了在接下来一些功能的实现中
进一步释放 Rust 语言的强表达能力来减轻我们的编码负担,本节我们尝试在内核中支持动态内存分配以待后面使用。</p>
<div class="section" id="id1">
<h2>静态与动态内存分配<a class="headerlink" href="#id1" title="永久链接至标题"></a></h2>
<p>若在某一时间点观察一个应用的地址空间,可以看到若干块连续内存,每一块都对应于一个生命周期尚未结束的变量。这个变量可能
是一个局部变量,它来自于当前正在执行的函数或者当前函数调用栈上某个正在等待调用返回的函数的栈帧,也即它是被分配在
栈上;这个变量也可能是一个全局变量,它被分配在数据段中。它们有一个共同点:在编译的时候编译器已经知道它们类型的字节大小,
于是给它们分配一块等大的内存将它们存储其中,这块内存在变量所属函数的栈帧/数据段中的位置也已经被固定了下来。</p>
<p id="term-static-allocation">这些变量是被 <strong>静态分配</strong> (Static Allocation) 的,这一过程来源于我们在程序中对变量的声明,在编译期由编译器完成。
如果应用仅使用静态分配,它也许可以应付绝大部分的需求,但是某些情况则不够灵活。比如,需要将一个文件读到内存进行处理,
而且必须将文件一次性完整读进来处理才能正确。此时,可以选择声明一个栈上的局部变量或者数据段中的全局变量作为缓冲区来暂存
文件的内容。但在编程的时候我们并不知道待处理的文件的大小,只能根据经验将缓冲区的大小设置为某一固定常数。在代码真正运行
的时候,如果待处理的文件很小,那么缓冲区多出的部分是被浪费掉的,也拉高了应用的内存占用;如果待处理的文件很大,应用则
无法正常运行。就像缓冲区的大小设置一样,还有很多其他的问题来源于某些数据结构需求的内存大小取决于应用的实际运行情况。</p>
<p id="term-dynamic-allocation">此时,使用 <strong>动态分配</strong> (Dynamic Allocation) 则可以解决这个问题。动态分配就是指应用不仅在自己的地址空间放置那些
自编译期开始就大小固定、用于静态内存分配的逻辑段(如全局数据段、栈段),还另外放置一个大小可以随着应用的运行动态增减
的逻辑段,它的名字叫做堆。同时,应用还要能够将这个段真正管理起来,即支持在运行的时候从里面分配一块空间来存放变量,而
在变量的生命周期结束之后,这块空间需要被回收以待后面的使用。如果堆的大小固定,那么这其实就是一个连续内存分配问题,
我们课上所介绍到的那些算法都可以随意使用。取决于应用的实际运行状况,每次分配的空间大小可能会有不同,因此也会产生外碎片。
如果在某次分配的时候发现堆空间不足,我们并不会像上一小节介绍的那样移动变量的存放位置让它们紧凑起来从而释放间隙用来分配
(事实上它很难做到这一点),
一般情况下应用会直接通过系统调用(如类 Unix 内核提供的 <code class="docutils literal notranslate"><span class="pre">sbrk</span></code> 调用)来向内核请求增加它地址空间内堆的大小,之后
就可以正常分配了。注意这一类系统调用也能缩减堆的大小。</p>
<p>鉴于动态分配是一项非常基础的功能,很多高级语言的标准库中都实现了它。以 C 语言为例,C 标准库中提供了如下两个动态分配
的接口函数:</p>
<div class="highlight-c notranslate"><div class="highlight"><pre><span></span><span class="kt">void</span><span class="o">*</span> <span class="nf">malloc</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">size</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">free</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">ptr</span><span class="p">);</span>
</pre></div>
</div>
<p>其中,<code class="docutils literal notranslate"><span class="pre">malloc</span></code> 的作用是从堆中分配一块大小为 <code class="docutils literal notranslate"><span class="pre">size</span></code> 字节的空间,并返回一个指向它的指针。而后续不用的时候,将这个
指针传给 <code class="docutils literal notranslate"><span class="pre">free</span></code> 即可在堆中回收这块空间。我们通过返回的指针变量来间接访问堆上的空间,而无法直接进行
访问。事实上,我们在程序中能够 <em>直接</em> 看到的变量都是被静态分配在栈或者全局数据段上的,它们大小在编译期已知,比如这里
一个指针类型的大小就等于平台的位宽。这样的它们却可以作为背后一块大小在编译期无法确定的空间的代表,这是一件非常有趣的
事情。</p>
<p>除了可以灵活利用内存之外,动态分配还允许我们以尽可能小的代价灵活调整变量的生命周期。一个局部变量被静态分配在它所在函数
的栈帧中,一旦函数返回,这个局部变量的生命周期也就结束了;而静态分配在数据段中的全局变量则是在应用的整个运行期间均存在。
动态分配允许我们构造另一种并不一直存在也不绑定于函数调用的变量生命周期:以 C 语言为例,可以说自 <code class="docutils literal notranslate"><span class="pre">malloc</span></code> 拿到指向
一个变量的指针到 <code class="docutils literal notranslate"><span class="pre">free</span></code> 将它回收之前的这段时间,这个变量在堆上存在。由于需要跨越函数调用,我们需要作为堆上数据代表
的变量在函数间以参数或返回值的形式进行传递,而这些变量一般都很小(如一个指针),其拷贝开销可以忽略。</p>
<p>而动态内存分配的缺点在于:它背后运行着连续内存分配算法,它相比静态分配会带来一些额外的开销。如果动态分配非常频繁,
它甚至可能会成为应用的性能瓶颈。</p>
</div>
<div class="section" id="id2">
<h2>Rust 中的堆数据结构<a class="headerlink" href="#id2" title="永久链接至标题"></a></h2>
<p>Rust 的标准库中提供了很多开箱即用的堆数据结构,利用它们能够大大提升我们的开发效率。</p>
<p id="term-smart-pointer">首先是一类 <strong>智能指针</strong> (Smart Pointer) 。智能指针和 Rust 中的其他两类指针也即裸指针 <code class="docutils literal notranslate"><span class="pre">*const</span> <span class="pre">T/*mut</span> <span class="pre">T</span></code>
以及引用 <code class="docutils literal notranslate"><span class="pre">&amp;T/&amp;mut</span> <span class="pre">T</span></code> 一样,都指向地址空间中的另一个区域并包含它的位置信息。但不同在于,它们携带的信息数量不等,
需要经过编译器不同等级的安全检查,可靠性和灵活程度也不同。</p>
<ul class="simple" id="term-borrow-check">
<li><p>裸指针 <code class="docutils literal notranslate"><span class="pre">*const</span> <span class="pre">T/*mut</span> <span class="pre">T</span></code> 基本等价于 C/C++ 里面的普通指针 <code class="docutils literal notranslate"><span class="pre">T*</span></code> ,它自身的内容仅仅是一个地址。它最为灵活,
但是也最不安全。编译器只能对它进行最基本的可变性检查, <a class="reference internal" href="../chapter1/4load-manually.html#term-raw-pointer"><span class="std std-ref">第一章</span></a> 曾经提到,对于裸指针
解引用访问它指向的那块数据是 unsafe 行为,需要被包裹在 unsafe 块中。</p></li>
<li><p>引用 <code class="docutils literal notranslate"><span class="pre">&amp;T/&amp;mut</span> <span class="pre">T</span></code> 自身的内容也仅仅是一个地址,但是 Rust 编译器会在编译的时候进行比较严格的 <strong>借用检查</strong>
(Borrow Check) ,要求引用的生命周期必须在被借用的变量的生命周期之内,同时可变借用和不可变借用不能共存,一个
变量可以同时存在多个不可变借用,而可变借用同时最多只能存在一个。这能在编译期就解决掉很多内存不安全问题。</p></li>
<li><p>智能指针不仅包含它指向的区域的地址,还含有一些额外的信息,因此这个类型的字节大小大于平台的位宽,属于一种胖指针。
从用途上看,它不仅可以作为一个媒介来访问它指向的数据,还能在这个过程中起到一些管理和控制的功能。</p></li>
</ul>
<p>在 Rust 中,与动态内存分配相关的智能指针有如下这些:</p>
<ul>
<li><p><code class="docutils literal notranslate"><span class="pre">Box&lt;T&gt;</span></code> 在创建时会在堆上分配一个类型为 <code class="docutils literal notranslate"><span class="pre">T</span></code> 的变量,它自身也只保存在堆上的那个变量的位置。而和裸指针或引用
不同的是,当 <code class="docutils literal notranslate"><span class="pre">Box&lt;T&gt;</span></code> 被回收的时候,它指向的——也就是在堆上被动态分配的那个变量也会被回收。</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">Rc&lt;T&gt;</span></code> 是一个单线程上使用的引用计数类型, <code class="docutils literal notranslate"><span class="pre">Arc&lt;T&gt;</span></code> 与其功能相同,只是它可以在多线程上使用。它提供了
多所有权,也即地址空间中同时可以存在指向同一个堆上变量的 <code class="docutils literal notranslate"><span class="pre">Rc&lt;T&gt;</span></code> ,它们都可以拿到指向变量的不可变引用来
访问这同一个变量。而它同时也是一个引用计数,事实上在堆上的另一个位置维护了堆上这个变量目前被引用了多少次,
也就是存在多少个 <code class="docutils literal notranslate"><span class="pre">Rc&lt;T&gt;</span></code> 。这个计数会随着 <code class="docutils literal notranslate"><span class="pre">Rc&lt;T&gt;</span></code> 的创建或复制而增加,并当 <code class="docutils literal notranslate"><span class="pre">Rc&lt;T&gt;</span></code> 生命周期结束
被回收时减少。当这个计数变为零之后,这个计数变量本身以及被引用的变量都会从堆上被回收。</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">Mutex&lt;T&gt;</span></code> 是一个互斥锁,在多线程中使用,它可以保护里层被动态分配到堆上的变量同一时间只有一个线程能对它
进行操作,从而避免数据竞争,这是并发安全的问题,会在后面详细说明。同时,它能够提供
<a class="reference internal" href="../chapter2/3batch-system.html#term-interior-mutability"><span class="std std-ref">内部可变性</span></a><code class="docutils literal notranslate"><span class="pre">Mutex&lt;T&gt;</span></code> 时常和 <code class="docutils literal notranslate"><span class="pre">Arc&lt;T&gt;</span></code> 配套使用,因为它是用来
保护多个线程可能同时访问的数据,其前提就是多个线程都拿到指向同一块堆上数据的 <code class="docutils literal notranslate"><span class="pre">Mutex&lt;T&gt;</span></code> 。于是,要么就是
这个 <code class="docutils literal notranslate"><span class="pre">Mutex&lt;T&gt;</span></code> 作为全局变量被分配到数据段上,要么就是我们需要将 <code class="docutils literal notranslate"><span class="pre">Mutex&lt;T&gt;</span></code> 包裹上一层多所有权变成
<code class="docutils literal notranslate"><span class="pre">Arc&lt;Mutex&lt;T&gt;&gt;</span></code> ,让它可以在线程间进行传递。请记住 <code class="docutils literal notranslate"><span class="pre">Arc&lt;Mutex&lt;T&gt;&gt;</span></code> 这个经典组合,我们后面会经常用到。</p>
<p>之前我们通过 <code class="docutils literal notranslate"><span class="pre">RefCell&lt;T&gt;</span></code> 来获得内部可变性。可以将 <code class="docutils literal notranslate"><span class="pre">Mutex&lt;T&gt;</span></code> 看成 <code class="docutils literal notranslate"><span class="pre">RefCell&lt;T&gt;</span></code> 的多线程版本,
因为 <code class="docutils literal notranslate"><span class="pre">RefCell&lt;T&gt;</span></code> 是只能在单线程上使用的。而且 <code class="docutils literal notranslate"><span class="pre">RefCell&lt;T&gt;</span></code> 并不会在堆上分配内存,它仅用到静态内存
分配。</p>
</li>
</ul>
<p>这和 C++ 很像, <code class="docutils literal notranslate"><span class="pre">Box&lt;T&gt;</span></code> 可以对标 C++ 的 <code class="docutils literal notranslate"><span class="pre">std::unique_ptr</span></code> ;而 <code class="docutils literal notranslate"><span class="pre">Arc&lt;T&gt;</span></code> 则类似于 C++ 的
<code class="docutils literal notranslate"><span class="pre">std::shared_ptr</span></code></p>
<p id="term-container"><span id="term-collection"></span>随后,是一些 <strong>集合</strong> (Collection) 或称 <strong>容器</strong> (Container) 类型,它们负责管理一组数目可变的元素,这些元素
的类型相同或是有着一些同样的特征。在 C++/Python/Java 等高级语言中我们已经对它们的使用方法非常熟悉了,对于
Rust 而言,我们则可以直接使用以下容器:</p>
<ul class="simple">
<li><p>向量 <code class="docutils literal notranslate"><span class="pre">Vec&lt;T&gt;</span></code> 类似于 C++ 中的 <code class="docutils literal notranslate"><span class="pre">std::vector</span></code></p></li>
<li><p>键值对容器 <code class="docutils literal notranslate"><span class="pre">BTreeMap&lt;K,</span> <span class="pre">V&gt;</span></code> 类似于 C++ 中的 <code class="docutils literal notranslate"><span class="pre">std::map</span></code></p></li>
<li><p>有序集合 <code class="docutils literal notranslate"><span class="pre">BTreeSet&lt;T&gt;</span></code> 类似于 C++ 中的 <code class="docutils literal notranslate"><span class="pre">std::set</span></code></p></li>
<li><p>链表 <code class="docutils literal notranslate"><span class="pre">LinkedList&lt;T&gt;</span></code> 类似于 C++ 中的 <code class="docutils literal notranslate"><span class="pre">std::list</span></code></p></li>
<li><p>双端队列 <code class="docutils literal notranslate"><span class="pre">VecDeque&lt;T&gt;</span></code> 类似于 C++ 中的 <code class="docutils literal notranslate"><span class="pre">std::deque</span></code></p></li>
Y
Yifan Wu 已提交
316
<li><p>变长字符串 <code class="docutils literal notranslate"><span class="pre">String</span></code> 类似于 C++ 中的 <code class="docutils literal notranslate"><span class="pre">std::string</span></code></p></li>
Y
Yifan Wu 已提交
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
</ul>
<p>下面是一张 Rust 智能指针/容器及其他类型的内存布局的经典图示,来自
<a class="reference external" href="https://docs.google.com/presentation/d/1q-c7UAyrUlM-eZyTo1pd8SZ0qwA_wYxmPZVOQkoDmH4/edit#slide=id.p">这里</a></p>
<img alt="../_images/rust-containers.png" src="../_images/rust-containers.png" />
<p>可以发现,在动态内存分配方面 Rust 和 C++ 很像,事实上 Rust 有意从 C++ 借鉴了这部分优秀特性。让我们先来看其他一些语言
使用动态内存的方式:</p>
<span id="term-reference-counting"></span><ul class="simple" id="term-garbage-collection">
<li><p>C 语言仅支持 <code class="docutils literal notranslate"><span class="pre">malloc/free</span></code> 这一对操作,它们必须恰好成对使用,否则就会出现错误。比如分配了之后没有回收,则会导致
内存溢出;回收之后再次 free 相同的指针,则会造成 Double-Free 问题;又如回收之后再尝试通过指针访问它指向的区域,这
属于 Use-After-Free 问题。总之,这样的内存安全问题层出不穷,毕竟人总是会犯错的。</p></li>
<li><p>Python/Java 通过 <strong>引用计数</strong> (Reference Counting) 对所有的对象进行运行时的动态管理,一套 <strong>垃圾回收</strong>
(GC, Garbage Collection) 机制会被自动定期触发,每次都会检查所有的对象,如果其引用计数为零则可以将该对象占用的内存
从堆上回收以待后续其他的对象使用。这样做完全杜绝了内存安全问题,但是性能开销则很大,而且 GC 触发的时机和每次 GC 的
耗时都是无法预测的,还使得性能不够稳定。</p></li>
</ul>
<p id="term-raii">C++ 的 <strong>资源获取即初始化</strong> (RAII, Resource Acquisition Is Initialization) 风格则致力于解决上述问题。
RAII 的含义是说,将一个使用前必须获取的资源的生命周期绑定到一个变量上。以 <code class="docutils literal notranslate"><span class="pre">Box&lt;T&gt;</span></code> 为例,在它被
创建的时候,会在堆上分配一块空间保存它指向的数据;而在 <code class="docutils literal notranslate"><span class="pre">Box&lt;T&gt;</span></code> 生命周期结束被回收的时候,堆上的那块空间也会
立即被一并回收。这也就是说,我们无需手动回收资源,它会和绑定到的变量同步由编译器自动回收,我们既不用担心忘记回收更不
可能回收多次;同时,由于我们很清楚一个变量的生命周期,则该资源何时被回收也是完全可预测的,我们也明确知道这次回收
操作的开销。在 Rust 中,不限于堆内存,将某种资源的生命周期与一个变量绑定的这种 RAII 的思想无处不见,甚至这种资源
可能只是另外一种类型的变量。</p>
</div>
<div class="section" id="id4">
<h2>在内核中支持动态内存分配<a class="headerlink" href="#id4" title="永久链接至标题"></a></h2>
Y
Yifan Wu 已提交
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
<p>上边介绍的那些与堆相关的智能指针或容器都可以在 Rust 自带的 <code class="docutils literal notranslate"><span class="pre">alloc</span></code> crate 中找到。当我们使用 Rust 标准库
<code class="docutils literal notranslate"><span class="pre">std</span></code> 的时候可以不用关心这个 crate ,因为标准库内已经已经实现了一套堆管理算法,并将 <code class="docutils literal notranslate"><span class="pre">alloc</span></code> 的内容包含在
<code class="docutils literal notranslate"><span class="pre">std</span></code> 名字空间之下让开发者可以直接使用。然而我们的内核是在禁用了标准库(即 <code class="docutils literal notranslate"><span class="pre">no_std</span></code> )的裸机平台,核心库
<code class="docutils literal notranslate"><span class="pre">core</span></code> 也并没有动态内存分配的功能,这个时候就要考虑利用 <code class="docutils literal notranslate"><span class="pre">alloc</span></code> 了。</p>
<p><code class="docutils literal notranslate"><span class="pre">alloc</span></code> 需要我们提供给它一个全局的动态内存分配器,它会利用该分配器来管理堆空间,从而它提供的数据结构可以正常
工作。我们的动态内存分配器需要实现它提供的 <code class="docutils literal notranslate"><span class="pre">GlobalAlloc</span></code> Trait,这个 Trait 有两个必须实现的抽象接口:</p>
<div class="highlight-rust notranslate"><div class="highlight"><pre><span></span><span class="c1">// alloc::alloc::GlobalAlloc</span>

<span class="k">pub</span><span class="w"> </span><span class="k">unsafe</span><span class="w"> </span><span class="k">fn</span> <span class="nf">alloc</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">layout</span>: <span class="nc">Layout</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="o">*</span><span class="k">mut</span><span class="w"> </span><span class="kt">u8</span><span class="p">;</span><span class="w"></span>
<span class="k">pub</span><span class="w"> </span><span class="k">unsafe</span><span class="w"> </span><span class="k">fn</span> <span class="nf">dealloc</span><span class="p">(</span><span class="o">&amp;</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">ptr</span>: <span class="o">*</span><span class="k">mut</span><span class="w"> </span><span class="kt">u8</span><span class="p">,</span><span class="w"> </span><span class="n">layout</span>: <span class="nc">Layout</span><span class="p">);</span><span class="w"></span>
</pre></div>
</div>
<p>可以看到,它们类似 C 语言中的 <code class="docutils literal notranslate"><span class="pre">malloc/free</span></code> ,分别代表堆空间的分配和回收,也同样使用一个裸指针(也就是地址)
作为分配的返回值和回收的参数。两个接口中都有一个 <code class="docutils literal notranslate"><span class="pre">alloc::alloc::Layout</span></code> 类型的参数,
它指出了分配的需求,分为两部分,分别是所需空间的大小 <code class="docutils literal notranslate"><span class="pre">size</span></code> ,以及返回地址的对齐要求 <code class="docutils literal notranslate"><span class="pre">align</span></code> 。这个对齐要求
必须是一个 2 的幂次,单位为字节数,限制返回的地址必须是 <code class="docutils literal notranslate"><span class="pre">align</span></code> 的倍数。</p>
<div class="admonition note">
<p class="admonition-title">注解</p>
<p><strong>为何 C 语言 malloc 的时候不需要提供对齐需求?</strong></p>
<p>在 C 语言中,所有对齐要求的最大值是一个平台有关的很小的常数,消耗少量内存即可使得每一次分配都符合这个最大
的对齐要求。因此也就不需要区分不同分配的对齐要求了。而在 Rust 中,某些分配的对齐要求可能很大,就只能采用更
加复杂的方法。</p>
</div>
<p>之后,只需将我们的动态内存分配器类型实例化为一个全局变量,并使用 <code class="docutils literal notranslate"><span class="pre">#[global_allocator]</span></code> 语义项标记即可。由于该
分配器的实现比较复杂,我们这里直接使用一个已有的伙伴分配器实现。首先添加 crate 依赖:</p>
<div class="highlight-toml notranslate"><div class="highlight"><pre><span></span><span class="c1"># os/Cargo.toml</span>

<span class="n">buddy_system_allocator</span> <span class="o">=</span> <span class="s">&quot;0.6&quot;</span>
</pre></div>
</div>
<p>接着,需要引入 <code class="docutils literal notranslate"><span class="pre">alloc</span></code> 的依赖,由于它算是 Rust 内置的 crate ,我们并不是在 <code class="docutils literal notranslate"><span class="pre">Cargo.toml</span></code> 中进行引入,而是在
<code class="docutils literal notranslate"><span class="pre">main.rs</span></code> 中声明即可:</p>
<div class="highlight-rust notranslate"><div class="highlight"><pre><span></span><span class="c1">// os/src/main.rs</span>

<span class="k">extern</span><span class="w"> </span><span class="k">crate</span><span class="w"> </span><span class="n">alloc</span><span class="p">;</span><span class="w"></span>
</pre></div>
</div>
<p>然后,根据 <code class="docutils literal notranslate"><span class="pre">alloc</span></code> 留好的接口提供全局动态内存分配器:</p>
<div class="highlight-rust notranslate"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="c1">// os/src/mm/heap_allocator.rs</span>

<span class="k">use</span><span class="w"> </span><span class="n">buddy_system_allocator</span>::<span class="n">LockedHeap</span><span class="p">;</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="k">crate</span>::<span class="n">config</span>::<span class="n">KERNEL_HEAP_SIZE</span><span class="p">;</span><span class="w"></span>

<span class="cp">#[global_allocator]</span><span class="w"></span>
<span class="k">static</span><span class="w"> </span><span class="n">HEAP_ALLOCATOR</span>: <span class="nc">LockedHeap</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">LockedHeap</span>::<span class="n">empty</span><span class="p">();</span><span class="w"></span>

<span class="k">static</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">HEAP_SPACE</span>: <span class="p">[</span><span class="kt">u8</span><span class="p">;</span><span class="w"> </span><span class="n">KERNEL_HEAP_SIZE</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="n">KERNEL_HEAP_SIZE</span><span class="p">];</span><span class="w"></span>

<span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">init_heap</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="n">HEAP_ALLOCATOR</span><span class="w"></span>
<span class="w">            </span><span class="p">.</span><span class="n">lock</span><span class="p">()</span><span class="w"></span>
<span class="w">            </span><span class="p">.</span><span class="n">init</span><span class="p">(</span><span class="n">HEAP_SPACE</span><span class="p">.</span><span class="n">as_ptr</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">usize</span><span class="p">,</span><span class="w"> </span><span class="n">KERNEL_HEAP_SIZE</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</td></tr></table></div>
<ul class="simple">
<li><p>第 7 行,我们直接将 <code class="docutils literal notranslate"><span class="pre">buddy_system_allocator</span></code> 中提供的 <code class="docutils literal notranslate"><span class="pre">LockedHeap</span></code> 实例化成一个全局变量,并使用
<code class="docutils literal notranslate"><span class="pre">alloc</span></code> 要求的 <code class="docutils literal notranslate"><span class="pre">#[global_allocator]</span></code> 语义项进行标记。注意 <code class="docutils literal notranslate"><span class="pre">LockedHeap</span></code> 已经实现了 <code class="docutils literal notranslate"><span class="pre">GlobalAlloc</span></code>
要求的抽象接口了。</p></li>
<li><p>第 11 行,在使用任何 <code class="docutils literal notranslate"><span class="pre">alloc</span></code> 中提供的堆数据结构之前,我们需要先调用 <code class="docutils literal notranslate"><span class="pre">init_heap</span></code> 函数来给我们的全局分配器
一块内存用于分配。在第 9 行可以看到,这块内存是一个 <code class="docutils literal notranslate"><span class="pre">static</span> <span class="pre">mut</span></code> 且被零初始化的字节数组,位于内核的
<code class="docutils literal notranslate"><span class="pre">.bss</span></code> 段中。 <code class="docutils literal notranslate"><span class="pre">LockedHeap</span></code> 也是一个被互斥锁保护的类型,在对它任何进行任何操作之前都要先获取锁以避免其他
线程同时对它进行操作导致数据竞争。然后,调用 <code class="docutils literal notranslate"><span class="pre">init</span></code> 方法告知它能够用来分配的空间的起始地址和大小即可。</p></li>
</ul>
<p>最后,让我们尝试一下动态内存分配吧!</p>
<div class="highlight-rust notranslate"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="c1">// os/src/mm/heap_allocator.rs</span>

<span class="cp">#[allow(unused)]</span><span class="w"></span>
<span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">heap_test</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">use</span><span class="w"> </span><span class="n">alloc</span>::<span class="n">boxed</span>::<span class="nb">Box</span><span class="p">;</span><span class="w"></span>
<span class="w">    </span><span class="k">use</span><span class="w"> </span><span class="n">alloc</span>::<span class="n">vec</span>::<span class="nb">Vec</span><span class="p">;</span><span class="w"></span>
<span class="w">    </span><span class="k">extern</span><span class="w"> </span><span class="s">&quot;C&quot;</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="k">fn</span> <span class="nf">sbss</span><span class="p">();</span><span class="w"></span>
<span class="w">        </span><span class="k">fn</span> <span class="nf">ebss</span><span class="p">();</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">bss_range</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sbss</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">usize</span><span class="o">..</span><span class="n">ebss</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">usize</span><span class="p">;</span><span class="w"></span>
<span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Box</span>::<span class="n">new</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="o">*</span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="n">assert</span><span class="o">!</span><span class="p">(</span><span class="n">bss_range</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="n">a</span><span class="p">.</span><span class="n">as_ref</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="o">*</span><span class="k">const</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">usize</span><span class="p">)));</span><span class="w"></span>
<span class="w">    </span><span class="nb">drop</span><span class="p">(</span><span class="n">a</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">v</span>: <span class="nb">Vec</span><span class="o">&lt;</span><span class="kt">usize</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="mi">500</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="n">v</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">i</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="mi">500</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="n">assert_eq</span><span class="o">!</span><span class="p">(</span><span class="n">v</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="w"> </span><span class="n">i</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="n">assert</span><span class="o">!</span><span class="p">(</span><span class="n">bss_range</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="n">v</span><span class="p">.</span><span class="n">as_ptr</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">usize</span><span class="p">)));</span><span class="w"></span>
<span class="w">    </span><span class="nb">drop</span><span class="p">(</span><span class="n">v</span><span class="p">);</span><span class="w"></span>
<span class="w">    </span><span class="n">println</span><span class="o">!</span><span class="p">(</span><span class="s">&quot;heap_test passed!&quot;</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</td></tr></table></div>
<p>其中分别使用智能指针 <code class="docutils literal notranslate"><span class="pre">Box&lt;T&gt;</span></code> 和向量 <code class="docutils literal notranslate"><span class="pre">Vec&lt;T&gt;</span></code> 在堆上分配数据并管理它们,通过 <code class="docutils literal notranslate"><span class="pre">as_ref</span></code><code class="docutils literal notranslate"><span class="pre">as_ptr</span></code>
方法可以分别看到它们指向的数据的位置,能够确认它们的确在 <code class="docutils literal notranslate"><span class="pre">.bss</span></code> 段的堆上。</p>
Y
Yifan Wu 已提交
480 481 482 483 484
<div class="admonition note">
<p class="admonition-title">注解</p>
<p>本节部分内容参考自 <a class="reference external" href="https://os.phil-opp.com/heap-allocation/">BlogOS 的相关章节</a></p>
</div>
</div>
Y
Yifan Wu 已提交
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
</div>


           </div>
           
          </div>
          <footer>
  
    <div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
      
        <a href="3sv39-implementation.html" class="btn btn-neutral float-right" title="实现 SV39 多级页表机制" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right"></span></a>
      
      
        <a href="1address-space.html" class="btn btn-neutral float-left" title="地址空间" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> Previous</a>
      
    </div>
  

  <hr/>

  <div role="contentinfo">
    <p>
        
        &copy; 版权所有 2020, Yifan Wu

    </p>
  </div>
    
    
    
    Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a
    
    <a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a>
    
    provided by <a href="https://readthedocs.org">Read the Docs</a>. 

</footer>

        </div>
      </div>

    </section>

  </div>
  

  <script type="text/javascript">
      jQuery(function () {
          SphinxRtdTheme.Navigation.enable(true);
      });
  </script>

  
  
    
   

</body>
</html>