<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>pkemb</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://pkemb.com/</id>
  <link href="https://pkemb.com/" rel="alternate"/>
  <link href="https://pkemb.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, pkemb</rights>
  <title>熊孩子程序员</title>
  <updated>2026-05-10T14:45:09.000Z</updated>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <content>
      <![CDATA[<p>WireGuard是一款简洁、快速且现代化的VPN。本文记录了利用wireguard搭建内网穿透的步骤。</p><span id="more"></span><h2 id="准备工作"><a class="header-anchor" href="#准备工作">¶</a>准备工作</h2><p>首先，需要一台公网服务器，假设其地址是<code>wg-server.com</code>。防火墙放开一个UDP端口给wireguard使用，假设是<code>54321</code>。</p><p>再准备一台局域网服务器，作为局域网入口设备。我选择的是<code>immortalWrt</code>旁路由。如果有多个局域网需要穿透，则每个局域网都需要准备一台。下面以两个局域网为例。假如第一个局域网网段是<code>192.168.x.0/24</code>，旁路由IP是<code>192.168.x.51</code>。第二个局域网网段是<code>192.168.y.0/24</code>，旁路由IP是<code>192.168.y.53</code>。需要特别注意的是，这两个局域网网段不能一样。</p><p>给wireguard网络选择一个网段，可以任意选择。下面以<code>10.28.0.0/24</code>为例。</p><p>对接入wireguard网络的设备，我选择静态分配IP地址。所以需要给所有接入wireguard网络的设备规划IP地址。我有3台移动设备，外加2台旁路由，所以总共5台设备。IP分配参考下表。</p><table><thead><tr><th>IP</th><th>主机名</th><th>备注</th></tr></thead><tbody><tr><td>10.28.0.1</td><td><a href="http://wg-server.com">wg-server.com</a></td><td>公网服务器</td></tr><tr><td>10.28.0.51</td><td>wrt-x.lan</td><td>第一个局域网旁路由，局域网网段<code>192.168.x.0/24</code>。DNS服务器。</td></tr><tr><td>10.28.0.53</td><td>wrt-y.lan</td><td>第二个局域网旁路由，局域网网段<code>192.168.y.0/24</code>。DNS服务器。</td></tr><tr><td>10.28.0.135</td><td>windows.lan</td><td>接入wireguard网络的Windows电脑</td></tr><tr><td>10.28.0.150</td><td>phone.lan</td><td>接入wireguard网络的手机</td></tr><tr><td>10.28.0.178</td><td>linux.lan</td><td>接入wireguard网络的linux电脑</td></tr></tbody></table><p>以上所有<code>lan</code>域名，均在两台旁路由的dns服务器添加了解析，解析地址是局域网IP，而不是wireguard网络IP。</p><p>最终设置完成后，windows电脑、Linux电脑以及手机，在任意网络环境下接入公网，连上wireguard网络后，就可以使用<code>lan</code>域名访问局域网下面的设备。</p><p>以下是设置的详细过程。</p><h2 id="服务器安装与配置"><a class="header-anchor" href="#服务器安装与配置">¶</a>服务器安装与配置</h2><p>所有接入wireguard网络的设备，通过这一台公网服务器交换数据。安装命令参考如下，需要root权限。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt install wireguard</span><br></pre></td></tr></table></figure><h3 id="生成私钥与公钥"><a class="header-anchor" href="#生成私钥与公钥">¶</a>生成私钥与公钥</h3><p>服务器和所有客户端都需要生成一对私钥和公钥。下面生成了服务器和所有客户端的私钥和公钥。后面编写配置文件的时候会用到。</p><figure class="highlight shell"><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">mkdir -p /etc/wireguard</span><br><span class="line">cd /etc/wireguard</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">服务端密钥</span></span><br><span class="line">wg genkey | tee server_privatekey | wg pubkey &gt; server_publickey</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">客户端密钥，按需生成</span></span><br><span class="line">wg genkey | tee wrt-x_privatekey | wg pubkey &gt; wrt-x_publickey</span><br><span class="line">wg genkey | tee wrt-y_privatekey | wg pubkey &gt; wrt-y_publickey</span><br><span class="line">wg genkey | tee windows_privatekey | wg pubkey &gt; windows_publickey</span><br><span class="line">wg genkey | tee phone_privatekey | wg pubkey &gt; phone_publickey</span><br><span class="line">wg genkey | tee linux_privatekey | wg pubkey &gt; linux_publickey</span><br></pre></td></tr></table></figure><h3 id="创建配置文件"><a class="header-anchor" href="#创建配置文件">¶</a>创建配置文件</h3><p>创建配置文件<code>/etc/wireguard/wg0.conf</code>。内容参考如下。<code>[Interface]</code>是服务器网卡的配置。PostUp表示启动网卡时执行的指令，这条iptable规则表示允许wireguard进来的流量通过本机转发。随后是5个<code>[Peer]</code>。公钥填入对应客户端的公钥，<code>AllowedIPs</code>填入给客户端分配的IP地址。对于两台旁路由，还需要填入其所在局域网的网段。</p><figure class="highlight plaintext"><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">[Interface]</span><br><span class="line">PrivateKey = 服务器私钥</span><br><span class="line">Address = 10.28.0.1/32</span><br><span class="line">ListenPort = 54321</span><br><span class="line">DNS = 10.28.0.51</span><br><span class="line">PostUp = echo 1 &gt; /proc/sys/net/ipv4/ip_forward</span><br><span class="line"></span><br><span class="line">PostUp = iptables -A FORWARD -i wg0 -j ACCEPT</span><br><span class="line">PostDown = iptables -D FORWARD -i wg0 -j ACCEPT</span><br><span class="line"></span><br><span class="line"># wrt-x.lan</span><br><span class="line">[Peer]</span><br><span class="line">PublicKey = 客户端私钥</span><br><span class="line">AllowedIPs = 10.28.0.51/32,192.168.x.0/24</span><br><span class="line"></span><br><span class="line"># wrt-y.lan</span><br><span class="line">[Peer]</span><br><span class="line">PublicKey = 客户端私钥</span><br><span class="line">AllowedIPs = 10.28.0.53/32,192.168.y.0/24</span><br><span class="line"></span><br><span class="line"># windows.lan</span><br><span class="line">[Peer]</span><br><span class="line">PublicKey = 客户端私钥</span><br><span class="line">AllowedIPs = 10.28.0.135/32</span><br><span class="line"></span><br><span class="line"># phone.lan</span><br><span class="line">[Peer]</span><br><span class="line">PublicKey = 客户端私钥</span><br><span class="line">AllowedIPs = 10.28.0.150/32</span><br><span class="line"></span><br><span class="line"># linux.lan</span><br><span class="line">[Peer]</span><br><span class="line">PublicKey = 客户端私钥</span><br><span class="line">AllowedIPs = 10.28.0.178/32</span><br></pre></td></tr></table></figure><h3 id="启动服务"><a class="header-anchor" href="#启动服务">¶</a>启动服务</h3><p>最后，启动wireguard服务，并打开开机自启动。如果服务启动失败，可以用命令<code>wg-quick up wg0</code>启动，观察log。配置文件修改后，可以使用命令<code>systemctl restart wg-quick@wg0</code>重启服务。服务重启后，客户端如果要立即重连，也需要重启。也可以等待一段时间后，自动重连。取决于客户端设置的<code>PersistentKeepalive</code>参数。</p><figure class="highlight shell"><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">systemctl start wg-quick@wg0.service</span><br><span class="line">systemctl enable wg-quick@wg0.service</span><br></pre></td></tr></table></figure><h2 id="客户端"><a class="header-anchor" href="#客户端">¶</a>客户端</h2><h3 id="immortalwrt系统"><a class="header-anchor" href="#immortalwrt系统">¶</a>immortalwrt系统</h3><p>两个旁路由的设置过程类型，这里以<code>wrt-x.lan</code>为例。软件包搜索安装 luci-proto-wireguard。安装完成后重启系统。</p><p>系统启动后，进入<code>网络-&gt;接口-&gt;添加新接口</code>，名字写wg0，协议选择<code>WireGuard VPN</code>，点击创建接口，进入接口配置页面。</p><p><strong>常规设置</strong>选项卡：</p><ul><li>私钥：步骤 <a href="#%E7%94%9F%E6%88%90%E7%A7%81%E9%92%A5%E4%B8%8E%E5%85%AC%E9%92%A5">生成私钥与公钥</a> 有给客户端生成私钥，填入即可</li><li>公钥：步骤 <a href="#%E7%94%9F%E6%88%90%E7%A7%81%E9%92%A5%E4%B8%8E%E5%85%AC%E9%92%A5">生成私钥与公钥</a> 有给客户端生成公钥，填入即可</li><li>监听端口：不填</li><li>IP地址：填入规划的IP地址。这是指定客户端网卡的IP地址，最好设置成 <code>/32</code>。</li></ul><img src="https://image.pkemb.com/2026/05/a7f2a130b29d02b52e70fdfa4da2726a.png" style="zoom: 60%;" /><p><strong>防火墙</strong>选项卡，创建防火墙区域<code>wg</code>。<strong>对端</strong>选项卡，点击<code>添加对端</code>。<code>公钥</code>填入服务器的公钥，<code>私钥</code>填入服务器的私钥，<code>允许的IP</code>填<code>10.28.0.0/24</code>和<code>192.168.x.0/24</code>，勾选<code>为允许的IP创建路由</code>，<code>对端地址</code>填入服务器的地址，<code>对端端口</code>填入服务器的端口，<code>持续保持连接</code>填25。点击<code>保存</code>，回到上一级页面，点击<code>保存并应用</code>。如果再次修改网卡配置，保存后，建议手工点一下<code>重启网卡</code>，确保设置生效。</p><blockquote><p>划重点，<code>持续保持连接</code>一定要填，如果没有填，客户端不会定时握手，NAT环境下可能会导致网络断连。</p></blockquote><img src="https://image.pkemb.com/2026/05/692a98029b80fce4921bb2b2b37bb709.png" style="zoom: 60%;" /><p>接口配置完成后，还需要配置防火墙，进入<code>网络-&gt;防火墙</code>，在常规设置选项卡中，找到wg区域，点击编辑，输入、出站数据、区域内转发都选择接受，<code>允许转发到目标区域</code>和<code>允许来自源区域的转发</code>都选择lan区域。这是允许wg区域访问lan区域的网络。最终设置效果如下。</p><p><img src="https://image.pkemb.com/2026/05/c57fc6aff158f5db57acf9455d523fac.png" alt=""></p><p>点击NAT规则选项卡，新增一个nat规则。名称填<code>wg-to-lan</code>，出站区域选lan，操作选MASQUERADE。如下图所示。点击保存。这条NAT规则是wireguard网络能访问局域网设备的关键。</p><img src="https://image.pkemb.com/2026/05/c1c7057b69b9e7855f99199a087cb674.png" style="zoom: 60%;" /><p>至此，immortalwrt wireguard设置完成。登录服务器，<code>10.28.0.51</code>和<code>192.168.x.51</code>，以及<code>192.168.x.0/24</code>局域网下的所有设备都能ping通了。如果旁路由使用<code>SamrtDNS</code>作为主dns服务器，还需要取消smartdns的设备绑定。<code>服务-&gt;SmartDNS</code>，高级设置选项卡，取消勾选绑定设备，点击保存。在主服务器，使用指令<code>nslookup wrt-x.lan 10.28.0.51</code>测试DNS服务器是否正常。</p><h3 id="iphone手机"><a class="header-anchor" href="#iphone手机">¶</a>iphone手机</h3><p>AppStore搜索并安装 wireguard。注意，需要美区账号才能搜索到。在任意一个Linux系统创建配置文件，示例如下。在<code>[Interface]</code>字段填入手机的私钥、地址和DNS服务器。因为要解析<code>lan</code>域名，所以DNS指向旁路由。<code>[Peer]</code>填入服务器的信息，关键是<code>AllowedIPs</code>的配置，这里决定了手机能访问哪一个局域网。假如手机已经在一个局域网了，那么<code>AllowedIPs</code>只需要加另一个局域网网段。</p><figure class="highlight plaintext"><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">[Interface]</span><br><span class="line">PrivateKey = phone私钥</span><br><span class="line">Address = 10.28.0.150/32</span><br><span class="line">DNS = 10.28.0.51</span><br><span class="line"></span><br><span class="line">[Peer]</span><br><span class="line">PublicKey = 服务器公钥</span><br><span class="line">Endpoint = 服务器地址</span><br><span class="line">AllowedIPs = 10.28.0.0/24, 192.168.x.0/24, 192.168.y.0/24</span><br><span class="line">PersistentKeepalive = 25</span><br></pre></td></tr></table></figure><p>使用指令 <code>qrencode -t ansiutf8 &lt; xxx.conf</code> 将配置文件转换成二维码。如果没有<code>qrencode</code>指令，可以使用<code>apt install qrencode</code> 安装。iPhone 打开wireguard，点击右上角的加号，选择扫描二维码，扫描刚刚添加的二维码即可。然后按提示操作即可。激活网络，在浏览器输入局域网网址，测试网络是否能正常访问。</p><h3 id="Linux系统"><a class="header-anchor" href="#Linux系统">¶</a>Linux系统</h3><p>我的Linux系统是Mint。安装指令如下，需要root权限。</p><figure class="highlight shell"><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">apt install wireguard</span><br><span class="line">mkdir -p /etc/wireguard</span><br><span class="line">cd /etc/wireguard</span><br></pre></td></tr></table></figure><p>创建配置文件 <code>/etc/wireguard/wg0.conf</code>，示例配置如下。添加了一条PostUp指令，将网卡wg0的域名设置为<code>lan</code>。确保能解析本地域名。使用指令<code>wg-quick up wg0</code>加入wireguard网络，执行<code>wg-quick down wg0</code>退出wireguard网络。加入和退出都需要root权限。</p><figure class="highlight plaintext"><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">[Interface]</span><br><span class="line">PrivateKey = 客户端私钥</span><br><span class="line">Address = 10.28.0.178/32</span><br><span class="line">DNS = 10.28.0.51</span><br><span class="line">PostUp = resolvectl domain wg0 &quot;lan&quot;</span><br><span class="line"></span><br><span class="line">[Peer]</span><br><span class="line">PublicKey = 服务器公钥</span><br><span class="line">Endpoint = 服务器地址</span><br><span class="line">AllowedIPs = 10.28.0.0/24, 192.168.x.0/24, 192.168.y.0/24</span><br><span class="line">PersistentKeepalive = 25</span><br></pre></td></tr></table></figure><h3 id="windows系统"><a class="header-anchor" href="#windows系统">¶</a>windows系统</h3><p>进入<a href="https://download.wireguard.com/windows-client/">https://download.wireguard.com/windows-client/</a>下载安装包，下载完成后安装并打开。首先创建配置文件，示例如下。在wireguard页面，新建隧道，选择刚刚创建的配置文件，点击连接，即可接入wireguard网络。</p><figure class="highlight plaintext"><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">[Interface]</span><br><span class="line">PrivateKey = 客户端私钥</span><br><span class="line">Address = 10.28.0.135/32</span><br><span class="line">DNS = 10.28.0.51</span><br><span class="line"></span><br><span class="line">[Peer]</span><br><span class="line">PublicKey = 服务器公钥</span><br><span class="line">Endpoint = 服务器地址</span><br><span class="line">AllowedIPs = 10.28.0.0/24, 192.168.x.0/24, 192.168.y.0/24</span><br><span class="line">PersistentKeepalive = 25</span><br></pre></td></tr></table></figure><h2 id="添加新的客户端"><a class="header-anchor" href="#添加新的客户端">¶</a>添加新的客户端</h2><p>如果未来有新的客户端需要加入，步骤如下：</p><ol><li>分配IP地址</li><li>创建私钥和公钥</li><li>服务器添加peer，并重启服务</li><li>编写配置文件</li><li>安装对应系统的客户端，导入配置文件</li></ol><h2 id="参考"><a class="header-anchor" href="#参考">¶</a>参考</h2><ul><li><a href="https://www.cnblogs.com/wangguishe/p/17286120.html">https://www.cnblogs.com/wangguishe/p/17286120.html</a></li><li><a href="https://zhuanlan.zhihu.com/p/27783376221">https://zhuanlan.zhihu.com/p/27783376221</a></li></ul>]]>
    </content>
    <id>https://pkemb.com/2026/05/wireguard/</id>
    <link href="https://pkemb.com/2026/05/wireguard/"/>
    <published>2026-05-10T14:45:09.000Z</published>
    <summary>
      <![CDATA[<p>WireGuard是一款简洁、快速且现代化的VPN。本文记录了利用wireguard搭建内网穿透的步骤。</p>]]>
    </summary>
    <title>wireguard搭建记录</title>
    <updated>2026-05-10T14:45:09.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <content>
      <![CDATA[<p>在搭建和完善HomeLab的过程中，有一个非常重要的服务就是DNS服务。本地DNS服务器负责解析本地域名，并转发上游DNS服务器的解析结果，是本地服务能正常访问的基石。本文介绍了自己搭建本地DNS服务器使用的几种方案，并简单对比了一下优缺点。</p><span id="more"></span><h2 id="方案1-hosts文件"><a class="header-anchor" href="#方案1-hosts文件">¶</a>方案1 hosts文件</h2><p>在进行DNS查询之前，会先检查一下<code>hosts</code>文件，如果有对应的记录，则直接返回结果。Windows系统的文件路径是<code>C:\Windows\System32\drivers\etc\hosts</code>，Linux系统是<code>/etc/hosts</code>。关于<code>hosts</code>文件更详细的介绍可以参考<a href="https://en.wikipedia.org/wiki/Hosts_(file)">wiki百科</a>。</p><p>这个方案最大的优点是简单。文件格式简单，修改起来很方便。如果要快速拉起一个服务，或者覆盖公共DNS服务器的结果，是一个不错的选择。</p><p>但是缺点也很明显，在某些场景下甚至是致命的。首先是只支持A、AAAA记录，无法添加<code>txt</code>、<code>mx</code>等其他类型的记录。假如要搭建一个邮箱服务，<code>hosts</code>文件完全无法支持。其次是只对本机有效，这意味着局域网内的其他机器无法通过域名的方式访问服务。</p><h2 id="方案2-本机Windows运行bind服务"><a class="header-anchor" href="#方案2-本机Windows运行bind服务">¶</a>方案2 本机Windows运行bind服务</h2><p>为了客服<code>hosts</code>无法添加A、AAAA记录的缺点，首先尝试了<code>bind</code>。<code>bind</code>是一款非常专业的DNS服务器，其详细介绍可以参考<a href="https://bind9.readthedocs.io/en/latest/">BIND 9 Administrator Reference Manual</a>。</p><p>使用<code>bind</code>确实能添加其他类型的记录，但引入了更多的问题。首先，bind太专业了，导致维护起来太难了。如果遇到什么问题，解决起来也很麻烦。第二，需要手工修改网卡的DNS服务器地址。如果只有一两台设备还好，设备一旦多了，那将是灾难。第三，本机Windows不是服务器，不会24小时开机。如果休眠或关机，那么DNS指向此台电脑的设备将无法上网，又要修改网卡的DNS地址。非常麻烦。</p><p>这是一个非常不完美的解决方案，所以只使用的一小段时间，就迅速切换到了下一个方案。</p><h2 id="方案3-路由器dnsmasq"><a class="header-anchor" href="#方案3-路由器dnsmasq">¶</a>方案3 路由器dnsmasq</h2><p><code>bind</code>的主要痛点是维护困难，不能24小时提供服务。而路由器里面的<code>dnsmasq</code>刚好能解决这两个问题。<code>dnsmasq</code>是一个轻量级的dns服务器软件，同时路由器又能满足24小时运行的要求，完美。详细的搭建过程可以参考<a href="https://pkemb.com/2021/10/local-dns-server/">搭建局域网DNS服务器</a>。在路由器的设置中，将DNS服务器设置为路由器的IP地址，还能解决需要为每一台设备修改DNS服务器地址的问题。</p><p>此方案看起来非常完美，但实际使用的过程中，还是遇到了一些令人非常不爽的问题。首先，这个方案挑路由器，需要路由器提供追加dnsmasq配置文件的方法。如果是一些低端或运营商提供的路由器，很大概率是没有的。就需要更换路由器，或者刷机。</p><p>其次，路由器的地理位置是比较固定的，一般不会发生变化。但我的绝大多数服务是搭建在笔记本的一台VMware Linux虚拟机中，而且是桥接网卡。逢年过节回老家的时候，理论上依旧能访问这些服务，但需要经过一番折腾。老家的路由器没有本地域名的解析服务，需要想办法添加上。回到工作地点后，又要把这些临时设置的DNS记录又删除，着实麻烦。</p><p>最后，虚拟机之间通过域名访问，流量还需要走路由器，绕了一大圈，效率太低。</p><h2 id="方案4-Linux服务器运行dnsmasq"><a class="header-anchor" href="#方案4-Linux服务器运行dnsmasq">¶</a>方案4 Linux服务器运行dnsmasq</h2><p>此方案其实是对方案3的一个补充，和方案3同时使用。为了解决虚拟机之间通信走路由器的问题，我给每个虚拟机添加了两个网卡，一个NAT，一个桥接。这样每台虚拟机都有两个IP地址。其中一台开机自启动的Linux服务器部署DNS服务。如果DNS地址指向路由器，那么返回桥接网卡的IP地址，如果DNS地址指向Linux服务器，那么返回NAT网卡的IP地址。</p><p>只要将笔记本网卡的DNS地址指向Linux服务器，那么无论接入什么路由器，都能保证笔记本能解析本地域名。</p><p>方案3+方案4一直工作的很好，但遇到了NAT网卡IP地址变化导致无法访问的问题。桥接网卡在路由器设置了静态IP绑定，所以没有问题。NAT网卡虽然可以在VMware的DHCP配置文件设置静态IP地址，但如果在UI界面修改了其他的网络设置，配置文件会被覆盖，静态IP的配置就会丢失。也可以关闭VMware DHCP服务，在各个系统内部设置静态IP，就是太麻烦了。不同操作系统，甚至同一个操作系统的不同版本，设置方法都不同。不过还好，NAT网卡获取到的IP地址一般不会变化，暂时还能接受。</p><h2 id="方案5-openwrt旁路由"><a class="header-anchor" href="#方案5-openwrt旁路由">¶</a>方案5 openwrt旁路由</h2><p>VMware的DHCP服务没有一个很方便的查询IP地址的方法，所以每次IP变化后，都需要废很大的功夫才能确定新的IP地址。痛苦了几次后，终于受不了了，开始寻找新的方案。这个新的方案就是 openwrt 旁路由。</p><p>主路由关闭DHCP服务，由旁路由提供DHCP和DNS服务。同时通过DHCP选项，将网关设置为主路由，DNS设置为旁路由。当接入局域网的时候，所有参数会自动设置好，不再需要手工调整。同时openwrt提供了web界面，可以很方便的设置静态IP，查询DHCP分配的IP地址。更重要的是，此方案可以搭配任何主路由实现，只要主路由可以关闭DHCP服务。</p><p>主路由我通过树莓派3B刷入immortalwrt实现，而VMware的虚拟机网络则是直接安装了一个immortalwrt虚拟机，开机自启动。</p><p>这套方案非常完美。设置好之后维护成本很低，通过DHCP下发网卡参数，不再需要手工调整网卡参数。而且兼容性很好，无论是路由器网络还是虚拟机网络，都能很好适配。最后，openwrt是一个非常开放的系统，有很多软件包和插件可以折腾，扩展性拉满。</p><h2 id="方案5-1-smartdns"><a class="header-anchor" href="#方案5-1-smartdns">¶</a>方案5.1 smartdns</h2><p>dnsmasq向上游DNS查询时，总是返回最先查询到的结果，但不一定是访问最快的IP地址。所以就再引入了smartdns，对查询的结果做测速，返回访问最快的IP地址。smartdns专注于DNS查询缓存和测速，没有提供很完善的DNS记录设置，所以dnsmasq还是要保留。</p><p>smartdns监听53端口，向局域网提供DNS服务。dnsmasq监听5354端口，提供本地域名解析服务，作为smartdns的一个上游服务器。</p>]]>
    </content>
    <id>https://pkemb.com/2026/04/dns-server/</id>
    <link href="https://pkemb.com/2026/04/dns-server/"/>
    <published>2026-04-05T23:33:17.000Z</published>
    <summary>
      <![CDATA[<p>在搭建和完善HomeLab的过程中，有一个非常重要的服务就是DNS服务。本地DNS服务器负责解析本地域名，并转发上游DNS服务器的解析结果，是本地服务能正常访问的基石。本文介绍了自己搭建本地DNS服务器使用的几种方案，并简单对比了一下优缺点。</p>]]>
    </summary>
    <title>本地DNS服务器方案对比</title>
    <updated>2026-04-05T23:33:17.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <content>
      <![CDATA[<p>前段时间win10虚拟机右下角提示无网，OneDrive也无法登录。但是edge还能访问网页。经过一番探索，发现是DNS服务器<code>本地域名</code>设置异常导致的。借这个问题，讨论一下本地DNS服务器的本地域名如何设置。</p><span id="more"></span><h2 id="问题现象"><a class="header-anchor" href="#问题现象">¶</a>问题现象</h2><p>win10虚拟机右下角提示无网，OneDrive也无法登录。但是edge浏览器还能正常访问网页。同一个局域网的Windows主机没有这些问题。在有问题的系统，使用nslookup 查询 <a href="http://bing.com">bing.com</a>，发现返回的是<code>bing.com.inc</code>。看起来nslookup在查询的域名后面自动添加了本地域名<code>inc</code>。</p><p><img src="https://image.pkemb.com/2025/12/efe0c67d70e366e79979688f8fb9cb1d.png" alt=""></p><h2 id="问题分析"><a class="header-anchor" href="#问题分析">¶</a>问题分析</h2><p>询问AI，发现注册表<code>HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters</code>有如下设置。左边是有问题的系统，右边是没有问题的系统。可以看出，有问题的系统多了<code>DhcpDomain</code>和<code>DhcpNameServer</code>两个配置。删除这两个配置后，问题消失。但是系统重启后，这两个选项又自动回来了。</p><p><img src="https://image.pkemb.com/2025/12/9e0ea353abe1c1d29bdbd068b7e32967.png" alt=""></p><p>打开Process Monitor，在管理员cmd执行<code>ipconfig /release</code> 和 <code>ipconfig /renew</code>，强制触发DHCP，抓到是 svchost 设置的。如下图所示。</p><p><img src="https://image.pkemb.com/2025/12/118246346b0a6b486819994947f03ce9.png" alt=""></p><p>在NG的机器上抓取 dhcp 数据包，路由器确实返回了 <code>DomainName</code> 和 <code>DomainNameServer</code> 两个字段。在OK的机器上强制触发DHCP，也变成NG了。看来问题还是出在DHCP服务器。</p><p><img src="https://image.pkemb.com/2025/12/e1479e14bad9ef07708de8eda85251c5.png" alt=""></p><p>将dnsmasq的本地域名设置为空，然后强制触发DHCP，<a href="http://xn--bing-fy8fn79iuzevq0f.com">再次查询bing.com</a>，通过抓包，发现直接就是查询的 <a href="http://bing.com">bing.com</a>，而不是 bing.com.inc。如果将本地域名设置为pk.inc，查询 <a href="http://bing.com">bing.com</a>，那么将会先查询 bing.com.pk.inc，查询不到，转而查询 <a href="http://bing.com">bing.com</a>。本地域名设置为 abc，先查询 bing.com.adb，查询找不到，<a href="http://xn--bing-kb2ir05jblr9og.com">转而查询bing.com</a>。</p><h2 id="问题原因"><a class="header-anchor" href="#问题原因">¶</a>问题原因</h2><p>看来nslookup 在域名后面追加本地域名是默认的行为。经过一番搜索，发现inc 是全球顶级域名，<a href="https://en.wikipedia.org/wiki/.inc">https://en.wikipedia.org/wiki/.inc</a>。怪不得 bing.com.inc 能返回查询结果。在云服务器查询，也能得到类似的结果。全部的顶级域名可以在<a href="https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains">https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains</a> 或 <a href="https://tld-list.com/tlds-from-a-z">https://tld-list.com/tlds-from-a-z</a> 查询。</p><figure class="highlight plaintext"><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">ubuntu@sg-arm-1:~$ nslookup bing.com.inc</span><br><span class="line">Server:         127.0.0.53</span><br><span class="line">Address:        127.0.0.53#53</span><br><span class="line"></span><br><span class="line">Non-authoritative answer:</span><br><span class="line">bing.com.inc    canonical name = recruitmentjp.r-cms.jp.</span><br><span class="line">recruitmentjp.r-cms.jp  canonical name = lb.r-cms.jp.</span><br><span class="line">Name:   lb.r-cms.jp</span><br><span class="line">Address: 46.51.244.145</span><br></pre></td></tr></table></figure><h2 id="解决方法"><a class="header-anchor" href="#解决方法">¶</a>解决方法</h2><p>经过搜索，发现了帖子<a href="https://serverfault.com/questions/17255/top-level-domain-domain-suffix-for-private-network">https://serverfault.com/questions/17255/top-level-domain-domain-suffix-for-private-network</a>。<a href="https://www.rfc-editor.org/rfc/rfc6762">RFC 6762</a>推荐在私有DNS使用如下顶级域名。将本地域名改为<code>lan</code>，重启系统后解析恢复正常。</p><figure class="highlight plaintext"><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">.intranet.</span><br><span class="line">.internal.</span><br><span class="line">.private.</span><br><span class="line">.corp.</span><br><span class="line">.home.</span><br><span class="line">.lan.</span><br></pre></td></tr></table></figure><p>本地域名不推荐设置为<code>.local</code>，因为mDNS协议默认使用<code>.local</code>域名，会导致mDNS协议工作异常。</p>]]>
    </content>
    <id>https://pkemb.com/2025/12/local-domain-suffix/</id>
    <link href="https://pkemb.com/2025/12/local-domain-suffix/"/>
    <published>2025-12-20T23:22:53.000Z</published>
    <summary>
      <![CDATA[<p>前段时间win10虚拟机右下角提示无网，OneDrive也无法登录。但是edge还能访问网页。经过一番探索，发现是DNS服务器<code>本地域名</code>设置异常导致的。借这个问题，讨论一下本地DNS服务器的本地域名如何设置。</p>]]>
    </summary>
    <title>私有DNS服务顶级域名</title>
    <updated>2025-12-26T23:40:05.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <content>
      <![CDATA[<p>记录一下ImmortalWrt的安装方法，以及一些配置。</p><span id="more"></span><h2 id="镜像下载"><a class="header-anchor" href="#镜像下载">¶</a>镜像下载</h2><p>下载地址：<a href="https://firmware-selector.immortalwrt.org/">https://firmware-selector.immortalwrt.org/</a>。根据目标设备选择对应的镜像即可，</p><h2 id="安装"><a class="header-anchor" href="#安装">¶</a>安装</h2><h3 id="VMware虚拟机"><a class="header-anchor" href="#VMware虚拟机">¶</a>VMware虚拟机</h3><p>在镜像下载页面搜索<code>Generic x86/64</code>，选择ext4 vmdk镜像下载。当前最新版本<code>24.10.2</code>，查看package，确认kernel版本是6.6。</p><p>将下载好的<code>vmdk</code>文件复制到存放虚拟机的文件夹。VMware新建虚拟机，操作系统类型选择<code>其他Linux 6.x 内核 64 位</code>，命名为<code>p73wrt</code>，处理器和内存按需配置。网络选择NAT。磁盘界面选择复制进去的vmdk文件。虚拟机创建好之后直接启动即可。</p><p>系统启动之后，如果终端一直在吐log，可以用<code>echo 0 &gt; /proc/sys/kernel/printk</code>关闭log。</p><h3 id="树莓派3B"><a class="header-anchor" href="#树莓派3B">¶</a>树莓派3B</h3><p>镜像下载页面搜索<code>Raspberry Pi 3</code>，我选择的64位ext4版本。解压后用Win32DiskImage或其他类似工具写入TF卡，插入树莓派启动即可。</p><h2 id="基础设置"><a class="header-anchor" href="#基础设置">¶</a>基础设置</h2><h3 id="IP地址"><a class="header-anchor" href="#IP地址">¶</a>IP地址</h3><p>进入虚拟机终端，编辑文件<code>/etc/config/network</code>，给网卡lan设置IP地址，网段和路由器保持一致。添加网关和dns的配置。网关可以填路由器的地址。最后<code>/etc/init.d/network restart</code>重启网卡。可以ping路由器、<code>8.8.8.8</code>或<code>baidu.com</code>测试网络是否正常。</p><figure class="highlight plaintext"><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">lan</span><br><span class="line">...</span><br><span class="line">option gateway &#x27;ipaddr&#x27;</span><br><span class="line">list dns &#x27;dns server&#x27;</span><br><span class="line">option ipaddr &#x27;ip&#x27;</span><br></pre></td></tr></table></figure><p>网络配置好之后，在浏览器输入IP地址，即可进入网页端，默认没有密码。以上关于网络接口的配置，也可以访问管理网页，进入<code>网络-&gt;接口</code>，进入设置。</p><h3 id="root密码"><a class="header-anchor" href="#root密码">¶</a>root密码</h3><p>shell执行 <code>passwd root</code>，或网页web进入<code>系统-&gt;管理权</code>进行设置。</p><h3 id="软件包更新和安装"><a class="header-anchor" href="#软件包更新和安装">¶</a>软件包更新和安装</h3><p>immortalwrt 使用opkg管理软件包，所以可以使用opkg安装软件包。也可以访问网页端，进入<code>系统-&gt;软件包</code>，对软件包进行管理。</p><h3 id="rootfs扩容"><a class="header-anchor" href="#rootfs扩容">¶</a>rootfs扩容</h3><p>安装系统的时候，默认分配了1GB大小的磁盘，但是rootfs最大只有290MB。</p><figure class="highlight plaintext"><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">root@ImmortalWrt:~# df -h</span><br><span class="line">Filesystem                Size      Used Available Use% Mounted on</span><br><span class="line">/dev/root               290.4M     42.1M    242.3M  15% /</span><br><span class="line">tmpfs                   991.9M      1.1M    990.8M   0% /tmp</span><br><span class="line">/dev/vda1                31.9M      8.3M     23.6M  26% /boot</span><br><span class="line">/dev/vda1                31.9M      8.3M     23.6M  26% /boot</span><br><span class="line">tmpfs                   512.0K         0    512.0K   0% /dev</span><br></pre></td></tr></table></figure><p>安装相关工具的指令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">opkg install resize2fs parted losetup fdisk</span><br></pre></td></tr></table></figure><p>关闭虚拟机，进入虚拟机设置，把磁盘设置为10GB大小（如果是树莓派，忽略）。启动虚拟机，进入终端，执行<code>fdisk /dev/vda</code>命令，进入交互式界面，敲<code>p</code>查看分区表，确认rootfs分区编号，应该是2。然后输入<code>e-&gt;2</code>，不输入大小，直接回车，用默认的最大值。最后敲<code>w</code>保持退出。</p><figure class="highlight shell"><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">Command (m for help): e</span><br><span class="line">Partition number (1,2,128, default 128): 2</span><br><span class="line"></span><br><span class="line">New &lt;size&gt;&#123;K,M,G,T,P&#125; in bytes or &lt;size&gt;S in sectors (default 10G):</span><br><span class="line"></span><br><span class="line">Partition 2 has been resized.</span><br><span class="line"></span><br><span class="line">Command (m for help): w</span><br><span class="line">The partition table has been altered.</span><br><span class="line">Syncing disks.</span><br></pre></td></tr></table></figure><p>重启系统，待系统重启成功后，执行 <code>resize2fs /dev/vda2</code>，扩大文件系统。最终效果如下。</p><figure class="highlight shell"><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">root@ImmortalWrt:~# df -h</span><br><span class="line">Filesystem                Size      Used Available Use% Mounted on</span><br><span class="line">/dev/root                 9.8G     43.1M      9.8G   0% /</span><br></pre></td></tr></table></figure><blockquote><p>也可以参考 <a href="https://blog.csdn.net/2301_81011704/article/details/142636930">https://blog.csdn.net/2301_81011704/article/details/142636930</a> 扩容rootfs。</p></blockquote><p>如果 rootfs 扩容遇到如下错误。</p><figure class="highlight shell"><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">root@ImmortalWrt:~# resize2fs /dev/sda2</span><br><span class="line">resize2fs 1.47.0 (5-Feb-2023)</span><br><span class="line">Filesystem at /dev/sda2 is mounted on /; on-line resizing required</span><br><span class="line">old_desc_blocks = 1, new_desc_blocks = 1</span><br><span class="line">Performing an on-line resize of /dev/sda2 to 2613120 (4k) blocks.</span><br><span class="line">resize2fs: Invalid argument While trying to add group #3</span><br></pre></td></tr></table></figure><p>kernel log 如下。</p><figure class="highlight shell"><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">root@ImmortalWrt:~# dmesg | grep sda2</span><br><span class="line">[   31.879942] EXT4-fs (sda2): resizing filesystem from 98304 to 2613120 blocks</span><br><span class="line">[   31.880641] EXT4-fs warning (device sda2): reserve_backup_gdb:1052: reserved block 24 not at offset 23</span><br><span class="line">[   31.880945] EXT4-fs warning (device sda2): ext4_resize_fs:2194: error (-22) occurred during file system resize</span><br><span class="line">[   31.881274] EXT4-fs (sda2): resized filesystem to 98304</span><br><span class="line">[   31.883174] EXT4-fs warning (device sda2): reserve_backup_gdb:1052: reserved block 24 not at offset 23</span><br></pre></td></tr></table></figure><p>参考<a href="https://github.com/openwrt/openwrt/issues/7729">https://github.com/openwrt/openwrt/issues/7729</a>，拿到如下修复命令。成功修复。如果是虚拟机，分区名字要换成 <code>/dev/sda2</code>。</p><figure class="highlight shell"><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">mount -o remount,ro / #Remount root as ReadOnly</span><br><span class="line">tune2fs -O^resize_inode /dev/mmcblk0p2 #Remove reserved GDT blocks</span><br><span class="line">fsck.ext4 /dev/mmcblk0p2 #Fix part, answer yes to remove GDT blocks remnants。所有全部选y</span><br><span class="line">reboot</span><br><span class="line"></span><br><span class="line">resize2fs /dev/mmcblk0p2</span><br></pre></td></tr></table></figure><h2 id="webserver-切换到nginx"><a class="header-anchor" href="#webserver-切换到nginx">¶</a>webserver 切换到nginx</h2><p>首先停止uhttpd，防止端口冲突。然后安装nginx。</p><figure class="highlight plaintext"><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">service uhttpd stop &amp;&amp; service uhttpd disable</span><br><span class="line">opkg update &amp;&amp; opkg install nginx-full nginx-mod-luci</span><br></pre></td></tr></table></figure><p>nginx的主配置文件是<code>/etc/nginx/uci.conf</code>。这个文件是根据<code>/etc/config/nginx</code>里面的配置生成来的。修改此文件，参考如下。主要修改了第一个server的名字，server_name，证书的文件名，以及包含文件<code>p73wrt.inc.conf</code>。</p><figure class="highlight nginx"><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="attribute">config</span> main global</span><br><span class="line">        option uci_enable <span class="string">&#x27;true&#x27;</span></span><br><span class="line"></span><br><span class="line">config server <span class="string">&#x27;_main&#x27;</span></span><br><span class="line">        list listen <span class="string">&#x27;443 ssl default_server&#x27;</span></span><br><span class="line">        list listen <span class="string">&#x27;[::]:443 ssl default_server&#x27;</span></span><br><span class="line">        option server_name <span class="string">&#x27;p73wrt.inc&#x27;</span></span><br><span class="line">        list include <span class="string">&#x27;restrict_locally&#x27;</span></span><br><span class="line">        list include <span class="string">&#x27;conf.d/*.locations&#x27;</span></span><br><span class="line">        list include <span class="string">&#x27;p73wrt.inc.conf&#x27;</span></span><br><span class="line">        <span class="comment"># option uci_manage_ssl &#x27;self-signed&#x27;</span></span><br><span class="line">        option ssl_certificate <span class="string">&#x27;/etc/nginx/conf.d/p73wrt.inc.crt&#x27;</span></span><br><span class="line">        option ssl_certificate_key <span class="string">&#x27;/etc/nginx/conf.d/p73wrt.inc.key&#x27;</span></span><br><span class="line">        option ssl_session_cache <span class="string">&#x27;shared:SSL:32k&#x27;</span></span><br><span class="line">        option ssl_session_timeout <span class="string">&#x27;64m&#x27;</span></span><br><span class="line">        option access_log <span class="string">&#x27;off; # logd openwrt&#x27;</span></span><br><span class="line"></span><br><span class="line">config server <span class="string">&#x27;_redirect2ssl&#x27;</span></span><br><span class="line">        list listen <span class="string">&#x27;80&#x27;</span></span><br><span class="line">        list listen <span class="string">&#x27;[::]:80&#x27;</span></span><br><span class="line">        option server_name <span class="string">&#x27;_redirect2ssl&#x27;</span></span><br><span class="line">        option return <span class="string">&#x27;302 https://<span class="variable">$host</span><span class="variable">$request_uri</span>&#x27;</span></span><br></pre></td></tr></table></figure><p>修改完成之后，使用如下命令重启nginx。</p><figure class="highlight shell"><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">touch /etc/nginx/p73wrt.inc.conf</span><br><span class="line">uci commit nginx</span><br><span class="line">service nginx restart</span><br></pre></td></tr></table></figure><p>在<code>p73wrt.inc.conf</code>添加如下内容，禁止IP直接访问。</p><figure class="highlight nginx"><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="attribute">if</span> (<span class="variable">$host</span> != <span class="string">&#x27;p73wrt.inc&#x27;</span>) &#123;</span><br><span class="line"><span class="attribute">return</span> <span class="number">403</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>参考：</p><ol><li>安装nginx <a href="https://segmentfault.com/a/1190000044574943">https://segmentfault.com/a/1190000044574943</a></li><li>禁止IP访问：<a href="https://zhuanlan.zhihu.com/p/372689544">https://zhuanlan.zhihu.com/p/372689544</a></li></ol><h2 id="DHCP"><a class="header-anchor" href="#DHCP">¶</a>DHCP</h2><p>把主路由的静态IP设置，同步到ImmortalWrt。关闭主路由的DHCP服务，使用软路由分配IP地址。在<code>网络-&gt;DHCP/DNS-&gt;静态地址分配</code>，可以为某台设备设置静态IP地址和标签。注意，如果只设置标签而没有设置固定IP地址，则标签设置不会生效。</p><p>dhcp 选项3用于指定网关。关闭主路由的dhcp后，软路由dhcp默认指定的网关是软路由地址，这样局域网内所有的设备都能科学上网了。但是不想所有设备都走代理。关键配置在于设备的网关地址。</p><p>经过搜索资料，有两种方式可以做到。黑名单，即默认网关指向软路由（默认走代理），如果某个设备不想走代理，通过设置把网关指向主路由。白名单，即默认网关指向主路由（默认不走代理），如果想让某个设备走代理，通过设置把这台设备的网关指向软路由。这里选择白名单模式。</p><p><code>网络-&gt;接口-&gt;点击lan编辑-&gt;DHCP服务器-&gt;高级设置</code>，增加三个DHCP选项，<code>3,192.168.100.1</code>、<code>6,192.168.100.25</code>和<code>tag:outsea,3,192.168.100.55</code>，保存并应用。</p><p>DHCP的地址范围在<code>网络-&gt;接口-&gt;点击lan编辑-&gt;DHCP服务器-&gt;常规设置</code>下面，默认是从100开始分配地址。</p><p>参考：</p><ul><li>DHCP协议，<a href="https://zhuanlan.zhihu.com/p/17391337608">https://zhuanlan.zhihu.com/p/17391337608</a></li><li>openwrt旁路由dhcp指定主路由网关方法，<a href="https://blog.51cto.com/fxn2025/9836451">https://blog.51cto.com/fxn2025/9836451</a></li><li>设置 OpenWrt 指定主机DHCP 获取不同网关DNS，<a href="https://www.right.com.cn/forum/thread-8225205-1-1.html">https://www.right.com.cn/forum/thread-8225205-1-1.html</a></li></ul><h2 id="DNS"><a class="header-anchor" href="#DNS">¶</a>DNS</h2><p>切换lan到inc，<code>网络-&gt;DHCP/DNS-&gt;常规</code>，将<code>本地解析这些项目</code>改成<code>/inc/</code>，<code>本地域名</code>改成<code>inc</code>。这样会自动增加一个<code>&lt;hostname&gt;.inc</code>的域名。</p><p><code>选项</code>卡下面的<code>所有服务器</code>，即打开<code>all-servers</code>选项，同时查询所有的上游DNS服务器。询问AI，会导致CPU使用率增加、带宽使用增加、减低缓存命中率。打开之后会提高查询速度，避免DNS劫持。综合考虑，性能优先，打开这个选项。</p><p><code>DNS记录</code>选项卡下面可以添加DNS记录，例如A记录、CNAME记录等。图形化界面。但是历史DNS记录比较多，图形化界面不利于重新部署，还是考虑自动化部署。首先将<code>/etc/dnsmasq.d</code>包含到dnsmasq的配置文件中，参考如下命令。最后修改相关自动化脚本，将DNS记录配置文件部署到/etc/dnsmasq.d 下面。。</p><figure class="highlight shell"><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">mkdir -p /etc/dnsmasq.d</span><br><span class="line">uci set dhcp.@dnsmasq[0].confdir=&quot;/etc/dnsmasq.d&quot;</span><br><span class="line">uci commit dhcp</span><br><span class="line">service dnsmasq restart</span><br></pre></td></tr></table></figure><p><code>转发</code>选项卡下面的<code>DNS转发</code>可以设置上游DNS服务器地址。可以设置为阿里的公共DNS服务器，<code>223.5.5.5</code>和<code>223.6.6.6</code>。注意，如果安装了openclash插件，clash默认会打开DNS劫持，<code>DNS转发</code>的设置总是会被自动修改成<code>127.0.0.1#7874</code>。建议在<code>服务-&gt;OpenClash-&gt;插件设置-&gt;DNS设置</code>关闭DNS劫持。避免部分网页无法打开。为了访问速度更快，建议使用运营商的dns。主路由拨号时会自动获取运营商的dns，所以这里填主路由IP即可。</p><p><code>Resolv和Hosts文件</code>选项卡下面的<code>解析文件</code>，默认也配置了两个上游DNS服务器。默认是文件<code>/tmp/resolv.conf.d/resolv.conf.auto</code>。参考<a href="https://www.openwrt.pro/post-141.html">https://www.openwrt.pro/post-141.html</a>，看起来是netifd自动生成的。</p><p>openclash也有设置上游DNS服务器的地方，在<code>服务-&gt;OpenClash-&gt;复写设置-&gt;DNS设置</code>，找到<code>设置自定义上游 DNS 服务器（在上方设置中启用本功能后生效）</code>，有<code>NameServer</code>和<code>Default-NameServer</code>两个选项卡可以配置。这里默认不生效。</p><p><code>Resolv和Hosts文件</code>选项卡下面的<code>忽略解析文件</code>，表示打开选项<code>noresolv</code>，忽略<code>/etc/resolv.conf</code>文件。</p><h2 id="打开dnsmasq的log"><a class="header-anchor" href="#打开dnsmasq的log">¶</a>打开dnsmasq的log</h2><p>修改dhcp和dns的时候，可能遇到问题，这时就需要打开dnsmasq的log进行debug。首先编辑<code>/etc/dnsmasq.conf</code>文件，注释<code>log-facility=/dev/null</code>。然后<code>service dnsmasq restart</code>重启服务。</p><p>进入web，进入<code>网络-&gt;DHCP/DNS-&gt;日志</code>，勾选记录查询日志，记录设施选自定义，填<code>/tmp/dnsmasq.log</code>，保存并应用。这时就能在<code>/tmp/dnsmasq.log</code>文件看到相关日志。</p><p>调整完成之后，记得关闭log，提高性能。</p><blockquote><p>如果在web界面打开log后，dnsmasq启动失败。使用命令<code>logread | grep dnsmasq</code>查询启动日志，确认启动失败的原因。</p></blockquote>]]>
    </content>
    <id>https://pkemb.com/2025/11/ImmortalWrt/</id>
    <link href="https://pkemb.com/2025/11/ImmortalWrt/"/>
    <published>2025-11-04T22:11:27.000Z</published>
    <summary>
      <![CDATA[<p>记录一下ImmortalWrt的安装方法，以及一些配置。</p>]]>
    </summary>
    <title>ImmortalWrt软路由安装及配置</title>
    <updated>2025-11-04T22:43:05.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="shell" scheme="https://pkemb.com/tags/shell/"/>
    <content>
      <![CDATA[<p>编写bash补全函数的关键就是根据上下文生成合适的<code>COMPREPLY</code>数组。这里记录了bash补全的相关资料，以及常用的代码片段。</p><span id="more"></span><p>关于补全，建议先通读一遍<a href="https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion">Programmable Completion</a>，可以了解当按下<code>&lt;tab&gt;</code>按键时，bash是如何处理的。这里专注于补全函数，相关描述如下。</p><blockquote><p>After these matches have been generated, any shell function or command specified with the -F and -C options is invoked. When the command or function is invoked, the COMP_LINE, COMP_POINT, COMP_KEY, and COMP_TYPE variables are assigned values as described above (see Bash Variables). If a shell function is being invoked, the COMP_WORDS and COMP_CWORD variables are also set. When the function or command is invoked, the first argument ($1) is the name of the command whose arguments are being completed, the second argument ($2) is the word being completed, and the third argument ($3) is the word preceding the word being completed on the current command line. No filtering of the generated completions against the word being completed is performed; the function or command has complete freedom in generating the matches.</p><p>Any function specified with -F is invoked first. The function may use any of the shell facilities, including the compgen and compopt builtins described below (see Programmable Completion Builtins), to generate the matches. It must put the possible completions in the COMPREPLY array variable, one per array element.</p></blockquote><h2 id="注册补全函数"><a class="header-anchor" href="#注册补全函数">¶</a>注册补全函数</h2><p>使用bash内置命令<code>complete</code>可以为指定shell命令注册补全函数。如下代码表示shell命令<code>foo</code>的补全函数是<code>_foo</code>。在shell命令中，依次敲入<code>foo</code>、<code>空格</code>、<code>&lt;tab&gt;</code>时，补全函数<code>_foo</code>将在当前shell运行。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">complete -F _foo foo</span><br></pre></td></tr></table></figure><p>可以使用命令<code>complete -p func</code>来查询指定命令的补全函数。例如<code>cd</code>命令的补全函数是<code>_cd</code>。</p><figure class="highlight shell"><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">pk@pkdev22:~$ complete -p cd</span><br><span class="line">complete -o nospace -F _cd cd</span><br></pre></td></tr></table></figure><h2 id="上下文"><a class="header-anchor" href="#上下文">¶</a>上下文</h2><p>阅读<a href="https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion">Programmable Completion</a>可以得知，当调用补全函数时，bash会准备好<code>COMP_LINE</code>、<code>COMP_POINT</code>、<code>COMP_KEY</code>、<code>COMP_TYPE</code>、<code>COMP_WORDS</code>和<code>COMP_CWORD</code>变量。bash手册关于这些变量的描述如下。</p><table><thead><tr><th>变量</th><th>说明</th></tr></thead><tbody><tr><td>COMP_LINE</td><td>The current command line. This variable is available only in shell functions and external commands invoked by the programmable completion facilities (see <a href="https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion">Programmable Completion</a>).</td></tr><tr><td>COMP_POINT</td><td>The index of the current cursor position relative to the beginning of the current command. If the current cursor position is at the end of the current command, the value of this variable is equal to <code>${#COMP_LINE}</code>. This variable is available only in shell functions and external commands invoked by the programmable completion facilities (see <a href="https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion">Programmable Completion</a>).</td></tr><tr><td>COMP_KEY</td><td>The key (or final key of a key sequence) used to invoke the current completion function.</td></tr><tr><td>COMP_TYPE</td><td>Set to an integer value corresponding to the type of completion attempted that caused a completion function to be called: TAB, for normal completion, ‘?’, for listing completions after successive tabs, ‘!’, for listing alternatives on partial word completion, ‘@’, to list completions if the word is not unmodified, or ‘%’, for menu completion. This variable is available only in shell functions and external commands invoked by the programmable completion facilities (see <a href="https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion">Programmable Completion</a>).</td></tr><tr><td>COMP_WORDS</td><td>An array variable consisting of the individual words in the current command line. The line is split into words as Readline would split it, using COMP_WORDBREAKS as described above. This variable is available only in shell functions invoked by the programmable completion facilities (see <a href="https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion">Programmable Completion</a>).</td></tr><tr><td>COMP_CWORD</td><td>An index into <code>${COMP_WORDS}</code> of the word containing the current cursor position. This variable is available only in shell functions invoked by the programmable completion facilities (see <a href="https://www.gnu.org/software/bash/manual/bash.html#Programmable-Completion">Programmable Completion</a>).</td></tr></tbody></table><p>直接看描述，有点迷迷糊糊，上例子。<code>COMP_LINE</code>是一个字符串，表示整个命令行，如果末尾有空格，则包含空格。<code>COMP_POINT</code>是<code>COMP_LINE</code>的长度。<code>COMP_KEY</code>不知道是干啥的。<code>COMP_TYPE</code>在第一次敲<code>&lt;tab&gt;</code>时，是9，即<code>&lt;tab&gt;</code>的ASCII值，第二次敲<code>&lt;tab&gt;</code>时，是63，即<code>?</code>的ASCII值。<code>COMP_WORDS</code>和<code>COMP_LINE</code>很类似，但<code>COMP_WORDS</code>是一个数组。<code>COMP_CWORD</code>是数组<code>COMP_WORDS</code>的下标，</p><table><thead><tr><th>变量</th><th><code>foo a b&lt;tab&gt;</code></th><th><code>foo a b&lt;space&gt;&lt;tab&gt;</code></th></tr></thead><tbody><tr><td>COMP_LINE</td><td><code>foo a b</code></td><td><code>foo a b&lt;space&gt;</code></td></tr><tr><td>COMP_POINT</td><td>7</td><td>8</td></tr><tr><td>COMP_KEY</td><td>9</td><td>9</td></tr><tr><td>COMP_TYPE</td><td>9 或 63</td><td>9 或 63</td></tr><tr><td>COMP_WORDS</td><td><code>foo</code> <code>a</code> <code>b</code></td><td><code>foo</code> <code>a</code> <code>b</code> <code>&lt;space&gt;</code></td></tr><tr><td>COMP_CWORD</td><td>2</td><td>3</td></tr></tbody></table><p>根据bash提供的这些变量，可以推算出两个关键的信息，<code>prev</code>和<code>cur</code>。<code>prev</code>表示上一个单词，<code>cur</code>表示当前的单词。绝大多数情况下，可以根据这两个信息计算出补全列表。</p><figure class="highlight shell"><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">_foo() &#123;</span><br><span class="line">    local prev=&quot;$&#123;COMP_WORDS[COMP_CWORD-1]&#125;&quot;</span><br><span class="line">    local cur=&quot;$&#123;COMP_WORDS[COMP_CWORD]&#125;&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>bash提供了辅助函数<code>_init_completion</code>用来初始化变量<code>prev</code>、<code>cur</code>、<code>words</code>、<code>cword</code>。其中<code>words</code>等同于<code>COMP_WORDS</code>，<code>cword</code>等同于<code>COMP_CWORD</code>。</p><figure class="highlight shell"><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">_foo() &#123;</span><br><span class="line">    _init_completion || return</span><br><span class="line">    # 接下来的代码可以使用 prev / cur / words / cword 变量。</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="返回补全结果"><a class="header-anchor" href="#返回补全结果">¶</a>返回补全结果</h2><p>补全函数需要填充<code>COMPREPLY</code>数组，向bash返回补全列表。根据日常使用补全的经验，有两种情况。第一种情况，当前的选项输入到一半了。第二种情况，上一个选项输入完成了，开始输入下一个选项。</p><p>对于第一种情况，只需要根据当前的输入<code>${cur}</code>做一个筛选，将匹配的结果填充到<code>COMPREPLY</code>数组即可。例如<code>foo</code>命令有三个选项<code>--foo1</code>、<code>--foo2</code>、<code>--bar</code>。当输入<code>foo --foo&lt;tab&gt;</code>时，期望bash会提示<code>--foo1</code>和<code>--foo2</code>。bash的内置命令<code>compgen</code>可以帮助做筛选。补全函数的参考实现如下。</p><figure class="highlight shell"><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">_foo() &#123;</span><br><span class="line">    _init_completion || return</span><br><span class="line">    COMPREPLY=( $(compgen -W &#x27;--foo1 --foo2 --bar&#x27; -- $&#123;cur&#125;) )</span><br><span class="line">    return 0</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>第二种情况比较复杂，需要根据之前的输入，向<code>COMPREPLY</code>数组填充不同的值。例如<code>foo</code>命令的所有选项只允许出现一次，当输入<code>foo --foo1 &lt;tab&gt;</code>时，期望只会提示<code>--foo2</code>和<code>--bar</code>。参考实现如下。首先遍历选项列表，如果不在<code>${words[*]</code>，则追加到变量<code>comp_opts</code>。最后根据<code>${cur}</code>做筛选。</p><figure class="highlight shell"><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">_foo() &#123;</span><br><span class="line">    _init_completion || return</span><br><span class="line">    COMPREPLY=()</span><br><span class="line"></span><br><span class="line">    local opts=(&quot;--foo1&quot; &quot;--foo2&quot; &quot;--bar&quot;)</span><br><span class="line">    local comp_opts</span><br><span class="line">    for (( i=0; i &lt; $&#123;#opts[@]&#125;; i++)); do</span><br><span class="line">        # 遍历所有选项，如果不在$&#123;words[*]&#125;，则追加到comp_opts。</span><br><span class="line">        if [[ &quot;$&#123;words[*]&#125;&quot; != *&quot; $&#123;opts[i]&#125; &quot;* ]]; then</span><br><span class="line">            comp_opts+=&quot;$&#123;opts[i]&#125; &quot;</span><br><span class="line">        fi</span><br><span class="line">    done</span><br><span class="line">    COMPREPLY=( $(compgen -W &quot;$&#123;comp_opts&#125;&quot; -- $&#123;cur&#125;) )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>bash比较子字符串时，子字符串必须放在后面。</p></blockquote><p>实际情况往往更加的复杂，比如很多shell命令还有子命令，这些子命令有不同的选项。但思路都是类似的，根据上下文生成合适的<code>COMPREPLY</code>数组。</p><h2 id="compgen"><a class="header-anchor" href="#compgen">¶</a>compgen</h2><p>除了用<code>-W</code>指定补全的数据外，<code>compgen</code>还提供了生成常用数据的选项。比如<code>compgen -u</code>或<code>compgen -A user</code>会输出系统的所有用户，编写<code>chmod</code>的补全函数就会用到。</p><table><thead><tr><th>-A <em>action</em></th><th>短选项</th><th>说明</th></tr></thead><tbody><tr><td>-A alias</td><td>-a</td><td>别名</td></tr><tr><td>-A arrayvar</td><td></td><td>数组变量名</td></tr><tr><td>-A binding</td><td></td><td></td></tr><tr><td>-A builtin</td><td>-b</td><td>shell内置命令</td></tr><tr><td>-A command</td><td>-c</td><td>命令</td></tr><tr><td>-A directory [dir]</td><td>-d</td><td>当前路径或指定路径的子目录</td></tr><tr><td>-A disabled</td><td></td><td>禁用的shell内置命令</td></tr><tr><td>-A enabled</td><td></td><td>使能的shell内置命令</td></tr><tr><td>-A export</td><td>-e</td><td>导出的shell变量</td></tr><tr><td>-A file [dir/]</td><td>-f</td><td>当前路径或指定路径的文件。如果加了可选参数，一定用<code>/</code>结尾</td></tr><tr><td>-A function</td><td></td><td>shell函数</td></tr><tr><td>-A group</td><td>-g</td><td>组名</td></tr><tr><td>-A helptopic</td><td></td><td>内置命令<code>help</code>接受的主题</td></tr><tr><td>-A hostname</td><td></td><td>主机名</td></tr><tr><td>-A job</td><td>-j</td><td>任务名</td></tr><tr><td>-A keyword</td><td>-k</td><td>shell关键字</td></tr><tr><td>-A running</td><td></td><td>正在运行的任务名</td></tr><tr><td>-A service</td><td>-s</td><td>服务名</td></tr><tr><td>-A setopt</td><td></td><td><code>set</code>内置命令<code>-o</code>选项的有效参数</td></tr><tr><td>-A shopt</td><td></td><td><code>shopt</code>内置命令接受的选项名</td></tr><tr><td>-A signal</td><td></td><td>信号名</td></tr><tr><td>-A stopped</td><td></td><td>停止的任务</td></tr><tr><td>-A user</td><td>-u</td><td>用户名</td></tr><tr><td>-A variable</td><td>-v</td><td>变量名</td></tr></tbody></table><h2 id="调试技巧"><a class="header-anchor" href="#调试技巧">¶</a>调试技巧</h2><p>在调试补全函数的过程中，经常需要打印某些变量的值。如果直接使用<code>echo</code>输出，会和补全提示混在一起，非常不好看。建议重定向到某一个文件。</p><h2 id="示例"><a class="header-anchor" href="#示例">¶</a>示例</h2><h3 id="bash-completion"><a class="header-anchor" href="#bash-completion">¶</a>bash-completion</h3><p>软件包<code>bash-completion</code>实现了非常多命令的补全，我们可以阅读这些脚本，学习是如何实现的。在Ubuntu系统，这些脚本在<code>/usr/share/bash-completion/</code>目录下面。</p><h3 id="cdwork"><a class="header-anchor" href="#cdwork">¶</a>cdwork</h3><p>假如有一个工作目录，期望在任意目录敲<code>cdwork</code>命令，则跳转到工作目录。如果执行<code>cdwork subdir1</code>，则跳转到工作目录下的<code>subdir1</code>子目录。支持<code>任意一级子目录</code>补全，即输入<code>cdwork &lt;tab&gt;</code>时，补全工作目录下的所有子目录。以下实现是参考<code>_cd</code>函数改的。</p><figure class="highlight shell"><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">export WORKDIR=&quot;/path/to/work/dir&quot;</span><br><span class="line">function cdwork() &#123;</span><br><span class="line">    cd &quot;$&#123;WORKDIR&#125;/$1&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">_cdwork()</span><br><span class="line">&#123;</span><br><span class="line">    local cur prev words cword;</span><br><span class="line">    _init_completion || return;</span><br><span class="line">    local IFS=&#x27;</span><br><span class="line">&#x27; i j k;</span><br><span class="line">    compopt -o filenames;</span><br><span class="line">    local -r mark_dirs=$(_rl_enabled mark-directories &amp;&amp; echo y);</span><br><span class="line">    local -r mark_symdirs=$(_rl_enabled mark-symlinked-directories &amp;&amp; echo y);</span><br><span class="line"></span><br><span class="line">    k=&quot;$&#123;#COMPREPLY[@]&#125;&quot;;</span><br><span class="line">    for j in $(compgen -d -- $WORKDIR/$cur);</span><br><span class="line">    do</span><br><span class="line">        if [[ ( -n $mark_symdirs &amp;&amp; -L $j || -n $mark_dirs &amp;&amp; ! -L $j ) &amp;&amp; ! -d $&#123;j#$WORKDIR/&#125; ]]; then</span><br><span class="line">            j+=&quot;/&quot;;</span><br><span class="line">        fi;</span><br><span class="line">        COMPREPLY[k++]=$&#123;j#$WORKDIR/&#125;;</span><br><span class="line">    done;</span><br><span class="line"></span><br><span class="line">    _filedir -d;</span><br><span class="line">    if (($&#123;#COMPREPLY[@]&#125; == 1)); then</span><br><span class="line">        i=$&#123;COMPREPLY[0]&#125;;</span><br><span class="line">        if [[ $i == &quot;$cur&quot; &amp;&amp; $i != &quot;*/&quot; ]]; then</span><br><span class="line">            COMPREPLY[0]=&quot;$&#123;i&#125;/&quot;;</span><br><span class="line">        fi;</span><br><span class="line">    fi;</span><br><span class="line">    return</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">complete -o nospace -F _cdwork cdwork</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://pkemb.com/2024/11/bash-completion/</id>
    <link href="https://pkemb.com/2024/11/bash-completion/"/>
    <published>2024-11-30T22:25:25.000Z</published>
    <summary>
      <![CDATA[<p>编写bash补全函数的关键就是根据上下文生成合适的<code>COMPREPLY</code>数组。这里记录了bash补全的相关资料，以及常用的代码片段。</p>]]>
    </summary>
    <title>bash补全函数</title>
    <updated>2024-12-01T21:06:34.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="dx4600" scheme="https://pkemb.com/tags/dx4600/"/>
    <content>
      <![CDATA[<p>绿联NAS DX4600安装虚拟机，非官方教程。基于docker，不会修改系统任何数据。本文在<a href="https://www.bilibili.com/video/BV1iB421k7Xe/">BV1iB421k7Xe</a>的基础上，对网络、配置文件持久化、反向代理等方面进行了完善。</p><span id="more"></span><h2 id="环境准备"><a class="header-anchor" href="#环境准备">¶</a>环境准备</h2><ol><li>SSH登录DX4600，<a href="https://pkemb.com/2024/03/dx4600-enable-ssh/">DX4600开启ssh</a>。</li><li>安装 docker-compose</li><li>切换到桥接模式<br><img src="http://image.pkemb.com/image/202403202120004.png" style="zoom:67%;" /></li><li>在某个地方创建2个目录，存放配置文件和ISO镜像。<code>webvirtcloud/config</code>和<code>webvirtcloud/iso</code>。</li><li>复制操作系统镜像文件（例如Ubuntu、Windows等）到<code>webvirtcloud/iso</code>。</li></ol><h2 id="备份配置文件"><a class="header-anchor" href="#备份配置文件">¶</a>备份配置文件</h2><p>下载镜像<code>linkease/webvirtcloud:0.2</code>。创建一个不启动的容器，从里面复制配置文件。需要备份<code>/etc/libvirt</code>和<code>/etc/nginx/conf.d/webvirtcloud.conf</code>。</p><figure class="highlight shell"><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">docker pull linkease/webvirtcloud:0.2</span><br><span class="line">docker container create --name temp_webvirt linkease/webvirtcloud:0.2</span><br><span class="line">docker container cp temp_webvirt:/etc/libvirt  M2_2/docker/webvirtcloud/etc</span><br><span class="line">docker container cp temp_webvirt:/etc/nginx/conf.d/webvirtcloud.conf M2_2/docker/webvirtcloud</span><br><span class="line">docker container rm -f temp_webvirt</span><br></pre></td></tr></table></figure><p>网络的配置放在 <code>/etc/libvirt/qemu/networks</code>，实例的配置放在 <code>/etc/libvirt/qemu</code>，存储池的配置放在 <code>/etc/libvirt/storage</code>。</p><p>修改 <code>webvirtcloud.conf</code>，将监听端口从<code>80</code>改为<code>6009</code>。因为只想通过域名访问，所以只监听一个IP地址，而不是所有IP。IP地址<code>192.168.0.1</code>是Docker NAT IP，而不是路由器的IP，<code>nginx</code>容器在这个网段。</p><figure class="highlight nginx"><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="section">server</span> &#123;</span><br><span class="line">    <span class="attribute">listen</span> <span class="number">192.168.0.1:6009</span>;</span><br><span class="line">....</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="配置反向代理"><a class="header-anchor" href="#配置反向代理">¶</a>配置反向代理</h2><p>首先配置好域名解析。这里使用的是<code>Nginx Proxy Manager</code>。填写<code>webvirtcloud</code>监听的IP地址端口，设置好SSL。</p><img src="http://image.pkemb.com/image/202403202125071.png" style="zoom:67%;" /><p>在<code>Advanced</code>选项卡下面配置好如下指令。主要是对这三个路径配置websocket反向代理。</p><figure class="highlight nginx"><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="attribute">client_max_body_size</span> <span class="number">1024M</span>;</span><br><span class="line"><span class="section">location</span> /novncd  &#123;</span><br><span class="line">        <span class="attribute">include</span> conf.d/include/proxy.conf;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> Upgrade <span class="variable">$http_upgrade</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> Connection <span class="string">&quot;upgrade&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="section">location</span> /socket.io/  &#123;</span><br><span class="line">        <span class="attribute">include</span> conf.d/include/proxy.conf;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> Upgrade <span class="variable">$http_upgrade</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> Connection <span class="string">&quot;upgrade&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="section">location</span> /websockify  &#123;</span><br><span class="line">        <span class="attribute">include</span> conf.d/include/proxy.conf;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> Upgrade <span class="variable">$http_upgrade</span>;</span><br><span class="line">        <span class="attribute">proxy_set_header</span> Connection <span class="string">&quot;upgrade&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="启动容器"><a class="header-anchor" href="#启动容器">¶</a>启动容器</h2><p>使用<code>docker compose</code>启动容器。</p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">docker</span> compose -f M2_2/docker-compose/dx4600.inc/webvirtcloud/webvirtcloud.yml up &amp;</span><br></pre></td></tr></table></figure><p>compose文件如下。关键点，<code>privileged: true</code>和<code>network_mode: host</code>。</p><figure class="highlight plaintext"><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">version: &quot;3.9&quot;</span><br><span class="line">services:</span><br><span class="line">    webvirtcloud:</span><br><span class="line">        image: &#x27;linkease/webvirtcloud:0.2&#x27;</span><br><span class="line">        container_name: webvirtcloud</span><br><span class="line">        privileged: true</span><br><span class="line">        network_mode: host</span><br><span class="line">        volumes:</span><br><span class="line">            - &#x27;/home/pkemb/M2_2/docker/webvirtcloud/config:/srv/webvirtcloud/dbconfig&#x27;</span><br><span class="line">            - &#x27;/home/pkemb/M2_2/docker/webvirtcloud/iso:/var/lib/libvirt/iso&#x27;</span><br><span class="line">            - &#x27;/home/pkemb/M2_2/docker/webvirtcloud/etc:/etc/libvirt&#x27;</span><br><span class="line">            - &#x27;/home/pkemb/M2_2/docker/webvirtcloud/webvirtcloud.conf:/etc/nginx/conf.d/webvirtcloud.conf:ro&#x27;</span><br><span class="line">            - &#x27;/sys/fs/cgroup:/sys/fs/cgroup&#x27;</span><br><span class="line">        tmpfs:</span><br><span class="line">            - /run/lock</span><br><span class="line">            - /run</span><br><span class="line">            - /tmp</span><br><span class="line">        cgroup: host</span><br><span class="line">        tty: true</span><br><span class="line">        stdin_open: true</span><br><span class="line">        restart: always</span><br></pre></td></tr></table></figure><h2 id="初始化服务"><a class="header-anchor" href="#初始化服务">¶</a>初始化服务</h2><p>第一次启动容器需要初始化服务。<code>docker exec</code>进入容器内部，执行如下命令启动服务，并向<code>local_settings.py</code>添加<code>DEBUG=True</code>。域名替换成实际解析的域名。这个步骤只需要做一次。<code>cloud.dx4600.inc</code>是反向代理的域名，<code>443</code>是https端口。如果不想要https，可以设置为80。修改<code>local_settings.py</code>中域名的配置，<code>http</code>替换为<code>https</code>，删除<code>:443</code>。</p><figure class="highlight shell"><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">docker exec -it webvirtcloud /bin/bash</span><br><span class="line">/srv/startup.sh cloud.dx4600.inc 443</span><br><span class="line">sed -i -e &#x27;s/http:/https:/&#x27; -e &#x27;s/:443//&#x27; /srv/webvirtcloud/dbconfig/local_settings.py</span><br><span class="line">echo &quot;DEBUG=True&quot; &gt;&gt; /srv/webvirtcloud/dbconfig/local_settings.py</span><br></pre></td></tr></table></figure><p>如果<code>local_settings.py</code>中，<code>CSRF_TRUSTED_ORIGINS</code>配置的域名与实际访问的域名不一样，会出现如下错误。</p><img src="http://image.pkemb.com/image/202403202127006.png" style="zoom:67%;" /><p>退出容器shell，重启容器。因为网络模式是<code>host</code>，容器会在主机创建<code>virbr0</code>虚拟网卡。启动容器之前，需要先删除网卡。</p><figure class="highlight nginx"><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="attribute">docker</span> compose -f M2_2/docker-compose/dx4600.inc/webvirtcloud/webvirtcloud.yml down</span><br><span class="line">ifconfig virbr0 down</span><br><span class="line">ip link delete virbr0</span><br><span class="line">docker compose -f M2_2/docker-compose/dx4600.inc/webvirtcloud/webvirtcloud.yml up -d</span><br></pre></td></tr></table></figure><h2 id="基础配置"><a class="header-anchor" href="#基础配置">¶</a>基础配置</h2><p>浏览输入<code>cloud.dx4600.inc</code> 访问控制台，默认的用户名和密码都是admin。关于节点、存储的创建，可以参考B站视频 <a href="https://www.bilibili.com/video/BV1iB421k7Xe/">绿联NAS如何安装虚拟机，DX4600全系列支持</a>。</p><h3 id="网络"><a class="header-anchor" href="#网络">¶</a>网络</h3><p>创建一个桥接网络，网桥名称填<code>br-wan</code>。创建虚拟机时选择这个网络，这样虚拟机可以拿到路由器分配的IP地址，方便访问虚拟机。</p><img src="http://image.pkemb.com/image/202403202128611.png" style="zoom:50%;" /><p>nat 网络使用自带的<code>default</code>。</p><img src="http://image.pkemb.com/image/202403202129675.png" style="zoom:50%;" /><p>如果重启了容器，在启动容器之前，要先删除虚拟网卡。不然网络会异常。</p><figure class="highlight shell"><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">ifconfig virbr0 down</span><br><span class="line">ip link delete virbr0</span><br></pre></td></tr></table></figure><h2 id="创建虚拟机"><a class="header-anchor" href="#创建虚拟机">¶</a>创建虚拟机</h2><p>具体步骤可以参考B站视频<a href="https://www.bilibili.com/video/BV1iB421k7Xe/">绿联NAS如何安装虚拟机，DX4600全系列支持</a>。这里提一下我安装虚拟机遇到的坑：</p><ul><li>Ubuntu 20.04 server：可以直接安装成功</li><li>Ubuntu 22.04 server：可以直接安装成功。不要安装<code>22.10</code>，已经EOL了。安装<code>22.10</code>的过程中会crash。</li><li>win10：默认创建的磁盘总线是<code>virtio</code>，Windows不认，会找不到磁盘。需要手工添加一个<code>sata</code>总线的磁盘。</li><li>win7：磁盘的问题和win10一样，手工添加一个<code>sata</code>硬盘。win7镜像自带的安装程序走到创建分区这一步时，创建分区失败。我的解决方法是找一个PE ISO镜像，先进入PE，在PE安装win7。<br><img src="http://image.pkemb.com/image/202403202141000.png" alt=""></li></ul><h3 id="ubuntu20-04"><a class="header-anchor" href="#ubuntu20-04">¶</a>ubuntu20.04</h3><p><img src="http://image.pkemb.com/image/202403202241517.png" alt=""></p><h3 id="ubuntu22-04"><a class="header-anchor" href="#ubuntu22-04">¶</a>ubuntu22.04</h3><p><img src="http://image.pkemb.com/image/202403202245030.png" alt=""></p><h3 id="win7"><a class="header-anchor" href="#win7">¶</a>win7</h3><p><img src="http://image.pkemb.com/image/202403202244051.png" alt=""></p><h3 id="win10"><a class="header-anchor" href="#win10">¶</a>win10</h3><p>太卡了。</p><p><img src="http://image.pkemb.com/image/202403202247600.png" alt=""></p>]]>
    </content>
    <id>https://pkemb.com/2024/03/dx4600-virtual-machine/</id>
    <link href="https://pkemb.com/2024/03/dx4600-virtual-machine/"/>
    <published>2024-03-20T21:11:50.000Z</published>
    <summary>
      <![CDATA[<p>绿联NAS DX4600安装虚拟机，非官方教程。基于docker，不会修改系统任何数据。本文在<a href="https://www.bilibili.com/video/BV1iB421k7Xe/">BV1iB421k7Xe</a>的基础上，对网络、配置文件持久化、反向代理等方面进行了完善。</p>]]>
    </summary>
    <title>DX4600安装虚拟机</title>
    <updated>2024-03-20T22:52:46.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="dx4600" scheme="https://pkemb.com/tags/dx4600/"/>
    <content>
      <![CDATA[<p>突破绿联系统的限制，彻底掌握自己的设备。</p><span id="more"></span><h2 id="使用浏览器获取-API-Token"><a class="header-anchor" href="#使用浏览器获取-API-Token">¶</a>使用浏览器获取 API Token</h2><p>随便用客户端创建个 Docker 容器，在浏览器开发者模式查看网络连接，获取Token：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://192.168.50.155:9999/containers/create?name=hack&amp;ugreen_nas_model=docker&amp;api_token=xxxxGJkMTIyMzM0Y2YxYTBlZTJmZGI2MDhlODE0YjM4YzhiODhkYg%3D%3D</span><br></pre></td></tr></table></figure><p>Token 示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xxxxGJkMTIyMzM0Y2YxYTBlZTJmZGI2MDhlODE0YjM4YzhiODhkYg%3D%3D</span><br></pre></td></tr></table></figure><h2 id="创建-Hack-容器"><a class="header-anchor" href="#创建-Hack-容器">¶</a>创建 Hack 容器</h2><h3 id="创建容器配置-container-json"><a class="header-anchor" href="#创建容器配置-container-json">¶</a>创建容器配置 container.json</h3><p>container.json 文件内容如下：</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;_query&quot;: &#123;</span><br><span class="line">    &quot;name&quot;: &quot;hack&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;name&quot;: &quot;hack&quot;,</span><br><span class="line">  &quot;AttachStdout&quot;: false,</span><br><span class="line">  &quot;AttachStderr&quot;: false,</span><br><span class="line">  &quot;ExposedPorts&quot;: &#123;&#125;,</span><br><span class="line">  &quot;Tty&quot;: true,</span><br><span class="line">  &quot;OpenStdin&quot;: true,</span><br><span class="line">  &quot;Env&quot;: [</span><br><span class="line">    &quot;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;Cmd&quot;: [</span><br><span class="line">    &quot;/bin/sh&quot;</span><br><span class="line">  ],</span><br><span class="line">  &quot;Healthcheck&quot;: &#123;&#125;,</span><br><span class="line">  &quot;Image&quot;: &quot;alpine:latest&quot;,</span><br><span class="line">  &quot;Volumes&quot;: null,</span><br><span class="line">  &quot;Entrypoint&quot;: null,</span><br><span class="line">  &quot;OnBuild&quot;: null,</span><br><span class="line">  &quot;Labels&quot;: null,</span><br><span class="line">  &quot;HostConfig&quot;: &#123;</span><br><span class="line">    &quot;PidMode&quot;: &quot;host&quot;,</span><br><span class="line">    &quot;Privileged&quot;: true,</span><br><span class="line">    &quot;Devices&quot;: [</span><br><span class="line">      &#123;</span><br><span class="line">        &quot;CgroupPermissions&quot;: &quot;mrw&quot;,</span><br><span class="line">        &quot;PathInContainer&quot;: &quot;/dev/dri/renderD128&quot;,</span><br><span class="line">        &quot;PathOnHost&quot;: &quot;/dev/dri/renderD128&quot;</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        &quot;CgroupPermissions&quot;: &quot;mrw&quot;,</span><br><span class="line">        &quot;PathInContainer&quot;: &quot;/dev/dri/card0&quot;,</span><br><span class="line">        &quot;PathOnHost&quot;: &quot;/dev/dri/card0&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    &quot;NetworkMode&quot;: &quot;host&quot;,</span><br><span class="line">    &quot;PortBindings&quot;: &#123;&#125;,</span><br><span class="line">    &quot;RestartPolicy&quot;: &#123;</span><br><span class="line">      &quot;Name&quot;: &quot;always&quot;</span><br><span class="line">    &#125;,</span><br><span class="line">    &quot;LogConfig&quot;: &#123;&#125;,</span><br><span class="line">    &quot;Sysctls&quot;: &#123;&#125;,</span><br><span class="line">    &quot;Mounts&quot;: [</span><br><span class="line">      &#123;</span><br><span class="line">        &quot;Target&quot;: &quot;/host&quot;,</span><br><span class="line">        &quot;Source&quot;: &quot;/&quot;,</span><br><span class="line">        &quot;ReadOnly&quot;: false,</span><br><span class="line">        &quot;Type&quot;: &quot;bind&quot;,</span><br><span class="line">        &quot;Consistency&quot;: &quot;default&quot;,</span><br><span class="line">        &quot;Mode&quot;: &quot;RW&quot;</span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    &quot;Links&quot;: []</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;NetworkingConfig&quot;: &#123;</span><br><span class="line">    &quot;EndpointsConfig&quot;: &#123;&#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="创建容器"><a class="header-anchor" href="#创建容器">¶</a>创建容器</h3><p>自行替换 <code>api_token</code> 内容</p><figure class="highlight plaintext"><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">#!/bin/bash</span><br><span class="line">curl --compressed \</span><br><span class="line">    -H &#x27;Host: 192.168.50.155:9999&#x27; \</span><br><span class="line">    -H &#x27;User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) UGREEN_Nas/3.9.0 Chrome/91.0.4472.164 Electron/13.3.0 Safari/537.36&#x27; \</span><br><span class="line">    -H &#x27;Content-Type: application/json&#x27; \</span><br><span class="line">    -H &#x27;Accept: */*&#x27; \</span><br><span class="line">    -H &#x27;Referer: http://192.168.50.155:9999/service/web/&#x27; \</span><br><span class="line">    -H &#x27;Accept-Language: en-US&#x27; \</span><br><span class="line">    --data-binary @container.json \</span><br><span class="line">    &#x27;http://192.168.50.155:9999/containers/create?name=hack&amp;ugreen_nas_model=docker&amp;api_token=xxxxxJkMTIyMzM0Y2YxYTBlZTJmZGI2MDhlODE0YjM4YzhiODhkYg%3D%3D&#x27;</span><br></pre></td></tr></table></figure><h2 id="开启ssh登录"><a class="header-anchor" href="#开启ssh登录">¶</a>开启ssh登录</h2><p>进入绿联的docker应用，找到刚刚创建的hack容器，进入shell。执行如下命令进入绿联系统。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nsenter -t 1 -m /bin/sh</span><br></pre></td></tr></table></figure><p>进入系统后执行下面的命令创建 ssh 登录。绿联系统开机会杀死sshd进程，所以把sshd复制一份改名，才能做到开机自启动。修改<code>/etc/ssh/sshd_config</code>的<code>PubkeyAuthentication</code>为<code>yes</code>，开启密钥登录。添加自己的公钥到 <code>/root/.ssh/authorized_keys</code>。</p><figure class="highlight plaintext"><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">cp /usr/sbin/sshd /usr/sbin/sshdpk</span><br><span class="line">ssh-keygen -A</span><br><span class="line">chmod go-w /root</span><br><span class="line">chmod 700 /root/.ssh</span><br><span class="line">chmod 600 /root/.ssh/authorized_keys</span><br><span class="line">cat &gt; /etc/init.d/sshdpk &lt;&lt;EOF</span><br><span class="line">#!/bin/sh /etc/rc.common</span><br><span class="line">START=99</span><br><span class="line">STOP=10</span><br><span class="line">USE_PROCD=1</span><br><span class="line">PROG=/usr/sbin/sshdpk</span><br><span class="line">mkdir -p /var/empty</span><br><span class="line">start_service() &#123;</span><br><span class="line">    procd_open_instance</span><br><span class="line">    procd_set_param command &quot;$PROG&quot; -D</span><br><span class="line">    procd_close_instance</span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line">chmod +x /etc/init.d/sshd</span><br><span class="line">service sshd enable</span><br><span class="line">sed -i &#x27;/PubkeyAuthentication/ s/no/yes/g&#x27; /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><p>默认的端口是 922，如果不是通过<code>/etc/ssh/sshd_config</code>文件确认。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -p 922 root@192.168.50.155</span><br></pre></td></tr></table></figure><p>如果想使用普通用户登录ssh，直接使用普通用户的本地密码，或者添加ssh公钥到普通用户的<code>.ssh/authorized_keys</code>文件。<code>root</code>用户执行<code>usermod -aG docker </code>​<code>**username**</code> 将普通用户加入docker组，这样普通用户也可以执行docker命令。</p><h2 id="参考"><a class="header-anchor" href="#参考">¶</a>参考</h2><ul><li><a href="https://blog.jim.plus/blog/post/jim/dx4600-enable-ssh">https://blog.jim.plus/blog/post/jim/dx4600-enable-ssh</a></li><li><a href="https://honmaple.me/articles/2023/07/%E7%BB%BF%E8%81%94%E4%BA%91Nas%E6%96%B0%E7%89%88%E6%9C%ACv3.4.0%E5%9B%BA%E4%BB%B6%E5%BC%80%E5%90%AFSSH.html">绿联云Nas新版本v3.4.0固件开启SSH</a></li></ul>]]>
    </content>
    <id>https://pkemb.com/2024/03/dx4600-enable-ssh/</id>
    <link href="https://pkemb.com/2024/03/dx4600-enable-ssh/"/>
    <published>2024-03-10T18:41:51.000Z</published>
    <summary>
      <![CDATA[<p>突破绿联系统的限制，彻底掌握自己的设备。</p>]]>
    </summary>
    <title>DX4600开启ssh</title>
    <updated>2024-03-10T18:52:12.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="yocto" scheme="https://pkemb.com/tags/yocto/"/>
    <content>
      <![CDATA[<p>在编写Yocto recipe的时候，可能会用到其他recipe的文件。这里简单总结一下recipe之间共享文件的几种方法，以及使用过程中遇到的问题。</p><span id="more"></span><h2 id="方法一：DEPENDS"><a class="header-anchor" href="#方法一：DEPENDS">¶</a>方法一：DEPENDS</h2><p>这种方法是最常用的，也是最推荐使用的。假设<code>foo.bb</code>安装了<code>${D}${includedir}/foo.h</code>。</p><figure class="highlight plaintext"><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"># foo.bb</span><br><span class="line">do_install() &#123;</span><br><span class="line">    install -d $&#123;D&#125;$&#123;includedir&#125;</span><br><span class="line">    isntall $&#123;S&#125;/foo.h $&#123;D&#125;$&#123;includedir&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而<code>bar.bb</code>需要用到<code>foo.h</code>，那么只需要在<code>bar.bb</code>加上<code>DEPENDS += 'foo'</code>，那么<code>bar.bb</code>就能使用<code>foo.h</code>了。</p><figure class="highlight plaintext"><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"># bar.bb</span><br><span class="line">DEPENDS += &#x27;foo&#x27;</span><br></pre></td></tr></table></figure><p>简简单单一句<code>DEPENDS</code>，yocto在背后做了很多工作。下面来追以下yocto是如何实现的。</p><h3 id="准备共享文件do-populate-sysroot"><a class="header-anchor" href="#准备共享文件do-populate-sysroot">¶</a>准备共享文件do_populate_sysroot</h3><p>首先，<code>foo.bb</code>要准备好共享给其他recipe的文件，这一步由<code>foo.bb</code>的<code>do_populate_sysroot</code>任务完成。这个任务会复制文件到<code>SYSROOT_DESTDIR</code>目录（即${WORKDIR}/sysroot-destdir）。简单看一下这个任务的实现，首先会调用函数<code>sysroot_stage_all</code>，这个函数会完成实际的复制动作；然后调用<code>sysroot_strip</code>，会对ELF文件做strip；<code>SYSROOT_PREPROCESS_FUNCS</code>注册了一些预处理的函数，如果想对共享的文件做一些个性化处理，可以向这个变量注册函数；最后是<code>BB_MULTI_PROVIDER_ALLOWED</code>（不知道有啥用）。</p><figure class="highlight python"><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">def</span> <span class="title function_">do_populate_sysroot</span>(<span class="params">d</span>):</span><br><span class="line">    <span class="comment"># SYSROOT &#x27;version&#x27; 2</span></span><br><span class="line">    bb.build.exec_func(<span class="string">&quot;sysroot_stage_all&quot;</span>, d)</span><br><span class="line">    bb.build.exec_func(<span class="string">&quot;sysroot_strip&quot;</span>, d)</span><br><span class="line">    <span class="keyword">for</span> f <span class="keyword">in</span> (d.getVar(<span class="string">&#x27;SYSROOT_PREPROCESS_FUNCS&#x27;</span>) <span class="keyword">or</span> <span class="string">&#x27;&#x27;</span>).split():</span><br><span class="line">        bb.build.exec_func(f, d)</span><br><span class="line">    pn = d.getVar(<span class="string">&quot;PN&quot;</span>)</span><br><span class="line">    multiprov = d.getVar(<span class="string">&quot;BB_MULTI_PROVIDER_ALLOWED&quot;</span>).split()</span><br><span class="line">    provdir = d.expand(<span class="string">&quot;$&#123;SYSROOT_DESTDIR&#125;$&#123;base_prefix&#125;/sysroot-providers/&quot;</span>)</span><br><span class="line">    bb.utils.mkdirhier(provdir)</span><br><span class="line">    <span class="keyword">for</span> p <span class="keyword">in</span> d.getVar(<span class="string">&quot;PROVIDES&quot;</span>).split():</span><br><span class="line">        <span class="keyword">if</span> p <span class="keyword">in</span> multiprov:</span><br><span class="line">            <span class="keyword">continue</span></span><br><span class="line">        p = p.replace(<span class="string">&quot;/&quot;</span>, <span class="string">&quot;_&quot;</span>)</span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(provdir + p, <span class="string">&quot;w&quot;</span>) <span class="keyword">as</span> f:</span><br><span class="line">            f.write(pn)</span><br></pre></td></tr></table></figure><p>关键是函数<code>sysroot_stage_all</code>，这个函数会将<code>${D}</code>下面的文件复制到<code>${SYSROOT_DESTDIR}</code>。当然，不是所有的文件都会复制，只会复制<code>${SYSROOT_DIRS}</code>变量列出的目录，还会删除<code>${SYSROOT_DIRS_IGNORE}</code>变量列出的目录。</p><figure class="highlight shell"><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">sysroot_stage_dir() &#123;</span><br><span class="line">src=&quot;$1&quot;</span><br><span class="line">dest=&quot;$2&quot;</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash"><span class="keyword">if</span> the src doesn<span class="string">&#x27;t exist don&#x27;</span>t <span class="keyword">do</span> anything</span></span><br><span class="line">if [ ! -d &quot;$src&quot; ]; then</span><br><span class="line"> return</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">mkdir -p &quot;$dest&quot;</span><br><span class="line">rdest=$(realpath --relative-to=&quot;$src&quot; &quot;$dest&quot;)</span><br><span class="line">(</span><br><span class="line">cd $src</span><br><span class="line">find . -print0 | cpio --null -pdlu $rdest</span><br><span class="line">)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">sysroot_stage_dirs() &#123;</span><br><span class="line">from=&quot;$1&quot;</span><br><span class="line">to=&quot;$2&quot;</span><br><span class="line"></span><br><span class="line">for dir in $&#123;SYSROOT_DIRS&#125;; do</span><br><span class="line">sysroot_stage_dir &quot;$from$dir&quot; &quot;$to$dir&quot;</span><br><span class="line">done</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Remove directories we <span class="keyword">do</span> not care about</span></span><br><span class="line">for dir in $&#123;SYSROOT_DIRS_IGNORE&#125;; do</span><br><span class="line">rm -rf &quot;$to$dir&quot;</span><br><span class="line">done</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">sysroot_stage_all() &#123;</span><br><span class="line">sysroot_stage_dirs $&#123;D&#125; $&#123;SYSROOT_DESTDIR&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面是<code>SYSROOT_DIRS</code>和<code>SYSROOT_DIRS_IGNORE</code>变量的默认值。所以如果没有把文件安装到<code>SYSROOT_DIRS</code>包含的目录，或者把文件安装到了<code>SYSROOT_DIRS_IGNORE</code>列出的目录，那么其他的recipe就拿不到文件。</p><figure class="highlight shell"><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></pre></td><td class="code"><pre><span class="line">SYSROOT_DIRS = &quot; \</span><br><span class="line">    $&#123;includedir&#125; \</span><br><span class="line">    $&#123;libdir&#125; \</span><br><span class="line">    $&#123;base_libdir&#125; \</span><br><span class="line">    $&#123;nonarch_base_libdir&#125; \</span><br><span class="line">    $&#123;datadir&#125; \</span><br><span class="line">    /sysroot-only \</span><br><span class="line">    &quot;</span><br><span class="line"></span><br><span class="line">SYSROOT_DIRS_IGNORE = &quot; \</span><br><span class="line">    $&#123;mandir&#125; \</span><br><span class="line">    $&#123;docdir&#125; \</span><br><span class="line">    $&#123;infodir&#125; \</span><br><span class="line">    $&#123;datadir&#125;/X11/locale \</span><br><span class="line">    $&#123;datadir&#125;/applications \</span><br><span class="line">    $&#123;datadir&#125;/bash-completion \</span><br><span class="line">    $&#123;datadir&#125;/fonts \</span><br><span class="line">    $&#123;datadir&#125;/gtk-doc/html \</span><br><span class="line">    $&#123;datadir&#125;/installed-tests \</span><br><span class="line">    $&#123;datadir&#125;/locale \</span><br><span class="line">    $&#123;datadir&#125;/pixmaps \</span><br><span class="line">    $&#123;datadir&#125;/terminfo \</span><br><span class="line">    $&#123;libdir&#125;/$&#123;BPN&#125;/ptest \</span><br><span class="line">    &quot;</span><br></pre></td></tr></table></figure><h3 id="获取共享文件do-prepare-recipe-sysroot"><a class="header-anchor" href="#获取共享文件do-prepare-recipe-sysroot">¶</a>获取共享文件do_prepare_recipe_sysroot</h3><p><code>do_prepare_recipe_sysroot</code>任务会根据<code>DEPENDS</code>变量，将文件安装到<code>STAGING_DIR_HOST</code>目录或<code>STAGING_DIR_NATIVE</code>目录。</p><blockquote><p>Installs the files into the individual recipe specific sysroots (i.e. recipe-sysroot and recipe-sysroot-native under ${WORKDIR} based upon the dependencies specified by DEPENDS). See the “staging” class for more information.</p></blockquote><p>这个任务的实现比较复杂，看不太懂。</p><h3 id="kernel-bbclass"><a class="header-anchor" href="#kernel-bbclass">¶</a>kernel.bbclass</h3><p>需要注意的是，<code>kernel.bbclass</code>将<code>SYSROOT_DIRS</code>变量清空了。这意味其他recipes拿不到kernel recipe安装到<code>${D}</code>下面的文件（被坑了一把，一度怀疑人生）。</p><figure class="highlight plaintext"><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"># We don&#x27;t need to stage anything, not the modules/firmware since those would clash with linux-firmware</span><br><span class="line">SYSROOT_DIRS = &quot;&quot;</span><br></pre></td></tr></table></figure><p>在yocto 4.0.6之前，是重写了<code>sysroot_stage_all</code>函数。</p><figure class="highlight plaintext"><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"># We don&#x27;t need to stage anything, not the modules/firmware since those would clash with linux-firmware</span><br><span class="line">sysroot_stage_all () &#123;</span><br><span class="line">:</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="方法二：work-shared"><a class="header-anchor" href="#方法二：work-shared">¶</a>方法二：work-shared</h2><p><code>work-shared</code>目录在<code>${TMPDIR}/work-sahred</code>，一般情况下是<code>build/tmp/work-sahred</code>。recipe可以将文件安装到此目录下，其他recipe就可以在此路径下拿到文件。从Yocto文档看，只用于gcc及其变种，但这个限制不是强制的。</p><blockquote><p>4.2.24 build/tmp/work-shared/</p><p>For efficiency, the OpenEmbedded build system creates and uses this directory to hold recipes that share a work directory with other recipes. In practice, this is only used for gcc and its variants (e.g. gcc-cross, libgcc, gcc-runtime, and so forth).</p></blockquote><h2 id="方法三：DEPLOY-DIR-IMAGE"><a class="header-anchor" href="#方法三：DEPLOY-DIR-IMAGE">¶</a>方法三：DEPLOY_DIR_IMAGE</h2><p>与work-shared目录类似，也是recipe安装文件到DEPLAY_DIR_IMAGE目录，其他recipe通过此目录读取。</p><blockquote><p>DEPLOY_DIR_IMAGE<br>…<br>Instead, it’s only useful when a recipe needs to “read” a file already deployed by a dependency.</p></blockquote><h2 id="总结"><a class="header-anchor" href="#总结">¶</a>总结</h2><p>通过<code>DEPENDS</code>可以很方便的拿到其他recipe安装到<code>${D}</code>目录的文件。但要注意<code>SYSROOT_DIRS</code>和<code>SYSROOT_DIRS_IGNORE</code>变量，一个控制什么目录会共享，一个控制什么目录不会共享。通过检查<code>${SYSROOT_DESTDIR}</code>可以确认共享给其他recipe的文件。</p><p>此外，还能通过<code>work-shared</code>和<code>DEPLOY_DIR_IMAGE</code>目录拿到其他recipe安装的文件。</p><h2 id="参考"><a class="header-anchor" href="#参考">¶</a>参考</h2><ul><li><a href="https://docs.yoctoproject.org">yocto doc</a></li></ul>]]>
    </content>
    <id>https://pkemb.com/2023/12/yocto-shareing-files-between-recipes/</id>
    <link href="https://pkemb.com/2023/12/yocto-shareing-files-between-recipes/"/>
    <published>2023-12-30T14:54:21.000Z</published>
    <summary>
      <![CDATA[<p>在编写Yocto recipe的时候，可能会用到其他recipe的文件。这里简单总结一下recipe之间共享文件的几种方法，以及使用过程中遇到的问题。</p>]]>
    </summary>
    <title>浅谈Yocto recipe之间共享文件的方法</title>
    <updated>2023-12-30T20:45:26.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="jenkins" scheme="https://pkemb.com/categories/jenkins/"/>
    <content>
      <![CDATA[<p>记录一下折腾Jenkins流水线过程中的一些笔记和心得。相比于自由风格任务，Jenkins流水线用代码来描述任务，并且支持从SCM中拉取代码，这样可以更快速的部署新任务，并对任务代码做版本控制。</p><span id="more"></span><h2 id="Jenkins官方文档"><a class="header-anchor" href="#Jenkins官方文档">¶</a>Jenkins官方文档</h2><p>主要参考内容是Jenkins官方文档。有中文版本，但汉化的不全，汉化版本可能也缺少内容。建议英文和中文配合一起看。刚开始对语法还不熟时，可以多看看<a href="https://www.jenkins.io/doc/book/pipeline/syntax/">流水线语法</a>。</p><table><thead><tr><th>文档</th><th>英文版</th><th>中文版</th></tr></thead><tbody><tr><td>流水线入门</td><td><a href="https://www.jenkins.io/doc/book/pipeline/getting-started/">getting-started</a></td><td><a href="https://www.jenkins.io/zh/doc/book/pipeline/getting-started/">getting-started</a></td></tr><tr><td>流水线语法</td><td><a href="https://www.jenkins.io/doc/book/pipeline/syntax/">syntax</a></td><td><a href="https://www.jenkins.io/zh/doc/book/pipeline/syntax/">syntax</a></td></tr><tr><td>git插件</td><td><a href="https://www.jenkins.io/doc/pipeline/steps/git/">git</a></td><td><a href="https://www.jenkins.io/zh/doc/pipeline/steps/git/">git</a></td></tr><tr><td>Email Extension</td><td><a href="https://www.jenkins.io/doc/pipeline/steps/email-ext/">email-ext</a></td><td><a href="https://www.jenkins.io/zh/doc/pipeline/steps/email-ext/">email-ext</a></td></tr></tbody></table><h2 id="代码生成器"><a class="header-anchor" href="#代码生成器">¶</a>代码生成器</h2><p>Jenkins提供了<a href="https://www.jenkins.io/zh/doc/book/pipeline/getting-started/#snippet-generator">片段生成器</a>和<a href="https://www.jenkins.io/zh/doc/book/pipeline/getting-started/#%E5%A3%B0%E6%98%8E%E5%BC%8F%E6%8C%87%E4%BB%A4%E7%94%9F%E6%88%90%E5%99%A8">声明式指令生成器</a>。在刚开始对语法还不是很熟悉的时候，这两个工具非常有用。</p><h3 id="片段生成器"><a class="header-anchor" href="#片段生成器">¶</a>片段生成器</h3><blockquote><p>内置的“片段生成器”工具有助于为各个步骤创建代码段，发现插件提供的新步骤，或者为特定的步骤尝试不同的参数。</p><p>片段生成器由 Jenkins 实例中可用的步骤动态添加。可用的步骤的数量依赖于安装的插件，这些插件显式地公开了流水线中使用的步骤。</p><p>要使用代码生成器生成一个步骤的片段：</p><ol><li>从已配置好的流水线导航到 流水线语法 链接（见上），或访问 <code>${YOUR_JENKINS_URL}/pipeline-syntax</code>。</li><li>在 示例步骤 下拉菜单中选择需要的步骤。</li><li>使用 示例步骤 下拉菜单的动态填充区来配置已选的步骤。</li><li>点击 生成流水线脚本 生成一个能够被复制并粘贴到流水线中的流水线片段。<br><img src="https://www.jenkins.io/zh/doc/book/resources/pipeline/snippet-generator.png" alt=""></li></ol></blockquote><h3 id="声明式指令生成器"><a class="header-anchor" href="#声明式指令生成器">¶</a>声明式指令生成器</h3><blockquote><p>片段生成器可以帮助生成脚本式流水线的步骤或者声明式流水线的 stage 中的 steps 代码块，但是其并没有包含用于定义声明式流水线的 section（节段）和 directive（指令）。声明式指令生成器（Declarative Directive Generator）这个工具可以做到这点。和 片段生成器类似，指令生成器允许你选择声明式的指令，对其以一种方式进行配置，然后生成这个指令的配置，让你将其用于声明式流水线。</p><p>要使用声明式指令生成器生成一个声明式的指令：</p><ol><li>从已配置好的流水线导航到 Pipeline Syntax/流水线语法 链接（见上），然后点击侧栏的 Declarative Directive Generator，或直接访问 <code>${YOUR_JENKINS_URL}/directive-generator</code>。</li><li>在下拉菜单中选择需要的指令。</li><li>使用下拉菜单下面动态生成的区域配置已选的指令。</li><li>点击 Generate Declarative Directive 生成一个能够被复制到流水线中的指令配置。</li></ol></blockquote><h2 id="流水线模板"><a class="header-anchor" href="#流水线模板">¶</a>流水线模板</h2><p>根据自己调试流水线的经验，和实际的需要，写了一个模板。包含环境变量、选项、触发器、stage以及构建失败的邮件发送。</p><figure class="highlight groovy"><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></pre></td><td class="code"><pre><span class="line">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line"></span><br><span class="line">    environment &#123;</span><br><span class="line">        ENV1 = <span class="string">&#x27;value1&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    options &#123;</span><br><span class="line">        timestamps()</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    triggers &#123;</span><br><span class="line">        cron <span class="string">&#x27;H 21 * * *&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    stages &#123;</span><br><span class="line">        stage(<span class="string">&#x27;clean-ws&#x27;</span>) &#123;</span><br><span class="line">            steps &#123;</span><br><span class="line">                deleteDir()</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        stage(<span class="string">&#x27;stage2&#x27;</span>) &#123;</span><br><span class="line">            steps &#123;</span><br><span class="line">                <span class="comment">// xxx</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    post &#123;</span><br><span class="line">        unsuccessful &#123;</span><br><span class="line">            emailext (</span><br><span class="line">                <span class="symbol">attachLog:</span> <span class="literal">true</span>,</span><br><span class="line">                <span class="symbol">body:</span> <span class="string">&#x27;xxxx&#x27;</span>,</span><br><span class="line">                <span class="symbol">compressLog:</span> <span class="literal">true</span>,</span><br><span class="line">                <span class="symbol">subject:</span> <span class="string">&#x27;xxxx&#x27;</span>,</span><br><span class="line">                <span class="symbol">to:</span> <span class="string">&#x27;xxx@xxx.com&#x27;</span></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></pre></td></tr></table></figure><h2 id="常用代码块"><a class="header-anchor" href="#常用代码块">¶</a>常用代码块</h2><h3 id="环境变量"><a class="header-anchor" href="#环境变量">¶</a>环境变量</h3><p><code>environment</code> 指令可以设置全局变量或局部变量。放在<code>pipeline</code>下面是全局变量，放在<code>stage</code>下面是局部变量。如下是官方文档给的示例，<code>CC</code>是一个全局环境变量。<code>AN_ACCESS_KEY</code>是一个局部变量。参考<a href="https://www.jenkins.io/doc/book/pipeline/syntax/#environment">environment</a>。</p><p>在shell指令中，直接使用<code>$ENVNAME</code>引用环境变量。在<code>echo</code>指令中，使用<code>$env.ENVNAME</code>引用环境变量，且必须用双引号。</p><figure class="highlight groovy"><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">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line">    environment &#123;</span><br><span class="line">        CC = <span class="string">&#x27;clang&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">    stages &#123;</span><br><span class="line">        stage(<span class="string">&#x27;Example&#x27;</span>) &#123;</span><br><span class="line">            environment &#123;</span><br><span class="line">                AN_ACCESS_KEY = credentials(<span class="string">&#x27;my-predefined-secret-text&#x27;</span>)</span><br><span class="line">            &#125;</span><br><span class="line">            steps &#123;</span><br><span class="line">                sh <span class="string">&#x27;printenv&#x27;</span></span><br><span class="line">                sh <span class="string">&#x27;echo $CC, $AN_ACCESS_KEY&#x27;</span> <span class="comment">//shell直接 $ 引用变量，双引号和单引号都可以</span></span><br><span class="line">                echo <span class="string">&quot;$env.CC&quot;</span> <span class="comment">// echo 指令需要使用 $env. 引用变量，必须用双引号。单引号不会解析变量</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></pre></td></tr></table></figure><h3 id="选项"><a class="header-anchor" href="#选项">¶</a>选项</h3><p><code>options</code>指令可以控制流水线的形为。文档<a href="https://www.jenkins.io/doc/book/pipeline/syntax/#options">options</a>。可以借助<a href="#%E5%A3%B0%E6%98%8E%E5%BC%8F%E6%8C%87%E4%BB%A4%E7%94%9F%E6%88%90%E5%99%A8">声明式指令生成器</a>来自动生成相关代码。<code>options</code>指令需要放在<code>pipeline</code>的下面。</p><img src="http://image.pkemb.com/image/202312102149496.png"/><p>官方示例。</p><figure class="highlight groovy"><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">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line">    options &#123;</span><br><span class="line">        timeout(<span class="attr">time:</span> <span class="number">1</span>, <span class="attr">unit:</span> <span class="string">&#x27;HOURS&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    stages &#123;</span><br><span class="line">        stage(<span class="string">&#x27;Example&#x27;</span>) &#123;</span><br><span class="line">            steps &#123;</span><br><span class="line">                echo <span class="string">&#x27;Hello World&#x27;</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></pre></td></tr></table></figure><h3 id="触发器"><a class="header-anchor" href="#触发器">¶</a>触发器</h3><p><code>triggers</code>指令用于定义流水线自动运行的方式。可以周期性定时运行，也可以外部事件触发运行，例如有代码提交到<code>Gerrit</code>。同样可以借助<a href="#%E5%A3%B0%E6%98%8E%E5%BC%8F%E6%8C%87%E4%BB%A4%E7%94%9F%E6%88%90%E5%99%A8">声明式指令生成器</a>来自动生成相关代码。参考<a href="https://www.jenkins.io/doc/book/pipeline/syntax/#triggers">triggers</a>。<code>triggers</code>指令需要放在<code>pipeline</code>的下面。</p><img src="http://image.pkemb.com/image/202312102150099.png"/><p>官方示例。</p><figure class="highlight groovy"><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="comment">// Declarative //</span></span><br><span class="line">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line">    triggers &#123;</span><br><span class="line">        cron(<span class="string">&#x27;H */4 * * 1-5&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    stages &#123;</span><br><span class="line">        stage(<span class="string">&#x27;Example&#x27;</span>) &#123;</span><br><span class="line">            steps &#123;</span><br><span class="line">                echo <span class="string">&#x27;Hello World&#x27;</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></pre></td></tr></table></figure><h3 id="参数"><a class="header-anchor" href="#参数">¶</a>参数</h3><p><code>parameters</code>指令定义了一个参数列表，在触发流水线任务的时候需要提供这些参数。参考<a href="https://www.jenkins.io/doc/book/pipeline/syntax/#parameters">parameters</a>。可以借助<a href="#%E5%A3%B0%E6%98%8E%E5%BC%8F%E6%8C%87%E4%BB%A4%E7%94%9F%E6%88%90%E5%99%A8">声明式指令生成器</a>来自动生成相关代码。<code>parameters</code>指令需要放在<code>pipeline</code>的下面。</p><img src="http://image.pkemb.com/image/202312102156295.png"/><p>下面是一个官方的示例。在shell中，直接使用<code>$PARAMNAME</code>引用参数，在<code>echo</code>指令中，使用<code>$params.PARAMNAME</code>引用参数，且必须是双引号。</p><blockquote><p>需要注意的是，如果修改了参数的配置，用旧的参数列表跑一次后，再跑一次才会是新的参数列表。</p></blockquote><figure class="highlight groovy"><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">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line">    parameters &#123;</span><br><span class="line">        string(<span class="attr">name:</span> <span class="string">&#x27;PERSON&#x27;</span>, <span class="attr">defaultValue:</span> <span class="string">&#x27;Mr Jenkins&#x27;</span>, <span class="attr">description:</span> <span class="string">&#x27;Who should I say hello to?&#x27;</span>)</span><br><span class="line">        text(<span class="attr">name:</span> <span class="string">&#x27;BIOGRAPHY&#x27;</span>, <span class="attr">defaultValue:</span> <span class="string">&#x27;&#x27;</span>, <span class="attr">description:</span> <span class="string">&#x27;Enter some information about the person&#x27;</span>)</span><br><span class="line">        booleanParam(<span class="attr">name:</span> <span class="string">&#x27;TOGGLE&#x27;</span>, <span class="attr">defaultValue:</span> <span class="literal">true</span>, <span class="attr">description:</span> <span class="string">&#x27;Toggle this value&#x27;</span>)</span><br><span class="line">        choice(<span class="attr">name:</span> <span class="string">&#x27;CHOICE&#x27;</span>, <span class="attr">choices:</span> [<span class="string">&#x27;One&#x27;</span>, <span class="string">&#x27;Two&#x27;</span>, <span class="string">&#x27;Three&#x27;</span>], <span class="attr">description:</span> <span class="string">&#x27;Pick something&#x27;</span>)</span><br><span class="line">        password(<span class="attr">name:</span> <span class="string">&#x27;PASSWORD&#x27;</span>, <span class="attr">defaultValue:</span> <span class="string">&#x27;SECRET&#x27;</span>, <span class="attr">description:</span> <span class="string">&#x27;Enter a password&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    stages &#123;</span><br><span class="line">        stage(<span class="string">&#x27;Example&#x27;</span>) &#123;</span><br><span class="line">            steps &#123;</span><br><span class="line">                echo <span class="string">&quot;Hello $&#123;params.PERSON&#125;&quot;</span></span><br><span class="line">                echo <span class="string">&quot;Biography: $&#123;params.BIOGRAPHY&#125;&quot;</span></span><br><span class="line">                echo <span class="string">&quot;Toggle: $&#123;params.TOGGLE&#125;&quot;</span></span><br><span class="line">                echo <span class="string">&quot;Choice: $&#123;params.CHOICE&#125;&quot;</span></span><br><span class="line">                echo <span class="string">&quot;Password: $&#123;params.PASSWORD&#125;&quot;</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></pre></td></tr></table></figure><h3 id="post"><a class="header-anchor" href="#post">¶</a>post</h3><p><code>post</code>段定义了一个或多个步骤，在<code>stages</code>运行完之后运行。可以根据不同的条件运行不同的步骤。例如构建失败发送邮件。参考<a href="https://www.jenkins.io/doc/book/pipeline/syntax/#post">post</a>。<code>post</code>块需要放在<code>pipeline</code>的下面。可以借助<a href="#%E5%A3%B0%E6%98%8E%E5%BC%8F%E6%8C%87%E4%BB%A4%E7%94%9F%E6%88%90%E5%99%A8">声明式指令生成器</a>来自动生成相关代码。<code>post</code>块支持多种条件块，如下表所示。</p><table><thead><tr><th>条件</th><th>说明</th></tr></thead><tbody><tr><td>always</td><td>Always run, regardless of build status</td></tr><tr><td>changed</td><td>Run if the current builds status is different than the previous builds status</td></tr><tr><td>fixed</td><td>Run if the previous build was not successful and the current builds status is “Success”</td></tr><tr><td>regression</td><td>Run if the current builds status is worse than the previous builds status</td></tr><tr><td>aborted</td><td>Run when the build status is “Aborted”</td></tr><tr><td>failure</td><td>Run if the build status is “Failure”</td></tr><tr><td>success</td><td>Run if the build status is “Success” or hasnt been set yet</td></tr><tr><td>unstable</td><td>Run if the build status is “Unstable”</td></tr><tr><td>unsuccessful</td><td>Run if the current builds status is “Aborted”, “Failure” or “Unstable”</td></tr><tr><td>cleanup</td><td>Always run after all other conditions, regardless of build status</td></tr><tr><td>notBuilt</td><td>Run if the build status is “Not Built”</td></tr></tbody></table><p>示例如下。在官方示例的基础上加了一个<code>unsuccessful</code>，表示在构建终止、失败或不稳定时发送邮件。</p><figure class="highlight groovy"><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></pre></td><td class="code"><pre><span class="line">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line">    stages &#123;</span><br><span class="line">        stage(<span class="string">&#x27;Example&#x27;</span>) &#123;</span><br><span class="line">            steps &#123;</span><br><span class="line">                echo <span class="string">&#x27;Hello World&#x27;</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    post &#123;</span><br><span class="line">        always &#123;</span><br><span class="line">            echo <span class="string">&#x27;I will always say Hello again!&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">        unsuccessful &#123;</span><br><span class="line">            emailext (</span><br><span class="line">                <span class="symbol">attachLog:</span> <span class="literal">true</span>,</span><br><span class="line">                <span class="symbol">body:</span> <span class="string">&#x27;xxxx&#x27;</span>,</span><br><span class="line">                <span class="symbol">compressLog:</span> <span class="literal">true</span>,</span><br><span class="line">                <span class="symbol">subject:</span> <span class="string">&#x27;xxxx&#x27;</span>,</span><br><span class="line">                <span class="symbol">to:</span> <span class="string">&#x27;xxx@xxx.com&#x27;</span></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><h3 id="邮件"><a class="header-anchor" href="#邮件">¶</a>邮件</h3><p><code>jenkins</code>自带的<code>mail</code>指令提供了基本的邮件发送功能。可以通过<a href="#%E7%89%87%E6%AE%B5%E7%94%9F%E6%88%90%E5%99%A8">片段生成器</a>生成相关代码。</p><figure class="highlight groovy"><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">mail(</span><br><span class="line">    <span class="symbol">bcc:</span> <span class="string">&#x27;密送&#x27;</span>,</span><br><span class="line">    <span class="symbol">body:</span> <span class="string">&#x27;正文&#x27;</span>,</span><br><span class="line">    <span class="symbol">cc:</span> <span class="string">&#x27;抄送&#x27;</span>,</span><br><span class="line">    <span class="symbol">from:</span> <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">    <span class="symbol">replyTo:</span> <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">    <span class="symbol">subject:</span> <span class="string">&#x27;主题&#x27;</span>,</span><br><span class="line">    <span class="symbol">to:</span> <span class="string">&#x27;收件人&#x27;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><img src="http://image.pkemb.com/image/202312102236441.png"/><p><a href="https://plugins.jenkins.io/email-ext/">Email Extension</a> 插件提供了更多的功能。安装插件后，可以在<a href="#%E7%89%87%E6%AE%B5%E7%94%9F%E6%88%90%E5%99%A8">片段生成器</a>找到<code>emailext</code>。</p><p>另外，发送邮件还需要在<code>系统管理 -&gt; 系统配置</code>中设置邮件服务器和发件人。</p><h3 id="错误捕获"><a class="header-anchor" href="#错误捕获">¶</a>错误捕获</h3><p>可以通过<code>catchError</code>指令捕获错误，进行一些错误处理或忽略错误。如下示例所示，如果没有新的commit，<code>git push</code>指令会失败，但这个错误其实是可以忽略的。</p><figure class="highlight groovy"><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">pipeline &#123;</span><br><span class="line">    agent any</span><br><span class="line"></span><br><span class="line">    stages &#123;</span><br><span class="line">        stage(<span class="string">&#x27;push&#x27;</span>) &#123;</span><br><span class="line">            steps &#123;</span><br><span class="line">                catchError(<span class="attr">buildResult:</span> <span class="string">&#x27;SUCCESS&#x27;</span>, <span class="attr">message:</span> <span class="string">&#x27;push 失败&#x27;</span>) &#123;</span><br><span class="line">                    sh <span class="string">&#x27;git push origin HEAD:refs/for/master&#x27;</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">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可以通过<a href="#%E7%89%87%E6%AE%B5%E7%94%9F%E6%88%90%E5%99%A8">片段生成器</a>生成相关代码。</p><img src="http://image.pkemb.com/image/202312102252897.png"/><h3 id="克隆代码仓库"><a class="header-anchor" href="#克隆代码仓库">¶</a>克隆代码仓库</h3><p>在<a href="#%E7%89%87%E6%AE%B5%E7%94%9F%E6%88%90%E5%99%A8">片段生成器</a>使用<code>git</code>或<code>checkout</code>生成克隆代码仓库的指令。<code>checkout</code>支持的选项更多，且支持更多的代码仓库。</p><img src="http://image.pkemb.com/image/202312102302695.png"/><img src="http://image.pkemb.com/image/202312102303978.png"/>]]>
    </content>
    <id>https://pkemb.com/2023/12/jenkins-pipeline/</id>
    <link href="https://pkemb.com/2023/12/jenkins-pipeline/"/>
    <published>2023-12-10T18:59:00.000Z</published>
    <summary>
      <![CDATA[<p>记录一下折腾Jenkins流水线过程中的一些笔记和心得。相比于自由风格任务，Jenkins流水线用代码来描述任务，并且支持从SCM中拉取代码，这样可以更快速的部署新任务，并对任务代码做版本控制。</p>]]>
    </summary>
    <title>Jenkins流水线</title>
    <updated>2023-12-10T23:04:53.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="Linux" scheme="https://pkemb.com/categories/Linux/"/>
    <content>
      <![CDATA[<p>POSIX消息队列允许进程以消息的形式交换数据，与System V消息队列不同，但提供了类似的功能。</p><span id="more"></span><h2 id="使用条件"><a class="header-anchor" href="#使用条件">¶</a>使用条件</h2><p>若想使用POSIX消息队列，kernel需要打开配置<code>CONFIG_POSIX_MQUEUE</code>（默认情况是打开的）。编译程序时需要加上<code>-lrt</code>参数。</p><h2 id="使用方法"><a class="header-anchor" href="#使用方法">¶</a>使用方法</h2><h3 id="打开或创建消息队列"><a class="header-anchor" href="#打开或创建消息队列">¶</a>打开或创建消息队列</h3><p>使用<a href="https://www.man7.org/linux/man-pages/man3/mq_open.3.html">mq_open()</a>创建或打开一个消息队列，这个函数会返回一个消息队列描述符（<code>mdq_t</code>），之后的函数调用会使用到这个描述符。返回<code>-1</code>表示打开失败，错误原因可以通过<code>errno</code>获取。</p><figure class="highlight plaintext"><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">#include &lt;fcntl.h&gt;           /* For O_* constants */</span><br><span class="line">#include &lt;sys/stat.h&gt;        /* For mode constants */</span><br><span class="line">#include &lt;mqueue.h&gt;</span><br><span class="line"></span><br><span class="line">mqd_t mq_open(const char *name, int oflag);</span><br><span class="line">mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);</span><br></pre></td></tr></table></figure><p>消息队列使用参数<code>name</code>标识，其格式为<code>/somename</code>。<code>name</code>是一个以空字符结尾、长度不大于<code>NAME_MAX</code>（i.e., 255）的字符串，以斜线<code>/</code>开头，后续是一个或多个不是斜线的字符。若两个进程需要操作同一个消息队列，需要使用相同的<code>name</code>。</p><p><code>oflag</code>参数用于控制函数的动作，与<code>open()</code>函数的标志位非常类似。</p><ul><li>O_RDONLY 只从消息队列中接收数据</li><li>O_WRONLY 只发送数据到消息队列中</li><li>O_RDWR 发送和接收数据</li><li>O_CLOEXEC</li><li>O_CREAT 如果消息队列不存在，则创建</li><li>O_EXCL 如果指定了O_CREATE且消息队列存在，则返回错误 EEXIST</li><li>O_NONBLOCK</li></ul><p><code>O_RDONLY</code>、<code>O_WRONLY</code>、<code>O_RDWR</code> 这三个只能指定一个，且必须指定一个，其余标志可以指定0个或多个，通过位或运算拼接。如果指定了<code>O_CREATE</code>，还需要指定<code>mode</code>和<code>attr</code>参数。</p><p><code>mode</code>参数用于指定消息队列的权限。</p><p><code>attr</code>参数是<code>struct mq_attr</code>结构体指针。<code>mq_open()</code>只会使用<code>mq_maxmsg</code>和<code>mq_msgsize</code>，忽略其他的值。如果<code>attr</code>是NULL，则会使用默认值。后面会介绍默认值。</p><p><code>mq_maxmsg</code>指消息队列最多能存储多少个消息。<code>mq_msgsize</code>指每个消息的最大大小。</p><figure class="highlight plaintext"><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">struct mq_attr &#123;</span><br><span class="line">    long mq_flags;       /* Flags (ignored for mq_open()) */</span><br><span class="line">    long mq_maxmsg;      /* Max. # of messages on queue */</span><br><span class="line">    long mq_msgsize;     /* Max. message size (bytes) */</span><br><span class="line">    long mq_curmsgs;     /* # of messages currently in queue</span><br><span class="line">                            (ignored for mq_open()) */</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p>消息队列描述符本质上是一个文件描述符。所以文件描述符的特性也适用于消息队列描述符。例如<code>fork()</code>后，子进程会继承父进程的消息队列描述符，且指向同一个消息队列。</p></blockquote><h3 id="发送消息"><a class="header-anchor" href="#发送消息">¶</a>发送消息</h3><p>使用<a href="https://www.man7.org/linux/man-pages/man3/mq_send.3.html">mq_send()</a>发送消息到消息队列。参数<code>mqdes</code>是消息队列描述符，表示消息将要发送到的队列。<code>msg_ptr</code>是指向消息的指针，其长度是<code>msg_len</code>，长度必须小于或等于消息队列的<code>mq_msqsize</code>属性。<code>msg_prio</code>是一个非负整数，用于表示消息的优先级。消息在队列中按照优先级降序排序，同优先级的消息放在旧消息的后面。</p><p>如果队列已经满了（消息数量等于队列的<code>mq_maxmsg</code>属性），<code>mq_send()</code>会一直阻塞直到有足够的空间放入消息，或被信号中断。如果消息队列使能了<code>O_NONBLOCK</code>标志，调用会立即返回错误<code>EAGAIN</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="meta">#<span class="keyword">include</span> <span class="string">&lt;mqueue.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">mq_send</span><span class="params">(<span class="type">mqd_t</span> mqdes, <span class="type">const</span> <span class="type">char</span> msg_ptr[.msg_len],</span></span><br><span class="line"><span class="params">            <span class="type">size_t</span> msg_len, <span class="type">unsigned</span> <span class="type">int</span> msg_prio)</span>;</span><br></pre></td></tr></table></figure><p><code>mq_timedsend()</code>的行为类似于<code>mq_send()</code>。消息队列满了并且<code>O_NONBLOCK</code>标志没有使能的情况下，<code>abs_timeout</code>表示调用会阻塞多久。值是从<code>1970-01-01 00:00:00 +0000 (UTC)</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="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;mqueue.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">mq_timedsend</span><span class="params">(<span class="type">mqd_t</span> mqdes, <span class="type">const</span> <span class="type">char</span> msg_ptr[.msg_len],</span></span><br><span class="line"><span class="params">                <span class="type">size_t</span> msg_len, <span class="type">unsigned</span> <span class="type">int</span> msg_prio,</span></span><br><span class="line"><span class="params">                <span class="type">const</span> <span class="keyword">struct</span> timespec *abs_timeout)</span>;</span><br></pre></td></tr></table></figure><p>成功发送消息，<code>mq_send()</code>和<code>mq_timedsend()</code>返回<code>0</code>。如果有错误则返回<code>-1</code>，<code>errno</code>表示错误。</p><blockquote><p>每个消息都会关联一个优先级，并且高优先级消息会被优先发送给接收进程。消息优先级的范围是从 0（低）到<code>sysconf(_SC_MQ_PRIO_MAX)-1</code>（高）。在Linux操作系统，<code>sysconf(_SC_MQ_PRIO_MAX)</code> 返回 32768。POSIX.1只要求实现0~31。</p></blockquote><h3 id="接收消息"><a class="header-anchor" href="#接收消息">¶</a>接收消息</h3><p>使用<a href="https://www.man7.org/linux/man-pages/man3/mq_receive.3.html">mq_receive()</a>从消息队列接收消息。<code>mq_receive()</code>移除消息队列<code>mqdes</code>中优先级最高、时间最久的消息，并将消息放到<code>msg_ptr</code>指向的缓存区，<code>msg_len</code>指定了缓冲区的长度，其值必须大于或等于消息队列的<code>mq_msgsize</code>属性。如果<code>msg_prio</code>不是NULL，那么用于返回接收到消息的优先级。</p><p>如果队列是空的，默认情况下，<code>mq_receive()</code>会一直阻塞直到有消息可用，或被信号中断。如果消息队列使能了<code>O_NONBLOCK</code>标志，调用会立即返回错误<code>EAGAIN</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="meta">#<span class="keyword">include</span> <span class="string">&lt;mqueue.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">ssize_t</span> <span class="title function_">mq_receive</span><span class="params">(<span class="type">mqd_t</span> mqdes, <span class="type">char</span> msg_ptr[.msg_len],</span></span><br><span class="line"><span class="params">                    <span class="type">size_t</span> msg_len, <span class="type">unsigned</span> <span class="type">int</span> *msg_prio)</span>;</span><br></pre></td></tr></table></figure><p><code>mq_timedreceive()</code>的行为类似于<code>mq_receive()</code>。消息队列为空并且<code>O_NONBLOCK</code>标志没有使能的情况下，<code>abs_timeout</code>表示调用会阻塞多久。值是从<code>1970-01-01 00:00:00 +0000 (UTC)</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="meta">#<span class="keyword">include</span> <span class="string">&lt;time.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;mqueue.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">ssize_t</span> <span class="title function_">mq_timedreceive</span><span class="params">(<span class="type">mqd_t</span> mqdes, <span class="type">char</span> *<span class="keyword">restrict</span> msg_ptr[.msg_len],</span></span><br><span class="line"><span class="params">                    <span class="type">size_t</span> msg_len, <span class="type">unsigned</span> <span class="type">int</span> *<span class="keyword">restrict</span> msg_prio,</span></span><br><span class="line"><span class="params">                    <span class="type">const</span> <span class="keyword">struct</span> timespec *<span class="keyword">restrict</span> abs_timeout)</span>;</span><br></pre></td></tr></table></figure><p>成功接收消息，<code>mq_receive()</code>和<code>mq_timedreceive()</code>返回<code>0</code>。如果有错误则返回<code>-1</code>，<code>errno</code>表示错误。</p><h3 id="关闭消息队列"><a class="header-anchor" href="#关闭消息队列">¶</a>关闭消息队列</h3><p>当进程不再使用消息队列时，可以使用<a href="https://www.man7.org/linux/man-pages/man3/mq_close.3.html">mq_close()</a>关闭消息队列。</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;mqueue.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">mq_close</span><span class="params">(<span class="type">mqd_t</span> mqdes)</span>;</span><br></pre></td></tr></table></figure><blockquote><p><code>mq_close()</code>只是为当前进程关闭消息队列，其他进程的消息队列可能还处于打开的状态。</p></blockquote><h3 id="删除消息队列"><a class="header-anchor" href="#删除消息队列">¶</a>删除消息队列</h3><p>当不需要消息队列的时候，可以使用<a href="https://www.man7.org/linux/man-pages/man3/mq_unlink.3.html">mq_unlink()</a>移除指定的消息队列<code>name</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;mqueue.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">mq_unlink</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *name)</span>;</span><br></pre></td></tr></table></figure><blockquote><p>如果只是用<code>mq_close</code>关闭消息队列，而没有调用<code>mq_unlink()</code>删除消息队列，那么消息队列会一直存在系统中，直到系统关闭。</p></blockquote><h2 id="消息队列属性"><a class="header-anchor" href="#消息队列属性">¶</a>消息队列属性</h2><p>使用<a href="https://www.man7.org/linux/man-pages/man3/mq_getattr.3.html">mq_getattr()</a>获取消息队列属性，使用<a href="https://www.man7.org/linux/man-pages/man3/mq_getattr.3.html">mq_setatrr()</a>设置消息队列属性。</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="meta">#<span class="keyword">include</span> <span class="string">&lt;mqueue.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">mq_getattr</span><span class="params">(<span class="type">mqd_t</span> mqdes, <span class="keyword">struct</span> mq_attr *attr)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">mq_setattr</span><span class="params">(<span class="type">mqd_t</span> mqdes, <span class="type">const</span> <span class="keyword">struct</span> mq_attr *<span class="keyword">restrict</span> newattr,</span></span><br><span class="line"><span class="params">                <span class="keyword">struct</span> mq_attr *<span class="keyword">restrict</span> oldattr)</span>;</span><br></pre></td></tr></table></figure><p>消息队列属性是一个<code>struct mq_attr</code>结构体。<code>mq_flags</code>字段包含打开消息队列时关联的标志，这个字段只会出现标志<code>O_NONBLOCK</code>。</p><p><code>mq_maxmsg</code>和<code>mq_msgsize</code>字段可以在使用<code>mq_open()</code>打开消息队列时设置。</p><p><code>mq_maxmsg</code>字段是消息数量的上限。<code>mq_msgsize</code>字段是消息大小的的上限。这两个字段的值必须大于0。在使用<code>mq_open()</code>打开消息队列的时候会设置这两个字段。</p><p><code>mq_curmsgs</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="class"><span class="keyword">struct</span> <span class="title">mq_attr</span> &#123;</span></span><br><span class="line">    <span class="type">long</span> mq_flags;   <span class="comment">/* Flags: 0 or O_NONBLOCK */</span></span><br><span class="line">    <span class="type">long</span> mq_maxmsg;  <span class="comment">/* Max. # of messages on queue */</span></span><br><span class="line">    <span class="type">long</span> mq_msgsize; <span class="comment">/* Max. message size (bytes) */</span></span><br><span class="line">    <span class="type">long</span> mq_curmsgs; <span class="comment">/* # of messages currently in queue */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><code>mq_setattr()</code>使用<code>newattr</code>修改消息队列的属性。但只有<code>mq_flags</code>字段的<code>O_NONBLOCK</code>标志可以修改，其他字段会被忽略。如果<code>oldattr</code>不是NULL，用于返回消息队列修改之前的属性。</p><h2 id="异步通知"><a class="header-anchor" href="#异步通知">¶</a>异步通知</h2><p>TODO</p><p><a href="https://www.man7.org/linux/man-pages/man3/mq_notify.3.html">https://www.man7.org/linux/man-pages/man3/mq_notify.3.html</a></p><h2 id="库接口和系统调用"><a class="header-anchor" href="#库接口和系统调用">¶</a>库接口和系统调用</h2><table><thead><tr><th>Library interface</th><th>System call</th></tr></thead><tbody><tr><td>mq_close(3)</td><td>close(2)</td></tr><tr><td>mq_getattr(3)</td><td>mq_getsetattr(2)</td></tr><tr><td>mq_notify(3)</td><td>mq_notify(2)</td></tr><tr><td>mq_open(3)</td><td>mq_open(2)</td></tr><tr><td>mq_receive(3)</td><td>mq_timedreceive(2)</td></tr><tr><td>mq_send(3)</td><td>mq_timedsend(2)</td></tr><tr><td>mq_setattr(3)</td><td>mq_getsetattr(2)</td></tr><tr><td>mq_timedreceive(3)</td><td>mq_timedreceive(2)</td></tr><tr><td>mq_timedsend(3)</td><td>mq_timedsend(2)</td></tr><tr><td>mq_unlink(3)</td><td>mq_unlink(2)</td></tr></tbody></table><h2 id="proc接口"><a class="header-anchor" href="#proc接口">¶</a>/proc接口</h2><p>以下接口可用于限制POSIX消息队列使用的内存总量，设置新消息队列的默认属性。</p><h3 id="proc-sys-fs-mqueue-msg-default"><a class="header-anchor" href="#proc-sys-fs-mqueue-msg-default">¶</a>/proc/sys/fs/mqueue/msg_default</h3><p><code>mq_maxmsg</code>属性的默认值。用<a href="#%E6%89%93%E5%BC%80%E6%88%96%E5%88%9B%E5%BB%BA%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97">mq_open()</a>创建队列并且<code>attr</code>参数是NULL时，新队列的<code>mq_maxmsg</code>属性会使用这个文件定义的值。这个文件的默认值是10。</p><h3 id="proc-sys-fs-mqueue-msg-default-v2"><a class="header-anchor" href="#proc-sys-fs-mqueue-msg-default-v2">¶</a>/proc/sys/fs/mqueue/msg_default</h3><p>TODO</p><h3 id="proc-sys-fs-mqueue-msg-max"><a class="header-anchor" href="#proc-sys-fs-mqueue-msg-max">¶</a>/proc/sys/fs/mqueue/msg_max</h3><p>TODO</p><h3 id="proc-sys-fs-mqueue-msgsize-default"><a class="header-anchor" href="#proc-sys-fs-mqueue-msgsize-default">¶</a>/proc/sys/fs/mqueue/msgsize_default</h3><p>TODO</p><h3 id="proc-sys-fs-mqueue-msgsize-max"><a class="header-anchor" href="#proc-sys-fs-mqueue-msgsize-max">¶</a>/proc/sys/fs/mqueue/msgsize_max</h3><p>TODO</p><h3 id="proc-sys-fs-mqueue-queues-max"><a class="header-anchor" href="#proc-sys-fs-mqueue-queues-max">¶</a>/proc/sys/fs/mqueue/queues_max</h3><p>TODO</p><h2 id="消息队列文件系统"><a class="header-anchor" href="#消息队列文件系统">¶</a>消息队列文件系统</h2><p>在Linux，消息队列创建在虚拟文件系统。可以使用如下命令挂载。</p><figure class="highlight shell"><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">mkdir /dev/mqueue</span><br><span class="line">mount -t mqueue none /dev/mqueue</span><br></pre></td></tr></table></figure><p>挂载文件系统后，可以使用操作文件的命令来查看和操作消息队列。例如<code>ls</code>和<code>rm</code>。这个文件夹的文件包含队列的信息。</p><figure class="highlight shell"><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="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cat</span> /dev/mqueue/mymq</span></span><br><span class="line">QSIZE:129     NOTIFY:2    SIGNO:0    NOTIFY_PID:8260</span><br></pre></td></tr></table></figure><p>字段说明如下：</p><ul><li>QSIZE：队列中所有消息的总大小，单位字节。</li><li>NOTIFY_PID：如果非0，则此进程使用<a href="#%E5%BC%82%E6%AD%A5%E9%80%9A%E7%9F%A5">mq_notify()</a>注册了异步通知。其余字段描述通知是如何发生的。</li><li>NOTIFY：通知的方法。0：SIGEV_SIGNAL；1：SIGEV_NONE；2：SIGEV_THREAD。</li><li>SIGNO：SIGEV_SIGNAL使用的信号编号。</li></ul><h2 id="POSIX消息队列与SystemV消息队列对比"><a class="header-anchor" href="#POSIX消息队列与SystemV消息队列对比">¶</a>POSIX消息队列与SystemV消息队列对比</h2><p>TODO</p><h2 id="测试程序"><a class="header-anchor" href="#测试程序">¶</a>测试程序</h2><p><a href="https://github.com/pkemb/test-api/blob/master/src/mqueue.c">mqueue.c</a></p><h3 id="查看消息队列的系统参数"><a class="header-anchor" href="#查看消息队列的系统参数">¶</a>查看消息队列的系统参数</h3><p><code>./mqueue server</code>启动服务端，会打印与消息队列相关的系统参数。Ctrl-C退出，<code>cat /dev/mqueue/test_posix_message_queue</code>可以看到消息队列的一些数据。可以看到现在消息队列占用的空间是0。</p><figure class="highlight shell"><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="meta prompt_">$ </span><span class="language-bash">./mqueue server</span></span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/msg_default: 10</span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/msg_max: 10</span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/msgsize_default: 8192</span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/msgsize_max: 8192</span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/queues_max: 256</span><br><span class="line">[INFO][show_mqueue_info:0206] SC_MQ_PRIO_MAX: 32768</span><br><span class="line">^C</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cat</span> /dev/mqueue/test_posix_message_queue</span></span><br><span class="line">QSIZE:0          NOTIFY:0     SIGNO:0     NOTIFY_PID:0</span><br></pre></td></tr></table></figure><h3 id="发送阻塞"><a class="header-anchor" href="#发送阻塞">¶</a>发送阻塞</h3><p>在服务器没有启动的情况下，执行命令<code>./mqueue client 1 2 3 4 5 6 7 8 9 10 11</code>发送11个消息。可以看到，消息<code>11</code>没有发送成功，说明消息队列满了。为了测试消息优先级，第一个消息优先级是1，第二个消息优先级是2，依此类推。</p><figure class="highlight shell"><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="meta prompt_">$ </span><span class="language-bash">./mqueue client 1 2 3 4 5 6 7 8 9 10 11</span></span><br><span class="line">[INFO][client:0129] mqd = 3</span><br><span class="line">[INFO][client:0140] 发送成功：1</span><br><span class="line">[INFO][client:0140] 发送成功：2</span><br><span class="line">[INFO][client:0140] 发送成功：3</span><br><span class="line">[INFO][client:0140] 发送成功：4</span><br><span class="line">[INFO][client:0140] 发送成功：5</span><br><span class="line">[INFO][client:0140] 发送成功：6</span><br><span class="line">[INFO][client:0140] 发送成功：7</span><br><span class="line">[INFO][client:0140] 发送成功：8</span><br><span class="line">[INFO][client:0140] 发送成功：9</span><br><span class="line">[INFO][client:0140] 发送成功：10</span><br></pre></td></tr></table></figure><p>在第二个终端执行<code>cat /dev/mqueue/test_posix_message_queue</code>，可以看到消息队列占用了11个字节的空间，符合预期。</p><figure class="highlight shell"><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="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cat</span> /dev/mqueue/test_posix_message_queue</span></span><br><span class="line">QSIZE:11         NOTIFY:0     SIGNO:0     NOTIFY_PID:0</span><br></pre></td></tr></table></figure><h3 id="消息优先级"><a class="header-anchor" href="#消息优先级">¶</a>消息优先级</h3><p>基于上一个实验，在第二个终端执行<code>./mqueue server</code>。消息10最先接收到，消息队列有个空位了，所以消息11发送成功。这时消息11的优先级最高，所以第二个收到的消息是11，然后是消息9，依次类推。</p><figure class="highlight shell"><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">第一个终端</span></span><br><span class="line">[INFO][client:0140] 发送成功：9</span><br><span class="line">[INFO][client:0140] 发送成功：10</span><br><span class="line">[INFO][client:0140] 发送成功：11</span><br><span class="line"><span class="meta prompt_">$</span><span class="language-bash"></span></span><br><span class="line"><span class="language-bash"></span><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">第二个终端</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">./mqueue server</span></span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/msg_default: 10</span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/msg_max: 10</span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/msgsize_default: 8192</span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/msgsize_max: 8192</span><br><span class="line">[INFO][show_mqueue_info:0200] /proc/sys/fs/mqueue/queues_max: 256</span><br><span class="line">[INFO][show_mqueue_info:0206] SC_MQ_PRIO_MAX: 32768</span><br><span class="line">[INFO][server:0099] 第 1 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：10</span><br><span class="line">[INFO][server:0101] 消息长度：2</span><br><span class="line">[INFO][server:0102] 优先级：10</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 2 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：11</span><br><span class="line">[INFO][server:0101] 消息长度：2</span><br><span class="line">[INFO][server:0102] 优先级：11</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 3 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：9</span><br><span class="line">[INFO][server:0101] 消息长度：1</span><br><span class="line">[INFO][server:0102] 优先级：9</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 4 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：8</span><br><span class="line">[INFO][server:0101] 消息长度：1</span><br><span class="line">[INFO][server:0102] 优先级：8</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 5 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：7</span><br><span class="line">[INFO][server:0101] 消息长度：1</span><br><span class="line">[INFO][server:0102] 优先级：7</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 6 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：6</span><br><span class="line">[INFO][server:0101] 消息长度：1</span><br><span class="line">[INFO][server:0102] 优先级：6</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 7 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：5</span><br><span class="line">[INFO][server:0101] 消息长度：1</span><br><span class="line">[INFO][server:0102] 优先级：5</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 8 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：4</span><br><span class="line">[INFO][server:0101] 消息长度：1</span><br><span class="line">[INFO][server:0102] 优先级：4</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 9 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：3</span><br><span class="line">[INFO][server:0101] 消息长度：1</span><br><span class="line">[INFO][server:0102] 优先级：3</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 10 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：2</span><br><span class="line">[INFO][server:0101] 消息长度：1</span><br><span class="line">[INFO][server:0102] 优先级：2</span><br><span class="line"></span><br><span class="line">[INFO][server:0099] 第 11 个消息</span><br><span class="line">[INFO][server:0100] 收到的消息：1</span><br><span class="line">[INFO][server:0101] 消息长度：1</span><br><span class="line">[INFO][server:0102] 优先级：1</span><br></pre></td></tr></table></figure><h2 id="参考"><a class="header-anchor" href="#参考">¶</a>参考</h2><ul><li><a href="https://www.man7.org/linux/man-pages/man7/mq_overview.7.html">mq_overview.7.html</a></li></ul>]]>
    </content>
    <id>https://pkemb.com/2023/12/posix-message-queues/</id>
    <link href="https://pkemb.com/2023/12/posix-message-queues/"/>
    <published>2023-12-05T22:25:18.000Z</published>
    <summary>
      <![CDATA[<p>POSIX消息队列允许进程以消息的形式交换数据，与System V消息队列不同，但提供了类似的功能。</p>]]>
    </summary>
    <title>POSIX消息队列</title>
    <updated>2023-12-24T20:41:40.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="Linux" scheme="https://pkemb.com/categories/Linux/"/>
    <content>
      <![CDATA[<p>最近遇到了一个和<code>seccomp</code>相关的问题，但还不清楚是什么，学习一下。</p><span id="more"></span><h2 id="概要"><a class="header-anchor" href="#概要">¶</a>概要</h2><p><code>seccomp</code>是Linux kernel的一种计算安全机制，让进程只能够执行指定的系统调用。如果尝试执行不允许执行的系统调用，则程序会收到<code>SIGKILL</code>或<code>SIGSYS</code>信号。可以通过系统调用<code>prctl(2)</code>使用参数<code>PR_SET_SECCOMP</code>，或系统调用<code>seccomp(2)</code>开启<code>seccomp</code>模式。<code>seccomp</code>有两种模式，分别是严格模式<code>SECCOMP_MODE_STRICT</code>和过滤模式<code>SECCOMP_MODE_FILTER</code>。</p><p><code>OpenSSH</code>、<code>vsftpd</code>、<code>Chrome/Chromium</code>、<code>Docker</code>等项目使用了<code>seccomp</code>。</p><h2 id="严格模式-SECCOMP-MODE-STRICT"><a class="header-anchor" href="#严格模式-SECCOMP-MODE-STRICT">¶</a>严格模式 SECCOMP_MODE_STRICT</h2><p>严格模式只允许调用<code>read(2)</code>、<code>write(2)</code>、<code>_exit(2)</code>（不包括<code>exit_group(2)</code>）和<code>sigreturn(2)</code>。其他的系统调用会导致调用的线程退出，但进程只有一个线程时，整个程序会因为<code>SIGKILL</code>终止。严格模式对于数字处理应用程序非常有用，可能需要执行从管道或套接字读取的不受信任的字节码。</p><p>需要注意的是，调用线程不能够再调用<code>sigprocmask(2)</code>，但可以使用<code>sigreturn(2)</code>阻塞除<code>SIGKILL</code>和<code>SIGSTOP</code>外的所有信号。这意味着<code>alarm(2)</code>（举例）不能用于限制程序的执行时间。此外，为了确保能终止程序，必须使用<code>SIGKILL</code>。通过向<code>timer_create(2)</code>传递<code>SIGEV_SIGNAL</code>参数，并且<code>sigev_signo</code>设置为<code>SIGKILL</code>可以完成这个。或者使用<code>setrlimit(2)</code>设置<code>RLIMIT_CPU</code>的硬限制。</p><p>使用如下代码开启严格模式。如果使用<code>seccomp(2)</code>，第二个参数<code>flags</code>必须为0，第三个参数<code>args</code>必须为NULL。kernel配置需要打开<code>CONFIG_SECCOMP</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></pre></td><td class="code"><pre><span class="line">syscall(SYS_seccomp, SECCOMP_SET_MODE_STRICT, <span class="number">0</span>, <span class="literal">NULL</span>);</span><br><span class="line"><span class="comment">// 等价于</span></span><br><span class="line">prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);</span><br></pre></td></tr></table></figure><h2 id="过滤模式-SECCOMP-MODE-FILTER"><a class="header-anchor" href="#过滤模式-SECCOMP-MODE-FILTER">¶</a>过滤模式 SECCOMP_MODE_FILTER</h2><p>允许使用<code>BPF</code>过滤系统调用，相比严格模式，更加灵活。</p><p>待完善。</p><h2 id="参考"><a class="header-anchor" href="#参考">¶</a>参考</h2><ul><li><a href="https://en.wikipedia.org/wiki/Seccomp">wikipedia seccomp</a></li><li><a href="https://docs.kernel.org/userspace-api/seccomp_filter.html">docs.kernel.org seccomp_filter</a><ul><li><a href="https://www.kernel.org/doc/html/latest/translations/zh_CN/userspace-api/seccomp_filter.html">seccomp_filter中文翻译</a></li></ul></li><li><a href="https://man7.org/linux/man-pages/man2/seccomp.2.html">seccomp(2)</a></li><li><a href="https://zhuanlan.zhihu.com/p/363174561">https://zhuanlan.zhihu.com/p/363174561</a></li></ul>]]>
    </content>
    <id>https://pkemb.com/2023/09/seccomp/</id>
    <link href="https://pkemb.com/2023/09/seccomp/"/>
    <published>2023-09-10T20:14:43.000Z</published>
    <summary>
      <![CDATA[<p>最近遇到了一个和<code>seccomp</code>相关的问题，但还不清楚是什么，学习一下。</p>]]>
    </summary>
    <title>seccomp</title>
    <updated>2023-09-10T21:35:57.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <content>
      <![CDATA[<p>又是一次自己坑自己。</p><span id="more"></span><h2 id="问题背景"><a class="header-anchor" href="#问题背景">¶</a>问题背景</h2><p>某天登录gitlab，卡在<code>检查站点连接是否安全</code>，不断的循环，怎么都无法进入到登录界面。经过一番搜索，也没有太多的帖子，没有找到解决方案。</p><img src="http://image.pkemb.com/image/202307072102821.png"/><p>既然如此，那就进开发者模式，看看网络请求。经过一番观察，发现<code>challenges.cloudflare.com</code>的请求总是返回<code>400</code>。无意之间，发现远程地址竟然是<strong>127.0.0.1:443</strong>！！！用<code>nslookup challenges.cloudflare.com</code>查询DNS解析，确实是127.0.0.1。</p><img src="http://image.pkemb.com/image/202307072109011.png"/><figure class="highlight plaintext"><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">服务器:  ax86u.inc</span><br><span class="line">Address:  192.168.50.1</span><br><span class="line"></span><br><span class="line">非权威应答:</span><br><span class="line">名称:    challenges.cloudflare.com</span><br><span class="line">Addresses:  ::1</span><br><span class="line">          127.0.0.1</span><br></pre></td></tr></table></figure><h2 id="尝试1：更换DNS服务器"><a class="header-anchor" href="#尝试1：更换DNS服务器">¶</a>尝试1：更换DNS服务器</h2><p>既然DNS解析错误，那就更换DNS服务器。在本机和路由器，把常见的DNS服务器试了一遍，解析结果一直都是<code>127.0.0.1</code>。在<code>nslookup</code>命令行参数指定DNS服务器，也都尝试了一遍，依旧没用。但是其他的域名没有问题。</p><figure class="highlight plaintext"><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">C:\Users\pk&gt;nslookup challenges.cloudflare.com 8.8.8.8</span><br><span class="line">服务器:  dns.google</span><br><span class="line">Address:  8.8.8.8</span><br><span class="line"></span><br><span class="line">非权威应答:</span><br><span class="line">名称:    challenges.cloudflare.com</span><br><span class="line">Addresses:  ::1</span><br><span class="line">          127.0.0.1</span><br><span class="line"></span><br><span class="line">C:\Users\pk&gt;nslookup baidu.com 8.8.8.8</span><br><span class="line">服务器:  dns.google</span><br><span class="line">Address:  8.8.8.8</span><br><span class="line"></span><br><span class="line">非权威应答:</span><br><span class="line">名称:    baidu.com</span><br><span class="line">Addresses:  110.242.68.66</span><br><span class="line">          39.156.66.10</span><br></pre></td></tr></table></figure><h2 id="尝试2：其他设备对比"><a class="header-anchor" href="#尝试2：其他设备对比">¶</a>尝试2：其他设备对比</h2><p>既然在本机和路由器修改DNS服务器没用，那就可能是ISP有问题？看看其他设备啥情况。结果如下表所示，看来不是ISP的问题。有问题的机器，要么是我的主机，要么主机的虚拟机。</p><table><thead><tr><th>No</th><th>设备</th><th>网络</th><th>结果</th></tr></thead><tbody><tr><td>1</td><td>手机</td><td>流量</td><td>OK</td></tr><tr><td>2</td><td>手机</td><td>局域网</td><td>OK</td></tr><tr><td>3</td><td>其他笔记本（物理机）</td><td>局域网</td><td>OK</td></tr><tr><td>4</td><td>Ubuntu虚拟机</td><td>局域网</td><td>NG</td></tr><tr><td>5</td><td>Win7 虚拟机</td><td>局域网</td><td>NG</td></tr><tr><td>6</td><td>有问题的主机</td><td>局域网</td><td>NG</td></tr></tbody></table><p>我的虚拟机都配置了两张网卡，一张桥接，直连路由器，一张NAT；并且NAT网卡的优先级高于桥接网卡。当停用桥接网卡后，依旧NG；<strong>当停用NAT网卡后，好了</strong>！！！看来问题在我的主机，应该是某个后台软件或服务在捣鬼。</p><h2 id="尝试3：抓包"><a class="header-anchor" href="#尝试3：抓包">¶</a>尝试3：抓包</h2><p>既然有东西在捣鬼，那就抓包看看。打开Wireshark，选好网卡，过滤DNS包，在cmd多敲几次<code>nslookup challenges.cloudflare.com</code>，没有抓到。但是换个域名，就能看到DNS的数据包。看来还没走到网卡，DNS数据包就被劫持了。</p><h2 id="尝试4：procexp和tcpview"><a class="header-anchor" href="#尝试4：procexp和tcpview">¶</a>尝试4：procexp和tcpview</h2><p><a href="https://learn.microsoft.com/zh-cn/sysinternals/downloads/process-explorer">procexp</a>可以查看进程的详细信息，<a href="https://learn.microsoft.com/zh-cn/sysinternals/downloads/tcpview">tcpview</a>可以查看TCP和UDP连接信息。尝试通过这两个工具，来找出可疑的进程。可惜没有看到可疑的进程，也没有进程监听53端口。这个方法不行。</p><h2 id="尝试5：FastGithub"><a class="header-anchor" href="#尝试5：FastGithub">¶</a>尝试5：FastGithub</h2><p>使用工具无法定位到可疑进程，那就只好回忆，自己安装了什么和DNS有关系的软件或服务。还真想到了一个：<a href="https://github.com/dotnetcore/FastGithub">FastGithub</a>。在FastGihub的软件目录用grep搜索，发现了如下异常log。把FastGithub服务关闭后，果然<code>challenges.cloudflare.com</code>的DNS解析就正常了。仔细看log文件，也没发现把<code>challenges.cloudflare.com</code>解析为127.0.0.1的原因。</p><figure class="highlight plaintext"><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">logs/log20230707.txt:11391:challenges.cloudflare.com-&gt;127.0.0.1</span><br><span class="line">logs/log20230707.txt:11395:challenges.cloudflare.com-&gt;::1</span><br></pre></td></tr></table></figure><p>经过一番探索，在<code>appsettings/appsettings.dnspollution.json</code>发现了如下配置。把其他正常解析的域名加上类似的配置，重启服务后，也会解析为127.0.0.1。</p><figure class="highlight plaintext"><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">&quot;*.cloudflare.com&quot;: &#123;</span><br><span class="line">  &quot;TlsSni&quot;: true</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>在issue列表搜索，有发现类似的问题，<a href="https://github.com/dotnetcore/FastGithub/issues/119">软件导致api.nuget.org 解析为127.0.0.1</a>，<a href="https://github.com/dotnetcore/FastGithub/issues/290">可能影响托管在Cloudflare DNS的域名</a>。解决方法就是删除相关节点。</p><h2 id="总结"><a class="header-anchor" href="#总结">¶</a>总结</h2><p>停止FastGithub服务后，访问github会很慢，甚至无响应。所以最终选择运行FastGithub服务，删除<code>cloudflare.com</code>的配置。</p>]]>
    </content>
    <id>https://pkemb.com/2023/07/fix-domain-resolve-to-localhost/</id>
    <link href="https://pkemb.com/2023/07/fix-domain-resolve-to-localhost/"/>
    <published>2023-07-07T21:03:02.000Z</published>
    <summary>
      <![CDATA[<p>又是一次自己坑自己。</p>]]>
    </summary>
    <title>记一次解决域名解析为127.0.0.1</title>
    <updated>2023-07-08T21:31:52.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <content>
      <![CDATA[<p>记录一下confluence的安装、使用与维护。</p><span id="more"></span><h2 id="安装"><a class="header-anchor" href="#安装">¶</a>安装</h2><p><code>confluence</code>需要jdk和数据库，这里选择mysql数据库。为了方便维护，使用宝塔面板安装和创建Mysql数据库。版本号可以参考<a href="https://confluence.atlassian.com/doc/supported-platforms-207488198.html">Supported Platforms</a>。</p><h3 id="安装JDK"><a class="header-anchor" href="#安装JDK">¶</a>安装JDK</h3><p><code>confluence</code>不能使用openjdk，只能使用oracal jdk。所以如果服务器之前安装了openjdk，请先卸载。jdk可以在镜像网站<a href="https://www.injdk.cn/">injdk</a>下载，建议使用<code>jdk11</code>。如果已安装，跳过此步。</p><figure class="highlight shell"><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="meta prompt_"># </span><span class="language-bash">需要root权限</span></span><br><span class="line">cd /usr/local</span><br><span class="line">wget https://d6.injdk.cn/oraclejdk/11/jdk-11.0.12_linux-x64_bin.tar.gz</span><br><span class="line">tar -xf jdk-11.0.12_linux-x64_bin.tar.gz</span><br></pre></td></tr></table></figure><p>编辑<code>/etc/bash.bashrc</code>或<code>/etc/bashrc</code>，文件最后加入如下内容。</p><figure class="highlight plaintext"><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">export JAVA_HOME=/usr/local/jdk-11.0.12</span><br><span class="line">export CLASSPATH=$JAVA_HOME/lib</span><br><span class="line">export PATH=$JAVA_HOME/bin:$PATH</span><br></pre></td></tr></table></figure><p>退出登录，再次登录服务器。使用如下命令验证安装是否成功。</p><figure class="highlight bash"><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">pk@pk:~$ java --version</span><br><span class="line">java 11.0.12 2021-07-20 LTS</span><br><span class="line">Java(TM) SE Runtime Environment 18.9 (build 11.0.12+8-LTS-237)</span><br><span class="line">Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.12+8-LTS-237, mixed mode)</span><br><span class="line">pk@pk:~$ <span class="built_in">which</span> java</span><br><span class="line">/usr/local/jdk-11.0.12/bin/java</span><br></pre></td></tr></table></figure><h3 id="创建数据库"><a class="header-anchor" href="#创建数据库">¶</a>创建数据库</h3><p>进入宝塔面板。如果没有安装mysql数据库，需要先在软件商店安装mysql，推荐安装<code>mysql8</code>。如果已经安装低版本的mysql，需要卸载重新安装<code>mysql8</code>。</p><p>mysql安装好之后，点击左侧的数据库，点击添加数据库，填写数据库名和用户名，编码选择<code>utf-8</code>，点击提交。记住数据库名、用户名和密码。</p><img src="http://image.pkemb.com/image/202303191636743.png" width="70%"><blockquote><p>安装宝塔面板可以参考 <a href="https://www.bt.cn/new/download.html#linux">bt.cn</a>。</p></blockquote><h4 id="修改数据库排序规则"><a class="header-anchor" href="#修改数据库排序规则">¶</a>修改数据库排序规则</h4><p>点击数据库右侧的管理，进入<code>phpMyadmin</code>，执行如下SQL语句。将<code>数据库名</code>替换为刚刚创建的数据库。如果没有安装<code>phpMyadmin</code>，可以在软件商店搜索安装。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">alter</span> database 数据库名 <span class="keyword">default</span> <span class="keyword">collate</span> utf8_bin;</span><br></pre></td></tr></table></figure><h4 id="修改数据库隔离级别"><a class="header-anchor" href="#修改数据库隔离级别">¶</a>修改数据库隔离级别</h4><p>在宝塔面板进入mysql的设置页面，选中左侧的配置修改，在<code>[mysqld]</code>下添加配置<code>transaction-isolation=READ-COMMITTED</code>。保存并重启mysql。</p><img src="http://image.pkemb.com/image/202303191730559.png" width="70%"><h3 id="安装confluence"><a class="header-anchor" href="#安装confluence">¶</a>安装confluence</h3><p>进入<a href="https://www.atlassian.com/software/confluence/download-archives">download-archives</a>下载confluence。建议选择一个LTS版本。选择<code>Linux 64 Bit</code>，点击<code>Download</code>。</p><img src="http://image.pkemb.com/image/202303191645628.png" width="80%"/><p>下载到服务器后，用chmod添加可执行权限，切换到root用户执行安装包即可。安装目录与安装包所在的目录没有关系。第一步，选择<code>o</code>，确认安装。</p><p><img src="http://image.pkemb.com/image/202303191647490.png" alt=""></p><p>选择安装模式，1 默认安装，2 自定义安装，3 升级。这里选择1。</p><p><img src="http://image.pkemb.com/image/202303191649933.png" alt=""></p><p>最后一步，确认安装。这里给出了几个重要的信息。配置信息和附件都存储在家目录。等待安装完成，服务启动。</p><blockquote><p>安装目录：/opt/atlassian/confluence<br>家目录：/var/atlassian/application-data/confluence<br>HTTP端口：8090<br>RMI端口：8080</p></blockquote><p><img src="http://image.pkemb.com/image/202303191650577.png" alt=""></p><h3 id="防火墙"><a class="header-anchor" href="#防火墙">¶</a>防火墙</h3><p>进入宝塔面板，点击左侧的安全，点击添加端口规则，放开8090端口。打开浏览器，输入<code>ip:8090</code>即可访问confluence。</p><img src="http://image.pkemb.com/image/202303191656106.png" width="60%"/><h3 id="破解"><a class="header-anchor" href="#破解">¶</a>破解</h3><h4 id="准备"><a class="header-anchor" href="#准备">¶</a>准备</h4><p>使用命令<code>systemctl stop confluence</code>停止服务。<br>下载atlassian-agent.jar并上传到服务器的<code>/var/atlassian/application-data</code>目录，并将文件的所有者和所属组改成confluence。修改文件<code>/opt/atlassian/confluence/bin/setenv.sh</code>，在文件末尾添加以下内容。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export JAVA_OPTS=&quot;-javaagent:/var/atlassian/application-data/atlassian-agent.jar $&#123;JAVA_OPTS&#125;&quot;</span><br></pre></td></tr></table></figure><p>使用命令<code>systemctl start confluence</code>启动服务。使用命令<code>systemctl status confluence</code>确认服务是否启动成功。如果启动失败，请检查<code>setenv.sh</code>文件是否有语法错误。</p><h4 id="生成序列号"><a class="header-anchor" href="#生成序列号">¶</a>生成序列号</h4><p>打开浏览器，输入<code>ip:8090</code>访问confluence，这里选择产品安装。</p><img src="http://image.pkemb.com/image/202303191710585.png" width="60%"/><p>获取应用，根据实际情况勾选。建议<strong>不要</strong>勾选。</p><img src="http://image.pkemb.com/image/202303191711249.png" width="60%"/><p>复制服务器ID。</p><img src="http://image.pkemb.com/image/202303191712147.png" width="60%"/><p>进入服务器命令行，使用如下命令生成序列号。相关参数需要替换成实际的值。将生成的序列号填入网页，点击下一步，即可完成破解。</p><figure class="highlight shell"><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">cd /var/atlassian/application-data</span><br><span class="line">java -jar atlassian-agent.jar -p conf -m aaa@bbb.com -n my_name -o http://ip -s ABCD-1234-EFGH-5678</span><br></pre></td></tr></table></figure><blockquote><p>-p product, conf: confluence<br>-m email<br>-n name<br>-o organisation<br>-s server id</p></blockquote><h3 id="连接mysql数据库"><a class="header-anchor" href="#连接mysql数据库">¶</a>连接mysql数据库</h3><p>数据库选择mysql，由于缺少mysql驱动，这里需要到<a href="https://dev.mysql.com/downloads/connector/j/5.1.html">dev.mysql.com</a>下载MySQL驱动程序。</p><p><img src="http://image.pkemb.com/image/202303191721407.png" alt=""></p><p>图示版本号确认是可以的。点击下载，将解压之后的<code>mysql-connector-java-8.0.22.jar</code>移动到<code>/opt/atlassian/confluence/confluence/WEB-INF/lib</code>目录。注意：将文件的所有者和所属组都更改为confluence。</p><p><img src="http://image.pkemb.com/image/202303191723854.png" alt=""></p><p><code>systemctl restart confluence</code>重启服务，重启好之后，刷新网页，填入<a href="#%E5%88%9B%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93">创建数据库</a>步骤记录下来的信息。如果遇到如下两个错误，请检查<a href="#%E4%BF%AE%E6%94%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8E%92%E5%BA%8F%E8%A7%84%E5%88%99">修改数据库排序规则</a>和<a href="#%E4%BF%AE%E6%94%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB">修改数据库隔离级别</a>。</p><p><img src="http://image.pkemb.com/image/202303191725205.png" alt=""></p><p><img src="http://image.pkemb.com/image/202303191725684.png" alt=""></p><h3 id="完成安装"><a class="header-anchor" href="#完成安装">¶</a>完成安装</h3><p>数据库连接成功后，点击空白站点，完成安装。</p><img src="http://image.pkemb.com/image/202303191738365.png" width="60%"/><h2 id="使用"><a class="header-anchor" href="#使用">¶</a>使用</h2><h3 id="基本设置"><a class="header-anchor" href="#基本设置">¶</a>基本设置</h3><p>默认URL、邮件服务器等。</p><h3 id="破解插件"><a class="header-anchor" href="#破解插件">¶</a>破解插件</h3><p>遇到付费插件时，点击立即购买，然后点击接受&amp;安装。</p><img src="http://image.pkemb.com/image/202303191840728.png" width="70%"/><p>安装好之后进入插件的管理界面，复制应用密钥。然后使用下面的命令生成许可证。注意-p后面是接应用密钥。</p><figure class="highlight shell"><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">cd /var/atlassian/application-data</span><br><span class="line">java -jar atlassian-agent.jar -p com.mxgraph.confluence.plugins.diagramly -m aaa@bbb.com -n my_name -o http://ip -s ABCD-1234-EFGH-5678</span><br></pre></td></tr></table></figure><img src="http://image.pkemb.com/image/202303191841346.png" width="40%"/><h2 id="维护"><a class="header-anchor" href="#维护">¶</a>维护</h2><p>数据的备份与还原，迁移，以及软件升级。</p><h3 id="备份"><a class="header-anchor" href="#备份">¶</a>备份</h3><p>备份分为confluence备份和数据备份。</p><h4 id="confluence自动备份"><a class="header-anchor" href="#confluence自动备份">¶</a>confluence自动备份</h4><p>使用管理员账号进入<code>一般配置</code>，在<code>预定作业-&gt;备份系统</code>，编辑备份系统的日程表。</p><img src="http://image.pkemb.com/image/202303191749124.png" width="80%"><p>点击左侧的<code>每日备份管理</code>，可以设置备份文件的文件名，是否备份附件，备份路径。务必确认<code>confluence</code>用户在备份路径有写入的权限，否则不会产生备份文件。</p><img src="http://image.pkemb.com/image/202303191750111.png" width="50%"><p><strong>自定义备份路径</strong></p><p>默认备份路径不可以修改。如果需要自定义备份路径，修改文件<code>&lt;confluence-home&gt;/confluence.cfg.xml</code>，将以下内容修改为<code>true</code>，重启confluence服务即可修改备份路径。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;property name=&quot;admin.ui.allow.daily.backup.custom.location&quot;&gt;true&lt;/property&gt;</span><br></pre></td></tr></table></figure><h4 id="confluence手工备份"><a class="header-anchor" href="#confluence手工备份">¶</a>confluence手工备份</h4><p>进入一般配置，点击左侧的<code>备份与还原</code>，可以手工导出整个网站。</p><img src="http://image.pkemb.com/image/202303191828819.png" width="60%"/><h4 id="数据库备份"><a class="header-anchor" href="#数据库备份">¶</a>数据库备份</h4><p>数据库备份借助宝塔的定时任务完成。</p><img src="http://image.pkemb.com/image/202303191755174.png" width="70%"/><h3 id="还原"><a class="header-anchor" href="#还原">¶</a>还原</h3><h4 id="confluence还原"><a class="header-anchor" href="#confluence还原">¶</a>confluence还原</h4><p>假如confluence奔溃了，无法访问了。停止confluence服务。将家目录<code>/var/atlassian/application-data/confluence</code>移动为<code>/var/atlassian/application-data/confluence.bak</code>，启动confluence服务。由于家目录为空，访问confluence会重新进入配置流程，进入到下图所示的步骤时，选择<code>从备份还原站点</code>。</p><p><img src="http://image.pkemb.com/image/202303191738365.png" alt=""></p><h4 id="数据库还原"><a class="header-anchor" href="#数据库还原">¶</a>数据库还原</h4><p>宝塔的数据库管理页面，以及phpmyadmin均可以导入数据包备份文件并还原。</p><h3 id="迁移"><a class="header-anchor" href="#迁移">¶</a>迁移</h3><p>可以参考官方文档<a href="https://confluence.atlassian.com/doc/migrating-confluence-between-servers-184150.html">Migrating Confluence Between Servers</a>。翻译如下：</p><ol><li>在新服务器安装confluce</li><li>旧服务器和新服务器都停止confluence服务</li><li>复制mysql驱动到新服务器</li><li>删除新服务器的家目录，拷贝旧服务器的家目录到新服务器</li><li>一些其他的必要修改</li><li><code>&lt;confluence-install&gt;/conf/server.xml</code>从旧服务器复制到新服务器</li><li>配置数据库连接</li><li>新服务器启动服务，进入一般设置，添加license。</li></ol><p>官方给的步骤可以确保<code>server id</code>不变，所以这么复杂。由于是破解版，所以<code>server id</code>变了也没关系，重新生成一个即可。步骤可以简化为：</p><ol><li>旧服务器强制触发一次备份</li><li>在新服务器安装</li><li>最后一步选择从备份文件还原</li></ol><h3 id="升级"><a class="header-anchor" href="#升级">¶</a>升级</h3><p>进入一般配置，点击左侧的<code>升级规划</code>，可以下载升级步骤，按照步骤操作即可。建议升级之前，进入<code>定时任务</code>，强制执行一次备份。如果服务器是虚拟机，也可以拍一个快照。</p><img src="http://image.pkemb.com/image/202303191831525.png" width="70%"/>]]>
    </content>
    <id>https://pkemb.com/2023/03/confluence/</id>
    <link href="https://pkemb.com/2023/03/confluence/"/>
    <published>2023-03-19T16:07:57.000Z</published>
    <summary>
      <![CDATA[<p>记录一下confluence的安装、使用与维护。</p>]]>
    </summary>
    <title>confluence的安装、使用与维护</title>
    <updated>2023-03-19T19:21:33.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="bpf" scheme="https://pkemb.com/categories/bpf/"/>
    <category term="tools" scheme="https://pkemb.com/tags/tools/"/>
    <category term="bpftrace" scheme="https://pkemb.com/tags/bpftrace/"/>
    <content>
      <![CDATA[<p>bpftrace最新发布的<a href="https://github.com/iovisor/bpftrace/releases/tag/v0.17.0">0.17.0</a>加入了对arm平台的支持，所以就想为arm平台编译一版bpftrace。值得注意的是，bpftrace才加入对arm 32位的支持（<a href="https://github.com/iovisor/bpftrace/pull/2360">#2360</a>，<a href="https://github.com/iovisor/bpftrace/pull/2361">#2361</a>），由于之前一直是基于64位系统，所以现在工作的还不是很好，还有一些bug。</p><span id="more"></span><h2 id="yocto环境准备"><a class="header-anchor" href="#yocto环境准备">¶</a>yocto环境准备</h2><p>bpftrace相关的bb在<a href="https://github.com/kraj/meta-clang">meta-clang</a>。同时我想使用一个真实arm平台的BSP，而不是yocto提供的虚拟机<code>qemuarm</code>，所以我还加入了<a href="https://git.yoctoproject.org/meta-raspberrypi">meta-raspberrypi</a>。考虑到需要克隆很多仓库，所以借助<code>repo</code>工具。</p><figure class="highlight plaintext"><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">mkdir bpftrace-arm</span><br><span class="line">cd bpftrace-arm</span><br><span class="line">repo init -u https://gitlab.com/pkemb/yocto-manifest.git -b master</span><br><span class="line">repo sync</span><br><span class="line">repo start master --all</span><br><span class="line">export TEMPLATECONF=$&#123;PWD&#125;/meta/meta-pkemb/conf/templates/bpftrace-arm</span><br><span class="line">source meta/poky/oe-init-build-env</span><br></pre></td></tr></table></figure><p>说明：</p><ul><li><a href="https://gitlab.com/pkemb/yocto-manifest">pkemb/yocto-manifest</a>默认使用内网的镜像地址。</li><li>环境变量<code>TEMPLATECONF</code>用于指定示例配置文件的位置，详细说明参考<a href="https://docs.yoctoproject.org/singleindex.html#term-TEMPLATECONF">yocto文档</a>。</li><li><code>repo sync</code>默认会从Google服务器下载最新版的<code>repo</code>工具。由于一些原因，国内无法下载。可以设置环境变量<code>export REPO_URL=https://mirrors.tuna.tsinghua.edu.cn/git/git-repo</code>，从清华源下载。</li><li>现在（2023年2月12日）meta-clang中的bpftrace版本是0.16.0，0.16.0 ~ 0.17.0有一些关于32位的修改，这些补丁在meta-pkemb中有加入。</li></ul><h2 id="修改bb文件"><a class="header-anchor" href="#修改bb文件">¶</a>修改bb文件</h2><p>修改bpftrace及其相关依赖的bb文件，在<code>COMPATIBLE_HOST</code>变量中加入<code>arm</code>。</p><h3 id="meta-meta-clang"><a class="header-anchor" href="#meta-meta-clang">¶</a>meta/meta-clang</h3><p><code>bcc_0.26.0.bb</code>和<code>bpftrace_0.16.0.bb</code>。</p><figure class="highlight diff"><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="comment">diff --git a/dynamic-layers/openembedded-layer/recipes-devtools/bcc/bcc_0.26.0.bb b/dynamic-layers/openembedded-layer/recipes-devtools/bcc/bcc_0.26.0.bb</span></span><br><span class="line"><span class="comment">index 36c6192..23009c6 100644</span></span><br><span class="line"><span class="comment">--- a/dynamic-layers/openembedded-layer/recipes-devtools/bcc/bcc_0.26.0.bb</span></span><br><span class="line"><span class="comment">+++ b/dynamic-layers/openembedded-layer/recipes-devtools/bcc/bcc_0.26.0.bb</span></span><br><span class="line"><span class="meta">@@ -68,4 +68,4 @@</span> do_install_ptest() &#123;</span><br><span class="line"> FILES:$&#123;PN&#125; += &quot;$&#123;PYTHON_SITEPACKAGES_DIR&#125;&quot;</span><br><span class="line"> FILES:$&#123;PN&#125;-doc += &quot;$&#123;datadir&#125;/$&#123;PN&#125;/man&quot;</span><br><span class="line"></span><br><span class="line"><span class="deletion">-COMPATIBLE_HOST = &quot;(x86_64.*|aarch64.*|powerpc64.*|riscv64.*)-linux&quot;</span></span><br><span class="line"><span class="addition">+COMPATIBLE_HOST = &quot;(x86_64.*|aarch64.*|arm.*|powerpc64.*|riscv64.*)-linux&quot;</span></span><br><span class="line"><span class="comment">diff --git a/dynamic-layers/openembedded-layer/recipes-devtools/bpftrace/bpftrace_0.16.0.bb b/dynamic-layers/openembedded-layer/recipes-devtools/bpftrace/bpftrace_0.16.0.bb</span></span><br><span class="line"><span class="comment">index ca324bc..19e8c64 100644</span></span><br><span class="line"><span class="comment">--- a/dynamic-layers/openembedded-layer/recipes-devtools/bpftrace/bpftrace_0.16.0.bb</span></span><br><span class="line"><span class="comment">+++ b/dynamic-layers/openembedded-layer/recipes-devtools/bpftrace/bpftrace_0.16.0.bb</span></span><br><span class="line"><span class="meta">@@ -54,5 +54,5 @@</span> EXTRA_OECMAKE = &quot; \</span><br><span class="line">     -DENABLE_MAN=OFF \</span><br><span class="line"> &quot;</span><br><span class="line"></span><br><span class="line"><span class="deletion">-COMPATIBLE_HOST = &quot;(x86_64.*|aarch64.*|powerpc64.*|riscv64.*)-linux&quot;</span></span><br><span class="line"><span class="addition">+COMPATIBLE_HOST = &quot;(x86_64.*|aarch64.*|arm.*|powerpc64.*|riscv64.*)-linux&quot;</span></span><br><span class="line"> COMPATIBLE_HOST:libc-musl = &quot;null&quot;</span><br></pre></td></tr></table></figure><h3 id="meta-meta-openembedded"><a class="header-anchor" href="#meta-meta-openembedded">¶</a>meta/meta-openembedded</h3><p><code>libbpf_0.8.0.bb</code>。</p><figure class="highlight diff"><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">diff --git a/meta-oe/recipes-kernel/libbpf/libbpf_0.8.0.bb b/meta-oe/recipes-kernel/libbpf/libbpf_0.8.0.bb</span></span><br><span class="line"><span class="comment">index 3aea7c079..909478be3 100644</span></span><br><span class="line"><span class="comment">--- a/meta-oe/recipes-kernel/libbpf/libbpf_0.8.0.bb</span></span><br><span class="line"><span class="comment">+++ b/meta-oe/recipes-kernel/libbpf/libbpf_0.8.0.bb</span></span><br><span class="line"><span class="meta">@@ -12,7 +12,7 @@</span> SRC_URI = &quot;git://github.com/libbpf/libbpf.git;protocol=https;branch=master&quot;</span><br><span class="line"> SRCREV = &quot;86eb09863c1c0177e99c2c703092042d3cdba910&quot;</span><br><span class="line"></span><br><span class="line"> PACKAGE_ARCH = &quot;$&#123;MACHINE_ARCH&#125;&quot;</span><br><span class="line"><span class="deletion">-COMPATIBLE_HOST = &quot;(x86_64|i.86|aarch64|riscv64|powerpc64).*-linux&quot;</span></span><br><span class="line"><span class="addition">+COMPATIBLE_HOST = &quot;(x86_64|i.86|aarch64|arm|riscv64|powerpc64).*-linux&quot;</span></span><br><span class="line"></span><br><span class="line"> S = &quot;$&#123;WORKDIR&#125;/git/src&quot;</span><br></pre></td></tr></table></figure><h2 id="启动编译"><a class="header-anchor" href="#启动编译">¶</a>启动编译</h2><p>yocto是在编译前通过<code>do_fetch</code>任务下载bb指定的源代码，下载很有可能会失败。所以建议把所有的fetch任务跑完之后，再开始编译。建议晚上睡觉前编译，因为<code>clang</code>需要编译非常非常非常久。</p><figure class="highlight shell"><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="meta prompt_"># </span><span class="language-bash">下载所有的源代码</span></span><br><span class="line">bitbake bpftrace --runall=fetch</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">开始编译</span></span><br><span class="line">bitbake bpftrace</span><br></pre></td></tr></table></figure><p>编译成功之后，bpftrace可执行文件在 <code>build/tmp/work/cortexa7t2hf-neon-vfpv4-poky-linux-gnueabi/bpftrace/0.16.0+gita277ec42102c463d656df8f64eb2f7e87e322210-r0/package/usr/bin</code>。</p><h2 id="运行bpftrace"><a class="header-anchor" href="#运行bpftrace">¶</a>运行bpftrace</h2><p>yocto编译时所使用的glibc版本，与平台里的glibc版本可能不一致，所以bpftrace是无法直接运行的。为了将bpftrace跑起来，需要一些技巧，这里提供两个参考的方法。</p><h3 id="复制文件到arm平台"><a class="header-anchor" href="#复制文件到arm平台">¶</a>复制文件到arm平台</h3><p>为了缩短路径长度，以下相对路径均基于bpftrace的工作目录<code>build/tmp/work/cortexa7t2hf-neon-vfpv4-poky-linux-gnueabi/bpftrace/0.16.0+gita277ec42102c463d656df8f64eb2f7e87e322210-r0/</code>。</p><ol><li>在编译主机打包<code>recipe-sysroot</code>目录，复制到arm平台并解压。假设是放在arm平台的<code>/root/bpf/recipe-sysroot</code>目录。</li><li>将编译主机的文件<code>package/usr/bin/bpftrace</code>复制到arm平台的<code>/root/bpf/recipe-sysroot/usr/bin</code>目录。</li></ol><h3 id="方法一：chroot"><a class="header-anchor" href="#方法一：chroot">¶</a>方法一：chroot</h3><p>通过<code>chroot</code>命令，为bpftrace打造一个专属的rootfs，包含bpftrace对应的glibc，以及依赖的库和头文件。</p><p>编写wrapper脚本<code>/usr/bin/bpftrace_chroot.sh</code>，内容如下。记得给脚本加可执行权限。</p><figure class="highlight shell"><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="meta prompt_">#</span><span class="language-bash">!/bin/sh</span></span><br><span class="line"></span><br><span class="line">SYSROOT=&quot;/root/bpf/recipe-sysroot&quot;</span><br><span class="line"></span><br><span class="line">mkdir -p $SYSROOT/proc $SYSROOT/sys</span><br><span class="line"></span><br><span class="line">if ! mount | grep -q $SYSROOT/proc; then</span><br><span class="line">    mount --bind /proc $SYSROOT/proc</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">if ! mount | grep -q $SYSROOT/sys; then</span><br><span class="line">    mount --bind /sys $SYSROOT/sys</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">if ! mount | grep -q $SYSROOT/sys/kernel/debug; then</span><br><span class="line">    mount --bind /sys/kernel/debug $SYSROOT/sys/kernel/debug</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">chroot /root/bpf/recipe-sysroot /usr/bin/bpftrace &quot;$@&quot;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight shell"><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">root@raspberrypi:~# bpftrace_chroot.sh -l &#x27;*sleep*&#x27;</span><br><span class="line">hardware:*sleep*:</span><br><span class="line">kprobe:__rpc_sleep_on_priority</span><br><span class="line">kprobe:alarm_timer_nsleep</span><br><span class="line">kprobe:alarm_timer_nsleep_restart</span><br><span class="line">kprobe:alarmtimer_do_nsleep</span><br><span class="line">kprobe:alarmtimer_nsleep_wakeup</span><br><span class="line">kprobe:brcmf_sdio_bus_sleep</span><br><span class="line">kprobe:brcmf_sdio_sleep</span><br><span class="line">kprobe:common_nsleep</span><br><span class="line">kprobe:do_cpu_nanosleep</span><br><span class="line">kprobe:do_nanosleep</span><br><span class="line">kprobe:dwc_otg_get_lpm_portsleepstatus</span><br><span class="line">kprobe:fscache_object_sleep_till_congested</span><br><span class="line">kprobe:gpiod_cansleep</span><br><span class="line">......</span><br></pre></td></tr></table></figure><h3 id="方法二：patchelf"><a class="header-anchor" href="#方法二：patchelf">¶</a>方法二：patchelf</h3><p>首先使用patchelf修改bpftrace的解释器，然后修改<code>LD_LIBRARY_PATH</code>。具体步骤如下。</p><figure class="highlight shell"><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">cd /root/bpftrace/recipe-sysroot/usr/bin</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">备份一下</span></span><br><span class="line">cp bpftrace bpftrace_patchelf</span><br><span class="line">patchelf --set-interpreter /root/bpf/recipe-sysroot/lib/ld-linux-armhf.so.3 ./bpftrace_patchelf</span><br></pre></td></tr></table></figure><p>编写wrapper脚本/usr/bin/bpftrace_patchelf.sh，内容如下。记得给脚本加可执行权限。</p><figure class="highlight shell"><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="meta prompt_">#</span><span class="language-bash">!/bin/bash</span></span><br><span class="line"></span><br><span class="line">export LD_LIBRARY_PATH=/root/bpf/recipe-sysroot/lib:/root/bpf/recipe-sysroot/usr/lib</span><br><span class="line"></span><br><span class="line">/root/bpftrace/recipe-sysroot/usr/bin/bpftrace_patchelf &quot;$@&quot;</span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight shell"><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">root@raspberrypi:~# bpftrace_patchelf.sh -l &#x27;*sleep*&#x27;</span><br><span class="line">hardware:*sleep*:</span><br><span class="line">kprobe:__rpc_sleep_on_priority</span><br><span class="line">kprobe:alarm_timer_nsleep</span><br><span class="line">kprobe:alarm_timer_nsleep_restart</span><br><span class="line">kprobe:alarmtimer_do_nsleep</span><br><span class="line">kprobe:alarmtimer_nsleep_wakeup</span><br><span class="line">kprobe:brcmf_sdio_bus_sleep</span><br><span class="line">kprobe:brcmf_sdio_sleep</span><br><span class="line">kprobe:common_nsleep</span><br><span class="line">kprobe:do_cpu_nanosleep</span><br><span class="line">......</span><br></pre></td></tr></table></figure><h2 id="已知问题"><a class="header-anchor" href="#已知问题">¶</a>已知问题</h2><h3 id="无法获取到正确的数值型数据"><a class="header-anchor" href="#无法获取到正确的数值型数据">¶</a>无法获取到正确的数值型数据</h3><p>如下面的命令，pid是一个非常大的值，系统中并没有这个进程。<code>tid</code>、<code>nsecs</code>等参数也是类似的。</p><figure class="highlight shell"><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">root@raspberrypi:~# bpftrace_patchelf.sh -e &#x27;kprobe:do_nanosleep &#123; printf(&quot;PID %s(%d) sleeping...\n&quot;, comm, pid); &#125;&#x27;</span><br><span class="line">sh: relocation error: /root/bpf/recipe-sysroot/lib/libc.so.6: symbol __nptl_set_robust_list_avail version GLIBC_PRIVATE not defined in file ld-linux-armhf.so.3 with link time reference</span><br><span class="line">Attaching 1 probe...</span><br><span class="line">PID sleep(2128712024) sleeping...</span><br><span class="line">PID sleep(2128712024) sleeping...</span><br><span class="line">PID sleep(2128712024) sleeping...</span><br><span class="line">PID cron(2128712024) sleeping...</span><br><span class="line">PID sleep(2128712024) sleeping...</span><br><span class="line">PID sleep(2128712024) sleeping...</span><br></pre></td></tr></table></figure><h2 id="参考"><a class="header-anchor" href="#参考">¶</a>参考</h2><ul><li><a href="https://github.com/iovisor/bpftrace/issues/1688">arm 32 support(issue 1688)</a></li><li><a href="https://github.com/iovisor/bpftrace/pull/2360">#2360</a></li><li><a href="https://chasinglulu.github.io/2021/07/07/%E5%9C%A8AArch64%E5%B9%B3%E5%8F%B0%E4%B8%8A%E9%9D%99%E6%80%81%E7%BC%96%E8%AF%91%E9%93%BE%E6%8E%A5eBPF%E7%9B%B8%E5%85%B3%E7%9A%84%E5%B7%A5%E5%85%B7/">在AArch64平台上静态编译链接eBPF相关的工具</a></li></ul>]]>
    </content>
    <id>https://pkemb.com/2023/02/use-yocto-compile-bpftrace-for-arm/</id>
    <link href="https://pkemb.com/2023/02/use-yocto-compile-bpftrace-for-arm/"/>
    <published>2023-02-11T15:21:11.000Z</published>
    <summary>
      <![CDATA[<p>bpftrace最新发布的<a href="https://github.com/iovisor/bpftrace/releases/tag/v0.17.0">0.17.0</a>加入了对arm平台的支持，所以就想为arm平台编译一版bpftrace。值得注意的是，bpftrace才加入对arm 32位的支持（<a href="https://github.com/iovisor/bpftrace/pull/2360">#2360</a>，<a href="https://github.com/iovisor/bpftrace/pull/2361">#2361</a>），由于之前一直是基于64位系统，所以现在工作的还不是很好，还有一些bug。</p>]]>
    </summary>
    <title>使用yocto为arm平台编译bpftrace</title>
    <updated>2023-02-12T17:17:54.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="linux" scheme="https://pkemb.com/tags/linux/"/>
    <content>
      <![CDATA[<p>最近好几台VMware Ubuntu虚拟机开机15分钟后卡死，断断续续查了将近半个月。一度想重装虚拟机，但考虑到要重新配置的软件太多了，遂放弃。最后决定还是死磕，终于解决了。</p><span id="more"></span><h2 id="问题描述"><a class="header-anchor" href="#问题描述">¶</a>问题描述</h2><p>VMware Ubuntu20.04虚拟机，开机后system和软中断占用率逐渐增加，大概15分钟后吃掉所有CPU，系统卡死，虚拟终端无响应，SSH断连，所有服务无响应。虚拟机暂停后resume可以登录系统操作，但过15分钟后依旧卡死。重启系统后依旧有问题。卡死前的top截图如下。</p><p><img src="http://image.pkemb.com/image/202210151539366.png" alt=""></p><h2 id="总结"><a class="header-anchor" href="#总结">¶</a>总结</h2><ul><li>解决方法：虚拟机打开<code>Intel VT-x/EPT</code>后没有问题了。</li><li>根本原因：没有找到，可能是Windows升级导致的。</li><li>感悟：要多思考，根据线索仔细分析可能的问题点，抓log并分析。<strong>不是瞎猜</strong>。</li></ul><h2 id="第一次交手：drm-kms-helper"><a class="header-anchor" href="#第一次交手：drm-kms-helper">¶</a>第一次交手：drm_kms_helper</h2><p>卡死后再等一会，从虚拟终端能看到一点点kernel的报错log，如下代码块所示。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Jul 27 09:21:10 (my server name) kernel: [drm:drm_atomic_helper_wait_for_dependencies [drm_kms_helper]] *ERROR* [PLANE:34lane-0] flip_done timed out</span><br></pre></td></tr></table></figure><h3 id="在网络上寻找答案"><a class="header-anchor" href="#在网络上寻找答案">¶</a>在网络上寻找答案</h3><p>用相关的关键字搜索，找到两个解决方法，但是都没有用，<strong>还是有问题</strong>。</p><ol><li>降低kernel版本到5.4.0-122，<a href="https://community.spiceworks.com/topic/2459117-kernel-errors-present-ubuntu-20-04-4-on-vmware-esxi-7-03">参考</a>。</li><li>更改kernel启动参数，<a href="https://askubuntu.com/questions/893817/boot-very-slow-because-of-drm-kms-helper-errors">参考</a>。</li></ol><h3 id="nogui"><a class="header-anchor" href="#nogui">¶</a>nogui</h3><p>考虑到<code>kms</code>和显示有关，所以尝试以<code>nogui</code>参数启动虚拟机，但是没有用，<strong>还是有问题</strong>。</p><h3 id="删除相关ko"><a class="header-anchor" href="#删除相关ko">¶</a>删除相关ko</h3><p>报错log中出现最多的关键字就是<code>kms</code>，通过lsmod发现有一个名为<code>drm_kms_helper</code>的模块，非常可疑。</p><figure class="highlight shell"><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">pk@localhost:~$ lsmod | grep drm</span><br><span class="line">drm_kms_helper               184320   1 vmwgfx</span><br><span class="line">syscopyarea                   16384   1 drm_kms_helper</span><br><span class="line">sysfillrect                   16384   1 drm_kms_helper</span><br></pre></td></tr></table></figure><p>在<code>blacklist.conf</code>把<code>vmwgfx</code>和<code>drm_kms_helper</code>加入黑名单，更新kernel，重启系统。但是没有用，<strong>还是有问题</strong>。</p><figure class="highlight shell"><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">pk@localhost:~$ tail -2 /etc/modprobe.d/blacklist.conf</span><br><span class="line">blacklist vmwgfx</span><br><span class="line">blacklist drm_kms_helper</span><br><span class="line">pk@localhost:~$ sudo update-initramfs -u</span><br><span class="line">pk@localhost:~$ sudo reboot</span><br></pre></td></tr></table></figure><h2 id="第二次交手：卸载软件或停止服务"><a class="header-anchor" href="#第二次交手：卸载软件或停止服务">¶</a>第二次交手：卸载软件或停止服务</h2><p>看来不是<code>drm_kms_helper</code>的问题。考虑到出现问题之后，好像是给confluence装了几个插件。会不会是插件有问题呢？赶紧卸载最近安装的插件，等待，<strong>还是有问题</strong>。</p><p>从<code>top</code>的输出截图看，有几个java程序的占用率也非常高。考虑到虚拟机跑了非常多的服务，所以把所有的服务都停了，等待，<strong>还是有问题</strong>。</p><h2 id="第三次交手：升级软件"><a class="header-anchor" href="#第三次交手：升级软件">¶</a>第三次交手：升级软件</h2><p>可能是VMWare低版本的一个bug，最近才被打到？现在的版本是16.12，官网的最新版本是16.2.4。赶紧下载最新的版本装上去，等待，<strong>还是有问题</strong>。</p><p>难道是Ubuntu20的一个bug？升级到Ubuntu22试试。考虑到正常工作时间才15分钟左右，可能无法完成升级，所以升级之前拍了一个快照。执行命令<code>do-release-upgrade</code>进行发现版本升级，中间pause、resume虚拟机好几次，终于升级上去了。升级完毕后重启系统，完蛋，启动失败，rootfs找不到。懒得修了，回滚到Ubuntu20吧。</p><p>不要怀疑，<code>apt upgrade</code>升级所有软件，也<strong>还是有问题</strong>。</p><h2 id="第四次交手：journalctl"><a class="header-anchor" href="#第四次交手：journalctl">¶</a>第四次交手：journalctl</h2><p>实在想不出问题在哪里，用<code>journalctl -b -1</code>看看上次启动的日志，发现有大量<code>multipathd</code>的log，很有可能这就是这里了。</p><figure class="highlight plaintext"><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">Oct 14 22:15:09 pk.inc multipathd[1032]: sda: failed to get udev uid: Invalid argument</span><br><span class="line">Oct 14 22:15:09 pk.inc multipathd[1032]: sda: failed to get sysfs uid: Invalid argument</span><br><span class="line">Oct 14 22:15:09 pk.inc multipathd[1032]: sda: failed to get sgio uid: No such file or directory</span><br><span class="line">Oct 14 22:15:10 pk.inc multipathd[1032]: sdb: add missing path</span><br><span class="line">Oct 14 22:15:10 pk.inc multipathd[1032]: sdb: failed to get udev uid: Invalid argument</span><br><span class="line">Oct 14 22:15:10 pk.inc multipathd[1032]: sdb: failed to get sysfs uid: Invalid argument</span><br><span class="line">Oct 14 22:15:10 pk.inc multipathd[1032]: sdb: failed to get sgio uid: No such file or directory</span><br></pre></td></tr></table></figure><p>参考<a href="https://blog.csdn.net/lk_luck/article/details/120730206">esx-Server 多路径配置</a>，在<code>/etc/multipath.conf</code>添加<code>blacklist</code>，重启服务器。在要准备庆祝的时候，<strong>服务器卡死了</strong>！！！</p><figure class="highlight shell"><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">pk@localhost:~$ cat /etc/multipath.conf</span><br><span class="line">defaults &#123;</span><br><span class="line">    user_friendly_names yes</span><br><span class="line">&#125;</span><br><span class="line">blacklist &#123;</span><br><span class="line">    devnode &quot;^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*&quot;</span><br><span class="line">    devnode &quot;^sd[a-z]?[0-9]*&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="第五次交手：kenrel"><a class="header-anchor" href="#第五次交手：kenrel">¶</a>第五次交手：kenrel</h2><p>仔细观察最终的top截图，发现<code>sy</code>和<code>si</code>的占用率非常高，很有可能问题是出在kernel。也不去猜了，直接硬碰硬，抓trace。</p><h3 id="perf-top"><a class="header-anchor" href="#perf-top">¶</a>perf top</h3><p><code>perf top</code>可以看到内核函数的占用率，可以精准定位。安装之后，执行<code>perf top</code>，没有任何输出，虚拟机好像好像还挂了，em…，难受。放弃这个方法。</p><h3 id="ftrace"><a class="header-anchor" href="#ftrace">¶</a>ftrace</h3><p>既然<code>si</code>的占用率高，那就抓一下irq的ftrace。执行下面的命令抓ftrace，待虚拟机卡死后，pause、resume，虚拟机恢复正常，Ctrl-C结束抓取。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">trace-cmd record -e irq</span><br></pre></td></tr></table></figure><p>用<code>kernelshark</code>加载trace.dat，发现irq有大段的空白。</p><img src=http://image.pkemb.com/image/202210151734293.png width=70%><p>只显示一个CPU的事件，随便选取一个空白，CPU0将近27秒没有任何irq，这很不正常。kernel本身应该不会有这么严重bug，难道是CPU的问题？</p><img src=http://image.pkemb.com/image/202210151738171.png width=70%><p>由于之前开启了Hyper-V，所以虚拟机关闭了<code>Intel VT-x/EPT</code>。难度是因为这个？赶紧进入控制面板，打开Windows功能，关闭<code>Hyper-V</code>、<code>Windows虚拟机程序监控平台</code>、<code>虚拟机平台</code>，重启电脑。</p><img src=http://image.pkemb.com/image/202210151812725.png width=50%><p>进入虚拟机的CPU设置，打开<code>Intel VT-x/EPT</code>，启动虚拟机，静静的等待，完美！！！不再出现卡死的情况，完结撒花！</p><img src=http://image.pkemb.com/image/202210151815689.png width=40%>]]>
    </content>
    <id>https://pkemb.com/2022/10/vmware-ubuntu-hangs/</id>
    <link href="https://pkemb.com/2022/10/vmware-ubuntu-hangs/"/>
    <published>2022-10-15T15:23:26.000Z</published>
    <summary>
      <![CDATA[<p>最近好几台VMware Ubuntu虚拟机开机15分钟后卡死，断断续续查了将近半个月。一度想重装虚拟机，但考虑到要重新配置的软件太多了，遂放弃。最后决定还是死磕，终于解决了。</p>]]>
    </summary>
    <title>记一次解决VMWare Ubuntu虚拟机卡死</title>
    <updated>2022-10-15T20:30:35.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="ftrace" scheme="https://pkemb.com/tags/ftrace/"/>
    <content>
      <![CDATA[<p><code>ftrace</code>是一个非常好用的调试工具，特别是在调试性能相关的问题时。但在实际使用过程中发行，log实在是太多了，手工分析非常困难。所以需要一个图形化的分析工具来帮忙分析<code>strace</code>。这里简单记录一下如何使用<code>trace compass</code>来分析<code>ftrace</code>。</p><span id="more"></span><h2 id="安装trace-compass"><a class="header-anchor" href="#安装trace-compass">¶</a>安装trace compass</h2><p>进入<a href="https://www.eclipse.org/tracecompass/">tracecompass官网</a>，点击下载。解压后即可直接运行。trace compass需要JDK才能运行，如果启动失败，请检查JDK是否安装，以及版本是否符合要求。下图是启动之后的界面。</p><p><img src="http://image.pkemb.com/image/202209031531586.png" alt=""></p><p>使用trace compass分析ftrace需要先安装插件。点击<code>Window-&gt;Preferences</code>，找到<code>Install/Update -&gt; Available Software Sites</code>，勾选所有的sites，最后点击<code>Apply and Close</code>。如下图所示。</p><p><img src="http://image.pkemb.com/image/202209031539361.png" alt=""></p><p>点击 <code>Help -&gt; Install New Software...</code>，<code>Work with</code>选择<code>download.eclipse.org</code>，勾选<code>Trace Types, Trace Compass ftrace (Incubation)</code>，一路点击下一步、同意即可。最后重启即完成安装。以同样的方法安装<code>Analyses, Additional Kernel Plug-ins(Incubation)</code>。</p><p><img src="http://image.pkemb.com/image/202209031544594.png" alt=""></p><h2 id="分析ftrace"><a class="header-anchor" href="#分析ftrace">¶</a>分析ftrace</h2><p>点击<code>File -&gt; OpenTrace...</code>，打开抓好的trace文件。有关如何抓ftrace，可以参考<a href="https://pkemb.com/2022/09/capture-ftrace/">抓ftrace</a>。</p>]]>
    </content>
    <id>https://pkemb.com/2022/09/analyze-ftrace-with-trace-compass/</id>
    <link href="https://pkemb.com/2022/09/analyze-ftrace-with-trace-compass/"/>
    <published>2022-09-03T13:57:08.000Z</published>
    <summary>
      <![CDATA[<p><code>ftrace</code>是一个非常好用的调试工具，特别是在调试性能相关的问题时。但在实际使用过程中发行，log实在是太多了，手工分析非常困难。所以需要一个图形化的分析工具来帮忙分析<code>strace</code>。这里简单记录一下如何使用<code>trace compass</code>来分析<code>ftrace</code>。</p>]]>
    </summary>
    <title>使用trace compass分析ftrace</title>
    <updated>2022-09-03T21:19:58.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="ftrace" scheme="https://pkemb.com/tags/ftrace/"/>
    <content>
      <![CDATA[<p><code>ftrace</code>是一个非常好用的调试工具，特别是在调试性能相关的问题时。这里简单记录一下如何抓ftrace。</p><span id="more"></span><h2 id="普通方法抓取ftrace"><a class="header-anchor" href="#普通方法抓取ftrace">¶</a>普通方法抓取ftrace</h2><p>有关ftrace的介绍，可以参考官方文档<a href="https://www.kernel.org/doc/html/latest/trace/ftrace.html">ftrace</a>。一般是使用如下命令来抓ftrace：</p><figure class="highlight shell"><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">cd /sys/kernel/debug/tracing/</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">关闭trace，并清空相关数据</span></span><br><span class="line">echo 0 &gt; tracing_on</span><br><span class="line">echo &gt; set_event</span><br><span class="line">echo &gt; trace</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置要抓取的event，根据实际需要更改</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">所有可用的event可以 <span class="built_in">cat</span> available_events</span></span><br><span class="line">echo &quot;sched&quot; &gt;&gt; set_event</span><br><span class="line">echo &quot;irq&quot; &gt;&gt;  set_event</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置buff，越大能抓的<span class="built_in">log</span>越多，但越占用内存空间。如果<span class="built_in">log</span>的大小超过buff，则覆盖最开始的<span class="built_in">log</span>。</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">注意这是每个CPU核心的buffer大小，实际使用的内存空间要乘以核心数</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">也可以 <span class="built_in">cat</span> buffer_total_size_kb 查看总buffer大小</span></span><br><span class="line">echo 20480 &gt; buffer_size_kb</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">开启抓ftrace</span></span><br><span class="line">echo 1 &gt; tracing_on</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">复现到bug后停止</span></span><br><span class="line">echo 0 &gt; tracing_on</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">保存<span class="built_in">log</span></span></span><br><span class="line">cat trace &gt; /path/to/ftrace.log</span><br></pre></td></tr></table></figure><p>但在实际的debug实践中，发行buffer的大小不太好控制。buffer设置太小，log容易被覆盖。但buffer设置太大，会占用较多的内存空间，影响其他程序的运行。而且一些嵌入式设备，内存很小。</p><h2 id="trace-cmd"><a class="header-anchor" href="#trace-cmd">¶</a>trace-cmd</h2><p><code>trace-cmd</code>是ftrace的前端应用程序，其官网是<a href="https://trace-cmd.org/">trace-cmd.org/</a>，源代码在<a href="https://git.kernel.org/pub/scm/utils/trace-cmd/trace-cmd.git/">trace-cmd.git</a>。</p><h3 id="安装"><a class="header-anchor" href="#安装">¶</a>安装</h3><p>对于常用的Linux发行版本，可以使用直接包管理器安装。例如Ubuntu使用如下命令安装：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install trace-cmd</span><br></pre></td></tr></table></figure><h3 id="交叉编译"><a class="header-anchor" href="#交叉编译">¶</a>交叉编译</h3><p>对于嵌入式Linux，一般来说需要交叉编译。可以参考如下命令：</p><figure class="highlight shell"><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装交叉编译工具链，或直接使用BSP提供的工具链</span></span><br><span class="line">sudo apt-get install gcc-arm-linux-gnueabi</span><br><span class="line">export CC=arm-linux-gnueabi-gcc</span><br><span class="line">export CROSS_COMPILE=arm-linux-gnueabi-</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">创建相关目录</span></span><br><span class="line">mkdir ~/trace</span><br><span class="line">cd ~/trace</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">编译 libtraceevent</span></span><br><span class="line">git clone https://git.kernel.org/pub/scm/libs/libtrace/libtraceevent.git</span><br><span class="line">cd libtraceevent</span><br><span class="line">make</span><br><span class="line">make DESTDIR=~/trace/install install</span><br><span class="line">sed -i -e &#x27;s@prefix=/usr/local@prefix=/home/pk/trace/install/usr/local@g&#x27; \</span><br><span class="line">~/trace/install/usr/local/lib/x86_64-linux-gnu/pkgconfig/libtraceevent.pc</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">编译 libtracefs</span></span><br><span class="line">cd ~/trace</span><br><span class="line">git clone https://git.kernel.org/pub/scm/libs/libtrace/libtracefs.git</span><br><span class="line">cd libtracefs</span><br><span class="line">export PKG_CONFIG_PATH=~/trace/install/usr/local/lib/x86_64-linux-gnu/pkgconfig</span><br><span class="line">make</span><br><span class="line">make DESTDIR=~/trace/install install</span><br><span class="line">sed -i -e &#x27;s@prefix=/usr/local@prefix=/home/pk/trace/install/usr/local@g&#x27; \</span><br><span class="line">~/trace/install/usr/local/lib/x86_64-linux-gnu/pkgconfig/libtracefs.pc</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">编译trace-cmd</span></span><br><span class="line">git clone https://git.kernel.org/pub/scm/utils/trace-cmd/trace-cmd.git</span><br><span class="line">LDFLAGS=-static make</span><br><span class="line">make DESTDIR=~/trace/install install</span><br></pre></td></tr></table></figure><p>参考：<a href="https://marryton007.github.io/2021/11/29/embedded/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91trace-cmd/">交叉编译trace-cmd</a></p><h3 id="子命令"><a class="header-anchor" href="#子命令">¶</a>子命令</h3><p><code>trace-cmd</code>支持的子命令如下。比较常用的有record、reset、report、stat、list。每个子命令都有很多选项，可以用命令<code>trace-cmd command -h</code>查看。</p><figure class="highlight shell"><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></pre></td><td class="code"><pre><span class="line">trace-cmd version 2.9.1 (not-a-git-repo)</span><br><span class="line"></span><br><span class="line">usage:</span><br><span class="line">  trace-cmd [COMMAND] ...</span><br><span class="line"></span><br><span class="line">  commands:</span><br><span class="line">     record - record a trace into a trace.dat file</span><br><span class="line">     set - set a ftrace configuration parameter</span><br><span class="line">     start - start tracing without recording into a file</span><br><span class="line">     extract - extract a trace from the kernel</span><br><span class="line">     stop - stop the kernel from recording trace data</span><br><span class="line">     restart - restart the kernel trace data recording</span><br><span class="line">     show - show the contents of the kernel tracing buffer</span><br><span class="line">     reset - disable all kernel tracing and clear the trace buffers</span><br><span class="line">     clear - clear the trace buffers</span><br><span class="line">     report - read out the trace stored in a trace.dat file</span><br><span class="line">     stream - Start tracing and read the output directly</span><br><span class="line">     profile - Start profiling and read the output directly</span><br><span class="line">     hist - show a histogram of the trace.dat information</span><br><span class="line">     stat - show the status of the running tracing (ftrace) system</span><br><span class="line">     split - parse a trace.dat file into smaller file(s)</span><br><span class="line">     options - list the plugin options available for trace-cmd report</span><br><span class="line">     listen - listen on a network socket for trace clients</span><br><span class="line">     agent - listen on a vsocket for trace clients</span><br><span class="line">     setup-guest - create FIFOs for tracing guest VMs</span><br><span class="line">     list - list the available events, plugins or options</span><br><span class="line">     restore - restore a crashed record</span><br><span class="line">     snapshot - take snapshot of running trace</span><br><span class="line">     stack - output, enable or disable kernel stack tracing</span><br><span class="line">     check-events - parse trace event formats</span><br><span class="line">     dump - read out the meta data from a trace file</span><br></pre></td></tr></table></figure><h3 id="trace-cmd示例"><a class="header-anchor" href="#trace-cmd示例">¶</a>trace-cmd示例</h3><p>首先使用record子命令抓ftrace，然后用report命令转化为文本格式。也可以不转换，直接导入<a href="https://kernelshark.org/">KernelShark</a>分析。</p><figure class="highlight shell"><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="meta prompt_"># </span><span class="language-bash">-m 设置buff的大小，-e 设置event，</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">-o 输出到指定文件。对于空间比较小的嵌入式设备，可以保存到外挂的U盘</span></span><br><span class="line">pi@pi4b:~ $ sudo trace-cmd record -m 40960 -e &#x27;sched_*&#x27; -e &#x27;irq_*&#x27; -o ./ftrace.dat</span><br><span class="line">Hit Ctrl^C to stop recording</span><br><span class="line">^CCPU0 data recorded at offset=0x5f7000</span><br><span class="line">    29159424 bytes in size</span><br><span class="line">CPU1 data recorded at offset=0x21c6000</span><br><span class="line">    22093824 bytes in size</span><br><span class="line">CPU2 data recorded at offset=0x36d8000</span><br><span class="line">    35000320 bytes in size</span><br><span class="line">CPU3 data recorded at offset=0x5839000</span><br><span class="line">    39243776 bytes in size</span><br><span class="line">pi@pi4b:~ $ trace-cmd report -i ftrace.dat &gt; ftrace.log</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://pkemb.com/2022/09/capture-ftrace/</id>
    <link href="https://pkemb.com/2022/09/capture-ftrace/"/>
    <published>2022-09-02T20:50:11.000Z</published>
    <summary>
      <![CDATA[<p><code>ftrace</code>是一个非常好用的调试工具，特别是在调试性能相关的问题时。这里简单记录一下如何抓ftrace。</p>]]>
    </summary>
    <title>抓ftrace</title>
    <updated>2022-09-03T20:57:59.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <content>
      <![CDATA[<p>由于网络的原因，下载国外站点的文件非常慢，还经常中断导致下载失败，又要重新下载。这简直是浪费人生！！！一种解决方法是通过镜像站点下载，但一些偏门资源可能没有。另一种解决方法是挂代理，方便、直接、有效。</p><span id="more"></span><h2 id="git"><a class="header-anchor" href="#git">¶</a>git</h2><p>设置代理</p><figure class="highlight shell"><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">git config --global http.proxy &quot;http://ip:port/&quot;</span><br><span class="line">git config --global https.proxy &quot;https://ip:port/&quot;</span><br></pre></td></tr></table></figure><p>代理需要鉴权</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git config --global http.proxy &quot;http://user:password@ip:port/&quot;</span><br></pre></td></tr></table></figure><p>密码中特殊字符的处理：<a href="https://stackoverflow.com/questions/6172719/escape-character-in-git-proxy-password">Escape @ character in git proxy password</a></p><p>取消代理</p><figure class="highlight shell"><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">git config --global --unset http.proxy</span><br><span class="line">git config --global --unset https.proxy</span><br></pre></td></tr></table></figure><h2 id="apt"><a class="header-anchor" href="#apt">¶</a>apt</h2><p><strong>方法1：设置环境变量</strong></p><figure class="highlight shell"><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">export http_proxy=&quot;http://ip:port/&quot;</span><br><span class="line">export https_proxy=&quot;htts://ip:port/&quot;</span><br></pre></td></tr></table></figure><p><strong>方法2：修改apt配置</strong></p><p>修改<code>/etc/apt/apt.conf</code>，增加：</p><figure class="highlight plaintext"><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">Acquire::http::proxy &quot;http://ip:port/&quot;;</span><br><span class="line">Acquire::ftp::proxy &quot;ftp://ip:port/&quot;;</span><br><span class="line">Acquire::https::proxy &quot;https://ip:port/&quot;;</span><br></pre></td></tr></table></figure><p><strong>方法3：命令行参数</strong></p><p>在命令行增加<code>-o</code>参数：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get -o Acquire::http::proxy=&quot;http://ip:port/&quot; update</span><br></pre></td></tr></table></figure><h2 id="wget"><a class="header-anchor" href="#wget">¶</a>wget</h2><p><strong>方法1：设置环境变量</strong></p><figure class="highlight shell"><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">export http_proxy=&quot;http://ip:port/&quot;</span><br><span class="line">export https_proxy=&quot;htts://ip:port/&quot;</span><br></pre></td></tr></table></figure><p><strong>方法2：修改配置文件</strong></p><p>在<code>~/.wgetrc</code>增加如下内容：</p><figure class="highlight shell"><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="meta prompt_"># </span><span class="language-bash">You can <span class="built_in">set</span> the default proxies <span class="keyword">for</span> Wget to use <span class="keyword">for</span> http, https, and ftp.</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">They will override the value <span class="keyword">in</span> the environment.</span></span><br><span class="line">https_proxy = http://ip:port/</span><br><span class="line">http_proxy = http://ip:port/</span><br><span class="line">ftp_proxy = http://ip:port/</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">If you <span class="keyword">do</span> not want to use proxy at all, <span class="built_in">set</span> this to off.</span></span><br><span class="line">use_proxy = on</span><br></pre></td></tr></table></figure><p>也可以使用命令行参数临时关闭或打开proxy：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">--proxy=on/off</span><br></pre></td></tr></table></figure><p><strong>方法3：-e参数</strong></p><p><code>-e</code>用于执行wgetrc格式的命令。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -e &quot;http_proxy=http://ip:port/&quot;</span><br></pre></td></tr></table></figure><h2 id="curl"><a class="header-anchor" href="#curl">¶</a>curl</h2><p><strong>方法1：设置环境变量</strong></p><figure class="highlight shell"><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">export http_proxy=&quot;http://ip:port/&quot;</span><br><span class="line">export https_proxy=&quot;htts://ip:port/&quot;</span><br></pre></td></tr></table></figure><p><strong>方法2：修改配置文件</strong></p><p>编辑文件<code>~/.curlrc</code>，增加如下内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">socks5 = &quot;ip:port&quot;</span><br></pre></td></tr></table></figure><p><strong>方法3：命令行参数</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -x socks5://ip:port ...</span><br></pre></td></tr></table></figure><p>参数``–noproxy`用于取消代理。</p>]]>
    </content>
    <id>https://pkemb.com/2022/07/proxy/</id>
    <link href="https://pkemb.com/2022/07/proxy/"/>
    <published>2022-07-16T23:09:20.000Z</published>
    <summary>
      <![CDATA[<p>由于网络的原因，下载国外站点的文件非常慢，还经常中断导致下载失败，又要重新下载。这简直是浪费人生！！！一种解决方法是通过镜像站点下载，但一些偏门资源可能没有。另一种解决方法是挂代理，方便、直接、有效。</p>]]>
    </summary>
    <title>常用软件的代理设置方法</title>
    <updated>2022-07-17T11:18:07.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="android" scheme="https://pkemb.com/categories/android/"/>
    <content>
      <![CDATA[<p>Binder驱动是Binder IPC的基石，学习了解Binder驱动的实现，有助于深入理解Binder。以下基于<code>Orangepi3lts</code>提供的源代码，分析Binder驱动的实现。</p><span id="more"></span><h2 id="binder设备使用方法"><a class="header-anchor" href="#binder设备使用方法">¶</a>binder设备使用方法</h2><p>了解Binder的使用方法，对理解驱动的设计思路有帮助。Binder是一种进程间通信的方法，采用主从结构，客户端可以借助Binder驱动给服务端发送消息，或者接收服务端的消息。自然可以得到下图。</p><p><img src="https://image.pkemb.com/image/202204122043628.png" alt=""></p><p>但是有一个问题，客户端怎么知道服务端的<code>handle</code>？所以Android引入了一个叫做<code>ServiceManager</code>的进程，这是一个非常特殊的服务端，其<code>handle</code>为0，主要功能是注册和查询Service。所以上图就扩展演变成了下图。由于SM的<code>handle</code>为0，所以服务端可以通过SM注册成为一个Service，客户端可以通过SM查询Service的handle。当得到服务端的handle后，客户端就可以直接与服务端通信了。</p><p><img src="https://image.pkemb.com/image/202204122102626.png" alt=""></p><p>通过上面的分析，Binder有以下的使用场景：</p><ol><li>服务端注册</li><li>客服端查询</li><li>客户端向服务端发起通信</li></ol><p>当然，还需要知道如何打开Binder驱动。下面以<code>ServiceManager</code>为例，展示了一个进程如何初始化Binder驱动。初始化大致分为以下3步。ServiceManager比较特殊，需要调用命令<code>BINDER_SET_CONTEXT_MGR</code>，整个系统也只有<code>ServiceManager</code>需要调用这个命令。</p><ol><li>打开设备驱动节点</li><li>执行mmap映射内存</li><li>调用ioctl读写数据</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><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 仅保留了主要代码</span></span><br><span class="line"><span class="comment">// frameworks/native/cmds/servicemanager/service_manager.c</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span>** argv)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">binder_state</span> *<span class="title">bs</span>;</span></span><br><span class="line">    <span class="type">char</span> *driver = <span class="string">&quot;/dev/binder&quot;</span>;</span><br><span class="line">    ...</span><br><span class="line">    bs = binder_open(driver, <span class="number">128</span>*<span class="number">1024</span>); <span class="comment">// 打开Binder驱动</span></span><br><span class="line">    binder_become_context_manager(bs);  <span class="comment">// 成为Binder大管家</span></span><br><span class="line">    binder_loop(bs, svcmgr_handler);    <span class="comment">// 循环处理消息</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// frameworks/native/cmds/servicemanager/binder.c</span></span><br><span class="line"><span class="keyword">struct</span> binder_state *<span class="title function_">binder_open</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* driver, <span class="type">size_t</span> mapsize)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">binder_state</span> *<span class="title">bs</span>;</span></span><br><span class="line"></span><br><span class="line">    bs = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(*bs));</span><br><span class="line">    bs-&gt;fd = open(driver, O_RDWR | O_CLOEXEC);</span><br><span class="line"></span><br><span class="line">    bs-&gt;mapsize = mapsize;</span><br><span class="line">    bs-&gt;mapped = mmap(<span class="literal">NULL</span>, mapsize, PROT_READ, MAP_PRIVATE, bs-&gt;fd, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">return</span> bs;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">binder_become_context_manager</span><span class="params">(<span class="keyword">struct</span> binder_state *bs)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> ioctl(bs-&gt;fd, BINDER_SET_CONTEXT_MGR, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="代码结构"><a class="header-anchor" href="#代码结构">¶</a>代码结构</h2><p>binder驱动代码在kernel源码的<code>drivers/android</code>目录下，头文件在<code>linux-4.9/include/uapi/linux/android</code>目录下，代码可通过<a href="https://github.com/orangepi-xunlong/linux-orangepi/tree/orange-pi-4.9-sun50iw6">linux-orangepi</a>获取。binder驱动代码主要分为两个文件，<a href="https://github.com/orangepi-xunlong/linux-orangepi/blob/orange-pi-4.9-sun50iw6/drivers/android/binder.c">binder.c</a>包含misc设备、fops等的实现代码，以及关键数据结构的定义。与内存分配有关的代码和数据结构都定义在<a href="https://github.com/orangepi-xunlong/linux-orangepi/blob/orange-pi-4.9-sun50iw6/drivers/android/binder_alloc.c">binder_alloc.c</a>中。</p><p>与binder有关的编译选项主要有两个，编译选项<code>CONFIG_ANDROID_BINDER_IPC</code>决定是否将binder编译进系统；<code>CONFIG_ANDROID_BINDER_DEVICES</code>提供了设备名字列表，用逗号分割，binder会根据这个列表提供的设备名，注册若干个设备。下面是<code>orangepi3lts</code>的编译选项设置。</p><figure class="highlight plaintext"><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">CONFIG_ANDROID_BINDER_IPC=y</span><br><span class="line">CONFIG_ANDROID_BINDER_DEVICES=&quot;binder,hwbinder,vndbinder&quot;</span><br></pre></td></tr></table></figure><h2 id="设备驱动初始化"><a class="header-anchor" href="#设备驱动初始化">¶</a>设备驱动初始化</h2><p>在<a href="https://github.com/orangepi-xunlong/linux-orangepi/blob/orange-pi-4.9-sun50iw6/drivers/android/binder.c">binder.c</a>的最底部，<code>device_initcall()</code>注册了binder的初始化函数<code>binder_init()</code>，下面是初始化函数的主体结构，删除了局部变量声明、错误处理相关的代码。首先在debugfs目录下创建文件夹和文件，然后根据编译选项的配置创建若干个设备。</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">// 仅保留主要代码</span></span><br><span class="line"><span class="type">static</span> <span class="type">char</span> *binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES;</span><br><span class="line">module_param_named(devices, binder_devices_param, charp, S_IRUGO);</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> __init <span class="title function_">binder_init</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"><span class="comment">// 在debugfs下创建目录 binder/proc，debugfs的挂载目录可以通过mount命令查看</span></span><br><span class="line">binder_debugfs_dir_entry_root = debugfs_create_dir(<span class="string">&quot;binder&quot;</span>, <span class="literal">NULL</span>);</span><br><span class="line"><span class="keyword">if</span> (binder_debugfs_dir_entry_root)</span><br><span class="line">binder_debugfs_dir_entry_proc = debugfs_create_dir(<span class="string">&quot;proc&quot;</span>,</span><br><span class="line"> binder_debugfs_dir_entry_root);</span><br><span class="line"><span class="comment">// 在debugfs/binder目录下创建若干个文件</span></span><br><span class="line"><span class="keyword">if</span> (binder_debugfs_dir_entry_root) &#123;</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 根据 CONFIG_ANDROID_BINDER_DEVICES 提供的设备名，创建若干个设备</span></span><br><span class="line">device_names = kzalloc(<span class="built_in">strlen</span>(binder_devices_param) + <span class="number">1</span>, GFP_KERNEL);</span><br><span class="line"><span class="built_in">strcpy</span>(device_names, binder_devices_param);</span><br><span class="line"><span class="keyword">while</span> ((device_name = strsep(&amp;device_names, <span class="string">&quot;,&quot;</span>))) &#123;</span><br><span class="line">ret = init_binder_device(device_name);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br><span class="line">device_initcall(binder_init);</span><br></pre></td></tr></table></figure><p>设备驱动节点的初始化由函数<code>init_binder_device()</code>完成，参数为设备名。首先为设备申请了一个设备结构体<code>binder_device</code>，然后就是对设备结构体的初始化。</p><p>首先初始化成员<code>miscdev</code>，这表明binder是一个misc字符设备，相关的操作函数存储在<code>binder_fops</code>，这是一个非常重要的结构体。然后调用<code>misc_register()</code>向系统注册binder设备。</p><p><code>context</code>表示binder设备的使用环境，主要记录了<code>contex mgr</code>相关的信息。可以从<a href="https://source.android.google.cn/devices/architecture/hidl/binder-ipc?hl=zh-cn">Android Binder文档</a>了解到不同binder设备的用途。</p><p>最后，通过<code>hlist</code>将所有的设备结构体链在一起，通过链表头<code>binder_devices</code>可以遍历所有的binder设备。</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="type">static</span> <span class="title function_">HLIST_HEAD</span><span class="params">(binder_devices)</span>;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_device</span> &#123;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_node</span> <span class="title">hlist</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">miscdevice</span> <span class="title">miscdev</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_context</span> <span class="title">context</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> __init <span class="title function_">init_binder_device</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *name)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="type">int</span> ret;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_device</span> *<span class="title">binder_device</span>;</span></span><br><span class="line">binder_device = kzalloc(<span class="keyword">sizeof</span>(*binder_device), GFP_KERNEL);</span><br><span class="line"></span><br><span class="line">binder_device-&gt;miscdev.fops = &amp;binder_fops;</span><br><span class="line">binder_device-&gt;miscdev.minor = MISC_DYNAMIC_MINOR;</span><br><span class="line">binder_device-&gt;miscdev.name = name;</span><br><span class="line"></span><br><span class="line">binder_device-&gt;context.binder_context_mgr_uid = INVALID_UID;</span><br><span class="line">binder_device-&gt;context.name = name;</span><br><span class="line">mutex_init(&amp;binder_device-&gt;context.context_mgr_node_lock);</span><br><span class="line"></span><br><span class="line">ret = misc_register(&amp;binder_device-&gt;miscdev);</span><br><span class="line">hlist_add_head(&amp;binder_device-&gt;hlist, &amp;binder_devices);</span><br><span class="line"><span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在来验证一下binder驱动的初始化。在debugfs的挂载目录下可以看到binder创建的目录和文件。这些文件的作用后面再说。<code>/dev</code>目录下也可以看到binder驱动创建的三个设备节点。从<a href="https://source.android.google.cn/devices/architecture/hidl/binder-ipc?hl=zh-cn">Android Binder文档</a>可以了解到每个设备节点的用途。</p><figure class="highlight shell"><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">console:/ # mount | grep debugfs</span><br><span class="line">debugfs on /sys/kernel/debug type debugfs (rw,seclabel,relatime)</span><br><span class="line">console:/ # ls -l /sys/kernel/debug/binder</span><br><span class="line">total 0</span><br><span class="line">-r--r--r-- 1 root root 0 1970-01-01 08:00 failed_transaction_log</span><br><span class="line">drwxr-xr-x 2 root root 0 2022-03-20 17:39 proc</span><br><span class="line">-r--r--r-- 1 root root 0 1970-01-01 08:00 state</span><br><span class="line">-r--r--r-- 1 root root 0 1970-01-01 08:00 stats</span><br><span class="line">-r--r--r-- 1 root root 0 1970-01-01 08:00 transaction_log</span><br><span class="line">-r--r--r-- 1 root root 0 1970-01-01 08:00 transactions</span><br><span class="line"></span><br><span class="line">console:/ # ls /dev/*binder* -l</span><br><span class="line">crw-rw-rw- 1 root root 10,  54 1970-01-01 08:00 /dev/binder</span><br><span class="line">crw-rw-rw- 1 root root 10,  53 1970-01-01 08:00 /dev/hwbinder</span><br><span class="line">crw-rw-rw- 1 root root 10,  52 1970-01-01 08:00 /dev/vndbinder</span><br></pre></td></tr></table></figure><blockquote><table><thead><tr><th>IPC 域</th><th>说明</th></tr></thead><tbody><tr><td>/dev/binder</td><td>框架/应用进程之间的 IPC，使用 AIDL 接口</td></tr><tr><td>/dev/hwbinder</td><td>框架/供应商进程之间的 IPC，使用 HIDL 接口<br>供应商进程之间的 IPC，使用 HIDL 接口</td></tr><tr><td>/dev/vndbinder</td><td>供应商/供应商进程之间的 IPC，使用 AIDL 接口</td></tr></tbody></table></blockquote><p>另外，在<code>/sys/module/binder/parameters</code>目录下，还可以看到binder驱动定义的参数。</p><figure class="highlight shell"><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">console:/ # ls -l /sys/module/binder/parameters/</span><br><span class="line">total 0</span><br><span class="line">-rw-r--r-- 1 root root 4096 2022-03-20 19:51 debug_mask</span><br><span class="line">-r--r--r-- 1 root root 4096 2022-03-20 19:51 devices</span><br><span class="line">-rw-r--r-- 1 root root 4096 2022-03-20 19:51 stop_on_user_error</span><br></pre></td></tr></table></figure><h2 id="open函数"><a class="header-anchor" href="#open函数">¶</a>open函数</h2><p>字符设备有一个非常重要的结构体，<code>struct file_operations</code>，以下是binder的定义。在对binder设备节点执行对应的系统调用时，最终会调用到设备驱动提供的函数。使用binder驱动的第一步是调用open()函数，所以需要先分析<code>binder_open()</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="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">file_operations</span> <span class="title">binder_fops</span> =</span> &#123;</span><br><span class="line">.owner = THIS_MODULE,</span><br><span class="line">.poll = binder_poll,</span><br><span class="line">.unlocked_ioctl = binder_ioctl,</span><br><span class="line">.compat_ioctl = binder_ioctl,</span><br><span class="line">.mmap = binder_mmap,</span><br><span class="line">.open = binder_open,</span><br><span class="line">.flush = binder_flush,</span><br><span class="line">.release = binder_release,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><code>binder_open()</code>主要是围绕结构体<code>binder_proc</code>来做文章，这里记录了一些打开binder的进程的信息，然在<code>debugfs/binder/proc</code>目录下创建一个以<code>pid</code>命名的文件。<code>binder_open()</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><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="type">static</span> <span class="type">int</span> <span class="title function_">binder_open</span><span class="params">(<span class="keyword">struct</span> inode *nodp, <span class="keyword">struct</span> file *filp)</span></span><br><span class="line">&#123;</span><br><span class="line">...</span><br><span class="line"><span class="comment">// misc 会将 filp-&gt;private_data 设置为 strcut miscdevice</span></span><br><span class="line"><span class="comment">// 而这个结构体包含在 struct binder_device中，所以可以通过</span></span><br><span class="line"><span class="comment">// container_of 拿到 binder_dev</span></span><br><span class="line">binder_dev = container_of(filp-&gt;private_data, <span class="keyword">struct</span> binder_device, miscdev);</span><br><span class="line">proc-&gt;context = &amp;binder_dev-&gt;context; <span class="comment">// 记录当前进程打开的设备节点</span></span><br><span class="line">binder_alloc_init(&amp;proc-&gt;alloc);</span><br><span class="line">...</span><br><span class="line"><span class="comment">// ioctl() 或其他fops中的函数，可通过private_data拿到proc</span></span><br><span class="line">filp-&gt;private_data = proc;</span><br><span class="line"><span class="comment">// 所有的proc链接到链表binder_procs</span></span><br><span class="line">mutex_lock(&amp;binder_procs_lock);</span><br><span class="line">hlist_add_head(&amp;proc-&gt;proc_node, &amp;binder_procs);</span><br><span class="line">mutex_unlock(&amp;binder_procs_lock);</span><br><span class="line">...</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="mmap函数"><a class="header-anchor" href="#mmap函数">¶</a>mmap函数</h2><p>设备驱动的mmap函数需要协助kernel完成<code>struct vmware_area_struct</code>结构体的设置，mmap函数就非常好理解了。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 删除了错误处理</span></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">binder_mmap</span><span class="params">(<span class="keyword">struct</span> file *filp, <span class="keyword">struct</span> vm_area_struct *vma)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="type">int</span> ret;</span><br><span class="line"><span class="comment">// 从 private_data取出proc，这是在open函数设置的</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_proc</span> *<span class="title">proc</span> =</span> filp-&gt;private_data;</span><br><span class="line"><span class="type">const</span> <span class="type">char</span> *failure_string;</span><br><span class="line"><span class="comment">// 只有进程的主线程才能调用mmap</span></span><br><span class="line"><span class="keyword">if</span> (proc-&gt;tsk != current-&gt;group_leader)</span><br><span class="line"><span class="keyword">return</span> -EINVAL;</span><br><span class="line"><span class="comment">// 最大映射4MB</span></span><br><span class="line"><span class="keyword">if</span> ((vma-&gt;vm_end - vma-&gt;vm_start) &gt; SZ_4M)</span><br><span class="line">vma-&gt;vm_end = vma-&gt;vm_start + SZ_4M;</span><br><span class="line"></span><br><span class="line">vma-&gt;vm_flags = (vma-&gt;vm_flags | VM_DONTCOPY) &amp; ~VM_MAYWRITE;</span><br><span class="line">vma-&gt;vm_ops = &amp;binder_vm_ops;</span><br><span class="line">vma-&gt;vm_private_data = proc;</span><br><span class="line"><span class="comment">// 为进程映射虚拟地址空间</span></span><br><span class="line">ret = binder_alloc_mmap_handler(&amp;proc-&gt;alloc, vma);</span><br><span class="line"><span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>binder_alloc_mmap_handler()</code>申请了一块连续的内核虚拟内存，为每个页面申请了<code>struct binder_lru_page</code>。</p><p>TODO：这些数据结构有什么用？物理内存是什么时候申请的？</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><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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">binder_alloc_mmap_handler</span><span class="params">(<span class="keyword">struct</span> binder_alloc *alloc,</span></span><br><span class="line"><span class="params">      <span class="keyword">struct</span> vm_area_struct *vma)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="type">int</span> ret;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">vm_struct</span> *<span class="title">area</span>;</span></span><br><span class="line"><span class="type">const</span> <span class="type">char</span> *failure_string;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_buffer</span> *<span class="title">buffer</span>;</span></span><br><span class="line"></span><br><span class="line">mutex_lock(&amp;binder_alloc_mmap_lock);</span><br><span class="line"><span class="comment">// 保留一个连续的内核虚拟区域</span></span><br><span class="line">area = get_vm_area(vma-&gt;vm_end - vma-&gt;vm_start, VM_IOREMAP);</span><br><span class="line"><span class="comment">// 记录内核虚拟地址</span></span><br><span class="line">alloc-&gt;buffer = area-&gt;addr;</span><br><span class="line">alloc-&gt;user_buffer_offset = vma-&gt;vm_start - (<span class="type">uintptr_t</span>)alloc-&gt;buffer;</span><br><span class="line">mutex_unlock(&amp;binder_alloc_mmap_lock);</span><br><span class="line"><span class="comment">// 为每个页面申请一个结构体 struct binder_lru_page</span></span><br><span class="line">alloc-&gt;pages = kzalloc(<span class="keyword">sizeof</span>(alloc-&gt;pages[<span class="number">0</span>]) *</span><br><span class="line">((vma-&gt;vm_end - vma-&gt;vm_start) / PAGE_SIZE),</span><br><span class="line">GFP_KERNEL);</span><br><span class="line">alloc-&gt;buffer_size = vma-&gt;vm_end - vma-&gt;vm_start;</span><br><span class="line"><span class="comment">// binder transactions的缓冲区</span></span><br><span class="line">buffer = kzalloc(<span class="keyword">sizeof</span>(*buffer), GFP_KERNEL);</span><br><span class="line"></span><br><span class="line">buffer-&gt;data = alloc-&gt;buffer;</span><br><span class="line">list_add(&amp;buffer-&gt;entry, &amp;alloc-&gt;buffers);</span><br><span class="line">buffer-&gt;<span class="built_in">free</span> = <span class="number">1</span>;</span><br><span class="line">binder_insert_free_buffer(alloc, buffer);</span><br><span class="line">alloc-&gt;free_async_space = alloc-&gt;buffer_size / <span class="number">2</span>;</span><br><span class="line">barrier();</span><br><span class="line">alloc-&gt;vma = vma;</span><br><span class="line">alloc-&gt;vma_vm_mm = vma-&gt;vm_mm;</span><br><span class="line"><span class="comment">/* Same as mmgrab() in later kernel versions */</span></span><br><span class="line"><span class="type">atomic_inc</span>(&amp;alloc-&gt;vma_vm_mm-&gt;mm_count);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="ioctl函数"><a class="header-anchor" href="#ioctl函数">¶</a>ioctl函数</h2><p><code>binder_ioctl()</code>是binder驱动的关键，主要功能都是在这个函数实现。首先从<code>private_data</code>拿到<code>binder_proc</code>，然后获取<code>binder_thread</code>，这个结构体表示执行ioctl的线程。如果是第一次执行，<code>binder_get_thread()</code>会创建相关的结构体并初始化。进程所有的线程以红黑树的形式存储在<code>proc-&gt;threads</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="type">static</span> <span class="type">long</span> <span class="title function_">binder_ioctl</span><span class="params">(<span class="keyword">struct</span> file *filp, <span class="type">unsigned</span> <span class="type">int</span> cmd, <span class="type">unsigned</span> <span class="type">long</span> arg)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="type">int</span> ret;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_proc</span> *<span class="title">proc</span> =</span> filp-&gt;private_data;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_thread</span> *<span class="title">thread</span>;</span></span><br><span class="line">...</span><br><span class="line">thread = binder_get_thread(proc);</span><br></pre></td></tr></table></figure><p>然后就是一个大的<code>switch</code>语句，根据不同的<code>cmd</code>执行不同的函数。下表列出了所有<code>cmd</code>、对应的参数，以及功能。其中最关键的命令非<code>BINDER_WRITE_READ</code>莫属。</p><table><thead><tr><th>cmd</th><th>arg</th><th>说明</th></tr></thead><tbody><tr><td>BINDER_WRITE_READ</td><td>指向<code>struct binder_write_read</code>的指针。</td><td>使用函数<code>binder_ioctl_write_read()</code>处理。</td></tr><tr><td>BINDER_SET_MAX_THREADS</td><td>整数。</td><td>设置<code>proc-&gt;max_threads</code>。</td></tr><tr><td>BINDER_SET_CONTEXT_MGR</td><td>None</td><td><code>ServiceManager</code>进程的专属命令。</td></tr><tr><td>BINDER_THREAD_EXIT</td><td>None</td><td>从<code>proc-&gt;threads</code>中删除线程，释放相关资源</td></tr><tr><td>BINDER_VERSION</td><td>指向<code>struct binder_version</code>的指针</td><td>版本号存储在宏BINDER_CURRENT_PROTOCOL_VERSION</td></tr><tr><td>BINDER_GET_NODE_DEBUG_INFO</td><td>指向<code>struct binder_node_debug_info</code>的指针</td><td></td></tr></tbody></table><h3 id="BINDER-SET-CONTEXT-MGR"><a class="header-anchor" href="#BINDER-SET-CONTEXT-MGR">¶</a>BINDER_SET_CONTEXT_MGR</h3><p><code>ServiceManager</code>在执行完mmap()函数后，会调用此命令。这个命令由函数<code>binder_ioctl_set_ctx_mgr()</code>处理。首先检查调用的进程是否符合要求，然后新建一个<code>binder_node</code>，并保存到<code>context-&gt;binder_context_mgr_node</code>。</p><p><code>binder_context</code>与binder设备有关，保存在设备结构体<code>binder_device</code>中，主要保存了mgr有关的信息。<code>binder_node</code>与Service有关，每个node表示一个service。</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="type">static</span> <span class="type">int</span> <span class="title function_">binder_ioctl_set_ctx_mgr</span><span class="params">(<span class="keyword">struct</span> file *filp)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="type">int</span> ret = <span class="number">0</span>;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_proc</span> *<span class="title">proc</span> =</span> filp-&gt;private_data;</span><br><span class="line"><span class="comment">// open() 函数会为 proc-&gt;context赋值，最终指向 struct binder_device中的context</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_context</span> *<span class="title">context</span> =</span> proc-&gt;context;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_node</span> *<span class="title">new_node</span>;</span></span><br><span class="line"><span class="type">kuid_t</span> curr_euid = current_euid();</span><br><span class="line"></span><br><span class="line">mutex_lock(&amp;context-&gt;context_mgr_node_lock);</span><br><span class="line"><span class="comment">// 检查context mgr是否已经设置，检查uid / euid是否符号要求</span></span><br><span class="line">new_node = binder_new_node(proc, <span class="literal">NULL</span>); <span class="comment">// 每个node表示一个service</span></span><br><span class="line"></span><br><span class="line">binder_node_lock(new_node);</span><br><span class="line">new_node-&gt;local_weak_refs++;</span><br><span class="line">new_node-&gt;local_strong_refs++;</span><br><span class="line">new_node-&gt;has_strong_ref = <span class="number">1</span>;</span><br><span class="line">new_node-&gt;has_weak_ref = <span class="number">1</span>;</span><br><span class="line"><span class="comment">// 表示ServiceManager的node</span></span><br><span class="line">context-&gt;binder_context_mgr_node = new_node;</span><br><span class="line">binder_node_unlock(new_node);</span><br><span class="line">binder_put_node(new_node);</span><br><span class="line">out:</span><br><span class="line">mutex_unlock(&amp;context-&gt;context_mgr_node_lock);</span><br><span class="line"><span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="BINDER-WRITE-READ"><a class="header-anchor" href="#BINDER-WRITE-READ">¶</a>BINDER_WRITE_READ</h3><p><code>BINDER_WRITE_READ</code>命令通过函数<code>binder_ioctl_write_read()</code>来处理，前三个参数来自于<code>ioctl()</code>函数的参数，<code>thread</code>是<code>binder_get_thread(proc)</code>的返回值。函数开头，首先将参数复制到结构体<code>struct binder_write_read</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><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="class"><span class="keyword">struct</span> <span class="title">binder_write_read</span> &#123;</span></span><br><span class="line"><span class="type">binder_size_t</span>write_size;<span class="comment">/* 需要写入的字节数 */</span></span><br><span class="line"><span class="type">binder_size_t</span>write_consumed;<span class="comment">/* 驱动消耗的字节数 */</span></span><br><span class="line"><span class="type">binder_uintptr_t</span>write_buffer;<span class="comment">/* 用户空间指针，指向要写入的数据 */</span></span><br><span class="line"><span class="type">binder_size_t</span>read_size;<span class="comment">/* 需要读取的字节数 */</span></span><br><span class="line"><span class="type">binder_size_t</span>read_consumed;<span class="comment">/* 驱动消耗的字节数 */</span></span><br><span class="line"><span class="type">binder_uintptr_t</span>read_buffer;<span class="comment">/* 用户空间指针，读取的数据复制到此buffer */</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">binder_ioctl_write_read</span><span class="params">(<span class="keyword">struct</span> file *filp,</span></span><br><span class="line"><span class="params"><span class="type">unsigned</span> <span class="type">int</span> cmd, <span class="type">unsigned</span> <span class="type">long</span> arg,</span></span><br><span class="line"><span class="params"><span class="keyword">struct</span> binder_thread *thread)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_proc</span> *<span class="title">proc</span> =</span> filp-&gt;private_data;</span><br><span class="line"><span class="type">void</span> __user *ubuf = (<span class="type">void</span> __user *)arg;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_write_read</span> <span class="title">bwr</span>;</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">if</span> (copy_from_user(&amp;bwr, ubuf, <span class="keyword">sizeof</span>(bwr))) &#123;</span><br><span class="line">ret = -EFAULT;</span><br><span class="line"><span class="keyword">goto</span> out;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>然后根据<code>bwr.write_size</code>和<code>bwr.read_size</code>的值决定是否调用<code>binder_thread_write()</code>、<code>binder_thread_read()</code>做进一步处理。最后将修改后的bwr结构体复制到用户空间，因为driver会更改<code>bwr.write_consumed</code>和<code>bwr.read_consumed</code>。</p><figure class="highlight plaintext"><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">if (bwr.write_size &gt; 0) &#123;</span><br><span class="line">ret = binder_thread_write(proc, thread,</span><br><span class="line">  bwr.write_buffer, bwr.write_size,</span><br><span class="line">  &amp;bwr.write_consumed);</span><br><span class="line">...</span><br><span class="line">&#125;</span><br><span class="line">if (bwr.read_size &gt; 0) &#123;</span><br><span class="line">ret = binder_thread_read(proc, thread, bwr.read_buffer,</span><br><span class="line"> bwr.read_size, &amp;bwr.read_consumed,</span><br><span class="line"> filp-&gt;f_flags &amp; O_NONBLOCK);</span><br><span class="line">if (!binder_worklist_empty_ilocked(&amp;proc-&gt;todo))</span><br><span class="line">binder_wakeup_proc_ilocked(proc);</span><br><span class="line">...</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line">if (copy_to_user(ubuf, &amp;bwr, sizeof(bwr))) &#123;</span><br><span class="line">ret = -EFAULT;</span><br><span class="line">goto out;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="binder-thread-write"><a class="header-anchor" href="#binder-thread-write">¶</a>binder_thread_write()</h4><p>函数<code>binder_thread_write()</code>非常长，但好在结构比较清晰。指针<code>ptr</code>是一个用户空间指针，始终指向待处理的字节。不断的从<code>ptr</code>取出<code>cmd</code>，然后根据cmd进行不同的处理，最后更新<code>consumed</code>，直到buffer所有的数据处理完毕。也就是说，<code>bwr.write_buffer</code>由若干个cmd和紧随其后的可选固定长度参数构成，参数的长度根据cmd的不同而不同。</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></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">binder_thread_write</span><span class="params">(<span class="keyword">struct</span> binder_proc *proc,</span></span><br><span class="line"><span class="params"><span class="keyword">struct</span> binder_thread *thread,</span></span><br><span class="line"><span class="params"><span class="type">binder_uintptr_t</span> binder_buffer, <span class="type">size_t</span> size,</span></span><br><span class="line"><span class="params"><span class="type">binder_size_t</span> *consumed)</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="type">uint32_t</span> cmd;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_context</span> *<span class="title">context</span> =</span> proc-&gt;context;</span><br><span class="line"><span class="type">void</span> __user *buffer = (<span class="type">void</span> __user *)(<span class="type">uintptr_t</span>)binder_buffer;</span><br><span class="line"><span class="type">void</span> __user *ptr = buffer + *consumed;</span><br><span class="line"><span class="type">void</span> __user *end = buffer + size;</span><br><span class="line"><span class="keyword">while</span> (ptr &lt; end &amp;&amp; thread-&gt;return_error.cmd == BR_OK) &#123;</span><br><span class="line"><span class="type">int</span> ret;</span><br><span class="line"><span class="keyword">if</span> (get_user(cmd, (<span class="type">uint32_t</span> __user *)ptr))</span><br><span class="line"><span class="keyword">return</span> -EFAULT;</span><br><span class="line">ptr += <span class="keyword">sizeof</span>(<span class="type">uint32_t</span>);</span><br><span class="line">...</span><br><span class="line"><span class="keyword">switch</span> (cmd) &#123;</span><br><span class="line">...</span><br><span class="line">*consumed = ptr - buffer;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>下表列出了<code>binder_thread_write</code>支持的所有cmd。后面结合具体的使用场景，再来分析这些命令的作用。</p><table><thead><tr><th>binder command protocol</th><th>param</th><th>说明</th></tr></thead><tbody><tr><td>BC_INCREFS<br>BC_ACQUIRE<br> BC_RELEASE<br> BC_DECREFS</td><td>无符号32位整数，target</td><td>增加或减少引用计数</td></tr><tr><td>BC_INCREFS_DONE<br>BC_ACQUIRE_DONE</td><td>两个指针参数，依次是node_ptr和cookie</td><td></td></tr><tr><td>BC_ATTEMPT_ACQUIRE<br>BC_ACQUIRE_RESULT</td><td>None</td><td>这两个命令不支持</td></tr><tr><td>BC_FREE_BUFFER</td><td>一个指针参数，data_ptr</td><td></td></tr><tr><td>BC_TRANSACTION_SG<br>BC_REPLY_SG</td><td>有一个<code>struct binder_transaction_data_sg</code>参数</td><td></td></tr><tr><td>BC_TRANSACTION<br>BC_REPLY</td><td>有一个<code>struct binder_transaction_data</code>参数</td><td></td></tr><tr><td>BC_REGISTER_LOOPER</td><td>None</td><td></td></tr><tr><td>BC_ENTER_LOOPER</td><td>None</td><td></td></tr><tr><td>BC_EXIT_LOOPER</td><td>None</td><td></td></tr><tr><td>BC_REQUEST_DEATH_NOTIFICATION<br>BC_CLEAR_DEATH_NOTIFICATION</td><td>一个无符号32位整数，target；<br>一个指针参数，cookie</td><td></td></tr><tr><td>BC_DEAD_BINDER_DONE</td><td>一个指针参数，cookie</td><td></td></tr></tbody></table><p>TODO：详细理解每一个cmd的作用</p><h4 id="binder-thread-read"><a class="header-anchor" href="#binder-thread-read">¶</a>binder_thread_read()</h4><p><code>binder_thread_read()</code>函数比较长，不是很好理解。主要工作流程如下：</p><ol><li>进入阻塞状态，等待<code>binder_work</code></li><li>如果队列不为空，取出一个<code>binder_work</code></li><li>根据work的不同类型，进行不同的处理</li><li>给读取的线程回复一个BR命令，某些命令还会带一个参数。</li></ol><p>下面来分析实现代码。首先，设置线程的等待标志位。Binder同时支持阻塞和非阻塞模式，假设是阻塞模式，则调用<code>binder_wait_for_work()</code>等待一个<code>binder_work</code>的到来。<code>binder_wait_for_work()</code>基于Linux内核的等待队列实现，等待队列是<code>thread-&gt;wait</code>。如果当前线程已经有<code>binder_work</code>，则不会阻塞，直接返回。<code>binder_work</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></pre></td><td class="code"><pre><span class="line">thread-&gt;looper |= BINDER_LOOPER_STATE_WAITING;</span><br><span class="line"><span class="keyword">if</span> (non_block) &#123;</span><br><span class="line"><span class="keyword">if</span> (!binder_has_work(thread, wait_for_proc_work))</span><br><span class="line">ret = -EAGAIN;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">ret = binder_wait_for_work(thread, wait_for_proc_work);</span><br><span class="line">&#125;</span><br><span class="line">thread-&gt;looper &amp;= ~BINDER_LOOPER_STATE_WAITING;</span><br></pre></td></tr></table></figure><p>然后进入一个死循环，根据work的不同类型进行不同的处理，具体参考下方代码块的注释。处理完毕后给读取线程的缓冲区写入一个BR命令，部分BR命令还有一个参数。<code>BINDER_WORK_TRANSACTION</code>的处理过于复杂，所以放在了<code>switch</code>语句的后面处理。暂时先不具体分析每个<code>work type</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><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="keyword">while</span> (<span class="number">1</span>) &#123;</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line">w = binder_dequeue_work_head_ilocked(<span class="built_in">list</span>);</span><br><span class="line"><span class="keyword">switch</span> (w-&gt;type) &#123;</span><br><span class="line"><span class="keyword">case</span> BINDER_WORK_TRANSACTION: &#123; <span class="comment">// 处理`binder_transaction`结构体</span></span><br><span class="line">t = container_of(w, <span class="keyword">struct</span> binder_transaction, work);</span><br><span class="line">&#125; <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> BINDER_WORK_RETURN_ERROR: &#123; <span class="comment">// 直接在读取线程的缓冲区写入`e-&gt;cmd`</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_error</span> *<span class="title">e</span> =</span> container_of( w, <span class="keyword">struct</span> binder_error, work);</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125; <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> BINDER_WORK_TRANSACTION_COMPLETE: &#123;</span><br><span class="line"><span class="comment">// 直接在读取线程的缓冲区写入命令`BR_TRANSACTION_COMPLETE`</span></span><br><span class="line">cmd = BR_TRANSACTION_COMPLETE;</span><br><span class="line">put_user(cmd, (<span class="type">uint32_t</span> __user *)ptr)：</span><br><span class="line">ptr += <span class="keyword">sizeof</span>(<span class="type">uint32_t</span>);</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125; <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> BINDER_WORK_NODE: &#123; <span class="comment">// 处理`binder_node`结构体</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_node</span> *<span class="title">node</span> =</span> container_of(w, <span class="keyword">struct</span> binder_node, work);</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125; <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> BINDER_WORK_DEAD_BINDER:</span><br><span class="line"><span class="keyword">case</span> BINDER_WORK_DEAD_BINDER_AND_CLEAR:</span><br><span class="line"><span class="keyword">case</span> BINDER_WORK_CLEAR_DEATH_NOTIFICATION: &#123; <span class="comment">// 处理`binder_ref_death`结构体</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_ref_death</span> *<span class="title">death</span>;</span></span><br><span class="line">death = container_of(w, <span class="keyword">struct</span> binder_ref_death, work);</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line">&#125; <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (!t) <span class="keyword">continue</span>;</span><br><span class="line"><span class="comment">// 处理binder_transaction结构体</span></span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>BR命令的全程是<code>binder driver return protocol</code>，由binder driver发送给service进程。下表列出了所有的BR命令及其对应的参数。</p><table><thead><tr><th>binder return protocol</th><th>param</th><th>说明</th></tr></thead><tbody><tr><td>BR_OK</td><td>None</td><td></td></tr><tr><td>BR_TRANSACTION<br>BR_REPLY</td><td>struct binder_transaction_data</td><td>the received command.</td></tr><tr><td>BR_DEAD_REPLY</td><td>None</td><td>The target of the last transaction (either a bcTRANSACTION or a bcATTEMPT_ACQUIRE) is no longer with us.</td></tr><tr><td>BR_INCREFS<br>BR_ACQUIRE<br>BR_RELEASE<br>BR_DECREFS</td><td>struct binder_ptr_cookie</td><td>ptr to binder, cookie for binder</td></tr><tr><td>BR_NOOP</td><td>None</td><td>什么都不做，检查下一个命令。 它的存在主要是可以用 BR_SPAWN_LOOPER 命令替换它。</td></tr><tr><td>BR_SPAWN_LOOPER</td><td>None</td><td>驱动程序已确定进程没有线程等待为传入事务提供服务。 当一个进程接收到这个命令时，它必须产生一个新的服务线程并通过 bcENTER_LOOPER 注册它。</td></tr><tr><td>BR_DEAD_BINDER</td><td>binder_uintptr_t, cookie</td><td></td></tr><tr><td>BR_CLEAR_DEATH_NOTIFICATION_DONE</td><td>binder_uintptr_t, cookie</td><td></td></tr><tr><td>BR_FAILED_REPLY</td><td>None</td><td>最后一个事务（bcTRANSACTION 或 bcATTEMPT_ACQUIRE）失败（例如内存不足）。</td></tr></tbody></table><h2 id="Binder协议"><a class="header-anchor" href="#Binder协议">¶</a>Binder协议</h2><p>之前的分析，更多是语法层面的分析，还没有深入理解分析Binder的数据结构。所以，接下来根据具体的通信实例，来深入理解Binder协议，理解BC、BR命令及其参数的含义。</p><h3 id="注册Service"><a class="header-anchor" href="#注册Service">¶</a>注册Service</h3><p><a href="#binder%E8%AE%BE%E5%A4%87%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95">binder设备使用方法</a>提到过，进程或线程可以向ServiceManager注册成为Service。所以现在来看看注册为Service需要发送什么样的数据。下面的代码片段来自<code>MediaPlayerService.cpp</code>，<code>defaultServiceManager()</code>会获取一个handle为0的<code>BpBinder</code>对象，并以此创建出一个<code>BpServiceManager</code>对象，BpBinder对象保存在<code>mRemote</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">// frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">MediaPlayerService::instantiate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">defaultServiceManager</span>()-&gt;<span class="built_in">addService</span>(</span><br><span class="line">            <span class="built_in">String16</span>(<span class="string">&quot;media.player&quot;</span>), <span class="keyword">new</span> <span class="built_in">MediaPlayerService</span>());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面是<code>BpServiceManager::addService()</code>的实现。首先使用<code>Parcel</code>打包数据。<code>Parcel</code>是一种数据容器，提供了非常多的接口来打包各种类型的数据，打包和解包需要使用对应的接口即可。然后调用<code>remote()-&gt;transact()</code>发送并接收返回的消息。<code>remote()</code>返回<code>mRemote</code>，而<code>mRemote</code>是handle等于0的<code>BpBinder</code>对象，所以<code>remote()-&gt;transact()</code>实际上是将消息发送给ServiceManager。</p><figure class="highlight cpp"><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="comment">// frameworks/native/libs/binder/IServiceManager.cpp</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BpServiceManager</span> : <span class="keyword">public</span> BpInterface&lt;IServiceManager&gt; &#123;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="type">status_t</span> <span class="title">addService</span><span class="params">(<span class="type">const</span> String16&amp; name, <span class="type">const</span> sp&lt;IBinder&gt;&amp; service,</span></span></span><br><span class="line"><span class="params"><span class="function">                                <span class="type">bool</span> allowIsolated, <span class="type">int</span> dumpsysPriority)</span> </span>&#123;</span><br><span class="line">        Parcel data, reply;</span><br><span class="line">        data.<span class="built_in">writeInterfaceToken</span>(IServiceManager::<span class="built_in">getInterfaceDescriptor</span>());</span><br><span class="line">        data.<span class="built_in">writeString16</span>(name);</span><br><span class="line">        data.<span class="built_in">writeStrongBinder</span>(service); <span class="comment">// 对象类型是BINDER_TYPE_HANDLE</span></span><br><span class="line">        data.<span class="built_in">writeInt32</span>(allowIsolated ? <span class="number">1</span> : <span class="number">0</span>); <span class="comment">// allowIsolated 默认等于 false</span></span><br><span class="line">        data.<span class="built_in">writeInt32</span>(dumpsysPriority);       <span class="comment">// dumpsysPriority 默认等于 DUMP_FLAG_PRIORITY_DEFAULT</span></span><br><span class="line">        <span class="type">status_t</span> err = <span class="built_in">remote</span>()-&gt;<span class="built_in">transact</span>(ADD_SERVICE_TRANSACTION, data, &amp;reply);</span><br><span class="line">        <span class="keyword">return</span> err == NO_ERROR ? reply.<span class="built_in">readExceptionCode</span>() : err;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>BpBinder::transact()</code>实际上会借助<code>IPCThreadState::transact()</code>来完成实际的发送动作，<code>IPCThreadState</code>对象每个线程有且只有一个。下面是函数的具体实现，<code>handle=0</code>，<code>code=ADD_SERVICE_TRANSACTION</code>，<code>data</code>在<code>BpServiceManager::addService()</code>函数中写入了一些对象，<code>flags</code>是默认值0，<code>reply</code>是ServiceManager返回的值。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// frameworks/native/libs/binder/IPCThreadState.cpp, flags 默认为0</span></span><br><span class="line"><span class="comment">// 仅保留主要代码，删除了log和错误处理</span></span><br><span class="line"><span class="function"><span class="type">status_t</span> <span class="title">IPCThreadState::transact</span><span class="params">(<span class="type">int32_t</span> handle,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  <span class="type">uint32_t</span> code, <span class="type">const</span> Parcel&amp; data,</span></span></span><br><span class="line"><span class="params"><span class="function">                                  Parcel* reply, <span class="type">uint32_t</span> flags)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="type">status_t</span> err;</span><br><span class="line">    flags |= TF_ACCEPT_FDS;</span><br><span class="line">    err = <span class="built_in">writeTransactionData</span>(BC_TRANSACTION, flags, handle, code, data, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> ((flags &amp; TF_ONE_WAY) == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (reply) &#123;</span><br><span class="line">            err = <span class="built_in">waitForResponse</span>(reply);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            Parcel fakeReply;</span><br><span class="line">            err = <span class="built_in">waitForResponse</span>(&amp;fakeReply);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        err = <span class="built_in">waitForResponse</span>(<span class="literal">NULL</span>, <span class="literal">NULL</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> err;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>writeTransactionData()</code>负责将待发送数据的相关信息组合成<code>binder_transaction_data</code>结构，然后将<code>cmd</code>和<code>binder_transaction_data</code>结构写入到<code>mOut</code>对象，这也是一个<code>Parcel</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><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="type">status_t</span> <span class="title">IPCThreadState::writeTransactionData</span><span class="params">(<span class="type">int32_t</span> cmd, <span class="type">uint32_t</span> binderFlags,</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="type">int32_t</span> handle, <span class="type">uint32_t</span> code, <span class="type">const</span> Parcel&amp; data, <span class="type">status_t</span>* statusBuffer)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    binder_transaction_data tr;</span><br><span class="line">    tr.target.ptr = <span class="number">0</span>; <span class="comment">/* Don&#x27;t pass uninitialized stack data to a remote process */</span></span><br><span class="line">    tr.target.handle = handle;  <span class="comment">// 等于0，代表ServiceManager</span></span><br><span class="line">    tr.code = code;             <span class="comment">// ADD_SERVICE_TRANSACTION</span></span><br><span class="line">    tr.flags = binderFlags;</span><br><span class="line">    tr.cookie = <span class="number">0</span>;</span><br><span class="line">    tr.sender_pid = <span class="number">0</span>;</span><br><span class="line">    tr.sender_euid = <span class="number">0</span>;</span><br><span class="line">    tr.data_size = data.<span class="built_in">ipcDataSize</span>();    <span class="comment">// 待发送数据的大小</span></span><br><span class="line">    tr.data.ptr.buffer = data.<span class="built_in">ipcData</span>();  <span class="comment">// 指向待发送数据的首地址</span></span><br><span class="line">    <span class="comment">// 待发送数据中对象的数量</span></span><br><span class="line">    tr.offsets_size = data.<span class="built_in">ipcObjectsCount</span>()*<span class="built_in">sizeof</span>(<span class="type">binder_size_t</span>);</span><br><span class="line">    tr.data.ptr.offsets = data.<span class="built_in">ipcObjects</span>(); <span class="comment">// 对象相对tr.data.ptr.buffer的偏移</span></span><br><span class="line"></span><br><span class="line">    mOut.<span class="built_in">writeInt32</span>(cmd);</span><br><span class="line">    mOut.<span class="built_in">write</span>(&amp;tr, <span class="built_in">sizeof</span>(tr));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> NO_ERROR;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>IPCThreadState::waitForResponse()</code>主要完成以下两个工作：</p><ol><li>调用<code>talkWithDriver()</code>，将<code>mOut</code>发送到driver，然后等待driver回复的信息并写入到<code>mIn</code></li><li>根据<code>mIn</code>中的数据，进行不同的处理</li></ol><p>由于现在还不知道driver会返回什么数据，所以这个函数先不分析。先来看看<code>talkWithDriver()</code>。首先判断<code>mProcess</code>对象的文件描述符是否大于0，然后根据<code>mIn</code>和<code>mOut</code>来构建<code>binder_write_read</code>结构体，最后调用ioctl的<code>BINDER_WRITE_READ</code>命令将数据发送到driver。不妨回头看看前面对<a href="#BINDER_WRITE_READ">BINDER_WRITE_READ</a>的分析，两个地方对上了。</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><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">status_t</span> <span class="title">IPCThreadState::talkWithDriver</span><span class="params">(<span class="type">bool</span> doReceive)</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">    binder_write_read bwr;</span><br><span class="line">    <span class="comment">// Is the read buffer empty?</span></span><br><span class="line">    <span class="type">const</span> <span class="type">bool</span> needRead = mIn.<span class="built_in">dataPosition</span>() &gt;= mIn.<span class="built_in">dataSize</span>();</span><br><span class="line">    <span class="comment">// We don&#x27;t want to write anything if we are still reading</span></span><br><span class="line">    <span class="comment">// from data left in the input buffer and the caller</span></span><br><span class="line">    <span class="comment">// has requested to read the next data.</span></span><br><span class="line">    <span class="type">const</span> <span class="type">size_t</span> outAvail = (!doReceive || needRead) ? mOut.<span class="built_in">dataSize</span>() : <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    bwr.write_size = outAvail;</span><br><span class="line">    bwr.write_buffer = (<span class="type">uintptr_t</span>)mOut.<span class="built_in">data</span>();</span><br><span class="line">    <span class="comment">// This is what we&#x27;ll read.</span></span><br><span class="line">    <span class="keyword">if</span> (doReceive &amp;&amp; needRead) &#123;</span><br><span class="line">        bwr.read_size = mIn.<span class="built_in">dataCapacity</span>();</span><br><span class="line">        bwr.read_buffer = (<span class="type">uintptr_t</span>)mIn.<span class="built_in">data</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        bwr.read_size = <span class="number">0</span>;</span><br><span class="line">        bwr.read_buffer = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// Return immediately if there is nothing to do.</span></span><br><span class="line">    <span class="keyword">if</span> ((bwr.write_size == <span class="number">0</span>) &amp;&amp; (bwr.read_size == <span class="number">0</span>)) <span class="keyword">return</span> NO_ERROR;</span><br><span class="line"></span><br><span class="line">    bwr.write_consumed = <span class="number">0</span>;</span><br><span class="line">    bwr.read_consumed = <span class="number">0</span>;</span><br><span class="line">    <span class="type">status_t</span> err;</span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">ioctl</span>(mProcess-&gt;mDriverFD, BINDER_WRITE_READ, &amp;bwr) &gt;= <span class="number">0</span>)</span><br><span class="line">            err = NO_ERROR;</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            err = -errno;</span><br><span class="line">    &#125; <span class="keyword">while</span> (err == -EINTR);</span><br><span class="line">    <span class="comment">// 一些错误处理，略</span></span><br><span class="line">    <span class="keyword">return</span> err;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下图展示了从<code>addService()</code>到<code>talkWithDriver()</code>是如何对数据封装的，箭头表示指针指向对应的数据结构。前面提到过，不同的BC指令会带有不同的参数，<code>BC_TRANSACTION</code>的参数是一个<code>binder_transaction_data</code>结构体，这个结构体表明了要发送的数据、目标以及如何处理数据。</p><p><img src="https://image.pkemb.com/image/202204162039111.png" alt=""></p><p>driver使用函数<code>binder_transaction()</code>来处理<code>BC_TRANSACTION</code>，这个函数非常的长（600行左右），并且和<code>BC_REPLAY</code>共用一个函数，所以分析起来有点困难。当<code>replay=0</code>是，函数的主要流程如下：</p><ol><li>根据target.handle找到target_node / target_proc</li><li>构建结构体 binder_transaction</li><li>依次取出<code>tr-&gt;data.buffer</code>中的binder对象，并根据不同的对象类型进行不同的处理，并处理结果附加到binder_transaction</li><li>t-&gt;work.type = BINDER_WORK_TRANSACTION</li><li>调用<code>binder_proc_transaction()</code>，将<code>t-&gt;work</code>附加到<code>target_proc-&gt;todo</code>队列，然后唤醒target_proc。</li></ol><p><code>addService()</code>写入了一个类型为<code>BINDER_TYPE_HANDLE</code>的binder对象，其存储了即将注册的service的handle值。<code>binder_translate_handle()</code>的工作是获取注册service的<code>binder_node</code>，并增加引用。现在，这里的<code>target_proc</code>是<code>service manager</code>，唤醒目标进程后，<code>binder_thread_read()</code>会继续运行。注意这里的work type是<code>BINDER_WORK_TRANSACTION</code>。</p><p>TODO：binder_node是什么时候在driver注册的</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><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></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">binder_transaction</span><span class="params">(</span></span><br><span class="line"><span class="params"><span class="keyword">struct</span> binder_proc *proc,</span></span><br><span class="line"><span class="params"><span class="keyword">struct</span> binder_thread *thread,       <span class="comment">// 想注册成为Service的线程</span></span></span><br><span class="line"><span class="params"><span class="keyword">struct</span> binder_transaction_data *tr, <span class="comment">// 在函数writeTransactionData()构建的结构体</span></span></span><br><span class="line"><span class="params"><span class="type">int</span> reply,                          <span class="comment">// 0, replay = cmd == BC_REPLY</span></span></span><br><span class="line"><span class="params"><span class="type">binder_size_t</span> extra_buffers_size)</span>   <span class="comment">// 0</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span> (reply) &#123;  <span class="comment">// 处理BC_REPLAY</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;      <span class="comment">// 处理BC_TRANSACTION</span></span><br><span class="line"><span class="comment">// 找到 target_node / target_proc</span></span><br><span class="line"><span class="keyword">if</span> (tr-&gt;target.handle) &#123; <span class="comment">// 目标进程不是ServiceManager</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">target_node = context-&gt;binder_context_mgr_node;</span><br><span class="line">target_node = binder_get_node_refs_for_txn(target_node, &amp;target_proc, &amp;return_error);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 构建 binder_transaction</span></span><br><span class="line">t-&gt;code = tr-&gt;code;   <span class="comment">// ADD_SERVICE_TRANSACTION</span></span><br><span class="line">t-&gt;buffer-&gt;target_node = target_node;</span><br><span class="line"><span class="keyword">for</span> (; offp &lt; off_end; offp++) &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_object_header</span> *<span class="title">hdr</span>;</span></span><br><span class="line">hdr = (<span class="keyword">struct</span> binder_object_header *)(t-&gt;buffer-&gt;data + *offp);</span><br><span class="line"><span class="keyword">switch</span> (hdr-&gt;type) &#123;</span><br><span class="line"><span class="comment">// 根据不同的类型进行不同的处理</span></span><br><span class="line"><span class="keyword">case</span> BINDER_TYPE_HANDLE: &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">flat_binder_object</span> *<span class="title">fp</span>;</span></span><br><span class="line">fp = to_flat_binder_object(hdr);</span><br><span class="line">ret = binder_translate_handle(fp, t, thread);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">t-&gt;work.type = BINDER_WORK_TRANSACTION;</span><br><span class="line"><span class="keyword">if</span> (reply) &#123;  <span class="comment">// 处理BC_REPLAY</span></span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (!(t-&gt;flags &amp; TF_ONE_WAY)) &#123;</span><br><span class="line">binder_proc_transaction(t, target_proc, target_thread);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">binder_proc_transaction(t, target_proc, <span class="literal">NULL</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从前面<a href="#binder_thread_read">binder_thread_read()</a>的分析看，<code>BINDER_WORK_TRANSACTION</code>的处理在<code>binder_thread_read()</code>函数的最后面，其主要工作是根据<code>binder_transaction</code>结构体构造<code>binder_transaction_data</code>结构体，然后写回到用户空间的buffer。注意这里的命令是<code>BR_TRANSACTION</code>，<code>tr.code</code>是<code>ADD_SERVICE_TRANSACTION</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><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="keyword">while</span> (<span class="number">1</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> (t-&gt;buffer-&gt;target_node) &#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">binder_node</span> *<span class="title">target_node</span> =</span> t-&gt;buffer-&gt;target_node;</span><br><span class="line">tr.target.ptr = target_node-&gt;ptr;</span><br><span class="line">tr.cookie =  target_node-&gt;cookie;</span><br><span class="line">cmd = BR_TRANSACTION;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line">tr.code = t-&gt;code;   <span class="comment">// ADD_SERVICE_TRANSACTION</span></span><br><span class="line">tr.flags = t-&gt;flags;</span><br><span class="line"></span><br><span class="line">tr.data_size = t-&gt;buffer-&gt;data_size;</span><br><span class="line">tr.offsets_size = t-&gt;buffer-&gt;offsets_size;</span><br><span class="line">tr.data.ptr.buffer = (<span class="type">binder_uintptr_t</span>)</span><br><span class="line">((<span class="type">uintptr_t</span>)t-&gt;buffer-&gt;data +</span><br><span class="line">binder_alloc_get_user_buffer_offset(&amp;proc-&gt;alloc));</span><br><span class="line">tr.data.ptr.offsets = tr.data.ptr.buffer +</span><br><span class="line">ALIGN(t-&gt;buffer-&gt;data_size, <span class="keyword">sizeof</span>(<span class="type">void</span> *));</span><br><span class="line">put_user(cmd, (<span class="type">uint32_t</span> __user *)ptr);</span><br><span class="line">ptr += <span class="keyword">sizeof</span>(<span class="type">uint32_t</span>);</span><br><span class="line">copy_to_user(ptr, &amp;tr, <span class="keyword">sizeof</span>(tr));</span><br><span class="line">ptr += <span class="keyword">sizeof</span>(tr);</span><br><span class="line"><span class="comment">//...</span></span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后看service manager对消息的处理，历经千难万险，SM终于拿到了消息，并完成了添加service的动作。</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">svcmgr_handler</span><span class="params">(<span class="keyword">struct</span> binder_state *bs,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="keyword">struct</span> binder_transaction_data *txn,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="keyword">struct</span> binder_io *msg,</span></span></span><br><span class="line"><span class="params"><span class="function">                   <span class="keyword">struct</span> binder_io *reply)</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">switch</span>(txn-&gt;code) &#123;</span><br><span class="line"><span class="keyword">case</span> SVC_MGR_ADD_SERVICE:</span><br><span class="line">s = <span class="built_in">bio_get_string16</span>(msg, &amp;len);</span><br><span class="line"><span class="keyword">if</span> (s == <span class="literal">NULL</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">&#125;</span><br><span class="line">handle = <span class="built_in">bio_get_ref</span>(msg);</span><br><span class="line">allow_isolated = <span class="built_in">bio_get_uint32</span>(msg) ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line">dumpsys_priority = <span class="built_in">bio_get_uint32</span>(msg);</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">do_add_service</span>(bs, s, len, handle, txn-&gt;sender_euid, allow_isolated, dumpsys_priority,</span><br><span class="line">txn-&gt;sender_pid))</span><br><span class="line"><span class="keyword">return</span> <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">&#125;</span><br></pre></td></tr></table></figure><h3 id="获取service"><a class="header-anchor" href="#获取service">¶</a>获取service</h3><p>TODO</p><h2 id="参考资料"><a class="header-anchor" href="#参考资料">¶</a>参考资料</h2><ul><li><a href="http://gityuan.com/2015/11/01/binder-driver/">http://gityuan.com/2015/11/01/binder-driver/</a></li><li><a href="http://gityuan.com/2015/11/02/binder-driver-2/">http://gityuan.com/2015/11/02/binder-driver-2/</a></li><li><a href="http://gityuan.com/2015/11/07/binder-start-sm/">http://gityuan.com/2015/11/07/binder-start-sm/</a></li></ul>]]>
    </content>
    <id>https://pkemb.com/2022/04/binder-driver/</id>
    <link href="https://pkemb.com/2022/04/binder-driver/"/>
    <published>2022-04-07T22:49:11.000Z</published>
    <summary>
      <![CDATA[<p>Binder驱动是Binder IPC的基石，学习了解Binder驱动的实现，有助于深入理解Binder。以下基于<code>Orangepi3lts</code>提供的源代码，分析Binder驱动的实现。</p>]]>
    </summary>
    <title>Binder驱动源码阅读</title>
    <updated>2022-07-17T18:32:30.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>pkemb</name>
    </author>
    <category term="android" scheme="https://pkemb.com/categories/android/"/>
    <category term="orangepi3lts" scheme="https://pkemb.com/categories/android/orangepi3lts/"/>
    <content>
      <![CDATA[<p><code>Orange Pi</code>提供了芯片厂商释放的原始<code>H6 Android9.0 SDK</code>，但没有提供<code>Orange Pi3 LTS</code>的适配代码，所以原始SDK编译出来的镜像不能在<code>Orange Pi3 LTS</code>上正常启动。本文来教你如何突破这一限制。</p><span id="more"></span><h2 id="背景"><a class="header-anchor" href="#背景">¶</a>背景</h2><p>前段时间决定学习Android系统，这就肯定需要一块开发板么，纸上谈兵是不行的。经过一番搜索，要么适配的Android系统版本太低（5或6），要么价格太贵了。经过艰苦的搜索，发现了<code>Orange Pi3 LST</code>这么一款开发板，适配Android9.0，官方店铺价格<code>￥229</code>加8元运费，价格亲民，<s>吃灰也不心疼</s>。马上下单！</p><p><a href="http://www.orangepi.cn/downloadresourcescn/">Orange Pi官网</a>提供了相关资料的百度云下载地址，在经历几天几夜的挂机下载后，终于将需要的文件都下载下来了。打开用户手册<code>OrangePi_3_LTS_H6_用户手册_v1.8.pdf</code>，通读一遍。在接近结束的时候，发现了下面一段话。真的是吐血的心都有了，只提供芯片原厂释放的原始SDK！！！不提供适配代码！！！</p><p><img src="https://image.pkemb.com/image/202203212150948.png" alt=""></p><p>不撞南墙不回头，按照用户手册的指导搭建好编译环境，经过一个下午的漫长等待，终于编译好了原始Android SDK。烧录到TF卡，上电，直接GG，SDRAM初始化失败。想到只有一个编译好的Android img，不能自己编译，万一耽误以后学习咋办，这个问题还是要尝试解决的。</p><figure class="highlight plaintext"><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">[218]HELLO! BOOT0 is starting!</span><br><span class="line">[222]boot0 commit : de6e50bf43bb48975c66a88b58eef6a7460bf0ef</span><br><span class="line"></span><br><span class="line">[241]set pll start</span><br><span class="line">[244]set pll end</span><br><span class="line">......</span><br><span class="line">[585]MP_PGSR0 IS = 0040005d</span><br><span class="line">[588]BYTE0 GATE ERRO IS = 00000003</span><br><span class="line">[591]BYTE1 GATE ERRO IS = 00000003</span><br><span class="line">[595]BYTE2 GATE ERRO IS = 00000003</span><br><span class="line">[598]BYTE3 GATE ERRO IS = 00000003</span><br><span class="line">[602]scan dram rank&amp;width fail !</span><br><span class="line">[605]initializing SDRAM Fail.</span><br></pre></td></tr></table></figure><h2 id="准备"><a class="header-anchor" href="#准备">¶</a>准备</h2><p>在解决开机失败问题之前，一定要对开机流程有一个大致的了解。经过简单的百度，发现了一篇博客：<a href="https://blog.csdn.net/wlwl0071986/article/details/47207809">全志平台linux启动流程分析</a>。简单总结一下，就是<code>Boot ROM</code> -&gt; <code>Boot0</code> -&gt; <code>Boot1</code> -&gt; <code>U-Boot</code> &gt; <code>kernel</code>。从正常的开机log看，基本就是这个流程。所以，原始Android SDK在<code>Boot0</code>就启动失败了，问题点定位到了。</p><p>同时，还需要拿到全志H6的芯片资料，这个可以在<a href="https://linux-sunxi.org/H6">linux-sunxi.org/H6</a>获取，这里有数据手册、用户手册、原始SDK。</p><h2 id="初步分析"><a class="header-anchor" href="#初步分析">¶</a>初步分析</h2><p>Android相关的代码，<code>OrangePi</code>提供了两个压缩包，<code>android.tar.gz</code>和<code>lichee.tar.gz</code>，boot0相关的内容应该是在<code>lichee.tar.gz</code>。经过一番搜索，发现了<code>brandy/basic_loader/boot0</code>目录，这里存储了<code>boot0</code>相关的代码。但是，没有H6的<code>boot0</code>！！！H6是A53内核，有make_a50、make_a67，就是没有make_a53。用grep搜索H6的芯片代码<code>sun50iw6p1</code>，也没有找到任何内容。</p><p>通过以上分析，可以得出结论，<code>OrangePi</code>没有提供Android系统的boot0源代码，而是使用了预编译文件。</p><p>另一边，按照用户手册的指导，为<code>OrangePi3 LTS</code>编译Linux系统，烧录到TF卡之后可以正常启动。也就是说，<code>OrangePi</code>为Linux系统提供了一份可以正常启动的boot0。经过一番搜索，在<code>orangepi-build/u-boot/v2014.07-sun50iw6-linux4.9/sunxi_spl/boot0</code>发现了boot0的源代码和编译好的<code>boot0_sdcard.bin</code>。</p><h2 id="找出预编译的boot0"><a class="header-anchor" href="#找出预编译的boot0">¶</a>找出预编译的boot0</h2><p>前面分析到，Android系统的boot0不是编译出来的，而是使用的预编译文件。关键点要找到这个文件。以下是官方用户手册给出的Android系统编译命令。显然，最后一步的pack命令用来收集所有编译好的文件，打包成img文件。所以要分析pack命令。</p><figure class="highlight shell"><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">test@test:~$ cd android</span><br><span class="line">test@test:~/android$ source build/envsetup.sh</span><br><span class="line">test@test:~/android$ lunch petrel_fvd_p1-eng</span><br><span class="line">test@test:~/android$ extract-bsp</span><br><span class="line">test@test:~/android$ make -j8</span><br><span class="line">test@test:~/android$ pack</span><br></pre></td></tr></table></figure><p><code>pack</code>命令是<code>source build/envsetup.sh</code>注册的一个shell函数，可以用命令<code>type pack</code>看到源码。经过追踪，pack命令最终会调用<code>lichee/tools/pack/pack</code>脚本。经过分析pack脚本，在函数<code>do_finish()</code>中，会调用命令<code>dragon image.cfg  sys_partition.fex</code>来打包、生成最终的img文件。image.cfg，看文件名就知道是最终img文件的配置文件。经过find搜索，image.cfg在l<code>ichee/tools/pack/out/image.cfg</code>。打开这个文件，发现如下内容，刚好out目录下面有一个boot0_sdcard.fex文件。</p><figure class="highlight plaintext"><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">&#123;filename = &quot;boot0_nand.fex&quot;,   maintype = ITEM_BOOT,         subtype = &quot;BOOT0_0000000000&quot;,&#125;,</span><br><span class="line">&#123;filename = &quot;boot0_sdcard.fex&quot;, maintype = &quot;12345678&quot;,        subtype = &quot;1234567890BOOT_0&quot;,&#125;,</span><br></pre></td></tr></table></figure><p><code>lichee</code>目录是<code>lichee.tar.gz</code>解压出来的，这个压缩包里面没有<code>tools/pack/out</code>目录，说明out目录是编译时某个命令产生的。用grep搜索<code>boot0_sdcard.fex</code>，找到如下关键内容。所以关键点还是在pack脚本。</p><figure class="highlight shell"><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">pack/pack:171:$&#123;RES_DIR&#125;/$&#123;LICHEE_BIN_PATH&#125;/boot0_sdcard_$&#123;PACK_CHIP&#125;.bin:out/boot0_sdcard.fex</span><br><span class="line">pack/pack:311:          mv out/boot0_sdcard-$&#123;OTA_TEST_NAME&#125;.fex        out/boot0_sdcard.fex</span><br><span class="line">pack/pack:517:        programmer_img boot0_sdcard.fex boot_package.fex $&#123;out_img&#125; &gt; /dev/null</span><br><span class="line">pack/pack:710:  update_boot0 boot0_sdcard.fex   sys_config.bin SDMMC_CARD &gt; /dev/null</span><br></pre></td></tr></table></figure><p>进入脚本一顿搜索分析，找到如下关键信息。<code>boot0_sdcard.fex</code>来源于<code>${RES_DIR}/${LICHEE_BIN_PATH}/boot0_sdcard_${PACK_CHIP}.bin</code>，命令<code>update_boot0</code>可能会修改文件<code>boot0_sdcard.fex</code>。</p><figure class="highlight shell"><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">boot_file_list=(</span><br><span class="line"><span class="meta prompt_">$</span><span class="language-bash">&#123;RES_DIR&#125;/<span class="variable">$&#123;LICHEE_BIN_PATH&#125;</span>/boot0_nand_<span class="variable">$&#123;PACK_CHIP&#125;</span>.bin:out/boot0_nand.fex</span></span><br><span class="line"><span class="meta prompt_">$</span><span class="language-bash">&#123;RES_DIR&#125;/<span class="variable">$&#123;LICHEE_BIN_PATH&#125;</span>/boot0_sdcard_<span class="variable">$&#123;PACK_CHIP&#125;</span>.bin:out/boot0_sdcard.fex</span></span><br><span class="line">......</span><br><span class="line">    printf &quot;copying boot file\n&quot;</span><br><span class="line">    for file in $&#123;boot_file_list[@]&#125; ; do</span><br><span class="line">        cp -f $(echo $file | sed -e &#x27;s/:/ /g&#x27;) 2&gt;/dev/null</span><br><span class="line">    done</span><br><span class="line">......</span><br><span class="line">    # Those files for Nand or Card</span><br><span class="line">    update_boot0 boot0_nand.fex sys_config.bin NAND &gt; /dev/null</span><br><span class="line">    update_boot0 boot0_sdcard.fex   sys_config.bin SDMMC_CARD &gt; /dev/null</span><br></pre></td></tr></table></figure><p>使用命令<code>find -name boot0_sdcard_*.bin</code>找到文件<code>./pack/chips/sun50iw6p1/bin/boot0_sdcard_sun50iw6p1.bin</code>（还有很多给其他IC用的，这里忽略了）。二进制对比文件<code>boot0_sdcard_sun50iw6p1.bin</code>和<code>boot0_sdcard.fex</code>，确实存在一些差异，确认了<code>update_boot0</code>确实会修改文件<code>boot0_sdcard.fex</code>。</p><h2 id="尝试1：Android使用Linux-boot0"><a class="header-anchor" href="#尝试1：Android使用Linux-boot0">¶</a>尝试1：Android使用Linux boot0</h2><p>前面提到，自己编译的Linux系统可以完美启动。我觉得在<code>boot loader</code>阶段，Android和Linux其实是没有什么区别的。所以尝试使用Linux boot0来启动Android。在boot0的main()函数加了几句log，用来确认是否编译进去。然后重新编译。</p><p>前面分析出来，Android系统的boot0存储在<code>lichee/tools/pack/chips/sun50iw6p1/bin/boot0_sdcard_sun50iw6p1.bin</code>。备份Android原始的boot0，复制Linux的boot0并重命名，最后重新执行pack命令生成新的img文件。</p><p>烧录新的img，上电启动。系统是起来了，但是boot0加的log没有出来。一番查看，原来是从EMMC启动了，也就是说跳过了TF卡中的系统。看来Linux编译出来的boot0，可能在Android系统无法通过某种校验，然后就从EMMC启动了。</p><p>综上，本次尝试失败，两个系统的boot0无法通用。</p><h2 id="尝试2：从官方Android-img提取boot0文件"><a class="header-anchor" href="#尝试2：从官方Android-img提取boot0文件">¶</a>尝试2：从官方Android img提取boot0文件</h2><p><code>OrangePi</code>有提供一个Android镜像文件，可以正常启动。既然img文件是由若干个文件打包而成，那么一定可以解包。但问题就在这，img文件是通过<code>dragon</code>命令打包的，<code>dragon</code>也只提供了编译好的二进制文件，没有提供源代码。所以无法从打包的方向获取img文件的内部结构。</p><p>使用file命令查看img文件，返回data，没有更多的信息。看来file命令也无法识别其具体的文件类型。</p><p>用二进制查看器打开img文件，发现文件头有一个魔法字符串<code>IMAGEWTY</code>，Google这个字符串，发现了如下两个非常有用的网址。</p><ul><li><a href="https://stackoverflow.com/questions/48872746/what-is-an-imagewty-firmware-format">https://stackoverflow.com/questions/48872746/what-is-an-imagewty-firmware-format</a></li><li><a href="https://github.com/Ithamar/awutils">https://github.com/Ithamar/awutils</a></li></ul><p><img src="https://image.pkemb.com/image/202203212247584.png" alt=""></p><p><code>awutils</code>提供了一个叫做<code>awimage</code>的工具，可以用来解压img文件。clone到本地，编译成可执行文件，尝试解包，成了。解出来的文件如下图所示。但是解包之后这么多文件，哪一个才是<code>boot0_sdcard.fex</code>呢？<code>dragon</code>命令有一个参数是<code>image.cfg</code>，再去碰碰运气。在<code>boot0_sdcard.fex</code>那一行，可以看到<code>maintype=&quot;12345678&quot;</code>，<code>subtype=&quot;1234567890BOOT_0&quot;</code>。好巧，解包之后存在一个名为<code>12345678_1234567890BOOT_0</code>的文件，且文件的大小也可以对应上，就是它了。</p><p><img src="https://image.pkemb.com/image/202203212248062.png" alt=""></p><p>之前提到过，pack脚本将<code>boot0_sdcard_sun50iw6p1.bin</code>复制为<code>boot0_sdcard.fex</code>后，还用<code>update_boot0</code>命令修改了<code>boot0_sdcard.fex</code>，最终打包到img文件的是<code>update_boot0</code>修改之后的文件。所以提取出来的文件也是<code>update_boot0</code>修改之后的。故要在<code>dragon</code>命令打包的前一刻，将提取出来的文件替换到<code>boot0_sdcard.fex</code>。所以对pack脚本做了如下修改，打包之前复制<code>12345678_1234567890BOOT_0</code>替换到<code>boot0_sdcard.fex</code>。</p><figure class="highlight shell"><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">cp -f /path/to/OrangePi_3_LTS_Android9_v1.0.img.dump/12345678_1234567890BOOT_0  boot0_sdcard.fex</span><br><span class="line">dragon image.cfg    sys_partition.fex</span><br></pre></td></tr></table></figure><p>重新执行pack命令打包img文件，烧录，启动成功。虽然启动过程中还有一些error log，但最终还是顺利进入了系统。全志H6原始Android SDK桌面如下。</p><p><img src="https://image.pkemb.com/image/202203212313364.jpg" alt=""></p>]]>
    </content>
    <id>https://pkemb.com/2022/03/boot-orangepi3lts-from-h6-android9.0-sdk/</id>
    <link href="https://pkemb.com/2022/03/boot-orangepi3lts-from-h6-android9.0-sdk/"/>
    <published>2022-03-21T21:27:28.000Z</published>
    <summary>
      <![CDATA[<p><code>Orange Pi</code>提供了芯片厂商释放的原始<code>H6 Android9.0 SDK</code>，但没有提供<code>Orange Pi3 LTS</code>的适配代码，所以原始SDK编译出来的镜像不能在<code>Orange Pi3 LTS</code>上正常启动。本文来教你如何突破这一限制。</p>]]>
    </summary>
    <title>使用全志H6 Android9.0 SDK启动OrangePi 3 LTS</title>
    <updated>2022-07-17T18:32:30.000Z</updated>
  </entry>
</feed>
