<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Brewlin&#39;s Wiki</title>
  
  <subtitle>found everthing</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://wiki.brewlin.com/"/>
  <updated>2022-06-30T08:06:37.448Z</updated>
  <id>http://wiki.brewlin.com/</id>
  
  <author>
    <name>brewlin</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>glusterfs-客户端源码分析</title>
    <link href="http://wiki.brewlin.com/wiki/middlewares/glusterfs/1.glusterfs-%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    <id>http://wiki.brewlin.com/wiki/middlewares/glusterfs/1.glusterfs-客户端源码分析/</id>
    <published>2022-06-30T13:28:59.000Z</published>
    <updated>2022-06-30T08:06:37.448Z</updated>
    
    <content type="html"><![CDATA[<p>glusterfs也属于典型的cs结构，有client端,server端，通过rpc调用通信</p><p>主要的进程有:</p><ul><li>glusterd: 管理进程，管理所有的配置、版本信息</li><li>glusterfsd: server进程，处理所有的文件处理请求并进行落盘</li><li>glusterfs|api: client端，将所有文件的相关操作代理到server端去处理，核心是根据hash算法计算出需要和哪个server端进行通信</li></ul><h1 id="一、client端架构流程图"><a href="#一、client端架构流程图" class="headerlink" title="一、client端架构流程图"></a>一、client端架构流程图</h1><p><img src="/images/middlewares/gluster/client_gluster1.png" alt="image"></p><p>从<code>sdk(client)</code>层就可以看出，所有的功能都进行了分类，并拆分为大量的so动态库实现，最后通过<code>配置</code>将so动态库串联起来组成了sdk的逻辑</p><p>上面所述的<code>配置</code>:可以用通过json、xml、yaml等等来实现，只要能表达关系即可，但在glsuter中通过yacc,lex(./libglusterfs/src/graph.l |graph.y)等词法工具自己实现了一个类似xml的配置语法:<code>volume</code></p><p>来看看上图中sdk对应的volume配置吧,这个配置是通过glusterd获取的，一般glusterd也是保存在本地配置中的（/var/lib/glusterd/vols//trusted*.vol）</p><h2 id="1-1-volume-配置语言"><a href="#1-1-volume-配置语言" class="headerlink" title="1.1 volume 配置语言"></a>1.1 volume 配置语言</h2><p>下面就是一个完整的client端volume配置文件，该文件对应的是一个3副本的配置<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">volume vol1-dr-client-0</span><br><span class="line">    type protocol/client</span><br><span class="line">    option remote-subvolume /replica/brick0</span><br><span class="line">    option remote-host 192.168.1.34</span><br><span class="line">    //省略了很多 option参数</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr-client-1</span><br><span class="line">    type protocol/client</span><br><span class="line">    option remote-subvolume /replica/brick1</span><br><span class="line">    option remote-host 192.168.1.34</span><br><span class="line">    //省略很多option参数</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr-client-2</span><br><span class="line">    type protocol/client</span><br><span class="line">    option remote-subvolume /replica/brick2</span><br><span class="line">    option remote-host 192.168.1.34</span><br><span class="line">    //省略很多option参数</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr-replicate-0</span><br><span class="line">    type cluster/replicate</span><br><span class="line">    //省略很多option参数</span><br><span class="line">    subvolumes vol1-dr-client-0 vol1-dr-client-1 vol1-dr-client-2</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr-dht</span><br><span class="line">    type cluster/distribute</span><br><span class="line">    subvolumes vol1-dr-replicate-0</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr-write-behind</span><br><span class="line">    type performance/write-behind</span><br><span class="line">    subvolumes vol1-dr-dht</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr-read-ahead</span><br><span class="line">    type performance/read-ahead</span><br><span class="line">    subvolumes vol1-dr-write-behind</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr-quick-read</span><br><span class="line">    type performance/quick-read</span><br><span class="line">    subvolumes vol1-dr-io-cache</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr-open-behind</span><br><span class="line">    type performance/open-behind</span><br><span class="line">    subvolumes vol1-dr-quick-read</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-io-threads</span><br><span class="line">    type performance/io-threads</span><br><span class="line">    subvolumes vol0-open-behind</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr</span><br><span class="line">    type debug/io-stats</span><br><span class="line">    subvolumes vol1-io-threads</span><br><span class="line">end-volume</span><br></pre></td></tr></table></figure></p><h3 id="1-1-2-关键字-volume-amp-end-volume-amp-subvolumes"><a href="#1-1-2-关键字-volume-amp-end-volume-amp-subvolumes" class="headerlink" title="1.1.2 关键字: volume&amp;end-volume&amp;subvolumes"></a>1.1.2 关键字: volume&amp;end-volume&amp;subvolumes</h3><p>整个配置语法非常简单，就是<code>以volume</code>开头以<code>end-volume</code>结尾的一段被作为一个组件，代码里对应每一个xlator</p><p>多个组件通过<code>subvolumes</code>连接起来</p><p>比如这段<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">volume vol1-io-threads</span><br><span class="line">    type performance/io-threads</span><br><span class="line">    subvolumes vol0-open-behind</span><br><span class="line">end-volume</span><br><span class="line">volume vol1-dr</span><br><span class="line">    type debug/io-stats</span><br><span class="line">    subvolumes vol1-io-threads</span><br><span class="line">end-volume</span><br></pre></td></tr></table></figure></p><p>说明 io-threads是io-stats的子节点，整个配置内容会自动被词法解析器(yacc)自动串联为一颗树: 因为subvolumes 可以对应多个子节点</p><p>但实际情况是串联起来后前面是一个链表，只有最后的叶子结点(protocol/client,cluster/replicate)会存在多个<br><img src="/images/middlewares/gluster/client_gluster2.png" alt="image"></p><h3 id="1-1-3-关键字-type-amp-option"><a href="#1-1-3-关键字-type-amp-option" class="headerlink" title="1.1.3 关键字: type&amp;option"></a>1.1.3 关键字: type&amp;option</h3><p>type关键字表明当前是一个什么组件(动态库),比如<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">type performance/io-threads</span><br></pre></td></tr></table></figure></p><p>代表这个组件是一个 performances/io-threads.so动态库，源代码是在<code>./xlators/performance/io-threads/src</code>下</p><p>不一定这个路径一定是按照源代码目录拆分的，但总体相差不大</p><p>option关键字代表的是参数，因为每个volume对应的是每个单独的动态库组件，他们全部都有统一的<code>init</code>函数，都需要初始化，所以option用于定义默认的初始化参数</p><p>例如:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">volume vol1-dr-client-0</span><br><span class="line">    type protocol/client</span><br><span class="line">    option remote-subvolume /replica/brick0</span><br><span class="line">    option remote-host 192.168.1.34</span><br><span class="line">    //省略了很多 option参数</span><br><span class="line">end-volume</span><br></pre></td></tr></table></figure></p><p>代表<code>protocol/client.so</code>动态库需要和server端连接的host为<code>192.168.1.34</code>,需要连接的brick是<code>/replica/brick0</code></p><p>当client.so初始化的时候会经过如下流程</p><ol><li>向glusterd(默认24007端口)查询(/replica/brick0)的配置（服务端口信息）</li><li>拿到brick的端口后，开始建立tcp连接</li><li>后续的读写都通过brick(glusterfsd)进行rpc通信</li></ol><h2 id="1-2-client端功能设计"><a href="#1-2-client端功能设计" class="headerlink" title="1.2 client端功能设计"></a>1.2 client端功能设计</h2><p>整个client的功能完全依赖上面的volume配置，如上图的流程图可得客户端的io流程如下<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">io-stats.so -&gt; io-thread.so -&gt; open-behind.so -&gt; quick-read.so -&gt; read-ahead.so </span><br><span class="line">                                                                                |</span><br><span class="line">                                                                                V</span><br><span class="line">&#123; client.so client.so client.so&#125; &lt;- distribute.so &lt;- write-behind.so &lt;-</span><br></pre></td></tr></table></figure></p><p>整个io流程还是比较长的，但非常灵活和清晰，你可以很方便的自定义插件插入到整个流程中，或者去除某些节点，只需要更改上面的volume配置即可</p><p><strong>整体设计和nginx的模块化思想差不多，nginx的所有模块都保存在一个数组中，所以io顺序是和数组的索引顺序有关，而gluster的所有模块是根据配置串联为一个树，就这里不同</strong></p><h3 id="1-2-1-volume配置获取"><a href="#1-2-1-volume配置获取" class="headerlink" title="1.2.1 volume配置获取"></a>1.2.1 volume配置获取</h3><p>glusterd服务核心是作为配置管理的服务，当client需要挂载brick时需要通过glusterd获取volume配置，<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/api/src/glfs.c</span><br><span class="line">int glfs_volumes_init (struct glfs *fs)</span><br><span class="line">&#123;  </span><br><span class="line">    //省略</span><br><span class="line">if (cmd_args-&gt;volfile_server) &#123;</span><br><span class="line">                //去glusterd获取卷信息</span><br><span class="line">ret = glfs_mgmt_init (fs);</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line">    //已经知道卷信息了，直接打开</span><br><span class="line">fp = get_volfp (fs);</span><br><span class="line">//..省略</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><ol><li>gfapi.so： sdk初始化的时候，你可以指定volume配置，也可以走默认的通过glusterd获取卷信息</li><li>手动提供volume配置就是上面将的那个demo，接下来看看通过glusterd获取的情况</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/api/src/glfs-mgmt.c  初始化</span><br><span class="line"></span><br><span class="line">int glfs_mgmt_init (struct glfs *fs)</span><br><span class="line">&#123;</span><br><span class="line">    //...省略参数初始化</span><br><span class="line">    </span><br><span class="line">    //创建rpc客户端，基本上所有的cli rpc客户端都是一样的逻辑，</span><br><span class="line">    //1. socket.so -&gt; connect</span><br><span class="line">    //2. request glusterd 查询  volume 信息</span><br><span class="line">rpc = rpc_clnt_new (options, THIS, THIS-&gt;name, 8);</span><br><span class="line">if (!rpc) &#123;</span><br><span class="line">ret = -1;</span><br><span class="line">gf_msg (THIS-&gt;name, GF_LOG_WARNING, 0,</span><br><span class="line">                        API_MSG_CREATE_RPC_CLIENT_FAILED,</span><br><span class="line">&quot;failed to create rpc clnt&quot;);</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line">    //注册回调函数，然后，socket.so::event_dispatch_handler会一层一层的传送到这里</span><br><span class="line">ret = rpc_clnt_register_notify (rpc, mgmt_rpc_notify, THIS);</span><br><span class="line">if (ret) &#123;</span><br><span class="line">gf_msg (THIS-&gt;name, GF_LOG_WARNING, 0,</span><br><span class="line">                        API_MSG_REG_NOTIFY_FUNC_FAILED,</span><br><span class="line">&quot;failed to register notify function&quot;);</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line">    //...省略一些默认逻辑</span><br><span class="line">    </span><br><span class="line">    //开始connect tcp glsterd</span><br><span class="line">ret = rpc_clnt_start (rpc);</span><br><span class="line">out:</span><br><span class="line">return ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>和glusterd的tcp连接成功后，会在单独的event线程里通过<code>epoll_wait</code>拿到事件后回调<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">static int</span><br><span class="line">mgmt_rpc_notify (struct rpc_clnt *rpc, void *mydata, rpc_clnt_event_t event,</span><br><span class="line"> void *data)</span><br><span class="line">&#123;</span><br><span class="line">    //省略无关核心内容</span><br><span class="line"></span><br><span class="line">switch (event) &#123;</span><br><span class="line">case RPC_CLNT_DISCONNECT: //断开连接</span><br><span class="line">        //省略。。        </span><br><span class="line">             </span><br><span class="line">case RPC_CLNT_CONNECT: //连接成功了</span><br><span class="line">rpc_clnt_set_connected (&amp;((struct rpc_clnt*)ctx-&gt;mgmt)-&gt;conn);</span><br><span class="line">        //向glusterd 发起rpc请求去 查 volfile 信息，在以前的老版本都是自己本地配volfile文件的</span><br><span class="line">        //现在的版本都是统一向glusterd去拿</span><br><span class="line">ret = glfs_volfile_fetch (fs);</span><br><span class="line">if (ret &amp;&amp; (ctx-&gt;active == NULL)) &#123;</span><br><span class="line">                break;</span><br><span class="line">default:</span><br><span class="line">break;</span><br><span class="line">&#125;</span><br><span class="line">out:</span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>通过glfs_volfile_fetch向glusterd发送rpc查询volume配置</p><h3 id="1-2-2-volume配置解析"><a href="#1-2-2-volume配置解析" class="headerlink" title="1.2.2 volume配置解析"></a>1.2.2 volume配置解析</h3><p>sdk（client)的功能取决于volume的配置组合，接下来看看如何初始化的</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line">int</span><br><span class="line">glfs_process_volfp (struct glfs *fs, FILE *fp)</span><br><span class="line">&#123;</span><br><span class="line">glusterfs_graph_t  *graph = NULL;</span><br><span class="line">int    ret = -1;</span><br><span class="line">xlator_t   *trav = NULL;</span><br><span class="line">glusterfs_ctx_t   *ctx = NULL;</span><br><span class="line"></span><br><span class="line">ctx = fs-&gt;ctx;</span><br><span class="line">graph = glusterfs_graph_construct (fp); //通过yacc，lex 词法工具自动解析内容生成一颗树</span><br><span class="line">if (!graph) &#123;</span><br><span class="line">gf_msg (&quot;glfs&quot;, GF_LOG_ERROR, errno,</span><br><span class="line">                        API_MSG_GRAPH_CONSTRUCT_FAILED,</span><br><span class="line">                        &quot;failed to construct the graph&quot;);</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">for (trav = graph-&gt;first; trav; trav = trav-&gt;next) &#123;</span><br><span class="line">if (strcmp (trav-&gt;type, &quot;mount/api&quot;) == 0) &#123;</span><br><span class="line">gf_msg (&quot;glfs&quot;, GF_LOG_ERROR, EINVAL,</span><br><span class="line">                                API_MSG_API_XLATOR_ERROR,</span><br><span class="line">&quot;api master xlator cannot be specified &quot;</span><br><span class="line">&quot;in volume file&quot;);</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ret = glusterfs_graph_prepare (graph, ctx, fs-&gt;volname);</span><br><span class="line">if (ret) &#123;</span><br><span class="line">glusterfs_graph_destroy (graph);</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ret = glusterfs_graph_activate (graph, ctx);</span><br><span class="line"></span><br><span class="line">if (ret) &#123;</span><br><span class="line">glusterfs_graph_destroy (graph);</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">        gf_log_dump_graph (fp, graph);</span><br><span class="line"></span><br><span class="line">ret = 0;</span><br><span class="line">out:</span><br><span class="line">if (fp)</span><br><span class="line">fclose (fp);</span><br><span class="line"></span><br><span class="line">if (!ctx-&gt;active) &#123;</span><br><span class="line">ret = -1;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">return ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>总的来说是通过<code>glusterfs_graph_construct</code>yacc生成的词法解析函数，直接帮你生成了一棵树<code>graph</code><br>在这个过程中，就会为每个xaltor去寻找对应的动态库并加载到内存中绑定对应的对方法和变量<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">int xlator_dynload (xlator_t *xl)</span><br><span class="line">&#123;</span><br><span class="line">        //设置动态库所在的路径</span><br><span class="line">        //比如type protocol/client来说</span><br><span class="line">        //name = &quot;/lib64/glusterfs/3.12.15/xlator&quot; + &quot;protocol/client.so&quot;</span><br><span class="line">        //加载动态库</span><br><span class="line">        handle = dlopen (name, RTLD_NOW|RTLD_GLOBAL);</span><br><span class="line">        if (!handle) &#123;</span><br><span class="line">                goto out;</span><br><span class="line">        &#125;</span><br><span class="line">        xl-&gt;dlhandle = handle;</span><br><span class="line">        //获取fops这个符号的地址，可能是函数也可能是全局变量</span><br><span class="line">        if (!(xl-&gt;fops = dlsym (handle, &quot;fops&quot;))) &#123;</span><br><span class="line">                goto out;</span><br><span class="line">        &#125;</span><br><span class="line">        //获取cbks回调函数</span><br><span class="line">        if (!(xl-&gt;cbks = dlsym (handle, &quot;cbks&quot;))) &#123;</span><br><span class="line">                goto out;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        //class_methods是个变量，所以可以直接加载对应的成员实现</span><br><span class="line">        vtbl = dlsym(handle,&quot;class_methods&quot;);</span><br><span class="line">        if (vtbl) &#123;</span><br><span class="line">                xl-&gt;init        = vtbl-&gt;init;</span><br><span class="line">                xl-&gt;fini        = vtbl-&gt;fini;</span><br><span class="line">                xl-&gt;reconfigure = vtbl-&gt;reconfigure;</span><br><span class="line">                xl-&gt;notify      = vtbl-&gt;notify;</span><br><span class="line">        &#125;</span><br><span class="line">        else &#123;</span><br><span class="line">            //忽略初始化init不走</span><br><span class="line">        &#125;</span><br><span class="line">        //忽略非重要逻辑</span><br><span class="line"></span><br><span class="line">        //这里比较重要，填充默认函数，设置默认的fop回调函数，fop都是相关文件的操作 create,open,mkdir.touch ....等等</span><br><span class="line">        fill_defaults (xl);</span><br><span class="line"></span><br><span class="line">        return ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>接下来就是挨个遍历整个树，然后初始化各个xlator<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">int glusterfs_graph_activate (glusterfs_graph_t *graph, glusterfs_ctx_t *ctx)</span><br><span class="line">&#123;</span><br><span class="line">        //忽略参数初始化</span><br><span class="line"></span><br><span class="line">        /* XXX: perform init () */</span><br><span class="line">        //调用每个xlator.init() 初始化</span><br><span class="line">        ret = glusterfs_graph_init (graph);</span><br><span class="line">        if (ret) &#123;</span><br><span class="line">                return ret;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        //忽略无关逻辑。。。</span><br><span class="line">        </span><br><span class="line">        //通知父节点，也就是 mount/api.so 所有的xlator已经初始化完毕</span><br><span class="line">        ret = glusterfs_graph_parent_up (graph);</span><br><span class="line">        if (ret) &#123;</span><br><span class="line">                return ret;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>挨个初始化所有的xaltor<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">int</span><br><span class="line">glusterfs_graph_init (glusterfs_graph_t *graph)</span><br><span class="line">&#123;</span><br><span class="line">        xlator_t           *trav = NULL;</span><br><span class="line">        int                 ret = -1;</span><br><span class="line"></span><br><span class="line">        trav = graph-&gt;first;</span><br><span class="line">        //对每个xlator进行初始化</span><br><span class="line">        while (trav) &#123;</span><br><span class="line">                ret = xlator_init (trav);</span><br><span class="line">                if (ret) &#123;</span><br><span class="line">                        return ret;</span><br><span class="line">                &#125;</span><br><span class="line">                trav = trav-&gt;next;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>最后就是依次调用每个xlator.init()方法<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">int</span><br><span class="line">xlator_init (xlator_t *xl)</span><br><span class="line">&#123;</span><br><span class="line">        int32_t ret = -1;</span><br><span class="line"></span><br><span class="line">        GF_VALIDATE_OR_GOTO (&quot;xlator&quot;, xl, out);</span><br><span class="line"></span><br><span class="line">        if (xl-&gt;mem_acct_init)</span><br><span class="line">                xl-&gt;mem_acct_init (xl);</span><br><span class="line"></span><br><span class="line">        xl-&gt;instance_name = NULL;</span><br><span class="line">        ret = __xlator_init (xl);</span><br><span class="line">        //忽略。。</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h1 id="二、xlator"><a href="#二、xlator" class="headerlink" title="二、xlator"></a>二、xlator</h1><p>gluster将大量的功能进行模块化，每个模块都是单独的动态库，并通过配置将相关模块关联起来提功完整的功能，和这种架构设计相同的还有熟知的nginx，nginx也是想相关功能都拆分为单独的模块，然后最后将相关模块存放到一个数组中，每个http请求过来都会一次顺序的变量整个数组，层层将处理传递个所有的模块</p><p>gluster也是这样，相比nginx，gluster的模块关联要智能一些，它自定义了一种配置语言<code>volume</code>，可以方便的将所有模块关联为一个树，整颗树的的每个节点都能决定改继续传递处理给子节点还是直接终止</p><p>虽说整个关联关系是一颗树，但不是一颗完全平衡的多叉树，更像是一个链表<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">                                         -&gt; replicate -&gt; client</span><br><span class="line">                                         |</span><br><span class="line">io-stats -&gt;  io-thread -&gt; ...-&gt; distribute</span><br><span class="line">                                         |</span><br><span class="line">                                         -&gt; replicate -&gt; client</span><br></pre></td></tr></table></figure></p><p>每个文件的读写流程依次通过各个xlator传递下去，最终到达client节点，通过rpc和glusterfsd（server)提交操作</p><h2 id="2-1-xlator实现"><a href="#2-1-xlator实现" class="headerlink" title="2.1 xlator实现"></a>2.1 xlator实现</h2><p>接下来分析下xlator的结构<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> _<span class="title">xlator</span> &#123;</span></span><br><span class="line">        <span class="comment">/* Built during parsing */</span></span><br><span class="line">        <span class="keyword">char</span>          *name;</span><br><span class="line">        <span class="keyword">char</span>          *type;</span><br><span class="line">        <span class="keyword">char</span>          *instance_name;  <span class="comment">/* Used for multi NFSd */</span></span><br><span class="line">        <span class="keyword">xlator_t</span>      *next; <span class="comment">//下一个节点</span></span><br><span class="line">        <span class="keyword">xlator_t</span>      *prev; <span class="comment">//上一个节点</span></span><br><span class="line">        <span class="keyword">xlator_list_t</span> *parents; <span class="comment">//父节点</span></span><br><span class="line">        <span class="keyword">xlator_list_t</span> *children; <span class="comment">//孩子节点</span></span><br><span class="line">        <span class="keyword">dict_t</span>        *options; <span class="comment">//配置</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">/* Set after doing dlopen() */</span></span><br><span class="line">        <span class="keyword">void</span>                  *dlhandle; <span class="comment">//动态库</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">xlator_fops</span>    *<span class="title">fops</span>;</span> <span class="comment">//成员函数</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">xlator_cbks</span>    *<span class="title">cbks</span>;</span> <span class="comment">//成员回调</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">xlator_dumpops</span> *<span class="title">dumpops</span>;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">list_head</span>       <span class="title">volume_options</span>;</span>  <span class="comment">/* list of volume_option_t */</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">void</span>              (*fini) (<span class="keyword">xlator_t</span> *<span class="keyword">this</span>);</span><br><span class="line">        <span class="keyword">int32_t</span>           (*init) (<span class="keyword">xlator_t</span> *<span class="keyword">this</span>);</span><br><span class="line">        <span class="keyword">int32_t</span>           (*reconfigure) (<span class="keyword">xlator_t</span> *<span class="keyword">this</span>, <span class="keyword">dict_t</span> *options);</span><br><span class="line">    <span class="keyword">int32_t</span>           (*mem_acct_init) (<span class="keyword">xlator_t</span> *<span class="keyword">this</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">void</span>               *<span class="keyword">private</span>;</span><br><span class="line">        <span class="comment">//忽略非xlator本身字段</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><p>核心的就是dlhandle,fops,cbks,private</p><ul><li>dlhandle: 动态库的句柄</li><li>fops: 动态库里fops变量的地址</li><li>cbks: 动态库里cbks</li><li>private: 当前xlator的私有配置</li></ul><p>总的来说看起来就像是一个对象类型语言中的class结构，将动态库里的函数绑定到xlator的函数指针成员中，动态绑定，在volume配置通过yacc词法解析器解析的时候就会加载对应的动态库并绑定对应的函数或者成员变量到当前xlator(this)成员中</p><p>后续调用当前xlator对应方法如:<code>xlator-&gt;fops-&gt;write(...)</code>实际调用的是对应动态库fops中的成员函数</p><h3 id="2-1-1-xlator兼容性"><a href="#2-1-1-xlator兼容性" class="headerlink" title="2.1.1 xlator兼容性"></a>2.1.1 xlator兼容性</h3><p>来看看标准的一个动态库基本结构,以<code>protocol/client.so</code>为例<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//./xlators/protocol/client/src/client.c</span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">init</span> <span class="params">(<span class="keyword">xlator_t</span> *<span class="keyword">this</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">xlator_cbks</span> <span class="title">cbks</span> = &#123;</span></span><br><span class="line">        .forget     = client_forget,</span><br><span class="line">        .release    = client_release,</span><br><span class="line">        .releasedir = client_releasedir</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">xlator_fops</span> <span class="title">fops</span> = &#123;</span></span><br><span class="line">        .stat        = client_stat,</span><br><span class="line">        .readlink    = client_readlink,</span><br><span class="line">        .mknod       = client_mknod,</span><br><span class="line">        .mkdir       = client_mkdir,</span><br><span class="line">        .unlink      = client_unlink,</span><br><span class="line">        .rmdir       = client_rmdir,</span><br><span class="line">        .symlink     = client_symlink,</span><br><span class="line">        .rename      = client_rename,</span><br><span class="line">        .link        = client_link,</span><br><span class="line">        .truncate    = client_truncate,</span><br><span class="line">        .open        = client_open,</span><br><span class="line">        .readv       = client_readv,</span><br><span class="line">        .writev      = client_writev,</span><br><span class="line">        <span class="comment">//...太多了省略一些</span></span><br><span class="line">  </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">xlator_dumpops</span> <span class="title">dumpops</span> = &#123;</span></span><br><span class="line">        .priv      =  client_priv_dump,</span><br><span class="line">        .inodectx  =  client_inodectx_dump,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></p><p>每个xlator都基本要有这些函数和变量，最重要的就是<code>fops</code>变量，里面保存了大量的函数指针，指向了大量的文件接口，如果当前xlator不处理相关操作可以不实现对应的方法，在xlator初始化的时候会为他填充一个默认的方法，默认的方法啥也不做，就是将当前操作继续代理给下一个子类节点<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">int32_t</span><br><span class="line">default_mkdir (</span><br><span class="line">call_frame_t *frame,</span><br><span class="line">xlator_t *this,</span><br><span class="line">loc_t * loc,</span><br><span class="line">mode_t mode,</span><br><span class="line">mode_t umask,</span><br><span class="line">dict_t * xdata)</span><br><span class="line">&#123;</span><br><span class="line">    //啥也不干，继续传递给下一个节点</span><br><span class="line">STACK_WIND_TAIL (frame,</span><br><span class="line"> FIRST_CHILD(this), FIRST_CHILD(this)-&gt;fops-&gt;mkdir,</span><br><span class="line"> loc, mode, umask, xdata);</span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="2-2-核心xlator"><a href="#2-2-核心xlator" class="headerlink" title="2.2 核心xlator"></a>2.2 核心xlator</h2><p>接下来分析一下重要的xlator实现，这里只做一个简介，会有专门章节去细细分析每个xlator的具体实现</p><p>接下来就按着处理流程顺序把重要的xlator概述一下</p><h3 id="2-2-1-io-threads-线程池"><a href="#2-2-1-io-threads-线程池" class="headerlink" title="2.2.1 io-threads: 线程池"></a>2.2.1 io-threads: 线程池</h3><p>每个glfs客户端都会默认初始化一个线程池，最小线程为1个，所有读写请求都会丢到线程队列里等待多线程消费<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">#define IOT_MIN_THREADS         1</span><br><span class="line">#define IOT_DEFAULT_THREADS     16</span><br><span class="line">#define IOT_MAX_THREADS         64</span><br></pre></td></tr></table></figure></p><h3 id="2-2-2-read-ahead-预读"><a href="#2-2-2-read-ahead-预读" class="headerlink" title="2.2.2 read-ahead: 预读"></a>2.2.2 read-ahead: 预读</h3><p>预先读取下一块顺序的内存，提高读取效率,将大量的、零散的读取操作集合成少量的、大一些的读操作，这样，减小了网络和磁盘的负载。page-size 描述了块的大小。page-count 描述了预读块的总数量</p><h3 id="2-2-3-write-behind-合并"><a href="#2-2-3-write-behind-合并" class="headerlink" title="2.2.3 write-behind: 合并"></a>2.2.3 write-behind: 合并</h3><p>就是一个aggregator器，会将大量小的写操作合并起来组合成一个更大的写操作后统一提交到glusterfsd 服务端进行落盘，优化并发写操作</p><h3 id="2-2-4-distribute-分布式hash"><a href="#2-2-4-distribute-分布式hash" class="headerlink" title="2.2.4 distribute: 分布式hash"></a>2.2.4 distribute: 分布式hash</h3><p>所有分布式的逻辑靠这个模块实现，也就是分布式的逻辑是靠客户端实现，当前模块会根据文件名进行hash然后进行匹配落到某一个子卷，后续的操作都在该子卷上操作</p><ol><li>子卷是一个副本，那么会将后续的读写请求提交到这个副本模块(replicate)</li><li>子卷是一个brick，那么会将后续的读写请求提交到client模块</li></ol><h3 id="2-2-5-replicate-副本"><a href="#2-2-5-replicate-副本" class="headerlink" title="2.2.5 replicate: 副本"></a>2.2.5 replicate: 副本</h3><p>这里的逻辑也相对简单，会遍历子节点,子节点必定是<code>protocol/client.so</code>模块，依次将对应的操作提交到子节点去</p><h3 id="2-2-6-client-最后一个模块"><a href="#2-2-6-client-最后一个模块" class="headerlink" title="2.2.6 client: 最后一个模块"></a>2.2.6 client: 最后一个模块</h3><p>最后的节点就是一个rpc客户端，和对应的brick（glusterd)保持长链接，将第一</p><h1 id="三、一次write的生命周期"><a href="#三、一次write的生命周期" class="headerlink" title="三、一次write的生命周期"></a>三、一次write的生命周期</h1><p><img src="/images/middlewares/gluster/client_gluster3.png" alt="image"></p><p>其实一次write调用主要分为两个大的操作</p><ul><li><ol><li>lookup: 查找文件是否存在，并基于hash初始化对应文件所在的卷等一些环境初始化</li></ol></li><li><ol start="2"><li>write: 实现文件write</li></ol></li></ul><p>两种操作都是基于xlator整颗树层层传递下去，这里只分析write的过程，两种过程的逻辑差不多</p><h2 id="3-1-gfapi-so-sdk入口"><a href="#3-1-gfapi-so-sdk入口" class="headerlink" title="3.1 gfapi.so: sdk入口"></a>3.1 gfapi.so: sdk入口</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/api/src/glfs-fops.c</span><br><span class="line">ssize_t pub_glfs_write (struct glfs_fd *glfd, const void *buf, size_t count, int flags)</span><br><span class="line">&#123;</span><br><span class="line">printf(&quot;mount/api.so glfs_write: \n&quot;);</span><br><span class="line">struct iovec iov = &#123;0, &#125;;</span><br><span class="line">ssize_t      ret = 0;</span><br><span class="line"></span><br><span class="line">iov.iov_base = (void *) buf;</span><br><span class="line">iov.iov_len = count;</span><br><span class="line"></span><br><span class="line">ret = pub_glfs_pwritev (glfd, &amp;iov, 1, glfd-&gt;offset, flags);</span><br><span class="line"></span><br><span class="line">return ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>拿到xlator的root节点，也就是 meta_autoload.so,这个动态库啥也没干，就是个代理<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">ssize_t</span><br><span class="line">pub_glfs_pwritev (struct glfs_fd *glfd, const struct iovec *iovec, int iovcnt,</span><br><span class="line">                  off_t offset, int flags)</span><br><span class="line">&#123;</span><br><span class="line">    //..忽略参数初始化</span><br><span class="line">//meta_autoload xlator</span><br><span class="line">subvol = glfs_active_subvol (glfd-&gt;fs);</span><br><span class="line">if (!subvol) &#123;</span><br><span class="line">ret = -1;</span><br><span class="line">errno = EIO;</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line">    //这里比较重要，是一个分支，用于解析这个文件是否存在，并初始化一些环境</span><br><span class="line">fd = glfs_resolve_fd (glfd-&gt;fs, subvol, glfd);</span><br><span class="line">if (!fd) &#123;</span><br><span class="line">ret = -1;</span><br><span class="line">errno = EBADFD;</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line">    //准备数据</span><br><span class="line">    ret = glfs_buf_copy (subvol, iovec, iovcnt, &amp;iobref, &amp;iobuf, &amp;iov);</span><br><span class="line">    if (ret)</span><br><span class="line">            goto out;</span><br><span class="line">    //开始准备通过xlator树传递操作</span><br><span class="line">ret = syncop_writev (subvol, fd, &amp;iov, 1, offset, iobref, flags, NULL,</span><br><span class="line">                             NULL);</span><br><span class="line">        DECODE_SYNCOP_ERR (ret);</span><br><span class="line">    //省略一些收尾操作</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h3 id="3-1-1-将write操作通过xlator树传递下去"><a href="#3-1-1-将write操作通过xlator树传递下去" class="headerlink" title="3.1.1 将write操作通过xlator树传递下去"></a>3.1.1 将write操作通过xlator树传递下去</h3><p>开始将请求通过xlator这颗树进行传递<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">int</span><br><span class="line">syncop_writev (xlator_t *subvol, fd_t *fd, const struct iovec *vector,</span><br><span class="line">               int32_t count, off_t offset, struct iobref *iobref,</span><br><span class="line">               uint32_t flags, dict_t *xdata_in, dict_t **xdata_out)</span><br><span class="line">&#123;</span><br><span class="line">        struct syncargs args = &#123;0, &#125;;</span><br><span class="line"></span><br><span class="line">        //开始写入数据</span><br><span class="line">        //subvol root(meta-autoload) -&gt; vol2 -&gt; .... vol2client </span><br><span class="line">        //args参数</span><br><span class="line">        //syncop_writev_cbk 回调参数</span><br><span class="line"></span><br><span class="line">        //其实就是执行 subvol-&gt;fops-&gt;writev()函数</span><br><span class="line">        SYNCOP (subvol, (&amp;args), syncop_writev_cbk, subvol-&gt;fops-&gt;writev,</span><br><span class="line">                fd, (struct iovec *) vector, count, offset, flags, iobref,</span><br><span class="line">                xdata_in);</span><br><span class="line"></span><br><span class="line">        //省略参数收尾</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>subvol是meta_autoload.so,接下来就要调用meta_autoload.fops.writev（） 函数,也就是<code>meta_writev</code>函数</p><h3 id="3-1-2-阻塞等待整颗树处理完"><a href="#3-1-2-阻塞等待整颗树处理完" class="headerlink" title="3.1.2 阻塞等待整颗树处理完"></a>3.1.2 阻塞等待整颗树处理完</h3><p>上文用了宏<code>SYNCOP</code>进行xlaotr函数调用，这个会阻塞等待子节点处理完毕在返回<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">#define SYNCOP(subvol, stb, cbk, op, params ...) do &#123;                   \</span><br><span class="line">                struct  synctask        *task = NULL;                   \</span><br><span class="line">                call_frame_t            *frame = NULL;                  \</span><br><span class="line">                                                                        \</span><br><span class="line">                task = synctask_get ();                                 \</span><br><span class="line">                stb-&gt;task = task;                                       \</span><br><span class="line">                if (task)                                               \</span><br><span class="line">                        frame = task-&gt;opframe;                          \</span><br><span class="line">                else                                                    \</span><br><span class="line">                        frame = syncop_create_frame (THIS);\</span><br><span class="line">                                                                        \</span><br><span class="line">                if (task) &#123;                                             \</span><br><span class="line">                        frame-&gt;root-&gt;uid = task-&gt;uid;                   \</span><br><span class="line">                        frame-&gt;root-&gt;gid = task-&gt;gid;                   \</span><br><span class="line">                &#125;                                                       \</span><br><span class="line">                                                                        \</span><br><span class="line">                __yawn (stb);                                           \</span><br><span class="line">                                                                        \</span><br><span class="line">                STACK_WIND_COOKIE (frame, cbk, (void *)stb, subvol,     \</span><br><span class="line">                                   op, params);                         \</span><br><span class="line">                                                                        \</span><br><span class="line">                __yield (stb);                                          \</span><br><span class="line">                if (task)                                               \</span><br><span class="line">                        STACK_RESET (frame-&gt;root);                      \</span><br><span class="line">                else                                                    \</span><br><span class="line">                        STACK_DESTROY (frame-&gt;root);                    \</span><br><span class="line">        &#125; while (0)</span><br></pre></td></tr></table></figure></p><p>因为客户端逻辑非synctask模式，只有server端菜会走synctask<br>协程模式，所以客户端需要阻塞<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">#define __yield(args) do &#123;\</span><br><span class="line">if (args-&gt;task) &#123;                                                \</span><br><span class="line">synctask_yield (args-&gt;task);\</span><br><span class="line">&#125; else &#123;                                                        \</span><br><span class="line">pthread_mutex_lock (&amp;args-&gt;mutex);\</span><br><span class="line">&#123;\</span><br><span class="line">while (!args-&gt;done)&#123;\</span><br><span class="line">pthread_cond_wait (&amp;args-&gt;cond,\</span><br><span class="line">   &amp;args-&gt;mutex);\</span><br><span class="line">                        &#125;   \</span><br><span class="line">&#125;\</span><br><span class="line">pthread_mutex_unlock (&amp;args-&gt;mutex);\</span><br><span class="line">pthread_mutex_destroy (&amp;args-&gt;mutex);\</span><br><span class="line">pthread_cond_destroy (&amp;args-&gt;cond);\</span><br><span class="line">&#125;\</span><br><span class="line">&#125; while (0)</span><br></pre></td></tr></table></figure></p><p>直接sleep(mutex+cond)，等待异步处理完后唤醒</p><h2 id="3-2-meta-autoload-so"><a href="#3-2-meta-autoload-so" class="headerlink" title="3.2 meta_autoload.so"></a>3.2 meta_autoload.so</h2><p>meta_autoload.so是在volume配置解析的时候手动加上的，所以在配置里是看不到的,手动加入后是作为root节点<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">volume vol2-meta_autoload</span><br><span class="line">    type meta/autoload</span><br><span class="line">    subvolumes vol2-io-stats</span><br><span class="line">end-volume</span><br></pre></td></tr></table></figure></p><p>子节点是io-stats.so模块<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">int</span><br><span class="line">meta_writev (call_frame_t *frame, xlator_t *this, fd_t *fd, struct iovec *iov,</span><br><span class="line">     int count, off_t offset, uint32_t flags, struct iobref *iobref,</span><br><span class="line">     dict_t *xdata)</span><br><span class="line">&#123;</span><br><span class="line">//写入文件</span><br><span class="line">printf(&quot;meta_autoload.so meta_writev\n&quot;);</span><br><span class="line">//this 是一个xlator，也就是当前的 meta.so 动态库,作为所有xlator的top节点</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">struct xlator_fops *_fops = NULL;</span><br><span class="line">//这里一般都是获取到默认的meta_fops</span><br><span class="line">//meta_fops 默认的方法都是一个代理，默认代理到xlator的第一个child去执行</span><br><span class="line">_fops = meta_fops_get (fd-&gt;inode, this);</span><br><span class="line">//默认方法在 /libglusterfs/src/default.c 中实现</span><br><span class="line">_fops-&gt;writev (frame, this, fd,iov,count,offset,flags,iobref,xdata);</span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>meta模块没有对逻辑做处理，实际是调用default的方法继续传递给下一个节点<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/libglusterfs/src/defaults.c</span><br><span class="line">int32_t</span><br><span class="line">default_writev (</span><br><span class="line">call_frame_t *frame,</span><br><span class="line">xlator_t *this,</span><br><span class="line">fd_t * fd,</span><br><span class="line">struct iovec * vector,</span><br><span class="line">int32_t count,</span><br><span class="line">off_t off,</span><br><span class="line">uint32_t flags,</span><br><span class="line">struct iobref * iobref,</span><br><span class="line">dict_t * xdata)</span><br><span class="line">&#123;</span><br><span class="line">//直接调用第一个child节点 xlator-&gt;chilid-&gt;fops-&gt;writev 方法</span><br><span class="line">STACK_WIND_TAIL (frame,</span><br><span class="line"> FIRST_CHILD(this), FIRST_CHILD(this)-&gt;fops-&gt;writev,</span><br><span class="line"> fd, vector, count, off, flags, iobref, xdata);</span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>FIRST_CHILD(this) = io-stats.so, 接下来就是调用<code>io-stats-&gt;fops-&gt;writev(io_stats_writev)</code>函数</p><h2 id="3-3-io-stats-so"><a href="#3-3-io-stats-so" class="headerlink" title="3.3 io-stats.so"></a>3.3 io-stats.so</h2><p>这个就是个debug模块，也是啥都没干，继续传递给下一个节点<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/xlators/debug/io-stats/src/io-stats.c</span><br><span class="line">int</span><br><span class="line">io_stats_writev (call_frame_t *frame, xlator_t *this,</span><br><span class="line">                 fd_t *fd, struct iovec *vector,</span><br><span class="line">                 int32_t count, off_t offset,</span><br><span class="line">                 uint32_t flags, struct iobref *iobref, dict_t *xdata)</span><br><span class="line">&#123;</span><br><span class="line">        printf(&quot;debug/io-stats.so: io_stats_writev\n&quot;);</span><br><span class="line">        int                 len = 0;</span><br><span class="line">        //找到fd</span><br><span class="line">        if (fd-&gt;inode)</span><br><span class="line">                frame-&gt;local = fd-&gt;inode;</span><br><span class="line">        //获取数据长度</span><br><span class="line">        len = iov_length (vector, count);</span><br><span class="line"></span><br><span class="line">        ios_bump_write (this, fd, len);</span><br><span class="line">        START_FOP_LATENCY (frame);</span><br><span class="line"></span><br><span class="line">        //开始调用下一个节点</span><br><span class="line">        STACK_WIND (frame, io_stats_writev_cbk,</span><br><span class="line">                    FIRST_CHILD(this),</span><br><span class="line">                    FIRST_CHILD(this)-&gt;fops-&gt;writev,</span><br><span class="line">                    fd, vector, count, offset, flags, iobref, xdata);</span><br><span class="line">        return 0;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>FIRST_CHILD(this) == xlators(io-threads.so) ，子节点就是<code>io-threads</code></p><p>接下来就是调用io-threads.so::iot_writev 方法</p><h3 id="3-4-io-threads-so-线程池"><a href="#3-4-io-threads-so-线程池" class="headerlink" title="3.4 io-threads.so 线程池"></a>3.4 io-threads.so 线程池</h3><p>这边会将操作提交到worker线程异步去执行<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/xlators/performance/io-threads/src/io-threads.c</span><br><span class="line"></span><br><span class="line">int</span><br><span class="line">iot_writev (call_frame_t *frame, xlator_t *this, fd_t *fd,</span><br><span class="line">            struct iovec *vector, int32_t count, off_t offset,</span><br><span class="line">            uint32_t flags, struct iobref *iobref, dict_t *xdata)</span><br><span class="line">&#123;</span><br><span class="line">        call_stub_t     *__stub     = NULL;                            </span><br><span class="line">        int              __ret      = -1;                              </span><br><span class="line">        //将stub投递到io-thread 线程池中去，真正执行的是 default_writev_resume                                            </span><br><span class="line">        __stub = fop_writev_stub(frame, default_writev_resume, fd,vector,count,offset,flags,iobref,xdata);  </span><br><span class="line">        if (!__stub) &#123;                                                 </span><br><span class="line">                __ret = -ENOMEM;                                       </span><br><span class="line">                goto out;                                              </span><br><span class="line">        &#125;                                                              </span><br><span class="line">                                                                        </span><br><span class="line">        __ret = iot_schedule (frame, this, __stub);                    </span><br><span class="line">                                                                        </span><br><span class="line">out:                                                                   </span><br><span class="line">        if (__ret &lt; 0) &#123;                                               </span><br><span class="line">                default_writev_failure_cbk (frame, -__ret);          </span><br><span class="line">                if (__stub != NULL) &#123;                                  </span><br><span class="line">                        call_stub_destroy (__stub);                    </span><br><span class="line">                &#125;                                                      </span><br><span class="line">        &#125;                                                              </span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>worker接收到任务后开始执行default_writev_resume ，继续调用下一个子节点<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/libglusterfs/src/defaults.c</span><br><span class="line"></span><br><span class="line">int32_t default_writev_resume (call_frame_t *frame, xlator_t *this, fd_t * fd,</span><br><span class="line">struct iovec * vector,</span><br><span class="line">int32_t count,</span><br><span class="line">off_t off,</span><br><span class="line">uint32_t flags,</span><br><span class="line">struct iobref * iobref,</span><br><span class="line">dict_t * xdata)</span><br><span class="line">&#123;</span><br><span class="line">//独立线程开始调用 xlator的子节点</span><br><span class="line">STACK_WIND (frame, default_writev_cbk,</span><br><span class="line">    FIRST_CHILD(this), FIRST_CHILD(this)-&gt;fops-&gt;writev,</span><br><span class="line">    fd, vector, count, off, flags, iobref, xdata);</span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>io-thread节点的子节点依然只有一个<code>open-behind.so</code>, 子节点的方法FIRST_CHILD(this)-&gt;fops-&gt;writev == ob_writev</p><h2 id="3-5-open-behind-so"><a href="#3-5-open-behind-so" class="headerlink" title="3.5 open-behind.so"></a>3.5 open-behind.so</h2><p>对于写请求基本上等于啥也没干，就是个代理节点<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/xlators/performance/open-behind/src/open-behind.c</span><br><span class="line"></span><br><span class="line">int ob_writev (call_frame_t *frame, xlator_t *this, fd_t *fd, struct iovec *iov,</span><br><span class="line">   int count, off_t offset, uint32_t flags, struct iobref *iobref,</span><br><span class="line">   dict_t *xdata)</span><br><span class="line">&#123;</span><br><span class="line">call_stub_t  *stub = NULL;</span><br><span class="line">//meta-autoload.so  io-thread.so  open-behind.so 啥事没干，代理了一下后直接调用default_writev_resume 继续传递给下一个child节点</span><br><span class="line">stub = fop_writev_stub (frame, default_writev_resume, fd, iov, count,</span><br><span class="line">offset, flags, iobref, xdata);</span><br><span class="line">if (!stub)</span><br><span class="line">goto err;</span><br><span class="line">open_and_resume (this, fd, stub);</span><br><span class="line"></span><br><span class="line">return 0;</span><br><span class="line">err:</span><br><span class="line">STACK_UNWIND_STRICT (writev, frame, -1, ENOMEM, 0, 0, 0);</span><br><span class="line"></span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>继续代理到子节点去处理<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/libglusterfs/src/defaults.c</span><br><span class="line">int32_t</span><br><span class="line">default_writev_resume (call_frame_t *frame, xlator_t *this, fd_t * fd,</span><br><span class="line">struct iovec * vector,</span><br><span class="line">int32_t count,</span><br><span class="line">off_t off,</span><br><span class="line">uint32_t flags,</span><br><span class="line">struct iobref * iobref,</span><br><span class="line">dict_t * xdata)</span><br><span class="line">&#123;</span><br><span class="line">//独立线程池开始调用 xlator的子节点</span><br><span class="line">STACK_WIND (frame, default_writev_cbk,</span><br><span class="line">    FIRST_CHILD(this), FIRST_CHILD(this)-&gt;fops-&gt;writev,</span><br><span class="line">    fd, vector, count, off, flags, iobref, xdata);</span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>子节点是quick-read.so，FIRST_CHILD(this)-&gt;Fops-&gt;writev == qr_writev()</p><h2 id="3-6-quick-read-so-快速读"><a href="#3-6-quick-read-so-快速读" class="headerlink" title="3.6 quick-read.so 快速读"></a>3.6 quick-read.so 快速读</h2><p>当前组件相当于一个缓存层，为了加速读的效率，会带有缓存，那么写入的时候必然导致不一致，需要清除缓存<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/xlators/performance/quick-read/src/quick-read.c</span><br><span class="line">int</span><br><span class="line">qr_writev (call_frame_t *frame, xlator_t *this, fd_t *fd, struct iovec *iov,</span><br><span class="line">   int count, off_t offset, uint32_t flags, struct iobref *iobref,</span><br><span class="line">   dict_t *xdata)</span><br><span class="line">&#123;</span><br><span class="line">        printf(&quot;performance/quick-read.so: qr_writev\n&quot;); </span><br><span class="line">qr_inode_prune (this, fd-&gt;inode);</span><br><span class="line">        //继续调用下面一层 performance/io-cache.so</span><br><span class="line">STACK_WIND (frame, default_writev_cbk,</span><br><span class="line">    FIRST_CHILD (this), FIRST_CHILD (this)-&gt;fops-&gt;writev,</span><br><span class="line">    fd, iov, count, offset, flags, iobref, xdata);</span><br><span class="line">return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><ol><li>清理缓存</li><li>将请求继续代理到下一个子节点</li><li>子节点是: readdir-ahead.so</li></ol><p>因为readdir-ahead没有实现writev函数，所以用的默认的default函数，所以跳过这里的介绍，直接去下一个节点 distribute.so,对应的方法是dht_writev</p><h2 id="3-7-dht-writev-核心-分布式hash"><a href="#3-7-dht-writev-核心-分布式hash" class="headerlink" title="3.7 dht_writev: 核心-分布式hash"></a>3.7 dht_writev: 核心-分布式hash</h2><p>在这里会决定当前文件写入到那个子卷，对应的哪些brick副本</p><p>为了专注写流程的生命周期，distribute.so的核心实现会另起文章分析，这里只需要知道，在调用dht_writev前，有一个lookup过程，就已经缓存好当前文件对应的是哪个子卷(xlator)</p><p>所以这里通过<code>local-&gt;cached_subvol</code>，直接就拿到了对应的子卷,不用在去临时计算hash分布找出对应的子卷</p><p>备注： 上面说的子卷就是replicate.so 或者不是副本的情况下就是 client.so<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line">int</span><br><span class="line">dht_writev (call_frame_t *frame, xlator_t *this, fd_t *fd,</span><br><span class="line">            struct iovec *vector, int count, off_t off, uint32_t flags,</span><br><span class="line">            struct iobref *iobref, dict_t *xdata)</span><br><span class="line">&#123;</span><br><span class="line">        xlator_t     *subvol   = NULL;</span><br><span class="line">        int           op_errno = -1;</span><br><span class="line">        dht_local_t  *local    = NULL;</span><br><span class="line"></span><br><span class="line">        VALIDATE_OR_GOTO (frame, err);</span><br><span class="line">        VALIDATE_OR_GOTO (this, err);</span><br><span class="line">        VALIDATE_OR_GOTO (fd, err);</span><br><span class="line">        //这里比较奇葩</span><br><span class="line">        //this-&gt;child 就是protocol/client xlator</span><br><span class="line">        //但是没有和其他组件那样直接就去调用this-&gt;child-&gt;fops-&gt;writev() 而是有一个缓存</span><br><span class="line">        //从缓存中拿到 subvol 是 哪个protocol/client </span><br><span class="line">        local = dht_local_init (frame, NULL, fd, GF_FOP_WRITE);</span><br><span class="line">        if (!local) &#123;</span><br><span class="line"></span><br><span class="line">                op_errno = ENOMEM;</span><br><span class="line">                goto err;</span><br><span class="line">        &#125;</span><br><span class="line">        //缓存的子卷信息</span><br><span class="line">        subvol = local-&gt;cached_subvol;</span><br><span class="line">        if (!subvol) &#123;</span><br><span class="line">                gf_msg_debug (this-&gt;name, 0,</span><br><span class="line">                              &quot;no cached subvolume for fd=%p&quot;, fd);</span><br><span class="line">                op_errno = EINVAL;</span><br><span class="line">                goto err;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        if (xdata)</span><br><span class="line">                local-&gt;xattr_req = dict_ref (xdata);</span><br><span class="line"></span><br><span class="line">        local-&gt;rebalance.vector = iov_dup (vector, count);</span><br><span class="line">        local-&gt;rebalance.offset = off;</span><br><span class="line">        local-&gt;rebalance.count = count;</span><br><span class="line">        local-&gt;rebalance.flags = flags;</span><br><span class="line">        local-&gt;rebalance.iobref = iobref_ref (iobref);</span><br><span class="line">        local-&gt;call_cnt = 1;</span><br><span class="line">        //1. 当replica=3  brick = 6 ，说明有两个子卷</span><br><span class="line">        //2. 说明当前distribute组件的作用就是随机分布选取一个子卷存储</span><br><span class="line">        //3. 子卷里需要循环将文件写入到3个brick节点落盘</span><br><span class="line"></span><br><span class="line">        //开始调用子卷的writev操作</span><br><span class="line">        STACK_WIND_COOKIE (frame, dht_writev_cbk, subvol, subvol,</span><br><span class="line">                           subvol-&gt;fops-&gt;writev, fd,</span><br><span class="line">                           local-&gt;rebalance.vector,</span><br><span class="line">                           local-&gt;rebalance.count,</span><br><span class="line">                           local-&gt;rebalance.offset,</span><br><span class="line">                           local-&gt;rebalance.flags,</span><br><span class="line">                           local-&gt;rebalance.iobref, local-&gt;xattr_req);</span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line"></span><br><span class="line">err:</span><br><span class="line">        op_errno = (op_errno == -1) ? errno : op_errno;</span><br><span class="line">        DHT_STACK_UNWIND (writev, frame, -1, op_errno, NULL, NULL, NULL);</span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>因为我们当前的测试是一个三副本，所以下一个节点是一个replicate.so子卷,对应的方法是<code>afr_writev</code></p><h2 id="3-8-replicate-so-副本机制"><a href="#3-8-replicate-so-副本机制" class="headerlink" title="3.8 replicate.so: 副本机制"></a>3.8 replicate.so: 副本机制</h2><p>因为我们当前的测试时基于三副本，所以当前的replicate的子节点有3个client.so,分别对应连接这3个glusterfsd(brick) 进程</p><p>当需要写入文件时，需要同时写入到3个client.so，最终落盘到三个brick</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/xlators/cluster/afr/src/afr-inode-write.c</span><br><span class="line">int</span><br><span class="line">afr_writev (call_frame_t *frame, xlator_t *this, fd_t *fd,</span><br><span class="line">            struct iovec *vector, int32_t count, off_t offset,</span><br><span class="line">            uint32_t flags, struct iobref *iobref, dict_t *xdata)</span><br><span class="line">&#123;</span><br><span class="line">    //忽略参数的组装</span><br><span class="line"></span><br><span class="line">local-&gt;append_write = _gf_true;</span><br><span class="line"></span><br><span class="line">local-&gt;stable_write = !!((fd-&gt;flags|flags)&amp;(O_SYNC|O_DSYNC));</span><br><span class="line"></span><br><span class="line">        afr_fix_open (fd, this);</span><br><span class="line"></span><br><span class="line">        afr_do_writev (frame, this);</span><br><span class="line"></span><br><span class="line">return 0;</span><br><span class="line">out:</span><br><span class="line">AFR_STACK_UNWIND (writev, frame, -1, op_errno, NULL, NULL, NULL);</span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-8-1-开启事务，提交rpc数据到client-so"><a href="#3-8-1-开启事务，提交rpc数据到client-so" class="headerlink" title="3.8.1 开启事务，提交rpc数据到client.so"></a>3.8.1 开启事务，提交rpc数据到client.so</h3><p>开始遍历三个client.so，将数据通过rpc，提交到三个glsuterfsd server去，最终落盘<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">//glusterfs/xlators/cluster/afr/src/afr-inode-write.c</span><br><span class="line">int</span><br><span class="line">afr_do_writev (call_frame_t *frame, xlator_t *this)</span><br><span class="line">&#123;</span><br><span class="line">        //忽略参数初始化</span><br><span class="line"></span><br><span class="line">        local-&gt;op = GF_FOP_WRITE;</span><br><span class="line"></span><br><span class="line">        local-&gt;transaction.wind   = afr_writev_wind;</span><br><span class="line">        local-&gt;transaction.fop    = __afr_txn_write_fop;</span><br><span class="line">        local-&gt;transaction.done   = __afr_txn_write_done;</span><br><span class="line">        local-&gt;transaction.unwind = afr_transaction_writev_unwind;</span><br><span class="line"></span><br><span class="line">        //忽略参数准备</span><br><span class="line">        //开始事务</span><br><span class="line">        ret = afr_transaction (transaction_frame, this, AFR_DATA_TRANSACTION);</span><br><span class="line">        //忽略错误处理，</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">int</span><br><span class="line">__afr_txn_write_fop (call_frame_t *frame, xlator_t *this)</span><br><span class="line">&#123;</span><br><span class="line">        //忽略参数初始化</span><br><span class="line"></span><br><span class="line">        //开始遍历三个client.so，写入数据</span><br><span class="line">        local-&gt;call_count = call_count;</span><br><span class="line">        for (i = 0; i &lt; priv-&gt;child_count; i++) &#123;</span><br><span class="line">                if (local-&gt;transaction.pre_op[i] &amp;&amp; !failed_subvols[i]) &#123;</span><br><span class="line">local-&gt;transaction.wind (frame, this, i);</span><br><span class="line"></span><br><span class="line">                        if (!--call_count)</span><br><span class="line">                                break;</span><br><span class="line">                &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="3-9-client-so-rpc-client"><a href="#3-9-client-so-rpc-client" class="headerlink" title="3.9 client.so: rpc client"></a>3.9 client.so: rpc client</h2><p>找到对应的rpc方法<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">int32_t</span><br><span class="line">client_writev (call_frame_t *frame, xlator_t *this, fd_t *fd,</span><br><span class="line">               struct iovec *vector, int32_t count, off_t off,</span><br><span class="line">               uint32_t flags, struct iobref *iobref, dict_t *xdata)</span><br><span class="line">&#123;</span><br><span class="line">        printf(&quot;protocol/client.so: client_writev\n&quot;);</span><br><span class="line">        int          ret  = -1;</span><br><span class="line">        clnt_conf_t *conf = NULL;</span><br><span class="line">        rpc_clnt_procedure_t *proc = NULL;</span><br><span class="line">        clnt_args_t  args = &#123;0,&#125;;</span><br><span class="line">        //私有配置</span><br><span class="line">        conf = this-&gt;private;</span><br><span class="line">        if (!conf || !conf-&gt;fops)</span><br><span class="line">                goto out;</span><br><span class="line"></span><br><span class="line">        args.fd     = fd;</span><br><span class="line">        args.vector = vector;</span><br><span class="line">        args.count  = count;</span><br><span class="line">        args.offset = off;</span><br><span class="line">        args.size   = iov_length (vector, count);</span><br><span class="line">        args.flags  = flags;</span><br><span class="line">        args.iobref = iobref;</span><br><span class="line">        args.xdata = xdata;</span><br><span class="line"></span><br><span class="line">        client_filter_o_direct (conf, &amp;args.flags);</span><br><span class="line">        //拿到rpc wirte的 路由信息</span><br><span class="line">        proc = &amp;conf-&gt;fops-&gt;proctable[GF_FOP_WRITE];</span><br><span class="line">        if (proc-&gt;fn)</span><br><span class="line">                ret = proc-&gt;fn (frame, this, &amp;args);</span><br><span class="line">out:</span><br><span class="line">        if (ret)</span><br><span class="line">                STACK_UNWIND_STRICT (writev, frame, -1, ENOTCONN, NULL, NULL, NULL);</span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h3 id="3-9-1-提交rpc数据到glusterfsd"><a href="#3-9-1-提交rpc数据到glusterfsd" class="headerlink" title="3.9.1 提交rpc数据到glusterfsd"></a>3.9.1 提交rpc数据到glusterfsd</h3><p>核心rpc处理逻辑<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">int32_t</span><br><span class="line">client3_3_writev (call_frame_t *frame, xlator_t *this, void *data)</span><br><span class="line">&#123;</span><br><span class="line">        clnt_args_t    *args     = NULL;</span><br><span class="line">        clnt_conf_t    *conf     = NULL;</span><br><span class="line">        gfs3_write_req  req      = &#123;&#123;0,&#125;,&#125;;</span><br><span class="line">        int             op_errno = ESTALE;</span><br><span class="line">        int             ret      = 0;</span><br><span class="line"></span><br><span class="line">        if (!frame || !this || !data)</span><br><span class="line">                goto unwind;</span><br><span class="line"></span><br><span class="line">        args = data;</span><br><span class="line">        conf = this-&gt;private;</span><br><span class="line">        //预处理 ，准备写入的数据包</span><br><span class="line">        ret = client_pre_writev (this, &amp;req, args-&gt;fd, args-&gt;size,</span><br><span class="line">                                 args-&gt;offset, args-&gt;flags, &amp;args-&gt;xdata);</span><br><span class="line"></span><br><span class="line">        if (ret) &#123;</span><br><span class="line">                op_errno = -ret;</span><br><span class="line">                goto unwind;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        ret = client_fd_fop_prepare_local (frame, args-&gt;fd, req.fd);</span><br><span class="line">        if (ret) &#123;</span><br><span class="line">                op_errno = -ret;</span><br><span class="line">                goto unwind;</span><br><span class="line">        &#125;</span><br><span class="line">        //开始发送写入数据的请求 到远程 glusterd rpc server处理</span><br><span class="line">        ret = client_submit_vec_request (this, &amp;req, frame, conf-&gt;fops,</span><br><span class="line">                                         GFS3_OP_WRITE, client3_3_writev_cbk,</span><br><span class="line">                                         args-&gt;vector, args-&gt;count,</span><br><span class="line">                                         args-&gt;iobref,</span><br><span class="line">                                         (xdrproc_t)xdr_gfs3_write_req);</span><br><span class="line">        if (ret) &#123;</span><br><span class="line">                /*</span><br><span class="line">                 * If the lower layers fail to submit a request, they&apos;ll also</span><br><span class="line">                 * do the unwind for us (see rpc_clnt_submit), so don&apos;t unwind</span><br><span class="line">                 * here in such cases.</span><br><span class="line">                 */</span><br><span class="line">                gf_msg (this-&gt;name, GF_LOG_WARNING, 0, PC_MSG_FOP_SEND_FAILED,</span><br><span class="line">                        &quot;failed to send the fop&quot;);</span><br><span class="line">        &#125;</span><br><span class="line">        //发送到远程后 释放客户端内存</span><br><span class="line">        GF_FREE (req.xdata.xdata_val);</span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line"></span><br><span class="line">unwind:</span><br><span class="line">        CLIENT_STACK_UNWIND (writev, frame, -1, op_errno, NULL, NULL, NULL);</span><br><span class="line">        GF_FREE (req.xdata.xdata_val);</span><br><span class="line"></span><br><span class="line">        return 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h1 id="四、概述"><a href="#四、概述" class="headerlink" title="四、概述"></a>四、概述</h1><p>到这里总共分析了有</p><ol><li>volume配置语言: 基于lex,bison简单的实现了一个类xml的配置</li><li>volume配置中每个节点对应的xlator作用</li><li>sdk整体逻辑需要依赖volume配置的依赖关系</li><li>最终通过一个write的io流程讲解整个client端的主功能</li></ol><p>像nginx一样，如果你要自定义一个插件介入到api的生命周期中，你只需要自己实现一个xlator，然后加入到配置中即可</p><p>总结出核心的副本，hash，分布式整个逻辑都是放到client端的，server端(glusterfsd)基本上只负责将文件高效的落盘到linux文件系统对应路径上</p><p>当然当前文章只进行了整体的概括，细节需要单独的文章分析，比如ddistribute.so核心实现，event调度器实现，server端的协程调度器实现。。等等后续在单独写文章分析了</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;glusterfs也属于典型的cs结构，有client端,server端，通过rpc调用通信&lt;/p&gt;
&lt;p&gt;主要的进程有:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;glusterd: 管理进程，管理所有的配置、版本信息&lt;/li&gt;
&lt;li&gt;glusterfsd: server进程，处理所有
      
    
    </summary>
    
      <category term="middlewares" scheme="http://wiki.brewlin.com/categories/middlewares/"/>
    
      <category term="glusterfs" scheme="http://wiki.brewlin.com/categories/middlewares/glusterfs/"/>
    
    
      <category term="linux" scheme="http://wiki.brewlin.com/tags/linux/"/>
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gluster" scheme="http://wiki.brewlin.com/tags/gluster/"/>
    
      <category term="glusterfs" scheme="http://wiki.brewlin.com/tags/glusterfs/"/>
    
  </entry>
  
  <entry>
    <title>rust_无栈协程&amp;调度器实现</title>
    <link href="http://wiki.brewlin.com/wiki/compiler/rust%E5%8D%8F%E7%A8%8B_%E8%B0%83%E5%BA%A6%E5%99%A8%E5%AE%9E%E7%8E%B0/"/>
    <id>http://wiki.brewlin.com/wiki/compiler/rust协程_调度器实现/</id>
    <published>2022-04-07T13:28:59.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>本次文章专注于分析rust的协程实现，并提前介绍它与其他语言的协程有什么区别</p><p>接下来我们就从有栈和无栈切入了解协程在内存方面的布局，典型的有栈协程就是golang，这里的<code>有栈</code>其实是一个潜台词，代表的是需要单独申请堆内存作为栈的意思</p><p>那么有栈协程对应的就是=&gt;需要独立申请一份内存作为代码指令运行的栈</p><h1 id="关于栈的解析"><a href="#关于栈的解析" class="headerlink" title="关于栈的解析"></a>关于栈的解析</h1><p>典型的就是c语言的栈，来看看c语言的运行栈的情况吧<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">void test1(long  var3,long var4)&#123;</span><br><span class="line">//函数参数也是变量，看看各个编译器的实现</span><br><span class="line">//一般也是存在栈上，所以从下面图片可以看到即时我没有显示定义两个变量，但实际还是占用了栈</span><br><span class="line">&#125;</span><br><span class="line">void test()&#123;</span><br><span class="line">    long var1 = 1; //用long 是因为64位下占8字节方便我下面画图。。。</span><br><span class="line">    long var2 = 2;</span><br><span class="line">    test1();</span><br><span class="line">    var2 = 3;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>当前没有讲解从main函数开始，是因为main函数有点特殊，因为main的参数只有<code>argc,argv</code>。且直接通过<code>(%rsp),8(%rsp),分别就能拿到 argc,argv。不是直接像其他那样通过</code>%rdi,%rsi`拿，为了保持简洁明了，直接用了非main函数其他的函数作为例子讲解</p><h2 id="栈的内存结构"><a href="#栈的内存结构" class="headerlink" title="栈的内存结构"></a>栈的内存结构</h2><p>上文的c语言在编译运行后的系统栈内存使用如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">|    +--------------+   %rbp &lt;---------------------                </span><br><span class="line">|    |   var1       |                             |</span><br><span class="line">|    +--------------+                             |</span><br><span class="line">|    |   var2       |                             |</span><br><span class="line">|    +--------------+                             |</span><br><span class="line">|    |   arg1       |                             |</span><br><span class="line">|    +--------------+                             |</span><br><span class="line">|    |   arg2       |                             |</span><br><span class="line">|    +--------------+                             |</span><br><span class="line">|    |Return address|    test                     |</span><br><span class="line">|    +--------------------------- %rsp            |</span><br><span class="line">|    |   %rbp       |    --------------------------</span><br><span class="line">|    +--------------+    </span><br><span class="line">|    |   var3       |    </span><br><span class="line">|    +--------------+</span><br><span class="line">|    |   var4       |    </span><br><span class="line">|    +--------------+</span><br><span class="line">|    |Return address|    test1</span><br><span class="line">V    +--------------------------- %rsp</span><br></pre></td></tr></table></figure></p><ol><li>首先栈是从高地址到低地址发展</li><li>所有的变量会提前在编译阶段就计算好栈中的地址<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">test函数内:</span><br><span class="line">//var1的变量</span><br><span class="line">var1 = -8(%rbp);</span><br><span class="line">var2 = -16(%rbp)</span><br><span class="line"></span><br><span class="line">test1函数内:</span><br><span class="line">var3 = -8(%rbp);</span><br><span class="line">var4 = -16(%rbp);</span><br></pre></td></tr></table></figure></li></ol><h2 id="栈的上下文恢复"><a href="#栈的上下文恢复" class="headerlink" title="栈的上下文恢复"></a>栈的上下文恢复</h2><p>从上文我们可以看到，整个程序运行就是各种函数调用，都会不断的追加到栈中，不停的阔栈，整个进程都是复用的同一个栈，所有变量和地址都是依赖于<code>%rbp</code>进行地址定位(依赖于编译器实现，一般都是根据%rbp进行定位)</p><p>那么当<code>test1</code>函数返回后，如何恢复<code>test</code>函数的环境呢？<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. 变量的定位需要依赖`%rbp`,所以将(test1.%rbp)恢复到(test.%rbp)即可</span><br><span class="line">2. %rsp指向的栈顶，当test1函数返回后，其实 %rsp = %rbp即可，因为test1函数的基站其实就是test函数的栈顶</span><br><span class="line">3. 当然还要恢复%rip，下一条指令执行的地址，也就是test函数var2=3的地址，也是在test1函数返回时需要恢复的</span><br></pre></td></tr></table></figure></p><p><code>rsp,rip,rbp</code>基本上靠这个就组成了整个函数的上下文，这也就理解了c类语言的内存栈布局</p><p>那么就可以非常明了的解释了为什么不能轻松操作栈引用的问题了<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span>* <span class="title">test1</span><span class="params">(<span class="keyword">int</span> var3)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> &amp;var3;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">test</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">int</span>* var1 = test1(<span class="number">10</span>);</span><br><span class="line">    <span class="keyword">int</span>* var2 = test1(<span class="number">20</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">|    +--------------+   %rbp &lt;---------------------                </span><br><span class="line">|    |   var1       |                             |</span><br><span class="line">|    +--------------+                             |</span><br><span class="line">|    |   var2       |                             |</span><br><span class="line">|    +--------------+                             |</span><br><span class="line">|    |   arg1       |                             |</span><br><span class="line">|    +--------------+                             |</span><br><span class="line">|    |   arg2       |                             |</span><br><span class="line">|    +--------------+                             |</span><br><span class="line">|    |Return address|    test                     |</span><br><span class="line">|    +--------------------------- %rsp            |</span><br><span class="line">|    |   %rbp       |    --------------------------</span><br><span class="line">|    +--------------+    </span><br><span class="line">|    |   var3       |    </span><br><span class="line">|    +--------------+</span><br><span class="line">|    |Return address|    test1</span><br><span class="line">V    +--------------------------- %rsp</span><br></pre></td></tr></table></figure></p><ol><li>每次调用进入到<code>test1</code>函数时，都返回了<code>lea -8(%rbp)</code>,因为%rbp的值是一样的所以他们引用的地址其实都是一样的</li><li>所以即使<code>*var1</code>第一次调用时的值是 10;</li><li>但是第二次调用时<code>*var1</code>所指向的地址内的值被20替换，导致最后<code>*var1,*var2</code>都是20</li></ol><h1 id="有栈协程-gt-golang"><a href="#有栈协程-gt-golang" class="headerlink" title="有栈协程 -&gt; golang"></a>有栈协程 -&gt; golang</h1><p>上面的栈例子中典型的用了c语言的例子，程序总体同步顺序运行，全部公用一个系统栈，并且随着不断的函数调用、函数返回，这个栈会不断复用</p><p>而有栈协程中的<code>有栈</code>到底是什么意思，这里其实是约定成俗想表达对于每个新创建的协程来说:他们都独立运行与一块新的栈，<code>这块栈是从堆(基于mmap维护了整个内存管理)上面申请的</code>，没用共用系统栈，那么这个协程的生命周期和上下文都能够被完整保存，可以被任意时间和任意线程独立执行</p><h2 id="go-创建协程"><a href="#go-创建协程" class="headerlink" title="go 创建协程"></a>go 创建协程</h2><p>在golang语言中，直接通过<code>go</code>关键字可以轻松创建一个协程，并传递一个待执行的<code>func函数</code>,在此之后整个func和当前主线程再无瓜葛，它会被任意调度到任意线程去执行或多次执行</p><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span>&#123;</span><br><span class="line">    fmt.Println(<span class="string">"this is a goroutine print!"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实际在编译后被换成了调用<code>runtime.newproc</code>方法<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">// 创建一个协程，用来运行传入的带有siz字节参数的函数</span><br><span class="line">// 将协程push到队列里 等待调度运行</span><br><span class="line">// 不能进行栈切分，因为函数参数需要拷贝，如果栈分裂的话可能fn后面的参数不完整了</span><br><span class="line">//go:nosplit</span><br><span class="line">func newproc(siz int32, fn *funcval) &#123;</span><br><span class="line">//总的来说这里是编译器来调用的newproc方法，第一个参数siz 指明了调用函数fn的参数大小</span><br><span class="line">//NOTICE: 参数全部是存放在栈上的，所以通过fn后面的偏移量+参数大小就可以完整的拷贝函数参数了</span><br><span class="line">argp := add(unsafe.Pointer(&amp;fn), sys.PtrSize)</span><br><span class="line">//stack: [size,fn,arg1,arg2,arg3....] size 指明了arg1..argn的栈范围大小</span><br><span class="line">gp := getg()</span><br><span class="line">//获取调用方的下个指令地址，一般用于设置ip寄存器用于表示下一行代码该执行哪</span><br><span class="line">pc := getcallerpc()</span><br><span class="line">systemstack(func() &#123;</span><br><span class="line">newproc1(fn, (*uint8)(argp), siz, gp, pc)</span><br><span class="line">&#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><ul><li><code>fn</code>就是上文我们传递的闭包函数，待异步执行的函数方法</li><li>如果在创建协程的时候，带上了参数，也能通过栈偏移量获取到函数参数<code>add(unsafe.Pointer(&amp;fn),sys.PtrSize)</code>.需要马上将参数拷贝到协程空间内，因为这些参数仍然是存放在主线程栈上的(go嵌套则不一样)</li></ul><h2 id="分配栈"><a href="#分配栈" class="headerlink" title="分配栈"></a>分配栈</h2><p>这里任然是处于创建协程的收尾部分，主要处理两件事:</p><ol><li>给协程分配2k的内存作为函数运行的占空间(会复用其他已经释放的协程栈)</li><li>将协程丢给全局队列等待释放。在rust.tokio中相当于丢给<code>worker.shared.inject.push(task)</code>全局队列等待调度</li><li>设定<code>exit</code>函数，实现永不返回的循环调度（不同的协程栈之间切换已经没有return的概念了，直接永不停歇的往前走）<figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在系统栈、g0栈上创建                                                                                                                                      uu一个协程</span></span><br><span class="line"><span class="comment">// 1. 拷贝参数到协程里</span></span><br><span class="line"><span class="comment">// 2. 初始化基本信息如，调用方的下一行代码地址，ip寄存器</span></span><br><span class="line"><span class="comment">// 3. 将协程推入全局列表等待调度</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">newproc1</span><span class="params">(fn *funcval, argp *<span class="keyword">uint8</span>, narg <span class="keyword">int32</span>, callergp *g, callerpc <span class="keyword">uintptr</span>)</span></span> &#123;</span><br><span class="line"><span class="comment">//从tls中获取线程对应的协程</span></span><br><span class="line">_g_ := getg()</span><br><span class="line"></span><br><span class="line"><span class="comment">//复用已经被释放了的之前的协程栈</span></span><br><span class="line">newg := gfget(_p_)</span><br><span class="line"><span class="keyword">if</span> newg == <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="comment">//立即创建一个协程+ 2k协程栈</span></span><br><span class="line">newg = malg(_StackMin)</span><br><span class="line"><span class="comment">//将g转换为dead状态</span></span><br><span class="line">casgstatus(newg, _Gidle, _Gdead)</span><br><span class="line"><span class="comment">//添加到allg全局队列管理</span></span><br><span class="line">allgadd(newg) <span class="comment">// publishes with a g-&gt;status of Gdead so GC scanner doesn't look at uninitialized stack.</span></span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">//....</span></span><br><span class="line">sp := newg.stack.hi - totalSize</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">memclrNoHeapPointers(unsafe.Pointer(&amp;newg.sched), unsafe.Sizeof(newg.sched))</span><br><span class="line"><span class="comment">//初始化时 记录了协程内当前栈顶 和 基栈</span></span><br><span class="line">newg.sched.sp = sp</span><br><span class="line">newg.stktopsp = sp</span><br><span class="line">    <span class="comment">//..</span></span><br><span class="line"><span class="comment">//协程内需要执行的代码指令地址，初始化时指向了函数的首地址,而在后面的生命周期中 会不断调度切换后会变化</span></span><br><span class="line">newg.startpc = fn.fn</span><br><span class="line">    <span class="comment">//协程id</span></span><br><span class="line">newg.goid = <span class="keyword">int64</span>(_p_.goidcache)</span><br><span class="line">_p_.goidcache++</span><br><span class="line">    <span class="comment">//..</span></span><br><span class="line"><span class="comment">//将协程投递到本地队列或者全局队列等待调度器调度</span></span><br><span class="line">runqput(_p_, newg, <span class="literal">true</span>)</span><br><span class="line">    <span class="comment">//..</span></span><br><span class="line"><span class="comment">//顺便检查下，如果当前需要抢占则处理抢占</span></span><br><span class="line"><span class="keyword">if</span> _g_.m.locks == <span class="number">0</span> &amp;&amp; _g_.preempt &#123; <span class="comment">// restore the preemption request in case we've cleared it in newstack</span></span><br><span class="line"><span class="comment">//编译器在函数调用的时候会检查是否栈溢出，这里巧妙的利用栈溢出来实现抢占</span></span><br><span class="line">_g_.stackguard0 = stackPreempt</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><h2 id="栈的上下文"><a href="#栈的上下文" class="headerlink" title="栈的上下文"></a>栈的上下文</h2><p>理想情况下面的函数在单线程中会顺序调用和执行，那么根据这种情况可以绘制一个理想的栈使用情况<br><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">go1_1</span><span class="params">()</span></span>&#123;<span class="keyword">var</span> var11 <span class="keyword">uint64</span>&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">go1</span><span class="params">()</span></span>&#123;</span><br><span class="line"><span class="keyword">var</span> var1 <span class="keyword">uint64</span></span><br><span class="line">go1_1()</span><br><span class="line"><span class="comment">//go1_e 函数结束的指令地址</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">runtime.GOMAXPROCS();</span><br><span class="line"><span class="keyword">go</span> go1()</span><br><span class="line"><span class="keyword">go</span> go2()</span><br><span class="line"><span class="keyword">select</span>&#123;&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">go2</span><span class="params">()</span></span>&#123;go2_1()</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">go2_1</span><span class="params">()</span></span>&#123;&#125;</span><br></pre></td></tr></table></figure></p><p><img src="/images/blog/rust/golang-stack.png" alt="image"><br>golang的协程永不return，不停的在协程间切换<br><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// One round of scheduler: find a runnable goroutine and execute it.</span></span><br><span class="line"><span class="comment">// Never returns.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">schedule</span><span class="params">()</span></span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>在研究调度器的时候会有一个疑问，注释明明写的<code>never returns</code>，但是却没有看到死循环的操作，那么是如何实现永不return的呢</p><p>总的来说可以总结以下几个函数的调用顺序来概览到这种循环机制<br><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">schedule()      g0栈上:开始执行一轮调度,找到需要唤醒的G</span><br><span class="line">execute()       g0栈上:开始唤醒协程G，切换到协程栈</span><br><span class="line">mcall(goexit1)  g栈   :开始切换到g0栈上回收以及结束的G</span><br><span class="line">schedule()      g0栈上:开始执行一轮调度，找到需要唤醒的G</span><br></pre></td></tr></table></figure></p><p>主要是三个函数就能描述了整个调度的生命周期，但其实还有一个问题在，上面的循环很像一个递归调用，那可不可能发生爆栈呢</p><p>核心就在于g0栈是复用的,也就是在每次从g0栈切换到g栈的时候是不保存g0栈的，这么就会导致g0栈始终会从默认的地方在下次继续执行<br><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">TEXT runtime·gogo(SB), NOSPLIT, $<span class="number">16</span><span class="number">-8</span></span><br><span class="line">MOVQbuf+<span class="number">0</span>(FP), BX<span class="comment">// bx = gobuf</span></span><br><span class="line">MOVQgobuf_g(BX), DX     <span class="comment">// dx = g</span></span><br><span class="line">MOVQ<span class="number">0</span>(DX), CX<span class="comment">// make sure g != nil</span></span><br><span class="line">get_tls(CX)</span><br><span class="line">MOVQDX, g(CX)       <span class="comment">// 将目标g 设置为当前线程 tls-&gt;g</span></span><br><span class="line">MOVQgobuf_sp(BX), SP<span class="comment">// restore SP  恢复sp栈顶指针 $rsp = gobuf.sp 实现栈切换</span></span><br><span class="line">MOVQgobuf_ret(BX), AX  <span class="comment">// ax = gobuf.ret</span></span><br><span class="line">MOVQgobuf_ctxt(BX), DX <span class="comment">// dx = gobuf.ctxt 上下文信息</span></span><br><span class="line">MOVQgobuf_bp(BX), BP    <span class="comment">// 恢复bp寄存器  $rbp = gobuf-&gt;bp 栈基指针 执行当前函数开始位置</span></span><br></pre></td></tr></table></figure></p><p>可以看到从g0栈切换到g栈的核心方法<code>gogo</code>中并没有保存当前g0的上下文，也就是说<code>g0-&gt;sched</code>上下文信息始终没有发生变化，在下次通过<code>mcall</code>等切换回g0时不会导致g0栈空间的开辟</p><h1 id="无栈协程-gt-rust"><a href="#无栈协程-gt-rust" class="headerlink" title="无栈协程-&gt;rust"></a>无栈协程-&gt;rust</h1><p>上面描述了两种栈，一种是c的全局系统栈，另外一个就是基于堆的golang协程栈</p><p>可以明显感受到golang的栈会复杂很多，而且开销非常大</p><ol><li>所有的协程都会默认分配2k内存</li><li>随着协程内函数调用的嵌套层级增大，2k栈明显不够用，那么会触发栈的扩容</li><li>栈扩容又会引发一系列引用问题</li></ol><p>但协程的实现又要保存上下文，不依赖单独的栈如何做到呢？。带着这个疑问来分析rust的黑魔法吧</p><p>还是先来感受下golang和rust关于协程的语法区别吧<br><figure class="highlight golang"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//golang</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">go</span> fun()&#123;</span><br><span class="line">        fmt.Println(<span class="string">"ready to sleep!"</span>)</span><br><span class="line">        time.Sleep(<span class="number">8</span> * time.Second) </span><br><span class="line">        <span class="comment">//会暂停当前函数执行,给其他协程继续执行</span></span><br><span class="line">        <span class="comment">//等待睡眠时间到后重新调度后继续从当前位置向下执行</span></span><br><span class="line">        fmt.Println(<span class="string">"hello world 1!"</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//rust</span></span><br><span class="line">tokio::spawn(<span class="keyword">async</span> &#123;</span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">"ready to sleep"</span>);</span><br><span class="line">     tokio::time::sleep(time::Duration::from_secs(<span class="number">2</span>)).<span class="keyword">await</span>;</span><br><span class="line">     <span class="comment">//注意： 一定要加await！</span></span><br><span class="line">     <span class="comment">//当前函数会在这里暂停，等待睡眠时间到后继续恢复执行</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">"hello world!"</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>两种语言的协程实现都能表现同样的功能，但rust已经能够感受到需要注意规范是比较多的</p><ol><li>rust没有自带的运行时，所有协程的调度、执行、切换都需要依赖三方实现，比较好的就是<code>tokio</code></li><li>编译器只干了一件事情: <code>在有await语句的地方检测是否ready，否则挂起函数，等待下次运行</code></li></ol><p>既然rust不像golang那样有单独的栈，那他怎么实现上下文保存和栈的重入呢？，比较都是依赖主线程栈，怎么切换呢，不得甚解</p><p>在了解rust通过await实现协程(特别强调await)前来一起看看什么叫做状态机吧</p><h2 id="rust-状态机"><a href="#rust-状态机" class="headerlink" title="rust 状态机"></a>rust 状态机</h2><p>理解协程的核心就是暂停和恢复，rust的协程通过状态机做到这一点，golang通过独立的栈做到这一点。理解这一点很重要</p><p>看个例子:(为了模拟暂停状态，需要自己实现一个future(async语句块))<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="class"><span class="keyword">struct</span> <span class="title">Task</span></span> &#123;</span><br><span class="line">    ready: <span class="built_in">bool</span>,</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">impl</span> Future <span class="keyword">for</span> Task &#123;</span><br><span class="line">    <span class="class"><span class="keyword">type</span> <span class="title">Output</span></span> = <span class="built_in">bool</span>;</span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">poll</span></span>(<span class="keyword">self</span>: Pin&lt;&amp;<span class="keyword">mut</span> <span class="keyword">Self</span>&gt;, cx: &amp;<span class="keyword">mut</span> Context&lt;<span class="symbol">'_</span>&gt;) -&gt; Poll&lt;Self::Output&gt; &#123;</span><br><span class="line">        <span class="keyword">let</span> <span class="keyword">mut</span> ms = <span class="keyword">unsafe</span> &#123; <span class="keyword">self</span>.get_unchecked_mut() &#125;;</span><br><span class="line">        <span class="keyword">if</span> !ms.ready &#123;</span><br><span class="line">            ms.ready = <span class="literal">true</span>;<span class="comment">//下一次再进来就是true</span></span><br><span class="line">            <span class="built_in">println!</span>(<span class="string">"task not ready!"</span>);</span><br><span class="line">            Poll::Pending    </span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="built_in">println!</span>(<span class="string">"task is ready!"</span>);</span><br><span class="line">            Poll::Ready(ms.ready)</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">fn</span> <span class="title">test</span></span>()&#123;</span><br><span class="line">    <span class="keyword">let</span> task1 = Task&#123;ready:<span class="literal">false</span>&#125;;</span><br><span class="line">    task1.<span class="keyword">await</span>; <span class="comment">// 发生第一次暂停，因为task1返回了Poll::Pending,当前test也是一个协程，会保存上下文，结束当前函数</span></span><br><span class="line">    <span class="built_in">println!</span>(<span class="string">"test done!"</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>()&#123;</span><br><span class="line">    tokio::spawn(test());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><ol><li>上面的实例永远只会打印一次<code>task is ready!</code>就结束了test（）函数的执行，下面的<code>test done!</code>永远不会被执行<ul><li>因为对于调度器tokio来说，他永远不知道task会在何时转换为Poll::Ready状态</li><li>所以一般真正的阻塞的需要切换的地方tokio都覆盖完了，比如(网络io，sleep等等api)，都会在发生阻塞的时候切换出去，在唤醒后主动在来调用一次</li><li>那么自己实现的阻塞的方法，那就需要注册唤醒器让tokio有能力重新调度了</li></ul></li><li>假定我们实现了唤醒器,并且将<code>ms.ready=true</code>注释掉`<ul><li>那么依然test函数会被重复调度运行，但用于只会执行 <code>task not ready</code>那段逻辑</li><li>因为：rust在编译阶段做了手脚，通过状态(Pending,Ready)来区分该执行哪段逻辑</li></ul></li></ol><p>现在还是有点晕，状态机到底是个什么东西，那么我们就从编译器的视角来看看上面的rust代码被编译器魔改后到底实际执行的是什么代码吧</p><h2 id="编译器生成的状态机代码"><a href="#编译器生成的状态机代码" class="headerlink" title="编译器生成的状态机代码"></a>编译器生成的状态机代码</h2><p>还是上面的那个例子，让我们来看看编译器最终生成的是什么代码吧(如下伪码)<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">//原始代码</span></span><br><span class="line"><span class="comment">async fn test()&#123;</span></span><br><span class="line"><span class="comment">    let task = Task&#123;ready:false&#125;;</span></span><br><span class="line"><span class="comment">    task.await;</span></span><br><span class="line"><span class="comment">    println!("test done!");</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">enum</span> <span class="title">test</span></span> &#123;</span><br><span class="line">    Enter,</span><br><span class="line">    Yield1 &#123;</span><br><span class="line">        task: Task,</span><br><span class="line">    &#125;,</span><br><span class="line">    Exit,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> test &#123;</span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">start</span></span>() -&gt; <span class="keyword">Self</span> &#123;</span><br><span class="line">        test::Enter</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">execute</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) &#123;</span><br><span class="line">        <span class="keyword">match</span> <span class="keyword">self</span> &#123;</span><br><span class="line">            test::Enter =&gt; &#123;</span><br><span class="line">                <span class="keyword">let</span> task = Task&#123;ready:<span class="literal">false</span>&#125;; <span class="comment">//源代码</span></span><br><span class="line">                *<span class="keyword">self</span> = test::Yield1&#123;task:task&#125; <span class="comment">//保存上下文</span></span><br><span class="line">            &#125;</span><br><span class="line">            test::Yield1 &#123;task&#125; =&gt; &#123;</span><br><span class="line">                <span class="keyword">if</span> task.poll() == Poll::Ready &#123;<span class="comment">//task.await 伪码</span></span><br><span class="line">                    <span class="built_in">println!</span>(<span class="string">"test done!"</span>);</span><br><span class="line">                    *<span class="keyword">self</span> = test::Exit; <span class="comment">//await结束 </span></span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">return</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            test::Exit =&gt; <span class="built_in">panic!</span>(<span class="string">"Can't do this again!"</span>),</span><br><span class="line">        &#125;    </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>()&#123;</span><br><span class="line">    <span class="keyword">let</span> t = test::start();</span><br><span class="line">    <span class="comment">//tokio::spawn(test())</span></span><br><span class="line">    <span class="comment">//背后其实就是多次t.execute()</span></span><br><span class="line">    <span class="comment">//第一次 t.execute() ready false 打印: not ready</span></span><br><span class="line">    <span class="comment">//第二次 t.execute() ready true  打印: test done</span></span><br><span class="line">    <span class="comment">// 如果task.ready状态一直为false，那么会一直执行test::Yield1这个分支</span></span><br><span class="line">    <span class="comment">//编译器将async fn test() 生成一个enum带有状态的状态机，从而实现了直接在系统栈上就能够实现协程的暂停与恢复</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><ol><li><code>重点</code>: rust编译器会将带有.await的代码快转换为一个enum 状态机，就像上文一下test函数被改成了<code>enum test</code></li><li>对于每个await的代码都实现为一个enum的分支</li><li>每次协程的暂停和恢复只是进入不同的代码分支罢了</li></ol><p>虽然没有了额外的占的开销，但实际上编译器会生成很多指令和分支来支持这个状态机</p><p>不过相比需要额外的栈内存来实现协程，这种方式已经非常棒了</p><h2 id="协程调度器-tokio-runtime"><a href="#协程调度器-tokio-runtime" class="headerlink" title="协程调度器(tokio runtime)"></a>协程调度器(tokio runtime)</h2><p>到上面为止我们只分析到了函数的暂停与恢复（协程的基本要素）。但何时暂停何时恢复这个rust并没有实现</p><p>调度器这种运行时功能目前比较好的三方实现是tokio</p><h3 id="1-启动多线程"><a href="#1-启动多线程" class="headerlink" title="1. 启动多线程"></a>1. 启动多线程</h3><p>初始化一个多线程的tokio运行时<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>()&#123;</span><br><span class="line">    <span class="keyword">let</span> rt = tokio::runtime::Builder::new_multi_thread().enable_all().build().unwrap();</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>builder会区分是多线程版本还是单线程版本<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//tokio/tokio/src/runtime/builder.rs:514</span></span><br><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">build</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) -&gt; io::<span class="built_in">Result</span>&lt;Runtime&gt; &#123;</span><br><span class="line">    <span class="keyword">match</span> &amp;<span class="keyword">self</span>.kind &#123;</span><br><span class="line">        Kind::CurrentThread =&gt; <span class="keyword">self</span>.build_basic_runtime(),</span><br><span class="line">        <span class="meta">#[cfg(feature = <span class="meta-string">"rt-multi-thread"</span>)]</span></span><br><span class="line">        Kind::MultiThread =&gt; <span class="keyword">self</span>.build_threaded_runtime(),</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>创建系统多线程<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//tokio/tokio/src/runtime/builder.rs:663</span></span><br><span class="line">cfg_rt_multi_thread! &#123;</span><br><span class="line">    <span class="keyword">impl</span> Builder &#123;</span><br><span class="line">        <span class="function"><span class="keyword">fn</span> <span class="title">build_threaded_runtime</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) -&gt; io::<span class="built_in">Result</span>&lt;Runtime&gt; &#123;</span><br><span class="line">        <span class="comment">//省略参数初始化。。。</span></span><br><span class="line">                    <span class="comment">// Spawn the thread pool workers</span></span><br><span class="line">        <span class="keyword">let</span> _enter = crate::runtime::context::enter(handle.clone());</span><br><span class="line">        <span class="comment">//开始派生系统线程</span></span><br><span class="line">        launch.launch();</span><br><span class="line">        <span class="literal">Ok</span>(Runtime &#123;</span><br><span class="line">            kind: Kind::ThreadPool(scheduler),</span><br><span class="line">            handle,</span><br><span class="line">            blocking_pool,</span><br><span class="line">        &#125;)</span><br><span class="line">        </span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h3 id="2-worker线程调度协程任务"><a href="#2-worker线程调度协程任务" class="headerlink" title="2. worker线程调度协程任务"></a>2. worker线程调度协程任务</h3><p>每个worker线程进入轮训模式<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//tokio/tokio/src/runtime/thread_pool/worker.rs:382</span></span><br><span class="line"><span class="keyword">impl</span> Context &#123;</span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">run</span></span>(&amp;<span class="keyword">self</span>, <span class="keyword">mut</span> core: <span class="built_in">Box</span>&lt;Core&gt;) -&gt; RunResult &#123;</span><br><span class="line">        <span class="keyword">while</span> !core.is_shutdown &#123;</span><br><span class="line">            <span class="comment">// Increment the tick</span></span><br><span class="line">            core.tick();</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Run maintenance, if needed</span></span><br><span class="line">            core = <span class="keyword">self</span>.maintenance(core);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// First, check work available to the current worker.</span></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">let</span> <span class="literal">Some</span>(task) = core.next_task(&amp;<span class="keyword">self</span>.worker) &#123;</span><br><span class="line">                core = <span class="keyword">self</span>.run_task(task, core)?;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// There is no more **local** work to process, try to steal work</span></span><br><span class="line">            <span class="comment">// from other workers.</span></span><br><span class="line">            <span class="keyword">if</span> <span class="keyword">let</span> <span class="literal">Some</span>(task) = core.steal_work(&amp;<span class="keyword">self</span>.worker) &#123;</span><br><span class="line">                core = <span class="keyword">self</span>.run_task(task, core)?;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// Wait for work</span></span><br><span class="line">                core = <span class="keyword">self</span>.park(core);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        core.pre_shutdown(&amp;<span class="keyword">self</span>.worker);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Signal shutdown</span></span><br><span class="line">        <span class="keyword">self</span>.worker.shared.shutdown(core);</span><br><span class="line">        <span class="literal">Err</span>(())</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>短小精干，整个调度轮训代码就这么多，有点借鉴了golang的调度器，基本都是</p><ol><li>LIFO slot: 从优先队列获取协程任务执行</li><li>local queue: 从本地队列获取协程任务执行</li><li>global queue: 从全局队列获取协程任务执行</li><li>steal: 从其他线程队列窃取任务来执行</li></ol><h3 id="3-没有任务时进入事件轮训-epoll-wait"><a href="#3-没有任务时进入事件轮训-epoll-wait" class="headerlink" title="3. 没有任务时进入事件轮训(epoll_wait)"></a>3. 没有任务时进入事件轮训(epoll_wait)</h3><p>可以看到上面4个队列都没有获取到任务后，会调用<code>sel.park(core)</code>让出cpu，等待有事件到来时恢复线程后继续寻找下一个等待执行的协程任务</p><p>park被层层包装后最终的实现是通过<code>epoll_wait</code>来等待事件<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">impl</span> Park <span class="keyword">for</span> runtime::Driver::Driver &#123;</span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">park</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) -&gt; <span class="built_in">Result</span>&lt;(), Self::Error&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.inner.park()      <span class="comment">// call time driver's park</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span>&lt;P&gt; Park <span class="keyword">for</span> time::driver::Driver&lt;P&gt; &#123;</span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">park</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) -&gt; <span class="built_in">Result</span>&lt;(), Self::Error&gt; &#123;</span><br><span class="line">        <span class="comment">// ... preprocess for time</span></span><br><span class="line">        <span class="comment">// may call self.park.park_timeout(duration)?;</span></span><br><span class="line">        <span class="keyword">self</span>.park.park()?;     <span class="comment">// call process driver's park</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">self</span>.handle.process();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> Park <span class="keyword">for</span> process::unix::driver::Driver &#123;</span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">park</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) -&gt; <span class="built_in">Result</span>&lt;(), Self::Error&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.park.park()?;      <span class="comment">// call signal driver's park</span></span><br><span class="line">        <span class="keyword">self</span>.inner.process();</span><br><span class="line">        <span class="literal">Ok</span>(())</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">impl</span> Park <span class="keyword">for</span> signal::unix::driver::Driver &#123;</span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">park</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) -&gt; <span class="built_in">Result</span>&lt;(), Self::Error&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.park.park()?;      <span class="comment">// call io driver's park</span></span><br><span class="line">        <span class="keyword">self</span>.process();</span><br><span class="line">        <span class="literal">Ok</span>(())</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">impl</span> Park <span class="keyword">for</span> io::Driver &#123;</span><br><span class="line">    <span class="function"><span class="keyword">fn</span> <span class="title">park</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>) -&gt; io::<span class="built_in">Result</span>&lt;()&gt; &#123;</span><br><span class="line">        <span class="keyword">self</span>.turn(<span class="literal">None</span>)?;</span><br><span class="line">        <span class="literal">Ok</span>(())</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>继续调用<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">turn</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, max_wait: <span class="built_in">Option</span>&lt;Duration&gt;) -&gt; io::<span class="built_in">Result</span>&lt;()&gt; &#123;</span><br><span class="line">    <span class="comment">// 省略了一些events处理</span></span><br><span class="line">    <span class="comment">// 重点就是这个</span></span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span>.poll.poll(&amp;<span class="keyword">mut</span> events, max_wait) &#123;</span><br><span class="line">        <span class="literal">Ok</span>(_) =&gt; &#123;&#125;</span><br><span class="line">        <span class="literal">Err</span>(<span class="keyword">ref</span> e) <span class="keyword">if</span> e.kind() == io::ErrorKind::Interrupted =&gt; &#123;&#125;</span><br><span class="line">        <span class="literal">Err</span>(e) =&gt; <span class="keyword">return</span> <span class="literal">Err</span>(e),</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//省略了一些处理</span></span><br><span class="line"></span><br><span class="line">    <span class="literal">Ok</span>(())</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>poll的实际实现是调用了mio的方法<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">poll</span></span>(&amp;<span class="keyword">mut</span> <span class="keyword">self</span>, events: &amp;<span class="keyword">mut</span> Events, timeout: <span class="built_in">Option</span>&lt;Duration&gt;) -&gt; io::<span class="built_in">Result</span>&lt;()&gt; &#123;</span><br><span class="line">    <span class="keyword">self</span>.registry.selector.select(events.sys(), timeout)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>select函数里通过syscall调用了epoll_wait,并且传入的timeout=-1，会一直阻塞直到有事件到来<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">pub</span> <span class="function"><span class="keyword">fn</span> <span class="title">select</span></span>(&amp;<span class="keyword">self</span>, events: &amp;<span class="keyword">mut</span> Events, timeout: <span class="built_in">Option</span>&lt;Duration&gt;) -&gt; io::<span class="built_in">Result</span>&lt;()&gt; &#123;</span><br><span class="line">    <span class="comment">//省略一些。。。</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">let</span> timeout = timeout</span><br><span class="line">        .map(|to| cmp::min(to.as_millis(), MAX_SAFE_TIMEOUT) <span class="keyword">as</span> libc::c_int)</span><br><span class="line">        .unwrap_or(-<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    events.clear();</span><br><span class="line">    syscall!(epoll_wait(</span><br><span class="line">        <span class="keyword">self</span>.ep,</span><br><span class="line">        events.as_mut_ptr(),</span><br><span class="line">        events.capacity() <span class="keyword">as</span> <span class="built_in">i32</span>,</span><br><span class="line">        timeout,</span><br><span class="line">    ))</span><br><span class="line">    .map(|n_events| &#123;</span><br><span class="line">        <span class="comment">// This is safe because `epoll_wait` ensures that `n_events` are</span></span><br><span class="line">        <span class="comment">// assigned.</span></span><br><span class="line">        <span class="keyword">unsafe</span> &#123; events.set_len(n_events <span class="keyword">as</span> <span class="built_in">usize</span>) &#125;;</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h3 id="4-事件到来后的协程唤醒"><a href="#4-事件到来后的协程唤醒" class="headerlink" title="4. 事件到来后的协程唤醒"></a>4. 事件到来后的协程唤醒</h3><p>上面epoll_wait过后，有事件到来则会进行事件分发<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">#![allow(unused)]</span></span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() &#123;</span><br><span class="line"><span class="keyword">match</span> <span class="keyword">self</span>.poll.poll(&amp;<span class="keyword">mut</span> events, max_wait)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> event <span class="keyword">in</span> events.iter() &#123;</span><br><span class="line">    <span class="keyword">let</span> token = event.token();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> token != TOKEN_WAKEUP &#123;</span><br><span class="line">        <span class="keyword">self</span>.dispatch(token, Ready::from_mio(event));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>接着就会进行事件处理，唤醒对应的线程，将task投递到队列中<br><figure class="highlight rust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">#![allow(unused)]</span></span><br><span class="line"><span class="function"><span class="keyword">fn</span> <span class="title">main</span></span>() &#123;</span><br><span class="line"><span class="comment">// set_readiness:</span></span><br><span class="line"><span class="keyword">let</span> <span class="keyword">mut</span> current = <span class="keyword">self</span>.readiness.load(Acquire);</span><br><span class="line"></span><br><span class="line"><span class="keyword">loop</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> current_generation = GENERATION.unpack(current);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 1.</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">let</span> <span class="literal">Some</span>(token) = token &#123;</span><br><span class="line">        <span class="keyword">if</span> GENERATION.unpack(token) != current_generation &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">Err</span>(());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2.</span></span><br><span class="line">    <span class="keyword">let</span> current_readiness = Ready::from_usize(current);</span><br><span class="line">    <span class="keyword">let</span> new = f(current_readiness);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3.</span></span><br><span class="line">    <span class="keyword">let</span> packed = <span class="keyword">match</span> tick &#123;</span><br><span class="line">        Tick::Set(t) =&gt; TICK.pack(t <span class="keyword">as</span> <span class="built_in">usize</span>, new.as_usize()),</span><br><span class="line">        Tick::Clear(t) =&gt; &#123;</span><br><span class="line">            <span class="keyword">if</span> TICK.unpack(current) <span class="keyword">as</span> <span class="built_in">u8</span> != t &#123;</span><br><span class="line">                <span class="comment">// Trying to clear readiness with an old event!</span></span><br><span class="line">                <span class="keyword">return</span> <span class="literal">Err</span>(());</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            TICK.pack(t <span class="keyword">as</span> <span class="built_in">usize</span>, new.as_usize())</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 4.</span></span><br><span class="line">    <span class="keyword">let</span> next = GENERATION.pack(current_generation, packed);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">match</span> <span class="keyword">self</span></span><br><span class="line">        .readiness</span><br><span class="line">        .compare_exchange(current, next, AcqRel, Acquire)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="literal">Ok</span>(_) =&gt; <span class="keyword">return</span> <span class="literal">Ok</span>(()),</span><br><span class="line">        <span class="comment">// we lost the race, retry!</span></span><br><span class="line">        <span class="literal">Err</span>(actual) =&gt; current = actual,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>所有都处理完后，park函数返回到最开始调度循环，重新开始新的一轮任务处理</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>rust的协程主要是靠编译器的状态机实现 + 三方的调度器实现</p><p>tokio调度器的逻辑也有些借鉴了golang的调度器</p><p>大体分析的设计就是这样，当然细节没有深挖都是非常多的，但不妨碍我们对rust协程实现的理解</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;本次文章专注于分析rust的协程实现，并提前介绍它与其他语言的协程有什么区别&lt;/p&gt;
&lt;p&gt;接下来我们就从有栈和无栈切入了解协程在内存方面的布局，典型的有栈协程就是golang，这里的&lt;code&gt;有栈&lt;/code&gt;其实是一个潜台词，代表的是需要单独申请堆内存作为栈的意思&lt;/
      
    
    </summary>
    
      <category term="compiler" scheme="http://wiki.brewlin.com/categories/compiler/"/>
    
    
      <category term="linux" scheme="http://wiki.brewlin.com/tags/linux/"/>
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="golang" scheme="http://wiki.brewlin.com/tags/golang/"/>
    
      <category term="rust" scheme="http://wiki.brewlin.com/tags/rust/"/>
    
      <category term="coroutine" scheme="http://wiki.brewlin.com/tags/coroutine/"/>
    
      <category term="scheduler" scheme="http://wiki.brewlin.com/tags/scheduler/"/>
    
  </entry>
  
  <entry>
    <title>稀疏图-邻接表</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/graph/%E7%A8%80%E7%96%8F%E5%9B%BE-%E9%82%BB%E6%8E%A5%E8%A1%A8/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/graph/稀疏图-邻接表/</id>
    <published>2021-02-10T14:41:28.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>无向图,邻接表的空间结构:</p><p><img src="/images/blog/graph/RMWJBRZSVR.png" alt=""></p><ol><li>邻接表主要由数组 + 链表的方式实现</li><li>如图表的第一列为<code>数组的索引</code>，代表所有的顶点,<code>0,1,2,3,4</code></li><li>如图表的每一行都对应了第列的所有相连接的点</li><li>行的关系是采用链表或者队列的方式实现</li></ol><p>总的结构就是一个非常简单的数组+链表方式组成,当前demo是<code>vector&lt;vector&lt;*&gt;&gt;</code>方式实现，方便理解</p><p>实现解析:</p><ol><li>用<code>Edge</code>来表示每条边，<code>a</code>代表左顶点,<code>b</code>代表右顶点,<code>v</code>代表边的权值（比如边的长度）</li><li><p><code>SparseGraph</code>的主要数据结构</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">bool</span> directed;<span class="comment">//表示是否是有方向，如果为fasle表示无向，在添加边的时候需要两端都条件一条</span></span><br><span class="line"><span class="built_in">vector</span>&lt;<span class="built_in">vector</span>&lt;Edge*&gt;&gt; g;<span class="comment">//存储所有的顶点关系，一维数组表示所有顶点，二维数组表示所有和顶点关联的其他顶点</span></span><br><span class="line"><span class="keyword">int</span> points<span class="comment">//顶点个数</span></span><br><span class="line"><span class="keyword">int</span> edges;<span class="comment">//边的个数</span></span><br></pre></td></tr></table></figure></li><li><p>添加一条边</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">非常简单，直接push到 g[v]队列里就可以了，表示w是属于g[v]所有可以连接的边的其中一个</span><br><span class="line">void addEdge(int v,int w,int weight)&#123;</span><br><span class="line">    assert(v &lt; points);</span><br><span class="line">    g[v].push_back(new Edge(v,w,weight));</span><br><span class="line">    //无向图需要 反向自动添加另外一条边</span><br><span class="line">    if(!directed)</span><br><span class="line">        g[w].push_back(new Edge(w,v,weight));</span><br><span class="line">    edges ++;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>查询某条边是否存在<code>O(E)</code>E为边数</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">因为该图的边对应关系是一个链表或者队列来存储的，所有需要有个遍历的步骤</span><br><span class="line">bool isEdge(int v,int w)&#123;</span><br><span class="line">    for(auto i : g[v])&#123;</span><br><span class="line">        if(i-&gt;b == w)&#123;</span><br><span class="line">            return true;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return false;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>打印图</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">void print()&#123;</span><br><span class="line">    //双向遍历即可</span><br><span class="line">    for(int i  = 0; i &lt; points ; i ++ )&#123;</span><br><span class="line">        cout &lt;&lt; i &lt;&lt; &quot;: &quot;;</span><br><span class="line">        for(auto i : g[i])&#123;</span><br><span class="line">            cout &lt;&lt; i-&gt;b &lt;&lt; &quot; &quot;;</span><br><span class="line">        &#125;</span><br><span class="line">        cout &lt;&lt; endl;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><p>完整代码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">struct Edge&#123;</span><br><span class="line">public:</span><br><span class="line">    int a;</span><br><span class="line">    int b;</span><br><span class="line">    int v;</span><br><span class="line">    Edge(int a,int b,int v):a(a),b(b),v(v)&#123;&#125;</span><br><span class="line">    ~Edge()&#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">class SparseGraph&#123;</span><br><span class="line">public:</span><br><span class="line">    //是否是有向图</span><br><span class="line">    bool directed;</span><br><span class="line">    //设置二维矩阵 表示图关系</span><br><span class="line">    vector&lt;vector&lt;Edge*&gt;&gt; g;</span><br><span class="line">    //设置顶点个数</span><br><span class="line">    int points;</span><br><span class="line">    //边的个数</span><br><span class="line">    int edges;</span><br><span class="line">    SparseGraph(int points,bool directed):points(points),directed(directed)&#123;</span><br><span class="line">        //初始化一个邻接表</span><br><span class="line">        g = vector&lt;vector&lt;Edge*&gt;&gt;(points,vector&lt;Edge*&gt;());</span><br><span class="line">    &#125;</span><br><span class="line">    void addEdge(int v,int w,int weight)&#123;</span><br><span class="line">        assert(v &lt; points);</span><br><span class="line">        g[v].push_back(new Edge(v,w,weight));</span><br><span class="line">        //无向图需要 反向自动添加另外一条边</span><br><span class="line">        if(!directed)</span><br><span class="line">            g[w].push_back(new Edge(w,v,weight));</span><br><span class="line">        edges ++;</span><br><span class="line">    &#125;</span><br><span class="line">    void print()&#123;</span><br><span class="line">        //双向遍历即可</span><br><span class="line">        for(int i  = 0; i &lt; points ; i ++ )&#123;</span><br><span class="line">            cout &lt;&lt; i &lt;&lt; &quot;: &quot;;</span><br><span class="line">            for(auto i : g[i])&#123;</span><br><span class="line">                cout &lt;&lt; i-&gt;b &lt;&lt; &quot; &quot;;</span><br><span class="line">            &#125;</span><br><span class="line">            cout &lt;&lt; endl;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;无向图,邻接表的空间结构:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog/graph/RMWJBRZSVR.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;邻接表主要由数组 + 链表的方式实现&lt;/li&gt;
&lt;li&gt;如图表的第一列为&lt;code&gt;数组的索引&lt;/c
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/categories/algorithm/graph/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/tags/graph/"/>
    
  </entry>
  
  <entry>
    <title>稀疏图-连通分量-bfs-dfs</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/graph/%E7%A8%80%E7%96%8F%E5%9B%BE-%E8%BF%9E%E9%80%9A%E5%88%86%E9%87%8F-bfs-dfs/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/graph/稀疏图-连通分量-bfs-dfs/</id>
    <published>2021-02-09T14:50:11.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>[toc]</p><p>针对稀疏图讲解，且结构按<a href="">稀疏图-邻接表SparseGraph</a>中的结构进行测试</p><h2 id="全局参数和初始化说明"><a href="#全局参数和初始化说明" class="headerlink" title="全局参数和初始化说明"></a>全局参数和初始化说明</h2><p>全局参数<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">SparseGraph graph(5,false);</span><br><span class="line">bool* visited;</span><br><span class="line">//求连通分量</span><br><span class="line">int count = 0</span><br></pre></td></tr></table></figure></p><ol><li>构建5个顶点的图，且是无向图</li><li>在遍历图的时候需要有个结构来存储每个节点是否已经访问过了<code>visited</code></li><li>count 用来记录连通分量</li></ol><p>初始化<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">visited = new bool[5];</span><br><span class="line">for(int i = 0; i &lt; 5; i ++)</span><br><span class="line">    visited[i] = false;</span><br><span class="line">graph.addEdge(0,1,1);</span><br><span class="line">graph.addEdge(0,3,1);</span><br><span class="line">graph.addEdge(1,1,1);</span><br><span class="line">graph.addEdge(1,3,1);</span><br><span class="line">graph.addEdge(2,4,1);</span><br><span class="line">graph.addEdge(2,1,1);</span><br></pre></td></tr></table></figure></p><p>总的来说就是构建一个记录节点是否访问的数组，然后增加一些默认的边</p><h2 id="dfs-深度优先遍历"><a href="#dfs-深度优先遍历" class="headerlink" title="dfs 深度优先遍历"></a>dfs 深度优先遍历</h2><p>流程图:<br><img src="/images/blog/graph/YOQPRHWROJ.png" alt=""></p><ol><li>从0节点开始递归遍历，0只有2一条边，开始遍历2</li></ol><p><img src="/images/blog/graph/CWDTXGINVD.png" alt=""></p><ol start="2"><li>2对应有<code>4,3</code>两条边，先遍历4，4有<code>1,3</code>两条边，继续遍历1</li></ol><p><img src="/images/blog/graph/ZABCEOJUYU.png" alt=""></p><ol start="3"><li>1对应有3 和4两条边，<code>4已经遍历过了</code>只遍历3</li></ol><p>总的访问顺序:<code>0-2-4-1-3</code>,但其实完整的遍历顺序的话会有很多重复访问，只是已经被访问过了就不会再继续访问了</p><p>==注意==:如果连通分量只有1的话，最外层循环是不需要的只需要<code>depth_first_search(0)</code>即可完成深度优先遍历，因为==从中任何一个顶点都能遍历完整个图==</p><p>最外层从每个顶点开始沿着边开始进行递归遍历<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span> ; i &lt; graph.points ;i ++)</span><br><span class="line">    <span class="keyword">if</span>(!visited[i])&#123;</span><br><span class="line">        depth_first_search(i);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></p><p>进行递归遍历，如果已经遍历过了则不需要再次进行遍历<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">void depth_first_search(int v)</span><br><span class="line">&#123;</span><br><span class="line">    if(visited[v]) return;</span><br><span class="line">    //记录当前节点为已经访问过了</span><br><span class="line">    visited[v] = true;</span><br><span class="line">    cout &lt;&lt; v &lt;&lt; &quot; &quot;;</span><br><span class="line">    //遍历某个点对应的所有的边</span><br><span class="line">    for(auto i : graph.g[v])&#123;</span><br><span class="line">        depth_first_search(i-&gt;b);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="bfs-广度优先遍历"><a href="#bfs-广度优先遍历" class="headerlink" title="bfs 广度优先遍历"></a>bfs 广度优先遍历</h2><p>流程图:<br><img src="/images/blog/graph/CMYDNAHBJB.png" alt=""></p><ol><li>从0开始遍历，将0对应的边<code>2</code>加入未访问队列,继续读取2，然后将2对应的边<code>4,3</code>加入为访问者队列</li></ol><p><img src="/images/blog/graph/KBGRAJPYXS.png" alt=""></p><ol start="2"><li>访问<code>4</code>，将对应的边<code>1</code>加入队列，继续访问3,已经没有未访问的边了，无需在加入队列</li></ol><p><img src="/images/blog/graph/VGDCUFFMCA.png" alt=""></p><ol start="3"><li>将最后一个1访问后结束遍历</li></ol><p>其实和其他广度优先遍历的机制是一样的</p><p>实现代码:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">//广度优先遍历</span><br><span class="line">void breadth_first_search()</span><br><span class="line">&#123;</span><br><span class="line">    //设置一个队列</span><br><span class="line">    queue&lt;int&gt; q;</span><br><span class="line">    q.push(0);</span><br><span class="line">    while(!q.empty())&#123;</span><br><span class="line">        int v = q.front();</span><br><span class="line">        q.pop();</span><br><span class="line">        for(auto i : graph.g[v])&#123;</span><br><span class="line">            if(!visited[i-&gt;b])&#123;</span><br><span class="line">                q.push(i-&gt;b);</span><br><span class="line">                cout &lt;&lt; i-&gt;b  &lt;&lt;&quot; &quot;;</span><br><span class="line">                visited[i-&gt;b] = true;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="连通分量"><a href="#连通分量" class="headerlink" title="连通分量"></a>连通分量</h2><p><img src="/images/blog/graph/JJFGFVJPKR.png" alt=""></p><p>如上图是有两个独立的块关系的，这种通过任意某个点无法关联所有电的情况就存在多个连通分量，上图的连通分量为<code>2</code></p><p>其实在求连通分量只需要在深度优先遍历上增加一个步骤即可</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">for(int i = 0 ; i &lt; graph.points ;i ++)</span><br><span class="line">    if(!visited[i])&#123;</span><br><span class="line">        count ++;</span><br><span class="line">        depth_first_search(i);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><ol><li>==上面特别强调过==:如果连通分量为1（代表任一节点进行深度遍历都可以访问完所有节点），但是在有多个连通分量的图中是不可能访问完所有节点的</li><li>所以需要在外面一次对每个顶点都进行一次遍历，如果后面顶点没有被访问过则一定能够证明有多个连通分量存在</li></ol><p>例如: 第一次从0开始访问，只能访问到2。那么外层继续访问到1时，发现居然没有被访问过，那么肯定1所在的网状关系是独立与0所在的区域的，连通分量<code>count ++</code>.</p><p>后面依次对<code>2,3,4</code>顶点继续深度优先遍历时发现已经被访问过了，则结束访问，至此连通分量为<code>2</code></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;[toc]&lt;/p&gt;
&lt;p&gt;针对稀疏图讲解，且结构按&lt;a href=&quot;&quot;&gt;稀疏图-邻接表SparseGraph&lt;/a&gt;中的结构进行测试&lt;/p&gt;
&lt;h2 id=&quot;全局参数和初始化说明&quot;&gt;&lt;a href=&quot;#全局参数和初始化说明&quot; class=&quot;headerlink&quot; title
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/categories/algorithm/graph/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/tags/graph/"/>
    
  </entry>
  
  <entry>
    <title>稀疏图-寻路</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/graph/%E7%A8%80%E7%96%8F%E5%9B%BE-%E5%AF%BB%E8%B7%AF/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/graph/稀疏图-寻路/</id>
    <published>2021-02-08T14:35:21.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>寻路的实现是在遍历图的过程中增加一个数组<code>from</code>，<code>from[i]</code>记录了到<code>i</code>节点的源节点</p><p>例如: <code>0 - 2</code> 由0开始访问到节点<code>2</code>，那么有: <code>from[2] = 0</code></p><h2 id="流程图"><a href="#流程图" class="headerlink" title="流程图"></a>流程图</h2><p>从0开始遍历整个图，默认初始化为<code>from[0] = -1</code> -&gt; <code>from[2] = 0</code><br><img src="/images/blog/graph/HRUBODGZFS.png" alt=""></p><p><code>from[4] = 2</code> -&gt; <code>from[1] = 4</code><br><img src="/images/blog/graph/SYEZUXNABT.png" alt=""></p><p><code>from[3] = 1</code> 结束所有遍历<br><img src="/images/blog/graph/KHDMSBGLUU.png" alt=""></p><p>有了<code>from</code>路径以后，就可以查到任意的路径信息了</p><h2 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h2><p>先初始化<code>visited</code> 和  <code>from</code> 等两个数组，分别用来记录是否节点被访问，以及访问路径关系<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">visited = <span class="keyword">new</span> <span class="keyword">bool</span>[<span class="number">5</span>];</span><br><span class="line">from    = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">5</span>];</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i ++)&#123;</span><br><span class="line">    from[i] = <span class="number">-1</span>;</span><br><span class="line">    visited[i] = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line">graph.addEdge(<span class="number">0</span>,<span class="number">2</span>,<span class="number">1</span>);</span><br><span class="line">graph.addEdge(<span class="number">1</span>,<span class="number">3</span>,<span class="number">1</span>);</span><br><span class="line">graph.addEdge(<span class="number">1</span>,<span class="number">4</span>,<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">graph.addEdge(<span class="number">2</span>,<span class="number">0</span>,<span class="number">1</span>);</span><br><span class="line">graph.addEdge(<span class="number">3</span>,<span class="number">1</span>,<span class="number">1</span>);</span><br><span class="line">graph.addEdge(<span class="number">3</span>,<span class="number">4</span>,<span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">graph.addEdge(<span class="number">4</span>,<span class="number">1</span>,<span class="number">1</span>);</span><br><span class="line">graph.addEdge(<span class="number">4</span>,<span class="number">3</span>,<span class="number">1</span>);</span><br></pre></td></tr></table></figure></p><p>构建如下图:<br><img src="/images/blog/graph/UUEDFINUMZ.png" alt=""></p><p>从2节点开始进行寻路,保存了2开始的所有路径<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">int p = 2;</span><br><span class="line">depth_first_search(p);</span><br></pre></td></tr></table></figure></p><p>直接判断是否存在<code>2 - 4</code>节点的路:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">if(!visited[3])</span><br><span class="line">    cout &lt;&lt; &quot;不存在&quot; &lt;&lt;endl;</span><br></pre></td></tr></table></figure></p><p>因为从2开始的遍历，是无法遍历到4的，所以<code>from [4]</code>肯定是<code>-1</code>所以<code>2-4</code>不通</p><h2 id="查看路径"><a href="#查看路径" class="headerlink" title="查看路径"></a>查看路径</h2><p>根据上面的测试用例，在新增一条关系使得该图为一个完整的单连通分量图<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">graph.addEdge(2,4,1);</span><br></pre></td></tr></table></figure></p><p><img src="/images/blog/graph/WEDNUOXDLT.png" alt=""></p><p>假入我们从<code>0</code>节点开始寻路，那么有如下<code>From</code>表<br><img src="/images/blog/graph/PTISTUAZKA.png" alt=""></p><p>查看<code>0-3</code>的路线<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> i = <span class="number">3</span>;</span><br><span class="line"><span class="built_in">cout</span> &lt;&lt; <span class="string">"3 "</span> ;</span><br><span class="line"><span class="keyword">while</span>(i != <span class="number">-1</span> &amp;&amp; i != from[i] &amp;&amp; from[i] != <span class="number">-1</span>)&#123;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="string">"-&gt;"</span>;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; from[i];</span><br><span class="line">    i = from[i];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>输出结果<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">3 -&gt; 1 -&gt; 4 -&gt; 2 -&gt; 0</span><br></pre></td></tr></table></figure></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;寻路的实现是在遍历图的过程中增加一个数组&lt;code&gt;from&lt;/code&gt;，&lt;code&gt;from[i]&lt;/code&gt;记录了到&lt;code&gt;i&lt;/code&gt;节点的源节点&lt;/p&gt;
&lt;p&gt;例如: &lt;code&gt;0 - 2&lt;/code&gt; 由0开始访问到节点&lt;code&gt;2&lt;/code&gt;，
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/categories/algorithm/graph/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/tags/graph/"/>
    
  </entry>
  
  <entry>
    <title>Dijkstra最短路径</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/graph/dijkstra/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/graph/dijkstra/</id>
    <published>2021-02-06T09:50:43.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>dijkstra利用松弛操作找到最短的路线距离，假设当前图结构为稀疏图，结构如下:<br><img src="/images/blog/graph/BGTRJIKUBX.png" alt=""></p><p>按照直观来说，<code>0-4</code>的最短路径有如下几种选择<br><img src="/images/blog/graph/VVKKYYDLXB.png" alt=""></p><p>最优路显然是<code>0-2-3-4</code>,权值只有<code>7</code>当属最小</p><p>接下来看看如何实现该寻路过程</p><h2 id="结构说明"><a href="#结构说明" class="headerlink" title="结构说明"></a>结构说明</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">SparseGraph <span class="title">graph</span><span class="params">(<span class="number">5</span>,<span class="literal">false</span>)</span></span>;</span><br><span class="line"><span class="keyword">bool</span>* visited;</span><br><span class="line"><span class="keyword">int</span> * from;</span><br><span class="line"><span class="keyword">int</span> * distTo;</span><br></pre></td></tr></table></figure><p>总共4个额外数组来维护寻路过程的记录</p><ol><li><code>visited</code>,当对某一节点<code>left</code>以及所有对应的边<code>right</code>进行访问时对<code>visited[left] = true</code>进行标记，表明以及访问过当前节点了</li><li><code>from</code>,这个作为辅助数据，在计算到最短路径后，如果需要打印完整的路径，则需要from来记录每次寻路的过程</li><li><code>distTo</code>，<code>distTo[i]</code>记录了原点<code>s</code>到<code>i</code>的最短距离</li></ol><h2 id="bfs进行寻路"><a href="#bfs进行寻路" class="headerlink" title="bfs进行寻路"></a>bfs进行寻路</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">breadth_first_search</span><span class="params">(<span class="keyword">int</span> s)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//采用最小索引堆来做,默认初始化n个顶点空间</span></span><br><span class="line">    <span class="function">IndexMinHeap&lt;<span class="keyword">int</span>&gt; <span class="title">qp</span><span class="params">(graph.points)</span></span>;</span><br><span class="line">    <span class="comment">//默认插入一个 源起始点</span></span><br><span class="line">    qp.insert(s,<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">while</span>(!qp.isEmpty())&#123;</span><br><span class="line">        <span class="comment">//每次获取s原点最短的那个距离</span></span><br><span class="line">        <span class="keyword">int</span> left = qp.extraMinIndex();</span><br><span class="line">        <span class="comment">//标记该节点已经被访问过了</span></span><br><span class="line">        visited[left] = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">//接下来访问该节点的所有邻边</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span> edge : graph.g[left])&#123;</span><br><span class="line">            <span class="comment">//查看对应的邻边有没有被访问过,edge-&gt;a 就是当前id, edge-&gt;b才是领边 edge-&gt;v 代表权值</span></span><br><span class="line">            <span class="keyword">int</span> right  = edge-&gt;b;</span><br><span class="line">            <span class="keyword">int</span> length = edge-&gt;v; </span><br><span class="line">            <span class="keyword">if</span>(!visited[right])&#123;</span><br><span class="line">                <span class="comment">//判断from 路径有没有记录 || 如果[s -&gt; left + left-&gt;right] &lt; [s -&gt; right] 说明找到了更短的距离</span></span><br><span class="line">                <span class="keyword">if</span>(from[right] == <span class="number">-1</span> || distTo[left] + length &lt; distTo[right])&#123;</span><br><span class="line">                    <span class="comment">//更新当前被访问的right节点的来源节点left</span></span><br><span class="line">                    from[right] = left;</span><br><span class="line">                    <span class="comment">//更新距离: s-&gt;right = s-&gt;left + left-&gt;right</span></span><br><span class="line">                    distTo[right] = distTo[left] + length;</span><br><span class="line">                    <span class="comment">//判断队列里有没有访问过当前的 right节点</span></span><br><span class="line">                    <span class="keyword">if</span>(qp.contain(right)) qp.change(right,distTo[right]);</span><br><span class="line">                    <span class="keyword">else</span> qp.insert(right,distTo[right]);</span><br><span class="line">                    </span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>从s原点开始,加入到最小索引堆中<code>qp.insert(0,0)</code>,因为<code>0-0</code>的距离默认为0，所以第一个节点默认权值为0 </li><li><p>接下来就是对<code>0</code>点的各个边进行扫描，如果发现有更短的距离，则直接更新新的距离</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">如上图所示:</span><br><span class="line">0 -&gt; 4 : 距离为9  ，那么 distTo[4] = 9;</span><br><span class="line"></span><br><span class="line">0-&gt;2-&gt;4 : 因为 0-&gt;2 = 2, 2-&gt;4 = 6 , 存在 0-2-4(8) &lt; 0-4(9)</span><br><span class="line"></span><br><span class="line">所以找到了更优的路径：</span><br><span class="line">o-&gt;4 = 8,</span><br><span class="line"></span><br><span class="line">依次类推，实现最短路径的查找</span><br></pre></td></tr></table></figure></li><li><p>不断重复步骤2，直到遍历完可达的顶点后，结束寻路计算</p></li><li>最后的结果应该如下模式<br><img src="/images/blog/graph/XWBXUNRBCW.png" alt=""></li></ol><h2 id="路径展示"><a href="#路径展示" class="headerlink" title="路径展示"></a>路径展示</h2><p>检查是否通路<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//判断0-4是否有路</span></span><br><span class="line"><span class="keyword">if</span>(distTo[<span class="number">4</span>])&#123;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="string">" 1-4 有路:"</span> &lt;&lt; distTo[<span class="number">4</span>] &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="string">" 2-4 有路"</span> &lt;&lt;<span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>路径展示<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> i = <span class="number">4</span>;</span><br><span class="line"><span class="built_in">cout</span> &lt;&lt; <span class="string">"4 "</span> ;</span><br><span class="line"><span class="keyword">while</span>(i != <span class="number">-1</span> )&#123;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="string">"-&gt;"</span>;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; from[i];</span><br><span class="line">    i = from[i];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h2 id="完整的寻路流程图"><a href="#完整的寻路流程图" class="headerlink" title="完整的寻路流程图"></a>完整的寻路流程图</h2><p><img src="/images/blog/graph/QYGEZQYLNK.png" alt=""></p><h2 id="完整的代码"><a href="#完整的代码" class="headerlink" title="完整的代码"></a>完整的代码</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"SparseGraph.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"IndexMinHeap.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stack&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;queue&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function">SparseGraph <span class="title">graph</span><span class="params">(<span class="number">5</span>,<span class="literal">false</span>)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//代表是否访问过</span></span><br><span class="line"><span class="keyword">bool</span>* visited;</span><br><span class="line"><span class="comment">//记录路径，from[i] 表示查找的路径i的上一个节点</span></span><br><span class="line"><span class="keyword">int</span> * from;</span><br><span class="line"><span class="comment">//记录权值 这里的权值采用int来表示</span></span><br><span class="line"><span class="keyword">int</span> * distTo;</span><br><span class="line"><span class="comment">//bfs 广度优先遍历</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">breadth_first_search</span><span class="params">(<span class="keyword">int</span> s)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//采用最小索引堆来做,默认初始化n个顶点空间</span></span><br><span class="line">    <span class="function">IndexMinHeap&lt;<span class="keyword">int</span>&gt; <span class="title">qp</span><span class="params">(graph.points)</span></span>;</span><br><span class="line">    <span class="comment">//默认插入一个 源起始点</span></span><br><span class="line">    qp.insert(s,<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span>(!qp.isEmpty())&#123;</span><br><span class="line">        <span class="comment">//每次获取s原点最短的那个距离</span></span><br><span class="line">        <span class="keyword">int</span> left = qp.extraMinIndex();</span><br><span class="line">        <span class="comment">//标记该节点已经被访问过了</span></span><br><span class="line">        visited[left] = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">//接下来访问该节点的所有邻边</span></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">auto</span> edge : graph.g[left])&#123;</span><br><span class="line">            <span class="comment">//查看对应的邻边有没有被访问过,edge-&gt;a 就是当前id, edge-&gt;b才是领边 edge-&gt;v 代表权值</span></span><br><span class="line">            <span class="keyword">int</span> right  = edge-&gt;b;</span><br><span class="line">            <span class="keyword">int</span> length = edge-&gt;v; </span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span>(!visited[right])&#123;</span><br><span class="line">                <span class="comment">//判断from 路径有没有记录 || 如果[s -&gt; left + left-&gt;right] &lt; [s -&gt; right] </span></span><br><span class="line">                <span class="keyword">if</span>(from[right] == <span class="number">-1</span> || distTo[left] + length &lt; distTo[right])&#123;</span><br><span class="line">                    <span class="comment">//更新当前被访问的right节点的来源节点left</span></span><br><span class="line">                    from[right] = left;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">//更新距离: s-&gt;right = s-&gt;left + left-&gt;right</span></span><br><span class="line">                    distTo[right] = distTo[left] + length;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">//判断队列里有没有访问过当前的 right节点</span></span><br><span class="line">                    <span class="keyword">if</span>(qp.contain(right))&#123;</span><br><span class="line">                        qp.change(right,distTo[right]);</span><br><span class="line">                    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                        qp.insert(right,distTo[right]);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    visited = <span class="keyword">new</span> <span class="keyword">bool</span>[<span class="number">5</span>];</span><br><span class="line">    from    = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">5</span>];</span><br><span class="line">    distTo  = <span class="keyword">new</span> <span class="keyword">int</span>[<span class="number">5</span>];</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; i ++)&#123;</span><br><span class="line">        from[i] = <span class="number">-1</span>;</span><br><span class="line">        visited[i] = <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">//disTo[i] 记录了原点s到i的最短距离</span></span><br><span class="line">        distTo[i] = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    graph.addEdge(<span class="number">0</span>,<span class="number">1</span>,<span class="number">1</span>);</span><br><span class="line">    graph.addEdge(<span class="number">0</span>,<span class="number">4</span>,<span class="number">2</span>);</span><br><span class="line">    graph.addEdge(<span class="number">1</span>,<span class="number">4</span>,<span class="number">6</span>);</span><br><span class="line">    graph.addEdge(<span class="number">1</span>,<span class="number">3</span>,<span class="number">3</span>);</span><br><span class="line">    graph.addEdge(<span class="number">3</span>,<span class="number">4</span>,<span class="number">4</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//标记从1开始到其他任意节点的最短路径</span></span><br><span class="line">    <span class="keyword">int</span> p = <span class="number">1</span>;</span><br><span class="line">    breadth_first_search(p);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//判断1-4是否有路</span></span><br><span class="line">    <span class="keyword">if</span>(distTo[<span class="number">4</span>])&#123;</span><br><span class="line">        <span class="built_in">cout</span> &lt;&lt; <span class="string">" 1-4 有路:"</span> &lt;&lt; distTo[<span class="number">4</span>] &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">        <span class="built_in">cout</span> &lt;&lt; <span class="string">" 2-4 有路"</span> &lt;&lt;<span class="built_in">endl</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// //查看 2-3有没有路径</span></span><br><span class="line">    <span class="comment">// if(!visited[3])&#123;</span></span><br><span class="line">    <span class="comment">//     cout &lt;&lt; "不存在" &lt;&lt;endl;</span></span><br><span class="line">    <span class="comment">// &#125;</span></span><br><span class="line">    <span class="comment">// //查看路径</span></span><br><span class="line">    <span class="keyword">int</span> i = <span class="number">4</span>;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="string">"4 "</span> ;</span><br><span class="line">    <span class="keyword">while</span>(i != <span class="number">-1</span> )&#123;</span><br><span class="line">        <span class="built_in">cout</span> &lt;&lt; <span class="string">"-&gt;"</span>;</span><br><span class="line">        <span class="built_in">cout</span> &lt;&lt; from[i];</span><br><span class="line">        i = from[i];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">   </span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;dijkstra利用松弛操作找到最短的路线距离，假设当前图结构为稀疏图，结构如下:&lt;br&gt;&lt;img src=&quot;/images/blog/graph/BGTRJIKUBX.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;按照直观来说，&lt;code&gt;0-4&lt;/code&gt;的最短路径有如下几
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/categories/algorithm/graph/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/tags/graph/"/>
    
  </entry>
  
  <entry>
    <title>三色标记清除</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/10.%E4%B8%89%E8%89%B2%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/10.三色标记清除/</id>
    <published>2020-11-15T07:23:58.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/VNJSIWWQMT.png" alt=""></p><p>当前实现的三色标记是一种<code>增量式-标记清除算法</code>,解决了标记清除算法中<code>stw</code>过长的问题，增量迭代演进式回收,而不是一次性标记和回收所有对象，优化了<code>stw</code>暂停时间过长的问题</p><ul><li><code>白色</code>: 未搜索过的对象</li><li><code>灰色</code>: 正在搜索的对象，其实就是<code>child</code>引用没有扫描完的对象</li><li><code>黑色</code>: 搜索完成后的对象</li></ul><p>当多个阶段全部标记完成后任然是<code>白色</code>的对象就是垃圾对象，应该回收</p><p>既然是增量，说明标记阶段每次只执行一部分，清除阶段也只执行一部分</p><ul><li><code>gc_phase</code>: 表示当前所在阶段</li><li><code>max_mark</code>：每阶段需要标记的灰色对象的最大数量</li><li><code>mar_sweep</code>: 每阶段需要清除的白色垃圾对象的最大数量</li></ul><p>gc过程主要分为如下流程</p><ol><li>标记阶段: 扫描根,进行多阶段标记，每次发现的白色对象转换为灰色对象丢入栈内</li><li>清除阶段: 多阶段清除垃圾，只清除白色垃圾(未标记的)</li></ol><p>在增量gc多个阶段之间有新的对象产生、更新等，会涉及到安全问题，需要有个写入屏障</p><h1 id="gc-阶段"><a href="#gc-阶段" class="headerlink" title="@gc 阶段"></a>@gc 阶段</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//tri-color.c</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span>  <span class="title">gc</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"执行gc\n"</span>);</span><br><span class="line">    <span class="keyword">switch</span>(gc_phase)&#123;</span><br><span class="line">        <span class="keyword">case</span> GC_ROOT_SCAN:</span><br><span class="line">            root_scan_phase();</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        <span class="keyword">case</span> GC_MARK:</span><br><span class="line">            mark_phase();</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        <span class="keyword">case</span> GC_SWEEP:</span><br><span class="line">            sweep_phase();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每次执行gc都会继续执行之前中断的阶段，如</p><ul><li>根扫描</li><li>标记阶段</li><li>清除阶段</li></ul><p>完全执行完清除阶段后，下一阶段又回到根扫描上</p><h1 id="stack-存储灰色对象的栈"><a href="#stack-存储灰色对象的栈" class="headerlink" title="@stack 存储灰色对象的栈"></a>@stack 存储灰色对象的栈</h1><p>实现了一个简单的栈，用于存储灰色对象，每次扫描到存活对象后直接入栈，转为灰色对象</p><p>在标记阶段会递归标记其子对象，完成后出栈，转为黑色对象</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//stack.h</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">link_list</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">void</span>*             value;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">link_list</span> *<span class="title">next</span>;</span></span><br><span class="line">&#125;Link;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">stack_header</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    Link* head;</span><br><span class="line">    Link* tail;</span><br><span class="line">&#125;Stack;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">push</span><span class="params">(Stack* stk,<span class="keyword">void</span>* v)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">empty</span><span class="params">(Stack* stk)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span>* <span class="title">pop</span><span class="params">(Stack* stk)</span></span>;</span><br></pre></td></tr></table></figure><ul><li>push 入栈</li><li>pop 出栈</li><li>empty 是否为空</li></ul><h1 id="root-scan-phase-根部扫描阶段"><a href="#root-scan-phase-根部扫描阶段" class="headerlink" title="@root_scan_phase 根部扫描阶段"></a>@root_scan_phase 根部扫描阶段</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">root_scan_phase</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//垃圾回收前 先从 root 开始 进行递归标记</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; root_used;i++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">void</span>* ptr = roots[i].ptr;</span><br><span class="line">        GC_Heap *gh;</span><br><span class="line">        Header *hdr;</span><br><span class="line">        <span class="keyword">if</span> (!(gh = is_pointer_to_heap(ptr))) <span class="keyword">continue</span>;</span><br><span class="line">        <span class="keyword">if</span> (!(hdr = get_header(gh, ptr)))    <span class="keyword">continue</span>;</span><br><span class="line">        <span class="keyword">if</span> (!FL_TEST(hdr, FL_ALLOC))         <span class="keyword">continue</span>;</span><br><span class="line">        <span class="keyword">if</span> (FL_TEST(hdr, FL_MARK))           <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//标记为灰色 并入栈</span></span><br><span class="line">        FL_SET(hdr, FL_MARK);</span><br><span class="line">        push(&amp;<span class="built_in">stack</span>,ptr);</span><br><span class="line">    &#125;</span><br><span class="line">    gc_phase = GC_MARK;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当前是直接将所有存活对象作为灰色对象推入标记栈中,结束了扫描阶段</p><p>但是在标记阶段其实还是会进行多次根扫描，因为多个阶段之间可能发生了更新，新增对象等，需要多次进行扫描</p><p>第一次gc的时候这里扫描完就可以直接进入下一阶段了</p><h1 id="mark-phase-标记阶段"><a href="#mark-phase-标记阶段" class="headerlink" title="@mark_phase 标记阶段"></a>@mark_phase 标记阶段</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//tri-color.c</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">mark_phase</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//1 全部将灰色标记完了在进行下一个清除阶段</span></span><br><span class="line">    <span class="comment">//2 未全部标记完则继续进行标记</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> scan_root = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; max_mark; ++i) &#123;</span><br><span class="line">        <span class="comment">//如果为空就继续去扫描一下root  看看在gc休息期间是否有新的没有进行标记</span></span><br><span class="line">        <span class="keyword">if</span>(empty(&amp;<span class="built_in">stack</span>))&#123;</span><br><span class="line">            <span class="comment">//如果扫描过了root，但是依然没有新增灰色对象 则结束标记</span></span><br><span class="line">            <span class="keyword">if</span>(scan_root &gt;= <span class="number">1</span>) &#123;</span><br><span class="line">                gc_phase = GC_SWEEP;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            root_scan_phase();</span><br><span class="line">            scan_root++;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">void</span>* obj = pop(&amp;<span class="built_in">stack</span>);</span><br><span class="line">        Header* hdr = CURRENT_HEADER(obj);</span><br><span class="line">        <span class="comment">//递归对child 进行标记</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">void</span>* p = obj; p &lt; (<span class="keyword">void</span>*)NEXT_HEADER(hdr); p++) &#123;</span><br><span class="line">            <span class="comment">//对内存解引用，因为内存里面可能存放了内存的地址 也就是引用，需要进行引用的递归标记</span></span><br><span class="line">            gc_mark(*(<span class="keyword">void</span> **)p);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//所有gc扫描完以后 只有空栈的话 说明标记完毕 需要进行清扫</span></span><br><span class="line">    <span class="keyword">if</span>(empty(&amp;<span class="built_in">stack</span>))&#123;</span><br><span class="line">        gc_phase = GC_SWEEP;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>scan_root</code>标志用于在标记栈为空的时候，悄悄再去扫描一下根，看看是否有新的活动对象产生</p><p><code>max_mark</code>标志用于指示每次只标记固定数量的灰色对象(栈里的都是灰色对象)</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">void</span>* p = obj; p &lt; (<span class="keyword">void</span>*)NEXT_HEADER(hdr); p++) &#123;</span><br><span class="line">    <span class="comment">//对内存解引用，因为内存里面可能存放了内存的地址 也就是引用，需要进行引用的递归标记</span></span><br><span class="line">    gc_mark(*(<span class="keyword">void</span> **)p);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>递归扫描标记子对象(<code>和标记清除章节一致</code>)</p><p>最后判断如果灰色对象标记完了，则进入下一阶段<code>GC_SWEEP</code><br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(empty(&amp;<span class="built_in">stack</span>))&#123;</span><br><span class="line">    gc_phase = GC_SWEEP;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><h1 id="sweep-phase-清除阶段"><a href="#sweep-phase-清除阶段" class="headerlink" title="@sweep_phase 清除阶段"></a>@sweep_phase 清除阶段</h1><p>也是多阶段清除，每次只清除固定<code>max_sweep</code>个白色垃圾，并记下索引，下次继续清除<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//tri-color.c</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">sweep_phase</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">size_t</span> i;</span><br><span class="line">    Header *p, *pend, *pnext;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//遍历所有的堆内存</span></span><br><span class="line">    <span class="comment">//因为所有的内存都从堆里申请，所以需要遍历堆找出待回收的内存</span></span><br><span class="line">    <span class="keyword">for</span> (i = sweeping; i &lt; gc_heaps_used &amp;&amp; i &lt; max_sweep + sweeping; i++) &#123;</span><br><span class="line">        <span class="comment">//pend 堆内存结束为止</span></span><br><span class="line">        pend = (Header *)(((<span class="keyword">size_t</span>)gc_heaps[i].slot) + gc_heaps[i].size);</span><br><span class="line">        <span class="comment">//堆的起始为止 因为堆的内存可能被分成了很多份，所以需要遍历该堆的内存</span></span><br><span class="line">        <span class="keyword">for</span> (p = gc_heaps[i].slot; p &lt; pend; p = NEXT_HEADER(p)) &#123;</span><br><span class="line">            <span class="comment">//查看该堆是否已经被使用</span></span><br><span class="line">            <span class="keyword">if</span> (FL_TEST(p, FL_ALLOC)) &#123;</span><br><span class="line">                <span class="comment">//查看该堆是否被标记过</span></span><br><span class="line">                <span class="keyword">if</span> (FL_TEST(p, FL_MARK)) &#123;</span><br><span class="line">                    DEBUG(<span class="built_in">printf</span>(<span class="string">"解除标记 : %p\n"</span>, p));</span><br><span class="line">                    <span class="comment">//取消标记，等待下次来回收，如果在下次回收前</span></span><br><span class="line">                    <span class="comment">//1. 下次回收前发现该内存又被重新访问了，则不需要清除</span></span><br><span class="line">                    <span class="comment">//2. 下次回收前发现该内存没有被访问过，所以需要清除</span></span><br><span class="line">                    FL_UNSET(p, FL_MARK);</span><br><span class="line">                &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">                    DEBUG(<span class="built_in">printf</span>(<span class="string">"清除回收 :\n"</span>));</span><br><span class="line">                    gc_free(p+<span class="number">1</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//如果堆扫描完则 切换到root扫描</span></span><br><span class="line">    sweeping = i;</span><br><span class="line">    <span class="keyword">if</span>(i == gc_heaps_used)&#123;</span><br><span class="line">        sweeping = <span class="number">0</span>;</span><br><span class="line">        gc_phase = GC_ROOT_SCAN;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>堆扫描完后，又重新回到<code>GC_ROOT_SCAN</code>阶段</p><h1 id="关于gc安全的两点"><a href="#关于gc安全的两点" class="headerlink" title="关于gc安全的两点"></a>关于gc安全的两点</h1><h2 id="写入屏障"><a href="#写入屏障" class="headerlink" title="写入屏障"></a>写入屏障</h2><p>处在gc执行后的暂停阶段期间，程序对对象又进行了更新会产生不可预料的后果，如下图所示:<br><img src="/images/blog/gc-learning/AZGHDFMZHN.png" alt=""></p><ol><li>首先<code>E</code>没有被<code>Root</code>管理，扫描根的时候无法扫描到，只有扫描<code>C</code>的子对象时才能标记到<code>C</code></li><li><code>A,B</code>对象已经是黑色对象了，不需要在进行扫描了，是觉得的安全对象</li><li>此时<code>C</code>到<code>E</code>的引用被切断了，<code>E</code>被黑色对象<code>B</code>引用</li><li>如果不做任何操作，<code>E</code>将会在gc结束后备清除</li></ol><p>这就是写入屏障的重要性，保证了在gc执行的多阶段暂停之间任然保证逻辑正确</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//tri-color.c</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">write_barrier</span><span class="params">(<span class="keyword">void</span> *obj_ptr,<span class="keyword">void</span> *field,<span class="keyword">void</span>* new_obj_ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line">    Header* obj     = CURRENT_HEADER(obj_ptr);</span><br><span class="line">    Header* new_obj = CURRENT_HEADER(new_obj_ptr);</span><br><span class="line">    <span class="comment">//如果老对象已经被标记了 就要检查新对象是否标记了</span></span><br><span class="line">    <span class="keyword">if</span>(IS_MARKED(obj))&#123;</span><br><span class="line">        <span class="keyword">if</span>(!IS_MARKED(new_obj))&#123;</span><br><span class="line">            FL_SET(new_obj,FL_MARK);</span><br><span class="line">            push(&amp;<span class="built_in">stack</span>,new_obj_ptr);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//obj-&gt;field = new_obj</span></span><br><span class="line">    *(<span class="keyword">void</span> **)field = new_obj_ptr;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上面所说，在更新对象的时候需要判断，如果原对象是一个黑色对象，则需要将当前对象标记为灰色后推入标记栈中</p><p>等待下次标记阶段扫描当前对象，解决了更新安全问题</p><h2 id="分配安全"><a href="#分配安全" class="headerlink" title="分配安全"></a>分配安全</h2><p>在gc暂停阶段期间新申请对象需要注意如果新申请的内存在清除内存的后方，只需要默认进行设置为黑色,防止被误回收<br><img src="/images/blog/gc-learning/WOZRLVRSJN.png" alt=""></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="tri-color-mark" scheme="http://wiki.brewlin.com/tags/tri-color-mark/"/>
    
  </entry>
  
  <entry>
    <title>分代回收算法</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/9.%E5%88%86%E4%BB%A3%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/9.分代回收算法/</id>
    <published>2020-11-14T07:22:59.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/MPPBAXQFTH.png" alt=""></p><p>分代垃圾回收也是一种组合算法实现(<code>gc复制 + 标记清除</code>)，在了解之前先来看看几个关键字概念</p><h1 id="分代概念"><a href="#分代概念" class="headerlink" title="@分代概念"></a>@分代概念</h1><p><code>新生代(newg)</code>: 作为内存分配区</p><p>幸存代<code>(survivorfromg)</code>: 作为内存分配区</p><p>幸存代<code>(survivorto)</code>:  新生代+幸存代执行复制算法,最终复制到幸存to区</p><p>老年代<code>(oldg)</code>: 老年代单独执行标记清除算法</p><p><code>promote(晋升)</code>: 当前规定经历过3次gc后任然幸存的新生代对象会晋升为老年代对象，会拷贝到老年区</p><p><code>remember_set(记录集)</code>: 用于存储那些任然引用新生代对象的老年代对象，也是老年代的<code>根ROOT</code></p><p><img src="/images/blog/gc-learning/PWTPPLWJAY.png" alt=""></p><ol><li>只从新生代和幸存代<code>from</code>分配内存</li><li>多次幸存后晋升为老年代,将对象拷贝到老年区</li><li>步骤2中如果晋升过后子对象还在新生代则记录到记录集中作为可达的根</li></ol><h1 id="gc-init-初始化"><a href="#gc-init-初始化" class="headerlink" title="@gc_init 初始化"></a>@gc_init 初始化</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">gc_init</span><span class="params">(<span class="keyword">size_t</span> heap_size)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//关闭自动扩充堆</span></span><br><span class="line">    auto_grow = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//gc_heaps[3] 用于老年代堆区</span></span><br><span class="line">    gc_heaps_used = <span class="number">3</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i &lt; <span class="number">4</span>; i++)&#123;</span><br><span class="line">        <span class="comment">//使用sbrk 向操作系统申请大内存块</span></span><br><span class="line">        <span class="keyword">void</span>* p = sbrk(heap_size + PTRSIZE);</span><br><span class="line">        <span class="keyword">if</span>(p == <span class="literal">NULL</span>)<span class="built_in">exit</span>(<span class="number">-1</span>);</span><br><span class="line"></span><br><span class="line">        gc_heaps[i].slot = (Header *)ALIGN((<span class="keyword">size_t</span>)p, PTRSIZE);</span><br><span class="line">        gc_heaps[i].size = heap_size;</span><br><span class="line">        gc_heaps[i].slot-&gt;size = heap_size;</span><br><span class="line">        gc_heaps[i].slot-&gt;next_free = <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//初始化新生代空闲链表指针</span></span><br><span class="line">    new_free_p = gc_heaps[newg].slot;</span><br><span class="line">    <span class="comment">//老年代分配需要用到空闲列表 通过gc_free 挂到空闲列表即可</span></span><br><span class="line">    gc_free(gc_heaps[oldg].slot + <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当前主要固定四个内存区,<code>new</code>,<code>from</code>,<code>to</code>,<code>old</code>。并且关闭自动扩充堆</p><p>空闲指针<code>free-list</code>是专用于老年代的分配指针，在晋升的时候会从老年代区分配一份内存用于拷贝</p><p>空闲指针<code>new_free_p</code>是专用于内存分配指针，新内存都是从新生代区分配</p><h1 id="minor-gc-新生代gc阶段"><a href="#minor-gc-新生代gc阶段" class="headerlink" title="@minor_gc 新生代gc阶段"></a>@minor_gc 新生代gc阶段</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>  <span class="title">minor_gc</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//每次gc前将 free指向 to的开头</span></span><br><span class="line">    gc_heaps[survivortog].slot-&gt;size = gc_heaps[survivortog].size;</span><br><span class="line">    <span class="comment">//初始化to空间的首地址</span></span><br><span class="line">    to_free_p = gc_heaps[survivortog].slot;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//递归进行复制  从 from  =&gt; to</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; root_used;i++)&#123;</span><br><span class="line">        <span class="keyword">void</span>* forwarded;</span><br><span class="line">        <span class="keyword">if</span>(!(forwarded = gc_copy(roots[i].ptr))) <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        *(Header**)roots[i].optr = forwarded;</span><br><span class="line">        <span class="comment">//将root 所有的执行换到 to上</span></span><br><span class="line">        roots[i].ptr = forwarded;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//更新跨代引用</span></span><br><span class="line">    update_reference();</span><br><span class="line"></span><br><span class="line">    <span class="comment">//清空 新生代</span></span><br><span class="line">    new_free_p = gc_heaps[newg].slot ;</span><br><span class="line">    <span class="built_in">memset</span>(new_free_p,<span class="number">0</span>,gc_heaps[newg].size);</span><br><span class="line">    new_free_p-&gt;size = gc_heaps[newg].size;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">//清空 幸存代  from</span></span><br><span class="line">    <span class="built_in">memset</span>(gc_heaps[survivorfromg].slot,<span class="number">0</span>,gc_heaps[survivorfromg].size);</span><br><span class="line">    gc_heaps[survivorfromg].slot-&gt;size = gc_heaps[survivorfromg].size;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//交换 swap(幸存代from ,幸存代to);</span></span><br><span class="line">    GC_Heap tmp             = gc_heaps[survivorfromg];</span><br><span class="line">    gc_heaps[survivorfromg] = gc_heaps[survivortog];</span><br><span class="line">    gc_heaps[survivortog]   = tmp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>新生代主要执行复制算法，将<code>newg</code>和<code>fromg</code>复制到<code>tog</code>空间，并置换<code>from</code>和<code>to</code>后结束新生代GC</p><p>主要流程和<code>GC复制</code>流程一致，可以去看<code>gc复制</code>的实现分析</p><ol><li>搜索根执行拷贝到<code>to</code>区</li><li>更新引用</li><li>清空新生代，清空幸存代</li><li>交换from和to</li></ol><h2 id="minor-malloc-内存分配"><a href="#minor-malloc-内存分配" class="headerlink" title="@minor_malloc 内存分配"></a>@minor_malloc 内存分配</h2><p>所有分配只能走新生代分配，直接从<code>newg</code>区分配一块空闲内存即可<br><img src="/images/blog/gc-learning/NCGKXFVVNS.png" alt=""><br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (new_free_p-&gt;size &lt; req_size) &#123;</span><br><span class="line">    <span class="comment">//一般是分块用尽会 才会执行gc 清除带回收的内存</span></span><br><span class="line">    <span class="keyword">if</span> (!do_gc) &#123;</span><br><span class="line">        do_gc = <span class="number">1</span>;</span><br><span class="line">        <span class="comment">//内存不够用的时候会触发 复制 释放空间</span></span><br><span class="line">        <span class="comment">//释放空间的时候会造成空间的压缩</span></span><br><span class="line">        minor_gc();</span><br><span class="line">        <span class="keyword">goto</span> alloc;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"内存不够"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><ol><li>从<code>new_free_p</code>执行的剩余空间分配一块足够大小的空间</li><li>内存不够则执行新生代gc<code>minor_gc</code>释放一定空闲内存</li></ol><h2 id="gc-copy-复制"><a href="#gc-copy-复制" class="headerlink" title="@gc_copy 复制"></a>@gc_copy 复制</h2><p>这里和复制算法稍微有点不一样，如果对象属于老年区则不作任何操作,复制只会拷贝新生代区的对象<br><img src="/images/blog/gc-learning/YWKJMMJCYB.png" alt=""><br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!(gh = is_pointer_to_heap(ptr)))</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"><span class="comment">//查看该对象是否存在于 新生代 这里的get_header 只会去查找 新生代和两个幸存代</span></span><br><span class="line"><span class="keyword">if</span> (!(hdr = get_header(gh,ptr))) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>同时在执行拷贝前需要检查年龄<code>age</code>是否小于<code>3</code>，否则需要晋升为老年代<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//没有复制过  0</span></span><br><span class="line"><span class="keyword">if</span>(!IS_COPIED(hdr))&#123;</span><br><span class="line">    <span class="comment">//判断年龄是否小于阈值</span></span><br><span class="line">    <span class="keyword">if</span>(hdr-&gt;age &lt; AGE_MAX)</span><br><span class="line">    &#123;</span><br><span class="line">     <span class="comment">//拷贝</span></span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">        <span class="comment">//晋升</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>复制流程和之前的复制算法流程一样，可以去看之前的分析</p><h2 id="promote-晋升"><a href="#promote-晋升" class="headerlink" title="@promote 晋升"></a>@promote 晋升</h2><p><img src="/images/blog/gc-learning/GELXVWHGDE.png" alt=""><br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">promote</span><span class="params">(<span class="keyword">void</span> *ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Header* obj = CURRENT_HEADER(ptr);</span><br><span class="line">    <span class="comment">//1 从老年代空间分配出一块 内存 (老年代堆 完全采用 gc标记-清除算法来管理)</span></span><br><span class="line">    <span class="keyword">void</span>* new_obj_ptr = major_malloc(CURRENT_HEADER(ptr)-&gt;size);</span><br><span class="line">    <span class="keyword">if</span>(new_obj_ptr == <span class="literal">NULL</span>) <span class="built_in">abort</span>();</span><br><span class="line"></span><br><span class="line">    Header* new_obj   = CURRENT_HEADER(new_obj_ptr);</span><br><span class="line">    <span class="comment">//将obj 拷贝到 new_obj中</span></span><br><span class="line">    <span class="built_in">memcpy</span>(new_obj,obj,obj-&gt;size);</span><br><span class="line"></span><br><span class="line">    obj-&gt;forwarding = new_obj;</span><br><span class="line">    <span class="comment">//标志已经复制过了 forwarded = true</span></span><br><span class="line">    FL_SET(obj,FL_COPIED);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//for child: obj 这里是为了检查老年代对象是否有对象依然指向新生代中</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">void</span> *p = ptr; p &lt; (<span class="keyword">void</span>*)NEXT_HEADER(obj); p++) &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">//解引用 如果该内存依然是指向的from，且有forwarding 则需要改了</span></span><br><span class="line">        <span class="keyword">void</span> *ptr = *(<span class="keyword">void</span>**)p;</span><br><span class="line">        GC_Heap *gh;</span><br><span class="line">        Header *hdr;</span><br><span class="line">        <span class="comment">/* mark check */</span></span><br><span class="line">        <span class="keyword">if</span> (!(gh = is_pointer_to_heap(ptr))) <span class="keyword">continue</span>;</span><br><span class="line">        <span class="comment">//查看该引用是否存在于 新生代 这里的get_header 只会去查找 新生代和两个幸存代</span></span><br><span class="line">        <span class="keyword">if</span> (!(hdr = get_header(gh,ptr)))     <span class="keyword">continue</span>;</span><br><span class="line">        <span class="comment">//存在就要将 new_obj 加入集合</span></span><br><span class="line">        rs[rs_index++] = new_obj_ptr;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>从老年代分配一块内存，将当前晋升对象拷贝过去</p><p>搜索子对象是否有在新生代和幸存区的，如果存在说明当前老年代对象有跨代引用，需要加入记录集<code>remember_set</code>管理</p><h2 id="update-reference-更新引用"><a href="#update-reference-更新引用" class="headerlink" title="@update_reference 更新引用"></a>@update_reference 更新引用</h2><p>这里的更新引用主要是针对跨代引用而言</p><p>如果某个对象没有加入ROOT，但是却被老年代引用了，这个时候就需要将它复制到<code>幸存to区</code>,如果不这么做的话，它就会被意外的清除了</p><p><img src="/images/blog/gc-learning/LNCONGTOEX.png" alt=""></p><p>如图所示,<code>B对象</code>没有加入根，在复制阶段无法被认定为活动对象，也就无法进行拷贝到幸存区，会被回收</p><p>但是他却被老年区的<code>D对象</code>引用，也应该被拷贝到幸存区</p><p>所以当前函数主要是处理这种情况，处理跨代引用的拷贝问题</p><p>需要注意一点: 当前函数会检查当老年代没有任何引用对象任然在新生代和幸存代时，<code>会从记录集中剔除老年对象</code>,那在老年代gc的时候会被清除，因为没有任何根能够搜索到该老年代对象了</p><h2 id="write-barrier-写屏障"><a href="#write-barrier-写屏障" class="headerlink" title="@write_barrier 写屏障"></a>@write_barrier 写屏障</h2><p>为了让老年代对象任然保持活性，那么写入屏障是或不可缺的,想像下如下场景</p><p><img src="/images/blog/gc-learning/XPZRJPEZBW.png" alt=""></p><ol><li>某老年代对象已经没有任何跨代对象了,且已经被剔除记录集了，如果下一次老年代gc启动，则必定是最先被优化的那一批。</li><li>这时候新生代一个对象突然被老年代引用了，如果不做任何操作，我们在<code>update_reference</code>阶段是无法追踪到这个新生代引用对象的</li><li>这样导致步骤2中的新生代对象被无辜清除了，正确的做法是:在更新引用的时候需要判断下发出引用的对象是否属于老年代，然后需要记录记录集</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">write_barrier</span><span class="params">(<span class="keyword">void</span> *obj_ptr,<span class="keyword">void</span> *field,<span class="keyword">void</span>* new_obj_ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line">    Header* obj     = CURRENT_HEADER(obj_ptr);</span><br><span class="line">    Header* new_obj = CURRENT_HEADER(new_obj_ptr);</span><br><span class="line">    <span class="comment">//obj 在老年代</span></span><br><span class="line">    <span class="comment">//new_obj 在新生代</span></span><br><span class="line">    <span class="comment">//且 obj 未保存在 记忆集</span></span><br><span class="line">    <span class="keyword">if</span>(is_pointer_to_space(obj,oldg) &amp;&amp;</span><br><span class="line">       !is_pointer_to_space(new_obj,oldg) &amp;&amp;</span><br><span class="line">       !IS_REMEMBERED(obj))</span><br><span class="line">    &#123;</span><br><span class="line">        rs[rs_index++] = obj;</span><br><span class="line">        <span class="comment">//设置该对象已经保存在了记忆集，无需再次保存</span></span><br><span class="line">        FL_SET(obj,FL_REMEMBERED);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//obj-&gt;field = new_obj</span></span><br><span class="line">    *(<span class="keyword">void</span> **)field = new_obj_ptr;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>检查对象是老年代，且被引用对象是新生代的情况</li><li>如果满足步骤1，则需要更新记录集</li></ol><h1 id="major-gc-老年代gc"><a href="#major-gc-老年代gc" class="headerlink" title="@major_gc 老年代gc"></a>@major_gc 老年代gc</h1><p>老年代gc完全按照标记清除算法执行，只是在搜索根的步骤换成了<code>搜索记录集(remember_set)</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>  <span class="title">major_gc</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//rs 里的基本都是老年代</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; rs_index; i ++) &#123;</span><br><span class="line">        <span class="comment">//只对老年代 对象进行gc</span></span><br><span class="line">        gc_mark(rs[i]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//标记完成后 在进行 清除 对于没有标记过的进行回收</span></span><br><span class="line">    gc_sweep();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>进行记录集搜索并标记</li><li>进行清除回收未标记垃圾</li></ol><h2 id="gc-mark-标记阶段"><a href="#gc-mark-标记阶段" class="headerlink" title="@gc_mark 标记阶段"></a>@gc_mark 标记阶段</h2><p>判断是否是老年代对象，如果不是则不需要标记</p><p>最后进行递归标记</p><p>可以看之前的<code>标记清除章节</code></p><h2 id="gc-sweep-清除阶段"><a href="#gc-sweep-清除阶段" class="headerlink" title="@gc_sweep 清除阶段"></a>@gc_sweep 清除阶段</h2><p>搜索老年代堆，对未标记内存进行回收</p><p>和之前<code>标记清除章节</code>一致</p><h2 id="major-malloc"><a href="#major-malloc" class="headerlink" title="@major_malloc"></a>@major_malloc</h2><p>从老年代堆中分配一份内存返回</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="generational" scheme="http://wiki.brewlin.com/tags/generational/"/>
    
  </entry>
  
  <entry>
    <title>关于保守式GC</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/8.%E4%BF%9D%E5%AE%88%E5%BC%8Fgc/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/8.保守式gc/</id>
    <published>2020-11-13T15:59:59.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/RXBIFNEGTM.png" alt=""></p><p>当前所有的gc实现都是基于<code>保守式gc</code>实现的，特点就是实现简单、容易理解，对应用层友好</p><h1 id="保守式gc的示例"><a href="#保守式gc的示例" class="headerlink" title="保守式gc的示例"></a>保守式gc的示例</h1><p>比如一个内存分配如下:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"gc.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">test</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">obj</span>&#123;</span></span><br><span class="line">        .....</span><br><span class="line">    &#125;Obj;</span><br><span class="line">    </span><br><span class="line">    Obj* p = gc_malloc(<span class="keyword">sizeof</span>(Obj);</span><br><span class="line">    <span class="comment">// do something else</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>没有任何依赖，只需要调用<code>gc_malloc()</code>分配一块空间即可，不需要应用层去操心如(标记，内嵌指针啥的)，实现简单</p><h1 id="保守式gc的缺陷"><a href="#保守式gc的缺陷" class="headerlink" title="保守式gc的缺陷"></a>保守式gc的缺陷</h1><p>在这简单的背后自然也有保守式gc的一个<code>缺陷</code>,那就是<code>不能识别指针和非指针</code>。接着上上面的函数来说,举个例子<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    test();</span><br><span class="line">    <span class="keyword">double</span> p = <span class="number">0x555555</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//内存不够了 自动执行gc</span></span><br><span class="line">    <span class="keyword">void</span>* pp = gc_malloc(size);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><ol><li>调用<code>test</code>函数,加入该函数内p的指针刚好也是 <code>0x555555</code></li><li><code>test函数</code>返回后继续执行，此时栈上有一个<code>double</code>变量，且值刚好也是<code>0x555555</code> 和上面test函数的指针值相同</li><li>假如再次申请空间时，内存不够了，默认启动<code>gc</code>垃圾回收</li><li>首先: test作用域已经退出了，在进行root扫描时(可以先去看<a href="./什么是root">什么是root?</a>) <code>Obj *p</code>已经是不可达对象，称为垃圾对象</li><li>但是: 在main中root扫描，扫到了 <code>double p</code> 且刚好该值是我们实现的堆里的一个合法地址</li><li>最终: 导致test的p本来该回收，但是因为 <code>double p</code>导致了回收失败</li></ol><p>这就是保守式gc，某些情况下会无法<code>准确识别指针 or 非指针</code>,导致内存得不到释放</p><p>当然现代很多语言都是基于保守式gc，也有很多对策来降低这种误差</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="conservative" scheme="http://wiki.brewlin.com/tags/conservative/"/>
    
  </entry>
  
  <entry>
    <title>压缩算法-two-finger</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/7.%E5%8E%8B%E7%BC%A9%E7%AE%97%E6%B3%95-two-finger/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/7.压缩算法-two-finger/</id>
    <published>2020-11-12T07:21:22.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/KHGOGFUIKO.png" alt=""></p><p><code>two-finger</code>相比<code>lisp2</code>而言优化了gc效率，只需要执行两次堆的遍历，但同时也多了限制条件,那就是需要保证每个对象的内存块大小一致</p><p>通过固定块大小，就可以去除之前<code>set_forwarding_ptr</code>计算移动地址的步骤，因为内存块都是一样的，所以直接移动即可</p><p><img src="/images/blog/gc-learning/PXQQCMLXPZ.png" alt=""></p><p>主要流程如下</p><ol><li><code>gc_mark</code>: 标记阶段，扫描root对活动对象进行标记</li><li><code>move</code>: 第一次遍历堆-双端同时遍历开始移动拷贝</li><li><code>adjust_ptr</code>:         第二次遍历堆-更新引用关系</li></ol><p><code>two-finger</code>和<code>lisp2</code>的区别:</p><ul><li><code>two-finger</code> 只需要2次堆遍历，而<code>lisp2</code>需要三次</li><li><code>two-finger</code> 规定每个对象大小必须一致, <code>lisp2</code>无要求</li><li><code>two-finger</code> 不需要移动全部对象(<code>注意这点区别</code>),可以用<code>填空</code>来描述这一行为</li></ul><h1 id="gc-init-初始化"><a href="#gc-init-初始化" class="headerlink" title="@gc_init 初始化"></a>@gc_init 初始化</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">gc_init</span><span class="params">(<span class="keyword">size_t</span> heap_size)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//关闭自动扩充堆</span></span><br><span class="line">    auto_grow = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    gc_heaps_used = <span class="number">1</span>;</span><br><span class="line">    <span class="comment">//使用sbrk 向操作系统申请大内存块</span></span><br><span class="line">    <span class="keyword">void</span>* p = sbrk(heap_size + PTRSIZE);</span><br><span class="line">    gc_heaps[<span class="number">0</span>].slot = (Header *)ALIGN((<span class="keyword">size_t</span>)p, PTRSIZE);</span><br><span class="line">    gc_heaps[<span class="number">0</span>].size = heap_size;</span><br><span class="line">    gc_heaps[<span class="number">0</span>].slot-&gt;size = heap_size;</span><br><span class="line">    gc_heaps[<span class="number">0</span>].slot-&gt;next_free = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="comment">//将堆初始化到free_list 链表上</span></span><br><span class="line">    gc_free(gc_heaps[<span class="number">0</span>].slot + <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了聚焦算法实现本身，这里只固定申请一个堆，且关闭自动扩充堆<code>auto_grow = 0</code></p><h1 id="gc-阶段"><a href="#gc-阶段" class="headerlink" title="@gc 阶段"></a>@gc 阶段</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>  <span class="title">gc</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"start gc()\n"</span>);</span><br><span class="line">    <span class="comment">//gc 递归标记</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; root_used;i++)</span><br><span class="line">        gc_mark(roots[i].ptr);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//移动对象</span></span><br><span class="line">    move_obj();</span><br><span class="line">    <span class="comment">//调整指针</span></span><br><span class="line">    adjust_ptr();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>遍历root,对所有可达对象以及子对象进行标记</li><li>直接移动对象即可，因为每个对象大小都一致，直接移动等分距离即可</li><li>更新活动对象的子对象指向新的地址</li></ol><h1 id="gc-mark-标记阶段"><a href="#gc-mark-标记阶段" class="headerlink" title="@gc_mark 标记阶段"></a>@gc_mark 标记阶段</h1><p><img src="/images/blog/gc-learning/JXHYAAGVXR.png" alt=""></p><p>扫描root根，这里是模拟的root，真实的root可以参考<code>gc-try</code>实现的扫描系统栈<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; root_used;i++)</span><br><span class="line">    gc_mark(roots[i].ptr);</span><br></pre></td></tr></table></figure></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>* <span class="title">gc_mark</span><span class="params">(<span class="keyword">void</span> *ptr)</span></span>&#123;</span><br><span class="line">    <span class="comment">//检查指针是否合法</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* marking */</span></span><br><span class="line">    FL_SET(hdr, FL_MARK);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">void</span>* p = ptr; p &lt; (<span class="keyword">void</span>*)NEXT_HEADER(hdr); p++) &#123;</span><br><span class="line">        gc_mark(*(<span class="keyword">void</span> **)p);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ptr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>常规操作，对当前指针进行检查，然后扫描当前指针执行的内存段，对<code>child</code>进行递归引用标记</p><h1 id="move-obj-移动压缩"><a href="#move-obj-移动压缩" class="headerlink" title="@move_obj 移动压缩"></a>@move_obj 移动压缩</h1><p><img src="/images/blog/gc-learning/QQLTNACQER.png" alt=""></p><p>注意和<code>lisp</code>的移动的区别，这里采用两个指针<code>空闲指针</code>和<code>live</code>指针</p><ul><li><code>空闲指针</code>从头遍历，总是去寻找空位</li><li><code>live指针</code>从尾遍历, 总是去寻找存活对象</li><li>当两个指针相遇的时候结束压缩</li></ul><p>两个指针就像是两个手指一样，所以取名<code>two-finger</code></p><h2 id="逻辑实现"><a href="#逻辑实现" class="headerlink" title="逻辑实现"></a>逻辑实现</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">move_obj</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    free_list = gc_heaps[i].slot;</span><br><span class="line">    total = gc_heaps[i].size;</span><br><span class="line">    <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="comment">//遍历到第一个非标记的地方，也就是空闲区</span></span><br><span class="line">        <span class="keyword">while</span> (FL_TEST(free_list, FL_ALLOC) &amp;&amp; FL_TEST(free_list, FL_MARK) &amp;&amp; free_list &lt; live)</span><br><span class="line">        &#123;</span><br><span class="line">            FL_UNSET(free_list,FL_MARK);</span><br><span class="line">            total -= free_list-&gt;size;</span><br><span class="line">            free_list = NEXT_HEADER(free_list);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//遍历到第一个被标记了的地方，这样就会将这个地方拷贝到上面的空闲区</span></span><br><span class="line">        <span class="keyword">while</span> (!FL_TEST(live, FL_MARK) &amp;&amp; live &gt; gc_heaps[i].slot)</span><br><span class="line">            <span class="comment">//<span class="doctag">TODO:</span>因为反向遍历的时候 没有域且内存非等分，所以不能通过 -= mem_size 来遍历</span></span><br><span class="line">            live = (Header *) ((<span class="keyword">void</span> *) live - <span class="number">1</span>);</span><br><span class="line">        <span class="comment">//进行拷贝</span></span><br><span class="line">        <span class="keyword">if</span> (free_list &lt; live)</span><br><span class="line">        &#123;</span><br><span class="line">            FL_UNSET(live, FL_MARK);</span><br><span class="line">            <span class="built_in">memcpy</span>(free_list, live, live-&gt;size);</span><br><span class="line">            live-&gt;forwarding = free_list;</span><br><span class="line">            total -= live-&gt;size;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    free_list-&gt;size = total;</span><br><span class="line">    free_list-&gt;next_free = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="comment">//方便测试 把空闲空间都清空</span></span><br><span class="line">    <span class="built_in">memset</span>(free_list + <span class="number">1</span>,<span class="number">0</span>,total);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>采用2层循环，三个循环来实现压缩</p><ol><li>外层循环 在两个指针相遇时结束，压缩也结束了</li><li>内层第一个循环，总是去寻找第一个空位</li><li>内层第二个循环，总是从尾部反向遍历寻找存活对象</li></ol><p>核心就是每个对象大小都是一样的，所以在进行填补的时候直接拷贝即可</p><h1 id="adjust-ptr-更新引用"><a href="#adjust-ptr-更新引用" class="headerlink" title="@adjust_ptr 更新引用"></a>@adjust_ptr 更新引用</h1><p><img src="/images/blog/gc-learning/YHEMAYWHWE.png" alt=""></p><p>和<code>lisp2</code>算法一样，更新子对象的引用</p><ol><li>初始时<code>block3</code>引用了<code>block4</code>,且<code>block3</code>应该移动到<code>block1</code>处，<code>block4</code>应该移动到<code>block2</code>处</li><li>更新后<code>block3</code>不再引用<code>block4</code>而是将要移动后的<code>block2</code>的位置</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">adjust_ptr</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//遍历所有对象</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; root_used; i ++)&#123;</span><br><span class="line">        Header* forwarding =  CURRENT_HEADER(roots[i].ptr)-&gt;forwarding;</span><br><span class="line">        roots[i].ptr = forwarding+<span class="number">1</span>;</span><br><span class="line">        *(Header**)roots[i].optr = forwarding+<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//堆的起始为止 因为堆的内存可能被分成了很多份，所以需要遍历该堆的内存</span></span><br><span class="line">    <span class="keyword">for</span> (p = gc_heaps[i].slot; p &lt; pend; p = NEXT_HEADER(p))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//可能申请的内存 里面又包含了其他内存</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">void</span>* obj = p+<span class="number">1</span>; obj &lt; (<span class="keyword">void</span>*)NEXT_HEADER(p); obj++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">//正确找到了 child 引用</span></span><br><span class="line">            GC_Heap *gh;</span><br><span class="line">            Header  *hdr;</span><br><span class="line">            <span class="keyword">if</span> (!(gh = is_pointer_to_heap(*(<span class="keyword">void</span>**)obj))) <span class="keyword">continue</span>;</span><br><span class="line">            <span class="keyword">if</span>((hdr = get_header(gh,*(<span class="keyword">void</span>**)obj))) &#123;</span><br><span class="line">                *(Header **) obj = hdr-&gt;forwarding + <span class="number">1</span>; <span class="comment">//更新引用</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先更新root，因为所有活动对象的内存都发生了移动，需要更新栈变量存储的地址，也就是更新root所指向的地址</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="compact" scheme="http://wiki.brewlin.com/tags/compact/"/>
    
      <category term="compact-two-finger" scheme="http://wiki.brewlin.com/tags/compact-two-finger/"/>
    
  </entry>
  
  <entry>
    <title>压缩算法-lisp2</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/6.%E5%8E%8B%E7%BC%A9%E7%AE%97%E6%B3%95-lisp2/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/6.压缩算法-lisp2/</id>
    <published>2020-11-12T07:20:21.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/AWIVGHTIRU.png" alt=""></p><p>压缩算法,也是一种充分利用空间解决内存碎片的算法。相较于<code>复制</code>称为<code>移动</code>更为恰当,因为压缩算法中不会将对象从<code>from</code>搬到<code>to</code>,仅限于在当前堆内活动，将所有活动对象去全部移动到头部进行压缩</p><p><img src="/images/blog/gc-learning/JQDBAMPLWX.png" alt=""></p><p>压缩算法相比复制算法解决了<code>内存碎片</code>和无法<code>充分利用堆</code>的问题,但也有新的问题产生，当前压缩算法的实现:<code>lisp2</code>:</p><ul><li>在gc过程中需要遍历三次堆，堆内存较大情况下比较耗费时间</li><li>不能像gc复制算法那样<code>将具有引用关系的对象就近排列加快访问速度</code></li></ul><p>主要流程如下</p><ol><li><code>gc_mark</code>: 标记阶段，扫描root对活动对象进行标记</li><li><code>set_forwarding_ptr</code>: 第一次遍历堆-计算活动对象移动后的地址</li><li><code>adjust_ptr</code>:         第二次遍历堆-更新引用关系，所有的子类都要执行<code>步骤2</code>中计算出来的新地址</li><li><code>move_obj</code>:           第三次遍历堆-移动对象执行压缩</li></ol><h1 id="gc-init-初始化"><a href="#gc-init-初始化" class="headerlink" title="@gc_init 初始化"></a>@gc_init 初始化</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">gc_init</span><span class="params">(<span class="keyword">size_t</span> heap_size)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//关闭自动扩充堆</span></span><br><span class="line">    auto_grow = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    gc_heaps_used = <span class="number">1</span>;</span><br><span class="line">    <span class="comment">//使用sbrk 向操作系统申请大内存块</span></span><br><span class="line">    <span class="keyword">void</span>* p = sbrk(heap_size + PTRSIZE);</span><br><span class="line">    gc_heaps[<span class="number">0</span>].slot = (Header *)ALIGN((<span class="keyword">size_t</span>)p, PTRSIZE);</span><br><span class="line">    gc_heaps[<span class="number">0</span>].size = heap_size;</span><br><span class="line">    gc_heaps[<span class="number">0</span>].slot-&gt;size = heap_size;</span><br><span class="line">    gc_heaps[<span class="number">0</span>].slot-&gt;next_free = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="comment">//将堆初始化到free_list 链表上</span></span><br><span class="line">    gc_free(gc_heaps[<span class="number">0</span>].slot + <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>为了聚焦算法实现本身，这里只固定申请一个堆，且关闭自动扩充堆<code>auto_grow = 0</code></p><h1 id="gc-阶段"><a href="#gc-阶段" class="headerlink" title="@gc 阶段"></a>@gc 阶段</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 执行gc</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span>  <span class="title">gc</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"start gc()\n"</span>);</span><br><span class="line">    <span class="comment">//gc 递归标记</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; root_used;i++)</span><br><span class="line">        gc_mark(roots[i].ptr);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//设置forwarding指针</span></span><br><span class="line">    set_forwarding_ptr();</span><br><span class="line">    <span class="comment">//调整指针</span></span><br><span class="line">    adjust_ptr();</span><br><span class="line">    <span class="comment">//移动对象</span></span><br><span class="line">    move_obj();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>遍历root,对所有可达对象以及子对象进行标记</li><li>计算活动对象将要移动后的目的地址</li><li>更新活动对象的子对象指向新的地址</li><li>移动所有活动对象到头部，执行压缩</li></ol><h1 id="gc-mark-标记阶段"><a href="#gc-mark-标记阶段" class="headerlink" title="@gc_mark 标记阶段"></a>@gc_mark 标记阶段</h1><p><img src="/images/blog/gc-learning/XJNACPXRJB.png" alt=""></p><p>扫描root根，这里是模拟的root，真实的root可以参考<code>gc-try</code>实现的扫描系统栈<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; root_used;i++)</span><br><span class="line">    gc_mark(roots[i].ptr);</span><br></pre></td></tr></table></figure></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>* <span class="title">gc_mark</span><span class="params">(<span class="keyword">void</span> *ptr)</span></span>&#123;</span><br><span class="line">    <span class="comment">//检查指针是否合法</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* marking */</span></span><br><span class="line">    FL_SET(hdr, FL_MARK);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">void</span>* p = ptr; p &lt; (<span class="keyword">void</span>*)NEXT_HEADER(hdr); p++) &#123;</span><br><span class="line">        gc_mark(*(<span class="keyword">void</span> **)p);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ptr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>常规操作，对当前指针进行检查，然后扫描当前指针执行的内存段，对<code>child</code>进行递归引用标记</p><h1 id="set-forwarding-ptr-计算移动地址"><a href="#set-forwarding-ptr-计算移动地址" class="headerlink" title="@set_forwarding_ptr 计算移动地址"></a>@set_forwarding_ptr 计算移动地址</h1><p><img src="/images/blog/gc-learning/PEMZVKDQQO.png" alt=""></p><p>主要是遍历堆，然后计算活动对象应该被移动到的目的地址</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>     <span class="title">set_forwarding_ptr</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">size_t</span> i;</span><br><span class="line">    Header *p, *pend, *pnext ,*new_obj;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//遍历所有的堆内存</span></span><br><span class="line">    <span class="comment">//因为所有的内存都从堆里申请，所以需要遍历堆找出待回收的内存</span></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; gc_heaps_used; i++) &#123;</span><br><span class="line">        <span class="comment">//pend 堆内存结束为止</span></span><br><span class="line">        pend = (Header *)(((<span class="keyword">size_t</span>)gc_heaps[i].slot) + gc_heaps[i].size);</span><br><span class="line">        p = gc_heaps[i].slot;</span><br><span class="line">        new_obj = gc_heaps[i].slot;</span><br><span class="line">        <span class="comment">//堆的起始为止 因为堆的内存可能被分成了很多份，所以需要遍历该堆的内存</span></span><br><span class="line">        <span class="keyword">for</span> (; p &lt; pend; p = NEXT_HEADER(p))</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">//查看该堆是否已经被使用</span></span><br><span class="line">            <span class="keyword">if</span> (FL_TEST(p, FL_ALLOC)) &#123;</span><br><span class="line">                <span class="comment">//查看该堆是否被标记过</span></span><br><span class="line">                <span class="keyword">if</span> (FL_TEST(p, FL_MARK)) &#123;</span><br><span class="line">                    p-&gt;forwarding = new_obj;</span><br><span class="line">                    <span class="comment">//new_obj 继续下移 p个空间大小</span></span><br><span class="line">                    new_obj = (<span class="keyword">void</span>*)new_obj + p-&gt;size;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>主要是设置双指针<code>p,new_obj</code>,同时从头部开始遍历，计算目的地址</p><ol><li><code>p</code>指针去寻找活动对象</li><li>找到活动对象后将当前<code>new_obj</code>地址记下，并向后移动n字节</li></ol><p><img src="/images/blog/gc-learning/AQATCJYINY.png" alt=""></p><h1 id="adjust-ptr-更新引用"><a href="#adjust-ptr-更新引用" class="headerlink" title="@adjust_ptr 更新引用"></a>@adjust_ptr 更新引用</h1><p><img src="/images/blog/gc-learning/NEOQAFPKHE.png" alt=""></p><ol><li>初始时<code>block3</code>引用了<code>block4</code>,且<code>block3</code>应该移动到<code>block1</code>处，<code>block4</code>应该移动到<code>block2</code>处</li><li>更新后<code>block3</code>不再引用<code>block4</code>而是将要移动后的<code>block2</code>的位置</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">adjust_ptr</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//遍历所有对象</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; root_used; i ++)&#123;</span><br><span class="line">        Header* forwarding =  CURRENT_HEADER(roots[i].ptr)-&gt;forwarding;</span><br><span class="line">        roots[i].ptr = forwarding+<span class="number">1</span>;</span><br><span class="line">        *(Header**)roots[i].optr = forwarding+<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//堆的起始为止 因为堆的内存可能被分成了很多份，所以需要遍历该堆的内存</span></span><br><span class="line">    <span class="keyword">for</span> (p = gc_heaps[i].slot; p &lt; pend; p = NEXT_HEADER(p))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//可能申请的内存 里面又包含了其他内存</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">void</span>* obj = p+<span class="number">1</span>; obj &lt; (<span class="keyword">void</span>*)NEXT_HEADER(p); obj++)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">//正确找到了 child 引用</span></span><br><span class="line">            GC_Heap *gh;</span><br><span class="line">            Header  *hdr;</span><br><span class="line">            <span class="keyword">if</span> (!(gh = is_pointer_to_heap(*(<span class="keyword">void</span>**)obj))) <span class="keyword">continue</span>;</span><br><span class="line">            <span class="keyword">if</span>((hdr = get_header(gh,*(<span class="keyword">void</span>**)obj))) &#123;</span><br><span class="line">                *(Header **) obj = hdr-&gt;forwarding + <span class="number">1</span>; <span class="comment">//更新引用</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先更新root，因为所有活动对象的内存都发生了移动，需要更新栈变量存储的地址，也就是更新root所指向的地址</p><p>接下来就是遍历堆，这已经是第二次堆的遍历了，更新所有引用</p><h1 id="move-obj-移动对象"><a href="#move-obj-移动对象" class="headerlink" title="@move_obj 移动对象"></a>@move_obj 移动对象</h1><p><img src="/images/blog/gc-learning/VAWRJSOQOX.png" alt=""><br>最后就简单了，第三次堆遍历直接将所有对象拷贝到目的地址即可<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">new_obj = p-&gt;forwarding;</span><br><span class="line"><span class="built_in">memcpy</span>(new_obj, p, p-&gt;size);</span><br><span class="line">FL_UNSET(new_obj,FL_MARK);</span><br><span class="line"><span class="comment">//空闲链表下移</span></span><br><span class="line">free_p = (<span class="keyword">void</span>*)free_p + new_obj-&gt;size;</span><br><span class="line">total -=  new_obj-&gt;size;</span><br></pre></td></tr></table></figure></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//这个时候free_p 后面就是空闲列表了</span></span><br><span class="line">free_list = free_p;</span><br><span class="line"><span class="comment">//total 需要单独计算剩余空间</span></span><br><span class="line">free_list-&gt;size = total;</span><br><span class="line">free_list-&gt;next_free = <span class="literal">NULL</span>;</span><br><span class="line"><span class="comment">//方便测试 把空闲空间都清空</span></span><br><span class="line"><span class="built_in">memset</span>(free_list + <span class="number">1</span>,<span class="number">0</span>,total);</span><br></pre></td></tr></table></figure><p>这里要注意下,当前<code>free_p</code>的后面一定是空闲空间，前面一定是被压缩后的活动对象的空间，直接和<code>free_p</code>即可</p><p>顺带同时清除垃圾空间,到此就全部完成了gc</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="compact" scheme="http://wiki.brewlin.com/tags/compact/"/>
    
      <category term="compact-lisp2" scheme="http://wiki.brewlin.com/tags/compact-lisp2/"/>
    
  </entry>
  
  <entry>
    <title>GC复制-标记清除</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/5.GC%E5%A4%8D%E5%88%B6-%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/5.GC复制-标记清除/</id>
    <published>2020-11-11T07:18:49.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/QWXYKEENTC.png" alt=""></p><p><code>copying-or-marking</code>是一种组合算法，结合了复制法和标记清除法来优化复制算法<code>无法完整利用空间(通常来说只能利用1/2堆)</code>的问题</p><p>程序默认初始化<code>N</code>个堆，那么有:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">gc次数   from堆索引    to堆索引    标记清除堆范围</span><br><span class="line">----------------------------------------------</span><br><span class="line">1        heaps[0]      heaps[1]    heaps[2-(N-1)]           </span><br><span class="line">2        heaps[1]      heaps[2]    heaps[3-(N-1)]</span><br><span class="line">.....</span><br></pre></td></tr></table></figure></p><p>这种组合方式既能结合了复制算法的优点，又能优化对应的缺点，目前来说空间利用率可以达到<code>(N-1)/N</code>,最多浪费<code>1/N</code>的空间</p><p>同时作为标记清除算法的对应堆也不用担心内存碎片的问题，因为每次gc后，from和to对换后进行自增，慢慢迭代后对所有堆进行了复制</p><p>复制算法部分和之前一样，gc标记清除部分也和之前一样，接下来分析如何组合应用</p><p>文件结构<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">- Makefile     构建文件</span><br><span class="line">- copying.c    复制算法实现</span><br><span class="line">- mark-sweep.c 标记清除算法实现</span><br><span class="line">- test.c       测试用例</span><br></pre></td></tr></table></figure></p><p>test:<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; make</span><br><span class="line">&gt; ./gc</span><br></pre></td></tr></table></figure></p><p>gc复制+标记清除算法主要流程如下:</p><ol><li><code>gc_init</code>: 初始化<code>N</code>个堆，0作为to，1作为from</li><li><code>gc_mark_or_copy</code>: 搜索根，将所有<code>可达对象</code>进行拷贝或者标记</li><li><code>copy_reference</code>: 复制过后，需要更新之前的引用关系</li><li><code>gc_sweep</code>: 对其他堆执行标记清除算法</li><li>交换from,to, 进行递增</li></ol><h1 id="gc-init-初始化"><a href="#gc-init-初始化" class="headerlink" title="gc_init 初始化"></a>gc_init 初始化</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">gc_init</span><span class="params">(<span class="keyword">size_t</span> heap_size)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//关闭扩充堆</span></span><br><span class="line">    auto_grow = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">size_t</span> i = <span class="number">0</span>; i &lt; gc_heaps_used; i++)&#123;</span><br><span class="line">        <span class="comment">//使用sbrk 向操作系统申请大内存块</span></span><br><span class="line">        <span class="keyword">void</span>* p = sbrk(heap_size + PTRSIZE);</span><br><span class="line">        gc_heaps[i].slot = (Header *)ALIGN((<span class="keyword">size_t</span>)p, PTRSIZE);</span><br><span class="line">        gc_heaps[i].size = heap_size;</span><br><span class="line">        gc_heaps[i].slot-&gt;size = heap_size;</span><br><span class="line">        gc_heaps[i].slot-&gt;next_free = <span class="literal">NULL</span>;</span><br><span class="line">        <span class="comment">//默认情况下0 是给 to堆使用的  不需要挂载到 free_list 空闲链表上</span></span><br><span class="line">        <span class="keyword">if</span>(i) gc_free(gc_heaps[i].slot + <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>auto_grow = 0</code> 关闭自动扩充堆，为了聚焦于算法本身，我们只允许存在固定个数的堆</p><p>除了我们规定的<code>0</code>作为to堆外，其他都需要更新到空闲链表上</p><p>所有当调用<code>gc_malloc()</code>时，分配的内存既可能来自from，也可能来自<code>mark</code>堆</p><h1 id="gc阶段"><a href="#gc阶段" class="headerlink" title="gc阶段"></a>gc阶段</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>  <span class="title">gc</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"执行gc复制----\n"</span>);</span><br><span class="line">    <span class="comment">//每次gc前将 free指向 to的开头</span></span><br><span class="line">    gc_heaps[to].slot-&gt;size = gc_heaps[to].size;</span><br><span class="line">    free_p = gc_heaps[to].slot;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//递归进行复制  从 from  =&gt; to</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; root_used;i++)&#123;</span><br><span class="line">        <span class="keyword">void</span>* forwarded = gc_mark_or_copy(roots[i].ptr);</span><br><span class="line">        *(Header**)roots[i].optr = forwarded;</span><br><span class="line">        <span class="comment">//将root 所有的执行换到 to上</span></span><br><span class="line">        roots[i].ptr = forwarded;</span><br><span class="line">    &#125;</span><br><span class="line">    copy_reference();</span><br><span class="line">    <span class="comment">//其他部分执行gc清除</span></span><br><span class="line">    gc_sweep();</span><br><span class="line">    <span class="comment">//首先将free_p 指向的剩余空间  挂载到空闲链表上 </span></span><br><span class="line">    <span class="comment">//其实就是将原先to剩余的空间继续利用起来</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//如果没有剩余空间了则不进行操作</span></span><br><span class="line">    <span class="keyword">if</span>(free_p &lt; ((<span class="keyword">void</span>*)gc_heaps[to].slot + gc_heaps[to].size))</span><br><span class="line">        gc_free((Header*)free_p+<span class="number">1</span>);</span><br><span class="line">    <span class="comment">//在gc的时候 from已经全部复制到to堆</span></span><br><span class="line">    <span class="comment">//这个时候需要清空from堆，但是在此之前我们需要将free_list空闲指针还保留在from堆上的去除</span></span><br><span class="line">    remove_from();</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 清空 from 空间前:</span></span><br><span class="line"><span class="comment">     * 因为空闲链表 还指着from空间的，所以需要更新free_list 指针</span></span><br><span class="line"><span class="comment">     * </span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="built_in">memset</span>(gc_heaps[from].slot,<span class="number">0</span>,gc_heaps[from].size+HEADER_SIZE);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//开始交换from 和to</span></span><br><span class="line">    to = from;</span><br><span class="line">    from = (from + <span class="number">1</span>)%<span class="number">10</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每次执行<code>gc</code>前，都会初始一个<code>free_p</code>指针，指向to堆的首地址，每发生一次拷贝，指针往后移，指向空余空间</p><h2 id="扫描Root"><a href="#扫描Root" class="headerlink" title="扫描Root"></a>扫描Root</h2><p>接着就是进行根的扫描，将所有可达对象执行<code>gc_mark_or_copy</code>,这个函数会进行判断</p><ol><li>如果对象来自<code>mark</code>堆则不发生拷贝，直接标记即可</li><li>如果对象来自<code>from</code>堆则需要发生拷贝</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span>* forwarded = gc_mark_or_copy(roots[i].ptr);</span><br><span class="line">*(Header**)roots[i].optr = forwarded;</span><br><span class="line"><span class="comment">//将root 所有的执行换到 to上</span></span><br><span class="line">roots[i].ptr = forwarded;</span><br></pre></td></tr></table></figure><p>如果只是进行了标记，<code>forwarded</code>就是本身</p><p>如果执行了拷贝，<code>forwarded</code>执向的是to空间的新对象地址</p><h2 id="执行拷贝和清除"><a href="#执行拷贝和清除" class="headerlink" title="执行拷贝和清除"></a>执行拷贝和清除</h2><p>对发生了复制的空间进行引用更新</p><p>对标记清除区域执行清除<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">copy_reference();</span><br><span class="line"><span class="comment">//其他部分执行gc清除</span></span><br><span class="line">gc_sweep();</span><br></pre></td></tr></table></figure></p><h2 id="递增from-to空间"><a href="#递增from-to空间" class="headerlink" title="递增from-to空间"></a>递增from-to空间</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">remove_from();</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 清空 from 空间前:</span></span><br><span class="line"><span class="comment"> * 因为空闲链表 还指着from空间的，所以需要更新free_list 指针</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="built_in">memset</span>(gc_heaps[from].slot,<span class="number">0</span>,gc_heaps[from].size+HEADER_SIZE);</span><br><span class="line"></span><br><span class="line"><span class="comment">//开始交换from 和to</span></span><br><span class="line">to = from;</span><br><span class="line">from = (from + <span class="number">1</span>)%<span class="number">10</span>;</span><br></pre></td></tr></table></figure><ol><li>gc执行完毕后,<code>from</code>区域作为新的<code>to</code>区域，需要清理<code>from</code>的依赖关系</li><li>最后resetfrom堆</li><li>开始交换<code>from</code>-<code>to</code></li></ol><h1 id="gc-mark-or-copy-标记或拷贝"><a href="#gc-mark-or-copy-标记或拷贝" class="headerlink" title="@gc_mark_or_copy 标记或拷贝"></a>@gc_mark_or_copy 标记或拷贝</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 对该对象进行标记 或拷贝</span></span><br><span class="line"><span class="comment"> * 并进行子对象标记 或拷贝</span></span><br><span class="line"><span class="comment"> * 返回to空间的 body</span></span><br><span class="line"><span class="comment"> * @param ptr</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span>* <span class="title">gc_mark_or_copy</span><span class="params">(<span class="keyword">void</span>* ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(is_pointer_to_space(ptr,from))</span><br><span class="line">        <span class="keyword">return</span> gc_copy(ptr);   </span><br><span class="line">    <span class="keyword">return</span> gc_mark(ptr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>直接判断对象是否属于from堆来决定是拷贝还是标记</p><h1 id="gc-copy-复制阶段"><a href="#gc-copy-复制阶段" class="headerlink" title="@gc_copy 复制阶段"></a>@gc_copy 复制阶段</h1><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">void* gc_copy(void *ptr)</span><br><span class="line">&#123;</span><br><span class="line">    Header  *hdr;</span><br><span class="line">    GC_Heap *gh;</span><br><span class="line">    if (!(gh = is_pointer_to_space(ptr,from))) return NULL;</span><br><span class="line">    if (!(hdr = get_header(gh,ptr))) return NULL;</span><br><span class="line">    assert(FL_TEST(hdr,FL_ALLOC));</span><br><span class="line">    //没有复制过  0 </span><br><span class="line">    if(!IS_COPIED(hdr))&#123;</span><br><span class="line">        //.....执行拷贝</span><br><span class="line">        //从forwarding 指向的空间开始递归</span><br><span class="line">        for (void* p = (void*)(forwarding + 1); p &lt; (void*)NEXT_HEADER(forwarding); p++) &#123;</span><br><span class="line">            //对内存解引用，因为内存里面可能存放了内存的地址 也就是引用，需要进行引用的递归标记</span><br><span class="line">            //递归进行 引用的拷贝</span><br><span class="line">            gc_mark_or_copy(*(void **)p);</span><br><span class="line">        &#125;</span><br><span class="line">        //返回body</span><br><span class="line">        return forwarding + 1;</span><br><span class="line">    &#125;</span><br><span class="line">    //forwarding 是带有header头部的，返回body即可</span><br><span class="line">    return hdr-&gt;forwarding+1;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>和之前一样，只有在对<code>child</code>子对象递归判断时需要调用<code>gc_mark_or_copy</code>来决定是复制还是标记<br><img src="/images/blog/gc-learning/WZPCUBQEKR.png" alt=""></p><h2 id="检查是否已拷贝过"><a href="#检查是否已拷贝过" class="headerlink" title="检查是否已拷贝过"></a>检查是否已拷贝过</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//没有复制过  0</span></span><br><span class="line"><span class="keyword">if</span>(!IS_COPIED(hdr))</span><br><span class="line">&#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//forwarding 是带有header头部的，返回body即可</span></span><br><span class="line"><span class="keyword">return</span> hdr-&gt;forwarding+<span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>通过<code>IS_COPIED(hdr)</code>宏来判断标志位是否设置，是否已经拷贝过了</p><p>拷贝后，都需要返回拷贝后的指针</p><h2 id="拷贝对象到to空间"><a href="#拷贝对象到to空间" class="headerlink" title="拷贝对象到to空间"></a>拷贝对象到to空间</h2><p>上面步骤如果没有拷贝过，则需要进行拷贝</p><ol><li><p>从to空间分配一个空闲地址</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//计算复制后的指针</span></span><br><span class="line">Header *forwarding = (Header*)free_p;</span><br><span class="line"><span class="comment">//在准备分配前的总空间</span></span><br><span class="line"><span class="keyword">size_t</span> total = forwarding-&gt;size;</span><br></pre></td></tr></table></figure></li><li><p>进行拷贝,并更新原有对象标志位为<code>COPIED</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//分配一份内存 将源对象拷贝过来</span></span><br><span class="line"><span class="built_in">memcpy</span>(forwarding, hdr, hdr-&gt;size);</span><br><span class="line"><span class="comment">//标记为已拷贝</span></span><br><span class="line">FL_SET(hdr,FL_COPIED);</span><br><span class="line">hdr-&gt;flags = <span class="number">1</span>;</span><br></pre></td></tr></table></figure></li><li><p>更新to空间的下一个空闲指针</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//free 指向下一个 body</span></span><br><span class="line">free_p += hdr-&gt;size;</span><br><span class="line"><span class="comment">//free_p 执行的剩余空间需要时刻维护着</span></span><br><span class="line">((Header*)free_p)-&gt;size = total - hdr-&gt;size;</span><br></pre></td></tr></table></figure></li></ol><h2 id="递归扫描child对象"><a href="#递归扫描child对象" class="headerlink" title="递归扫描child对象"></a>递归扫描<code>child</code>对象</h2><p>当前对象拷贝过后，还需要对其<code>child</code>子对象引用进行拷贝，直接进行内存扫描即可<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//从forwarding 指向的空间开始递归</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">void</span>* p = (<span class="keyword">void</span>*)(forwarding+<span class="number">1</span>); p &lt; (<span class="keyword">void</span>*)NEXT_HEADER(forwarding); p++) &#123;</span><br><span class="line">    <span class="comment">//对内存解引用，因为内存里面可能存放了内存的地址 也就是引用，需要进行引用的递归标记</span></span><br><span class="line">    <span class="comment">//递归进行 引用的拷贝</span></span><br><span class="line">    gc_copy(*(<span class="keyword">void</span> **)p);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p><img src="/images/blog/gc-learning/HBZQJNDPVF.png" alt=""></p><h1 id="copy-reference-更新引用"><a href="#copy-reference-更新引用" class="headerlink" title="@copy_reference 更新引用"></a>@copy_reference 更新引用</h1><p>和复制法一样</p><h1 id="gc-sweep-执行清除"><a href="#gc-sweep-执行清除" class="headerlink" title="@gc_sweep 执行清除"></a>@gc_sweep 执行清除</h1><p>在root扫描阶段，除了会复制from堆上的内存外，其他堆都需要执行标记<br><img src="/images/blog/gc-learning/GEBWIWEGHS.png" alt=""><br>对图中除<code>from,to</code>以外的所有未标记的内存进行回收，已标记的置为未标记等待下次gc在进行判断</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//copying-or-mark/mark-sweep.c</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 清除 未标记内存 进行回收利用</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">void</span>     <span class="title">gc_sweep</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">size_t</span> i;</span><br><span class="line">    Header *p, *pend, *pnext;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//遍历所有的堆内存</span></span><br><span class="line">    <span class="comment">//因为所有的内存都从堆里申请，所以需要遍历堆找出待回收的内存</span></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; gc_heaps_used; i++) &#123;</span><br><span class="line">        <span class="comment">//to 和 from堆不需要进行清除</span></span><br><span class="line">        <span class="keyword">if</span>(i == from || i == to) <span class="keyword">continue</span>;</span><br><span class="line">        <span class="comment">//pend 堆内存结束为止</span></span><br><span class="line">        pend = (Header *)(((<span class="keyword">size_t</span>)gc_heaps[i].slot) + gc_heaps[i].size);</span><br><span class="line">        <span class="comment">//堆的起始为止 因为堆的内存可能被分成了很多份，所以需要遍历该堆的内存</span></span><br><span class="line">        <span class="keyword">for</span> (p = gc_heaps[i].slot; p &lt; pend; p = NEXT_HEADER(p)) &#123;</span><br><span class="line">            <span class="comment">//1. 是否已分配</span></span><br><span class="line">            <span class="comment">//2. 是否标记</span></span><br><span class="line">            <span class="comment">//3. 未标记回收</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>在清除阶段过滤掉<code>from,to</code>堆,<code>if(i == from || i == to) continue;</code></li><li>接下来和之前一样,找到未标记且分配过的内存，进行回收</li></ol><h1 id="remove-from-回收from空间"><a href="#remove-from-回收from空间" class="headerlink" title="@remove_from 回收from空间"></a>@remove_from 回收from空间</h1><p>这里主要指的是在<code>from</code>已经完全拷贝到<code>to</code>空间之后，空闲链表<code>free_list</code>依然有空闲的节点指向<code>from</code></p><p>这个时候就是找出<code>free_list</code>列表中还存留的<code>from</code>指针，找到后剔除<br><img src="/images/blog/gc-learning/DOTCENRAAQ.png" alt=""></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="copying-or-marking" scheme="http://wiki.brewlin.com/tags/copying-or-marking/"/>
    
  </entry>
  
  <entry>
    <title>GC复制</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/4.GC%E5%A4%8D%E5%88%B6/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/4.GC复制/</id>
    <published>2020-11-11T07:17:05.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/OVADTRYZNR.png" alt=""></p><p>复制算法，<code>安全解决了</code>标记清除算法中内存碎片的问题，每次执行gc时会将存活的对象<code>全部拷贝</code>到新的堆上,并且紧挨着排列</p><p>缺点是需要空出一个堆来作为存放区，带来的结果就是不能充分利用堆，在当前的实现中，总共初始化两个堆<code>from</code>,<code>to</code>堆，各占1/2</p><p>文件结构<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">- Makefile     构建文件</span><br><span class="line">- copying.c    主要代码实现</span><br><span class="line">- test.c       测试用例</span><br></pre></td></tr></table></figure></p><p>test:<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; make</span><br><span class="line">&gt; ./gc</span><br></pre></td></tr></table></figure></p><p>gc复制算法主要流程如下:</p><ol><li><code>gc_init</code>: 这里固定生成2个堆,<code>from</code>，<code>to</code></li><li><code>gc_copy</code>: 搜索根，将所有<code>可达对象</code>全部拷贝到<code>to</code>堆</li><li><code>copy_reference</code>: 复制过后，需要更新之前的引用关系</li></ol><h1 id="gc-init-初始化"><a href="#gc-init-初始化" class="headerlink" title="gc_init 初始化"></a>gc_init 初始化</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">gc_init</span><span class="params">(<span class="keyword">size_t</span> req_size)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    auto_gc   = <span class="number">1</span>;</span><br><span class="line">    <span class="comment">//关闭自动扩充堆</span></span><br><span class="line">    auto_grow = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">//使用sbrk 向操作系统申请大内存块</span></span><br><span class="line">    <span class="keyword">void</span>* from_p = sbrk(req_size + PTRSIZE );</span><br><span class="line">    from.slot  = (Header *)ALIGN((<span class="keyword">size_t</span>)from_p, PTRSIZE);</span><br><span class="line">    from.slot-&gt;next_free = <span class="literal">NULL</span>;</span><br><span class="line">    from.slot-&gt;size = req_size;</span><br><span class="line">    from.size       = req_size;</span><br><span class="line">    gc_free((<span class="keyword">void</span>*)(from.slot + <span class="number">1</span>));</span><br><span class="line">    DEBUG(<span class="built_in">printf</span>(<span class="string">"扩堆内存:%ld ptr:%p\n"</span>,req_size,from_p));</span><br><span class="line"></span><br><span class="line">    <span class="comment">//使用sbrk 向操作系统申请大内存块</span></span><br><span class="line">    <span class="keyword">void</span>* to_p = sbrk(req_size + PTRSIZE + HEADER_SIZE);</span><br><span class="line">    to.slot  = (Header *)ALIGN((<span class="keyword">size_t</span>)to_p, PTRSIZE);</span><br><span class="line">    to.slot-&gt;next_free = <span class="literal">NULL</span>;</span><br><span class="line">    to.slot-&gt;size = req_size;</span><br><span class="line">    to.size = req_size;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>auto_grow = 0</code> 关闭自动扩充堆，为了聚焦于算法本身，我们只允许存在两个堆</p><p>接下来就是剩下两个堆，分别作为<code>from</code>,<code>to</code>堆使用</p><ol><li><code>from</code>作为内存分配的堆</li><li><code>to</code> 作为每次gc后新的from堆(gc执行完后会<code>swap(from,to)</code>)</li></ol><h1 id="gc阶段"><a href="#gc阶段" class="headerlink" title="gc阶段"></a>gc阶段</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>  <span class="title">gc</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//每次gc前jiang free指向 to的开头</span></span><br><span class="line">    to.slot-&gt;size = to.size;</span><br><span class="line">    free_p = to.slot;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//递归进行复制  从 from  =&gt; to</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; root_used;i++)&#123;</span><br><span class="line">        <span class="keyword">void</span>* forwarded = gc_copy(roots[i].ptr);</span><br><span class="line">        *(Header**)roots[i].optr = forwarded;</span><br><span class="line">        <span class="comment">//将root 所有的执行换到 to上</span></span><br><span class="line">        roots[i].ptr = forwarded;</span><br><span class="line">    &#125;</span><br><span class="line">    copy_reference();</span><br><span class="line"></span><br><span class="line">    <span class="comment">//清空 from</span></span><br><span class="line">    <span class="built_in">memset</span>(from.slot,<span class="number">0</span>,from.size+HEADER_SIZE);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//开始交换from 和to</span></span><br><span class="line">    Header* tmp = from.slot;</span><br><span class="line">    from.slot = to.slot;</span><br><span class="line">    to.slot = tmp;</span><br><span class="line">    <span class="comment">//将空闲链表放到 to的最后一个索引</span></span><br><span class="line">    free_list = free_p;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每次执行<code>gc</code>前，都会初始一个<code>free_p</code>指针，指向to堆的首地址，每发生一次拷贝，指针往后移，指向空余空间</p><p>接着就是进行根的扫描，将所有可达对象执行<code>gc_copy</code>拷贝到<code>to</code>堆，也就是当前<code>free_p</code>指向的空间</p><p>注意这里<code>*(Header**)roots[i].optr = forwarded</code>就是root篇讲的关于引用地址更新的问题<br><img src="/images/blog/gc-learning/EDAAOXIGTL.png" alt=""></p><p>拷贝完成后,接着就是更新引用</p><p>最后就是交换<code>from</code>和<code>to</code>空间，之前的<code>to</code>空间继续作为<code>from</code>来使用</p><h1 id="gc-copy-复制阶段"><a href="#gc-copy-复制阶段" class="headerlink" title="@gc_copy 复制阶段"></a>@gc_copy 复制阶段</h1><p>复制阶段分为几个步骤:</p><ol><li>校验指针是否合法并获取对象头</li><li>检查是否已经拷贝过</li><li>从<code>to</code>空间分配新的空间用于存储待复制的对象</li><li>递归扫描<code>child</code>引用对象</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>* <span class="title">gc_copy</span><span class="params">(<span class="keyword">void</span> * ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Header *hdr;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!(hdr = get_header_by_from(ptr))) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>检查指针是否合法，并返回对象头</p><h2 id="检查是否已拷贝过"><a href="#检查是否已拷贝过" class="headerlink" title="检查是否已拷贝过"></a>检查是否已拷贝过</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//没有复制过  0</span></span><br><span class="line"><span class="keyword">if</span>(!IS_COPIED(hdr))</span><br><span class="line">&#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//forwarding 是带有header头部的，返回body即可</span></span><br><span class="line"><span class="keyword">return</span> hdr-&gt;forwarding+<span class="number">1</span>;</span><br></pre></td></tr></table></figure><p>通过<code>IS_COPIED(hdr)</code>宏来判断标志位是否设置，是否已经拷贝过了</p><p>拷贝后，都需要返回拷贝后的指针</p><h2 id="拷贝对象到to空间"><a href="#拷贝对象到to空间" class="headerlink" title="拷贝对象到to空间"></a>拷贝对象到to空间</h2><p>上面步骤如果没有拷贝过，则需要进行拷贝</p><ol><li><p>从to空间分配一个空闲地址</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//计算复制后的指针</span></span><br><span class="line">Header *forwarding = (Header*)free_p;</span><br><span class="line"><span class="comment">//在准备分配前的总空间</span></span><br><span class="line"><span class="keyword">size_t</span> total = forwarding-&gt;size;</span><br></pre></td></tr></table></figure></li><li><p>进行拷贝,并更新原有对象标志位为<code>COPIED</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//分配一份内存 将源对象拷贝过来</span></span><br><span class="line"><span class="built_in">memcpy</span>(forwarding, hdr, hdr-&gt;size);</span><br><span class="line"><span class="comment">//标记为已拷贝</span></span><br><span class="line">FL_SET(hdr,FL_COPIED);</span><br><span class="line">hdr-&gt;flags = <span class="number">1</span>;</span><br></pre></td></tr></table></figure></li><li><p>更新to空间的下一个空闲指针</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//free 指向下一个 body</span></span><br><span class="line">free_p += hdr-&gt;size;</span><br><span class="line"><span class="comment">//free_p 执行的剩余空间需要时刻维护着</span></span><br><span class="line">((Header*)free_p)-&gt;size = total - hdr-&gt;size;</span><br></pre></td></tr></table></figure></li></ol><h2 id="递归扫描child对象"><a href="#递归扫描child对象" class="headerlink" title="递归扫描child对象"></a>递归扫描<code>child</code>对象</h2><p>当前对象拷贝过后，还需要对其<code>child</code>子对象引用进行拷贝，直接进行内存扫描即可<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//从forwarding 指向的空间开始递归</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">void</span>* p = (<span class="keyword">void</span>*)(forwarding+<span class="number">1</span>); p &lt; (<span class="keyword">void</span>*)NEXT_HEADER(forwarding); p++) &#123;</span><br><span class="line">    <span class="comment">//对内存解引用，因为内存里面可能存放了内存的地址 也就是引用，需要进行引用的递归标记</span></span><br><span class="line">    <span class="comment">//递归进行 引用的拷贝</span></span><br><span class="line">    gc_copy(*(<span class="keyword">void</span> **)p);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p><img src="/images/blog/gc-learning/ZYXTGNDSDY.png" alt=""></p><h1 id="copy-reference-更新引用"><a href="#copy-reference-更新引用" class="headerlink" title="@copy_reference 更新引用"></a>@copy_reference 更新引用</h1><p>上一步骤执行完拷贝以及递归拷贝子引用后，空间结构应该是这样</p><p><img src="/images/blog/gc-learning/UASJANMKFJ.png" alt=""></p><p>可以看到<code>A,B</code>被root直接引用，被拷贝到了<code>to</code>空间，而<code>C</code>被A引用，也同时被拷贝过去了</p><p>而且复制过后的空间，引用对象会连续排列在一起，如<code>A,C</code>,这同时也是复制算法的一个优点，加快缓存访问速度</p><h2 id="引用更新"><a href="#引用更新" class="headerlink" title="引用更新"></a>引用更新</h2><p>上面讲了拷贝过后的A任然指向了<code>From</code>的C，需要更正这一点，完全复制依赖关系</p><p><img src="/images/blog/gc-learning/HKYDYXGEZU.png" alt=""></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">copy_reference</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//遍历所有对象</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; root_used; i ++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">void</span>* start =  roots[i].ptr;</span><br><span class="line">        <span class="keyword">void</span>* end   =  (<span class="keyword">void</span>*)NEXT_HEADER(CURRENT_HEADER(start));</span><br><span class="line"></span><br><span class="line">        <span class="comment">//可能申请的内存 里面又包含了其他内存</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">void</span> *p = start; p &lt; end;  p++) &#123;</span><br><span class="line"></span><br><span class="line">            Header* hdr;</span><br><span class="line">            <span class="comment">//解引用 如果该内存依然是指向的from，且有forwarding 则需要改了</span></span><br><span class="line">            <span class="keyword">void</span> *ptr = *(<span class="keyword">void</span>**)p;</span><br><span class="line">            <span class="keyword">if</span> (!(hdr = get_header_by_from(ptr))) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span>(hdr-&gt;forwarding)&#123;</span><br><span class="line">                <span class="built_in">printf</span>(<span class="string">"拷贝引用 hdr:%p forwarding:%p\n"</span>,hdr,hdr-&gt;forwarding);</span><br><span class="line">                *(Header**)p = hdr-&gt;forwarding + <span class="number">1</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>遍历所有的Root，判断是对象是否发生过拷贝<code>if(hdr-&gt;forwarding)</code></li><li>将拷贝的引用更新<code>*(Header**)p = hdr-&gt;forwarding + 1</code></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="copying" scheme="http://wiki.brewlin.com/tags/copying/"/>
    
  </entry>
  
  <entry>
    <title>引用计数</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/3.%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/3.引用计数/</id>
    <published>2020-11-10T07:15:55.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/SZTPNPTBML.png" alt=""></p><p>所有gc算法中引用计数最为特别,</p><p>相较于其他算法来说最明显的特点有:</p><ol><li>没有 <code>stw(stop the world)</code>时间</li><li>没有 <code>root</code>的概念，不需要扫描栈</li></ol><p>当然相较于其他算法来说弱点也很明显:</p><ol><li>增加了应用方的负担(<code>需要时刻注意增减引用计数</code>)</li><li>通常需要搭载其他算法才能解决<code>无法回收循环引用</code>的问题</li></ol><p>其他gc算法中基本没有对外提供api调用，对于应用层无感知，引用计数则需要开发者自己来维护跟踪分配的对象</p><ul><li><code>gc_inc</code>: 引用计数 + 1 通常在更低级的分配器中默认指向</li><li><code>gc_dec</code>: 引用计数 - 1 为0 则直接回收</li><li><code>gc_update</code>: 许多情况下需要保证计数正确</li></ul><h1 id="gc-inc-增加计数"><a href="#gc-inc-增加计数" class="headerlink" title="gc_inc 增加计数"></a>gc_inc 增加计数</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">gc_inc</span><span class="params">(<span class="keyword">void</span> *ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="comment">//该内存属于哪个堆</span></span><br><span class="line">  GC_Heap *gh;</span><br><span class="line">  <span class="comment">//该内存的header</span></span><br><span class="line">  Header *hdr;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//find header</span></span><br><span class="line">  <span class="keyword">if</span> (!(gh = is_pointer_to_heap(ptr)))&#123;</span><br><span class="line"><span class="comment">//      printf("not pointer\n");</span></span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (!(hdr = get_header(gh, ptr))) &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"not find header\n"</span>);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  hdr-&gt;ref++;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>is_pinter_to_heap</code>：常规操作，进行合法性指针检查</p><p><code>get_header</code>： 获取指针的头部，在确保该指针一定安全的情况下可以直接使用<code>CURRENT_HEADER</code>获取对象头</p><p><code>hdr-&gt;ref++</code>: 直接递增即可</p><h1 id="gc-dec-减少计数"><a href="#gc-dec-减少计数" class="headerlink" title="gc_dec 减少计数"></a>gc_dec 减少计数</h1><p>减少计数的步骤要多一点，除了递减计数外还要执行一次检查，如果小于等于0则直接回收垃圾<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">gc_dec</span><span class="params">(<span class="keyword">void</span> *ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  GC_Heap *gh;</span><br><span class="line">  Header *hdr;</span><br><span class="line">  <span class="comment">//find header</span></span><br><span class="line">  <span class="keyword">if</span> (!(gh = is_pointer_to_heap(ptr)))&#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (!(hdr = get_header(gh, ptr))) &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"not find header\n"</span>);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  hdr-&gt;ref -- ;</span><br><span class="line">  </span><br><span class="line">  <span class="comment">//接下来执行计数检查</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>和递增计数一样，找到对象头后，直接<code>hdr-&gt;ref--</code>计数即可，接下来看当计数为0时执行回收的情况<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (hdr-&gt;ref == <span class="number">0</span>) &#123;</span><br><span class="line">  <span class="comment">//对引用对象进行递归标记</span></span><br><span class="line">  <span class="keyword">void</span> *p;</span><br><span class="line">  <span class="keyword">void</span> *end = (<span class="keyword">void</span>*)NEXT_HEADER(hdr);</span><br><span class="line">  <span class="comment">//对引用进行递归 减引用</span></span><br><span class="line">  <span class="keyword">for</span> (p = ptr; p &lt; end; p++) &#123;</span><br><span class="line">    <span class="comment">//对内存解引用，因为内存里面可能存放了内存的地址 也就是引用，需要进行引用的递归标记</span></span><br><span class="line">    gc_dec(*(<span class="keyword">void</span> **)p);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//回收</span></span><br><span class="line">  gc_free(ptr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>对<code>[p,end]</code>的内存段进行扫描，递归进行<code>child</code>引用递归更新计数<br><img src="/images/blog/gc-learning/PUKYKOLBFI.png" alt=""></p><p>最后调用<code>gc_free</code>释放当前内存块即可</p><h1 id="gc-update-引用更新"><a href="#gc-update-引用更新" class="headerlink" title="gc_update 引用更新"></a>gc_update 引用更新</h1><p><img src="/images/blog/gc-learning/NWZUSIZGPI.png" alt=""><br><code>p-&gt;next</code>指向<code>p2</code>，在进行指针更新过后，没有任何对象在继续引用p2，所以在更新<code>p-&gt;next</code>的时候应该同时要<code>gc_dec(p-&gt;next)</code>来保证p2的计数正确,这就是<code>gc_udpate</code>的作用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">gc_update</span><span class="params">(<span class="keyword">void</span> *ptr,<span class="keyword">void</span> *obj)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  gc_inc(obj);</span><br><span class="line">  gc_dec(*(<span class="keyword">void</span>**)ptr);</span><br><span class="line">  *(<span class="keyword">void</span>**)ptr = obj;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>对目标对象<code>obj</code>进行计数 += 1，因为他被当前对象引用了</li><li>如上面所说需要将被更新的指针进行计数 -= 1</li><li>最后是更新即可</li></ol><p>使用上面的例子:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">obj</span>&#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">obj</span>* <span class="title">next</span>;</span></span><br><span class="line">    <span class="keyword">int</span>         value;</span><br><span class="line">&#125;Obj;</span><br><span class="line"></span><br><span class="line">Obj* p  = gc_malloc(<span class="keyword">sizeof</span>(Obj));</span><br><span class="line">p-&gt;next = gc_malloc(<span class="keyword">sizeof</span>(obj));</span><br><span class="line">Obj* p2 = gc_malloc(<span class="keyword">sizeof</span>(Obj));</span><br><span class="line"></span><br><span class="line">gc_update(&amp;p-&gt;next,p2);</span><br><span class="line"><span class="comment">//等价于 p-&gt;next = p2;</span></span><br></pre></td></tr></table></figure></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="reference-count" scheme="http://wiki.brewlin.com/tags/reference-count/"/>
    
  </entry>
  
  <entry>
    <title>标记清除-多链表法</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/2.%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4-%E5%A4%9A%E9%93%BE%E8%A1%A8%E6%B3%95/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/2.标记清除-多链表法/</id>
    <published>2020-11-09T13:29:59.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/LJFFHZORKL.png" alt=""></p><p>多链表法相较于单链表法提升了分配速度,在之前的<code>gc_malloc</code>分配中都是采用的单链表，在分配的时候需要去搜索单链表</p><p>多链表的好处就是省去了去查找块的时间，直接就获取了最近的空闲块</p><p>默认创建33个空闲链表<code>Header *free_list[33]</code>:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">* 请求大小 bytes        对齐后的大小 bytes        空闲链表的索引</span><br><span class="line">* ----------------------------------------------------------------</span><br><span class="line">*     <span class="number">1</span><span class="number">-8</span>                    <span class="number">8</span>                       <span class="number">0</span></span><br><span class="line">*  <span class="number">9</span><span class="number">-16</span>                   <span class="number">16</span>                       <span class="number">1</span></span><br><span class="line">* <span class="number">17</span><span class="number">-24</span>                   <span class="number">24</span>                       <span class="number">2</span></span><br><span class="line">* <span class="number">25</span><span class="number">-32</span>                   <span class="number">32</span>                       <span class="number">3</span></span><br><span class="line">* <span class="number">33</span><span class="number">-40</span>                   <span class="number">40</span>                       <span class="number">4</span></span><br><span class="line">* <span class="number">41</span><span class="number">-48</span>                   <span class="number">48</span>                       <span class="number">5</span></span><br><span class="line">* <span class="number">49</span><span class="number">-56</span>                   <span class="number">56</span>                       <span class="number">6</span></span><br><span class="line">* <span class="number">57</span><span class="number">-64</span>                   <span class="number">64</span>                       <span class="number">7</span></span><br><span class="line">* <span class="number">65</span><span class="number">-72</span>                   <span class="number">72</span>                       <span class="number">8</span></span><br><span class="line">*  ...                   ...                     ...</span><br><span class="line">*<span class="number">241</span><span class="number">-248</span>                 <span class="number">248</span>                      <span class="number">30</span></span><br><span class="line">*<span class="number">249</span><span class="number">-256</span>                 <span class="number">256</span>                      <span class="number">31</span></span><br><span class="line">*  &gt;   <span class="number">256</span>                                          <span class="number">32</span></span><br></pre></td></tr></table></figure></p><p>文件结构<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">- gc.c         堆实现</span><br><span class="line">- gc.h         头部定义</span><br><span class="line">- Makefile     构建文件</span><br><span class="line">- mark_sweep.c 主要代码实现</span><br><span class="line">- test.c       测试用例</span><br></pre></td></tr></table></figure></p><p>test:<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; make</span><br><span class="line">&gt; ./gc</span><br></pre></td></tr></table></figure></p><p>关于多链表的结构<br><img src="/images/blog/gc-learning/mark-sweep-multi.png" alt=""></p><p>接下来分析一下两种标记清除法的区别，标记和清除阶段都是一样的，主要需要分析下关于分配和释放的区别</p><h1 id="gc-malloc-内存分配"><a href="#gc-malloc-内存分配" class="headerlink" title="gc_malloc 内存分配"></a>gc_malloc 内存分配</h1><ol><li>根据size获取对应<code>空闲链表中的索引</code></li><li>在对应的索引中进行搜索</li><li>如果步骤2没有搜索到则需要向操作系统申请一份内存扩充堆</li></ol><h2 id="计算索引"><a href="#计算索引" class="headerlink" title="计算索引"></a>计算索引</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>*   <span class="title">gc_malloc</span><span class="params">(<span class="keyword">size_t</span> req_size)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"gc_malloc :%ld\n"</span>,req_size);</span><br><span class="line">    Header *p, *prevp;</span><br><span class="line">    <span class="keyword">size_t</span> do_gc = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span> (req_size &lt;= <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//对齐 字节</span></span><br><span class="line">    req_size = ALIGN(req_size, PTRSIZE);</span><br><span class="line">    <span class="keyword">int</span> index = (req_size - <span class="number">1</span>) &gt;&gt; ALIGNMENT_SHIFT;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(index &gt; MAX_SLICE_HEAP)</span><br><span class="line">        index = HUGE_BLOCK;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"gc_malloc :%d size:%ld\n"</span>,index,req_size);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//do sth...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>传入的<code>req_size</code>先加上<code>HEADER_SIZE</code>后进行字节对齐</p><p>然后通过<code>req_size - 1 &gt;&gt; 3</code>来获得索引，等价于<code>(req_size - 1) / 8</code>,因为当前都是根据<code>PTRSIZE(sizeof(void*))</code>8字节对齐的</p><p>得到索引后再判断一下，如果大于<code>MAX_SLICE_HEAP == 31</code>,说明字节数过大。大内存块统一走索引为<code>32</code>的空闲链表</p><h2 id="分配空间"><a href="#分配空间" class="headerlink" title="分配空间"></a>分配空间</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">alloc:</span><br><span class="line"><span class="comment">//从空闲链表上去搜寻 空余空间</span></span><br><span class="line">prevp = free_list[index];</span><br><span class="line"><span class="comment">//死循环 遍历</span></span><br><span class="line"><span class="keyword">for</span> (p = prevp; p; prevp = p, p = p-&gt;next_free) &#123;</span><br><span class="line">    <span class="comment">//堆的内存足够</span></span><br><span class="line">    <span class="keyword">if</span> (p-&gt;size &gt;= req_size) &#123;</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>free_ist[index]</code>处开始遍历该空闲链表，如果找到满足的情况进行分配后返回</p><h2 id="扩充空间"><a href="#扩充空间" class="headerlink" title="扩充空间"></a>扩充空间</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!do_gc &amp;&amp; auto_gc) &#123;</span><br><span class="line">    gc();</span><br><span class="line">    do_gc = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">goto</span> alloc;</span><br><span class="line">&#125;</span><br><span class="line">    <span class="comment">//上面说明 执行了gc之后 内存依然不够用 那么需要扩充堆大小</span></span><br><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> ((p = grow(req_size)) != <span class="literal">NULL</span>)&#123;</span><br><span class="line">    <span class="keyword">goto</span> alloc;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">NULL</span>;</span><br></pre></td></tr></table></figure><p>当无空闲链表可用时，先进行<code>gc</code>后进行<code>grow</code>新分配一份内存</p><p>如果都失败了则返回<code>NULL</code></p><h1 id="释放阶段"><a href="#释放阶段" class="headerlink" title="释放阶段"></a>释放阶段</h1><p>释放和之前的差不多，区别就是要对应索引</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>    <span class="title">gc_free</span><span class="params">(<span class="keyword">void</span> *ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    DEBUG(<span class="built_in">printf</span>(<span class="string">"start free mem:%p\n"</span>,ptr));</span><br><span class="line">    Header *target, *hit;</span><br><span class="line">    <span class="keyword">int</span> index;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//通过内存地址向上偏移量找到  header头</span></span><br><span class="line">    target = (Header *)ptr - <span class="number">1</span>;</span><br><span class="line">    <span class="comment">//回收的数据立马清空</span></span><br><span class="line">    <span class="built_in">memset</span>(ptr,<span class="number">0</span>,target-&gt;size);</span><br><span class="line"></span><br><span class="line">    index = (target-&gt;size - <span class="number">1</span>) &gt;&gt; ALIGNMENT_SHIFT;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>根据字节数计算出索引</p><p>这里和之前的释放还是有点区别<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//如果是小内存 不需要合并直接挂到最新的表头即可</span></span><br><span class="line"><span class="keyword">if</span>(index &lt;= MAX_SLICE_HEAP)&#123;</span><br><span class="line">    <span class="keyword">if</span>(free_list[index])&#123;</span><br><span class="line">        target-&gt;next_free = free_list[index]-&gt;next_free;</span><br><span class="line">        free_list[index]-&gt;next_free = target;</span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">        free_list[index] = target;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>现在不需要遍历空闲链表找到合适的位置进行<code>插入和合并了</code>,只需要插入表头即可,效率要高很多，复杂度为<code>O(1)</code></p><p>只有大内存块继续走之前的合并流程</p><h1 id="标记阶段"><a href="#标记阶段" class="headerlink" title="标记阶段"></a>标记阶段</h1><p>和<a href="https://wiki.brewlin.com/wiki/blog/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/1.%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95/">mark-sweep</a>篇标记阶段一样的</p><h1 id="清除阶段"><a href="#清除阶段" class="headerlink" title="清除阶段"></a>清除阶段</h1><p>和<a href="https://wiki.brewlin.com/wiki/blog/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/1.%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95/">mark-sweep</a>篇清除阶段一样的 </p><p>清除过后的情况应该是这样<br><img src="/images/blog/gc-learning/mark-sweep2.png" alt=""></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="mark-sweep" scheme="http://wiki.brewlin.com/tags/mark-sweep/"/>
    
  </entry>
  
  <entry>
    <title>GC算法分析与实现</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/GC%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90%E4%B8%8E%E5%AE%9E%E7%8E%B0/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/GC算法分析与实现/</id>
    <published>2020-11-09T13:28:59.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p>本系列文章主要阅读<code>垃圾回收的算法与实现</code>一书进行的实现解说，主要因为之前没有完整的gc算法实现的代码样例，书中各种实现都是基于伪码讲解，虽能理解作者的意思但难免还是有些抽象</p><p>遂写这些文章，记录下自己在学习算法理论并实际实现的过程。后续会追加分析其他语言的<code>gc实现</code>，深入理解生产级别是如何应用gc，以及如何极限的优化gc性能</p><h1 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h1><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ol><li>什么是root根? </li><li>什么是heaps堆?</li></ol><h2 id="算法实现"><a href="#算法实现" class="headerlink" title="算法实现"></a>算法实现</h2><ol><li>标记清除算法 - 基础实现</li><li>标记清除算法 - 多空闲链表法</li><li>引用计数算法</li><li>GC  复制算法</li><li>复制+标记清除 - 组合实现的多空间复制算法</li><li>标记压缩算法 - 基础实现</li><li>标记压缩算法 - two_finger实现</li><li>保守式gc算法 - <code>当前都是基于保守式gc算法</code></li><li>分代垃圾回收 - 复制算法+标记清除组合实现</li><li>增量式垃圾回收 - 三色标记</li></ol><h1 id="OS-环境参数"><a href="#OS-环境参数" class="headerlink" title="OS 环境参数"></a>OS 环境参数</h1><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&gt; gcc -v</span><br><span class="line">Thread model: posix</span><br><span class="line">gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)</span><br><span class="line"></span><br><span class="line">&gt; uname -a</span><br><span class="line">Linux ubuntu 4.4.0-157-generic <span class="comment">#185-Ubuntu SMP Tue Jul 23 09:17:01 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux</span></span><br></pre></td></tr></table></figure><h2 id="关于测试"><a href="#关于测试" class="headerlink" title="关于测试"></a>关于测试</h2><p>每个算法实现目录都有<code>test.c</code>,都是对当前算法的简单逻辑验证</p><p>根目录有一个<code>auto_test.sh</code> 脚本可以一次性跑全部的测试用例<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&gt; <span class="built_in">cd</span> gc-learning</span><br><span class="line">&gt; dos2unix auto_test.sh</span><br><span class="line">&gt; sh auto_test.sh</span><br></pre></td></tr></table></figure></p><h1 id="代码结构"><a href="#代码结构" class="headerlink" title="代码结构"></a>代码结构</h1><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">gc-learning</span><br><span class="line"></span><br><span class="line">----- gc.c</span><br><span class="line">----- gc.h</span><br><span class="line"></span><br><span class="line">----- mark-sweep </span><br><span class="line">----- mark-sweep-mulit-free-list</span><br><span class="line">----- reference-count</span><br><span class="line">----- copying</span><br><span class="line">----- copying-or-mark</span><br><span class="line">----- compact-lisp2</span><br><span class="line">----- compact-two-finger</span><br><span class="line">----- generational</span><br><span class="line">----- tri-color-marking</span><br></pre></td></tr></table></figure><p>所有的gc算法都依赖于公用<code>gc.c</code>中的的<code>heaps</code>堆内存池实现，可以先看<a href="./什么是堆?">什么是堆?</a>了解内存管理</p><p><code>gc.c</code>和<code>gc.h</code>是公用内存实现</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本系列文章主要阅读&lt;code&gt;垃圾回收的算法
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
  </entry>
  
  <entry>
    <title>最小生成树</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/graph/%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/graph/最小生成树/</id>
    <published>2020-11-09T13:28:59.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<h1 id="什么是最小生成树"><a href="#什么是最小生成树" class="headerlink" title="什么是最小生成树"></a>什么是最小生成树</h1><p><img src="/images/blog/graph/EGAEHJWVWH.png" alt=""></p><p>在一个有权图中，所有的边连接为一个图，那么用最少的边将所有节点连接起来且该连接和最小，可以称为最小生成树</p><ol><li>如上图: 图中==红色边==连接起来的就是最小树，既然最小，那么图就是有权的（有可比较的数值）</li><li>最小生成树 一定有v-1条边 v是顶点，也就是每个点只有一条连接<h2 id="前置要求"><a href="#前置要求" class="headerlink" title="前置要求"></a>前置要求</h2></li></ol><ul><li>带权无向图 (每个节点具有可比较的值)</li><li>针对连通图</li></ul><h1 id="应用场景举例"><a href="#应用场景举例" class="headerlink" title="应用场景举例"></a>应用场景举例</h1><ol><li>铁路线路构建</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">每个城市之间其实不需要都建立铁轨，只要保证所有的城市之间能够连通即可</span><br><span class="line"></span><br><span class="line">如上图:所有城市组成了一个图，而所有边是城市的连接.</span><br><span class="line">那么只有红色边是需要建立铁轨的，且保证了所有城市都有连通线路</span><br></pre></td></tr></table></figure><ol start="2"><li>电线布局</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">依然如此，使用最短的路径来连接所有节点</span><br></pre></td></tr></table></figure><h1 id="求证思路"><a href="#求证思路" class="headerlink" title="求证思路"></a>求证思路</h1><p>假入我们有如下组成的图结构:</p><p><img src="/images/blog/graph/IXJGBEZRFU.png" alt=""></p><p>总共有<code>a,b,c,d,e,f</code>6个节点，且存在10条边，每条边上的数字代表了该路径权值. 拿上面铁轨来说就是每个城市之间的距离。现在需要求出最小生成树，也就是去掉无关的边</p><ol><li><p><code>从a开始访问，将</code>a的所有边加入最小堆中<code>，</code>且标记a为已访问`<br><img src="/images/blog/graph/KDEZTDFWOB.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">从a出发有3条边 a-b a-e a-c</span><br><span class="line">那么可以看到a-b的权值为1 最小，且b没有访问过那么a-b就一定是</span><br><span class="line">最小生成树的一条边，进行标记并去除最小堆</span><br></pre></td></tr></table></figure></li><li><p>将b的所有连接边加入最小堆,并取出最小权值的边<br><img src="/images/blog/graph/CAYKDFVTLO.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">可以发现b-f在最小堆中权值最小，且f点未被访问过.</span><br><span class="line">那么b-f绝对是最小生成树中的一条边，对f进行标记并移除最小堆</span><br></pre></td></tr></table></figure></li><li><p>将f对应的边加入最小堆，并取出最小权值的边<br><img src="/images/blog/graph/MSWRWZMDUK.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">f-c 权值为2，目前为堆中最小，且c未被访问过，则f-c为生成树的一条边</span><br><span class="line">进行标记，剔除最小堆</span><br></pre></td></tr></table></figure></li><li><p>没有新加入边则，继续在堆中找出最小边<br><img src="/images/blog/graph/APPIYAWCAE.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">发现有三个节点的权值相同，但有一点不同</span><br><span class="line">b-c a-c 两个边的点都被标记过了，所以需要剔除</span><br><span class="line">最后只剩下b-e 最小，则进行标记并剔除最小堆</span><br></pre></td></tr></table></figure></li><li><p>加入e关联的边，继续找出堆中最小边<br><img src="/images/blog/graph/UENLXKASRP.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">a-e 已经访问过了，需要剔除</span><br><span class="line">e-d 最小，且d未访问过，则一定是最小生成树的边，进行标记、剔除</span><br></pre></td></tr></table></figure></li></ol><p>这样基本就全部访问完了，接下来就是剔除无关的边，最后得出如下最小生成树<br><img src="/images/blog/graph/UHKYSJRYFQ.png" alt=""></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h1 id=&quot;什么是最小生成树&quot;&gt;&lt;a href=&quot;#什么是最小生成树&quot; class=&quot;headerlink&quot; title=&quot;什么是最小生成树&quot;&gt;&lt;/a&gt;什么是最小生成树&lt;/h1&gt;&lt;p&gt;&lt;img src=&quot;/images/blog/graph/EGAEHJWVWH.png&quot; a
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/categories/algorithm/graph/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="graph" scheme="http://wiki.brewlin.com/tags/graph/"/>
    
      <category term="tree" scheme="http://wiki.brewlin.com/tags/tree/"/>
    
  </entry>
  
  <entry>
    <title>什么是Heap</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E5%89%8D%E8%A8%80/2.%E4%BB%80%E4%B9%88%E6%98%AFheaps/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/前言/2.什么是heaps/</id>
    <published>2020-11-09T13:28:59.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><code>heap</code>也就是堆，本意应该是系统堆的概念的，现代的语言为了加快内存分配速度，基本上都会自己预先分配一块大内存，也可以叫做内存池。这块大内存就是用户态的堆</p><p>在gc概念中就是heap,例如在标记类算法中，有一个gc环节叫做<code>清除(sweep)</code>，也就是回收垃圾，那么要实现这个功能，就要对<code>heap</code>进行遍历找出待回收的垃圾,所以这个堆就是我们用户态的一块大内存，非系统的堆</p><p>heap有以下api:</p><ul><li><code>gc_malloc</code> 内存分配</li><li><code>gc_free</code>   内存回收(搭载了gc的heap，不需要用户显示调用)</li><li><code>is_pointer_to_heap</code> 是否是heap中申请的内存</li></ul><p>接下来看下当前是如何管理内存的</p><h1 id="heap的结构"><a href="#heap的结构" class="headerlink" title="heap的结构"></a>heap的结构</h1><p><img src="/images/blog/gc-learning/heap.png" alt=""></p><h2 id="相关结构体"><a href="#相关结构体" class="headerlink" title="相关结构体"></a>相关结构体</h2><p><code>GC_HEAP</code>：维护堆信息<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">gc_heap</span> &#123;</span></span><br><span class="line">    Header *slot;</span><br><span class="line">    <span class="keyword">size_t</span> size;</span><br><span class="line">&#125; GC_Heap;</span><br></pre></td></tr></table></figure></p><ol><li><code>slot</code> 指向从操作系统申请的内存首地址,默认<code>4k</code>,也就是操作系统的一页大小</li><li><code>size</code> 每个heap的内存大小</li></ol><p><code>Header</code>: 实际上每份分配的内存都是默认会用掉一个头部空间<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">header</span> &#123;</span></span><br><span class="line">    <span class="keyword">size_t</span> ref;               <span class="comment">//引用计数中使用，其他算法忽略</span></span><br><span class="line">    <span class="keyword">size_t</span> flags;             <span class="comment">//marked,remembered,copied</span></span><br><span class="line">    <span class="keyword">size_t</span> size;              <span class="comment">//当前内存块大小</span></span><br><span class="line">    <span class="keyword">size_t</span> age;               <span class="comment">//分代回收中使用，表示年龄</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">header</span> *<span class="title">next_free</span>;</span> <span class="comment">//回收链表中使用，指向下一个空闲内存</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">header</span> *<span class="title">forwarding</span>;</span><span class="comment">//复制算法中使用, 指向拷贝后的新内存地址</span></span><br><span class="line">&#125; Header;</span><br></pre></td></tr></table></figure></p><p>为了更好的描述gc算法的实现，各种算法的公用标志都统一放到同一个header中，实际中肯定不能这么搞，太耗费空间了，很多都是用位来标记</p><ol><li><code>ref</code> 在引用计数中代表被引用的次数</li><li><code>flags</code> 有多个位标记</li><li><code>size</code> 指示当前内存块的大小 注意:<code>size = sizeof(Header) + sizeof(Obj)</code> 是包含了当前头部信息了的</li><li><code>age</code> 分代回收中表示年龄</li><li><code>next_free</code> 指向下一个空闲的内存,在内存分配的时候直接通过该字段来遍历空闲内存</li><li><code>forwarding</code> 复制类算法中指向了新地址</li></ol><p>header头 每个用户申请的内存都有一个隐形的头部,例如: gc_alloc(16) 实际申请了 16 + sizeof(header) 那么返回给用户的地址其实是 ptr + sizeof(header).<br>同样的也可以通过 ptr-sizeof(header) 拿到header头</p><p><img src="/images/blog/gc-learning/VBUXNWMHKD.png" alt=""></p><h2 id="宏定义和全局变量"><a href="#宏定义和全局变量" class="headerlink" title="宏定义和全局变量"></a>宏定义和全局变量</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* marco */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  TINY_HEAP_SIZE 4 * 1024              <span class="comment">//计算指针 所占内存大小</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  PTRSIZE ((size_t) sizeof(void *))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  HEADER_SIZE ((size_t) sizeof(Header))<span class="comment">//堆的上限</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  HEAP_LIMIT 100000                    <span class="comment">//字节对齐 向上取整</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  ALIGN(x,a) (((x) + (a - 1)) &amp; ~(a - 1))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  NEXT_HEADER(x) ((Header *)((size_t)(x+1) + (x-&gt;size- HEADER_SIZE))) <span class="comment">//[ [header] x-&gt;size [header] x-&gt;size ....]</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  CURRENT_HEADER(x) ((Header *)x - 1)</span></span><br></pre></td></tr></table></figure><ol><li><code>ALIGN</code> 是向上进行地址对齐，<code>ALIGN(6,8) == 8, ALIGN(9,8) == 16</code></li><li><code>NEXT_HEADER</code> 方便直接获取下一个连续内存地址</li><li><code>CURRENT_HEADER</code> 这个宏需要注意的一点是:需要自己保证传入的指针已经确认是堆里分配的，否则会导致不可预料的错误</li></ol><p>一些<code>flags</code> 标志位<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* flags */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  FL_ALLOC  0x1</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  FL_MARK   0x2</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  FL_COPIED 0x4</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  FL_REMEMBERED 0x8</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  FL_SET(x, f) (((Header *)x)-&gt;flags |= f)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  FL_UNSET(x, f) (((Header *)x)-&gt;flags &amp;= ~(f))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  FL_TEST(x, f) (((Header *)x)-&gt;flags &amp; f)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  IS_MARKED(x) (FL_TEST(x, FL_ALLOC) &amp;&amp; FL_TEST(x, FL_MARK))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  IS_COPIED(x) (FL_TEST(x, FL_ALLOC) &amp;&amp; FL_TEST(x, FL_COPIED))</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  IS_REMEMBERED(x) (FL_TEST(x, FL_ALLOC) &amp;&amp; FL_TEST(x, FL_REMEMBERED))</span></span><br></pre></td></tr></table></figure></p><p>一些全局变量<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* global variable */</span></span><br><span class="line"><span class="keyword">extern</span>   Header *free_list;</span><br><span class="line"><span class="keyword">extern</span>   GC_Heap gc_heaps[HEAP_LIMIT];</span><br><span class="line"><span class="keyword">extern</span>   <span class="keyword">size_t</span> gc_heaps_used;</span><br><span class="line"><span class="keyword">extern</span>   <span class="keyword">int</span> auto_gc;                  <span class="comment">//测试的时候 有时候需要关闭内存不够时执行gc</span></span><br><span class="line"><span class="keyword">extern</span>   <span class="keyword">int</span> auto_grow;                <span class="comment">//测试的时候 有时候需要关闭内存不够时扩充堆</span></span><br></pre></td></tr></table></figure></p><ol><li><code>free_list</code> 是一个单向链表，将所有heap的空闲空间串联起来,在执行<code>gc_malloc</code>时直接基于<code>first-fit</code>分配法遍历当前链表进行查找符合的大小内存,关于分配法:</li></ol><ul><li>best-fit 遍历空闲链表，找出刚好符合那块内存，优点是减少了内存碎片，缺点是增加了分配时间</li><li>first-fit 找到第一块符合大小的空间，如果大于申请的则进行拆分，缺点显然是内存碎片</li><li>worse-fit 每次都去寻找最大的内存块 然后切割分配，增加了内存碎片 时间也不咋地，所以避免使用这种</li></ul><ol start="2"><li><code>gc_heaps</code> 用户态堆，管理用户所有内存，默认每个heap 4k大小</li><li><code>auto_gc</code> 为了方便测试增加的开关，表示在内存不够时是否需要去执行<code>gc</code></li><li><code>auto_grow</code> 为了方便测试，表示在内存不够时是否需要立即扩充堆，新增一个4k页大小</li></ol><h1 id="gc-malloc分配"><a href="#gc-malloc分配" class="headerlink" title="gc_malloc分配"></a>gc_malloc分配</h1><p>内存分配流程，从内存池中查找一块空闲内存返回给申请方，主要流程如下:</p><ol><li>遍历<code>free_list</code>链表，找到大于等于当前内存的块</li><li>刚好满足则直接更新下返回即可，否则需要拆分块大小</li><li>步骤1没找到可用的块，则考虑进行gc</li><li>步骤3依然无可用块，则考虑扩充堆</li></ol><h2 id="字节对齐"><a href="#字节对齐" class="headerlink" title="字节对齐"></a>字节对齐</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//gc.c</span></span><br><span class="line">req_size += HEADER_SIZE;</span><br><span class="line"><span class="comment">//对齐 字节</span></span><br><span class="line">req_size = ALIGN(req_size, PTRSIZE);</span><br></pre></td></tr></table></figure><p>对申请的内存进行字节对齐,并且除了本身的size外，还要额外加上<code>header</code>的空间</p><h2 id="搜索块"><a href="#搜索块" class="headerlink" title="搜索块"></a>搜索块</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//gc.c</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//从空闲链表上去搜寻 空余空间</span></span><br><span class="line">prevp = free_list;</span><br><span class="line"><span class="comment">//死循环 遍历</span></span><br><span class="line"><span class="keyword">for</span> (p = prevp; p; prevp = p, p = p-&gt;next_free) &#123;</span><br><span class="line">    <span class="comment">//堆的内存足够</span></span><br><span class="line">    <span class="keyword">if</span> (p-&gt;size &gt;= req_size) </span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">if</span> (p-&gt;size == req_size)&#123;</span><br><span class="line">            <span class="comment">//刚好满足</span></span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="comment">//需要拆分当前块</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>直接遍历<code>free_list</code>空闲链表，前提是这个free_list已经将所有可用内存串联在一起了，而这些主要是在<code>gc_free</code>中做到的</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//gc.c</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 从空闲列表上 移除当前的 堆，因为申请的大小刚好把堆消耗完了</span></span><br><span class="line"><span class="keyword">if</span> (p-&gt;size == req_size)&#123;</span><br><span class="line">    <span class="keyword">if</span>(p == prevp)</span><br><span class="line">        free_list = prevp = p-&gt;next_free;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        prevp-&gt;next_free = p-&gt;next_free;    </span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">    prevp = (<span class="keyword">void</span>*)prevp + req_size;</span><br><span class="line">    <span class="built_in">memcpy</span>(prevp,p,HEADER_SIZE);</span><br><span class="line">    prevp-&gt;size = p-&gt;size - req_size;    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果空闲块刚刚好，则直接将空闲块移除链表，然后返回即可</p><p>如果空闲块比较大，则需要进行拆分,拆分从块起始处开始拆分</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">p-&gt;size = req_size;</span><br><span class="line">free_list = prevp;</span><br><span class="line"><span class="comment">//给新分配的p 设置为标志位 fl_alloc 为新分配的空间</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"%p\n"</span>,p);</span><br><span class="line">p-&gt;flags = <span class="number">0</span>;</span><br><span class="line">p-&gt;ref   = <span class="number">1</span>;</span><br><span class="line">FL_SET(p, FL_ALLOC);</span><br><span class="line"><span class="comment">//设置年龄为0</span></span><br><span class="line">p-&gt;age = <span class="number">0</span>;</span><br><span class="line">p-&gt;forwarding = <span class="literal">NULL</span>;</span><br></pre></td></tr></table></figure><p>这里是对新分配的块进行初始化操作，比如标志位置0等</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//gc.c</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!do_gc &amp;&amp; auto_gc) &#123;</span><br><span class="line">    gc();</span><br><span class="line">    do_gc = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">goto</span> alloc;</span><br><span class="line">&#125;<span class="keyword">else</span> <span class="keyword">if</span>(auto_grow)&#123; <span class="comment">//上面说明 执行了gc之后 内存依然不够用 那么需要扩充堆大小</span></span><br><span class="line">    p = gc_grow(req_size);</span><br><span class="line">    <span class="keyword">if</span>(p != <span class="literal">NULL</span>) <span class="keyword">goto</span> alloc;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="扩充堆-amp-gc"><a href="#扩充堆-amp-gc" class="headerlink" title="扩充堆&amp;gc"></a>扩充堆&amp;gc</h2><p>上面如果没有找到可用的空闲块，则需要考虑进行辅助操作，<code>gc or grow</code></p><p>接下来看看<code>grow</code>扩充一个堆的逻辑:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//gc.c</span></span><br><span class="line"></span><br><span class="line"><span class="function">Header* <span class="title">gc_grow</span><span class="params">(<span class="keyword">size_t</span> req_size)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Header *cp, *up;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!(cp = add_heap(req_size)))&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    up = (Header *) cp;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(free_list == <span class="literal">NULL</span>)&#123;</span><br><span class="line">        <span class="built_in">memset</span>(up +  <span class="number">1</span>,<span class="number">0</span>,up-&gt;size - HEADER_SIZE);</span><br><span class="line">        free_list = up;</span><br><span class="line">        up-&gt;flags = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">return</span> free_list;</span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">        gc_free((<span class="keyword">void</span> *)(up+<span class="number">1</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> free_list;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><ol><li>通过<code>add_heap</code> 申请一块最小为<code>4k</code>的空间</li><li>如果空闲链表为空则直接替换上去，返回</li><li><code>gc_free</code>不但可以释放小内存块，也可以将新的堆串联到空闲链表上</li></ol><p>这里基本就完成了内存块的分配</p><h1 id="gc-free释放"><a href="#gc-free释放" class="headerlink" title="gc_free释放"></a>gc_free释放</h1><p>释放的流程要稍微多一点，主要分为三个步骤:</p><ol><li>格式化待释放的内存</li><li>找到内存所对应的位置</li><li>挂载的链表上后结束</li></ol><h2 id="格式化"><a href="#格式化" class="headerlink" title="格式化"></a>格式化</h2><p>格式化内存<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//gc.c</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span>    <span class="title">gc_free</span><span class="params">(<span class="keyword">void</span> *ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    Header *target, *hit,*prevp;</span><br><span class="line">    <span class="comment">//调用方需要保证内存是合法的当前堆内存，否则就会发生段错误</span></span><br><span class="line">    target = (Header *)ptr - <span class="number">1</span>;</span><br><span class="line">    <span class="comment">//回收的数据立马清空</span></span><br><span class="line">    <span class="built_in">memset</span>(ptr,<span class="number">0</span>,target-&gt;size-HEADER_SIZE);</span><br><span class="line">    target-&gt;flags = <span class="number">0</span>;</span><br></pre></td></tr></table></figure></p><h2 id="特殊情况"><a href="#特殊情况" class="headerlink" title="特殊情况"></a>特殊情况</h2><p>特殊情况一:free_list为空时直接替换free_list返回即可<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//空闲链表为空，直接将当前target挂到上面</span></span><br><span class="line"><span class="keyword">if</span>(free_list == <span class="literal">NULL</span>)&#123;</span><br><span class="line">    free_list = target;</span><br><span class="line">    target-&gt;flags = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>特殊情况二:当前内存在free_list头部<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(NEXT_HEADER(target) == free_list)&#123;</span><br><span class="line">    target-&gt;size += (free_list-&gt;size);</span><br><span class="line">    target-&gt;next_free = free_list-&gt;next_free;</span><br><span class="line">    free_list = target;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p><img src="/images/blog/gc-learning/STMGAUIQAA.png" alt=""><br>直接将当前<code>target</code>合并到空闲链表头部</p><h2 id="定位内存在堆中的位置"><a href="#定位内存在堆中的位置" class="headerlink" title="定位内存在堆中的位置"></a>定位内存在堆中的位置</h2><p>定位待回收内存在堆中的位置，这个步骤是为了合并，相邻的两块内存必须要合并，否则会造成即使空闲空间足够但是依然不能够分配的窘迫</p><p><img src="/images/blog/gc-learning/BMZKONMDVQ.png" alt=""></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//搜索target可能在空闲链表上的区间位置</span></span><br><span class="line">prevp = free_list;</span><br><span class="line"><span class="keyword">for</span>(hit = prevp; hit &amp;&amp; hit-&gt;next_free ; prevp = hit,hit = hit-&gt;next_free)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">//刚好 target就在 [hit,hit-&gt;next_free] 之间</span></span><br><span class="line">    <span class="keyword">if</span>(target &gt;= hit &amp;&amp; target &lt;= hit-&gt;next_free)&#123;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//跨堆的情况 说明target在两个堆之间 (heap1_end,heap2_start)</span></span><br><span class="line">    <span class="keyword">if</span>(hit &gt;= hit-&gt;next_free &amp;&amp; (target &gt; hit || target &lt; hit-&gt;next_free))</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>主要分为4种情况:</p><ol><li><code>target</code> 属于右区间</li></ol><p><img src="/images/blog/gc-learning/XMJLYZNPYQ.png" alt=""></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//1. 判断右区间  如果target属于右区间 则合并</span></span><br><span class="line"><span class="keyword">if</span> (NEXT_HEADER(target) == hit-&gt;next_free) &#123;</span><br><span class="line">    target-&gt;size += hit-&gt;next_free-&gt;size;</span><br><span class="line">    target-&gt;next_free = hit-&gt;next_free-&gt;next_free;</span><br><span class="line">&#125;<span class="keyword">else</span> &#123;</span><br><span class="line">    target-&gt;next_free = hit-&gt;next_free;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个时候说明<code>NEXT_HEADER(target) == hit-&gt;next_free</code>成立，需要合并<code>target + hit-&gt;next_free</code></p><ol start="2"><li><code>target</code>不属于右区间</li></ol><p>如果右区间没有相邻，则直接插入<code>hit-&gt;next_free</code>前就行了</p><ol start="3"><li><code>target</code> 属于左区间</li></ol><p><img src="/images/blog/gc-learning/GSPPCNNPCH.png" alt=""></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//2. 判断左区间  如果target属于左区间 则合并</span></span><br><span class="line"><span class="keyword">if</span> (NEXT_HEADER(hit) == target) &#123;</span><br><span class="line">    <span class="comment">/* merge */</span></span><br><span class="line">    hit-&gt;size += target-&gt;size;</span><br><span class="line">    hit-&gt;next_free = target-&gt;next_free;</span><br><span class="line">&#125;<span class="keyword">else</span> &#123;</span><br><span class="line">    hit-&gt;next_free = target;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个时候<code>NEXT_HEADER(hit) == target</code>成立，合并左区间</p><ol start="4"><li><code>target</code> 不属于左区间</li></ol><p>直接挂在hit后就可以了</p><p>如果是新初始化的扩充堆基本上都不会触发上面的条件，直接挂到<code>free_list</code>尾节点即可</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;heap&lt;/code&gt;也就是堆，
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="前言" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E5%89%8D%E8%A8%80/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
  </entry>
  
  <entry>
    <title>什么是Root</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E5%89%8D%E8%A8%80/1.%E4%BB%80%E4%B9%88%E6%98%AFroots/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/前言/1.什么是roots/</id>
    <published>2020-11-09T13:28:59.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p>讲<code>root</code>根之前我们要先理解什么是<code>gc 回收</code>，怎么定义垃圾等等,区别垃圾可以简单这么理解:</p><ol><li>如果某对象没有被任何地方引用 - <code>垃圾对象</code></li><li>如果某对象至少还有一次被引用 - <code>合法对象</code></li></ol><p>那么如何辨别对象没有被任何地方引用呢，这就是<code>root</code>的作用了</p><h1 id="当前root的定义"><a href="#当前root的定义" class="headerlink" title="当前root的定义"></a>当前root的定义</h1><p>root根在不同场景有不同的意思，但有一点不变<code>通过root能够访问到的对象一定是合法对象</code>，则不应该被清除</p><p>root通常有以下的形式表示:</p><ul><li><code>全局变量空间</code></li><li><code>寄存器</code></li><li><code>函数栈</code></li></ul><p>并非只有上面的空间才能成为根,通常情况下对于动态运行时语言来说，都会在程序层面创建一个集合，然后自己来管理分配的对象，实现了根对象的管理</p><p>当前系列的gc实现不会真的去<code>搜索上面的这些区域去实现根的查找</code>,因为这样有些复杂而且不方便测试和演示</p><p><code>为了更加集中于gc算法的实现表示</code>，采用了一个<code>roots</code>数组来作为根，有如下的规则:</p><ol><li>只要是存在数组里的对象，都称为可达对象，<code>一定是合法对象，不可以回收</code></li><li>只要不在数组里的对象，都是不可达对象，作为垃圾需要回收<code>(被引用的内存除外)</code></li></ol><h2 id="root结构体定义"><a href="#root结构体定义" class="headerlink" title="root结构体定义"></a>root结构体定义</h2><p>root结构体的定义<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//gc.h</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">root_</span>&#123;</span></span><br><span class="line">    <span class="keyword">void</span> *ptr; <span class="comment">//从heap中申请的内存地址</span></span><br><span class="line">    <span class="keyword">void</span> *optr;<span class="comment">//用户栈变量的地址</span></span><br><span class="line">&#125;root;</span><br></pre></td></tr></table></figure></p><p><code>ptr</code> 指向了我们从<code>heaps</code>中分配的内存地址,也就是用户使用的对象</p><p><code>opr</code> 指向了用户变量的地址，这里讲一下这个成员的作用:</p><p>在有些算法中，例如<code>gc复制算法</code>,在执行gc之后，也许当前对象不是垃圾对象不会被回收,但是:当前对象的内存发生了拷贝，内存位置发生了改变<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Obj* p = gc_malloc(<span class="keyword">sizeof</span>(Obj));</span><br><span class="line">gc();</span><br></pre></td></tr></table></figure></p><p><img src="/images/blog/gc-learning/copying.png" alt=""></p><p>例如这种情况，<code>optr</code>的作用就体现出来了，在发生gc复制后，<code>p</code>本来应该指向新的空间，但是如果不更新p的值的话那么就会导致异常</p><p>因为我们的根保留了引用对象的地址(<code>临时变量基本都是存储在栈上的</code>，其实就是保留了栈的地址<code>rbp - offset</code>),这样只需要在gc执行复制的时候将引用对象一并修改了即可</p><p>root全局数组的定义<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span>  ROOT_RANGES_LIMIT 100000</span></span><br><span class="line"><span class="keyword">extern</span>   <span class="keyword">size_t</span> root_used;</span><br><span class="line"><span class="keyword">extern</span>   root roots[ROOT_RANGES_LIMIT];</span><br></pre></td></tr></table></figure></p><p>为了方便测试，直接在栈上分配了默认100000大小的根,通过<code>root_used</code>来记录根对象的个数</p><h2 id="添加root"><a href="#添加root" class="headerlink" title="添加root"></a>添加root</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Obj* p= gc_malloc(Obj);</span><br><span class="line">add_roots(&amp;p);</span><br></pre></td></tr></table></figure><p>通过<code>add_roots</code>将对象加入root，成为可达对象，那么只要一直在root中，当前对象永远不会被回收</p><p><code>&amp;p</code>注意这里是引用，在上面部分说了，如果执行的内存被拷贝到新地址了，需要同时更新p的地址</p><h1 id="Real-Root-尝试"><a href="#Real-Root-尝试" class="headerlink" title="Real Root 尝试"></a>Real Root 尝试</h1><p>上面基本都是讲的模拟的根，那么我们来尝试一下可不可以实现真正意义的根访问呢</p><p>这里的测试主要分为<code>寄存器的访问</code>，<code>系统栈的遍历搜索</code></p><p>完整代码可以在<code>gc-try</code>下测试</p><h2 id="寄存器的扫描"><a href="#寄存器的扫描" class="headerlink" title="寄存器的扫描"></a>寄存器的扫描</h2><p>首先来统计一下我们在程序运行期间能够使用到的寄存器</p><ol><li><code>函数参数寄存器</code>: rdi,rsi,rdx,rcx,r8,r9 多的就存放在栈上了不用管</li><li><code>通用寄存器</code>    : rax,rbx,rbp,rsp,%10-%15</li></ol><p>不严谨的说上面这些寄存器是我们最常用的通用寄存器，也就是说寄存器里面可能存储着有我们的对象，需要我们gc的时候进行扫描<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">scan_register</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">void</span> *reg;</span><br><span class="line">    <span class="keyword">if</span>(reg = get_sp())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">    <span class="keyword">if</span>(reg = get_bp())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">    <span class="keyword">if</span>(reg = get_di())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">    <span class="keyword">if</span>(reg = get_si())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">    <span class="keyword">if</span>(reg = get_dx())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">    <span class="keyword">if</span>(reg = get_cx())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">    <span class="keyword">if</span>(reg = get_r8())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">    <span class="keyword">if</span>(reg = get_r9())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">    <span class="keyword">if</span>(reg = get_ax())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">    <span class="keyword">if</span>(reg = get_bx())  gc_mark(*(<span class="keyword">void</span>**)reg);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>上面相关的函数可以在这里找到<code>/gc-try/root.s</code></p><h2 id="系统栈的扫描"><a href="#系统栈的扫描" class="headerlink" title="系统栈的扫描"></a>系统栈的扫描</h2><p>这里是gc过程的一部分<code>遍历root</code><br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//现在开始是真正的扫描系统栈空间</span></span><br><span class="line"><span class="keyword">void</span> * cur_sp = get_sp();</span><br><span class="line"><span class="comment">//高低往低地址增长</span></span><br><span class="line">assert(sp_start &gt;= cur_sp);</span><br><span class="line"><span class="keyword">for</span> (; cur_sp &lt; sp_start ; cur_sp += <span class="number">4</span>)&#123;</span><br><span class="line">    gc_mark(*(<span class="keyword">void</span>**)cur_sp);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><ol><li>通过<code>get_sp()</code> 直接获取当前的系统栈顶,也就是<code>rsp寄存器</code>的地址</li><li><code>sp_start</code> 是我们在<code>main</code>开始前记录的栈起始位置</li><li><code>[sp_start,cur_sp]</code> 这个区间就是我们当前的栈范围，直接扫描整个区间就可以访问我们所有的可达变量</li><li><code>(void**)cur_sp</code> 是一个解引用操作，此时获取的值就是我们的代码里的临时变量</li></ol><p>要理解我们扫描栈的意义就要先理解什么是栈，一张图说明一下c的函数栈帧结构:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">                         +--------------+  -&gt; 这里就是函数A的栈帧范围了</span><br><span class="line">                         |              |  </span><br><span class="line">                    +    |              |</span><br><span class="line">                    |    +--------------+</span><br><span class="line">                    |    |              |</span><br><span class="line">                    |    |   arg(N-1)   |  -&gt; 参数超过6个后，其他参数就放在这里</span><br><span class="line">                    |    |              |</span><br><span class="line">                    |    +--------------+</span><br><span class="line">                    |    |              |</span><br><span class="line">                    |    |Return address|  -&gt; 这里指向函数A的中断的下一个指令地址</span><br><span class="line">Stack grows down    |    |              |</span><br><span class="line">                    |    +--------------+</span><br><span class="line">                    |    |              |</span><br><span class="line">                    |    |     %rbp     |  -&gt; 这里指向函数A的起始栈帧rbp</span><br><span class="line">                    |    |              |</span><br><span class="line">                    |    +--------------+  -&gt; 下面就是函数B的栈帧，当前rbp</span><br><span class="line">                    |    |              |</span><br><span class="line">                    |    |  local(N-1)  |  -&gt; 函数B的本地变量</span><br><span class="line">                    |    |              |</span><br><span class="line">                    v    +--------------+  </span><br><span class="line">                         |              |</span><br><span class="line">                         |              |</span><br><span class="line">                         +--------------+  -&gt; 当前栈顶</span><br></pre></td></tr></table></figure><p><code>上面get_sp()</code>函数是通过汇编实现获取当前寄存器<code>rsp</code>的值，如下:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">.text</span><br><span class="line">.globl get_sp</span><br><span class="line">get_sp:</span><br><span class="line">    movq %rsp,%rax</span><br><span class="line">    ret</span><br></pre></td></tr></table></figure></p><p>其实就是简单的返回了<code>rsp</code>寄存器的值而已，翻译为c函数的话像这样:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> *<span class="title">get_sp</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">register</span>(rsp);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>到这里就是实现了真正意义上的<code>根root</code>，只要当前栈未被释放，那么当前栈帧上能搜索到的的对象都是合法对象</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;讲&lt;code&gt;root&lt;/code&gt;根之前我
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="前言" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E5%89%8D%E8%A8%80/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
  </entry>
  
  <entry>
    <title>标记清除算法</title>
    <link href="http://wiki.brewlin.com/wiki/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/1.%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95/"/>
    <id>http://wiki.brewlin.com/wiki/algorithm/gc-learning/算法实现/1.标记清除算法/</id>
    <published>2020-11-09T13:28:59.000Z</published>
    <updated>2022-06-30T08:06:37.444Z</updated>
    
    <content type="html"><![CDATA[<p>github: <a href="https://github.com/brewlin/garbage-collect">https://github.com/brewlin/garbage-collect</a></p><p><img src="/images/blog/gc-learning/mark-sweep.png" alt=""></p><p><code>标记-清除</code> 算法主要分为两个过程:<code>标记O(N)</code>、<code>清除O(N)</code>,接下来讲解gc的代码实现</p><p>文件结构<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">- Makefile     构建文件</span><br><span class="line">- mark_sweep.c 主要代码实现</span><br><span class="line">- test.c       测试用例</span><br></pre></td></tr></table></figure></p><p>test:<br><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; make</span><br><span class="line">&gt; ./gc</span><br></pre></td></tr></table></figure></p><h1 id="gc阶段"><a href="#gc阶段" class="headerlink" title="gc阶段"></a>gc阶段</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>  <span class="title">gc</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//垃圾回收前 先从 root 开始 进行递归标记</span></span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i = <span class="number">0</span>;i &lt; root_used;i++)</span><br><span class="line">        gc_mark(roots[i].ptr);</span><br><span class="line">    <span class="comment">//标记完成后 在进行 清除 对于没有标记过的进行回收</span></span><br><span class="line">    gc_sweep();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li>对根进行遍历，不清楚根可以去看(<code>什么是Root?</code>),进行可达性标记</li><li>进入清除阶段，将所有垃圾进行回收，释放可用空间，更新空闲链表</li></ol><p>标记清除的实现主要就是<code>标记(marked)</code>，通过对根的访问，对所有可以追踪到的对象都进行标记<code>mark = 1</code>，标记阶段就完成任务了</p><p>清除阶段会遍历整个堆，所以复杂度是<code>O(N)</code>，随着堆的增加而呈线性增长，清除阶段会对每份内存进行判断，如果<code>mark = 0</code>则认定为垃圾对象，进行回收。</p><p>整个gc阶段就完成了，如果有释放垃圾，此时新分配的内存就可以重复利用刚才释放的空间了</p><h1 id="标记阶段"><a href="#标记阶段" class="headerlink" title="标记阶段"></a>标记阶段</h1><p>接下来详细了解一下标记阶段，标记阶段主要完成四件事</p><ol><li>判断当前指针是否合法</li><li>进行标记,如果已经标记过则不需要再次标记</li><li>对当前对象进行标记</li><li><code>对child引用</code>进行递归标记</li></ol><h2 id="检查指针是否合法"><a href="#检查指针是否合法" class="headerlink" title="检查指针是否合法"></a>检查指针是否合法</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">gc_mark</span><span class="params">(<span class="keyword">void</span> * ptr)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    GC_Heap *gh;</span><br><span class="line">    Header *hdr;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* mark check */</span></span><br><span class="line">    <span class="keyword">if</span> (!(gh = is_pointer_to_heap(ptr)))&#123;</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">if</span> (!(hdr = get_header(gh, ptr))) &#123;</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (!FL_TEST(hdr, FL_ALLOC)) &#123;</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (FL_TEST(hdr, FL_MARK)) &#123;</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol><li><code>is_pointer_to_heap</code>: 判断传入的指针是否是堆里的合法内存，通过地址范围判断</li><li><code>get_header</code>: 获取指针的对象头</li><li><code>FL_*</code>： 这个开头的是一些宏定义，可以进行位操作，这里是判断<code>header-&gt;flags</code>有没有设置 <code>FL_ALLOC</code>内存分配标志</li><li><code>FL_TEST</code>: 这里判断如果已经标记过了，不需要再次标记</li></ol><h2 id="标记对象"><a href="#标记对象" class="headerlink" title="标记对象"></a>标记对象</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//.....</span></span><br><span class="line"><span class="comment">/* marking */</span></span><br><span class="line">FL_SET(hdr, FL_MARK);</span><br><span class="line"><span class="comment">//.....</span></span><br></pre></td></tr></table></figure><p>这里展开就是:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">((Header*)hdr)-&gt;flags |= <span class="number">0x2</span></span><br></pre></td></tr></table></figure></p><p>对对象头进行标记，表明当前对象是可达对象，是合法对象，不能被清除</p><h2 id="递归进行child标记"><a href="#递归进行child标记" class="headerlink" title="递归进行child标记"></a>递归进行<code>child</code>标记</h2><p>关于引用的标记其实就是遍历当前内存的地址空间，对每一个字节逐字扫描，发现了合法指针就进行标记,例如:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">obj</span>&#123;</span></span><br><span class="line">        <span class="keyword">int</span>         value;</span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">obj</span>* <span class="title">next</span>;</span></span><br><span class="line">    &#125;Obj;</span><br><span class="line">    </span><br><span class="line">    Obj* ptr  = gc_malloc(<span class="keyword">sizeof</span>(Obj));</span><br><span class="line">    ptr-&gt;next = gc_malloc(<span class="keyword">sizeof</span>(Obj));</span><br><span class="line">    ptr-&gt;next-&gt;next = gc_malloc(<span class="keyword">sizeof</span>(Obj));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>如果对p进行了标记，那么<code>ptr-&gt;next</code>也应该被标记，因为他们之间有引用关系，怎么做到的呢<br><img src="/images/blog/gc-learning/CGEMWKIOUD.png" alt=""></p><p>对<code>ptr</code>的内存段<code>start</code>到<code>end</code>这个区间进行遍历<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//进行child 节点递归 标记</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">void</span>* p = ptr; p &lt; (<span class="keyword">void</span>*)NEXT_HEADER(hdr); p++) &#123;</span><br><span class="line">    <span class="comment">//对内存解引用，因为内存里面可能存放了内存的地址 也就是引用，需要进行引用的递归标记</span></span><br><span class="line">    gc_mark(*(<span class="keyword">void</span> **)p);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>正常情况下遍历到<code>(void*)ptr + sizeof(int)</code>处应该就是<code>p-&gt;next</code>的地址，如此递归不放过任何的角落</p><ol><li><code>gc_mark(ptr)</code></li><li><code>gc_mark(ptr-&gt;next)</code></li><li><code>gc_mark(ptr-&gt;next-&gt;next)</code></li></ol><p>标记完应该是这样的</p><p><img src="/images/blog/gc-learning/mark-sweep1.png" alt=""></p><h1 id="清除阶段"><a href="#清除阶段" class="headerlink" title="清除阶段"></a>清除阶段</h1><p>清除阶段就简单啦，直接搜索堆，将所有的<code>已使用</code>却<code>没标记</code>的内存释放</p><ol><li><p>遍历<code>gc_heaps</code>数组</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span>     <span class="title">gc_sweep</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">size_t</span> i;</span><br><span class="line">    Header *p, *pend, *pnext;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//遍历所有的堆内存</span></span><br><span class="line">    <span class="comment">//因为所有的内存都从堆里申请，所以需要遍历堆找出待回收的内存</span></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; gc_heaps_used; i++) &#123;</span><br><span class="line">        <span class="comment">//pend 堆内存结束为止</span></span><br><span class="line">        pend = (Header *)(((<span class="keyword">size_t</span>)gc_heaps[i].slot) + gc_heaps[i].size);</span><br><span class="line">        <span class="comment">//do ...</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>搜索<code>heap</code>查看该分块是否已经分配<code>FL_ALLOC</code>,没有该标志说明是空闲块，不需要理会</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//堆的起始为止 因为堆的内存可能被分成了很多份，所以需要遍历该堆的内存</span></span><br><span class="line"><span class="keyword">for</span> (p = gc_heaps[i].slot; p &lt; pend; p = NEXT_HEADER(p)) &#123;</span><br><span class="line">    <span class="comment">//查看该堆是否已经被使用</span></span><br><span class="line">    <span class="keyword">if</span> (FL_TEST(p, FL_ALLOC)) &#123;</span><br><span class="line">        <span class="comment">//do..</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>解除标志，如果没有被标记过说明是<code>垃圾</code>: 进行<code>gc_free</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//查看该堆是否被标记过</span></span><br><span class="line"><span class="keyword">if</span> (FL_TEST(p, FL_MARK)) &#123;</span><br><span class="line">    DEBUG(<span class="built_in">printf</span>(<span class="string">"解除标记 : %p\n"</span>, p));</span><br><span class="line">    <span class="comment">//取消标记，等待下次来回收，如果在下次回收前</span></span><br><span class="line">    <span class="comment">//1. 下次回收前发现该内存又被重新访问了，则不需要清除</span></span><br><span class="line">    <span class="comment">//2. 下次回收前发现该内存没有被访问过，所以需要清除</span></span><br><span class="line">    FL_UNSET(p, FL_MARK);</span><br><span class="line">&#125;<span class="keyword">else</span> &#123;</span><br><span class="line">    DEBUG(<span class="built_in">printf</span>(<span class="string">"清除回收 :\n"</span>));</span><br><span class="line">    gc_free(p+<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ol><p>清除过后的堆应该是这样的:</p><p><img src="/images/blog/gc-learning/mark-sweep2.png" alt=""></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;github: &lt;a href=&quot;https://github.com/brewlin/garbage-collect&quot;&gt;https://github.com/brewlin/garbage-collect&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/blog
      
    
    </summary>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/categories/algorithm/"/>
    
      <category term="gc-learning" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/"/>
    
      <category term="算法实现" scheme="http://wiki.brewlin.com/categories/algorithm/gc-learning/%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0/"/>
    
    
      <category term="c" scheme="http://wiki.brewlin.com/tags/c/"/>
    
      <category term="gc" scheme="http://wiki.brewlin.com/tags/gc/"/>
    
      <category term="algorithm" scheme="http://wiki.brewlin.com/tags/algorithm/"/>
    
      <category term="mark-sweep" scheme="http://wiki.brewlin.com/tags/mark-sweep/"/>
    
  </entry>
  
</feed>
