<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>qiushao</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://qiushao.net/"/>
  <updated>2020-12-27T01:22:23.744Z</updated>
  <id>http://qiushao.net/</id>
  
  <author>
    <name>qiushao</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Linux profile 和 bashrc 的区别</title>
    <link href="http://qiushao.net/2020/12/27/Linux/linux-profile-bashrc-difference/"/>
    <id>http://qiushao.net/2020/12/27/Linux/linux-profile-bashrc-difference/</id>
    <published>2020-12-27T01:22:23.744Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>Linux 系统上有四个环境配置文件，分别为:</p><ul><li>/etc/profile</li><li>/etc/bash.bashrc</li><li>~/.profile</li><li>~/.bashrc</li></ul><h2 id="1-先从作用域来说"><a href="#1-先从作用域来说" class="headerlink" title="1. 先从作用域来说"></a>1. 先从作用域来说</h2><p>/etc 目录下的配置文件影响所有用户。<br>~/ 用户家目录下的配置文件只影响本用户。</p><h2 id="2-从加载时机来说"><a href="#2-从加载时机来说" class="headerlink" title="2. 从加载时机来说"></a>2. 从加载时机来说</h2><p>profile 是用户在登录系统的时候加载的。profile 在用户登录使用的过程中只加载一次。修改之后，需要重启系统生效。我们把 profile 叫做登录式 SHELL 配置文件，<br>bashrc 是用户在启动 bash shell 的时候加载的。也就是启动一个 terminal 终端的时候加载。修改之后，只要重启 terminal 就生效了。或者手动 source ~/.bashrc 也能生效。我们把 bashrc 叫做非登录式 SHELL 配置。</p><h2 id="3-各文件的加载流程"><a href="#3-各文件的加载流程" class="headerlink" title="3. 各文件的加载流程"></a>3. 各文件的加载流程</h2><p>登陆式 SHELLL 配置文件加载顺序:/etc/profile &gt; .bash_profile &gt; .bash_login &gt; .profile &gt; .bash_logout<br>非登录式 SHELL 配置文件加载顺序:/etc/bash.bashrc &gt; .bashrc</p><h2 id="4-知识点应用举例"><a href="#4-知识点应用举例" class="headerlink" title="4. 知识点应用举例"></a>4. 知识点应用举例</h2><p>问题：在 clion 中设置交叉编译环境，获取不到 .bashrc 中设置的 PATH 环境变量。</p><p>在 ~/.bashrc 中设置交叉编译工具环境变量</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export PATH&#x3D;$PATH:&#x2F;media&#x2F;deep&#x2F;source-code&#x2F;Hi3516&#x2F;sourceCode&#x2F;toolchain&#x2F;arm-himix200-linux&#x2F;bin</span><br></pre></td></tr></table></figure><p>在 clion 中设置交叉编译工具</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">set(CMAKE_SYSTEM_NAME Linux)</span><br><span class="line">set(CMAKE_C_COMPILER arm-himix200-linux-gcc)</span><br><span class="line">set(CMAKE_CXX_COMPILER arm-himix200-linux-g++)</span><br></pre></td></tr></table></figure><p>clion 中提示 arm-himix200-linux-gcc 找不到。但直接在 terminal 中执行 cmake 是可以编译成功的。</p><p>原因分析：clion 是由 gnome 桌面启动的。gnome 是由登录式 shell 启动的。因此环境变量是继承的登录式 shell 的环境变量。也就是 profile 配置的环境变量。profile 中没配置 arm-himix200-linux 路径，因此找不到。启动的终端是非登录式 shell。会加载 bashrc 文件, 因此能找到 arm-himix200-linux。</p><p>解决方案：在　~/.profile 中设置编译工具环境变量，然后重启即可。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-PC:~$ tail .profile </span><br><span class="line"># set PATH so it includes user&#39;s private bin if it exists</span><br><span class="line">if [ -d &quot;$HOME&#x2F;.local&#x2F;bin&quot; ] ; then</span><br><span class="line">    PATH&#x3D;&quot;$HOME&#x2F;.local&#x2F;bin:$PATH&quot;</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line">export PATH&#x3D;$PATH:&#x2F;media&#x2F;deep&#x2F;source-code&#x2F;Hi3516&#x2F;sourceCode&#x2F;toolchain&#x2F;arm-himix200-linux&#x2F;bin</span><br><span class="line"></span><br><span class="line">qiushao@qiushao-PC:~$</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Linux 系统上有四个环境配置文件，分别为:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/etc/profile&lt;/li&gt;
&lt;li&gt;/etc/bash.bashrc&lt;/li&gt;
&lt;li&gt;~/.profile&lt;/li&gt;
&lt;li&gt;~/.bashrc&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1-先
      
    
    </summary>
    
    
      <category term="Linux" scheme="http://qiushao.net/categories/Linux/"/>
    
    
      <category term="Linux" scheme="http://qiushao.net/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Linux Mint 20 安装 QT5</title>
    <link href="http://qiushao.net/2020/07/31/Linux/linuxmint-install-qt5/"/>
    <id>http://qiushao.net/2020/07/31/Linux/linuxmint-install-qt5/</id>
    <published>2020-07-31T14:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>最近想写些 pc 的小工具给自己使用。决定使用 qt5 来开发，首先需要安装开发包。网上的教程基本都是从 qt 官网上下载安装包安装的，另外一些教程则只是安装了基本开发包而已，没有安装文档和 example 的。<br>因此记录一下使用 apt 安装 qt5 开发包及文档，example 的过程。</p><h2 id="1-安装-qt5-基本开发包"><a href="#1-安装-qt5-基本开发包" class="headerlink" title="1. 安装 qt5 基本开发包"></a>1. 安装 qt5 基本开发包</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install build-essential qt5-default qtcreator</span><br></pre></td></tr></table></figure><p>如果你不需要看文档和例子的话，这样子就足够了。但对于我这种新手菜鸡来说，没有例子来学习是不行的。</p><h2 id="2-安装开发文档和例子"><a href="#2-安装开发文档和例子" class="headerlink" title="2. 安装开发文档和例子"></a>2. 安装开发文档和例子</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install qt5-doc qt5-doc-html qtbase5-doc-html qtbase5-examples</span><br></pre></td></tr></table></figure><p>安装完后，打开 qtcreator ，示例里面就可以看到各种各样的例子了。<br>以上只是安装了 qt5 的基本界面开发包，文档及例子而已。qt5 还把很多开发包给拆分了出来，比如说串口，多媒体，webengine 等。大家根据自己需要来选择安装。</p><h2 id="3-安装串口相关的开发包及文档"><a href="#3-安装串口相关的开发包及文档" class="headerlink" title="3. 安装串口相关的开发包及文档"></a>3. 安装串口相关的开发包及文档</h2><p>这次是想写个串口工具，因此需要用到串口开发包，我们可以这样查找一下 qt 都提供了哪些扩展开发包</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><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">mint@mint-pc:~$ apt search libqt | grep dev | grep -v i386</span><br><span class="line">p   libqt5-ukui-style-dev                - Development files of libqt5-ukui-style1                                                                                                        </span><br><span class="line">p   libqt5charts5-dev                    - Qt charts development files                                                                                                                    </span><br><span class="line">p   libqt5datavisualization5-dev         - APIs <span class="keyword">for</span> data visualization functionality - development files                                                                                  </span><br><span class="line">p   libqt5gamepad5-dev                   - Qt 5 gamepad module - development files                                                                                                        </span><br><span class="line">p   libqt5gstreamer-dev                  - Development headers <span class="keyword">for</span> QtGStreamer - Qt 5 build                                                                                               </span><br><span class="line">p   libqt5networkauth5-dev               - online account access <span class="keyword">for</span> Qt apps - Development Files                                                                                          </span><br><span class="line">v   libqt5opengl5-desktop-dev            -                                                                                                                                                </span><br><span class="line">i A libqt5opengl5-dev                    - Qt 5 OpenGL library development files                                                                                                          </span><br><span class="line">p   libqt5pas-dev                        - Development files <span class="keyword">for</span> Qt5Pas                                                                                                                   </span><br><span class="line">p   libqt5remoteobjects5-dev             - Qt module <span class="keyword">for</span> IPC — development files                                                                                                          </span><br><span class="line">v   libqt5scintilla2-dev                 -                                                                                                                                                </span><br><span class="line">p   libqt5scxml5-dev                     - Qt module <span class="keyword">for</span> creating state machines from SCXML files development                                                                             </span><br><span class="line">p   libqt5sensors5-dev                   - Qt 5 Sensors development files                                                                                                                 </span><br><span class="line">p   libqt5serialbus5-dev                 - Qt serialbus serial bus access development                                                                                                     </span><br><span class="line">p   libqt5serialport5-dev                - Qt 5 serial port development files                                                                                                             </span><br><span class="line">p   libqt5svg5-dev                       - Qt 5 SVG module development files                                                                                                              </span><br><span class="line">p   libqt5texttospeech5-dev              - Speech library <span class="keyword">for</span> Qt - development files                                                                                                      </span><br><span class="line">p   libqt5virtualkeyboard5-dev           - Qt virtual keyboard - development files                                                                                                        </span><br><span class="line">p   libqt5waylandclient5-dev             - QtWayland client development files                                                                                                             </span><br><span class="line">p   libqt5waylandcompositor5-dev         - QtWayland compositor development files                                                                                                         </span><br><span class="line">p   libqt5webchannel5-dev                - Web communication library <span class="keyword">for</span> Qt - development files                                                                                           </span><br><span class="line">p   libqt5webkit5-dev                    - Web content engine library <span class="keyword">for</span> Qt - development files                                                                                          </span><br><span class="line">p   libqt5websockets5-dev                - Qt 5 Web Sockets module - development files</span><br></pre></td></tr></table></figure><p>我们可以看到有很多模块开发包，比如 gamepad, gstreamer, opengl, sensor, serialport 等，这里我们要安装的串口开发模块是 libqt5serialport5-dev。相关的开发文档可以这么搜索</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mint@mint-pc:~$ apt search qt5serialport</span><br><span class="line">p A libqt5serialport5                    - Qt 5 serial port support  </span><br><span class="line">p   libqt5serialport5-dev                - Qt 5 serial port development files </span><br><span class="line">p   qt5serialport-examples               - Qt 5 serial port examples</span><br></pre></td></tr></table></figure><p>因此我们要安装以下两个包</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install libqt5serialport5-dev qt5serialport-examples</span><br></pre></td></tr></table></figure><p>安装完之后，重启 qtcreator，我们就可以看到多出了一些串口相关的例子。<br><img src="/images/qtserialport-example.png" alt="qtserialport-example"></p><p>其他的模块也是类似的安装方法。</p><h2 id="使用-clion-来开发-qt-程序"><a href="#使用-clion-来开发-qt-程序" class="headerlink" title="使用 clion 来开发 qt 程序"></a>使用 clion 来开发 qt 程序</h2><p>使用过各种 IDE, 发现还是 clion 最好用。qtcreator 只是用来看文档和例子而已<br>只要在 CMakeLists.txt 中添加以下配置即可：</p><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment"># qt5 setttings</span></span><br><span class="line"><span class="keyword">set</span>(CMAKE_INCLUDE_CURRENT_DIR <span class="keyword">ON</span>)</span><br><span class="line"><span class="keyword">set</span>(CMAKE_AUTOMOC <span class="keyword">ON</span>)</span><br><span class="line"><span class="keyword">set</span>(CMAKE_AUTOUIC <span class="keyword">ON</span>)</span><br><span class="line"><span class="keyword">set</span>(CMAKE_AUTORCC <span class="keyword">ON</span>)</span><br><span class="line"><span class="keyword">find_package</span>(Qt5 REQUIRED Widgets SerialPort)</span><br><span class="line"></span><br><span class="line"><span class="keyword">FILE</span>(GLOB_RECURSE header_list <span class="string">"src/*.h"</span>)</span><br><span class="line"><span class="keyword">FILE</span>(GLOB_RECURSE cpp_list <span class="string">"src/*.cpp"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># qt5 settings, 信号，槽相关</span></span><br><span class="line"><span class="keyword">qt5_wrap_cpp</span>(MOC <span class="variable">$&#123;cpp_list&#125;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># qt5 settings, 有资源文件的话，需要做以下配置</span></span><br><span class="line"><span class="comment">#set(RESOURCE_DIR res/resources.qrc)</span></span><br><span class="line"><span class="comment">#qt5_wrap_ui(UIC widget.ui)</span></span><br><span class="line"><span class="comment">#qt5_add_resources(RCC resources.qrc)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">add_executable</span>(qsterminal <span class="variable">$&#123;cpp_list&#125;</span> <span class="variable">$&#123;header_list&#125;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># qt5 settings</span></span><br><span class="line"><span class="keyword">target_link_libraries</span>(qsterminal Qt5::Widgets Qt5::SerialPort)</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最近想写些 pc 的小工具给自己使用。决定使用 qt5 来开发，首先需要安装开发包。网上的教程基本都是从 qt 官网上下载安装包安装的，另外一些教程则只是安装了基本开发包而已，没有安装文档和 example 的。&lt;br&gt;因此记录一下使用 apt 安装 qt5 开发包及文档，
      
    
    </summary>
    
    
      <category term="QT" scheme="http://qiushao.net/categories/QT/"/>
    
    
      <category term="Linux" scheme="http://qiushao.net/tags/Linux/"/>
    
      <category term="QT" scheme="http://qiushao.net/tags/QT/"/>
    
  </entry>
  
  <entry>
    <title>Linux Mint 20 安装微信</title>
    <link href="http://qiushao.net/2020/07/28/Linux/linuxmint-install-wechat/"/>
    <id>http://qiushao.net/2020/07/28/Linux/linuxmint-install-wechat/</id>
    <published>2020-07-28T14:00:00.000Z</published>
    <updated>2021-01-04T13:37:15.247Z</updated>
    
    <content type="html"><![CDATA[<p>最近 Linux Mint 20 发布了，手痒又折腾了一下。体验了几天，个人感觉非常好，就是国产那堆应用安装麻烦了一点。感谢 deepin 对很多国产应用做了移植，适配工作，让我们可以直接在 debian 系的系统上直接安装使用。下面以安装微信为例说明一下在 debian 系的系统上怎么安装 deepin 适配过的部分国产应用。</p><h2 id="1-添加-apt-source"><a href="#1-添加-apt-source" class="headerlink" title="1. 添加 apt source"></a>1. 添加 apt source</h2><p>在 /etc/apt/sources.list.d/ 目录下新建文件 deepin-wine.list，内容如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">deb [trusted&#x3D;yes] https:&#x2F;&#x2F;deepin-wine.i-m.dev &#x2F;</span><br></pre></td></tr></table></figure><p>deepin 把QQ/微信之类的 deepin wine 应用打包放在了 deepin 仓库中，因此先提取出这些应用及依赖的软件包，再减去 Debian/Ubuntu 等发行版官方仓库中固有的软件包，就可以打包成一个移植于对应发行版的“差量仓库”。感谢这个仓库的作者 <a href="https://github.com/zq1997/deepin-wine" target="_blank" rel="noopener">https://github.com/zq1997/deepin-wine</a> ，想了解更多的细节的话，可以去这个仓库看看。</p><h2 id="2-刷新软件源"><a href="#2-刷新软件源" class="headerlink" title="2. 刷新软件源"></a>2. 刷新软件源</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update</span><br></pre></td></tr></table></figure><h2 id="3-查看-deepin-wine-相关的软件包"><a href="#3-查看-deepin-wine-相关的软件包" class="headerlink" title="3. 查看 deepin-wine 相关的软件包"></a>3. 查看 deepin-wine 相关的软件包</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line">mint@mint-pc:~$ apt search deepin</span><br><span class="line">p   deepin-calculator                                    - Calculator for DDE (Deepin Desktop Environment)                                                                                                </span><br><span class="line">p   deepin-deb-installer                                 - Deepin Package Manager                                                                                                                         </span><br><span class="line">i A deepin-fonts-wine                                    - Windows API implementation - fonts                                                                                                             </span><br><span class="line">v   deepin-fonts-wine:i386                               -                                                                                                                                                </span><br><span class="line">p   deepin-gettext-tools                                 - Deepin Internationalization utilities                                                                                                          </span><br><span class="line">p   deepin-icon-theme                                    - Icon Theme for Deepin software and Deepin Desktop Environment                                                                                  </span><br><span class="line">p   deepin-image-viewer                                  - Image Viewer for Deepin Desktop Environment                                                                                                    </span><br><span class="line">i A deepin-libwine:i386                                  - Windows API implementation - library                                                                                                           </span><br><span class="line">p   deepin-menu                                          - Deepin menu service                                                                                                                            </span><br><span class="line">p   deepin-movie                                         - Deepin movie player                                                                                                                            </span><br><span class="line">p   deepin-music                                         - music player with brilliant and tweakful UI                                                                                                    </span><br><span class="line">p   deepin-notifications                                 - System notifications for Deepin Desktop Environment                                                                                            </span><br><span class="line">p   deepin-picker                                        - Color picker tool for deepin                                                                                                                   </span><br><span class="line">p   deepin-screen-recorder                               - Simple recorder tools for deepin 视频录制工具                                                                                                              </span><br><span class="line">p   deepin-screenshot                                    - Advanced screen shoting tool  截图工具                                                                                                                </span><br><span class="line">p   deepin-shortcut-viewer                               - Pop-up shortcut viewer for Deepin applications                                                                                                 </span><br><span class="line">p   deepin-terminal                                      - Deepin terminal emulator application                                                                                                           </span><br><span class="line">p   deepin-voice-recorder                                - Deepin&#39;s Voice recorder   录音工具                                                                                                                     </span><br><span class="line">i A deepin-wine                                          - Windows API implementation - standard suite                                                                                                    </span><br><span class="line">v   deepin-wine:i386                                     -                                                                                                                                                </span><br><span class="line">i A deepin-wine-helper:i386                              - Deepin Wine Helper                                                                                                                             </span><br><span class="line">v   deepin-wine-plugin                                   -                                                                                                                                                </span><br><span class="line">i A deepin-wine-plugin:i386                              - Deepin Wine plugin                                                                                                                             </span><br><span class="line">i A deepin-wine-plugin-virtual                           - Deepin Wine plugin virtual package                                                                                                             </span><br><span class="line">v   deepin-wine-plugin-virtual:i386                      -                                                                                                                                                </span><br><span class="line">i A deepin-wine-uninstaller:i386                         - Deepin Wine Uninstaller Tool                                                                                                                   </span><br><span class="line">v   deepin-wine32                                        -                                                                                                                                                </span><br><span class="line">i A deepin-wine32:i386                                   - Windows API implementation - 32-bit binary loader                                                                                              </span><br><span class="line">i A deepin-wine32-preloader:i386                         - Windows API implementation - prelinked 32-bit binary loader                                                                                    </span><br><span class="line">p   deepin.cn.360.yasuo:i386                             - 360zip on Deepin Wine                                                                                                                          </span><br><span class="line">p   deepin.cn.com.winrar:i386                            - WinRAR on Deepin Wine                                                                                                                          </span><br><span class="line">p   deepin.com.95579.cjsc:i386                           - CHANGJIANG SECURITIES Client on Deepin Wine                                                                                                    </span><br><span class="line">p   deepin.com.aaa-logo:i386                             - SWGSoft AAALogo on Deepin Wine                                                                                                                 </span><br><span class="line">p   deepin.com.baidu.pan:i386                            - Baidu net disk client on Deepin Wine   百度网盘                                                                                                        </span><br><span class="line">p   deepin.com.cmbchina:i386                             - CMBChina Client on Deepin Wine                                                                                                                 </span><br><span class="line">i   deepin.com.foxmail:i386                              - Tencent Foxmail Client on Deepin Wine                                                                                                          </span><br><span class="line">p   deepin.com.gtja.fuyi:i386                            - GUOTAI JUNAN SECURITIES RichEasy Client on Deepin Wine                                                                                         </span><br><span class="line">p   deepin.com.qq.b.crm:i386                             - Tencent bizQQ Client on Deepin Wine                                                                                                            </span><br><span class="line">p   deepin.com.qq.b.eim:i386                             - Tencent QQEIM Client on Deepin Wine                                                                                                            </span><br><span class="line">p   deepin.com.qq.im:i386                                - Tencent QQ Client on Deepin Wine  QQ                                                                                                             </span><br><span class="line">p   deepin.com.qq.im.light:i386                          - Tencent QQ Client on Deepin Wine  TIM                                                                                                            </span><br><span class="line">p   deepin.com.qq.office:i386                            - Tencent TIM Client on Deepin Wine                                                                                                              </span><br><span class="line">p   deepin.com.qq.rtx2015:i386                           - Tencent QQ Client on Deepin Wine                                                                                                               </span><br><span class="line">p   deepin.com.taobao.aliclient.qianniu:i386             - Ali Qian Niu Client on Deepin Wine  阿里千牛                                                                                                           </span><br><span class="line">p   deepin.com.taobao.wangwang:i386                      - Ali WangWang Client on Deepin Wine  阿里旺旺                                                                                                           </span><br><span class="line">p   deepin.com.thunderspeed:i386                         - Thunder Speed Client on Deepin Wine  迅雷下载                                                                                                          </span><br><span class="line">i   deepin.com.wechat:i386                               - Tencent WeChat Client on Deepin Wine  微信                                                                                                         </span><br><span class="line">p   deepin.com.weixin.work:i386                          - Tencent WeChat Work Client on Deepin Wine  企业微信                                                                                                     </span><br><span class="line">p   deepin.net.263.em:i386                               - 263 EM Client on Deepin Wine                                                                                                                   </span><br><span class="line">p   deepin.org.7-zip:i386                                - 7-Zip on Deepin Wine                                                                                                                           </span><br><span class="line">p   deepin.org.foobar2000:i386                           - Foobar2000 on Deepin Wine                                                                                                                      </span><br><span class="line">v   deepinwine-cmbchina:i386                             -                                                                                                                                                </span><br><span class="line">v   libqt5deepintheme-plugin                             -                                                                                                                                                </span><br><span class="line">mint@mint-pc:~$</span><br></pre></td></tr></table></figure><p>根据包名大概可以知道是哪些软件。</p><h2 id="4-安装微信"><a href="#4-安装微信" class="headerlink" title="4. 安装微信"></a>4. 安装微信</h2><p>只要执行以下命令即可</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install deepin.com.wechat</span><br><span class="line">sudo apt install deepin-screenshot </span><br><span class="line">sudo apt install libjpeg62:i386</span><br></pre></td></tr></table></figure><p><code>deepin-screenshot</code> ：深度截图<br><code>libjpeg62:i386</code> ：不安装这个包的话，发送不了截图。</p><p>之前旧版本安装完之后，就可以直接从应用列表里面看到了，但 2020-10 月之后更新了新版本，新版本的安装位置变成了 <code>/opt/apps</code>，这个路径不在标准的应用 desktop 配置搜索路径。<br>所以新版本还要做些设置才能在应用列表里面显示。<br>在  ~/.profile 文件最后面添加以下配置：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">XDG_DATA_DIRS&#x3D;$&#123;XDG_DATA_DIRS:-&#x2F;usr&#x2F;local&#x2F;share:&#x2F;usr&#x2F;share&#125;</span><br><span class="line">for deepin_dir in &#x2F;opt&#x2F;apps&#x2F;*&#x2F;entries; do</span><br><span class="line">   if [ -d &quot;$deepin_dir&#x2F;applications&quot; ]; then</span><br><span class="line">      XDG_DATA_DIRS&#x3D;&quot;$XDG_DATA_DIRS:$deepin_dir&quot;</span><br><span class="line">   fi</span><br><span class="line">done</span><br><span class="line">export XDG_DATA_DIRS</span><br></pre></td></tr></table></figure><p>然后重启就行了</p><h2 id="5-截图"><a href="#5-截图" class="headerlink" title="5. 截图"></a>5. 截图</h2><p>默认的截图快捷键是 Alt + A，但这个快捷键只有焦点在微信界面的时候才可以使用。<br>为了能够全局截图，我们可以使用深度截图。打开 系统设置 –&gt; 键盘 –&gt; 快捷键 –&gt; 自定义快捷键<br>设置如下：<br><img src="/images/%E6%B7%BB%E5%8A%A0%E5%85%A8%E5%B1%80%E6%88%AA%E5%9B%BE%E5%BF%AB%E6%8D%B7%E9%94%AE1.png" alt="添加全局截图快捷键1"><br><img src="/images/%E6%B7%BB%E5%8A%A0%E5%85%A8%E5%B1%80%E6%88%AA%E5%9B%BE%E5%BF%AB%E6%8D%B7%E9%94%AE2.png" alt="添加全局截图快捷键2"></p><p>然后就可以全局使用 Ctrl + Alt + A 来截图啦。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最近 Linux Mint 20 发布了，手痒又折腾了一下。体验了几天，个人感觉非常好，就是国产那堆应用安装麻烦了一点。感谢 deepin 对很多国产应用做了移植，适配工作，让我们可以直接在 debian 系的系统上直接安装使用。下面以安装微信为例说明一下在 debian 
      
    
    </summary>
    
    
      <category term="Linux" scheme="http://qiushao.net/categories/Linux/"/>
    
    
      <category term="Linux" scheme="http://qiushao.net/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>内存泄漏分析工具：tcmalloc</title>
    <link href="http://qiushao.net/2020/07/11/Linux/memory-leak-analyze-tcmalloc/"/>
    <id>http://qiushao.net/2020/07/11/Linux/memory-leak-analyze-tcmalloc/</id>
    <published>2020-07-11T12:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>最近遇到一个内存泄漏的问题。由于代码量比较庞大，且使用了很多第三方库，部分第三方库还是商业闭源的，没法通过 code review 还排查了。对于闭源部分的库，静态代码检查工具也没法派上用场。所以一直在寻找一个能够追踪内存分配，释放，定位内存泄漏点的工具。尝试了 valgrind 和 gperftools。由于是嵌入式开发，内存，cpu 都比较有限。valgrind 资源占用太多，应用根本就没法跑起来。后面了解了 gperftools 的原理后，觉得内存，cpu 的占用应该是可以接受的，所以就一直折腾 gperftools，经过几天的折腾，总算靠 gperftools 这个工具找出了内存泄漏的真凶。下面介绍一下 gperftools 工具的使用方法。</p><h2 id="1-下载编译"><a href="#1-下载编译" class="headerlink" title="1. 下载编译"></a>1. 下载编译</h2><p>先从 github 上下载最新版本的代码</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></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~/projects/opensources$ git <span class="built_in">clone</span> https://github.com/gperftools/gperftools</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources$ <span class="built_in">cd</span> gperftools</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$</span><br></pre></td></tr></table></figure><p>我们先编译安装 pc 上使用的版本：</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></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$ ./autogen.sh</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$ ./configure</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$ make -j</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$ sudo make install</span><br></pre></td></tr></table></figure><p>如果是需要交叉编译的话，则再重新编译。 ./configure 时指定目标平台，编译工具链即可。只需要编译，不需要安装，后面我们直接 copy so 库到板子上就行：</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></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$ make clean</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$ ./configure --host=arm-linux CXX=arm-himix200-linux-g++ CC=arm-himix200-linux-gcc</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$ make -j</span><br></pre></td></tr></table></figure><p>编译的结果在 .libs 目录下，gperftools 是一个工具集合，我们分析内存泄漏问题时，只需要使用其中的 libtcmalloc.so 即可。</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></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$ file .libs/libtcmalloc.so*</span><br><span class="line">.libs/libtcmalloc.so:       symbolic link to libtcmalloc.so.4.5.5</span><br><span class="line">.libs/libtcmalloc.so.4:     symbolic link to libtcmalloc.so.4.5.5</span><br><span class="line">.libs/libtcmalloc.so.4.5.5: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, with debug_info, not stripped</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources/gperftools$</span><br></pre></td></tr></table></figure><h2 id="2-tcmalloc-使用"><a href="#2-tcmalloc-使用" class="headerlink" title="2. tcmalloc 使用"></a>2. tcmalloc 使用</h2><p>我们先在 pc 上测试一下效果，如果能满足我们的需求的话，再到板子上进行测试。<br>先写个有内存泄漏的 demo：</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span><span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">char</span>* <span class="title">get_buffer</span><span class="params">(<span class="keyword">size_t</span> <span class="built_in">size</span>)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">char</span> *<span class="built_in">buffer</span> = (<span class="keyword">char</span> *)<span class="built_in">malloc</span>(<span class="built_in">size</span>);  <span class="comment">// 这里分配了内存，但后面 leak_memory 函数使用完之后没有释放，造成内存泄漏。</span></span><br><span class="line">    <span class="built_in">memset</span>(<span class="built_in">buffer</span>, <span class="number">0</span>, <span class="built_in">size</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">buffer</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">leak_memory</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">char</span> *<span class="built_in">buffer</span> = get_buffer(<span class="number">1024</span> * <span class="number">1024</span>);</span><br><span class="line">    <span class="built_in">sprintf</span>(<span class="built_in">buffer</span>, <span class="string">"do something with buffer"</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"%s\n"</span>, <span class="built_in">buffer</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"tcmalloc test"</span>);</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">        leak_memory();</span><br><span class="line">    &#125;</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><p>编译时需要加上调试信息 <code>-g</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~/projects/<span class="built_in">test</span>$ g++ -o tcmalloc_test -g tcmalloc_test.cpp</span><br></pre></td></tr></table></figure><p>我们在写代码，和编译代码的时候都不需要 tcmalloc  的参与，我觉得这一点是做得非常好的。对代码完全没有侵入。<br>只需要按以下的方式执行就可以：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~/projects/<span class="built_in">test</span>$ LD_PRELOAD=/usr/<span class="built_in">local</span>/lib/libtcmalloc.so HEAPCHECK=normal ./tcmalloc_test</span><br></pre></td></tr></table></figure><ul><li>LD_PRELOAD : 指定 libtcmalloc.so 的路径</li><li>HEAPCHECK: 指定检查等级。包括 minimal, normal, strict, draconian 这四种等级。具体差别参考文档： gperftools/docs/heap_checker.html 。如果 normal 没有检查出来问题的话，再用 draconian 试试。</li></ul><h2 id="3-解析内存泄漏分析结果"><a href="#3-解析内存泄漏分析结果" class="headerlink" title="3. 解析内存泄漏分析结果"></a>3. 解析内存泄漏分析结果</h2><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><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">qiushao@qiushao-pc:~/projects/<span class="built_in">test</span>$ LD_PRELOAD=/usr/<span class="built_in">local</span>/lib/libtcmalloc.so HEAPCHECK=normal ./tcmalloc_test </span><br><span class="line">WARNING: Perftools heap leak checker is active -- Performance may suffer</span><br><span class="line">tcmalloc testdo something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line">Have memory regions w/o callers: might report <span class="literal">false</span> leaks</span><br><span class="line">Leak check _main_ detected leaks of 10485760 bytes <span class="keyword">in</span> 10 objects</span><br><span class="line">The 1 largest leaks:</span><br><span class="line">*** WARNING: Cannot convert addresses to symbols <span class="keyword">in</span> output below.</span><br><span class="line">*** Reason: Cannot find <span class="string">'pprof'</span> (is PPROF_PATH <span class="built_in">set</span> correctly?)</span><br><span class="line">*** If you cannot fix this, try running pprof directly.</span><br><span class="line">Leak of 10485760 bytes <span class="keyword">in</span> 10 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 </span><br><span class="line">        @ 55d4280a4763 </span><br><span class="line">        @ 55d4280a47d2 </span><br><span class="line">        @ 7fc6cb53bb97 </span><br><span class="line">        @ 55d4280a463a </span><br><span class="line"></span><br><span class="line"></span><br><span class="line">If the preceding stack traces are not enough to find the leaks, try running THIS shell <span class="built_in">command</span>:</span><br><span class="line"></span><br><span class="line">pprof ./tcmalloc_test <span class="string">"/tmp/tcmalloc_test.14180._main_-end.heap"</span> --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv</span><br><span class="line"></span><br><span class="line">If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1</span><br><span class="line">If the leak report occurs <span class="keyword">in</span> a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=<span class="literal">false</span>, it might <span class="built_in">help</span> find lea</span><br><span class="line">Exiting with error code (instead of crashing) because of whole-program memory leaks</span><br><span class="line">qiushao@qiushao-pc:~/projects/<span class="built_in">test</span>$</span><br></pre></td></tr></table></figure><p>tcmalloc 的内存泄漏检查生效时会有这个警告：<code>WARNING: Perftools heap leak checker is active -- Performance may suffer</code><br>运行完程序之后，提示:  <code>Leak of 10485760 bytes in 10 objects allocated from:</code> 。即有 10 处内存地址泄漏了，总共泄漏了 10485760 bytes。<br>tcmalloc 会在 /tmp 目录下生成一个 heap 文件：/tmp/tcmalloc_test.14180.<em>main</em>-end.heap。这个文件记录了所有的内存分配信息。<br>pprof 是 gperftools 自带的一个 perl 脚本，如果我们之前有 make install 的话，直接使用就行，没有 make install 的话，就得设置一下环境变量了。<br>我们可以通过 pprof 脚本来解析 heap 文件，获取每一个内存泄漏的位置及调用堆栈：</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><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~/projects/<span class="built_in">test</span>$ pprof ./tcmalloc_test <span class="string">"/tmp/tcmalloc_test.14180._main_-end.heap"</span>  --lines  --text --stack</span><br><span class="line">Using <span class="built_in">local</span> file ./tcmalloc_test.</span><br><span class="line">Using <span class="built_in">local</span> file /tmp/tcmalloc_test.14180._main_-end.heap.</span><br><span class="line">Total: 10 objects</span><br><span class="line">Stacks:</span><br><span class="line"></span><br><span class="line">10       (000055d4280a4732) /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:6:get_buffer</span><br><span class="line">         (000055d4280a4762) /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12:leak_memory</span><br><span class="line">         (000055d4280a47d1) /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20:main</span><br><span class="line">         (00007fc6cb53bb96) /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310:__libc_start_main</span><br><span class="line">         (000055d4280a4639) ??:0:_start</span><br><span class="line"></span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line">Leak of 1048576 bytes <span class="keyword">in</span> 1 objects allocated from:</span><br><span class="line">        @ 55d4280a4732 unknown</span><br><span class="line">        @ 000055d4280a4762 leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">        @ 000055d4280a47d1 main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">        @ 00007fc6cb53bb96 __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">        @ 000055d4280a4639 _start ??:0</span><br><span class="line"></span><br><span class="line">      10 100.0% 100.0%       10 100.0% get_buffer /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:6</span><br><span class="line">       0   0.0% 100.0%       10 100.0% __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310</span><br><span class="line">       0   0.0% 100.0%       10 100.0% _start ??:0</span><br><span class="line">       0   0.0% 100.0%       10 100.0% leak_memory /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:12</span><br><span class="line">       0   0.0% 100.0%       10 100.0% main /home/qiushao/projects/<span class="built_in">test</span>/tcmalloc_test.cpp:20</span><br><span class="line">qiushao@qiushao-pc:~/projects/<span class="built_in">test</span>$</span><br></pre></td></tr></table></figure><p>其中：</p><ul><li><code>--lines</code>: 打印文件路径和行号。</li><li><code>--text</code> : 以文本形式输出分析结果，还可以以 pdf,  调用图 gv 等形式输出。</li><li><code>--stack</code>: 打印每一处泄漏的调用堆栈。</li></ul><h2 id="4-arm-linux-上使用-tcmalloc"><a href="#4-arm-linux-上使用-tcmalloc" class="headerlink" title="4. arm linux 上使用 tcmalloc"></a>4. arm linux 上使用 tcmalloc</h2><p>在开发板上使用 tcmalloc 跟 pc 上使用是差不多的。我们把交叉编译生成的 libtcmalloc.so*  库 copy 到板子上。我放到了 /vendor/lib 目录下。然后手动执行程序：</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></pre></td><td class="code"><pre><span class="line">~ <span class="comment"># mount -t nfs -o nolock -o tcp -o rsize=32768,wsize=32768 192.168.181.112:/media/qiushao/source-code/Hi3516/sourceCode/trunk  /mnt</span></span><br><span class="line">~ <span class="comment"># mount -o rw,remount /vendor</span></span><br><span class="line">~ <span class="comment"># cp -f /mnt/libtcmalloc.so* /vendor/lib/</span></span><br><span class="line">~ <span class="comment"># cp -f /mnt/tcmalloc_test /vendor/bin/</span></span><br><span class="line">~ <span class="comment"># LD_PRELOAD=/vendor/lib/libtcmalloc.so HEAPCHECK=normal /vendor/bin/tcmalloc_test</span></span><br></pre></td></tr></table></figure><p>执行结果跟在 pc 上执行的是一样的：</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><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"># LD_PRELOAD=/vendor/lib/libtcmalloc.so HEAPCHECK=normal /vendor/bin/tcmalloc_</span></span><br><span class="line"><span class="built_in">test</span></span><br><span class="line">WARNING: Perftools heap leak checker is active -- Performance may suffer</span><br><span class="line">tcmalloc testdo something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line"><span class="keyword">do</span> something with buffer</span><br><span class="line">Have memory regions w/o callers: might report <span class="literal">false</span> leaks</span><br><span class="line">Leak check _main_ detected leaks of 10485760 bytes <span class="keyword">in</span> 10 objects</span><br><span class="line">The 1 largest leaks:</span><br><span class="line">*** WARNING: Cannot convert addresses to symbols <span class="keyword">in</span> output below.</span><br><span class="line">*** Reason: Cannot find <span class="string">'pprof'</span> (is PPROF_PATH <span class="built_in">set</span> correctly?)</span><br><span class="line">*** If you cannot fix this, try running pprof directly.</span><br><span class="line">Leak of 10485760 bytes <span class="keyword">in</span> 10 objects allocated from:</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">If the preceding stack traces are not enough to find the leaks, try running THIS shell <span class="built_in">command</span>:</span><br><span class="line"></span><br><span class="line">pprof /vendor/bin/tcmalloc_test <span class="string">"/tmp/tcmalloc_test.251._main_-end.heap"</span> --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv</span><br><span class="line"></span><br><span class="line">If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1</span><br><span class="line">If the leak report occurs <span class="keyword">in</span> a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=<span class="literal">false</span>, it might <span class="built_in">help</span> </span><br><span class="line">Exiting with error code (instead of crashing) because of whole-program memory leaks</span><br><span class="line">~ <span class="comment">#</span></span><br></pre></td></tr></table></figure><p>同样也生成了 “/tmp/tcmalloc_test.251.<em>main</em>-end.heap” 文件。由于在板子上没法跑 pprof 脚本。这个脚本实际上是 perl 脚本，还依赖一大堆其他工具。<br>我们可以把 “/tmp/tcmalloc_test.251.<em>main</em>-end.heap” 文件 copy 到 pc 上，然后在 pc 用 pprof 进行分析。</p><p>在分析的时候可能会提示一些库找不到，我们需要把这些库从板子上也 copy 到 pc 上对应的目录。</p><h2 id="5-tcmalloc-基本原理介绍"><a href="#5-tcmalloc-基本原理介绍" class="headerlink" title="5. tcmalloc 基本原理介绍"></a>5. tcmalloc 基本原理介绍</h2><p>使用完 tcmalloc 之后，我们有以下几个疑问，只要把这几个疑问解决了，那我们对 tcmalloc 的基本原理也就清楚了。</p><ul><li><ol><li>如何记录内存分配，释放:<br>tcmalloc 其实就是定义了一套自己的内存分配释放函数把标准库中的 malloc, alloc, free, new, delete 等替换掉。在分配内存的时候，通过 libunwind 获取调用堆栈，把内存分配调用堆栈记录下来。</li></ol></li><li><ol start="2"><li>如何替换标准库的内存分配，释放函数：<br>LD_PRELOAD 是 Linux 系统的一个环境变量，它可以影响程序的运行时的链接，它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量，我们可以在主程序和其动态链接库的中间加载别的动态链接库，甚至覆盖正常的函数库。一方面，我们可以以此功能来使用自己的或是更好的函数（无需别人的源码），而另一方面，我们也可以以向别人的程序注入程序，从而达到特定的目的。</li></ol></li><li><ol start="3"><li>如何在程序 main 函数结束后，执行内存泄漏分析：<br>只要定义一个全局静态类变量就行，这个变量的构造函数会在 main 之前执行，析构函数会在 main 函数之后执行。</li></ol></li></ul><p>再详细的东西就不展开讨论了，想了解细节的同学，可以自己阅读代码。<br>更详细的使用细节说明，请阅读 gperftools 代码里面的 docs 文档。</p><p>参考资料：</p><ul><li><a href="https://wallenwang.com/2018/11/tcmalloc/" target="_blank" rel="noopener">https://wallenwang.com/2018/11/tcmalloc/</a></li><li><a href="https://www.cnblogs.com/my_life/articles/4683313.html" target="_blank" rel="noopener">https://www.cnblogs.com/my_life/articles/4683313.html</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最近遇到一个内存泄漏的问题。由于代码量比较庞大，且使用了很多第三方库，部分第三方库还是商业闭源的，没法通过 code review 还排查了。对于闭源部分的库，静态代码检查工具也没法派上用场。所以一直在寻找一个能够追踪内存分配，释放，定位内存泄漏点的工具。尝试了 valgr
      
    
    </summary>
    
    
      <category term="Linux" scheme="http://qiushao.net/categories/Linux/"/>
    
    
      <category term="Linux" scheme="http://qiushao.net/tags/Linux/"/>
    
      <category term="gperftools" scheme="http://qiushao.net/tags/gperftools/"/>
    
      <category term="tcmalloc" scheme="http://qiushao.net/tags/tcmalloc/"/>
    
      <category term="内存泄漏" scheme="http://qiushao.net/tags/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>根据条件禁用Android.bp模块</title>
    <link href="http://qiushao.net/2020/05/29/Android/Android.bp-disable-module/"/>
    <id>http://qiushao.net/2020/05/29/Android/Android.bp-disable-module/</id>
    <published>2020-05-29T12:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.740Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇文章我们提到了在 Android 10 之后，Android.bp 定义的模块才能加入到 bootjar 中。但是我们有个中间件模块是各平台同一套代码的。我们还有 Android 5.1 的平台在维护，而 Android 5.1 是还不支持 Android.bp 的。为了兼容各平台，我们需要同时编写 Android.mk 和 Android.bp 规则，里面都定义了中间件模块。但这样子在同时支持 mk 和 bp 的平台就会有模块重定义的问题。所以我们需要一种机制，能实现以下需求：</p><ul><li>Android 版本 &gt;= 10 时：Android.bp 的模块定义生效，Android.mk 的模块定义不生效。</li><li>Android 版本 &lt; 10  时：Android.mk 的模块定义生效，Android.bp 的模块定义不生效。</li></ul><h2 id="Android-mk-根据-sdk-版本，禁用模块"><a href="#Android-mk-根据-sdk-版本，禁用模块" class="headerlink" title="Android.mk 根据 sdk 版本，禁用模块"></a>Android.mk 根据 sdk 版本，禁用模块</h2><p>Android.mk 是 Makefile 语法，本身就有条件判断功能，所以要实现这个功能是很简单的：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">LOCAL_PATH :&#x3D; $(call my-dir)  </span><br><span class="line">include $(CLEAR_VARS)  </span><br><span class="line">LOCAL_MODULE    :&#x3D; hello          # 模块名</span><br><span class="line">LOCAL_SRC_FILES :&#x3D; hello.cpp 　　　# 源文件列表</span><br><span class="line">LOCAL_SHARED_LIBRARIES :&#x3D; liblog  # 依赖关系</span><br><span class="line">LOCAL_xxx       :&#x3D; xxx</span><br><span class="line"></span><br><span class="line"># 小于 Android 10 才在 Android.mk 中定义模块。include $(BUILD_XXX) 才会真正定义一个模块。</span><br><span class="line">ifeq (1,$(filter 1,$(shell echo &quot;$$(( $(PLATFORM_SDK_VERSION) &lt; 29 ))&quot; )))  </span><br><span class="line">include $(BUILD_EXECUTABLE)       # 模块类型</span><br><span class="line">endif</span><br></pre></td></tr></table></figure><h2 id="Android-bp-根据-sdk-版本，禁用模块"><a href="#Android-bp-根据-sdk-版本，禁用模块" class="headerlink" title="Android.bp 根据 sdk 版本，禁用模块"></a>Android.bp 根据 sdk 版本，禁用模块</h2><p>Android.bp 的条件编译，前面的文章已经有讨论过了，这里就不再展开了。<br><a href="http://qiushao.net/2020/02/05/Android系统开发入门/15-Anroid.bp条件编译/">http://qiushao.net/2020/02/05/Android系统开发入门/15-Anroid.bp条件编译/</a><br>只展示一下关键配置即可：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">func helloHook(ctx android.LoadHookContext) &#123;</span><br><span class="line">    &#x2F;&#x2F;AConfig() function is at build&#x2F;soong&#x2F;android&#x2F;config.go</span><br><span class="line">    fmt.Println(&quot;PlatformSdkVersion &#x3D; &quot;, ctx.AConfig().PlatformSdkVersion())</span><br><span class="line">    type props struct &#123;</span><br><span class="line">        Enabled *bool</span><br><span class="line">    &#125;</span><br><span class="line">    p :&#x3D; &amp;props&#123;&#125;</span><br><span class="line">    var int_platform_sdk_version int</span><br><span class="line">    int_platform_sdk_version &#x3D; ctx.AConfig().PlatformSdkVersionInt()</span><br><span class="line"></span><br><span class="line">    var enabled bool</span><br><span class="line">    if int_platform_sdk_version &gt;&#x3D; 29 &#123;</span><br><span class="line">        enabled &#x3D; true</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">        enabled &#x3D; false</span><br><span class="line">    &#125;</span><br><span class="line">    p.Enabled &#x3D; &amp;enabled</span><br><span class="line">    ctx.AppendProperties(p)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Android.bp 中有一个 enabled 字段，当 enabled = false 时，这个模块就不会被定义，所以我们只要根据 sdk 的版本来给 enabled 字段赋值即可。<br>这里需要注意的是 props struct 中定义的 Enabled 是指针类型。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;上一篇文章我们提到了在 Android 10 之后，Android.bp 定义的模块才能加入到 bootjar 中。但是我们有个中间件模块是各平台同一套代码的。我们还有 Android 5.1 的平台在维护，而 Android 5.1 是还不支持 Android.bp 的。
      
    
    </summary>
    
    
      <category term="Android" scheme="http://qiushao.net/categories/Android/"/>
    
    
      <category term="Android" scheme="http://qiushao.net/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Android系统开发入门-16.Android系统添加 bootjar</title>
    <link href="http://qiushao.net/2020/05/23/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8/16-Android-add-bootjar/"/>
    <id>http://qiushao.net/2020/05/23/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8/16-Android-add-bootjar/</id>
    <published>2020-05-23T02:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<h2 id="1-什么是-bootjar"><a href="#1-什么是-bootjar" class="headerlink" title="1. 什么是 bootjar"></a>1. 什么是 bootjar</h2><p>要理解 boojar 首先得理解 <a href="http://qiushao.net/2020/02/18/Java/Java-%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6%E4%BB%8B%E7%BB%8D/">Java-类的加载机制</a>。<br>其中最重要的概念是双亲委托加载模式：<br><img src="/images/%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%A8%A1%E5%9E%8B.png" alt="双亲委托加载"></p><p>从上图我们可以看到 Bootstrap ClassLoader 的加载路径就包含了 BOOTCLASSPATH 路径。BOOTCLASSPATH 是一个环境变量，我们可以这样查看：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pure:&#x2F; # echo $BOOTCLASSPATH</span><br><span class="line">&#x2F;apex&#x2F;com.android.runtime&#x2F;javalib&#x2F;core-oj.jar:&#x2F;apex&#x2F;com.android.runtime&#x2F;javalib&#x2F;core-libart.jar:&#x2F;apex&#x2F;com.android.runtime&#x2F;javalib&#x2F;okhttp.jar:&#x2F;apex&#x2F;com.android.runtime&#x2F;javalib&#x2F;bouncycastle.jar:&#x2F;apex&#x2F;com.android.runtime&#x2F;javalib&#x2F;apache-xml.jar:&#x2F;system&#x2F;framework&#x2F;framework.jar:&#x2F;system&#x2F;framework&#x2F;ext.jar:&#x2F;system&#x2F;framework&#x2F;telephony-common.jar:&#x2F;system&#x2F;framework&#x2F;voip-common.jar:&#x2F;system&#x2F;framework&#x2F;ims-common.jar:&#x2F;system&#x2F;framework&#x2F;android.test.base.jar:&#x2F;apex&#x2F;com.android.conscrypt&#x2F;javalib&#x2F;conscrypt.jar:&#x2F;apex&#x2F;com.android.media&#x2F;javalib&#x2F;updatable-media.jar</span><br><span class="line">pure:&#x2F; #</span><br></pre></td></tr></table></figure><p>Android 中的 bootjar 其实就是要将某个 jar 包添加到 BOOTCLASSPATH。</p><h2 id="2-什么时候需要添加-boojar"><a href="#2-什么时候需要添加-boojar" class="headerlink" title="2. 什么时候需要添加 boojar"></a>2. 什么时候需要添加 boojar</h2><p>想像一下，你是做 TV 系统的。TV 芯片的供应商有 hisi, rtk, mtk 等，他们在系统中都提供了一套 TV 相关的 java api 给我们应用使用。 我们同时要开发这几个不同芯片供应商的平台。我们的应用应该怎么开发呢？</p><ul><li><ol><li>最原始的方案是应用层分别对接不同芯片供应商提供的接口<br>这种方案的问题是假如有N个应用需要用到 TV 接口的话，那我们就需要维护 3 * N个应用。这种方法的代码维护成本太高了，不可接受。</li></ol></li><li><ol start="2"><li>封装一层中间层，我们称之为 middleware（中间件）, apk 只对接中间件，平台差异部分由中间来分别对接。<br>这种方案我们需要维护N个应用 + 三份中间件接口。这种方案有以下好处：<ul><li>那所有应用都统一对接到中间件即可。不需要处理平台差异。大大减少应用代码的维护量。</li><li>供应商接口有变化时，应用层不需要更改。只需要在中间件调整适配即可。</li></ul></li></ol></li></ul><p>这种方案有两种实现方式。<br>    - 中间件 jar 带完整实现，编译 apk 时打包到 apk 中。这种方案的问题是当中间件实现有变化时，所有的 apk 都需要重新打包发布，不好维护。<br>    - 中间件 jar 是空接口。具体实现放到平台中。当中间件实现有变化时，apk 不需要任何更改，只需要更改平台的中间实现就行。要实现这种方案就得通过 bootjar 来实现了。目前我们使用的是这种方案。</p><h2 id="3-添加-bootjar"><a href="#3-添加-bootjar" class="headerlink" title="3. 添加 bootjar"></a>3. 添加 bootjar</h2><p>添加 bootjar 的方法很简单，假设我们有个 java_library <code>api.pure</code> 需要加入 bootjar， 只需要做以下工作即可。<br>device.mk 中加入</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">PRODUCT_PACKAGES +&#x3D; api.pure</span><br><span class="line">PRODUCT_BOOT_JARS +&#x3D; api.pure</span><br></pre></td></tr></table></figure><p>Android/build/core/tasks/check_boot_jars/package_whitelist.txt 中加入</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">api\.pure</span><br><span class="line">api\.pure\..*</span><br></pre></td></tr></table></figure><h2 id="4-Android-10-boojar-的变化"><a href="#4-Android-10-boojar-的变化" class="headerlink" title="4. Android 10 boojar 的变化"></a>4. Android 10 boojar 的变化</h2><p>最近在搞 hisiv900, Android 10 的平台。需要把之前的中间导入到系统里面。以前的中间件是 Android.mk 模块。按照上面的方法导入时编译出现类似以下的错误：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">[100% 2&#x2F;2] out&#x2F;soong&#x2F;.bootstrap&#x2F;bin&#x2F;soong_build out&#x2F;soong&#x2F;build.ninja</span><br><span class="line">FAILED: out&#x2F;soong&#x2F;build.ninja</span><br><span class="line">out&#x2F;soong&#x2F;.bootstrap&#x2F;bin&#x2F;soong_build -t -l out&#x2F;.module_paths&#x2F;Android.bp.list -b out&#x2F;soong -n out -d out&#x2F;soong&#x2F;build.ninja.d -globFile out&#x2F;soong&#x2F;.bootstrap&#x2F;build-globs.ninja -o out&#x2F;soong&#x2F;build.ninja Android.bp</span><br><span class="line">internal error: failed to find dex jar path for module &quot;api.pure&quot;</span><br><span class="line">internal error: failed to find dex jar path for module &quot;api.pure&quot;</span><br><span class="line">15:18:21 soong bootstrap failed with: exit status 1</span><br><span class="line"></span><br><span class="line">#### failed to build some targets (15 seconds) ####</span><br><span class="line"></span><br><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;android-10.0.0_r33$</span><br></pre></td></tr></table></figure><p>只提示找不到 dex jar, 也不知道为什么错了。折腾了好久，后面在 build 目录搜索 PRODUCT_BOOT_JARS 关键字，发现 build/make/core/java.mk 有以下代码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ifneq ($(filter $(LOCAL_MODULE),$(PRODUCT_BOOT_JARS)),)</span><br><span class="line">  $(call pretty-error,Modules in PRODUCT_BOOT_JARS must be defined in Android.bp files)</span><br><span class="line">endif</span><br></pre></td></tr></table></figure><p>才发现，原来 Android 10 之后，Android.mk 定义的模块是没法加入到 bootjar 了。需要切换成 Android.bp。<br>经过实验总结，要加入到 bootjar 需要满足以下条件：<br>Android.bp 模块 + installable 为 true + source file 需要是源文件（不能是jar包）</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;1-什么是-bootjar&quot;&gt;&lt;a href=&quot;#1-什么是-bootjar&quot; class=&quot;headerlink&quot; title=&quot;1. 什么是 bootjar&quot;&gt;&lt;/a&gt;1. 什么是 bootjar&lt;/h2&gt;&lt;p&gt;要理解 boojar 首先得理解 &lt;a href
      
    
    </summary>
    
    
      <category term="Android系统开发入门" scheme="http://qiushao.net/categories/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8/"/>
    
    
      <category term="Android" scheme="http://qiushao.net/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>hisi Camera 开发--HiMPP媒体处理软件开发基本概念</title>
    <link href="http://qiushao.net/2020/05/17/IPCamera/himpp-overview/"/>
    <id>http://qiushao.net/2020/05/17/IPCamera/himpp-overview/</id>
    <published>2020-05-17T13:30:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<h2 id="1-HIMPP平台架构简介"><a href="#1-HIMPP平台架构简介" class="headerlink" title="1. HIMPP平台架构简介"></a>1. HIMPP平台架构简介</h2><p>海思提供的媒体处理软件平台(Media Process Platform,简称 MPP),可支持应用软件快速开发。该平台对应用软件屏蔽了芯片相关的复杂的底层处理,并对应用软件直接提供 MPI(MPP Program Interface)接口完成相应功能。该平台支持应用软件快速开发以下功能:输入视频捕获、H.265/H.264/JPEG 编码、H.265/H.264/JPEG 解码、视频输出显示、视频图像前处理(包括去噪、增强、锐化)、图像拼接、图像几何矫正、智能、音频捕获及输出、音频编解码等功能。</p><h2 id="2-系统分层结构"><a href="#2-系统分层结构" class="headerlink" title="2. 系统分层结构"></a>2. 系统分层结构</h2><p>系统大概层次结构如下：<br><img src="/images/hi3516%E7%B3%BB%E7%BB%9F%E5%88%86%E5%B1%82%E6%9E%B6%E6%9E%84.png" alt="hi3516系统分层架构"></p><ul><li>硬件层<br>硬件层由 Hi35xx 芯片加上必要的外围器件构成。外围器件包括 Flash、DDR、视频 Sensor 或 AD、音频 AD 等。</li><li>操作系统层<br>基于 Linux 或 Huawei LiteOS 的 OS 系统。</li><li>操作系统适配层<br>提供操作系统系统调用基础函数,屏蔽操作系统差异,支持媒体处理平台运行在不同的操作系统上,或相同操作系统不同版本。</li><li>媒体处理平台<br>基于操作系统适配层,控制芯片完成相应的媒体处理功能。它对应用层屏蔽了硬件处理细节,并为应用层提供 API 接口完成相应功能。</li><li>其他驱动<br>除媒体处理平台外,海思为 Hi35xx 芯片的其他相关硬件处理单元提供了相应的驱动,包括 CIPHER、RTC 等驱动。</li><li>应用层<br>基于海思媒体处理平台及其他驱动,由用户开发的应用软件系统。</li></ul><p>以上这些分层，我们主要关注的是 HIMPP 媒体软件处理平台和应用层。我们必须对 HIMPP 这层的内容进行深入的学习了解，才能在其基础之上开发应用。</p><h2 id="3-HIMPP-数据处理流程"><a href="#3-HIMPP-数据处理流程" class="headerlink" title="3. HIMPP 数据处理流程"></a>3. HIMPP 数据处理流程</h2><p>海思媒体处理平台主要流程分为视频处理及音频处理两条路线：<br><img src="/images/hi3516%E6%95%B0%E6%8D%AE%E6%B5%81%E7%A8%8B%E5%9B%BE.png" alt="hi3516数据流程图"></p><p>我们先来大概了解一下各个模块的作用：</p><ul><li>sensor: 包括镜头，感光芯片等器件，主要作用是光电信号转换。</li><li>disk: 为硬盘存储文件。视频的来源既可以是从 sensor 中采集的，也可以是从文件中读取出来的。视频回放时走的是这条通路。</li><li>VI：视频采集模块。捕获视频图像，可对其做剪切、缩放、镜像等处理，并输出多路不同分辨率的图像数据。</li><li>VDEC：视频解码模块。对编码后的视频码流进行解码，并将解析后的图像数据送 VPSS 进行图像处理或直接送 VO 显示。可对 H.264/H.265/VC1/MPEG4/MPEG2/AVS 格式的视 频码流进行解码。</li><li>VPSS：图像处理子系统。接收 VI 或 VDEC 发送过来的图像，可对图像进行去噪、图像增强、锐化等处理，并实现同源输出多路不同分辨率的图像数据用于编码、预览或抓拍。</li><li>NNIE：AI算法模块。从 VPSS 拿到帧数据，然后进行人脸检测，识别等各种 AI 算法。算法结果可以通过 histreaming 等协议发送给客户端 app。</li><li>VENC：编码模块。接收 VI 捕获并经 VPSS 处理后输出的图像数据，可叠加用户通过 Region 模块设置的 OSD 图像，然后按不同协议进行编码并输出相应码流(H264,MJPEG等)。然后可以通过各种视频传输协议进行传输。比如 uvc, rtsp, http 等。也可以保存到文件，后续可以进行回放。</li><li>VO：视频输出模块。接收 VPSS 处理后的输出图像，可进行播放控制等处理，最后按用户配置的输出协议输出给外围视频设备 HDMI 等。</li><li>mic：麦克风。主要作用是声电信号转换。</li><li>AI: 音频采集模块。捕获音频数据，然后 AENC 模块支持按多种音频协议对其进行编码，最后输出音频码流。</li><li>用户从网络或外围存储设备获取的音频码流可直接送给 ADEC 模块，ADEC 支持 解码多种不同的音频格式码流，解码后数据送给 AO 模块即可播放声音。</li></ul><p>我们对以上各模块的作用及数据处理流程一定要非常熟悉。因为应用层的开发基本上都是围绕着这个流程来进行编码实现的。应用层只需要配置好各模块的参数，启动，然后把各模块连接起来就行。</p><h2 id="4-系统初始化"><a href="#4-系统初始化" class="headerlink" title="4. 系统初始化"></a>4. 系统初始化</h2><p>系统控制模块根据芯片特性，完成硬件各个部件的复位、基本初始化工作，同时负责完成 MPP（Media Process Platform 媒体处理平台）系统各个业务模块的初始化、去初始化以及管理 MPP 系统各个业务模块的工作状态、提供当前 MPP 系统的版本信息、提供大块物理内存管理等功能。应用程序启动 MPP 业务前，必须完成 MPP 系统初始化工作。同理，应用程序退出 MPP 业务后，也要完成 MPP 系统去初始化工作，释放资源。</p><h3 id="4-1-视频缓冲池-VB"><a href="#4-1-视频缓冲池-VB" class="headerlink" title="4.1 视频缓冲池 VB"></a>4.1 视频缓冲池 VB</h3><p>视频缓存池主要向媒体业务提供大块物理内存管理功能，负责内存的分配和回收，充分发挥内存缓存池的作用，让物理内存资源在各个媒体处理模块中合理使用。一组大小相同、物理地址连续的缓存块组成一个视频缓存池。<br>视频输入通道需要使用公共视频缓存池。所有的视频输入通道都可以从公共视频缓存池中获取视频缓存块用于保存采集的图像（如图中所示从公共视频缓存池 B 中获取视频缓存块 Bm）。由于视频输入通道不提供创建和销毁公共视频缓存池功能，因此，在系统初始化之前，必须为视频输入通道配置公共视频缓存池。</p><p>典型的公共视频缓存池数据流图如下：</p><p><img src="/images/vb%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B.png" alt="vb数据处理流程"></p><p>VI 从公共视频缓存池 B 中获取视频缓存块 Bm,缓存块 Bm 经 VI发送给 VPSS,输入缓存块 Bm 经过 VPSS 处理之后被释放回公共视频缓存池。假设 VPSS 通道的工作模式是 USER, 则 VPSS 通道 0 从公共视频缓存池 B 中获取缓存块 Bi 作为输出图像缓存 buffer 发送给 VENC,VPSS 通道 1 从公共视频缓存池 B 中获取缓存块 Bk 作为输出图像缓存 buffer 发送给 VO,Bi 经 VENC 编码完之后释放回公共视频缓存池,Bk 经 VO 显示完之后释放回公共视频缓存池。</p><h2 id="5-VI"><a href="#5-VI" class="headerlink" title="5. VI"></a>5. VI</h2><p>视频输入(VI)模块实现的功能:通过 MIPI Rx(含 MIPI 接口、LVDS 接口和 HISPI 接口),SLVS-EC,BT.1120,BT.656,BT.601,DC 等接口接收视频数据。VI 将接收到的数据存入到指定的内存区域,在此过程中,VI 可以对接收到的原始视频图像数据进行处理, 实现视频数据的采集。<br>VI 在软件层次上划分 4 个部分：<br><img src="/images/vi%E8%BD%AF%E4%BB%B6%E9%80%9A%E8%B7%AF.png" alt="vi软件通路"><br>VI 从软件上划分了输入设备(DEV), 输入 PIPE(图示为物理 PIPE,虚拟 PIPE 只包含ISP_BE)、物理通道(PHY_CHN)、扩展通道(EXT_CHN)四个层级。</p><ul><li>视频输入设备<br>视频输入设备支持若干种时序输入,负责对时序进行解析。</li><li>视频输入物理 PIPE<br>视频输入 PIPE 绑定在设备后端,负责设备解析后的数据再处理。包含了 ISP 的相关处理功能,主要是对图像数据进行流水线处理,输出YUV 图像格式给通道。</li><li>视频输入虚拟 PIPE<br>视频输入虚拟 PIPE 不绑定设备,负责其他模块或用户发送过来的数据再处理。</li><li>视频物理通道<br>物理通道负责将最终处理后的数据输出到 DDR,在真正将数据输出到 DDR 之前,它可以实现裁剪等功能。Hi3519AV100 VI 的一个 PIPE 包含 2 个物理通道。CH0 具有裁剪、压缩等功能。CH1 具有缩小的功能,支持 8 个扩展通道。</li><li>视频扩展通道<br>扩展通道是物理通道的扩展,扩展通道具备缩放、裁剪、鱼眼矫正功能,它通过绑定物理通道,将物理通道输出作为自己的输入,然后输出用户设置的目标图像。</li></ul><p>VI 的硬件通路如下：<br><img src="/images/vi%E7%A1%AC%E4%BB%B6%E9%80%9A%E8%B7%AF.png" alt="vi硬件通路"></p><ul><li>镜头畸变校正(LDC)<br>镜头畸变校正,一些低端镜头容易产生图像畸变,需要根据畸变程度对其图像进行校正。</li><li>DIS<br>DIS 模块通过比较当前图像与前两帧图像采用不同自由度的防抖算法计算出当前图像在各个轴方向上的抖动偏移向量,然后根据抖动偏移向量对当前图像进行校正,从而起到防抖的效果。</li><li>BAS<br>Bayer scaling,即 Bayer 域缩放。</li><li>低延时<br>低延时指图像写出指定的行数到 DDR 后,VI 上报一个中断,把图像发给后端模块处理,可以减少延时,且硬件会有机制保证图像是先写后读,不会出现读图像错误。</li><li>提前上报中断<br>提前上报中断指图像写出指定的行数到 DDR 后,VI 上报一个中断,把图像发给后端模块处理,可以减少延时,但没有和低延时一样的硬件机制保证后端模块读图像不会出错。</li></ul><h2 id="6-VPSS"><a href="#6-VPSS" class="headerlink" title="6. VPSS"></a>6. VPSS</h2><p>VPSS(Video Process Sub-System)是视频处理子系统,支持的具体图像处理功能包括 FRC(Frame Rate Control)、CROP、Sharpen、3DNR、Scale、像素格式转换、LDC、Spread、固定角度旋转、任意角度旋转、鱼眼校正、Cover/Coverex、Overlayex、Mosaic、Mirror/Flip、HDR、Aspect Ratio、压缩解压等。</p><p>vpss 的数据处理流程如下：<br><img src="/images/vpss%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B.png" alt="vpss数据处理流程"></p><p>vpss 相关基本概念：</p><ul><li>GROUP<br>VPSS 对用户提供组(GROUP)的概念。最大个数请参见 VPSS_MAX_GRP_NUM 定义,各 GROUP 分时复用 VPSS 硬件,硬件依次处理各个组提交的任务。</li><li>CHANNEL<br>VPSS 组的通道。通道分为 2 种:物理通道和扩展通道。VPSS 硬件提供多个物理通道,每个通道具有缩放、裁剪等功能。扩展通道具备裁剪、缩放功能,它通过绑定物理通道,将物理通道输出作为自己的输入,把图像裁剪、缩放成用户设置的目标分辨率输出。</li><li>PIPE<br>VPSS 组的管道,取值只能为 0。</li><li>FRC<br>帧率控制,分为 2 种:组帧率控制和通道帧率控制。<ul><li>组帧率控制:用于控制各 GROUP 对输入图像的接收。</li><li>通道帧率控制:用于控制各个物理通道和扩展通道图像的处理。</li></ul></li><li>CROP<br>裁剪,分为 3 种:组裁剪、物理通道裁剪以及扩展通道裁剪。<ul><li>组裁剪,VPSS 对输入图像进行裁剪。</li><li>物理通道裁剪,VPSS 对各个物理通道的输出图像进行裁剪。</li><li>扩展通道裁剪,VPSS 调用 VGS 对扩展通道的输出图像进行裁剪</li></ul></li><li>Scale<br>缩放,对图像进行缩小放大。物理通道水平、垂直最大支持 15 倍缩小,最大支持 16 倍放大;扩展通道水平、垂直最大支持 30 倍缩小,最大支持 16 倍放大。</li><li>Mirror/Flip<br>Mirror 即水平镜像,Flip 即上下翻转。可使用 Mirror+Flip 实现 180°旋转。</li><li>Mosaic<br>马赛克,对 VPSS 的输出图像填充马赛克块。</li><li>Cover<br>视频遮挡区域,对 VPSS 的输出图像填充纯色块。</li><li>Overlayex<br>视频叠加区域,调用 VGS 对 VPSS 通道的输出图像叠加位图,支持位图格式 ARGB4444、ARGB1555、ARGB8888、2BPP(仅 Hi3516EV200/Hi3516EV300 支持)。</li><li>3DNR<br>去噪。通过参数配置,把图像中的高斯噪声去除,使得图像变得平滑,有助于降低编码码率。Hi3559AV100ES/Hi3559AV100 不支持。</li><li>固定角度旋转<br>支持 0 度、90 度、180 度以及 270 度固定角度的旋转功能。</li><li>任意角度旋转<br>支持任意角度的旋转功能。Hi3559AV100ES/Hi3559AV100/Hi3516EV200 不支持。</li><li>LDC<br>对输入图像做镜头畸变校正。Hi3559AV100ES/Hi3559AV100/Hi3516EV200 不支<br>持。</li><li>Spread<br>对输入图像做展宽处理。Hi3559AV100ES/Hi3559AV100/Hi3516EV200 不支持。</li><li>鱼眼校正<br>仅 Hi3519AV100/Hi3516CV500/Hi3516AV300/Hi3516DV300 支持。</li><li>Aspect Ratio<br>幅形比,指定输出画面相对于输入画面的宽高纵横比。</li></ul><h3 id="6-1-从-VPSS-模块中获取帧数据"><a href="#6-1-从-VPSS-模块中获取帧数据" class="headerlink" title="6.1 从 VPSS 模块中获取帧数据"></a>6.1 从 VPSS 模块中获取帧数据</h3><p>我们需要进行 AI 算法运行的时候，需要从 VPSS 模块中获取帧数据，输入给算法模块进行处理。帧数据的获取大概如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 获取帧数据，保存到 pstVideoFrame 中。</span></span><br><span class="line"><span class="function">HI_S32 <span class="title">HI_MPI_VPSS_GetChnFrame</span><span class="params">(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, VIDEO_FRAME_INFO_S *pstVideoFrame, HI_S32 s32MilliSec)</span></span>;\</span><br><span class="line"><span class="comment">// 2. 第一步中获取到的数据地址是物理地址，需要调用 mmap 把物理地址映射成逻辑地址。</span></span><br><span class="line"><span class="function">HI_VOID *<span class="title">HI_MPI_SYS_Mmap</span><span class="params">(HI_U64 u64PhyAddr, HI_U32 u32Size)</span></span>;</span><br><span class="line"><span class="comment">// 3. 数据使用</span></span><br><span class="line">将帧数据输入给算法模块使用。</span><br><span class="line"><span class="comment">// 4. 释放帧数据</span></span><br><span class="line"><span class="function">HI_S32 <span class="title">HI_MPI_VPSS_ReleaseChnFrame</span><span class="params">(VPSS_GRP VpssGrp, VPSS_CHN VpssChn, <span class="keyword">const</span> VIDEO_FRAME_INFO_S *pstVideoFrame)</span></span>;</span><br></pre></td></tr></table></figure><h2 id="7-VO"><a href="#7-VO" class="headerlink" title="7. VO"></a>7. VO</h2><p>VO(Video Output,视频输出)模块主动从内存相应位置读取视频和图形数据,并通过相应的显示设备（HDMI等）输出视频和图形。</p><h2 id="8-VENC"><a href="#8-VENC" class="headerlink" title="8. VENC"></a>8. VENC</h2><p>视频编码模块支持多路实时编码,且每路编码独立,编码协议和编码 profile 可以不同。本模块支持视频编码同时,调度 Region 模块对编码图像内容进行叠加和遮挡。<br>视频编码流程如下：<br><img src="/images/venc%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B.png" alt="venc数据处理流程"></p><p>其中编码通道处理流程如下：<br><img src="/images/venc%E7%BC%96%E7%A0%81%E9%80%9A%E9%81%93.png" alt="venc编码通道"></p><p>venc 相关基本概念：</p><ul><li>编码通道<br>编码通道作为基本容器,保存编码通道的多种用户设置和管理编码通道的多种内部资源。编码通道完成图像转化为码流的功能,具体由码率控制器和编码器协同完成。这里的编码器指的是狭义上的编码器,只完成编码功能。码率控制器提供了对编码参数的控制和调整,从而对输出码率进行控制。</li><li>码率控制<br>码率控制器实现对编码码率进行控制。从信息学的角度分析,图像的压缩比越低,压缩图像的质量越高;图像压缩比例越高,压缩图像的质量越低。对于场景变化的真实场景,图像质量稳定,编码码率会波动;编码码率稳定,图像质量会波动。以 H.264 编码为例,通常图像 Qp 越低,图像的质量越好,码率越高;图像 Qp 越高,图像质量越差,码率越低。<br>码率控制是针对连续的编码码流而言,所以,JPEG 协议编码通道不包括码率控制功能。<br>码率控制器分别提供了对 MJPEG 协议编码通道 CBR、VBR、FIXQP 三种码率控制模式,H.264/H.265 协议编码通道 CBR、VBR、AVBR、QVBR、CVBR、FIXQP、QPMAP 七种码率控制模式,对图像质量和码率进行调节,对于 H.264/H.265/协议通道的 QPMAP 模式,码控的策略由用户决定。</li></ul><h3 id="8-1-从-VENC-模块中获取帧数据"><a href="#8-1-从-VENC-模块中获取帧数据" class="headerlink" title="8.1 从 VENC 模块中获取帧数据"></a>8.1 从 VENC 模块中获取帧数据</h3><p>VENC 编码完成后，需要 uvc, rtsp 等模块从 VENC 中获取帧数据，然后对外发送。帧数据的获取流程大概如下：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 查询 VENC 编码状态， VENC_CHN_STATUS_S.u32CurPacks 为当前帧的 package 数量。为 0 时说明还没编码好，等会再查询。</span></span><br><span class="line"><span class="function">HI_S32 <span class="title">HI_MPI_VENC_QueryStatus</span><span class="params">(VENC_CHN VeChn, VENC_CHN_STATUS_S *pstStatus)</span></span>;</span><br><span class="line"><span class="comment">// 2. 获取帧数据，保存到 pstStream 中。</span></span><br><span class="line"><span class="function">HI_S32 <span class="title">HI_MPI_VENC_GetStream</span><span class="params">(VENC_CHN VeChn, VENC_STREAM_S *pstStream, HI_S32 s32MilliSec)</span></span>;</span><br><span class="line"><span class="comment">// 3. 数据使用</span></span><br><span class="line">将帧数据输入给 uvc, rtsp 等模块。</span><br><span class="line"><span class="comment">// 4. 释放帧数据</span></span><br><span class="line"><span class="function">HI_S32 <span class="title">HI_MPI_VENC_ReleaseStream</span><span class="params">(VENC_CHN VeChn, VENC_STREAM_S *pstStream)</span></span>;</span><br></pre></td></tr></table></figure><h2 id="9-VI-VPSS-工作模式"><a href="#9-VI-VPSS-工作模式" class="headerlink" title="9. VI, VPSS 工作模式"></a>9. VI, VPSS 工作模式</h2><p>VI 和 VPSS 各自的工作模式分为在线,离线,并行模式,工作模式说明如下：</p><table><thead><tr><th>模式</th><th>VI_CAP 与 VI_PROC</th><th>VI_PROC 与 VPSS</th></tr></thead><tbody><tr><td>在线模式</td><td>VI_CAP 与 VI_PROC 之间在线数据流传输,此模式下 VI_CAP不会写出 RAW 数据到 DDR,而是直接把数据流送给VI_PROC。</td><td>VI_PROC 与 VPSS 之间的在线数据流传输,在此模式下 VI_PROC不会写出 YUV 数据到 DDR,而是直接把数据流送给 VPSS。</td></tr><tr><td>离线模式</td><td>VI_CAP 写出 RAW 数据到DDR,然后 VI_PROC 从 DDR 读取 RAW 数据进行后处理。</td><td>VI_PROC 写出 YUV 数据到DDR,然后 VPSS 从 DDR 读取 YUV 数据进行后处理。</td></tr></tbody></table><p>从以上的描述来看在线模式和离线模式的主要差别是是否将数据写出 DDR。在线模式时 VI 进行时序解析后直接在芯片内部将数据传递到VPSS，中间无DDR 写出的过程。在线模式可以省一定的带宽和内存，降低端到端的延时。但是，在线模式时，因为VI 不写出数据到DDR，无法进行CoverEx、OverlayEx、Rotate、LDC 等操作，需要在VPSS 各通道写出后再进行Rotate/LDC 等处理，而且有些功能只在离线下能支持，比如DIS。所以使用在线模式还是离线模式需要根据具体需求来决定。如果追求低延时，那自然要使用在线模式。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;1-HIMPP平台架构简介&quot;&gt;&lt;a href=&quot;#1-HIMPP平台架构简介&quot; class=&quot;headerlink&quot; title=&quot;1. HIMPP平台架构简介&quot;&gt;&lt;/a&gt;1. HIMPP平台架构简介&lt;/h2&gt;&lt;p&gt;海思提供的媒体处理软件平台(Media Proc
      
    
    </summary>
    
    
      <category term="Camera" scheme="http://qiushao.net/categories/Camera/"/>
    
    
      <category term="Hi3519AV100" scheme="http://qiushao.net/tags/Hi3519AV100/"/>
    
      <category term="Camera" scheme="http://qiushao.net/tags/Camera/"/>
    
  </entry>
  
  <entry>
    <title>arm linux 开启 coredump file</title>
    <link href="http://qiushao.net/2020/05/01/Linux/linux-enable-coredump/"/>
    <id>http://qiushao.net/2020/05/01/Linux/linux-enable-coredump/</id>
    <published>2020-05-01T13:30:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<h2 id="1-基本概念"><a href="#1-基本概念" class="headerlink" title="1. 基本概念"></a>1. 基本概念</h2><p>之前一直在做 Android 相关的工作，在 Android 系统上无论是 java 进程还是 c++ 进程崩溃的时候都会有堆栈信息可以追溯是哪里挂了。java 进程挂的话，logcat 里面直接有错误堆栈信息。c++ 进程挂的话，/data/tomstone 目录里面会生成 coredump 信息。里面有错误堆栈，但错误堆栈只是一个地址调用而已，需要使用 addr2line 工具来将地址转换成文件行数。<br>现在纯 Linux 环境中开发 Camera 应用。当程序运行在过程中崩溃时，就只提示了一个进程 Abort 了。然后啥信息都没有。鬼才知道哪里出问题了。这个时候就得 coredump 出场了。Linux 系统默认是把 coredump 关闭的。打开 coredump 后，进程崩溃时，操作系统会将程序当时的内存状态记录下来，保存在一个文件中，这种行为就叫做 Core Dump（中文有的翻译成“核心转储”)。我们可以认为 core dump 是“内存快照”，但实际上，除了内存信息之外，还有些关键的程序运行状态也会同时 dump 下来，例如寄存器信息（包括程序指针、栈指针等）、内存管理信息、其他处理器和操作系统状态和信息。core dump 对于编程人员诊断和调试程序是非常有帮助的，因为对于有些程序错误是很难重现的，例如指针异常，而 core dump 文件可以再现程序出错时的情景。</p><h2 id="2-开启-core-dump"><a href="#2-开启-core-dump" class="headerlink" title="2. 开启 core dump"></a>2. 开启 core dump</h2><p>我们先查询一下 coredump 是否打开了</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">&#x2F; # ulimit -a</span><br><span class="line">-f: file size (blocks)             unlimited</span><br><span class="line">-t: cpu time (seconds)             unlimited</span><br><span class="line">-d: data seg size (kb)             unlimited</span><br><span class="line">-s: stack size (kb)                8192</span><br><span class="line">-c: core file size (blocks)        0</span><br><span class="line">-m: resident set size (kb)         unlimited</span><br><span class="line">-l: locked memory (kb)             64</span><br><span class="line">-p: processes                      1832</span><br><span class="line">-n: file descriptors               1024</span><br><span class="line">-v: address space (kb)             unlimited</span><br><span class="line">-w: locks                          unlimited</span><br><span class="line">-e: scheduling priority            0</span><br><span class="line">-r: real-time priority             0</span><br><span class="line">&#x2F; #</span><br></pre></td></tr></table></figure><p>ulimit -a 命令为查询当前的系统限制规则。 其中 <code>core file size          (blocks, -c) 0</code> 表示 coredump 文件的大小为限制为 0，即关闭 coredump 功能。<br>通过以下命令来打开 coredump 功能：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">&#x2F; # ulimit -c unlimited</span><br><span class="line">&#x2F; # ulimit -a</span><br><span class="line">-f: file size (blocks)             unlimited</span><br><span class="line">-t: cpu time (seconds)             unlimited</span><br><span class="line">-d: data seg size (kb)             unlimited</span><br><span class="line">-s: stack size (kb)                8192</span><br><span class="line">-c: core file size (blocks)        unlimited</span><br><span class="line">-m: resident set size (kb)         unlimited</span><br><span class="line">-l: locked memory (kb)             64</span><br><span class="line">-p: processes                      1832</span><br><span class="line">-n: file descriptors               1024</span><br><span class="line">-v: address space (kb)             unlimited</span><br><span class="line">-w: locks                          unlimited</span><br><span class="line">-e: scheduling priority            0</span><br><span class="line">-r: real-time priority             0</span><br><span class="line">&#x2F; #</span><br></pre></td></tr></table></figure><p>我们可以看到 core file size 的限制变为 unlimited 为。也就是没有限制。这样就可以打开 coredump 功能了。<br>默认情况下 coredump 文件的存放路径为当前目录。我们可以通过以下命令来修改 coredump 的存放路径：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">echo 0 &gt; &#x2F;proc&#x2F;sys&#x2F;kernel&#x2F;core_uses_pid</span><br><span class="line">echo &quot;&#x2F;data&#x2F;core_%e_%p&quot; &gt; &#x2F;proc&#x2F;sys&#x2F;kernel&#x2F;core_pattern</span><br></pre></td></tr></table></figure><p>以下命令把 coredump 文件存放到了 /data 目录</p><p>其中 %e, %p 的解析如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">%p - insert pid into filename 添加pid</span><br><span class="line">%u - insert current uid into filename 添加当前uid</span><br><span class="line">%g - insert current gid into filename 添加当前gid</span><br><span class="line">%s - insert signal that caused the coredump into the filename 添加导致产生core的信号</span><br><span class="line">%t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间</span><br><span class="line">%h - insert hostname where the coredump happened into filename 添加主机名</span><br><span class="line">%e - insert coredumping executable name into filename 添加命令名</span><br></pre></td></tr></table></figure><p>在测试过程中，我们需要一直打开 coredump 功能的，因此我们把以下命令加入到 /etc/profile 中。以便每次开机都生效</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">#</span><span class="bash"> <span class="built_in">enable</span> coredump <span class="keyword">for</span> debug</span></span><br><span class="line">echo "enable coredump file"</span><br><span class="line">ulimit -c unlimited </span><br><span class="line">echo 0 &gt; /proc/sys/kernel/core_uses_pid</span><br><span class="line">echo "/data/core_%e_%p" &gt; /proc/sys/kernel/core_pattern</span><br></pre></td></tr></table></figure><h2 id="3-gdb-调试-core-dump"><a href="#3-gdb-调试-core-dump" class="headerlink" title="3. gdb 调试 core dump"></a>3. gdb 调试 core dump</h2><p>我们来写一个会崩溃的程序试试看会不会产生 coredump 文件。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">get_value</span><span class="params">(<span class="keyword">int</span> *value)</span> </span>&#123;</span><br><span class="line">    *value = <span class="number">31</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> *value = <span class="literal">nullptr</span>;</span><br><span class="line">    get_value(value);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"value = %d\n"</span>, *value);</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><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></pre></td><td class="code"><pre><span class="line">/data # ls</span><br><span class="line">test</span><br><span class="line">/data # ./test </span><br><span class="line">Segmentation fault (core dumped)</span><br><span class="line">/data # ls</span><br><span class="line">core_test_252  test</span><br><span class="line">/data #</span><br></pre></td></tr></table></figure><p>我们看到产生了一个 coredump 文件 <code>core_test_252</code>。接下来我们需要使用 gdb 来查看 coredump 文件里面的错误堆栈。</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></pre></td><td class="code"><pre><span class="line">/data # ls</span><br><span class="line">core_test_252  test</span><br><span class="line">/data # gdb test core_test_252 </span><br><span class="line">GNU gdb (GDB) 7.9.1</span><br><span class="line">Copyright (C) 2015 Free Software Foundation, Inc.</span><br><span class="line">License GPLv3+: GNU GPL version 3 or later &lt;http://gnu.org/licenses/gpl.html&gt;</span><br><span class="line">This is free software: you are free to change and redistribute it.</span><br><span class="line">There is NO WARRANTY, to the extent permitted by law.  Type "show copying"</span><br><span class="line">and "show warranty" for details.</span><br><span class="line">This GDB was configured as "arm-himix200-linux".</span><br><span class="line">Type "show configuration" for configuration details.</span><br><span class="line">For bug reporting instructions, please see:</span><br><span class="line">&lt;http://www.gnu.org/software/gdb/bugs/&gt;.</span><br><span class="line">Find the GDB manual and other documentation resources online at:</span><br><span class="line">&lt;http://www.gnu.org/software/gdb/documentation/&gt;.</span><br><span class="line">For help, type "help".</span><br><span class="line">Type "apropos word" to search for commands related to "word"...</span><br><span class="line">Reading symbols from test...done.</span><br><span class="line">[New LWP 252]</span><br><span class="line"></span><br><span class="line">warning: Could not load shared library symbols for linux-vdso.so.1.</span><br><span class="line">Do you need "set solib-search-path" or "set sysroot"?</span><br><span class="line">Core was generated by `./test'.</span><br><span class="line">Program terminated with signal SIGSEGV, Segmentation fault.</span><br><span class="line"><span class="meta">#</span><span class="bash">0  0x00010530 <span class="keyword">in</span> get_value (value=0x0) at main.cpp:4</span></span><br><span class="line">4       main.cpp: No such file or directory.</span><br><span class="line">(gdb) bt</span><br><span class="line"><span class="meta">#</span><span class="bash">0  0x00010530 <span class="keyword">in</span> get_value (value=0x0) at main.cpp:4</span></span><br><span class="line"><span class="meta">#</span><span class="bash">1  0x00010568 <span class="keyword">in</span> main (argc=1, argv=0xbeedfe34) at main.cpp:9</span></span><br><span class="line">(gdb)</span><br></pre></td></tr></table></figure><p>使用 <code>gdb test core_test_252</code> 命令来加载 coredump 文件。 gdb execute_file_path coredump_file_path<br>加载完之后，使用 bt 指令来打印错误堆栈。bt 即 backtrace 的简写。<br>从上面的分析结果我们知道发生错误的地方在 main.cpp 第 4 行代码。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;1-基本概念&quot;&gt;&lt;a href=&quot;#1-基本概念&quot; class=&quot;headerlink&quot; title=&quot;1. 基本概念&quot;&gt;&lt;/a&gt;1. 基本概念&lt;/h2&gt;&lt;p&gt;之前一直在做 Android 相关的工作，在 Android 系统上无论是 java 进程还是 c++ 
      
    
    </summary>
    
    
      <category term="Linux" scheme="http://qiushao.net/categories/Linux/"/>
    
    
      <category term="Linux" scheme="http://qiushao.net/tags/Linux/"/>
    
      <category term="coredump" scheme="http://qiushao.net/tags/coredump/"/>
    
  </entry>
  
  <entry>
    <title>hdmi edid 分辨率设置</title>
    <link href="http://qiushao.net/2020/04/22/hdmi/hdmi-edid-reslution-setting/"/>
    <id>http://qiushao.net/2020/04/22/hdmi/hdmi-edid-reslution-setting/</id>
    <published>2020-04-22T10:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.748Z</updated>
    
    <content type="html"><![CDATA[<p>以下说明截图基于 R_980mgr_4.16.39.13141_Win.msi 工具(以下简称 980)，最好先安装这个工具，然后对着说明进行操作。<br>关于 edid 的各字段这里不再作解析，请网上查阅资料。可以参考以下文章:</p><p><a href="https://www.cnblogs.com/beyond-rxl/archive/2018/07/05/9266997.html" target="_blank" rel="noopener">https://www.cnblogs.com/beyond-rxl/archive/2018/07/05/9266997.html</a><br><a href="https://blog.csdn.net/Gplusplus/article/details/52597033" target="_blank" rel="noopener">https://blog.csdn.net/Gplusplus/article/details/52597033</a></p><p>本文只解析说明如何修改 edid 以支持目标分辨率。设置推荐分辨率。</p><h2 id="1-导入-edid"><a href="#1-导入-edid" class="headerlink" title="1. 导入 edid"></a>1. 导入 edid</h2><p>打开 980, 切换到 EDID/DPCD 标签页，右键 EDID - USER 菜单，选择 import 菜单。然后选择需要编辑的 EDID 文件即可。<br><img src="/images/edid-import.png" alt="edid-import"></p><p>980 的界面菜单大致功能划分如下：<br><img src="/images/980-introduce.png" alt="980-introduce"></p><h2 id="2-分辨率设置"><a href="#2-分辨率设置" class="headerlink" title="2. 分辨率设置"></a>2. 分辨率设置</h2><p>在EDID中定义了几种 Timing，分别是 Established Timing、Standard Timing、Detailed Timing 还有一些额外的Timing，比如GTF和CVT Timing。<br>其中 Detailed Timing 又包含了： Preferred Timing， 普通 Detailed Timing， Extension Block中的Detailed Timing。这三者的格式基本是一致的。</p><p>对于显示器来说，支持的这些 Timing 有一个优先级：<br>Preferred Timing &gt; 普通 Detailed Timing &gt; Extension Block 中的 Detailed Timing &gt; CVT &gt; Standard Timing &gt; Established Timing</p><p>下面对这几种 Timing 分别解析。</p><h3 id="2-1-Established-Timings"><a href="#2-1-Established-Timings" class="headerlink" title="2.1. Established Timings"></a>2.1. Established Timings</h3><p>提供一些基本固定的VESA, Apple, Mac, IBM VGA等输出的Timing。这个比较简单，如果确认芯片支持这些分辨率的话，直接打钩就行了。不需要解析。<br><img src="/images/Established-Timings.png" alt="Established-Timings"></p><h3 id="2-2-Standard-Timings"><a href="#2-2-Standard-Timings" class="headerlink" title="2.2. Standard Timings"></a>2.2. Standard Timings</h3><p>总共16个字节，提供最多8种分辨率的识别，每一种分辨率都是由2个字节的模式格式和刷新率得到的。这些分辨率应该是对 Established Timings 的一种扩充。<br><img src="/images/Standard-Timings.png" alt="Standard Timings"></p><p>每个字段的意义解析如下：</p><ul><li>Unused : 是否使用这个分辨率。</li><li>H-Active Pixels(256-2288 by 8) :水平分辨率, 例如 1280</li><li>Image Aspect Ratio: 画面比例(水平分辨率 : 垂直分辨率), 例如 5:4 </li><li>Refresh Rate(60 - 123 HZ): 刷新率，也就是帧率，例如 60</li></ul><p>其中水平分辨率和画面比例共同决定了分辨率。如上面的例子。水平分辨率为 1280, 画面比例为 5 : 4。那我们就可以算出垂直分辨率：<br>水平分辨率 / 垂直分辨率 = 5 / 4<br>–&gt; 垂直分辨率 = 水平分辨率 * 4 / 5 = 1280 * 4 / 5 = 1024</p><p>也就是整个分辨率块定义的分辨率为： 1280 * 1024 60HZ 。</p><h3 id="2-3-Detailed-Timing"><a href="#2-3-Detailed-Timing" class="headerlink" title="2.3 Detailed Timing"></a>2.3 Detailed Timing</h3><h4 id="2-3-1-Preferred-Timing-推荐分辨率"><a href="#2-3-1-Preferred-Timing-推荐分辨率" class="headerlink" title="2.3.1 Preferred Timing (推荐分辨率)"></a>2.3.1 Preferred Timing (推荐分辨率)</h4><p>推荐分辨率就是 hdmi 设备优先输出的分辨率。当用户没有手动设置过分辨率时，且 hdmi 设备支持时，输出的分辨率就是推荐分辨率。<br>如果是 windows 电脑接入电视的话，我们在设置分辨的时候分看到分辨率列表里面有(推荐) 的字样提示：<br><img src="/images/windows-reslution-setting.png" alt="windows-reslution-setting"></p><p>Preferred Timing 其实用的就是第一个 Detailed Timing:<br><img src="/images/Preferred-Timing.png" alt="Preferred-Timing"></p><h4 id="2-3-2-普通-Detailed-Timing"><a href="#2-3-2-普通-Detailed-Timing" class="headerlink" title="2.3.2 普通 Detailed Timing"></a>2.3.2 普通 Detailed Timing</h4><p>各字段的描述跟 Preferred Timing 一样的。 目前其实也只有 Descriptor 2。<br>普通 Detailed Timing Descriptions。共72个字节分为四个详细时序描述块（DTD），每部分18个字节，故最多可以对四个TIMING 进行详细描述。一般来说 Descriptor 1/2 用来描述分辨率, 其中 Descriptor 1 作为推荐分辨率。 Descriptor 3/4 用来描述显示器信息（比如说显示器名）。</p><p>显示器名的设置如下：<br><img src="/images/monitor-name.png" alt="monitor-name"></p><h4 id="2-3-3-Extension-Block-中的-Detailed-Timing"><a href="#2-3-3-Extension-Block-中的-Detailed-Timing" class="headerlink" title="2.3.3 Extension Block 中的 Detailed Timing"></a>2.3.3 Extension Block 中的 Detailed Timing</h4><p>扩展块中的 Detailed Timing 跟普通 Detailed Timing 的字段是一样的。只不过是放的位置不一样而已。当前面三种方法不能囊括我们想要支持的分辨率，且 EDID 还有足够的空间的时候。我们就可以通过这种方法来添加分辨率支持。<br><img src="/images/Extension-Block-Detailed-Timing.png" alt="Extension-Block-Detailed-Timing"></p><h2 id="4-导出-edid"><a href="#4-导出-edid" class="headerlink" title="4. 导出 edid"></a>4. 导出 edid</h2><p>修改完之后，点一下 Apply 按钮，然后然后 File –&gt; Export， 选择 bin 形式导出即可。<br><img src="/images/edid-export.png" alt="edid-export"></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;以下说明截图基于 R_980mgr_4.16.39.13141_Win.msi 工具(以下简称 980)，最好先安装这个工具，然后对着说明进行操作。&lt;br&gt;关于 edid 的各字段这里不再作解析，请网上查阅资料。可以参考以下文章:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https
      
    
    </summary>
    
    
      <category term="hdmi" scheme="http://qiushao.net/categories/hdmi/"/>
    
    
      <category term="edid" scheme="http://qiushao.net/tags/edid/"/>
    
      <category term="hdmi" scheme="http://qiushao.net/tags/hdmi/"/>
    
  </entry>
  
  <entry>
    <title>Hi3519AV100 sdk 介绍</title>
    <link href="http://qiushao.net/2020/04/15/IPCamera/Hi3519AV100-sdk-overview/"/>
    <id>http://qiushao.net/2020/04/15/IPCamera/Hi3519AV100-sdk-overview/</id>
    <published>2020-04-15T13:30:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0-文档目录结构"><a href="#0-文档目录结构" class="headerlink" title="0. 文档目录结构"></a>0. 文档目录结构</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;ReleaseDoc&#x2F;zh$ tree -L 4</span><br><span class="line">.</span><br><span class="line">├── 00.hardware  # 硬件相关文档</span><br><span class="line">├── 01.software  # 软件相关文档</span><br><span class="line">│   ├── board</span><br><span class="line">│   │   ├── Hi3519AV100 SDK 安装及升级使用说明.pdf  # sdk 安装,编译，烧录说明。本文后面的内容主要参考此文档。</span><br><span class="line">│   │   ├── Hi3519AV100与Hi3559AV100 开发包差异说明.pdf</span><br><span class="line">│   │   ├── MPP</span><br><span class="line">│   │   │   ├── HDMI 开发参考.pdf</span><br><span class="line">│   │   │   ├── HiMPP V4.0 媒体处理软件 FAQ.pdf</span><br><span class="line">│   │   │   ├── HiMPP V4.0 媒体处理软件开发参考.pdf  # camera 功能开发参考文档。进行 camera 应用开发时主要参考此文档。</span><br><span class="line">│   │   ├── OSDRV</span><br><span class="line">│   │   │   ├── Hi3519AV100╱Hi3556AV100 U-boot 移植应用开发指南.pdf</span><br><span class="line">│   │   │   ├── Hi3519AV100╱Hi3556AV100 开发环境用户指南.pdf</span><br><span class="line">│   │   │   ├── UBI 文件系统使用指南.pdf</span><br><span class="line">│   │   │   ├── 外围设备驱动 操作指南.doc</span><br><span class="line">│   │   │   └── 裸烧及非裸烧升级 使用手册.pdf</span><br><span class="line">│   │   ├── PQ</span><br><span class="line">│   │   └── SVP</span><br><span class="line">│   ├── Hi3519AV100R001 Open Source Software Notice.doc</span><br><span class="line">│   ├── HiMPP SDK 二次开发网络安全注意事项.pdf</span><br><span class="line">│   ├── pc</span><br><span class="line">├── 02.only for reference</span><br></pre></td></tr></table></figure><h2 id="1-sdk-目录结构"><a href="#1-sdk-目录结构" class="headerlink" title="1. sdk 目录结构"></a>1. sdk 目录结构</h2><p>我们拿到的 sdk 有以下文件：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519$ ll</span><br><span class="line">-rwxr-x---  1 qiushao geoclue 1870000000 Apr  3 08:54 arm-himix200-linux.part1.rar*</span><br><span class="line">-rwxr-x---  1 qiushao geoclue 1861766810 Apr  3 08:54 arm-himix200-linux.part2.rar*</span><br><span class="line">-rwxr-x---  1 qiushao geoclue 1519247343 Apr  3 08:55 Hi3519AV100R001C02SPC020.rar*</span><br><span class="line">-rwxr-x---  1 qiushao geoclue   12771415 Apr  3 08:56 HiDPU_PC_V2.0.0.0.tgz*</span><br><span class="line">-rwxr-x---  1 qiushao geoclue   22645270 Apr  3 08:56 HiIVE_PC_V2.1.0.8_64bit_sec.tgz*</span><br><span class="line">-rwxr-x---  1 qiushao geoclue 1710000000 Apr  3 08:55 HiSVP_PC_V1.2.2.0.part1.rar*</span><br><span class="line">-rwxr-x---  1 qiushao geoclue 1710000000 Apr  3 08:55 HiSVP_PC_V1.2.2.0.part2.rar*</span><br><span class="line">-rwxr-x---  1 qiushao geoclue 1710000000 Apr  3 08:54 HiSVP_PC_V1.2.2.0.part3.rar*</span><br><span class="line">-rwxr-x---  1 qiushao geoclue 1681662082 Apr  3 08:56 HiSVP_PC_V1.2.2.0.part4.rar*</span><br><span class="line">-rwxr-x---  1 qiushao geoclue      15852 Apr  3 08:55 linux-4.9.y.rar*</span><br><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519$</span><br></pre></td></tr></table></figure><p>我们目前用到的只有前面三个文件。其中 arm-himix200-linux 是编译工具链。 Hi3519AV100R001C02SPC020 是 sdk 及一些工具。<br>Hi3519AV100R001C02SPC020.rar 解压后的目录结构如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;Hi3519AV100R001C02SPC020$ tree</span><br><span class="line">.</span><br><span class="line">├── 00.hardware  # 硬件相关设计文档</span><br><span class="line">├── 01.software  # 软件相关，包括 sdk 代码和烧录工具,调试工具等</span><br><span class="line">│   ├── board</span><br><span class="line">│   │   └── Hi3519AV100_SDK_V2.0.2.0.tgz</span><br><span class="line">│   └── pc</span><br><span class="line">│       └── HiTool</span><br><span class="line">│           └── HiTool-BVT-5.2.7.zip</span><br><span class="line">└── 02.only for reference  # 其他参考文档，包括 sensor 规则书，uboot 移植相关的硬件信息表等</span><br></pre></td></tr></table></figure><p>其中 Hi3519AV100_SDK_V2.0.2.0.tgz 是最重要的 sdk 代码包。我们把它拿出来解压。目录结构如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;Hi3519AV100_SDK_V2.0.2.0$ tree</span><br><span class="line">.</span><br><span class="line">├── package</span><br><span class="line">│   ├── drv.tgz</span><br><span class="line">│   ├── mpp_smp_linux.tgz</span><br><span class="line">│   ├── osal.tgz</span><br><span class="line">│   └── osdrv.tgz</span><br><span class="line">├── scripts</span><br><span class="line">│   └── common.sh</span><br><span class="line">├── sdk.cleanup</span><br><span class="line">├── sdk.unpack</span><br><span class="line">└── smp_image_glibc</span><br><span class="line">    ├── rootfs_hi3519av100_128k.jffs2</span><br><span class="line">    ├── rootfs_hi3519av100_256k.jffs2</span><br><span class="line">    ├── rootfs_hi3519av100_2k_24bit.yaffs2</span><br><span class="line">    ├── rootfs_hi3519av100_2k_4bit.yaffs2</span><br><span class="line">    ├── rootfs_hi3519av100_32M.ext4</span><br><span class="line">    ├── rootfs_hi3519av100_64k.jffs2</span><br><span class="line">    ├── u-boot-hi3519av100.bin</span><br><span class="line">    └── uImage_hi3519av100_smp</span><br></pre></td></tr></table></figure><p>代码压缩文件都放在 package 目录。我们需要执行 sdk.unpack 脚本来解压代码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;Hi3519AV100_SDK_V2.0.2.0$ .&#x2F;sdk.unpack</span><br></pre></td></tr></table></figure><p>解压代码后目录结构如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;Hi3519AV100_SDK_V2.0.2.0$ tree -L 4</span><br><span class="line">.</span><br><span class="line">├── osdrv  # bsp 相关代码, 主要包含 uboot, kernel, rootfs </span><br><span class="line">│   ├── components</span><br><span class="line">│   │   ├── ipcm</span><br><span class="line">│   │   └── pcie_mcc</span><br><span class="line">│   ├── Makefile</span><br><span class="line">│   ├── opensource</span><br><span class="line">│   │   ├── busybox  # busybox 源码</span><br><span class="line">│   │   ├── kernel   # kernel 源码，</span><br><span class="line">│   │   └── uboot</span><br><span class="line">│   ├── pub  # 编译出来的镜像放在这个目录</span><br><span class="line">│   ├── rootfs_scripts</span><br><span class="line">│   └── tools # 编译镜像需要的一些工具</span><br><span class="line">├── package  # 压缩的 sdk</span><br><span class="line">├── scripts  # sdk 解压相关脚本</span><br><span class="line">├── sdk.cleanup</span><br><span class="line">├── sdk.unpack </span><br><span class="line">├── smp  </span><br><span class="line">│   ├── a53_linux</span><br><span class="line">│   │   ├── drv</span><br><span class="line">│   │   │   ├── extdrv    ＃ 外设驱动</span><br><span class="line">│   │   │   └── interdrv  # 内部芯片驱动</span><br><span class="line">│   │   ├── mpp           # 媒体处理相关的驱动，库，头文件以及 sample等。应用层的开发主要就是使用这些库来进行开发了。</span><br><span class="line">│   │   │   ├── cfg.mak</span><br><span class="line">│   │   │   ├── component</span><br><span class="line">│   │   │   ├── include　　＃　mpi 头文件</span><br><span class="line">│   │   │   ├── init       # 媒体模块驱动初始化</span><br><span class="line">│   │   │   ├── ko         # 预编译好的驱动文件及加载脚本</span><br><span class="line">│   │   │   ├── lib        # 预编译好的 mpi 静态库和动态库 </span><br><span class="line">│   │   │   ├── Makefile.linux.param</span><br><span class="line">│   │   │   ├── Makefile.param</span><br><span class="line">│   │   │   ├── obj        # 预编译好的驱动 .o 文件</span><br><span class="line">│   │   │   ├── sample     # mpi 接口使用样例，供开发参考。</span><br><span class="line">│   │   │   └── tools      # 调试工具</span><br><span class="line">│   │   └── osal           ＃ 操作系统抽象层(Linux, Liteos)</span><br><span class="line">│   │       ├── include</span><br><span class="line">│   │       └── linux</span><br><span class="line">│   └── dsp_liteos</span><br><span class="line">└── smp_image_glibc       # 预编译好的系统镜像: uboot, kernel, rootfs</span><br></pre></td></tr></table></figure><h2 id="2-编译工具安装"><a href="#2-编译工具安装" class="headerlink" title="2. 编译工具安装"></a>2. 编译工具安装</h2><h3 id="2-1-依赖安装"><a href="#2-1-依赖安装" class="headerlink" title="2.1 依赖安装"></a>2.1 依赖安装</h3><p>先安装以下这些工具，不然编译会出现各种莫名其妙的错误：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install gperf bison flex libuuid1 u-boot-tools zlib1g-dev build-essential ncurses-dev libacl1-dev liblzo2-dev uuid-dev zlib1g-dev liblzo2-dev uuid-dev pkg-config texinfo</span><br></pre></td></tr></table></figure><h3 id="2-2-编译工具链安装"><a href="#2-2-编译工具链安装" class="headerlink" title="2.2 编译工具链安装"></a>2.2 编译工具链安装</h3><p>把 arm-himix200-linux.rar 解压后，得到 arm-himix200-linux.tgz。再解压一次得到 arm-himix200-linux 目录，结构如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;arm-himix200-linux$ tree </span><br><span class="line">.</span><br><span class="line">├── arm-himix200-linux.install</span><br><span class="line">├── arm-himix200-linux.tar.bz2</span><br><span class="line">├── gdb_build.sh</span><br><span class="line">├── gdb_readme_cn.txt</span><br><span class="line">├── gdb_readme_en.txt</span><br><span class="line">├── readme.txt</span><br><span class="line">└── runtime_glibc.tgz</span><br></pre></td></tr></table></figure><p>按文档的操作，是需要 sudo 执行 arm-himix200-linux.install 进行安装。其实只要把工具链解压，并设置一下 PATH 环境变量就行了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;arm-himix200-linux$ tar xvf arm-himix200-linux.tar.bz2</span><br><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;arm-himix200-linux$ tar xvf runtime_glibc.tgz -C arm-himix200-linux&#x2F;</span><br><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;arm-himix200-linux&#x2F;arm-himix200-linux&#x2F;bin$ cd </span><br><span class="line">qiushao@qiushao-pc:~$ echo &#39;PATH&#x3D;&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;arm-himix200-linux&#x2F;arm-himix200-linux&#x2F;bin:$PATH&#39; &gt;&gt; ~&#x2F;.bashrc</span><br><span class="line">qiushao@qiushao-pc:~$ source ~&#x2F;.bashrc</span><br></pre></td></tr></table></figure><h2 id="3-编译烧录-uboot-kernel-rootfs"><a href="#3-编译烧录-uboot-kernel-rootfs" class="headerlink" title="3. 编译烧录 uboot, kernel, rootfs"></a>3. 编译烧录 uboot, kernel, rootfs</h2><p>编译方法主要参考 Hi3519AV100_SDK/osdrv/readme_cn.txt 说明文件。</p><h3 id="3-1-下载-kernel"><a href="#3-1-下载-kernel" class="headerlink" title="3.1 下载 kernel"></a>3.1 下载 kernel</h3><p>由于 GPL 协议的问题，海思并没有把 kernel 的代码一起发布。需要我们自己下载 kernel 源码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519&#x2F;Hi3519AV100_SDK&#x2F;osdrv&#x2F;opensource&#x2F;kernel$ wget https:&#x2F;&#x2F;mirrors.edge.kernel.org&#x2F;pub&#x2F;linux&#x2F;kernel&#x2F;v4.x&#x2F;linux-4.9.37.tar.gz</span><br></pre></td></tr></table></figure><h3 id="3-2-编译系统镜像"><a href="#3-2-编译系统镜像" class="headerlink" title="3.2 编译系统镜像"></a>3.2 编译系统镜像</h3><p>在 osdrv 目录下执行 make all 就行</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK&#x2F;osdrv$ make all</span><br><span class="line">...</span><br><span class="line">---------finish osdrv work</span><br></pre></td></tr></table></figure><p>出现 finish osdrv work 提示，说明已经编译完成了。<br>编译生成的镜像放在 osdrv/pub/smp_image_glibc 目录下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK&#x2F;osdrv&#x2F;pub&#x2F;smp_image_glibc$ ls</span><br><span class="line">rootfs_hi3519av100_128k.jffs2  rootfs_hi3519av100_2k_24bit.yaffs2  rootfs_hi3519av100_32M.ext4   u-boot-hi3519av100.bin</span><br><span class="line">rootfs_hi3519av100_256k.jffs2  rootfs_hi3519av100_2k_4bit.yaffs2   rootfs_hi3519av100_64k.jffs2  uImage_hi3519av100_smp</span><br></pre></td></tr></table></figure><h3 id="3-3-烧录系统"><a href="#3-3-烧录系统" class="headerlink" title="3.3 烧录系统"></a>3.3 烧录系统</h3><p>SPI Nand Flash 的地址空间安排如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">|           1M         |          4M         |        32M          |</span><br><span class="line">|----------------------|---------------------|---------------------|</span><br><span class="line">|          uboot       |         kernel      |       rootfs        |</span><br></pre></td></tr></table></figure><h4 id="3-3-1-hitool-烧录"><a href="#3-3-1-hitool-烧录" class="headerlink" title="3.3.1 hitool 烧录"></a>3.3.1 hitool 烧录</h4><p>如果板子是空板的话，需要使用 hitool 进行烧录。请参考 HiBurn工具使用指南.pdf</p><h4 id="3-3-2-tftp-烧录"><a href="#3-3-2-tftp-烧录" class="headerlink" title="3.3.2 tftp 烧录"></a>3.3.2 tftp 烧录</h4><p>如果板子上有 uboot 的话，且板子有网口的话，则可以使用 tftp 来烧录。因为 hitool 没有提供 linux 版本，所以我一般都是使用 tftp 来烧录了。</p><ul><li><ol><li>tftp 配置<br>上一篇文章我们已经介绍过 tftp 服务环境的搭建了。先修改 tftp 的目录为编译生成 images 的目录：<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># &#x2F;etc&#x2F;default&#x2F;tftpd-hpa</span><br><span class="line"></span><br><span class="line">TFTP_USERNAME&#x3D;&quot;tftp&quot;</span><br><span class="line">TFTP_DIRECTORY&#x3D;&quot;&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK&#x2F;osdrv&#x2F;pub&#x2F;smp_image_glibc&quot;</span><br><span class="line">TFTP_ADDRESS&#x3D;&quot;:69&quot;</span><br><span class="line">TFTP_OPTIONS&#x3D;&quot;-l -c -s&quot;</span><br></pre></td></tr></table></figure>然后重启 tftp 服务。<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;etc&#x2F;default$ sudo service tftpd-hpa restart</span><br></pre></td></tr></table></figure></li></ol></li><li><ol start="2"><li>pc 网络信息查看<br>ubuntu 下查看 pc 的 ip, 网关等信息的方法<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ ifconfig</span><br><span class="line">wlp4s0: flags&#x3D;4163&lt;UP,BROADCAST,RUNNING,MULTICAST&gt;  mtu 1500</span><br><span class="line">        inet 192.168.3.29  netmask 255.255.255.0  broadcast 192.168.3.255</span><br><span class="line">        inet6 fe80::f7d3:b73c:461a:af45  prefixlen 64  scopeid 0x20&lt;link&gt;</span><br><span class="line">        inet6 240e:fe:3839:339:c717:1207:24ef:fd76  prefixlen 64  scopeid 0x0&lt;global&gt;</span><br><span class="line">        inet6 240e:fe:3839:300:7c94:2acf:4818:8  prefixlen 128  scopeid 0x0&lt;global&gt;</span><br><span class="line">        inet6 240e:fe:3839:339:1002:ba0c:2ed:5ffa  prefixlen 64  scopeid 0x0&lt;global&gt;</span><br><span class="line">        ether 40:74:e0:b3:f2:63  txqueuelen 1000  (Ethernet)</span><br><span class="line">        RX packets 36413  bytes 33826854 (33.8 MB)</span><br><span class="line">        RX errors 0  dropped 0  overruns 0  frame 0</span><br><span class="line">        TX packets 16292  bytes 2617305 (2.6 MB)</span><br><span class="line">        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0</span><br><span class="line"></span><br><span class="line">qiushao@qiushao-pc:~$ </span><br><span class="line">qiushao@qiushao-pc:~$ route -n</span><br><span class="line">Kernel IP routing table</span><br><span class="line">Destination     Gateway         Genmask         Flags Metric Ref    Use Iface</span><br><span class="line">0.0.0.0         192.168.3.1     0.0.0.0         UG    600    0        0 wlp4s0</span><br><span class="line">169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 virbr0</span><br><span class="line">172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0</span><br><span class="line">192.168.3.0     0.0.0.0         255.255.255.0   U     600    0        0 wlp4s0</span><br><span class="line">192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0</span><br><span class="line">qiushao@qiushao-pc:~$</span><br></pre></td></tr></table></figure>根据以上信息得到 pc 的网络信息如下：<br>ip: 192.168.3.29<br>netmask: 255.255.255.0<br>gateway: 192.168.3.1<br>mac: 40:74:e0:b3:f2:63</li></ol></li><li><ol start="3"><li>板子网络信息配置<br>板子需要接入和 pc 同一个局域网。板子上电后, 敲任意键进入 u-boot。设置 serverip(即 tftp 服务器的 ip), ipaddr(板子 ip)和 ethaddr(板子的 MAC 地址)。<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">hisilicon # setenv serverip 192.168.3.29</span><br><span class="line">hisilicon # setenv ipaddr 192.168.3.30  # 板子的 ip，参考 serverip, 修改最后一位即可。</span><br><span class="line">hisilicon # setenv netmask 255.255.255.0</span><br><span class="line">hisilicon # setenv gatewayip 192.168.3.1</span><br><span class="line">hisilicon # setenv ethaddr 40:74:e0:b3:f2:64  # 参考 pc 的 mac, 修改最后一位即可。</span><br><span class="line">hisilicon # saveenv</span><br><span class="line">Saving Environment to NAND...</span><br><span class="line">Erasing NAND...</span><br><span class="line">Erasing at 0xa0000 -- 100% complete.</span><br><span class="line">Writing to NAND... OK</span><br><span class="line">hisilicon # ping 192.168.3.29  # 确认网络设置正常。</span><br><span class="line">ETH0: PHY(phyaddr&#x3D;1, rgmii) link UP: DUPLEX&#x3D;FULL : SPEED&#x3D;1000M</span><br><span class="line">MAC:   54-E1-AD-0A-A1-F8</span><br><span class="line">Using gmac0 device</span><br><span class="line">host 192.168.3.29 is alive</span><br><span class="line">hisilicon #</span><br></pre></td></tr></table></figure></li></ol></li></ul><ul><li><ol start="4"><li>烧录分区<ul><li>烧写 u-boot<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mw.b 42000000 ff 80000</span><br><span class="line">tftp 0x42000000 u-boot-hi3519av100.bin</span><br><span class="line">nand erase 0 80000;nand write 42000000 0 80000</span><br></pre></td></tr></table></figure></li><li>烧写内核<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mw.b 42000000 ff 400000</span><br><span class="line">tftp 42000000 uImage_hi3519av100_smp</span><br><span class="line">nand erase 100000 400000;nand write 42000000 100000 400000</span><br></pre></td></tr></table></figure></li><li>烧写根文件系统<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mw.b 42000000 ff 2000000</span><br><span class="line">tftp 42000000 rootfs_hi3519av100_2k_4bit.yaffs2</span><br><span class="line">nand erase 500000 2000000;nand write.yaffs 42000000 500000 0xafeb00 (0xafeb00 为实际文件大小)</span><br></pre></td></tr></table></figure></li><li>设置启动参数<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">setenv bootargs &#39;mem&#x3D;256M console&#x3D;ttyAMA0,115200 clk_ignore_unused root&#x3D;&#x2F;dev&#x2F;mtdblock2 rw rootfstype&#x3D;yaffs2 mtdparts&#x3D;hinand:1M(boot),4M(kernel),32M(rootfs)&#39;</span><br><span class="line">setenv bootcmd &#39;nand read 0x42000000 100000 400000; bootm 0x42000000&#39;</span><br><span class="line">saveenv</span><br></pre></td></tr></table></figure></li><li>重启系统<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">reset</span><br></pre></td></tr></table></figure></li></ul></li></ol></li></ul><h2 id="4-板子网络环境配置"><a href="#4-板子网络环境配置" class="headerlink" title="4. 板子网络环境配置"></a>4. 板子网络环境配置</h2><p>经过上面的步骤，我们的系统是跑起来了，但只是把 kernel 启动了，根文件系统挂载了而已，什么业务都没有运行的。我们需要编写自己的应用在上面运行才可以。<br>在写代码之前我们还需要配置一下板子的网络环境，以便开发调试。在开发阶段,推荐使用 NFS 作为开发环境,可以省去重新制作和烧写根文件系统的工作。也不需要频繁用 u 盘 copy 文件。</p><h3 id="4-1-pc端配置nfs服务"><a href="#4-1-pc端配置nfs服务" class="headerlink" title="4.1 pc端配置nfs服务"></a>4.1 pc端配置nfs服务</h3><p>pc 端 nfs 服务的配置之前已经介绍过了，这里面就不再重复。我们把 nfs share 的目录改成 Hi3519AV100_SDK：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;etc$ cat exports </span><br><span class="line"># &#x2F;etc&#x2F;exports: the access control list for filesystems which may be exported</span><br><span class="line">#               to NFS clients.  See exports(5).</span><br><span class="line">#</span><br><span class="line"># Example for NFSv2 and NFSv3:</span><br><span class="line"># &#x2F;srv&#x2F;homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)</span><br><span class="line">#</span><br><span class="line"># Example for NFSv4:</span><br><span class="line"># &#x2F;srv&#x2F;nfs4        gss&#x2F;krb5i(rw,sync,fsid&#x3D;0,crossmnt,no_subtree_check)</span><br><span class="line"># &#x2F;srv&#x2F;nfs4&#x2F;homes  gss&#x2F;krb5i(rw,sync,no_subtree_check)</span><br><span class="line">#</span><br><span class="line">&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK *(rw,sync,no_subtree_check,root_squash)</span><br><span class="line">qiushao@qiushao-pc:&#x2F;etc$</span><br><span class="line">qiushao@qiushao-pc:&#x2F;etc$ sudo exportfs -a </span><br><span class="line">qiushao@qiushao-pc:&#x2F;etc$ sudo service nfs-kernel-server restart</span><br></pre></td></tr></table></figure><h3 id="4-2-设置板子的-mac-ip-等信息"><a href="#4-2-设置板子的-mac-ip-等信息" class="headerlink" title="4.2 设置板子的 mac,ip 等信息"></a>4.2 设置板子的 mac,ip 等信息</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">~ # ifconfig eth0 hw ether 40:74:e0:b3:f2:64</span><br><span class="line">~ # ifconfig eth0 192.168.3.30 netmask 255.255.255.0</span><br><span class="line">IPv6: ADDRCONF(NETDEV_UP): eth0: link is not ready</span><br><span class="line">~ # hi_gmac_v200 40e0000.ethernet eth0: Link is Up - 1Gbps&#x2F;Full - flow control off</span><br><span class="line">IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready</span><br><span class="line"></span><br><span class="line">~ # route add default gw 192.168.3.1</span><br><span class="line">~ # ping 192.168.3.29</span><br><span class="line">PING 192.168.3.29 (192.168.3.29): 56 data bytes</span><br><span class="line">64 bytes from 192.168.3.29: seq&#x3D;0 ttl&#x3D;64 time&#x3D;43.142 ms</span><br><span class="line">64 bytes from 192.168.3.29: seq&#x3D;1 ttl&#x3D;64 time&#x3D;62.610 ms</span><br><span class="line">64 bytes from 192.168.3.29: seq&#x3D;2 ttl&#x3D;64 time&#x3D;76.662 ms</span><br></pre></td></tr></table></figure><h3 id="4-3-板子上挂载-nfs-文件系统"><a href="#4-3-板子上挂载-nfs-文件系统" class="headerlink" title="4.3 板子上挂载 nfs 文件系统"></a>4.3 板子上挂载 nfs 文件系统</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">~ # mount -t nfs -o nolock -o tcp -o rsize&#x3D;32768,wsize&#x3D;32768 192.168.3.29:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK &#x2F;mnt</span><br><span class="line">~ # ls &#x2F;mnt</span><br><span class="line">osdrv            scripts          sdk.unpack       smp_image_glibc</span><br><span class="line">package          sdk.cleanup      smp</span><br><span class="line">~ #</span><br></pre></td></tr></table></figure><h2 id="5-编译运行-sample"><a href="#5-编译运行-sample" class="headerlink" title="5. 编译运行 sample"></a>5. 编译运行 sample</h2><h3 id="5-1-编译-sample"><a href="#5-1-编译-sample" class="headerlink" title="5.1 编译 sample"></a>5.1 编译 sample</h3><p>海思 sdk 里面带了一些 sample:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;sample$ ls</span><br><span class="line">audio  awb_online_calibration  dis  fisheye  lsc_online_cali  Makefile.param  region      smp_linux.mak  svp  traffic_capture  vdec  vgs</span><br><span class="line">avs    common                  dpu  hifb     Makefile         pciv            scene_auto  snap           tde  uvc_app          venc  vio</span><br><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;sample$</span><br></pre></td></tr></table></figure><p>我们先试着在上面跑个简单的 vio sample 看看。vio sample 的功能是从摄像头输出，然后输出到 hdmi。<br>编译的方法很简单，进入 sample/vio 目录，直接 make 就行。编译成功的话，会在 vio 目录生成一个 sample_vio_main 文件:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;sample$ cd vio</span><br><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;sample&#x2F;vio$ ls</span><br><span class="line">Makefile  res  sample_vio.h  sample_vio_main.c  smp</span><br><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;sample&#x2F;vio$ make</span><br><span class="line">...</span><br><span class="line">qiushao@qiushao-pc:&#x2F;media&#x2F;qiushao&#x2F;source-code&#x2F;Hi3519AV100_SDK&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;sample&#x2F;vio$ ls</span><br><span class="line">Makefile  res  sample_vio.h  sample_vio_main  sample_vio_main.c  sample_vio_main.o  smp</span><br></pre></td></tr></table></figure><h3 id="5-2-加载设备驱动"><a href="#5-2-加载设备驱动" class="headerlink" title="5.2 加载设备驱动"></a>5.2 加载设备驱动</h3><p>现在我们又回到板子上进行操作。<br>在运行 sample 之前我们需要先加载 hisi 的驱动</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#x2F; # cd &#x2F;mnt&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;ko</span><br><span class="line">&#x2F;mnt&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;ko # .&#x2F;load3519av100 -i -sensor0 imx334</span><br><span class="line">mmz_start: 0x32000000, mmz_size: 1760M</span><br><span class="line">sys_config: loading out-of-tree module taints kernel.</span><br><span class="line">&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;sensr0: imx334&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</span><br><span class="line">&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;sensr1: imx334&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</span><br><span class="line">&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;sensr2: imx334&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</span><br><span class="line">&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;sensr3: imx334&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</span><br><span class="line">&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;sensr4: imx334&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</span><br><span class="line">Module himedia: init ok</span><br></pre></td></tr></table></figure><h3 id="5-3-运行-sample"><a href="#5-3-运行-sample" class="headerlink" title="5.3 运行 sample"></a>5.3 运行 sample</h3><p>驱动加载成功之后，就可以开始运行 sample 啦</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;mnt&#x2F;smp&#x2F;a53_linux&#x2F;mpp # cd sample&#x2F;vio&#x2F;</span><br><span class="line">&#x2F;mnt&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;sample&#x2F;vio # .&#x2F;sample_vio_main</span><br><span class="line">Usage : .&#x2F;sample_vio_main &lt;index&gt; &lt;intf&gt;</span><br><span class="line">index:</span><br><span class="line">         0)VI(Online) - VPSS(Online) - VO.</span><br><span class="line">         1)VI(Offline)- VPSS(Offline) - VO. LDC+DIS+SPREAD.</span><br><span class="line">         2)VI(Offline)- VPSS(Online) - VO.  Double pipe.</span><br><span class="line">         3)VI(Online)- VPSS(Offline) - VO.  Double chn.</span><br><span class="line">         4)Resolute Ratio Switch.</span><br><span class="line">         5)GDC - VPSS LowDelay.</span><br><span class="line">         6)Double WDR Pipe.</span><br><span class="line">         7)FPN Calibrate &amp; Correction.</span><br><span class="line">         8)WDR Switch.</span><br><span class="line">         9)90&#x2F;180&#x2F;270 Rotate.</span><br><span class="line">         10)Mipi Demux Yuv.</span><br><span class="line">         11)UserPic.</span><br><span class="line">intf:</span><br><span class="line">         0) vo HDMI output, default.</span><br><span class="line">         1) vo BT1120 output.</span><br><span class="line">&#x2F;mnt&#x2F;smp&#x2F;a53_linux&#x2F;mpp&#x2F;sample&#x2F;vio # .&#x2F;sample_vio_main 0</span><br><span class="line">[SAMPLE_COMM_VI_SetMipiAttr]-1408: &#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D; MipiDev 0, SetMipiAttr enWDRMode: 0</span><br><span class="line">linear mode</span><br><span class="line">&#x3D;&#x3D;&#x3D;Imx334 8M30fps 12bit LINE Init OK!&#x3D;&#x3D;&#x3D;</span><br><span class="line">[SAMPLE_COMM_ISP_Thread]-187: ISP Dev 0 running !</span><br><span class="line">[SAMPLE_COMM_VO_StartChn]-544: u32Width:1920, u32Height:1080, u32Square:1</span><br><span class="line">---------------press Enter key to exit!---------------</span><br></pre></td></tr></table></figure><p>至此，系统就已经运行起来了， sample 也运行起来了。后面我们再来逐步学习 camera 系统相关的开发知识。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;0-文档目录结构&quot;&gt;&lt;a href=&quot;#0-文档目录结构&quot; class=&quot;headerlink&quot; title=&quot;0. 文档目录结构&quot;&gt;&lt;/a&gt;0. 文档目录结构&lt;/h2&gt;&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td 
      
    
    </summary>
    
    
      <category term="Camera" scheme="http://qiushao.net/categories/Camera/"/>
    
    
      <category term="Hi3519AV100" scheme="http://qiushao.net/tags/Hi3519AV100/"/>
    
      <category term="Camera" scheme="http://qiushao.net/tags/Camera/"/>
    
  </entry>
  
  <entry>
    <title>busybox rx 传输文件</title>
    <link href="http://qiushao.net/2020/04/12/Linux/busybox-rx/"/>
    <id>http://qiushao.net/2020/04/12/Linux/busybox-rx/</id>
    <published>2020-04-12T12:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>如果你的板子上没有网口，没有 usb 口，只有一个串口可以交互的时候，我想要把一个文件从 pc 传到板子上，该怎么办呢？<br>这个时候就只能用串口传输了，还好 busybox 就带了一个 rx 命令，支持串口传输文件。<br>因为串口的传输速度很慢，只有 10k/s 左右而已。所以传输稍大一点的文件就力不从心了。但对于一两M的小文件还是挺方便的。<br>下面来演示一下使用方法。</p><ol><li><p>在板子上启动接收文件服务</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;data # busybox rx camera_app</span><br><span class="line">CC</span><br></pre></td></tr></table></figure><p>这时就是在等待接收 pc 端发送的文件了。接收到的文件保存为 camera_app。</p></li><li><p>pc 端通过 xmodern 发送文件<br>我使用的是 secureCRT 来连接的串口。<br>SecureCRT –&gt; Transfer –&gt; send xmodern<br>然后选择要发送的文件即可。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;data # busybox rx camera_app</span><br><span class="line">CC</span><br><span class="line">Starting xmodem transfer.  Press Ctrl+C to cancel.</span><br><span class="line">Transferring &#x2F;home&#x2F;qiushao&#x2F;camera_app...</span><br><span class="line">   72%    3110 KB      11 KB&#x2F;sec    00:01:47 ETA   0 Errors</span><br></pre></td></tr></table></figure></li></ol>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;如果你的板子上没有网口，没有 usb 口，只有一个串口可以交互的时候，我想要把一个文件从 pc 传到板子上，该怎么办呢？&lt;br&gt;这个时候就只能用串口传输了，还好 busybox 就带了一个 rx 命令，支持串口传输文件。&lt;br&gt;因为串口的传输速度很慢，只有 10k/s 左右
      
    
    </summary>
    
    
      <category term="Linux" scheme="http://qiushao.net/categories/Linux/"/>
    
    
      <category term="Linux" scheme="http://qiushao.net/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>ubuntu 18.04 安装配置 nfs 服务</title>
    <link href="http://qiushao.net/2020/04/12/Linux/ubuntu-nfs-settings/"/>
    <id>http://qiushao.net/2020/04/12/Linux/ubuntu-nfs-settings/</id>
    <published>2020-04-12T08:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>最近在学习 hi3519av100 camera 开发，为避免频繁使用 u 盘 copy 文件，因此需要搭建 nfs 服务。</p><h2 id="1-安装服务端和客户端"><a href="#1-安装服务端和客户端" class="headerlink" title="1. 安装服务端和客户端"></a>1. 安装服务端和客户端</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install nfs-kernel-server nfs-common</span><br></pre></td></tr></table></figure><p>其中 nfs-kernel-server 为服务端，　nfs-common 为客户端。</p><h2 id="2-配置-nfs-共享目录"><a href="#2-配置-nfs-共享目录" class="headerlink" title="2. 配置 nfs 共享目录"></a>2. 配置 nfs 共享目录</h2><p>在家目录创建共享目录，并在 /etc/exports 中导出:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ mkdir nfs-share</span><br><span class="line">qiushao@qiushao-pc:~$ sudo vim &#x2F;etc&#x2F;exports </span><br><span class="line">&#x2F;home&#x2F;qiushao&#x2F;nfs-share *(rw,sync,no_root_squash,no_subtree_check)</span><br></pre></td></tr></table></figure><p>格式如下：<br>共享目录  可访问共享目录的ip(共享目录权限列表)<br>各字段解析如下：<br>/home/qiushao/nfs-share: 要共享的目录<br><em>：指定可以访问共享目录的用户 ip, * 代表所有用户。192.168.3.</em>　指定网段。192.168.3.29 指定 ip。<br>rw：可读可写。如果想要只读的话，可以指定 ro。<br>sync：文件同步写入到内存与硬盘中。<br>async：文件会先暂存于内存中，而非直接写入硬盘。<br>no_root_squash：登入 nfs 主机使用分享目录的使用者，如果是 root 的话，那么对于这个分享的目录来说，他就具有 root 的权限！这个项目『极不安全』，不建议使用！但如果你需要在客户端对 nfs 目录进行写入操作。你就得配置 no_root_squash。方便与安全不可兼得。<br>root_squash：在登入 nfs 主机使用分享之目录的使用者如果是 root 时，那么这个使用者的权限将被压缩成为匿名使用者，通常他的 UID 与 GID 都会变成 nobody 那个系统账号的身份。<br>subtree_check：强制 nfs 检查父目录的权限（默认）<br>no_subtree_check：不检查父目录权限</p><p>配置完成后，执行以下命令导出共享目录，并重启 nfs 服务:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ sudo exportfs -a      </span><br><span class="line">qiushao@qiushao-pc:~$ sudo service nfs-kernel-server restart</span><br><span class="line">qiushao@qiushao-pc:~$</span><br></pre></td></tr></table></figure><p>这样服务端就配置完成啦。</p><h2 id="3-客户端访问测试"><a href="#3-客户端访问测试" class="headerlink" title="3. 客户端访问测试"></a>3. 客户端访问测试</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ sudo mount localhost:&#x2F;home&#x2F;qiushao&#x2F;nfs-share &#x2F;mnt</span><br><span class="line">qiushao@qiushao-pc:~$ cd &#x2F;mnt</span><br><span class="line">qiushao@qiushao-pc:&#x2F;mnt$ ls</span><br><span class="line">qiushao@qiushao-pc:&#x2F;mnt$ touch foobar</span><br><span class="line">qiushao@qiushao-pc:&#x2F;mnt$ ls ~&#x2F;nfs-share&#x2F;</span><br><span class="line">foobar</span><br></pre></td></tr></table></figure><p>我们把 nfs-share 共享文件系统挂载到 /mnt，　可以看到我们在 /mnt 目录下创建一个 foobar 文件，然后在 nfs-share 目录下也有一个 foobar 文件。看起来 nfs 的配置是已经 ok 了。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;最近在学习 hi3519av100 camera 开发，为避免频繁使用 u 盘 copy 文件，因此需要搭建 nfs 服务。&lt;/p&gt;
&lt;h2 id=&quot;1-安装服务端和客户端&quot;&gt;&lt;a href=&quot;#1-安装服务端和客户端&quot; class=&quot;headerlink&quot; title=&quot;
      
    
    </summary>
    
    
      <category term="Linux" scheme="http://qiushao.net/categories/Linux/"/>
    
    
      <category term="Linux" scheme="http://qiushao.net/tags/Linux/"/>
    
      <category term="nfs" scheme="http://qiushao.net/tags/nfs/"/>
    
  </entry>
  
  <entry>
    <title>ubuntu 18.04 安装配置 tftp</title>
    <link href="http://qiushao.net/2020/04/04/Linux/ubuntu-tftp-settings/"/>
    <id>http://qiushao.net/2020/04/04/Linux/ubuntu-tftp-settings/</id>
    <published>2020-04-04T08:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>因为最近要开始搞 Camera 了，初步看了下开发文档，发现需要使用 tftp 来进行烧录，因此在电脑上配置好 tftp 服务先。</p><h2 id="1-安装-tftp-服务端，客户端"><a href="#1-安装-tftp-服务端，客户端" class="headerlink" title="1. 安装 tftp 服务端，客户端"></a>1. 安装 tftp 服务端，客户端</h2><p>一行命令即可：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ sudo apt-get install tftp-hpa tftpd-hpa</span><br></pre></td></tr></table></figure><p>其中 tftp-hpa 是客户端， tftpd-hpa 是服务端。</p><h2 id="2-配置服务端"><a href="#2-配置服务端" class="headerlink" title="2. 配置服务端"></a>2. 配置服务端</h2><p>修改 /etc/default/tftpd-hpa 文件内容如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># &#x2F;etc&#x2F;default&#x2F;tftpd-hpa</span><br><span class="line"></span><br><span class="line">TFTP_USERNAME&#x3D;&quot;tftp&quot;</span><br><span class="line">TFTP_DIRECTORY&#x3D;&quot;&#x2F;home&#x2F;qiushao&#x2F;tftp-root&quot;</span><br><span class="line">TFTP_ADDRESS&#x3D;&quot;:69&quot;</span><br><span class="line">TFTP_OPTIONS&#x3D;&quot;-l -c -s&quot;</span><br></pre></td></tr></table></figure><ul><li><p>TFTP_DIRECTORY : tftp 启动根目录，　修改成自己想要的目录</p></li><li><p>TFTP_OPTIONS : tftp 启动选项，各选项解析如下：</p><ul><li><p><code>-l</code> –Listen</p></li><li><p><code>-c</code> –create</p></li><li><p><code>-s</code> –secure</p></li></ul></li></ul><p>如果你在客户端获取文件时出现 <code>Error code 1: File not found</code> 这个错误，请检查一下 <code>TFTP_OPTIONS=&quot;-l -c -s&quot;</code>。<br>配置好后，重启 tftp 服务:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ sudo service tftpd-hpa restart</span><br></pre></td></tr></table></figure><p>检查一下服务是否在运行了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ netstat -a | grep tftp</span><br><span class="line">udp        0      0 0.0.0.0:tftp            0.0.0.0:*                          </span><br><span class="line">udp6       0      0 [::]:tftp               [::]:*                             </span><br><span class="line">qiushao@qiushao-pc:~$</span><br></pre></td></tr></table></figure><p>有 tftp 监听了，说明服务已经启动了。</p><h2 id="3-客户端测试"><a href="#3-客户端测试" class="headerlink" title="3. 客户端测试"></a>3. 客户端测试</h2><p>我们先在 /home/qiushao/tftp-root 目录下创建一个文件 foobar，<br>然后在 /home/qiushao　目录执行 tftp get 来下载文件:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ cd tftp-root&#x2F;</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;tftp-root$ touch foobar</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;tftp-root$ ls</span><br><span class="line">foobar</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;tftp-root$ cd </span><br><span class="line">qiushao@qiushao-pc:~$ tftp localhost</span><br><span class="line">tftp&gt; get foobar</span><br><span class="line">qiushao@qiushao-pc:~$ ls foobar </span><br><span class="line">foobar</span><br><span class="line">qiushao@qiushao-pc:~$</span><br></pre></td></tr></table></figure><p>tftp get 下载文件成功了，说明我们的 tftp 服务应该是没有问题的了。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;因为最近要开始搞 Camera 了，初步看了下开发文档，发现需要使用 tftp 来进行烧录，因此在电脑上配置好 tftp 服务先。&lt;/p&gt;
&lt;h2 id=&quot;1-安装-tftp-服务端，客户端&quot;&gt;&lt;a href=&quot;#1-安装-tftp-服务端，客户端&quot; class=&quot;head
      
    
    </summary>
    
    
      <category term="Linux" scheme="http://qiushao.net/categories/Linux/"/>
    
    
      <category term="Linux" scheme="http://qiushao.net/tags/Linux/"/>
    
      <category term="tftp" scheme="http://qiushao.net/tags/tftp/"/>
    
  </entry>
  
  <entry>
    <title>c++静态代码检查-TscanCode</title>
    <link href="http://qiushao.net/2020/03/21/c++/c++%E9%9D%99%E6%80%81%E4%BB%A3%E7%A0%81%E6%A3%80%E6%9F%A5/"/>
    <id>http://qiushao.net/2020/03/21/c++/c++%E9%9D%99%E6%80%81%E4%BB%A3%E7%A0%81%E6%A3%80%E6%9F%A5/</id>
    <published>2020-03-21T12:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.748Z</updated>
    
    <content type="html"><![CDATA[<p>前面两篇文章我们制定了编码规范，还有编码规范检查的方法。编码规范是为了让大家写代码的风格更统一，为了让人更好的阅读理解代码。相比编码规范，我觉得代码静态检查，提前发现潜在的错误更为重要。有很多开源的工具，或者商业的产品提供了静态检查的功能：<br>开源的工具：TscanCode, cppcheck, clang等<br>商业收费的工具：pclint, coverity 等</p><p>目前只用过 cppcheck, TscanCode, 个人感觉 TscanCode 更加专业一点。 在公司的代码仓库上使用 cppckeck 检查，有很多误报，还有很多编码风格的小问题，真正的严重错误基本上没有发现。用 TscanCode 发现了五十几个严重错误，基本上没有误报的。下面就详细介绍一下 TscanCode 的使用方法，其他工具的就做介绍了，感兴趣的同学请自行搜索。</p><h2 id="1-安装"><a href="#1-安装" class="headerlink" title="1. 安装"></a>1. 安装</h2><p>TscanCode 是开源的工具，代码仓库为：<a href="https://github.com/Tencent/TscanCode" target="_blank" rel="noopener">https://github.com/Tencent/TscanCode</a>, 我们可以自己下载下来编译，也可以使用腾讯预编译好的可执行文件(代码仓库的 release 目录)。简单起见，我们直接使用预编译好的文件就行。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources$ git clone https:&#x2F;&#x2F;github.com&#x2F;Tencent&#x2F;TscanCode.git</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources$ cd TscanCode&#x2F;release&#x2F;linux&#x2F;</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources&#x2F;TscanCode&#x2F;release&#x2F;linux$ unzip TscanCodeV2.14.24.linux.zip </span><br><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources&#x2F;TscanCode&#x2F;release&#x2F;linux$ cd TscanCodeV2.14.24.linux&#x2F;TscanCodeV2.14.2395.linux</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources&#x2F;TscanCode&#x2F;release&#x2F;linux&#x2F;TscanCodeV2.14.24.linux&#x2F;TscanCodeV2.14.2395.linux$ chmod a+x tscancode </span><br><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources&#x2F;TscanCode&#x2F;release&#x2F;linux&#x2F;TscanCodeV2.14.24.linux&#x2F;TscanCodeV2.14.2395.linux$ echo &quot;PATH&#x3D;$PATH:$(pwd)&quot; &gt;&gt; ~&#x2F;.bashrc</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources&#x2F;TscanCode&#x2F;release&#x2F;linux&#x2F;TscanCodeV2.14.24.linux&#x2F;TscanCodeV2.14.2395.linux$ source ~&#x2F;.bashrc</span><br></pre></td></tr></table></figure><p>这样就安装完了。</p><h2 id="2-基本使用方法"><a href="#2-基本使用方法" class="headerlink" title="2. 基本使用方法"></a>2. 基本使用方法</h2><p>我们先看看帮助</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><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></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources&#x2F;TscanCode$ tscancode -h</span><br><span class="line">TscanCode - A tool for static C&#x2F;C++ code analysis</span><br><span class="line"></span><br><span class="line">Syntax:</span><br><span class="line">    tscancode [OPTIONS] [files or paths]</span><br><span class="line"></span><br><span class="line">If a directory is given instead of a filename, *.cpp, *.cxx, *.cc, *.c++, *.c,</span><br><span class="line">*.tpp, and *.txx files are checked recursively from the given directory.</span><br><span class="line"></span><br><span class="line">Options:</span><br><span class="line">    -D&lt;ID&gt;               Define preprocessor symbol. Unless --max-configs or</span><br><span class="line">                         --force is used, TscanCode will only check the given</span><br><span class="line">                         configuration when -D is used.</span><br><span class="line">                         Example: &#39;-DDEBUG&#x3D;1 -D__cplusplus&#39;.</span><br><span class="line">    -U&lt;ID&gt;               Undefine preprocessor symbol. Use -U to explicitly</span><br><span class="line">                         hide certain #ifdef &lt;ID&gt; code paths from checking.</span><br><span class="line">                         Example: &#39;-UDEBUG&#39;</span><br><span class="line">    --enable&#x3D;&lt;id&gt;        Enable additional checks. The available ids are:</span><br><span class="line">                          * all</span><br><span class="line">                                  Enable all checks. It is recommended to only</span><br><span class="line">                                  use --enable&#x3D;all when the whole program is</span><br><span class="line">                                  scanned, because this enables unusedFunction.</span><br><span class="line">                          * warning</span><br><span class="line">                                  Enable warning messages</span><br><span class="line">                          * style</span><br><span class="line">                                  Enable all coding style checks. All messages</span><br><span class="line">                                  with the severities &#39;style&#39;, &#39;performance&#39; and</span><br><span class="line">                                  &#39;portability&#39; are enabled.</span><br><span class="line">                          * performance</span><br><span class="line">                                  Enable performance messages</span><br><span class="line">                          * portability</span><br><span class="line">                                  Enable portability messages</span><br><span class="line">                          * information</span><br><span class="line">                                  Enable information messages</span><br><span class="line">                          * unusedFunction</span><br><span class="line">                                  Check for unused functions. It is recommend</span><br><span class="line">                                  to only enable this when the whole program is</span><br><span class="line">                                  scanned.</span><br><span class="line">                          * missingInclude</span><br><span class="line">                                  Warn if there are missing includes. For</span><br><span class="line">                                  detailed information, use &#39;--check-config&#39;.</span><br><span class="line">                         Several ids can be given if you separate them with</span><br><span class="line">                         commas. See also --std</span><br><span class="line">    -h, --help           Print this help.</span><br><span class="line">    -I &lt;dir&gt;             Give path to search for include files. Give several -I</span><br><span class="line">                         parameters to give several paths. First given path is</span><br><span class="line">                         searched for contained header files first. If paths are</span><br><span class="line">                         relative to source files, this is not needed.</span><br><span class="line">    -j &lt;jobs&gt;            Start [jobs] threads to do the checking simultaneously.</span><br><span class="line">    -q, --quiet          Do not show progress reports.</span><br><span class="line">    --xml                Write results in xml format to error stream (stderr).</span><br><span class="line"></span><br><span class="line">Example usage:</span><br><span class="line">  # Recursively check the current folder. Print the progress on the screen and</span><br><span class="line">  # write errors to a file:</span><br><span class="line">  tscancode . 2&gt; err.txt</span><br><span class="line"></span><br><span class="line">  # Recursively check ..&#x2F;myproject&#x2F; and don&#39;t print progress:</span><br><span class="line">  tscancode --quiet ..&#x2F;myproject&#x2F;</span><br><span class="line"></span><br><span class="line">  # Check test.cpp, enable all checks:</span><br><span class="line">  tscancode --enable&#x3D;all test.cpp</span><br><span class="line"></span><br><span class="line">  # Check f.cpp and search include files from inc1&#x2F; and inc2&#x2F;:</span><br><span class="line">  tscancode -I inc1&#x2F; -I inc2&#x2F; f.cpp</span><br><span class="line"></span><br><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources&#x2F;TscanCode$</span><br></pre></td></tr></table></figure><p>语法格式如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tscancode [OPTIONS] [files or paths]</span><br></pre></td></tr></table></figure><p>可以对文件和目录进行扫描，如果是目录的话，会递归扫描目录下的所有 *.cpp, *.cxx, *.cc, *.c++, *.c, *.tpp, and *.txx 文件。<br>提供以下选项：</p><ul><li><code>-D</code>: 定义宏定义。比如:<code>-DDEBUG=1 -D__cplusplus</code>。</li><li><code>-U</code>: 取消宏定义。比如：<code>-UDEBUG</code>。</li><li><code>--enable=</code>: 启用附加检查。取值范围：<ul><li>all : 启用所有附加检查。</li><li>warning : 打开 warning 消息。</li><li>style : 启用代码风格检查。</li><li>performance : 启用性能检查。</li><li>portability : 启用可移植性检查。</li><li>information : 打开 information 消息。</li><li>unusedFunction : 启用未调用函数检查。</li><li>missingInclude : 启用找不到头文件消息。</li></ul></li><li><code>-I</code>: 指定头文件路径。比如：<code>-Ifoobar/include</code>。</li><li><code>-j</code>: 并发检查线程数。比如: <code>-j8</code>。</li><li><code>-q</code>: 不打印处理进度，只输出结果。</li><li><code>--xml</code>: 指定输出格式为 xml。</li></ul><p>使用例子1：<br>递归检查 samples 目录下所有源文件, 进度信息显示在终端，结果重定向到 result.txt 文件中</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tscancode samples 2&gt; result.txt</span><br></pre></td></tr></table></figure><p>实际使用时发现有错误：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;opensources&#x2F;TscanCode$ tscancode samples 2&gt; result.txt</span><br><span class="line">Failed to load setting file &#39;cfg.xml&#39;. File not found</span><br><span class="line">Failed to load library configuration file &#39;std.cfg&#39;. File not found</span><br><span class="line">Failed to load library configuration file &#39;posix.cfg&#39;. File not found</span><br><span class="line">Failed to load library configuration file &#39;windows.cfg&#39;. File not found</span><br></pre></td></tr></table></figure><p>看信息是没有找到 cfg.xml 文件， cfg.xml 文件在 tscancode 文件同级目录下。查看代码，发现居然需要使用绝对路径或者相对路径调用 tscancode 才行(也就是调用路径一定要存在<code>/</code>)，<br>改成以下调用方式后就可以了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.&#x2F;release&#x2F;linux&#x2F;TscanCodeV2.14.24.linux&#x2F;TscanCodeV2.14.2395.linux&#x2F;tscancode .&#x2F;samples 2&gt; result.txt</span><br></pre></td></tr></table></figure><p>呵呵，居然不知道获取执行文件的路径，然后从同级目录下读取 cfg。</p><p>使用例子2:<br>指定头文件目录</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tscancode -I inc1&#x2F; -I inc2&#x2F; f.cpp</span><br></pre></td></tr></table></figure><h2 id="3-启用关闭部分检查"><a href="#3-启用关闭部分检查" class="headerlink" title="3. 启用关闭部分检查"></a>3. 启用关闭部分检查</h2><p>配置文件在 tscancode 同级目录下的 cfg/cfg.xml 里面。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"> &lt;section name&#x3D;&quot;Checks&quot;&gt;</span><br><span class="line">    &lt;id name&#x3D;&quot;nullpointer&quot; value&#x3D;&quot;1&quot;&gt;</span><br><span class="line">      &lt;subid name&#x3D;&quot;dereferenceAfterCheck&quot; value&#x3D;&quot;1&quot; severity&#x3D;&quot;Serious&quot; rule_name&#x3D;&quot;先判空后解引用&quot; desc&#x3D;&quot;指针先判空，然后在判空作用域外解引用，因为指针判空暗示当前上下文该指针可能为空，因此建议对没有在&gt;</span><br><span class="line">判空作用域的指针添加判空保护。&quot;&#x2F;&gt;</span><br><span class="line">      &lt;subid name&#x3D;&quot;funcRetNull&quot; value&#x3D;&quot;0&quot; severity&#x3D;&quot;Warning&quot; rule_name&#x3D;&quot;函数返回值未判空解引用&quot; desc&#x3D;&quot;函数存在返回NULL的分支，所以返回值可能为空，应该先判空再使用&quot;&#x2F;&gt;</span><br><span class="line">      &lt;subid name&#x3D;&quot;funcRetNullStatistic&quot; value&#x3D;&quot;1&quot; severity&#x3D;&quot;Serious&quot; rule_name&#x3D;&quot;基于统计推断函数返回值未判空&quot; desc&#x3D;&quot;通过分析代码调用模式，如果调用func函数，有10处对返回值进行了判空，而只有1处没有判空&gt;</span><br><span class="line">，那么很有可能是漏掉了判空。&quot;&#x2F;&gt;</span><br></pre></td></tr></table></figure><p>比如说我们不需要空指针检查，把 value 的值改为 0 即可。</p><h2 id="4-规则扩展"><a href="#4-规则扩展" class="headerlink" title="4. 规则扩展"></a>4. 规则扩展</h2><p>目前看起来好像只能通过修改代码来添加规则了。这里先不讨论了，后面有需求再研究一下。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;前面两篇文章我们制定了编码规范，还有编码规范检查的方法。编码规范是为了让大家写代码的风格更统一，为了让人更好的阅读理解代码。相比编码规范，我觉得代码静态检查，提前发现潜在的错误更为重要。有很多开源的工具，或者商业的产品提供了静态检查的功能：&lt;br&gt;开源的工具：TscanCo
      
    
    </summary>
    
    
      <category term="c++" scheme="http://qiushao.net/categories/c/"/>
    
    
      <category term="c++" scheme="http://qiushao.net/tags/c/"/>
    
      <category term="静态代码检查" scheme="http://qiushao.net/tags/%E9%9D%99%E6%80%81%E4%BB%A3%E7%A0%81%E6%A3%80%E6%9F%A5/"/>
    
  </entry>
  
  <entry>
    <title>c++编码规范检查-cpplint</title>
    <link href="http://qiushao.net/2020/03/15/c++/c++%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83%E6%A3%80%E6%9F%A5/"/>
    <id>http://qiushao.net/2020/03/15/c++/c++%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83%E6%A3%80%E6%9F%A5/</id>
    <published>2020-03-15T02:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.748Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇文章我们已经制定了编码规范，但规范条例这么多，是不可能人工 review 的，必须得用工具自动化检查才行。<br>因为我们使用的是 google 的编码规范，所以我们直接使用 google 提供的编码规范检查工具 <a href="https://github.com/google/styleguide/tree/gh-pages/cpplint" target="_blank" rel="noopener">cpplint</a> 就行了。下面详细介绍一下 cpplint 的用法。</p><h2 id="1-下载安装"><a href="#1-下载安装" class="headerlink" title="1. 下载安装"></a>1. 下载安装</h2><p>cpplint 其实就是 google styleguide 仓库中的一个 python 脚本而已，直接下载下来就可以使用了。这里我选择 git clone 整个 styleguide 工程，然后把 cpplint 目录加入到 PATH：</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">qiushao@qiushao-pc:~$ cd projects/opensources/</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources$ git clone https://github.com/google/styleguide.git</span><br><span class="line">qiushao@qiushao-pc:~/projects/opensources$ cd</span><br><span class="line">qiushao@qiushao-pc:~$ echo 'export PATH=$PATH:/home/qiushao/projects/opensources/styleguide/cpplint' &gt;&gt; .bashrc </span><br><span class="line">qiushao@qiushao-pc:~$ source ~/.bashrc</span><br></pre></td></tr></table></figure><p>这样 cpplint.py 就安装完了，可以使用了。</p><h2 id="2-基本使用方法"><a href="#2-基本使用方法" class="headerlink" title="2. 基本使用方法"></a>2. 基本使用方法</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><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">qiushao@qiushao-pc:~$ cpplint.py --help</span><br><span class="line"></span><br><span class="line">Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]</span><br><span class="line">                   [--counting=total|toplevel|detailed] [--root=subdir]</span><br><span class="line">                   [--linelength=digits] [--headers=x,y,...]</span><br><span class="line">                   [--quiet]</span><br><span class="line">        &lt;file&gt; [file] ...</span><br><span class="line">......</span><br></pre></td></tr></table></figure><p>各参数解析如下：</p><h3 id="2-1-verbose"><a href="#2-1-verbose" class="headerlink" title="2.1 --verbose"></a>2.1 <code>--verbose</code></h3><p>cpplint 把检查结果分成 0 ~ 5 六个等级。等级 0 为输出全部结果。<br>换句话来说就是当错误等级 &gt;= verbose 时，就输出。</p><h3 id="2-2-output"><a href="#2-2-output" class="headerlink" title="2.2 --output"></a>2.2 <code>--output</code></h3><p>指定输出格式，默认格式是 <code>emacs</code>, 还可能指定为 <code>vs7</code>(Visual Studio) 的输出格式。</p><h3 id="2-3-filter"><a href="#2-3-filter" class="headerlink" title="2.3 --filter"></a>2.3 <code>--filter</code></h3><p>分类过滤器，决定是否要输出某些类型的错误。默认输出所有的错误类型。<br><code>-</code> :不输出这个类型的错误。<br><code>+</code> :输出这个类型的错误。<br>比如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">--filter&#x3D;-whitespace,+whitespace&#x2F;braces</span><br><span class="line">--filter&#x3D;whitespace,runtime&#x2F;printf,+runtime&#x2F;printf_format</span><br><span class="line">--filter&#x3D;-,+build&#x2F;include_what_you_use</span><br></pre></td></tr></table></figure><p>可以这样来查找所有的错误类型：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ cpplint.py --filter&#x3D;</span><br><span class="line">  build&#x2F;class</span><br><span class="line">  build&#x2F;c++11</span><br><span class="line">  build&#x2F;c++14</span><br><span class="line">  build&#x2F;c++tr1</span><br><span class="line">  build&#x2F;deprecated</span><br><span class="line">  ...</span><br></pre></td></tr></table></figure><h3 id="2-4-counting"><a href="#2-4-counting" class="headerlink" title="2.4 --counting"></a>2.4 <code>--counting</code></h3><p>输出结果统计，有三个选项, 默认选项是 total：</p><ul><li>total    :只输出错误总数</li><li>toplevel :输出各顶层分类的错误统计，比如说 ‘build’, ‘whitespace’ </li><li>detailed :输出各子分类的错误统计，比如说 ‘build/class’, ‘whitespace/braces’</li></ul><p>例子：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~&#x2F;source&#x2F;android-10&#x2F;system&#x2F;core&#x2F;adb$ cpplint.py --verbose&#x3D;5 --counting&#x3D;toplevel adb.cpp </span><br><span class="line">adb.cpp:33:  &lt;chrono&gt; is an unapproved C++11 header.  [build&#x2F;c++11] [5]</span><br><span class="line">adb.cpp:34:  &lt;condition_variable&gt; is an unapproved C++11 header.  [build&#x2F;c++11] [5]</span><br><span class="line">adb.cpp:35:  &lt;mutex&gt; is an unapproved C++11 header.  [build&#x2F;c++11] [5]</span><br><span class="line">adb.cpp:37:  &lt;thread&gt; is an unapproved C++11 header.  [build&#x2F;c++11] [5]</span><br><span class="line">adb.cpp:62:  Do not use namespace using-directives.  Use using-declarations instead.  [build&#x2F;namespaces] [5]</span><br><span class="line">adb.cpp:135:  Missing space before ( in switch(  [whitespace&#x2F;parens] [5]</span><br><span class="line">adb.cpp:135:  Missing space before &#123;  [whitespace&#x2F;braces] [5]</span><br><span class="line">adb.cpp:322:  Missing space before ( in switch(  [whitespace&#x2F;parens] [5]</span><br><span class="line">adb.cpp:322:  Missing space before &#123;  [whitespace&#x2F;braces] [5]</span><br><span class="line">adb.cpp:390:  Missing space before ( in if(  [whitespace&#x2F;parens] [5]</span><br><span class="line">Done processing adb.cpp</span><br><span class="line">Category &#39;build&#39; errors found: 5</span><br><span class="line">Category &#39;whitespace&#39; errors found: 5</span><br><span class="line">Total errors found: 10</span><br></pre></td></tr></table></figure><h3 id="2-5-root"><a href="#2-5-root" class="headerlink" title="2.5 --root"></a>2.5 <code>--root</code></h3><p>指定代码的根目录。这个参数会影响头文件保护的宏命名检查结果，比如, 有个头文件的路径为 chrome/browser/ui/browser.h, 则头文件保护的宏命名为：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">未设置--root &#x3D;&gt; CHROME_BROWSER_UI_BROWSER_H_</span><br><span class="line">--root&#x3D;chrome &#x3D;&gt; BROWSER_UI_BROWSER_H_</span><br><span class="line">--root&#x3D;chrome&#x2F;browser &#x3D;&gt; UI_BROWSER_H_</span><br></pre></td></tr></table></figure><h3 id="2-6-linelength"><a href="#2-6-linelength" class="headerlink" title="2.6 --linelength"></a>2.6 <code>--linelength</code></h3><p>一行代码的长度限制。默认值为 80 个字符，超 80 个字符后，会提示错误。对于现代的大屏幕来说，我感觉 80 个字符太小了，设置为 120 比较合理。</p><h3 id="2-7-headers"><a href="#2-7-headers" class="headerlink" title="2.7 --headers"></a>2.7 <code>--headers</code></h3><p>设置头文件的扩展名，默认只接受 .h 后缀。例子：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">--headers&#x3D;hpp,hxx</span><br><span class="line">--headers&#x3D;hpp</span><br></pre></td></tr></table></figure><h3 id="2-8-extensions"><a href="#2-8-extensions" class="headerlink" title="2.8 --extensions"></a>2.8 <code>--extensions</code></h3><p>设置 cpplint 要检查的文件扩展名，比如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">--extensions&#x3D;hpp,cpp</span><br></pre></td></tr></table></figure><h3 id="2-9-quiet"><a href="#2-9-quiet" class="headerlink" title="2.9 --quiet"></a>2.9 <code>--quiet</code></h3><p>如果没有错误，则不输出任何信息</p><h3 id="2-10-配置文件"><a href="#2-10-配置文件" class="headerlink" title="2.10 配置文件"></a>2.10 配置文件</h3><p>我们可以在代码目录里面放一个 CPPLINT.cfg 文件，把参数配置都写到里面来，比如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">set noparent</span><br><span class="line">filter&#x3D;+filter1,-filter2</span><br><span class="line">exclude_files&#x3D;regex</span><br><span class="line">linelength&#x3D;80</span><br><span class="line">root&#x3D;subdir</span><br><span class="line">headers&#x3D;x,y</span><br></pre></td></tr></table></figure><p><code>set noparent</code> :让 cpplint 不要搜索上级目录查找其他的 .cfg 文件。<br><code>exclude_files</code> :排除某些文件，文件名可以使用正则表达式。</p><h3 id="3-添加自定义规则"><a href="#3-添加自定义规则" class="headerlink" title="3. 添加自定义规则"></a>3. 添加自定义规则</h3><p>cpplint 定义了很多规则，但也不可能满足所有人的需求。下面就来说说怎么扩展规则。<br>我们先看看 cpplint 的基本框架：<br><img src="/images/cpplint%E4%BB%A3%E7%A0%81%E6%A1%86%E6%9E%B6.png" alt="cpplint代码框架"></p><p>从上面的图可以看到几个关键的函数：</p><ul><li>ProcessFile : 对单个文件的整体处理，包括编码，换行符等</li><li>ProcessFileData : 对单个文件的内容进行处理，包括 copyright 声明等</li><li>ProcessLine : 对每一行进行处理，大部分规则都在这里处理了</li></ul><p>每一项规则都定义成一个函数，以 CheckXXX 方法命名。<br>检查到错误之后调用 <code>Error(filename, linenum, category, confidence, message)</code> 函数来输出错误。</p><ul><li>filename : 文件名</li><li>linenum : 出错行数</li><li>category : 错误类型</li><li>confidence : 错误等级，　1 ~ 5 </li><li>message 错误提示</li></ul><p>我们只需要参考现有的规则添加即可。基本上都是对正则表达式的使用而已。也因为 cpplint 只是使用了模式匹配的方法来做检查，并没有进行语法分析，所以有很多规则没法检查。比如，命名规则等。这也是一个非常大的缺陷。不过目前也没有其他更优秀的开源工具了，就将就着用先了。</p><h3 id="4-git-提交前自动检查"><a href="#4-git-提交前自动检查" class="headerlink" title="4. git 提交前自动检查"></a>4. git 提交前自动检查</h3><p>我们的目标是在开发者 git commit 之前自动进行代码风格检查，如果检查有任何错误，则提交失败。所以这个检查需要是在本地进行的。让错误尽早地消灭在源头。幸好，git 提供了 pre-commit hooks 可以满足我们的要求。<br>git pre-commit 默认是关闭的，打开的方式如下：把 .git/hooks/ 目录下的 pre-commit.sample 文件重命名为 pre-commit 即可。然后稍作修改如下：</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"><span class="meta">#</span><span class="bash">!/bin/sh</span></span><br><span class="line"><span class="meta">#</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> An example hook script to verify what is about to be committed.</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> Called by <span class="string">"git commit"</span> with no arguments.  The hook should</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> <span class="built_in">exit</span> with non-zero status after issuing an appropriate message <span class="keyword">if</span></span></span><br><span class="line"><span class="meta">#</span><span class="bash"> it wants to stop the commit.</span></span><br><span class="line"><span class="meta">#</span></span><br><span class="line"><span class="meta">#</span><span class="bash"> To <span class="built_in">enable</span> this hook, rename this file to <span class="string">"pre-commit"</span>.</span></span><br><span class="line"></span><br><span class="line">if git rev-parse --verify HEAD &gt;/dev/null 2&gt;&amp;1</span><br><span class="line">then</span><br><span class="line">        against=HEAD</span><br><span class="line">else</span><br><span class="line">        # Initial commit: diff against an empty tree object</span><br><span class="line">        against=4b825dc642cb6eb9a060e54bf8d69288fbee4904</span><br><span class="line">fi</span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"> Redirect output to stderr.</span></span><br><span class="line">exec 1&gt;&amp;2</span><br><span class="line"></span><br><span class="line">cpplint=cpplint.py</span><br><span class="line">sum=0</span><br><span class="line">filters='-whitespace/line_length,-build/include'</span><br><span class="line">        </span><br><span class="line"><span class="meta">#</span><span class="bash"> <span class="keyword">for</span> cpp</span></span><br><span class="line">for file in $(git diff-index --name-status $against -- | grep -E '\.[ch](pp)?$' | awk '&#123;print $2&#125;'); do</span><br><span class="line">    $cpplint --filter=$filters $file</span><br><span class="line">    sum=$(expr $&#123;sum&#125; + $?)</span><br><span class="line">done</span><br><span class="line">    </span><br><span class="line">if [ $&#123;sum&#125; -eq 0 ]; then</span><br><span class="line">    exit 0</span><br><span class="line">else</span><br><span class="line">    exit 1</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><p>我们来尝试提交一个有错误的代码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;clion&#x2F;test$ git commit -m &#39;test&#39;      </span><br><span class="line">main.cpp:0:  No copyright message found.  You should have a line: &quot;Copyright [year] &lt;Copyright Owner&gt;&quot;  [legal&#x2F;copyright] [5]</span><br><span class="line">main.cpp:2:  Line contains invalid UTF-8 (or Unicode replacement character).  [readability&#x2F;utf8] [5]</span><br><span class="line">main.cpp:7:  Could not find a newline character at the end of the file.  [whitespace&#x2F;ending_newline] [5]</span><br><span class="line">Done processing main.cpp</span><br><span class="line">Total errors found: 3</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;projects&#x2F;clion&#x2F;test$</span><br></pre></td></tr></table></figure><p>pre-commit 脚本的返回值为非 0 值，则认为有错误，就会提交失败。<br>上面这个例子把 filters 参数直接写在这个脚本里面了，实际应用时最好写到 CPPLINT.cfg 配置文件里面。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;上一篇文章我们已经制定了编码规范，但规范条例这么多，是不可能人工 review 的，必须得用工具自动化检查才行。&lt;br&gt;因为我们使用的是 google 的编码规范，所以我们直接使用 google 提供的编码规范检查工具 &lt;a href=&quot;https://github.com
      
    
    </summary>
    
    
      <category term="c++" scheme="http://qiushao.net/categories/c/"/>
    
    
      <category term="c++" scheme="http://qiushao.net/tags/c/"/>
    
      <category term="静态代码检查" scheme="http://qiushao.net/tags/%E9%9D%99%E6%80%81%E4%BB%A3%E7%A0%81%E6%A3%80%E6%9F%A5/"/>
    
  </entry>
  
  <entry>
    <title>c++编码规范</title>
    <link href="http://qiushao.net/2020/03/11/c++/c++%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83/"/>
    <id>http://qiushao.net/2020/03/11/c++/c++%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83/</id>
    <published>2020-03-11T06:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.748Z</updated>
    
    <content type="html"><![CDATA[<p>本文中的规范大部分是参考 google 发布的 c++ 编码规范，有同学翻译成了<a href="https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/" target="_blank" rel="noopener">中文版本</a>。但这个规范我觉得顺序有点乱，有些地方讨论太详细，有些地方又表达得不清楚。因此整理，挑选了部分规范。</p><h2 id="1-文件编码格式"><a href="#1-文件编码格式" class="headerlink" title="1. 文件编码格式"></a>1. 文件编码格式</h2><h3 id="1-1-文件编码：统一使用-utf-8-编码"><a href="#1-1-文件编码：统一使用-utf-8-编码" class="headerlink" title="1.1 文件编码：统一使用 utf-8 编码"></a>1.1 文件编码：统一使用 utf-8 编码</h3><p>如果文件编码不统一，又有同学使用中文注释的话，就很容易出现乱码的问题。应该所有的文本编辑器都是支持 utf-8 编码的。Linux 的文本编辑器默认使用的都是 utf-8 编码。但 windows 的系统文本编辑器大部分默认编码是 gbk，　所以使用 windows 的同学需要检测一下自己使用的代码编辑器是否已经设置默认编码为 utf-8 了。</p><h3 id="1-２-换行符：统一使用-unix-换行符"><a href="#1-２-换行符：统一使用-unix-换行符" class="headerlink" title="1.２ 换行符：统一使用 unix 换行符"></a>1.２ 换行符：统一使用 unix 换行符</h3><p>换行符有三种：unix(LF), macos(CR), windows(CRLF)。其中, CR 表示回车(ASCII 13, \r)， LF 表示换行(ASCII 10, \n)。<br>虽然我们在代码编辑器里面看不出来有什么区别。但在一些特殊情况下是会有影响的。比如板子上运行的 shell 脚本，如果换行符是 windows 换行符的话，可能就会执行错误了。为了避免这类问题，我们要求统一使用 unix 换行符。如果现有文件的换行符是 windows 的话，可以通过 dos2unix 这个工具来做转换。代码编辑器也是可以设置默认换行符的。</p><h2 id="2-命名约定"><a href="#2-命名约定" class="headerlink" title="2. 命名约定"></a>2. 命名约定</h2><p>最重要的一致性规则是命名管理。 命名的风格能让我们在不需要去查找类型声明的条件下快速地了解某个名字代表的含义: 类型, 变量, 函数, 常量, 宏, 等等。 甚至, 我们大脑中的模式匹配引擎非常依赖这些命名规则。命名规则具有一定随意性, 但相比按个人喜好命名, 一致性更重要, 所以无论你认为它们是否重要, 规则总归是要遵守的。</p><h3 id="2-1-通用命名规则"><a href="#2-1-通用命名规则" class="headerlink" title="2.1 通用命名规则"></a>2.1 通用命名规则</h3><ul><li>总述<br>函数命名, 变量命名, 文件命名要有描述性; 少用缩写。</li><li>说明<br>尽可能使用描述性的命名, 别心疼空间, 毕竟相比之下让代码易于新读者理解更重要。 不要用只有项目开发者能理解的缩写, 也不要通过砍掉几个字母来缩写单词。</li><li>示例<br>好的命名：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> price_count_reader;    <span class="comment">// 无缩写</span></span><br><span class="line"><span class="keyword">int</span> num_errors;            <span class="comment">// "num" 是一个常见的写法</span></span><br><span class="line"><span class="keyword">int</span> num_dns_connections;   <span class="comment">// 人人都知道 "DNS" 是什么</span></span><br></pre></td></tr></table></figure>不好的命名：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> n;                     <span class="comment">// 毫无意义.</span></span><br><span class="line"><span class="keyword">int</span> nerr;                  <span class="comment">// 含糊不清的缩写.</span></span><br><span class="line"><span class="keyword">int</span> n_comp_conns;          <span class="comment">// 含糊不清的缩写.</span></span><br><span class="line"><span class="keyword">int</span> wgc_connections;       <span class="comment">// 只有贵团队知道是什么意思.</span></span><br><span class="line"><span class="keyword">int</span> pc_reader;             <span class="comment">// "pc" 有太多可能的解释了.</span></span><br><span class="line"><span class="keyword">int</span> cstmr_id;              <span class="comment">// 删减了若干字母.</span></span><br></pre></td></tr></table></figure></li></ul><p><strong>例外，一些特定的广为人知的缩写是允许的, 例如用 i 表示迭代变量和用 T 表示模板参数。</strong></p><h3 id="2-2-文件命名"><a href="#2-2-文件命名" class="headerlink" title="2.2 文件命名"></a>2.2 文件命名</h3><ul><li>总述<br>文件名要全部小写, 单词之间使用下划线 <code>_</code> 连接。</li><li>说明<br>C++ 文件要以 .cpp 结尾, 头文件以 .h 结尾。<br>不要使用已经存在于 /usr/include 下的文件名 (注: 即编译器搜索系统头文件的路径), 如 string.h。<br>通常应尽量让文件名更加明确， http_server_logs.h 就比 logs.h 要好。 定义类时文件名一般成对出现, 如 foo_bar.h 和 foo_bar.cpp, 对应于类 FooBar。</li><li>示例<br>可接受的文件命名：<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http_server_logs.h</span><br></pre></td></tr></table></figure>不接受的文件命名：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">httpserverlogs.h       <span class="comment">// 没有单词分隔，看得眼花 </span></span><br><span class="line">http-server-logs.cpp   <span class="comment">// 单词分隔没有使用下划线 _</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="2-3-类型命名"><a href="#2-3-类型命名" class="headerlink" title="2.3 类型命名"></a>2.3 类型命名</h3><ul><li>总述<br>类型名称的每个单词首字母均大写, 不包含下划线，比如: MyExcitingClass, MyExcitingEnum。</li><li>说明<br>所有类型命名 (类, 结构体, 类型定义 (typedef), 枚举, 类型模板参数) 均使用相同约定, 即以大写字母开始, 每个单词首字母均大写, 不包含下划线。</li><li>示例<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 类和结构体</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UrlTable</span> &#123;</span>&#125;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UrlTableTester</span> &#123;</span>&#125;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">UrlTableProperties</span> &#123;</span>&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 枚举</span></span><br><span class="line"><span class="keyword">enum</span> UrlTableErrors &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// typedef 别名</span></span><br><span class="line"><span class="keyword">typedef</span> hash_map&lt;UrlTableProperties *, <span class="built_in">string</span>&gt; PropertiesMap;</span><br><span class="line"></span><br><span class="line"><span class="comment">// using 别名</span></span><br><span class="line"><span class="keyword">using</span> PropertiesMap = hash_map&lt;UrlTableProperties *, <span class="built_in">string</span>&gt;;</span><br></pre></td></tr></table></figure></li></ul><h3 id="2-4-变量命名"><a href="#2-4-变量命名" class="headerlink" title="2.4 变量命名"></a>2.4 变量命名</h3><ul><li>总述<br>变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接。 类的成员变量以下划线结尾, 但结构体的就不用, 如: a_local_variable, a_struct_data_member, a_class_data_member_。</li><li>说明<br>类的成员变量最后面需要添加下划线，结构体的成员变量不需要在最后面添加下划线。因类的成员变量我们要求是都是 private 的，不能直接访问。　加下载线是为了容易区分这个变量是成员变量还是局部变量。结构体的成员变量默认是 public 的，我们使用结构体的一般用法也是要直接访问其成员变量的。另外，结构体一般也没有方法，不需要区分成员变量和局部变量，所以后面不用加下划线。</li><li>示例<br>普通变量命名<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">string</span> table_name;  <span class="comment">// 好 - 用下划线</span></span><br><span class="line"><span class="built_in">string</span> tableName;  <span class="comment">// 差 - 混合大小写</span></span><br></pre></td></tr></table></figure>类数据成员<br>不管是静态的还是非静态的, 类数据成员都可以和普通变量一样, 但要接下划线。<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TableInfo</span> &#123;</span></span><br><span class="line">  ...</span><br><span class="line"> <span class="keyword">private</span>:</span><br><span class="line">  <span class="built_in">string</span> table_name_;  <span class="comment">// 好 - 后加下划线</span></span><br><span class="line">  <span class="keyword">static</span> Pool&lt;TableInfo&gt;* pool_;  <span class="comment">// 好</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>结构体变量<br>不管是静态的还是非静态的, 结构体数据成员都可以和普通变量一样, 不用像类那样接下划线。<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">UrlTableProperties</span> &#123;</span></span><br><span class="line">  <span class="built_in">string</span> name;</span><br><span class="line">  <span class="keyword">int</span> num_entries;</span><br><span class="line">  <span class="keyword">static</span> Pool&lt;UrlTableProperties&gt;* pool;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li></ul><h3 id="2-5-常量命名"><a href="#2-5-常量命名" class="headerlink" title="2.5 常量命名"></a>2.5 常量命名</h3><ul><li>总述<br>声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合. </li><li>说明<br>所有具有静态存储类型的变量 (例如静态变量或全局变量, 参见 <a href="#">存储类型</a>) 都应当以此方式命名。</li><li>示例<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> kDaysInAWeek = <span class="number">7</span>;</span><br></pre></td></tr></table></figure></li></ul><h3 id="2-6-函数命名"><a href="#2-6-函数命名" class="headerlink" title="2.6 函数命名"></a>2.6 函数命名</h3><ul><li>总述<br>常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable()。</li><li>说明<br>一般来说, 函数名的每个单词首字母大写 (即 “驼峰变量名” 或 “帕斯卡变量名”), 没有下划线。 对于首字母缩写的单词, 更倾向于将它们视作一个单词进行首字母大写。例如, 写作 StartRpc() 而非 StartRPC()。</li><li>示例<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">AddTableEntry()</span><br><span class="line">DeleteUrl()</span><br><span class="line">OpenFileOrDie()</span><br><span class="line">count()              <span class="comment">// 取值</span></span><br><span class="line">set_count(<span class="keyword">int</span> count) <span class="comment">// 设值</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="2-7-枚举命名"><a href="#2-7-枚举命名" class="headerlink" title="2.7 枚举命名"></a>2.7 枚举命名</h3><ul><li>总述<br>枚举的命名应当和 <a href="#2-5-%E5%B8%B8%E9%87%8F%E5%91%BD%E5%90%8D">常量</a> 一致: kEnumName 。</li><li>说明<br>单独的枚举值应该优先采用常量的命名方式。 枚举名 UrlTableErrors (以及 AlternateUrlTableErrors) 是类型, 所以要用大小写混合的方式。</li><li>示例<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="keyword">enum</span> UrlTableErrors &#123;</span><br><span class="line">    kOK = <span class="number">0</span>,</span><br><span class="line">    kErrorOutOfMemory,</span><br><span class="line">    kErrorMalformedInput,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li></ul><h3 id="2-8-宏命名"><a href="#2-8-宏命名" class="headerlink" title="2.8 宏命名"></a>2.8 宏命名</h3><ul><li>总述<br>一般来说不推荐使用宏定义，应该使用常量或者内联函数替代。如果你一定要用, 像这样命名: MY_MACRO_THAT_SCARES_SMALL_CHILDREN。</li><li>说明<br>通常不应该使用宏. 如果不得不用, 其命名全部大写, 使用下划线分隔单词。像 LOGD 这种调试信息，需要输出函数名，位置信息的，就必须得用宏定义了。</li><li>示例<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LOGD(priority, tag, ...) Log::print(priority, tag, __FUNCTION__, __LINE__, __VA_ARGS__)</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> PI_ROUNDED 3.0</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="3-头文件"><a href="#3-头文件" class="headerlink" title="3. 头文件"></a>3. 头文件</h2><p>通常每一个 .cpp 文件都有一个对应的 .h 文件。 也有一些常见例外, 如单元测试代码和只包含 main() 函数的 .cpp 文件。<br>正确使用头文件可令代码在可读性、文件大小和性能上大为改观。下面的规则将引导你规避使用头文件时的各种陷阱。</p><h3 id="3-1-Self-contained-头文件"><a href="#3-1-Self-contained-头文件" class="headerlink" title="3.1 Self-contained 头文件"></a>3.1 Self-contained 头文件</h3><ul><li>总述<br>看 google 原来的规范，写了一大堆，还是不太明白。个人的理解是：头文件本身依赖的其它头文件，需要全部包含。</li><li>说明<br>确保你的 header files 包含了你需要的所有东西， 而不是假设你 #include 进来的某个（些）headers 帮你包含了你需要的东西。</li><li>示例<br>不好的做法:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// A.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"B.h"</span></span></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// B.h</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"C.h"</span></span></span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// C.h</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span>&#123;</span>&#125;;</span><br></pre></td></tr></table></figure>有 A.h, B.h, C.h 三个头文件，A.h 中需要使用 C.h 中的 MyClass 类，虽然你 #include B.h 也实现了使用 MyClass 的目的，但是这并不是编程的最佳实践。这种写法会令人感到困惑， MyClass 到底是来自于哪里？最好的做法是在 A.h 中直接 #include C.h。<br>另一种不好的做法：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// my_class.h </span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">   MyClass(<span class="built_in">std</span>::<span class="built_in">string</span> s);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><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">// my_class.cpp </span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"my_class.h"</span></span></span><br><span class="line">MyClass::MyClass(<span class="built_in">std</span>::<span class="built_in">string</span> s)</span><br><span class="line">&#123;&#125;</span><br></pre></td></tr></table></figure>这里 my_class.h 就不是 self-contained 的，他需要 <code>#include &lt;string&gt;</code>才能编译。上面的代码虽然编译不会带来问题，前提是 cpp 中你把 <code>#include &lt;string&gt;</code> 放在 <code>#include &quot;my_class.h</code> 的前面。如果将来某一天你不小心把 <code>#include &quot;my_class.h</code> 放在了<code>#include &lt;string&gt;</code> 前面， 就会报 string 未定义错误。cpp可以认为是单纯的使用某个头文件，我们不应该对头文件使用者做出任何假设。因此头文件要做到 self-contained。正确的做法是在 my_class.h 中 <code>#include &lt;string&gt;</code>。</li></ul><h3 id="3-2-避免头文件重复包含"><a href="#3-2-避免头文件重复包含" class="headerlink" title="3.2 避免头文件重复包含"></a>3.2 避免头文件重复包含</h3><ul><li>总述<br>所有头文件都应该使用 #define 来防止头文件被重复包含, 命名格式当是: <code>&lt;PROJECT&gt;_&lt;PATH&gt;_&lt;FILE&gt;_H_</code>。</li><li>说明<br>为保证唯一性, 头文件的命名应该基于所在项目源代码树的全路径。 例如, 项目 foo 中的头文件 foo/src/bar/baz.h 可按如下方式保护:</li><li>示例<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="meta-keyword">ifndef</span> FOO_BAR_BAZ_H_</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> FOO_BAR_BAZ_H_</span></span><br><span class="line">...</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">// FOO_BAR_BAZ_H_</span></span></span><br></pre></td></tr></table></figure></li></ul><h3 id="3-3-前置声明"><a href="#3-3-前置声明" class="headerlink" title="3.3 前置声明"></a>3.3 前置声明</h3><ul><li>总述<br>尽可能地避免使用前置声明。使用 #include 包含需要的头文件即可。</li><li>说明<br>所谓「前置声明」（forward declaration）是类、函数和模板的纯粹声明，没伴随着其定义。</li></ul><p>优点：</p><ol><li>前置声明能够节省编译时间，多余的 #include 会迫使编译器展开更多的文件，处理更多的输入。</li><li>前置声明能够节省不必要的重新编译的时间。 #include 使代码因为头文件中无关的改动而被重新编译多次。</li></ol><p>缺点：</p><ol><li>前置声明隐藏了依赖关系，头文件改动时，用户的代码会跳过必要的重新编译过程。</li><li>前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其 API. 例如扩大形参类型，加个自带默认参数的模板形参等等。</li><li>前置声明来自命名空间 std:: 的 symbol 时，其行为未定义。</li><li>前置声明了不少来自头文件的 symbol 时，就会比单单一行的 include 冗长。</li><li>仅仅为了能前置声明而重构代码（比如用指针成员代替对象成员）会使代码变得更慢更复杂。</li><li>极端情况下，用前置声明代替 includes 甚至都会暗暗地改变代码的含义。</li></ol><h3 id="3-4-include-的路径及顺序"><a href="#3-4-include-的路径及顺序" class="headerlink" title="3.4 #include 的路径及顺序"></a>3.4 #include 的路径及顺序</h3><ul><li>总述<br>使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖，头文件的包含顺序应为: 相关头文件(foobar.cpp 的相关头文件为 foobar.h) –&gt; C 库 –&gt; C++ 库 –&gt; 其他库的 .h –&gt; 本项目内的 .h。<br>在这些类型的头文件之间使用空行来分隔开。</li><li>说明<br>项目内头文件应按照项目源代码目录树结构排列, 避免使用 UNIX 特殊的快捷目录 <code>.</code> (当前目录) 或 <code>..</code> (上级目录)。 例如, google-awesome-project/src/base/logging.h 应该按如下方式包含:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"base/logging.h"</span></span></span><br></pre></td></tr></table></figure></li><li>示例<br>google-awesome-project/src/foo/internal/fooserver.cpp 的头文件包含顺序如下:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"foo/public/fooserver.h"</span> <span class="comment">// 相关头文件放在第一位</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// c 库头文件</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// c++ 库头文件</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;hash_map&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;vector&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 本项目内的其他头文件</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"base/basictypes.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"base/commandlineflags.h"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"foo/public/bar.h"</span></span></span><br></pre></td></tr></table></figure></li></ul><h2 id="4-作用域"><a href="#4-作用域" class="headerlink" title="4. 作用域"></a>4. 作用域</h2><h3 id="4-1-局部变量"><a href="#4-1-局部变量" class="headerlink" title="4.1 局部变量"></a>4.1 局部变量</h3><ul><li>总述<br>将函数变量尽可能置于最小作用域内, 并在变量声明时进行初始化。</li><li>说明<br>C++ 允许在函数的任何位置声明变量。 我们提倡在尽可能小的作用域中声明变量, 离第一次使用越近越好。 这使得代码浏览者更容易定位变量声明的位置, 了解变量的类型和初始值。 特别是，应使用初始化的方式替代声明再赋值, 比如:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> i;</span><br><span class="line">i = f(); <span class="comment">// 坏——初始化和声明分离</span></span><br><span class="line"><span class="keyword">int</span> j = g(); <span class="comment">// 好——初始化时声明</span></span><br><span class="line"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; v;</span><br><span class="line">v.push_back(<span class="number">1</span>); <span class="comment">// 用花括号初始化更好</span></span><br><span class="line">v.push_back(<span class="number">2</span>);</span><br><span class="line"><span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; v = &#123;<span class="number">1</span>, <span class="number">2</span>&#125;; <span class="comment">// 好——v 一开始就初始化</span></span><br></pre></td></tr></table></figure>属于 if, while 和 for 语句的变量应当在这些语句中正常地声明，这样子这些变量的作用域就被限制在这些语句中了，举例而言:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (<span class="keyword">const</span> <span class="keyword">char</span>* p = <span class="built_in">strchr</span>(str, <span class="string">'/'</span>)) str = p + <span class="number">1</span>;</span><br></pre></td></tr></table></figure></li><li>例外<br>有一个例外, 如果变量是一个对象, 每次进入作用域都要调用其构造函数, 每次退出作用域都要调用其析构函数， 这会导致效率降低。<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 低效的实现</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1000000</span>; ++i) &#123;</span><br><span class="line">    Foo f;                  <span class="comment">// 构造函数和析构函数分别调用 1000000 次!</span></span><br><span class="line">    f.DoSomething(i);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>在循环作用域外面声明这类变量要高效的多:<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">Foo f;                      <span class="comment">// 构造函数和析构函数只调用 1 次</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1000000</span>; ++i) &#123;</span><br><span class="line">    f.DoSomething(i);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="4-2-静态和全局变量"><a href="#4-2-静态和全局变量" class="headerlink" title="4.2 静态和全局变量"></a>4.2 静态和全局变量</h3><ul><li>总述<br>非基本类型变量的变量，不能作为全局变量和静态变量。静态变量或者全局变量不能通过函数调用来赋初始值</li><li>说明<br>基本类型变量是指 char, int, float, double 等。全局变量和静态变量是在静态存储区的，它们的初始化顺序是不确定的， 如果一个 class 类型的变量作为作为静态变量，则它的构造函数、析构函数和初始化的顺序在 C++ 中是只有部分明确的，甚至随着构建变化而变化，导致难以发现的 bug。</li></ul><h3 id="4-3-非成员函数、静态成员函数和全局函数"><a href="#4-3-非成员函数、静态成员函数和全局函数" class="headerlink" title="4.3 非成员函数、静态成员函数和全局函数"></a>4.3 非成员函数、静态成员函数和全局函数</h3><ul><li>总述<br>使用静态成员函数或命名空间内的非成员函数, 尽量不要用裸的全局函数。 将一系列函数直接置于命名空间中，不要用类的静态方法模拟出命名空间的效果，类的静态方法应当和类的实例或静态数据紧密相关。</li><li>说明<br>有时, 把函数的定义同类的实例脱钩是有益的, 甚至是必要的. 这样的函数可以被定义成静态成员, 或是非成员函数。 非成员函数不应依赖于外部变量, 应尽量置于某个命名空间内。 相比单纯为了封装若干不共享任何静态数据的静态成员函数而创建类, 不如使用命名空间(namespace) 。</li><li>示例<br>对于头文件 myproject/foo_bar.h , 应当使用<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> myproject &#123;</span><br><span class="line"><span class="keyword">namespace</span> foo_bar &#123;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Function1</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Function2</span><span class="params">()</span></span>;</span><br><span class="line">&#125;  <span class="comment">// namespace foo_bar</span></span><br><span class="line">&#125;  <span class="comment">// namespace myproject</span></span><br></pre></td></tr></table></figure>而非<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> myproject &#123;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FooBar</span> &#123;</span></span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Function1</span><span class="params">()</span></span>;</span><br><span class="line">  <span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Function2</span><span class="params">()</span></span>;</span><br><span class="line">&#125;;</span><br><span class="line">&#125;  <span class="comment">// namespace myproject</span></span><br></pre></td></tr></table></figure>定义在同一编译单元的函数, 被其他编译单元直接调用可能会引入不必要的耦合和链接时依赖; 静态成员函数对此尤其敏感。 可以考虑提取到新类中, 或者将函数置于独立库的命名空间内。<br>如果你必须定义非成员函数, 又只是在当前 .cpp 文件中使用它, 应该 static 关键字限定其作用域在当前文件内。如：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">Foobar</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  DoSomething();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><h2 id="5-类"><a href="#5-类" class="headerlink" title="5. 类"></a>5. 类</h2><p>类是 C++ 中代码的基本单元。 显然, 它们被广泛使用。 本节列举了在写一个类时的主要注意事项。</p><h3 id="5-1-构造函数"><a href="#5-1-构造函数" class="headerlink" title="5.1 构造函数"></a>5.1 构造函数</h3><ul><li>总述<br>不要在构造函数中调用虚函数, 也不要在构造函数中抛异常。<br>－说明<br>这两个问题需要花点篇幅才能讲明白，这里不讨论，请参考 <a href="http://qiushao.net/2019/04/07/c++/do-not-call-virtual-function-in-constructor/">永远不要在构造函数和析构函数中调用虚函数</a> 和 <a href="https://harttle.land/2015/07/26/effective-cpp-8.html" target="_blank" rel="noopener">不要在构造函数和析构函数中抛异常</a></li></ul><h3 id="5-2-存取控制"><a href="#5-2-存取控制" class="headerlink" title="5.2 存取控制"></a>5.2 存取控制</h3><ul><li>总述<br>将所有数据成员声明为 private, 除非是 static const 类型成员 (遵循 常量命名规则)。</li></ul><h3 id="5-3-声明顺序"><a href="#5-3-声明顺序" class="headerlink" title="5.3 声明顺序"></a>5.3 声明顺序</h3><ul><li>总述<br>将相似的声明放在一起, 将 public 部分放在最前。</li><li>说明<br>类定义一般应以 public 开始, 后跟 protected, 最后是 private。<br>在各个部分中, 建议将类似的声明放在一起, 并且建议以如下的顺序: 类型 (包括 typedef, using 和嵌套的结构体与类), 常量, 工厂函数, 构造函数, 赋值运算符, 析构函数, 其它函数, 数据成员。</li><li>注意<br>不要将大段的函数定义内联在类定义中。 通常，只有那些普通的, 或性能关键且短小的函数可以内联在类定义中。</li></ul><h2 id="6-函数"><a href="#6-函数" class="headerlink" title="6. 函数"></a>6. 函数</h2><h3 id="6-1-参数顺序"><a href="#6-1-参数顺序" class="headerlink" title="6.1 参数顺序"></a>6.1 参数顺序</h3><ul><li>总述<br>函数的参数顺序为: 输入参数在先, 输出参数在后。</li><li>说明<br>C/C++ 中的函数参数或者是函数的输入, 或者是函数的输出, 或兼而有之。 输入参数通常是值参或 const 引用, 输出参数或输入/输出参数则一般为非 const 指针。 在排列参数顺序时, 将所有的输入参数置于输出参数之前。 特别要注意, 在加入新参数时不要因为它们是新参数就置于参数列表最后, 而是仍然要按照前述的规则, 即将新的输入参数也置于输出参数之前。</li><li>示例<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">foobar</span><span class="params">(<span class="keyword">const</span> <span class="keyword">int</span> foo_in, <span class="keyword">const</span> MyClass &amp;bar_in, <span class="keyword">int</span> *result_out)</span></span>;</span><br></pre></td></tr></table></figure></li></ul><h3 id="6-2-编写简短函数"><a href="#6-2-编写简短函数" class="headerlink" title="6.2 编写简短函数"></a>6.2 编写简短函数</h3><ul><li>总述<br>我们倾向于编写简短, 凝练的函数。</li><li>说明<br>我们承认长函数有时是合理的, 因此并不硬性限制函数的长度。 如果函数超过 40 行, 可以思索一下能不能在不影响程序结构的前提下对其进行分割。<br>即使一个长函数现在工作的非常好, 一旦有人对其修改, 有可能出现新的问题, 甚至导致难以发现的 bug。 使函数尽量简短, 以便于他人阅读和修改代码。<br>在处理代码时, 你可能会发现复杂的长函数。 不要害怕修改现有代码: 如果证实这些代码使用 / 调试起来很困难, 或者你只需要使用其中的一小段代码, 考虑将其分割为更加简短并易于管理的若干函数。</li></ul><h3 id="6-3-引用参数"><a href="#6-3-引用参数" class="headerlink" title="6.3 引用参数"></a>6.3 引用参数</h3><ul><li>总述<br>所有按引用传递的参数必须加上 const。</li><li>定义<br>在 C 语言中, 如果函数需要修改变量的值, 参数必须为指针, 如 int foo(int *pval)。 在 C++ 中, 函数还可以声明为引用参数: int foo(int &amp;val)。</li><li>优点<br>定义引用参数可以防止出现 (*pval)++ 这样丑陋的代码。 引用参数对于拷贝构造函数这样的应用也是必需的， 同时也更明确地不接受空指针。</li><li>缺点<br>容易引起误解, 因为引用在语法上是值变量却拥有指针的语义。</li><li>结论</li></ul><p><strong>函数参数列表中, 所有引用参数都必须是 const</strong>:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">Foo</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span> &amp;in, <span class="built_in">string</span> *out)</span></span>;</span><br></pre></td></tr></table></figure><p>事实上这在 Google Code 是一个硬性约定: 输入参数是值参或 const 引用, 输出参数为指针。 输入参数可以是 const 指针, 但决不能是非 const 的引用参数, 除非特殊要求, 比如 swap()。<br>有时候, 在输入形参中用 const T* 指针比 const T&amp; 更明智， 比如:</p><ol><li>可能会传递空指针。</li><li>函数要把指针或对地址的引用赋值给输入形参。<br>总而言之, 大多时候输入形参往往是 const T&amp;。 若用 const T* 则说明输入另有处理。 所以若要使用 const T*, 则应给出相应的理由, 否则会使得读者感到迷惑。</li></ol><h3 id="6-4-函数重载"><a href="#6-4-函数重载" class="headerlink" title="6.4 函数重载"></a>6.4 函数重载</h3><ul><li>总述<br>若要使用函数重载, 则必须能让读者一看调用点就胸有成竹, 而不用花心思猜测调用的重载函数到底是哪一种。 这一规则也适用于构造函数。</li><li>定义<br>你可以编写一个参数类型为 const string&amp; 的函数, 然后用另一个参数类型为 const char* 的函数对其进行重载:<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="class"><span class="keyword">class</span> <span class="title">MyClass</span> &#123;</span></span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">Analyze</span><span class="params">(<span class="keyword">const</span> <span class="built_in">string</span> &amp;<span class="built_in">text</span>)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">Analyze</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *<span class="built_in">text</span>, <span class="keyword">size_t</span> textlen)</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li>优点<br>通过重载参数不同的同名函数, 可以令代码更加直观。 模板化代码需要重载, 这同时也能为使用者带来便利。</li><li>缺点<br>如果函数单靠不同的参数类型而重载 (acgtyrant 注：这意味着参数数量不变), 读者就得十分熟悉 C++ 五花八门的匹配规则, 以了解匹配过程具体到底如何。 另外, 如果派生类只重载了某个函数的部分变体, 继承语义就容易令人困惑。</li><li>结论<br>如果打算重载一个函数, 可以试试改在函数名里加上参数信息。 例如, 用 AppendString() 和 AppendInt() 等, 而不是一口气重载多个 Append()。 如果重载函数的目的是为了支持不同数量的同一类型参数, 则优先考虑使用 std::vector 以便使用者可以用 <a href="#">列表初始化</a> 指定参数。</li></ul><h3 id="6-5-缺省参数"><a href="#6-5-缺省参数" class="headerlink" title="6.5 缺省参数"></a>6.5 缺省参数</h3><ul><li>总述<br>只允许在非虚函数中使用缺省参数, 且必须保证缺省参数的值始终一致。 缺省参数与 <a href="#">函数重载</a> 遵循同样的规则。 一般情况下建议使用函数重载, 尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺点的情况下。</li><li>优点<br>有些函数一般情况下使用默认参数, 但有时需要又使用非默认的参数。 缺省参数为这样的情形提供了便利, 使程序员不需要为了极少的例外情况编写大量的函数。和函数重载相比, 缺省参数的语法更简洁明了, 减少了大量的样板代码, 也更好地区别了 “必要参数” 和 “可选参数”。</li><li>缺点</li></ul><ol><li>缺省参数实际上是函数重载语义的另一种实现方式, 因此所有 <code>不应当使用函数重载的理由</code> 也都适用于缺省参数。</li><li>虚函数调用的缺省参数取决于目标对象的静态类型, 此时无法保证给定函数的所有重载声明的都是同样的缺省参数。</li><li>缺省参数是在每个调用点都要进行重新求值的, 这会造成生成的代码迅速膨胀。 作为读者, 一般来说也更希望缺省的参数在声明时就已经被固定了, 而不是在每次调用时都可能会有不同的取值。</li><li>缺省参数会干扰函数指针, 导致函数签名与调用点的签名不一致. 而函数重载不会导致这样的问题。</li></ol><ul><li>结论<br>对于虚函数, 不允许使用缺省参数, 因为在虚函数中缺省参数不一定能正常工作。如果在每个调用点缺省参数的值都有可能不同, 在这种情况下缺省函数也不允许使用。 例如, 不要写像 <code>void f(int n = counter++);</code> 这样的代码。<br>在其他情况下, 如果缺省参数对可读性的提升远远超过了以上提及的缺点的话, 可以使用缺省参数。 如果仍有疑惑, 就使用函数重载。</li></ul><h2 id="7-注释"><a href="#7-注释" class="headerlink" title="7. 注释"></a>7. 注释</h2><p>注释虽然写起来很痛苦, 但对保证代码可读性至关重要。 下面的规则描述了如何注释以及在哪儿注释。 当然也要记住: 注释固然很重要, 但最好的代码应当本身就是文档。 有意义的类型名和变量名, 要远胜过要用注释解释的含糊不清的名字。<br>你写的注释是给代码读者看的, 也就是下一个需要理解你的代码的人。 所以慷慨些吧, 下一个读者可能就是你!</p><h3 id="7-1-注释风格"><a href="#7-1-注释风格" class="headerlink" title="7.1 注释风格"></a>7.1 注释风格</h3><ul><li>总述<br>统一使用 <code>//</code> 注释风格。</li><li>说明<br>除非是更改遗留代码，遗留代码的风格是使用 <code>/* */</code>，那就跟着用 <code>/* */</code>。<br>除非你的英文很好，可以描述清楚，否则还是使用中文注释吧。</li></ul><h3 id="7-2-文件注释"><a href="#7-2-文件注释" class="headerlink" title="7.2 文件注释"></a>7.2 文件注释</h3><ul><li>总述<br>在每一个文件开头加入版权公告。<br>文件注释描述了该文件的内容. 如果一个文件只声明, 或实现, 或测试了一个对象, 并且这个对象已经在它的声明处进行了详细的注释, 那么就没必要再加上文件注释。 除此之外的其他文件都需要文件注释。</li><li>说明</li></ul><ol><li>作者信息<br>版权信息里面包含作者信息，可以炫耀你的成就, 也是为了出问题别人可以找你。<br>如果你对原始作者的文件做了重大修改, 请考虑删除原作者信息，增加你自己的信息。</li><li>文件内容<br>如果一个 .h 文件声明了多个概念, 则文件注释应当对文件的内容做一个大致的说明, 同时说明各概念之间的联系。 一个一到两行的文件注释就足够了, 对于每个概念的详细文档应当放在各个概念中, 而不是文件注释中。<br>不要在 .h 和 .cc 之间复制注释, 这样的注释偏离了注释的实际意义。<br>具体的文件注释格式，请参考项目中现有的格式。</li></ol><h3 id="7-3-类注释"><a href="#7-3-类注释" class="headerlink" title="7.3 类注释"></a>7.3 类注释</h3><ul><li>总述<br>每个类的定义都要附带一份注释, 描述类的功能和用法, 除非它的功能相当明显。比如：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Iterates over the contents of a GargantuanTable.</span></span><br><span class="line"><span class="comment">// Example:</span></span><br><span class="line"><span class="comment">//    GargantuanTableIterator* iter = table-&gt;NewIterator();</span></span><br><span class="line"><span class="comment">//    for (iter-&gt;Seek("foo"); !iter-&gt;done(); iter-&gt;Next()) &#123;</span></span><br><span class="line"><span class="comment">//      process(iter-&gt;key(), iter-&gt;value());</span></span><br><span class="line"><span class="comment">//    &#125;</span></span><br><span class="line"><span class="comment">//    delete iter;</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">GargantuanTableIterator</span> &#123;</span></span><br><span class="line">  ...</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li><li>说明<br>类注释应当为读者理解如何使用与何时使用类提供足够的信息, 同时应当提醒读者在正确使用此类时应当考虑的因素。 如果类有任何同步前提, 请用文档说明。 如果该类的实例可被多线程访问, 要特别注意文档说明多线程环境下相关的规则和常量使用。<br>如果你想用一小段代码演示这个类的基本用法或通常用法, 放在类注释里也非常合适。<br>如果类的声明和定义分开了(例如分别放在了 .h 和 .cc 文件中), 此时, <strong>描述类用法的注释应当和接口定义放在一起。 描述类的操作和实现的注释应当和实现放在一起。</strong></li></ul><h3 id="7-4-函数注释"><a href="#7-4-函数注释" class="headerlink" title="7.4 函数注释"></a>7.4 函数注释</h3><ul><li>总述<br>函数声明处的注释描述函数功能; 定义处的注释描述函数实现。</li><li>说明</li></ul><ol><li>函数声明<br>基本上每个函数声明处前都应当加上注释, 描述函数的功能和用途。 只有在函数的功能简单而明显时才能省略这些注释(例如, 简单的取值和设值函数)。<br>函数声明处注释的内容:<ul><li>函数的输入输出.</li><li>对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.</li><li>函数是否分配了必须由调用者释放的空间.</li><li>参数是否可以为空指针.</li><li>是否存在函数使用上的性能隐患.</li><li>如果函数是可重入的, 其同步前提是什么?</li></ul></li></ol><p>举例如下:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Returns an iterator for this table.  It is the client's</span></span><br><span class="line"><span class="comment">// responsibility to delete the iterator when it is done with it,</span></span><br><span class="line"><span class="comment">// and it must not use the iterator once the GargantuanTable object</span></span><br><span class="line"><span class="comment">// on which the iterator was created has been deleted.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// The iterator is initially positioned at the beginning of the table.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// This method is equivalent to:</span></span><br><span class="line"><span class="comment">//    Iterator* iter = table-&gt;NewIterator();</span></span><br><span class="line"><span class="comment">//    iter-&gt;Seek("");</span></span><br><span class="line"><span class="comment">//    return iter;</span></span><br><span class="line"><span class="comment">// If you are going to immediately seek to another place in the</span></span><br><span class="line"><span class="comment">// returned iterator, it will be faster to use NewIterator()</span></span><br><span class="line"><span class="comment">// and avoid the extra seek.</span></span><br><span class="line"><span class="function">Iterator* <span class="title">GetIterator</span><span class="params">()</span> <span class="keyword">const</span></span>;</span><br></pre></td></tr></table></figure><p>但也要避免罗罗嗦嗦, 或者对显而易见的内容进行说明. 下面的注释就没有必要加上 “否则返回 false”, 因为已经暗含其中了:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Returns true if the table cannot hold any more entries.</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">IsTableFull</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure><p>注释函数重载时, 注释的重点应该是函数中被重载的部分, 而不是简单的重复被重载的函数的注释。 多数情况下, 函数重载不需要额外的文档, 因此也没有必要加上注释。<br>注释构造/析构函数时, 切记读代码的人知道构造/析构函数的功能, 所以 “销毁这一对象” 这样的注释是没有意义的。 你应当注明的是注明构造函数对参数做了什么 (例如, 是否取得指针所有权) 以及析构函数清理了什么。 如果都是些无关紧要的内容, 直接省掉注释。 析构函数前没有注释是很正常的。<br>2. 函数定义<br>如果函数的实现过程中用到了很巧妙的方式, 那么在函数定义处应当加上解释性的注释。 例如, 你所使用的编程技巧, 实现的大致步骤, 或解释如此实现的理由。 举个例子, 你可以说明为什么函数的前半部分要加锁而后半部分不需要。<br>不要从 .h 文件或其他地方的函数声明处直接复制注释。 简要重述函数功能是可以的, 但注释重点要放在如何实现上。</p><h3 id="7-5-变量注释"><a href="#7-5-变量注释" class="headerlink" title="7.5 变量注释"></a>7.5 变量注释</h3><ul><li>总述<br>通常变量名本身足以很好说明变量用途。 某些情况下, 也需要额外的注释说明.</li><li>说明<ul><li>类数据成员<br>每个类数据成员 (也叫实例变量或成员变量) 都应该用注释说明用途。 如果有非变量的参数(例如特殊值, 数据成员之间的关系, 生命周期等)不能够用类型与变量名明确表达, 则应当加上注释。 然而, 如果变量类型与变量名已经足以描述一个变量, 那么就不再需要加上注释。<br>特别地, 如果变量可以接受 NULL 或 -1 等警戒值, 须加以说明. 比如:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="comment">// Used to bounds-check table accesses. -1 means</span></span><br><span class="line"> <span class="comment">// that we don't yet know how many entries the table has.</span></span><br><span class="line"> <span class="keyword">int</span> num_total_entries_;</span><br></pre></td></tr></table></figure></li><li>全局变量<br>和数据成员一样, 所有全局变量也要注释说明含义及用途, 以及作为全局变量的原因。 比如:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The total number of tests cases that we run through in this regression test.</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">int</span> kNumTestCases = <span class="number">6</span>;</span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="7-6-实现注释"><a href="#7-6-实现注释" class="headerlink" title="7.6 实现注释"></a>7.6 实现注释</h3><ul><li>总述<br>对于代码中巧妙的, 晦涩的, 有趣的, 重要的地方加以注释。</li><li>说明<ul><li>代码前注释<br>巧妙或复杂的代码段前要加注释. 比如:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Divide result by two, taking into account that x</span></span><br><span class="line"><span class="comment">// contains the carry from the add.</span></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; result-&gt;<span class="built_in">size</span>(); i++) &#123;</span><br><span class="line">  x = (x &lt;&lt; <span class="number">8</span>) + (*result)[i];</span><br><span class="line">  (*result)[i] = x &gt;&gt; <span class="number">1</span>;</span><br><span class="line">  x &amp;= <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>行注释<br>比较隐晦的地方要在行尾加入注释, 在行尾空两格进行注释. 比如:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// If we have enough memory, mmap the data portion too.</span></span><br><span class="line">mmap_budget = <span class="built_in">max</span>&lt;int64&gt;(<span class="number">0</span>, mmap_budget - index_-&gt;length());</span><br><span class="line"><span class="keyword">if</span> (mmap_budget &gt;= data_size_ &amp;&amp; !MmapData(mmap_chunk_bytes, mlock))</span><br><span class="line">  <span class="keyword">return</span>;  <span class="comment">// Error already logged.</span></span><br></pre></td></tr></table></figure>注意, 这里用了两段注释分别描述这段代码的作用, 和提示函数返回时错误已经被记入日志.<br>如果你需要连续进行多行注释, 可以使之对齐获得更好的可读性:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">DoSomething();                  <span class="comment">// Comment here so the comments line up.</span></span><br><span class="line">DoSomethingElseThatIsLonger();  <span class="comment">// Two spaces between the code and the comment.</span></span><br></pre></td></tr></table></figure></li><li>函数参数注释<br>如果函数参数的意义不明显, 考虑用下面的方式进行弥补:<ul><li>如果参数是一个字面常量, 并且这一常量在多处函数调用中被使用, 用以推断它们一致, 你应当用一个常量名让这一约定变得更明显, 并且保证这一约定不会被打破.</li><li>考虑更改函数的签名, 让某个 bool 类型的参数变为 enum 类型, 这样可以让这个参数的值表达其意义.</li><li>如果某个函数有多个配置选项, 你可以考虑定义一个类或结构体以保存所有的选项, 并传入类或结构体的实例. 这样的方法有许多优点, 例如这样的选项可以在调用处用变量名引用, 这样就能清晰地表明其意义. 同时也减少了函数参数的数量, 使得函数调用更易读也易写. 除此之外, 以这样的方式, 如果你使用其他的选项, 就无需对调用点进行更改.</li><li>用具名变量代替大段而复杂的嵌套表达式.</li><li>万不得已时, 才考虑在调用点用注释阐明参数的意义.<br>比如下面的示例的对比:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// What are these arguments?</span></span><br><span class="line"><span class="keyword">const</span> DecimalNumber product = CalculateProduct(values, <span class="number">7</span>, <span class="literal">false</span>, <span class="literal">nullptr</span>);</span><br></pre></td></tr></table></figure>和<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">ProductOptions options;</span><br><span class="line">options.set_precision_decimals(<span class="number">7</span>);</span><br><span class="line">options.set_use_cache(ProductOptions::kDontUseCache);</span><br><span class="line"><span class="keyword">const</span> DecimalNumber product = CalculateProduct(values, options, <span class="comment">/*completion_callback=*/</span><span class="literal">nullptr</span>);</span><br></pre></td></tr></table></figure>哪个更清晰一目了然。</li></ul></li><li>不允许的行为<br>不要描述显而易见的现象, 永远不要 用自然语言翻译代码作为注释, 除非即使对深入理解 C++ 的读者来说代码的行为都是不明显的。 要假设读代码的人 C++ 水平比你高, 即便他/她可能不知道你的用意。<br>你所提供的注释应当解释代码为什么要这么做和代码的目的, 或者最好是让代码自文档化.<br>比较这样的注释:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Find the element in the vector.  &lt;-- 差: 这太明显了!</span></span><br><span class="line"><span class="keyword">auto</span> iter = <span class="built_in">std</span>::<span class="built_in">find</span>(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>(), element);</span><br><span class="line"><span class="keyword">if</span> (iter != v.<span class="built_in">end</span>()) &#123;</span><br><span class="line">  <span class="built_in">Process</span>(element);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>和这样的注释:<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">// Process "element" unless it was already processed.</span></span><br><span class="line"><span class="keyword">auto</span> iter = <span class="built_in">std</span>::<span class="built_in">find</span>(v.<span class="built_in">begin</span>(), v.<span class="built_in">end</span>(), element);</span><br><span class="line"><span class="keyword">if</span> (iter != v.<span class="built_in">end</span>()) &#123;</span><br><span class="line">  <span class="built_in">Process</span>(element);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>自文档化的代码根本就不需要注释. 上面例子中的注释对下面的代码来说就是毫无必要的:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!IsAlreadyProcessed(element)) &#123;</span><br><span class="line">  <span class="built_in">Process</span>(element);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="7-7-TODO-注释"><a href="#7-7-TODO-注释" class="headerlink" title="7.7 TODO 注释"></a>7.7 TODO 注释</h3><ul><li>总述<br>对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释。</li><li>说明<br>TODO 注释要使用全大写的字符串 TODO, 在随后的圆括号里写上你的名字, 邮件地址, bug ID, 或其它身份标识和与这一 TODO 相关的 issue。 主要目的是让添加注释的人 (也是可以请求提供更多细节的人) 可根据规范的 TODO 格式进行查找. 添加 TODO 注释并不意味着你要自己来修正, 因此当你加上带有姓名的 TODO 时, 一般都是写上自己的名字。<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="comment">// TODO(kl@gmail.com): Use a "*" here for concatenation operator.</span></span><br><span class="line"><span class="comment">// TODO(Zeke) change this to use relations.</span></span><br><span class="line"><span class="comment">// TODO(bug 12345): remove the "Last visitors" feature</span></span><br></pre></td></tr></table></figure>如果加 TODO 是为了在 “将来某一天做某事”, 可以附上一个非常明确的时间 (“Fix by November 2005”), 或者一个明确的事项 (“Remove this code when all clients can handle XML responses.”)。</li></ul><h3 id="7-8-弃用注释"><a href="#7-8-弃用注释" class="headerlink" title="7.8 弃用注释"></a>7.8 弃用注释</h3><ul><li>总述<br>通过弃用注释（DEPRECATED comments）以标记某接口点已弃用。</li><li>说明<br>您可以写上包含全大写的 DEPRECATED 的注释, 以标记某接口为弃用状态. 注释可以放在接口声明前, 或者同一行。<br>在 DEPRECATED 一词后, 在括号中留下您的名字, 邮箱地址以及其他身份标识。<br>弃用注释应当包涵简短而清晰的指引, 以帮助其他人修复其调用点。 在 C++ 中, 你可以将一个弃用函数改造成一个内联函数, 这一函数将调用新的接口。<br>仅仅标记接口为 DEPRECATED 并不会让大家不约而同地弃用, 您还得亲自主动修正调用点（callsites）, 或是找个帮手。<br>修正好的代码应该不会再涉及弃用接口点了, 着实改用新接口点。 如果您不知从何下手, 可以找标记弃用注释的当事人一起商量。</li></ul><h2 id="8-格式"><a href="#8-格式" class="headerlink" title="8. 格式"></a>8. 格式</h2><p>每个人都可能有自己的代码风格和格式, 但如果一个项目中的所有人都遵循同一风格的话, 这个项目就能更顺利地进行。 每个人未必能同意下述的每一处格式规则, 而且其中的不少规则需要一定时间的适应, 但整个项目服从统一的编程风格是很重要的, 只有这样才能让所有人轻松地阅读和理解代码。</p><h3 id="8-1-缩进"><a href="#8-1-缩进" class="headerlink" title="8.1 缩进"></a>8.1 缩进</h3><ul><li>总述<br>统一使用 4 个空格。</li><li>说明<br>不要使用 Tab 键来缩进，不然其他同学看你的代码的时候，缩进可能就是乱七八糟的。虽然大多数文本编辑器上默认 Tab 键的长度是 4 个空格，你自己看起来没问题。但也有部分编辑器默认 tab 是 2 个空格或者 8 个空格的。比如 gerrit 的默认配置好像就是 tab 的显示宽度是 8 个空格。我在 gerrit 上 review 代码的时候，经常看到乱七八糟的缩进。所有的文本编辑器都是可以设置 tab 转换成 4 个空格的。最好设置一下自动转换，这样当你不小心使用了 tab 来缩进的时候，编辑器会帮你改成 4 个空格。</li></ul><p><strong>Attention 例外：Makefile 要求命令是以 tab 缩进的</strong></p><h3 id="8-2-函数声明与定义"><a href="#8-2-函数声明与定义" class="headerlink" title="8.2 函数声明与定义"></a>8.2 函数声明与定义</h3><ul><li>总述<br>返回类型和函数名在同一行, 参数也尽量放在同一行, 如果放不下就对形参分行。</li><li>说明<br>函数看上去像这样:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">ReturnType <span class="title">ClassName::FunctionName</span><span class="params">(Type par_name1, Type par_name2)</span> </span>&#123;</span><br><span class="line">    DoSomething();</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>如果同一行文本太多, 放不下所有参数:<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="function">ReturnType <span class="title">ClassName::ReallyLongFunctionName</span><span class="params">(Type par_name1, Type par_name2,</span></span></span><br><span class="line"><span class="function"><span class="params">                                             Type par_name3)</span> </span>&#123;</span><br><span class="line">    DoSomething();</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>甚至连第一个参数都放不下:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">ReturnType <span class="title">LongClassName::ReallyReallyReallyLongFunctionName</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    Type par_name1, </span></span></span><br><span class="line"><span class="function"><span class="params">    Type par_name2,</span></span></span><br><span class="line"><span class="function"><span class="params">    Type par_name3)</span> </span>&#123;</span><br><span class="line">    DoSomething(); </span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li>注意以下几点:<ul><li>使用好的参数名，参考命名规范。</li><li>只有在参数未被使用或者其用途非常明显时, 才能省略参数名。</li><li>如果返回类型和函数名在一行放不下, 分行。</li><li>如果返回类型与函数声明或定义分行了, 不要缩进。</li><li>左圆括号总是和函数名在同一行。</li><li>函数名和左圆括号间永远没有空格。</li><li>圆括号与参数间没有空格。</li><li>左大括号总在最后一个参数同一行的末尾处, 不另起新行。</li><li>右大括号总是单独位于函数最后一行, 或者与左大括号同一行。</li><li>右圆括号和左大括号间总是有一个空格。</li></ul></li></ul><h3 id="8-3-函数调用"><a href="#8-3-函数调用" class="headerlink" title="8.3 函数调用"></a>8.3 函数调用</h3><ul><li>总述<br>要么一行写完函数调用, 要么在圆括号里对参数分行, 要么参数另起一行且缩进四格。 如果没有其它顾虑的话, 尽可能精简行数, 比如把多个参数适当地放在同一行里.</li><li>说明<br>函数调用遵循如下形式：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">bool</span> retval = DoSomething(argument1, argument2, argument3);</span><br></pre></td></tr></table></figure>如果同一行放不下, 可断为多行, 后面每一行都和第一个实参对齐, 左圆括号后和右圆括号前不要留空格：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">bool</span> retval = DoSomething(averyveryveryverylongargument1,</span><br><span class="line">                          argument2, argument3);</span><br></pre></td></tr></table></figure>参数也可以放在次行, 缩进四格：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (...) &#123;</span><br><span class="line">  ...</span><br><span class="line">  ...</span><br><span class="line">  <span class="keyword">if</span> (...) &#123;</span><br><span class="line">    DoSomething(</span><br><span class="line">        argument1, argument2,  <span class="comment">// 4 空格缩进</span></span><br><span class="line">        argument3, argument4);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>如果一系列参数本身就有一定的结构, 可以酌情地按其结构来决定参数格式：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通过 3x3 矩阵转换 widget.</span></span><br><span class="line">my_widget.Transform(x1, x2, x3,</span><br><span class="line">                    y1, y2, y3,</span><br><span class="line">                    z1, z2, z3);</span><br></pre></td></tr></table></figure></li></ul><h3 id="8-4-条件-循环语句"><a href="#8-4-条件-循环语句" class="headerlink" title="8.4 条件/循环语句"></a>8.4 条件/循环语句</h3><ul><li>总述<br>以下规则适用于 if 条件判断，和 for, while 循环。<br>if 后面即使只有一行代码，也一定要加 <code>{}</code><br><code>if</code> 与 <code>()</code> 之间有空格， <code>()</code> 与条件之间不需要空格。</li><li>说明<br>有些同学觉得 if 条件后面就只有一行代码而已，加不加 {} 无所谓：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (condition) </span><br><span class="line">    doSomething();</span><br></pre></td></tr></table></figure>但我们强制要加 {}，因为我们经历过了几次不加 {}, 引起的惨痛教训。假如有以上代码，最初的时候只有一行，不加 {}，但是后来人维护的时候，需要在满足这个条件的情况下，做些其他操作，于是他加了一行代码：<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (condition) </span><br><span class="line">    doSomething();</span><br><span class="line">    doOtherThing();</span><br></pre></td></tr></table></figure>于是坑就被埋下了。<code>doOtherThing();</code> 在任何条件下都会执行的。</li><li>示例<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(condition)     <span class="comment">// 差 - IF 后面没空格.</span></span><br><span class="line"><span class="keyword">if</span> (condition)&#123;   <span class="comment">// 差 - &#123; 前面没空格.</span></span><br><span class="line"><span class="keyword">if</span>(condition)&#123;    <span class="comment">// 变本加厉地差.</span></span><br><span class="line"><span class="keyword">if</span> (condition) &#123;  <span class="comment">// 好 - IF 和 &#123; 都与空格紧邻.</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="8-5-指针和引用表达式"><a href="#8-5-指针和引用表达式" class="headerlink" title="8.5 指针和引用表达式"></a>8.5 指针和引用表达式</h3><ul><li>总述<br>句点或箭头前后不要有空格。 指针/地址操作符 (*, &amp;) 之后不能有空格。</li><li>说明<br>下面是指针和引用表达式的正确使用范例:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">x = *p;</span><br><span class="line">p = &amp;x;</span><br><span class="line">x = r.y;</span><br><span class="line">x = r-&gt;y;</span><br></pre></td></tr></table></figure>在声明指针变量或参数时, 星号统一与变量名紧挨:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 好, 星号统变量名紧挨</span></span><br><span class="line"><span class="keyword">char</span> *c;</span><br><span class="line"><span class="keyword">const</span> <span class="built_in">string</span> &amp;str;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 差, 星号与类型紧挨</span></span><br><span class="line"><span class="keyword">char</span>* c;</span><br><span class="line"><span class="keyword">const</span> <span class="built_in">string</span>&amp; str;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 差, */&amp; 两边都有空格</span></span><br><span class="line"><span class="keyword">char</span> * c;  </span><br><span class="line"><span class="keyword">const</span> <span class="built_in">string</span> &amp; str; </span><br><span class="line"></span><br><span class="line"><span class="comment">// 不允许 - 在多重声明中不能使用 &amp; 或 *</span></span><br><span class="line"><span class="keyword">int</span> x, *y;</span><br></pre></td></tr></table></figure></li></ul><h3 id="8-6-预处理指令"><a href="#8-6-预处理指令" class="headerlink" title="8.6 预处理指令"></a>8.6 预处理指令</h3><ul><li>总述<br>预处理指令不要缩进, 从行首开始。</li><li>说明<br>即使预处理指令位于缩进代码块中, 指令也应从行首开始。</li><li>示例<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 好 - 指令从行首开始</span></span><br><span class="line">  <span class="keyword">if</span> (lopsided_score) &#123;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> DISASTER_PENDING </span></span><br><span class="line">    DropEverything();</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    BackToNormal();</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 差 - 指令缩进</span></span><br><span class="line">  <span class="keyword">if</span> (lopsided_score) &#123;</span><br><span class="line">    <span class="meta">#<span class="meta-keyword">if</span> DISASTER_PENDING </span></span><br><span class="line">    DropEverything();</span><br><span class="line">    <span class="meta">#<span class="meta-keyword">endif</span> </span></span><br><span class="line">    BackToNormal();</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure></li></ul><h3 id="8-7-类格式"><a href="#8-7-类格式" class="headerlink" title="8.7 类格式"></a>8.7 类格式</h3><ul><li>总述<br>访问控制块的声明依次序是 public, protected, private。public 这些关键字不需要缩进。</li><li>说明<br>类声明 (下面的代码中缺少注释, 参考 类注释) 的基本格式如下:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyClass</span> :</span> <span class="keyword">public</span> OtherClass &#123;</span><br><span class="line"><span class="keyword">public</span>:      <span class="comment">// 不需要空格缩进</span></span><br><span class="line">    <span class="comment">// 先构造/析构函数</span></span><br><span class="line">    MyClass();  <span class="comment">// 四空格缩进</span></span><br><span class="line">    <span class="function"><span class="keyword">explicit</span> <span class="title">MyClass</span><span class="params">(<span class="keyword">int</span> var)</span></span>;</span><br><span class="line">    ~MyClass() &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 对外提供的方法</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">SomeFunction</span><span class="params">()</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">SomeFunctionThatDoesNothing</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// set/get 方法</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">set_some_var</span><span class="params">(<span class="keyword">int</span> var)</span> </span>&#123; some_var_ = var; &#125;</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">some_var</span><span class="params">()</span> <span class="keyword">const</span> </span>&#123; <span class="keyword">return</span> some_var_; &#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="comment">// 内部处理，不对外提供的方法</span></span><br><span class="line">    <span class="function"><span class="keyword">bool</span> <span class="title">SomeInternalFunction</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 成员变量，所有成员变量都要是 private 的</span></span><br><span class="line">    <span class="keyword">int</span> some_var_;</span><br><span class="line">    <span class="keyword">int</span> some_other_var_;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li></ul><h3 id="8-8-命名空间格式化"><a href="#8-8-命名空间格式化" class="headerlink" title="8.8 命名空间格式化"></a>8.8 命名空间格式化</h3><ul><li>总述<br>命名空间内容不缩进.</li><li>说明<br>命名空间 不要增加额外的缩进层次, 例如:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">foo</span><span class="params">()</span> </span>&#123;  <span class="comment">// 正确. 命名空间内没有额外的缩进.</span></span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125;  <span class="comment">// namespace</span></span><br></pre></td></tr></table></figure>不要在命名空间内缩进:<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 错, 缩进多余了.</span></span><br><span class="line">  <span class="function"><span class="keyword">void</span> <span class="title">foo</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;  <span class="comment">// namespace</span></span><br></pre></td></tr></table></figure>声明嵌套命名空间时, 每个命名空间都独立成行.<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">namespace</span> foo &#123;</span><br><span class="line"><span class="keyword">namespace</span> bar &#123;</span><br></pre></td></tr></table></figure></li></ul><h3 id="8-9-水平留白"><a href="#8-9-水平留白" class="headerlink" title="8.9 水平留白"></a>8.9 水平留白</h3><ul><li>总述<br>水平留白的使用根据在代码中的位置决定. 永远不要在行尾添加没意义的留白.</li><li>说明<ul><li>通用<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f</span><span class="params">(<span class="keyword">bool</span> b)</span> </span>&#123;  <span class="comment">// 左大括号前总是有空格.</span></span><br><span class="line">  ...</span><br><span class="line"><span class="keyword">int</span> i = <span class="number">0</span>;  <span class="comment">// 分号前不加空格.</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 继承与初始化列表中的冒号前后恒有空格.</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Foo</span> :</span> <span class="keyword">public</span> Bar &#123;</span><br><span class="line"> <span class="keyword">public</span>:</span><br><span class="line">  <span class="comment">// 对于单行函数的实现, 在大括号内加上空格</span></span><br><span class="line">  <span class="comment">// 然后是函数实现</span></span><br><span class="line">  Foo(<span class="keyword">int</span> b) : Bar(), baz_(b) &#123;&#125;  <span class="comment">// 大括号里面是空的话, 不加空格.</span></span><br><span class="line">  <span class="function"><span class="keyword">void</span> <span class="title">Reset</span><span class="params">()</span> </span>&#123; baz_ = <span class="number">0</span>; &#125;  <span class="comment">// 用括号把大括号与实现分开.</span></span><br><span class="line">  ...</span><br></pre></td></tr></table></figure>添加冗余的留白会给其他人编辑时造成额外负担. 因此, 行尾不要留空格. 如果确定一行代码已经修改完毕, 将多余的空格去掉; 或者在专门清理空格时去掉（尤其是在没有其他人在处理这件事的时候). (注: 现在大部分代码编辑器稍加设置后, 都支持自动删除行首/行尾空格, 如果不支持, 考虑换一款编辑器或 IDE)</li><li>循环和条件语句<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (b) &#123;          <span class="comment">// if 条件语句和循环语句关键字后均有空格.</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;          <span class="comment">// else 前后有空格.</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">while</span> (test) &#123;&#125;   <span class="comment">// 圆括号内部不紧邻空格.</span></span><br><span class="line"><span class="keyword">switch</span> (i) &#123;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; ++i) &#123;</span><br><span class="line"><span class="keyword">switch</span> ( i ) &#123;    <span class="comment">// 循环和条件语句的圆括号里可以与空格紧邻.</span></span><br><span class="line"><span class="keyword">if</span> ( test ) &#123;     <span class="comment">// 圆括号, 但这很少见. 总之要一致.</span></span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">5</span>; ++i ) &#123;</span><br><span class="line"><span class="keyword">for</span> ( ; i &lt; <span class="number">5</span> ; ++i) &#123;  <span class="comment">// 循环里内 ; 后恒有空格, ;  前可以加个空格.</span></span><br><span class="line"><span class="keyword">switch</span> (i) &#123;</span><br><span class="line">  <span class="keyword">case</span> <span class="number">1</span>:         <span class="comment">// switch case 的冒号前无空格.</span></span><br><span class="line">    ...</span><br><span class="line">  <span class="keyword">case</span> <span class="number">2</span>: <span class="keyword">break</span>;  <span class="comment">// 如果冒号有代码, 加个空格.</span></span><br></pre></td></tr></table></figure></li><li>操作符<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 赋值运算符前后总是有空格.</span></span><br><span class="line">x = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 其它二元操作符也前后恒有空格, 不过对于表达式的子式可以不加空格.</span></span><br><span class="line"><span class="comment">// 圆括号内部没有紧邻空格.</span></span><br><span class="line">v = w * x + y / z;</span><br><span class="line">v = w*x + y/z;</span><br><span class="line">v = w * (x + z);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在参数和一元操作符之间不加空格.</span></span><br><span class="line">x = <span class="number">-5</span>;</span><br><span class="line">++x;</span><br><span class="line"><span class="keyword">if</span> (x &amp;&amp; !y)</span><br><span class="line">  ...</span><br></pre></td></tr></table></figure></li><li>模板和转换<figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 尖括号(&lt; and &gt;) 不与空格紧邻, &lt; 前没有空格, &gt; 和 ( 之间也没有.</span></span><br><span class="line"><span class="built_in">vector</span>&lt;<span class="built_in">string</span>&gt; x;</span><br><span class="line">y = <span class="keyword">static_cast</span>&lt;<span class="keyword">char</span>*&gt;(x);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在类型与指针操作符之间留空格也可以, 但要保持一致.</span></span><br><span class="line"><span class="built_in">vector</span>&lt;<span class="keyword">char</span> *&gt; x;</span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="8-10垂直留白"><a href="#8-10垂直留白" class="headerlink" title="8.10垂直留白"></a>8.10垂直留白</h3><ul><li>总述<br>垂直留白越少越好.</li><li>说明<br>这不仅仅是规则而是原则问题了: 不在万不得已, 不要使用空行. 尤其是: 两个函数定义之间的空行不要超过 2 行, 函数体首尾不要留空行, 函数体中也不要随意添加空行.<br>基本原则是: 同一屏可以显示的代码越多, 越容易理解程序的控制流. 当然, 过于密集的代码块和过于疏松的代码块同样难看, 有时候在不同的逻辑块中添加空行分隔，会显示更有条理。</li></ul><h2 id="9-c-特性"><a href="#9-c-特性" class="headerlink" title="9. c++ 特性"></a>9. c++ 特性</h2><p>c++ 11 之后增加了一些新的语法特性，使用这些语法特性我觉得可以改善代码可读性，安全性。</p><h3 id="9-1-nullptr"><a href="#9-1-nullptr" class="headerlink" title="9.1 nullptr"></a>9.1 nullptr</h3><p>nullptr 是为了补充并替代 NULL 的，由于之前老版本的 NULL 定义一般为 0， 但有时候又被编译器定义为((void*)0)。这样就会出现混乱，特别是进行函数重载的时候，就会让编译器搞不清楚 NULL 的具体类型，因此，引入 nullptr 可以更好的区分 0 和 空指针， 因此，在新版中，尽量使用 nullptr 代表空指针进行初始化。</p><h3 id="9-2-initializer-list"><a href="#9-2-initializer-list" class="headerlink" title="9.2 initializer_list"></a>9.2 initializer_list</h3><p>在C++11之前，我们无法以花括号的形式初始一个 vector：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">std</span>::<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt; vec = &#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>&#125;;</span><br></pre></td></tr></table></figure><p>但这在C++11却是可行的，因为其引入了初始化列表 std::initializer_list。<br>initializer_list　为参数个数不固定的函数提供了一个简单的解决方案：</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></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;initializer_list&gt;</span></span></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">error_msg</span><span class="params">(<span class="keyword">int</span> ErrCode, <span class="built_in">initializer_list</span>&lt;<span class="built_in">string</span>&gt; il)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="string">"errno("</span> &lt;&lt; ErrCode &lt;&lt; <span class="string">"): "</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> beg = il.<span class="built_in">begin</span>(); beg != il.<span class="built_in">end</span>(); ++beg)</span><br><span class="line">        <span class="built_in">cout</span> &lt;&lt; *beg &lt;&lt; <span class="string">" "</span> ;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; <span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    error_msg(<span class="number">2</span>, &#123;<span class="string">"ENOENT"</span>, <span class="string">"No such file or directory!"</span>&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="9-3-auto-类型"><a href="#9-3-auto-类型" class="headerlink" title="9.3 auto 类型"></a>9.3 auto 类型</h3><p>在C++中最烦的就算是各种类型声明的编写，太多字母了，而且有时候也会忘记，由于他们的类型定义太多太乱了！因此 C++11 中使用 auto 对数据类型进行自动推导。新版中，已经弃用了之前有类似功能的 register 关键字，变得更加强大，比如下面例子：</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="keyword">for</span>(<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)</span><br><span class="line"><span class="comment">// 可以改写为</span></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">auto</span> itr = vec.cbegin(); itr != vec.cend(); ++itr)</span><br></pre></td></tr></table></figure><h3 id="9-4-范围for语句"><a href="#9-4-范围for语句" class="headerlink" title="9.4 范围for语句"></a>9.4 范围for语句</h3><p>相信学过 python 的同学都很清楚，在 python 中经常使用的 for 语句是 for….in….，十分的方便，而在 C 中 for 循环是又丑又长，C++ 标准为了简化代码量，提供了新的范围 for 语句：for(auto c : str);</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// C风格</span></span><br><span class="line"><span class="keyword">for</span>(<span class="built_in">std</span>::<span class="built_in">vector</span>&lt;<span class="keyword">int</span>&gt;::iterator i = arr.<span class="built_in">begin</span>(); i != arr.<span class="built_in">end</span>(); ++i) &#123;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; *i &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// C++11</span></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">auto</span> &amp;i : arr)&#123;</span><br><span class="line">    <span class="built_in">cout</span> &lt;&lt; i &lt;&lt; <span class="string">" "</span>;  <span class="comment">// 加上引用可以为左值，用于修改</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="智能指针"><a href="#智能指针" class="headerlink" title="智能指针"></a>智能指针</h3><p>我们知道c++的内存管理是让很多人头疼的事，当我们写一个new语句时，一般就会立即把delete语句直接也写了，但是我们不能避免程序还未执行到delete时就跳转了或者在函数中没有执行到最后的delete语句就返回了，如果我们不在每一个可能跳转或者返回的语句前释放资源，就会造成内存泄露。使用智能指针可以很大程度上的避免这个问题。智能指针本质上是使用了引用计数的方法来自动释放内存的。具体细节这里就不讨论了，感兴趣的同学请自行查找相关资料。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;本文中的规范大部分是参考 google 发布的 c++ 编码规范，有同学翻译成了&lt;a href=&quot;https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/&quot; target=&quot;_b
      
    
    </summary>
    
    
      <category term="c++" scheme="http://qiushao.net/categories/c/"/>
    
    
      <category term="c++" scheme="http://qiushao.net/tags/c/"/>
    
  </entry>
  
  <entry>
    <title>Android系统开发进阶-init 进程启动流程</title>
    <link href="http://qiushao.net/2020/03/10/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%BF%9B%E9%98%B6/init%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/"/>
    <id>http://qiushao.net/2020/03/10/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%BF%9B%E9%98%B6/init%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/</id>
    <published>2020-03-10T14:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇文章我们详细解析了 init.rc 文件的语法规范。同学们可以对照着去看看 Android/system/core/rootdir/init.rc 这个文件。我们会发现里面有很多 event trigger, 比如 <code>on boot</code>, <code>on fs</code> 等，然后系统的各种核心服务，基本上都是在这些 event trigger 里面启动的。以前一直搞不明白这些 event trigger 的先后执行顺序。这次把源码看了一遍过，终于搞明白了。本文不会从源码的角度去分析 init 的启动流程，只是把自己看过源码之后的理解, 整理成一张思维导图，相信同学们看过这张图之后应该能大概明白 init 都干了哪些活。这些活的先后顺序，这就足够了。<br>init 启动流程概要:<br><img src="/images/init%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.png" alt="init 启动流程概要"></p><p>从上图来看， init 的初始化可以分为三个阶段：</p><ul><li>FirstStageMain<br>这个阶段的工作主要是挂载一些虚拟文件系统，还有挂载两个系统最核心的分区 system, vendor。<br>这两步都是为了后续的工作做准备。比如设置 selinux 需要从 system, vendor 分区中读取 sepolicy。 SecondStageMain　需要从 system, vendor 分区中读取 property 信息，需要启动系统核心服务。</li><li>SetupSelinux<br>设置 Selinux 安全机制。</li><li>SecondStageMain<br>启动 property 服务（从这里可以知道 property 服务是运行在 init 进程里面的），然后加载各分区下的 .rc 文件。然后把各种事件加入到事件队列。最后进入事件循环，按队列方式（先进先出）处理事件队列中的事件。</li></ul><p>从上面的流程来看，其实 init 把大部分的初始化工作都放到 init.rc 中，　这样可以更灵活的适配各种平台的定制需求。</p><p>需要提一下的是各阶段的启动方式，第一阶段的启动是由 kernel 启动的，这里不讨论，第二，三阶段的启动比较有意思，都是通过 exec 来启动的：</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="keyword">const</span> <span class="keyword">char</span>* path = <span class="string">"/system/bin/init"</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">char</span>* args[] = &#123;path, <span class="string">"selinux_setup"</span>, <span class="literal">nullptr</span>&#125;;</span><br><span class="line">execv(path, <span class="keyword">const_cast</span>&lt;<span class="keyword">char</span>**&gt;(args));</span><br></pre></td></tr></table></figure><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="keyword">char</span>* path = <span class="string">"/system/bin/init"</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="keyword">char</span>* args[] = &#123;path, <span class="string">"second_stage"</span>, <span class="literal">nullptr</span>&#125;;</span><br><span class="line">execv(path, <span class="keyword">const_cast</span>&lt;<span class="keyword">char</span>**&gt;(args));</span><br></pre></td></tr></table></figure><p>我也不知道为什么要通过 execv 启动一个新进程替换旧进程的方式来切换各阶段呢？这样相比直接一个函数调用来说有什么好处呢？知道的同学麻烦指导一下哈。</p><p>单独把 secondStage 部分的流程拎出来扩展:<br><img src="/images/secondStage.png" alt="secondStage"></p><p>这张图主要是想表现一下各事件触发器的触发时序，各服务的启动时序。整理如下：<br>0. 初始化 biner 驱动相关</p><ol><li><p>early-init 事件<br>启动了 ueventd, apexd-bootstrap 服务</p></li><li><p>init 事件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">on init</span><br><span class="line">    # 创建各种系统运行所需的目录，更改各种目录的权限</span><br><span class="line">    mkdir &#x2F;mnt&#x2F;media_rw 0750 root media_rw</span><br><span class="line">    mkdir &#x2F;mnt&#x2F;user 0755 root root</span><br><span class="line">    # Start logd before any other services run to ensure we capture all of their logs.</span><br><span class="line">    start logd</span><br><span class="line"></span><br><span class="line">    # Start essential services.</span><br><span class="line">    start servicemanager</span><br><span class="line">    start hwservicemanager</span><br><span class="line">    start vndservicemanager</span><br></pre></td></tr></table></figure></li><li><p>late-init 事件<br>把一堆的事件按顺序加入事件队列</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">on late-init</span><br><span class="line">    trigger early-fs</span><br><span class="line">    trigger fs</span><br><span class="line">    trigger post-fs</span><br><span class="line">    trigger late-fs</span><br><span class="line">    trigger post-fs-data</span><br><span class="line">    trigger load_persist_props_action</span><br><span class="line">    trigger zygote-start</span><br><span class="line">    trigger firmware_mounts_complete</span><br><span class="line">    trigger early-boot</span><br><span class="line">    trigger boot</span><br></pre></td></tr></table></figure></li><li><p>early-fs 事件<br>启动 vold 服务，负责外接磁盘的挂载。</p></li><li><p>fs 事件<br>Action 定义在 vendor/etc/init/init.ranchu.rc 中， 只有一个动作，根据 fstab 挂载所有分区。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mount_all &#x2F;vendor&#x2F;etc&#x2F;fstab.ranchu</span><br></pre></td></tr></table></figure><p>在挂完文件系统后，代码中会把 nonencrypted 事件加入到事件队列。</p></li><li><p>post-fs 事件<br>重新挂载 / 根分区为只读分区，创建各种系统目录。</p></li><li><p>late-fs 事件<br>启动 early_hal 类型的所有服务，当然被 disable 的除外。</p></li><li><p>post-fs-data 事件<br>启动 bootchart, apexd，　等待 apexd 服务完成</p></li><li><p>load_persist_props_action 事件<br>加载 /data 分区下的 persist property</p></li><li><p>zygote-start 事件<br>Android 世界的鼻祖在这里启动了</p></li><li><p>firmware_mounts_complete 事件<br>这个不知道干啥用的了</p></li><li><p>early-boot 事件<br>目前没有使用</p></li><li><p>boot 事件<br>启动 hal, core 类型的所有服务，当然被 disable 的除外。</p></li><li><p>nonencrypted 事件<br>启动 main 类型的所有服务，当然被 disable 的除外。</p></li></ol><p>以上，我们可以知道各种服务的启动顺序了：<br>最先启动的是 logd, 和三个 servicemanager 服务。<br>接下来按这个顺序来启动各种类型的服务：<br>early_hal –&gt; hal –&gt; core –&gt; main</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;上一篇文章我们详细解析了 init.rc 文件的语法规范。同学们可以对照着去看看 Android/system/core/rootdir/init.rc 这个文件。我们会发现里面有很多 event trigger, 比如 &lt;code&gt;on boot&lt;/code&gt;, &lt;cod
      
    
    </summary>
    
    
      <category term="Android系统开发进阶" scheme="http://qiushao.net/categories/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%BF%9B%E9%98%B6/"/>
    
    
      <category term="Android" scheme="http://qiushao.net/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>Android系统开发进阶-init.rc 详解</title>
    <link href="http://qiushao.net/2020/03/01/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%BF%9B%E9%98%B6/init.rc%E4%BB%8B%E7%BB%8D/"/>
    <id>http://qiushao.net/2020/03/01/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%BF%9B%E9%98%B6/init.rc%E4%BB%8B%E7%BB%8D/</id>
    <published>2020-03-01T04:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>init 是 Android 系统启动的第一个用户空间进程，它的主要工作是对系统进行初始化，然后启动系统的各种核心服务。我们知道，Android 可能运行在各种不同的产品上，不同的产品所需要启动的服务也是有很大的差异的。为了满足不同产品的需求，init 进程把一部分初始化工作交给 init.rc 配置文件来管理。init.rc 以 Android Init Language 作为语法，下文我们简称 Android Init Language 为 init 语言。配置文件的主入口文件是 /init.rc，这个文件会通过 import 引入其他的配置文件。本文中，我们统称这些文件为 init.rc。下面我们就来详细介绍一下 init.rc 的语法细节。</p><p>我们先来看一下 init.rc 的语法结构图：<br><img src="/images/init.rc.png" alt="init.rc"></p><p>init.rc 由一个个 section 组成，　每一个 section 定义了一个 Action, 或者一个 Service。<br>Action 由 trigger 和 一组 command 组成。trigger 表示触发这个 Action 的条件， command 表示这个 Action 被触发后所执行的一系列动作。<br>Service 描述了一个服务的名字，执行路径，启动参数等信息。还可以通过 option 来约定服务的一些行为，权限等。比如是否只启动一次，服务进程的 uid, gid等。</p><p>init.rc 的所有语法介绍都可以在 Android/system/core/init/README.md 这个文件里面查找到，下面的大部分内容都是参考这个文档，顺便整理，扩展一下。</p><h2 id="Android-Init-Language"><a href="#Android-Init-Language" class="headerlink" title="Android Init Language"></a>Android Init Language</h2><p>The Android Init Language 主要由５种语法类型组成:<br>Actions, Commands, Services, Options, 和 Imports.</p><p>每一行是一个语句，单词之间用空格分开，如果单词中有空格可以用反斜杠 <code>\</code> 转义，也可以用双引号 <code>&quot;&quot;</code> 来引用文本避免和空格冲突，如果一行语句太长可以用反斜杠 <code>\</code> 换行， 如果一行以 <code>#</code> 开头(当然 <code>#</code> 前面可以是空白符: space, tab)，则表示这行是注释。</p><p>在 init.rc 中我们可以使用系统属性 property，其使用方法跟 shell 语法中的变量引用是一样的，比如：<code>import /init.recovery.${ro.hardware}.rc</code>。其中 <code>ro.hardware</code> 就是一个系统属性，我们通过 <code>${property}</code> 这种形式来引用。</p><p>每个 Action 和 Service 都定义了一个 Section, 所有的 Commands 和 Options 从属于紧挨着的 Actions 或 Services， <strong>定义在第一个 Section 前的 Commands 和 Options 将被忽略掉</strong>。Service 的名称都唯一的， 如果定义了两个名称一样的 Service，那第二个 Service 将被忽略掉并打印错误日志。</p><h2 id="Init-rc-Files"><a href="#Init-rc-Files" class="headerlink" title="Init .rc Files"></a>Init .rc Files</h2><p>Android Init Language 是用后缀为 <code>.rc</code> 的纯文本编写的, 而且是由多个分布在不同目录下的 .rc 文件组成, 如下所述:<br>/init.rc 是最主要的一个 .rc 文件， 它由 init 进程在开始执行的时候加载，主要负责系统初始化。</p><p>系统通过 init 进程的第一阶段 mount 机制会挂载 /system, /vendor 分区。这里简要说明一下， init 进程的启动是分为三个阶段的: </p><ul><li>FirstStageMain 主是要挂载文件系统，包括虚拟文件系统(/dev, /sys, /proc等), 和部分实际的文件系统(/sytem, /vendor, /odm等)。还有一部分文件系统是在第三阶段通过 mount_all fstab 来挂载的，比如说 /data。</li><li>SetupSelinux 设置 Selinux 环境</li><li>SecondStageMain 这个阶段才会加载 init.rc 文件</li></ul><p>init 进程在加载完 /init.rc 文件之后，会接着加载 /{system,vendor,odm,product}/etc/init/ 这些目录下的所有 .rc 文件：<br>/system/etc/init/  用于系统本身，比如 SurfaceFlinger, MediaService, and logcatd<br>/vendor/etc/init/  用于SoC(系统级核心厂商，如高通), 为他们提供一些核心功能和服务<br>/odm/etc/init/     用于设备制造商（odm定制厂商，如华为、小米），为他们的传感器或外围设备提供一些核心功能和服务<br>/product/etc/init/ 用于产品机型的配置</p><p>一些旧的设备是没有第一个阶段的挂载文件系统机制的，那些设备的 .rc 加载流程是这样的：</p><ol><li>/init.rc 会 import soc 厂商提供的 .rc 文件： /init.${ro.hardware}.rc</li><li>/init.${ro.hardware}.rc 里面会有 mount_all 指令。当执行到 mount_all 指令时，init 进程会挂载所有文件系统，然后加载 /{system,vendor,odm}/etc/init/ 这些目录下的所有 .rc 文件。</li></ol><p>system, vendor, odm, 或者 product 分区下的所有 binaries services 都有一个对应的 .rc 文件。这些 .rc 文件都放在各分区的 $partion/etc/init 目录下。Android.mk 中提供了一个 <code>LOCAL_INIT_RC</code> 配置， Android.bp 中也提供了一个 <code>init_rc</code> 配置。这两个配置可以为服务指定它的 .rc 文件。这里需要说明的是 service bin 编译到哪个分区，它对应的 .rc 文件也会编译到对应分区的 etc/init 目录。比如 system/core 目录下的 logd 模块，它的 Android.bp 如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">cc_binary &#123;</span><br><span class="line">    name: &quot;logd&quot;,</span><br><span class="line">    init_rc: [&quot;logd.rc&quot;],</span><br><span class="line"></span><br><span class="line">    srcs: [&quot;main.cpp&quot;],</span><br><span class="line"></span><br><span class="line">    static_libs: [</span><br><span class="line">        &quot;liblog&quot;,</span><br><span class="line">        &quot;liblogd&quot;,</span><br><span class="line">    ],</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>init_rc: [&quot;logd.rc&quot;]</code> 配置指定了 logd 服务对应的 .rc 文件为当前目录下的 logd.rc。logd 会被编译到 /system/bin 目录，logd.rc 文件则会被编译到 /system/etc/init 目录。<br>将 .rc 根据不同服务分拆到不同文件中，要比之前放在单个 <code>init.rc</code> 文件好。 这种方案确保 init 进程读取的 service 和 action 信息能和同目录下的 Services 二进制文件更加符合, 不再像以前单个 init.rc 那样。另外，这样还可以解决多个 services 加入到系统时发生的冲突，因为他们都拆分到了不同的文件中。</p><p>在 mount_all 命令中有 “early” 和 “late” 两个可选项，当 early 设置的时候，init 进程将跳过被 latemount 标记的挂载操作，并触发 fs encryption state 事件，<br>当 late 被设置的时候，init 进程只会执行 latemount 标记的挂载操作，但是会跳过导入的 .rc 文件的执行. 默认情况下，不设置任何选项， init 进程将执行所有挂载操作</p><h2 id="Actions"><a href="#Actions" class="headerlink" title="Actions"></a>Actions</h2><p>Actions 由一行行的命令序列组成。 trigger 用来决定什么时候触发这些命令, 当一个事件满足 trigger 的触发条件时， 这个 action 就会被加入到处理队列中（除非队列中已经存在）。<br>队列中的 action 按顺序取出执行， action 中的命令按顺序执行。 这些命令主要用来执行一些操作（设备创建/销毁，属性设置，进程重启等）。<br>Actions的格式如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">on &lt;trigger&gt; [&amp;&amp; &lt;trigger&gt;]*</span><br><span class="line">   &lt;command&gt;</span><br><span class="line">   &lt;command&gt;</span><br><span class="line">   &lt;command&gt;</span><br></pre></td></tr></table></figure><p>Actions 被加进队列的顺序是由包含这个 Action 的文件被 init 解析的顺序（import 的顺序）决定的。同一个 .rc 文件的多个 Action 命令也是出现在前面的优先入队列。<br>比如一个 .rc 文件有以下 Actions:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">on boot</span><br><span class="line">   setprop a 1</span><br><span class="line">   setprop b </span><br><span class="line">on boot &amp;&amp; property:foobar&#x3D;true</span><br><span class="line">   setprop c 1</span><br><span class="line">   setprop d 2</span><br><span class="line">on boot</span><br><span class="line">   setprop e 1</span><br><span class="line">   setprop f 2</span><br></pre></td></tr></table></figure><p>当 boot 事件被触发，且 property <code>foobar</code> 的值为 true 时，这些命令被执行的顺序是：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">setprop a 1</span><br><span class="line">setprop b 2</span><br><span class="line">setprop c 1</span><br><span class="line">setprop d 2</span><br><span class="line">setprop e 1</span><br><span class="line">setprop f 2</span><br></pre></td></tr></table></figure><h2 id="Services"><a href="#Services" class="headerlink" title="Services"></a>Services</h2><p>Services 是 init 进程启动的程序, 我们可以配置 Services 在挂掉时是否自动重启。Services 的格式如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">service &lt;name&gt; &lt;pathname&gt; [ &lt;argument&gt; ]*</span><br><span class="line">   &lt;option&gt;</span><br><span class="line">   &lt;option&gt;</span><br><span class="line">   ...</span><br></pre></td></tr></table></figure><ul><li>name : 服务名，也是服务运行的进程名</li><li>pathname : 服务的可执行程序入口</li><li>argument : 服务运行的参数</li></ul><h2 id="Options"><a href="#Options" class="headerlink" title="Options"></a>Options</h2><p>Options是Services的参数配置. 它们影响Service如何运行及运行时机。下面对所有的 Options 进行说明：</p><ul><li><p><code>capabilities [ &lt;capability&gt;\* ]</code><br>执行服务的时候设置 capability。关于 <code>capability</code> 的概念，简单来说，就是一种权限控制机制，相对于 uid, gid 来说更细粒度的权限控制。详细的权限列表请参考 <a href="http://man7.org/linux/man-pages/man7/capabilities.7.html" target="_blank" rel="noopener">capabilities</a>。参数中的 <code>capability</code> 不需要 <code>CAP_</code> 前缀， 比如 <code>NET_ADMIN</code>，或者 <code>SETPCAP</code>。如果没有设置任何权限，则所有的权限都会被移除掉，即使这个服务是以 root 用户运行的也一样。</p></li><li><p><code>class &lt;name&gt; [ &lt;name&gt;\* ]</code><br>为服务指定 class 名字。 同一个 class 名字的服务会被一起启动或退出, 默认值是 <code>default</code>, 第二个 name 可以不设置， 用于service组。<br>开机动画和关机动画服务都应该包含 <code>animation</code> 这个 class。因为开机动画在系统启动过程中启动得非常早，然后在关机的时候又需要运行到最后一个关机阶段。在运行动画的过程中不能保证 /data 分区是已经挂载可用的了。开机动画和关机动画服务可以操作 /data 分区下的文件，但不能一直打开 /data 分区下的文件。而且当 /data 分区还没挂载时，也不能影响服务的运行。</p></li><li><p><code>console [&lt;console&gt;]</code><br>这个选项表明服务需要一个控制台。 第二个参数 console 的意思是可以设置你想要的控制台类型，默认控制台是 /dev/console, /dev 这个前缀通常是被省略的， 比如你要设置控制台 /dev/tty0, 那么只需要设置为console tty0 即可。</p></li><li><p><code>critical</code><br>表示服务是严格模式。 如果这个服务在4分钟内或者启动完成前退出超过4次，那么设备将重启进入 bootloader 模式。</p></li><li><p><code>disabled</code><br>这个服务不会随着 class 一起启动。只能通过服务名来显式启动。比如 foobar 服务的 class 是 core, 且是 disabled 的，当执行 <code>class_start core</code> 时，foobar 服务是不会被启动的。 foobar 服务只能通过 <code>start foobar</code> 这种方法来启动。</p></li><li><p><code>enter_namespace &lt;type&gt; &lt;path&gt;</code><br>Enters the namespace of type <em>type</em> located at <em>path</em>. Only network namespaces are supported with <em>type</em> set to “net”. Note that only one namespace of a given <em>type</em> may be entered.</p></li><li><p><code>file &lt;path&gt; &lt;type&gt;</code><br>根据文件路径 <code>path</code> 来打开文件，然后把文件描述符 <code>fd</code> 传递给服务进程。<code>type</code> 表示打工文件的方式，只有三种取值 <code>r</code>, <code>w</code>, <code>rw</code>。对于 native 程序来说，可以通过 libcutils 库提供的 <code>android_get_control_file()</code> 函数来获取传递过来的文件描述符。举个例子， logd.rc 部分内容如下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">service logd &#x2F;system&#x2F;bin&#x2F;logd</span><br><span class="line">    socket logd stream 0666 logd logd</span><br><span class="line">    socket logdr seqpacket 0666 logd logd</span><br><span class="line">    socket logdw dgram+passcred 0222 logd logd</span><br><span class="line">    file &#x2F;proc&#x2F;kmsg r</span><br><span class="line">    file &#x2F;dev&#x2F;kmsg w</span><br><span class="line">    user logd</span><br></pre></td></tr></table></figure><p>其中通过 <code>file /proc/kmsg r</code> 以只读方式打开了设备文件 /proc/kmsg, 然后在代码中这么获取打开的文件, 见 sytem/core/logd/main.cpp main 函数：</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="keyword">static</span> <span class="keyword">const</span> <span class="keyword">char</span> dev_kmsg[] = <span class="string">"/dev/kmsg"</span>;</span><br><span class="line">fdDmesg = android_get_control_file(dev_kmsg);</span><br><span class="line"><span class="keyword">if</span> (fdDmesg &lt; <span class="number">0</span>) &#123;</span><br><span class="line">    fdDmesg = TEMP_FAILURE_RETRY(<span class="built_in">open</span>(dev_kmsg, O_WRONLY | O_CLOEXEC));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里可能有点奇怪，为什么要通过 file 这个选项来打开文件，而不直接在代码里面通过 open() 函数来打开呢？我觉得主要是权限问题，还是以 logd 为例子。/dev/kmsg 的权限是：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pure:&#x2F; # ls -l &#x2F;dev&#x2F;kmsg </span><br><span class="line">crw------- 1 root root 1,  11 2020-02-27 21:49 &#x2F;dev&#x2F;kmsg</span><br></pre></td></tr></table></figure><p>只有 root 用户可读写。而 logd 服务是以 <code>user logd</code> 选项来启动的，自然没有权限用 open() 函数来打开 /dev/kmsg 这个设备文件。file 选项则可以通过 init 进程把文件打开，然后把文件描述符传递给子进程， 从而解决了权限的问题。</p></li><li><p><code>group &lt;groupname&gt; [ &lt;groupname&gt;\* ]</code><br>在启动 Service 前将 Service 的用户组改为第一个 groupname, 第一个 groupname 是必须有的， 第二个 groupname 可以不设置，用于追加组（通过setgroups）。目前默认的用户组是 root 组。但我觉得为了安全起见，默认用户组应该是 nobody 才对。</p></li><li><p><code>interface &lt;interface name&gt; &lt;instance name&gt;</code><br>将这个服务进程与这个进程提供的一系列 services 关联起来。<code>interface name</code> 参数必须是全限定名。这个配置的作用是允许 hwservicemanager 惰性启动服务进程。（我的理解是，这个服务不会开机自动启动，需要等其他进程显式的调用这个服务时，才会启动）。如果有多个 interfaces, 则多次使用 <code>interface</code> 这个选项来列举出来。比如 /vendor/etc/init/android.hardware.drm@1.0-service.rc ：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">service vendor.drm-hal-1-0 &#x2F;vendor&#x2F;bin&#x2F;hw&#x2F;android.hardware.drm@1.0-service</span><br><span class="line">    interface android.hardware.drm@1.0::ICryptoFactory default</span><br><span class="line">    interface android.hardware.drm@1.0::IDrmFactory default</span><br><span class="line">    class hal</span><br><span class="line">    user media</span><br><span class="line">    group mediadrm drmrpc</span><br></pre></td></tr></table></figure></li><li><p><code>ioprio &lt;class&gt; &lt;priority&gt;</code><br>通过 SYS_ioprio_set 系统调用来设置此服务的 IO 优先级和 IO 优先级类别。</p><ul><li><code>class</code> 优先级类别必须是 <code>rt</code>(real-time)，<code>be</code>(best-effort) 或 <code>idle</code> 之一。 </li><li><code>priority</code> 优先级必须为 0 到 7 之间的整数。</li></ul></li></ul><p><code>keycodes &lt;keycode&gt; [ &lt;keycode&gt;\* ]</code><br>设置触发此服务的按键，可以是组合按键。如果这个组合键被按下了，这个服务就会启动。一般用来启动 bugreport 服务：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">service bugreport &#x2F;system&#x2F;bin&#x2F;dumpstate -d -p -B -z \</span><br><span class="line">        -o &#x2F;data&#x2F;user_de&#x2F;0&#x2F;com.android.shell&#x2F;files&#x2F;bugreports&#x2F;bugreport</span><br><span class="line">    class main</span><br><span class="line">    disabled</span><br><span class="line">    oneshot</span><br><span class="line">    keycodes 114 115 116</span><br></pre></td></tr></table></figure><p>我们也可以用一个 property 来替代键值列表。这种情况下只需要提供一个 property 即可。这个 property 的值为：以逗号分隔的键值列表；或者 “none” 表示不禁用组合键启动服务。<br>比如： <code>keycodes ${some.property.name:-none}</code>。 some.property.name 的值为 “123,124,125”。因为 keycodes 在 init 进程中是很早被处理的。所以只有在 PRODUCT_DEFAULT_PROPERTY_OVERRIDES 定义的 property 才可以使用。目前我还没见过这种用法，也不明白为什么要有这种用法。</p><ul><li><p><code>memcg.limit_in_bytes &lt;value&gt;</code> and <code>memcg.limit_percent &lt;value&gt;</code><br>这个主要是用来设置服务的内存使用限制。当服务的内存使用超出了限制，则会触发 oom kill。具体原理请搜索 <code>cgroup memory</code> 相关的知识。<br>limit_in_bytes 指按字节来限制； limit_percent 指按物理内存的百分比来限制。value 的值必须要大于 0。<br>这个配置需要 /dev/memcg 节点被挂载才会生效。</p></li><li><p><code>memcg.limit_property &lt;value&gt;</code><br>通过 property 来设置 <code>memory.limit_in_bytes</code> 的值。这里 value 的值为一个 property 名。当设置了 memcg.limit_property 时，memcg.limit_in_bytes 和 memcg.limit_percent 的值将会被覆盖。</p></li><li><p><code>memcg.soft_limit_in_bytes &lt;value&gt;</code><br>soft_limit_in_bytes: 内存软限制。<br>如果超过了 memcg.limit_in_bytes 所定义的限制，那么进程会被 oom killer 干掉或者被暂停，这相当于硬限制，因为进程无法申请超过自身 cgroup 限制的内存，但是软限制确是可以突破的。<br>我们假定一个场景，如果你的实体机上有四个 cgroup，实体机的内存总量是64G，那么一般情况我们会考虑给每个 cgroup 限制到16G内存。但是现实情况并不会这么理想，首先实体机上其他进程和内核会占用部分内存，这将导致实际上每个 cgroup 都不会真的有16G内存可用，如果四个 cgroup 都尽量占用内存的话，他们可能谁都不会到达内存的上限触发超限的行为，这可能将导致进程都抢不到内存而被饿死。</p></li></ul><p>类似的情况还可能发上在内存超卖的环境中，比如，我们仍然只有64G内存，但是确开了8个cgroup，每个都限制了16G内存。这样每个 cgroup 分配的内存之和达到了128G，但是实际内存量只有64G。这种情况是出于绝大多数应用可能不会占用满所有的内存来考虑的，这样就可以把本来属于它的那份内存”借用”给其它 cgroup。如果全局内存已经耗尽了，但是某些 cgroup 还没达到他的内存使用上限，而它们此时如果要申请内存的话，此时该从哪里回收内存？如果我们配置了 memcg.soft_limit_in_bytes，那么内核将去回收那些内存超过了这个软限制的 cgroup 的内存，尽量缩减它们的内存占用达到软限制的量以下 ，以便让没有达到软限制的 cgroup 有内存可以用。在没有这样的内存竞争以及没有达到硬限制的情况下，软限制是不会生效的。还有，软限制的起作用时间可能会比较长，毕竟内核要平衡多个cgroup的内存使用。</p><p>根据软限制的这些特点，我们应该明白如果想要软限制生效，应该把它的值设置成小于硬限制。</p><ul><li><p><code>memcg.swappiness &lt;value&gt;</code><br>swappiness 的值的大小对如何使用 swap 分区是有着很大的联系的。swappiness=0 的时候表示最大限度使用物理内存，然后才是 swap 空间，swappiness＝100 的时候表示积极的使用 swap 分区，并且把内存上的数据及时的搬运到 swap 空间里面。不过在 Android 系统上好像没有见过 swap 分区。</p></li><li><p><code>namespace &lt;pid|mnt&gt;</code><br>当 fork 这个 service 时，设置 pid 或 mnt namespace。目前也没发现哪个服务有用到这个 option。</p></li><li><p><code>oneshot</code><br>当服务退出的时候，不自动重启。适用于那些开机只运行一次的服务。</p></li><li><p><code>onrestart</code><br>在服务重启的时候执行一个命令。这个命令并不是 shell 命令哦，具体可以执行的命令有哪些，文章后面会介绍。</p></li><li><p><code>oom_score_adjust &lt;value&gt;</code><br>设置服务的 /proc/$pid/oom_score_adj 的值为 value,在 -1000 ～ 1000之间。值越大，越可能被 oom Killer 杀掉。</p></li><li><p><code>override</code><br>指定此服务要覆盖之前定义的同名服务。前面我们有说过，服务名是唯一的，重复定义的话，后面的定义会被忽略。但我们可以通过 override 来使用后面定义的服务来覆盖前面的。一般的使用场景是使用 /odm 分区定义的服务来覆盖 /vendor 分区定义的同名服务。init 进程将以最后解析到的服务定义为准，因此我们要特别关注 init.rc 文件被解析的顺序。</p></li><li><p><code>priority &lt;priority&gt;</code><br>设置服务的 cpu 调试优先级。取值范围为 -20 ~ 19。默认值是 0。实质上是通过 setpriority() 函数调用来设置优先级。</p></li><li><p><code>restart_period &lt;seconds&gt;</code><br>如果一个非 oneshot 的服务退出了，它将会在设置的 period 时间周期内重新启动。为了限制服务的崩溃频率，默认周期是 5 秒。对于一些本应该周期运行的服务来说，我们可以增加这个周期。比如说，可以设置成 3600 来指定服务每小时运行一次。或者指定为 86400 来指定服务每天运行一次。</p></li><li><p><code>rlimit &lt;resource&gt; &lt;cur&gt; &lt;max&gt;</code><br>rlimit 是 resource limit 的意思。每个进程在运行时系统不会无限制的允许单个进程不断的消耗资源，因此都会设置资源限制。Linux 系统中使用 resource limit 来表示，每个进程都可以设置不同的资源限制，rlimit 的设置是会传递给子进程的，也就是以这个 service 为根节点的进程树上的所有进程都会受这个设置的影响。关于 rlimit 的介绍请自行搜索。</p></li><li><p><code>seclabel &lt;seclabel&gt;</code><br>在启动 Service 前设置指定的 seclabel，默认使用init的安全策略。 主要用于在 rootfs 上启动的 service，比如 ueventd, adbd。 在系统分区上运行的 service 有自己的 SELinux安全策略。</p></li><li><p><code>setenv &lt;name&gt; &lt;value&gt;</code><br>设置进程的环境变量。</p></li><li><p><code>shutdown &lt;shutdown_behavior&gt;</code><br>设置关机时这个服务的行为。如果没有设置，这个服务在关机时将会被 SIGTERM 和 SIGKILL 杀掉。如果指定了 <code>shutdown critical</code>，则这个服务在关机过程中不会被杀掉，直接关机超时。<br>如果这个服务指定了 <code>shutdown critical</code>，且在开始关机时这个服务没有在运行，则这个服务将会被启动。servicemanager， vold 等服务设置了这个 option。</p></li><li><p><code>sigstop</code><br>在服务被启动的时候马上发送 SIGSTOP 信号给这个服务。这个选项一般是用来调试的。文章后面介绍 debugging 的时候会说明怎么使用这个选项。</p></li><li><p><code>socket &lt;name&gt; &lt;type&gt; &lt;perm&gt; [ &lt;user&gt; [ &lt;group&gt; [ &lt;seclabel&gt; ] ] ]</code><br>创建一个 unix domain socket, 路径为 <code>/dev/socket/name</code> , 并将fd返回给Service。 type 只能是 “dgram”, “stream” or “seqpacket”。<br>user 和 group 默认值是 0。 seclabel 是这个 socket 的 SELinux security context, 它的默认值是 service 的security context或者基于其可执行文件的security context。<br>在代码中，可以通过 libcutils 库提供的 android_get_control_socket 函数来获取这个 socket 的 fd。logd 服务就使用到了这个 option。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">service logd &#x2F;system&#x2F;bin&#x2F;logd</span><br><span class="line">    socket logd stream 0666 logd logd</span><br><span class="line">    socket logdr seqpacket 0666 logd logd</span><br><span class="line">    socket logdw dgram+passcred 0222 logd logd</span><br><span class="line">    file &#x2F;proc&#x2F;kmsg r</span><br><span class="line">    file &#x2F;dev&#x2F;kmsg w</span><br><span class="line">    user logd</span><br></pre></td></tr></table></figure></li><li><p><code>timeout_period &lt;seconds&gt;</code><br>设置一个超时周期，当到达超时周期，服务将会被 kill 掉。如果服务被设置了 oneshot 自然不会被重启。但其他非 oneshot 服务则会被自动重启。这个 option 通常和上面提到过的 restart_period 结合使用，定义一个周期性的服务。 </p></li><li><p><code>updatable</code></p><blockquote><p>Mark that the service can be overridden (via the ‘override’ option) later in<br>the boot sequence by APEXes. When a service with updatable option is started<br>before APEXes are all activated, the execution is delayed until the activation<br>is finished. A service that is not marked as updatable cannot be overridden by<br>APEXes.</p></blockquote></li><li><p><code>user &lt;username&gt;</code><br>在启动 Service 前修改进程的所属用户, 默认启动时 user 为 root (安全起见，或许默认应该是 nobody)。<br>在Android M版本，如果一个进程想拥有 Linux capabilities（相当于Android中的权限吧），也只能通过设置这个值。 以前，一个程序要想有 Linux capabilities，必须先以root身份运行，然后再降级到所需的uid。现在已经有一套新的机制取而代之，它通过 fs_config 允许厂商赋予特殊二进制文件 Linux capabilities。 这套机制的说明文档在 <a href="https://source.android.google.cn/devices/tech/config/filesystem.html" target="_blank" rel="noopener">https://source.android.google.cn/devices/tech/config/filesystem.html</a>。 当使用这套新的机制时，程序可以通过 user 参数选择自己所需的 uid, 而不需要以 root 权限运行. 在 Android O 版本，程序可以通过 capabilities option 直接申请所需的能力，参见上面的 capabilities 说明。</p></li><li><p><code>writepid &lt;file&gt; [ &lt;file&gt;\* ]</code><br>当 Service 调用 for 时将子进程的 pid 写入到指定文件. 用于 cgroup/cpuset 的使用，当 /dev/cpuset/ 下面没有文件但 ro.cpuset.default 的值却不为空时, 将pid的值写入到 /dev/cpuset/${ro.cpuset.default}/tasks 文件中。</p></li></ul><h2 id="Triggers"><a href="#Triggers" class="headerlink" title="Triggers"></a>Triggers</h2><p>Triggers 的作用是用来触发 Actions 的执行。Triggers 可以分为 event triggers 和 property triggers。<br>event triggers 由 trigger 命令 或者在 init 中使用 QueueEventTrigger 函数触发。它的格式是个简单的字符串，比如’boot’ 或 ‘late-init’。<br>property triggers 是在 property 被设置或发生改变时触发。 格式是<code>on property:property_name=property_value</code>。<br>一个 Action 可以有多个 property triggers, 但是只能有一个 event trigger。比如：<br><code>on boot &amp;&amp; property:a=b</code>　定义的 Action 只有在 <code>boot</code> 这个 event trigger 被触发，且 property a = b 时，这个 Action 才会执行。<br><code>on property:a=b &amp;&amp; property:c=d</code> 定义的 Action 在以下三种情况都会执行:</p><ol><li>系统启动时 property a=b 且 property c=d。</li><li>系统运行过程中， 当 property a 的值变成 b, 且这个时候 property c 的值已经是 d.</li><li>系统运行过程中， 当 property c 的值变成 d, 且这个时候 property a 的值已经是 b.</li></ol><h2 id="Commands"><a href="#Commands" class="headerlink" title="Commands"></a>Commands</h2><p>Command 是 Action 被触发后，可以执行的一系列命令。这些命令并不是 shell 命令。下面介绍一下都有哪些命令可以使用。</p><ul><li><p><code>bootchart [start|stop]</code><br>启动或者停止 bootchart。这两个 command 出现在 init.rc 这个文件中，但只有在 /data/bootchart/enabled 这个文件存在的时候 bootchart 才会执行。否则 bootchart start/stop 不做任何操作。</p></li><li><p><code>chmod &lt;octal-mode&gt; &lt;path&gt;</code><br>修改文件的权限，这个跟我们使用 shell 命令的格式是一样的。权限以 8 进制数指定。</p></li><li><p><code>chown &lt;owner&gt; &lt;group&gt; &lt;path&gt;</code><br>修改文件属主。格式跟 shell 命令有点差别。 shell 命令的格式是 chown [user][:[group]] <path></p></li><li><p><code>class_start &lt;serviceclass&gt;</code><br>启动所有 class 是 <code>&lt;serviceclass&gt;</code> 的 service(如果服务未启动的话，才启动)。</p></li><li><p><code>class_start_post_data &lt;serviceclass&gt;</code><br>与 <code>class_start</code> 相似， 但是只考虑那些在 /data 分区被挂载之后才启动的服务， 或者系统运行过程中 <code>class_reset_post_data</code> 命令被调用。这个命令只适用于开启了 FDE(Full-Disk Encryption 全盘加密) 的设备。</p></li><li><p><code>class_stop &lt;serviceclass&gt;</code><br>停止所有 class 为 <code>&lt;serviceclass&gt;</code> 且正在运行的服务。并且把这些服务设置为 disable。也就是不能再通过 class_star 来启动，只能通过 start service_name 这种指定服务名的方式显式启动。</p></li><li><p><code>class_reset &lt;serviceclass&gt;</code><br>停止所有 class 为 <code>&lt;serviceclass&gt;</code> 且正在运行的服务。但不会 disable 这些服务。这些服务还可以通过 <code>class_start</code> 来启动。</p></li><li><p><code>class_reset_post_data &lt;serviceclass&gt;</code><br>与 <code>class_reset</code> 相似，但是只考虑那些在 /data 分区被挂载之后才启动的服务。这个命令只适用于开启了 FDE(Full-Disk Encryption 全盘加密) 的设备。</p></li><li><p><code>class_restart &lt;serviceclass&gt;</code><br>重启所有 class 是 <code>&lt;serviceclass&gt;</code> 的服务。</p></li><li><p><code>copy &lt;src&gt; &lt;dst&gt;</code><br>复制一个文件，与 write 命令相似，但适用于二进制文件或者大文件拷贝。<br>对于 src 文件来说，不允许从符号链接文件复制，也不可以从 world-writable(other用户可写) 或者 group-writable(组用户可写) 的文件复制。 linux 的文件权限位可以分为三组：文件属主权限，文件属主所在的用户组的权限，其他用户的权限。<br>对于 dst 文件来说，如果文件不存在的话，创建的文件默认权限是 0600。如果文件已经存在的普通文件，则这个文件会被清空。</p></li><li><p><code>domainname &lt;name&gt;</code><br>设置 domain name.</p></li><li><p><code>enable &lt;servicename&gt;</code><br>把一个 disable 的服务设置成 enable 状态。</p></li><li><p><code>exec [ &lt;seclabel&gt; [ &lt;user&gt; [ &lt;group&gt;\* ] ] ] -- &lt;command&gt; [ &lt;argument&gt;\* ]</code><br>新建一个子进程并运行一个带指定参数的命令。 命令跟在 <code>--</code> 后面，以便前面可以指定 seclabel（安全策略），user(所有者)，group(用户组)等选项。<br>直到这个命令运行完才可以运行其他命令。seclabel 可以设置为 <code>-</code> 表示用默认值， argument 可以使用属性扩展形式 ${property}。<br>直到子进程新建完毕，init进程才继续执行.</p></li></ul><ul><li><p><code>exec_background [ &lt;seclabel&gt; [ &lt;user&gt; [ &lt;group&gt;\* ] ] ] -- &lt;command&gt; [ &lt;argument&gt;\* ]</code><br>与 exec 相似，差别在于 exec_background 不会阻塞 init 进程的运行。</p></li><li><p><code>exec_start &lt;service&gt;</code><br>与 exec 相似，但 exec_start 使用的是定义好的 service。</p></li><li><p><code>export &lt;name&gt; &lt;value&gt;</code><br>设置全局环境变量。在执行这个命令之后再启动的所有进程都会继承这个环境变量。</p></li><li><p><code>hostname &lt;name&gt;</code><br>设置 hostname</p></li><li><p><code>ifup &lt;interface&gt;</code><br>启动指定的网络接口</p></li><li><p><code>insmod [-f] &lt;path&gt; [&lt;options&gt;]</code><br>安装 path 指定的内核驱动模块(ko 文件)。<br><code>-f</code> 参数：即使当前运行的内核版本与模块编译的内核版本不一致，也强制安装这个驱动模块。</p></li><li><p><code>load_system_props</code><br>这个命令已经被 deprecated 了。</p></li><li><p><code>load_persist_props</code><br>当 /data 分区被 decrypted 之后， 加载 /data 分区下的 persistent 属性。在 init.rc 这个文件里面有执行这个命令。</p></li><li><p><code>loglevel &lt;level&gt;</code><br>设置 kernel 打印等级。level 可以使用属性扩展形式 ${property}。</p></li><li><p><code>mark_post_data</code><br>/data 分区被挂载后，马上用这个命令来标志。用来实现 <code>class_reset_post_data</code> 和 <code>class_start_post_data</code> 命令。</p></li><li><p><code>mkdir &lt;path&gt; [mode] [owner] [group]</code><br>创建目录。如果没有提供[mode] [owner] [group]的话，则默认权限是 755，默认用户，和组是 root。如果目录已经存在了，则目录的权限和用户属主会被更新。</p></li><li><p><code>mount_all &lt;fstab&gt; [ &lt;path&gt; ]\* [--&lt;option&gt;]</code><br>调用 fs_mgr_mount_all 来挂载 fstab 指定的分区。挂载完之后，马上 import 这些分区 etc/init/ 目录下面的 .rc 文件。</p></li><li><p><code>mount &lt;type&gt; &lt;device&gt; &lt;dir&gt; [ &lt;flag&gt;\* ] [&lt;options&gt;]</code><br>挂载 type 类型的 device 设备到 dir 目录。<br>flag 包括 “ro”, “rw”, “remount”, “noatime” 等。<br>options 包括 “barrier=1”, “noauto_da_alloc”, “discard” 等，使用逗号分隔，比如：barrier=1,noauto_da_alloc</p></li><li><p><code>restart &lt;service&gt;</code><br>重启服务。如果服务正在运行，先 stop 再 start，如果服务没有运行则只需要 start。</p></li><li><p><code>rm &lt;path&gt;</code><br>删除指定文件，实际上是调用 unlink 函数。可以使用 <code>exec -- rm path</code> 来替代。需要确保 path 所在的分区已经被挂载。</p></li><li><p><code>rmdir &lt;path&gt;</code><br>删除指定目录，实际上是调用 rmdir 函数。</p></li><li><p><code>readahead &lt;file|dir&gt; [--fully]</code><br>调用 readahead 函数把文件或目录下的文件加载到页面缓冲区。–fully option 表示加载整个文件。<br>Linux的文件预读 readahead，指Linux系统内核将指定文件的某区域预读进页缓存起来，便于接下来对该区域进行读取时，不会因缺页（page fault）而阻塞。因为从内存读取比从磁盘读取要快很多。预读可以有效的减少磁盘的寻道次数和应用程序的I/O等待时间，是改进磁盘读I/O性能的重要优化手段之一。</p></li><li><p><code>setprop &lt;name&gt; &lt;value&gt;</code><br>设置 property。value 可以使用 property 扩展形式 ${property}。</p></li><li><p><code>setrlimit &lt;resource&gt; &lt;cur&gt; &lt;max&gt;</code><br>设置资源限制。同上面 service option 里面提到的 rlimit。<br>cur 和 max 可以设置为 ‘unlimited’ 或者 ‘-1’ 表示不做限制。</p></li><li><p><code>start &lt;service&gt;</code><br>如果指定服务没有在运行的话，则启动这个服务。</p></li><li><p><code>stop &lt;service&gt;</code><br>如果指定的服务正在运行的话，则停止这个服务。</p></li><li><p><code>symlink &lt;target&gt; &lt;path&gt;</code><br>创建软链接。</p></li><li><p><code>sysclktz &lt;mins_west_of_gmt&gt;</code><br>设置系统时区。</p></li><li><p><code>trigger &lt;event&gt;</code><br>触发一个 event 事件。</p></li><li><p><code>umount &lt;path&gt;</code><br>umount 分区</p></li><li><p><code>wait &lt;path&gt; [ &lt;timeout&gt; ]</code><br>查看指定路径是否存在。 如果发现则返回, 可以设置超时时间，默认值是5秒。这个命令会阻塞 init 进程的运行。之前优化开机速度时，就发现有人在 init.rc 里面用了 wait 命令，导致系统启动慢了几秒。</p></li><li><p><code>wait_for_prop &lt;name&gt; &lt;value&gt;</code><br>等待 property 的值变成 <code>&lt;value&gt;</code>。这个也会阻塞 init 进程的运行。</p></li><li><p><code>write &lt;path&gt; &lt;content&gt;</code><br>打开 path 路径指定的文件，然后调用 write 函数往文件中写一个字符串 content。如果文件路径不存在，则会新创建一个文件。如果文件已经存在了，则文件会被清空。<code>content</code> 的值可以使用 property 扩展形式 ${property}。</p></li></ul><p>Imports<br>import 我觉得可以按 c 语言的 include 头文件来理解即可。主要是为了把配置文件按模块进行拆分，方便维护。</p><hr><p><code>import &lt;path&gt;</code><br>导入 init rc 文件，　以括展当前文件的配置。如果 path 是个目录， 则导入这个目录下所有.rc文件，但是不会递归查找。</p><h2 id="Properties"><a href="#Properties" class="headerlink" title="Properties"></a>Properties</h2><p>init 进程通过以下 property 可以提供一个状态信息:</p><ul><li><code>init.svc.&lt;name&gt;</code><br>一个服务的运行状态：”stopped”, “stopping”, “running”, “restarting”</li></ul><h2 id="Boot-timing"><a href="#Boot-timing" class="headerlink" title="Boot timing"></a>Boot timing</h2><p>init 进程记录了一些启动过程中的耗时信息在 property 里面。</p><ul><li><p><code>ro.boottime.init</code><br>从 boot 开始到 init 进程初始化第一阶段开始的时间，单位为 ns。</p></li><li><p><code>ro.boottime.init.selinux</code><br>从初始化第一阶段开始到初始化 selinux 的耗时。其实也就是 init 进程第一阶段初始化的耗时了。</p></li><li><p><code>ro.boottime.init.cold_boot_wait</code><br>ueventd 的冷启动耗时。</p></li></ul><p><code>ro.boottime.&lt;service-name&gt;</code><br>从 boot 到 service 第一次启动的时间。</p><p>后面的部分内容跟 init.rc 文件的语法没有什么关系了，就不再展开了。后续再单独在其他文章中讨论以下部分的内容。<br>看完以上的介绍，相信同学们应该对 init.rc 文件有了大概的了解了。编写自己的服务，或者 Action 应该不成问题了。</p><h2 id="Bootcharting"><a href="#Bootcharting" class="headerlink" title="Bootcharting"></a>Bootcharting</h2><p>This version of init contains code to perform “bootcharting”: generating log<br>files that can be later processed by the tools provided by <a href="http://www.bootchart.org/" target="_blank" rel="noopener">http://www.bootchart.org/</a>.</p><p>On the emulator, use the -bootchart <em>timeout</em> option to boot with bootcharting<br>activated for <em>timeout</em> seconds.</p><p>On a device:</p><pre><code>adb shell &apos;touch /data/bootchart/enabled&apos;</code></pre><p>Don’t forget to delete this file when you’re done collecting data!</p><p>The log files are written to /data/bootchart/. A script is provided to<br>retrieve them and create a bootchart.tgz file that can be used with the<br>bootchart command-line utility:</p><pre><code>sudo apt-get install pybootchartgui# grab-bootchart.sh uses $ANDROID_SERIAL.$ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh</code></pre><p>One thing to watch for is that the bootchart will show init as if it started<br>running at 0s. You’ll have to look at dmesg to work out when the kernel<br>actually started init.</p><h2 id="Comparing-two-bootcharts"><a href="#Comparing-two-bootcharts" class="headerlink" title="Comparing two bootcharts"></a>Comparing two bootcharts</h2><p>A handy script named compare-bootcharts.py can be used to compare the<br>start/end time of selected processes. The aforementioned grab-bootchart.sh<br>will leave a bootchart tarball named bootchart.tgz at /tmp/android-bootchart.<br>If two such barballs are preserved on the host machine under different<br>directories, the script can list the timestamps differences. For example:</p><p>Usage: system/core/init/compare-bootcharts.py <em>base-bootchart-dir</em> <em>exp-bootchart-dir</em></p><pre><code>process: baseline experiment (delta) - Unit is ms (a jiffy is 10 ms on the system)------------------------------------/init: 50 40 (-10)/system/bin/surfaceflinger: 4320 4470 (+150)/system/bin/bootanimation: 6980 6990 (+10)zygote64: 10410 10640 (+230)zygote: 10410 10640 (+230)system_server: 15350 15150 (-200)bootanimation ends at: 33790 31230 (-2560)</code></pre><h2 id="Systrace"><a href="#Systrace" class="headerlink" title="Systrace"></a>Systrace</h2><p>Systrace (<a href="http://developer.android.com/tools/help/systrace.html" target="_blank" rel="noopener">http://developer.android.com/tools/help/systrace.html</a>) can be<br>used for obtaining performance analysis reports during boot<br>time on userdebug or eng builds.</p><p>Here is an example of trace events of “wm” and “am” categories:</p><pre><code>$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py \      wm am --boot</code></pre><p>This command will cause the device to reboot. After the device is rebooted and<br>the boot sequence has finished, the trace report is obtained from the device<br>and written as trace.html on the host by hitting Ctrl+C.</p><p>Limitation: recording trace events is started after persistent properties are loaded, so<br>the trace events that are emitted before that are not recorded. Several<br>services such as vold, surfaceflinger, and servicemanager are affected by this<br>limitation since they are started before persistent properties are loaded.<br>Zygote initialization and the processes that are forked from the zygote are not<br>affected.</p><h2 id="Debugging-init"><a href="#Debugging-init" class="headerlink" title="Debugging init"></a>Debugging init</h2><p>Launching init services without init is not recommended as init sets up a significant amount of<br>environment (user, groups, security label, capabilities, etc) that is hard to replicate manually.</p><p>If it is required to debug a service from its very start, the <code>sigstop</code> service option is added.<br>This option will send SIGSTOP to a service immediately before calling exec. This gives a window<br>where developers can attach a debugger, strace, etc before continuing the service with SIGCONT.</p><p>This flag can also be dynamically controled via the ctl.sigstop_on and ctl.sigstop_off properties.</p><p>Below is an example of dynamically debugging logd via the above:</p><pre><code>stop logdsetprop ctl.sigstop_on logdstart logdps -e | grep logd&gt; logd          4343     1   18156   1684 do_signal_stop 538280 T initgdbclient.py -p 4343b mainccc&gt; Breakpoint 1, main (argc=1, argv=0x7ff8c9a488) at system/core/logd/main.cpp:427</code></pre><p>Below is an example of doing the same but with strace</p><pre><code>stop logdsetprop ctl.sigstop_on logdstart logdps -e | grep logd&gt; logd          4343     1   18156   1684 do_signal_stop 538280 T initstrace -p 4343(From a different shell)kill -SIGCONT 4343&gt; strace runs</code></pre><h2 id="Host-Init-Script-Verification"><a href="#Host-Init-Script-Verification" class="headerlink" title="Host Init Script Verification"></a>Host Init Script Verification</h2><p>Init scripts are checked for correctness during build time. Specifically the below is checked.</p><p>1) Well formatted action, service and import sections, e.g. no actions without a preceding ‘on’<br>line, and no extraneous lines after an ‘import’ statement.<br>2) All commands map to a valid keyword and the argument count is within the correct range.<br>3) All service options are valid. This is stricter than how commands are checked as the service<br>options’ arguments are fully parsed, e.g. UIDs and GIDs must resolve.</p><p>There are other parts of init scripts that are only parsed at runtime and therefore not checked<br>during build time, among them are the below.</p><p>1) The validity of the arguments of commands, e.g. no checking if file paths actually exist, if<br>SELinux would permit the operation, or if the UIDs and GIDs resolve.<br>2) No checking if a service exists or has a valid SELinux domain defined<br>3) No checking if a service has not been previously defined in a different init script.</p><h2 id="Early-Init-Boot-Sequence"><a href="#Early-Init-Boot-Sequence" class="headerlink" title="Early Init Boot Sequence"></a>Early Init Boot Sequence</h2><p>The early init boot sequence is broken up into three stages: first stage init, SELinux setup, and<br>second stage init.</p><p>First stage init is responsible for setting up the bare minimum requirements to load the rest of the<br>system. Specifically this includes mounting /dev, /proc, mounting ‘early mount’ partitions (which<br>needs to include all partitions that contain system code, for example system and vendor), and moving<br>the system.img mount to / for devices with a ramdisk.</p><p>Note that in Android Q, system.img always contains TARGET_ROOT_OUT and always is mounted at / by the<br>time first stage init finishes. Android Q will also require dynamic partitions and therefore will<br>require using a ramdisk to boot Android. The recovery ramdisk can be used to boot to Android instead<br>of a dedicated ramdisk as well.</p><p>First stage init has three variations depending on the device configuration:</p><p>1) For system-as-root devices, first stage init is part of /system/bin/init and a symlink at /init<br>points to /system/bin/init for backwards compatibility. These devices do not need to do anything to<br>mount system.img, since it is by definition already mounted as the rootfs by the kernel.</p><p>2) For devices with a ramdisk, first stage init is a static executable located at /init. These<br>devices mount system.img as /system then perform a switch root operation to move the mount at<br>/system to /. The contents of the ramdisk are freed after mounting has completed.</p><p>3) For devices that use recovery as a ramdisk, first stage init it contained within the shared init<br>located at /init within the recovery ramdisk. These devices first switch root to<br>/first_stage_ramdisk to remove the recovery components from the environment, then proceed the same<br>as 2). Note that the decision to boot normally into Android instead of booting<br>into recovery mode is made if androidboot.force_normal_boot=1 is present in the<br>kernel commandline.</p><p>Once first stage init finishes it execs /system/bin/init with the “selinux_setup” argument. This<br>phase is where SELinux is optionally compiled and loaded onto the system. selinux.cpp contains more<br>information on the specifics of this process.</p><p>Lastly once that phase finishes, it execs /system/bin/init again with the “second_stage”<br>argument. At this point the main phase of init runs and continues the boot process via the init.rc<br>scripts.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;init 是 Android 系统启动的第一个用户空间进程，它的主要工作是对系统进行初始化，然后启动系统的各种核心服务。我们知道，Android 可能运行在各种不同的产品上，不同的产品所需要启动的服务也是有很大的差异的。为了满足不同产品的需求，init 进程把一部分初始化工
      
    
    </summary>
    
    
      <category term="Android系统开发进阶" scheme="http://qiushao.net/categories/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%BF%9B%E9%98%B6/"/>
    
    
      <category term="Android" scheme="http://qiushao.net/tags/Android/"/>
    
  </entry>
  
  <entry>
    <title>ubuntu 18.04 升级 Linux 5.4 内核</title>
    <link href="http://qiushao.net/2020/03/01/Linux/ubuntu-upgrade-kernel/"/>
    <id>http://qiushao.net/2020/03/01/Linux/ubuntu-upgrade-kernel/</id>
    <published>2020-03-01T03:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>目前在用的系统是 ubuntu 18.04.4 版本，内核版本如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ uname -a</span><br><span class="line">Linux qiushao-pc 5.3.0-28-generic #30~18.04.1-Ubuntu SMP Fri Jan 17 06:14:09 UTC 2020 x86_64 x86_64 x86_64 GNU&#x2F;Linux</span><br><span class="line">qiushao@qiushao-pc:~$</span><br></pre></td></tr></table></figure><p>cpu 是: amd 3900x, 主板是: 华硕X570， cpu,主板是比较新的， 5.3 版本的内核还有很多问题，比如说耳机口不能用，蓝牙连接不稳定等。一开始我还以为是硬件本身的问题，还以为是我自己装机时接错线了。前两天看 manjaro 出了新的版本，内核是 5.4 的。就安装到另一个分区尝试了一下，发现耳机，蓝牙的问题都没有。猜测是新版本的内核对主板的支持更完善了。于是想试试给 ubuntu 升级下内核到 5.4 版本，看看能不能解决耳机，蓝牙的问题。</p><h2 id="1-下载官方编译好的内核"><a href="#1-下载官方编译好的内核" class="headerlink" title="1. 下载官方编译好的内核"></a>1. 下载官方编译好的内核</h2><p>升级内核有两种方法：1. 自己下载源码编译；2. 使用官方编译好的内核。一般来说最好都是使用官方编译好的。<br>官方编译的内核可以从这里下载：<a href="https://kernel.ubuntu.com/~kernel-ppa/mainline/" target="_blank" rel="noopener">https://kernel.ubuntu.com/~kernel-ppa/mainline/</a><br>因为我的目的是升级到 5.4 版本的内核，找到 5.4 的最新版本是 5.4.23：<a href="https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.4.23/" target="_blank" rel="noopener">https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.4.23/</a><br>页面前面有一段描述：</p><blockquote><p>Build for amd64 succeeded (see BUILD.LOG.amd64):<br>  linux-headers-5.4.23-050423_5.4.23-050423.202002281329_all.deb<br>  linux-headers-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb<br>  linux-headers-5.4.23-050423-lowlatency_5.4.23-050423.202002281329_amd64.deb<br>  linux-image-unsigned-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb<br>  linux-image-unsigned-5.4.23-050423-lowlatency_5.4.23-050423.202002281329_amd64.deb<br>  linux-modules-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb<br>  linux-modules-5.4.23-050423-lowlatency_5.4.23-050423.202002281329_amd64.deb</p></blockquote><p>generic 用于支持通用硬件系统， lowlatency 用于低延迟硬件系统，amd64用于64位系统。我们的电脑一般用 generic 就行了， 不用下载 lowlatency 相关的安装包。只需要下载下面几个安装包即可：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">wget https:&#x2F;&#x2F;kernel.ubuntu.com&#x2F;~kernel-ppa&#x2F;mainline&#x2F;v5.4.23&#x2F;linux-headers-5.4.23-050423_5.4.23-050423.202002281329_all.deb</span><br><span class="line">wget https:&#x2F;&#x2F;kernel.ubuntu.com&#x2F;~kernel-ppa&#x2F;mainline&#x2F;v5.4.23&#x2F;linux-headers-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb</span><br><span class="line">wget https:&#x2F;&#x2F;kernel.ubuntu.com&#x2F;~kernel-ppa&#x2F;mainline&#x2F;v5.4.23&#x2F;linux-image-unsigned-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb</span><br><span class="line">wget https:&#x2F;&#x2F;kernel.ubuntu.com&#x2F;~kernel-ppa&#x2F;mainline&#x2F;v5.4.23&#x2F;linux-modules-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb</span><br></pre></td></tr></table></figure><h2 id="2-安装内核"><a href="#2-安装内核" class="headerlink" title="2. 安装内核"></a>2. 安装内核</h2><p>进入下载内核的目录，然后执行以下命令即可：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~&#x2F;Downloads&#x2F;kernel$ ls</span><br><span class="line">linux-headers-5.4.23-050423_5.4.23-050423.202002281329_all.deb            linux-image-unsigned-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb</span><br><span class="line">linux-headers-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb  linux-modules-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;Downloads&#x2F;kernel$</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;Downloads&#x2F;kernel$ sudo dpkg -i *.deb</span><br><span class="line">(Reading database ... 244666 files and directories currently installed.)</span><br><span class="line">Preparing to unpack linux-headers-5.4.23-050423_5.4.23-050423.202002281329_all.deb ...</span><br><span class="line">Unpacking linux-headers-5.4.23-050423 (5.4.23-050423.202002281329) over (5.4.23-050423.202002281329) ...</span><br><span class="line">Preparing to unpack linux-headers-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb ...</span><br><span class="line">Unpacking linux-headers-5.4.23-050423-generic (5.4.23-050423.202002281329) over (5.4.23-050423.202002281329) ...</span><br><span class="line">Preparing to unpack linux-image-unsigned-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb ...</span><br><span class="line">Unpacking linux-image-unsigned-5.4.23-050423-generic (5.4.23-050423.202002281329) over (5.4.23-050423.202002281329) ...</span><br><span class="line">Preparing to unpack linux-modules-5.4.23-050423-generic_5.4.23-050423.202002281329_amd64.deb ...</span><br><span class="line">Unpacking linux-modules-5.4.23-050423-generic (5.4.23-050423.202002281329) over (5.4.23-050423.202002281329) ...</span><br><span class="line">Setting up linux-headers-5.4.23-050423 (5.4.23-050423.202002281329) ...</span><br><span class="line">Setting up linux-headers-5.4.23-050423-generic (5.4.23-050423.202002281329) ...</span><br><span class="line">Setting up linux-modules-5.4.23-050423-generic (5.4.23-050423.202002281329) ...</span><br><span class="line">Setting up linux-image-unsigned-5.4.23-050423-generic (5.4.23-050423.202002281329) ...</span><br><span class="line">Processing triggers for linux-image-unsigned-5.4.23-050423-generic (5.4.23-050423.202002281329) ...</span><br><span class="line">&#x2F;etc&#x2F;kernel&#x2F;postinst.d&#x2F;initramfs-tools:</span><br><span class="line">update-initramfs: Generating &#x2F;boot&#x2F;initrd.img-5.4.23-050423-generic</span><br><span class="line">W: Possible missing firmware &#x2F;lib&#x2F;firmware&#x2F;rtl_nic&#x2F;rtl8125a-3.fw for module r8169</span><br><span class="line">I: The initramfs will attempt to resume from &#x2F;dev&#x2F;nvme0n1p4</span><br><span class="line">I: (UUID&#x3D;5d386fca-a168-4aae-9b13-425eae5932ea)</span><br><span class="line">I: Set the RESUME variable to override this.</span><br><span class="line">&#x2F;etc&#x2F;kernel&#x2F;postinst.d&#x2F;zz-update-grub:</span><br><span class="line">Sourcing file &#96;&#x2F;etc&#x2F;default&#x2F;grub&#39;</span><br><span class="line">Generating grub configuration file ...</span><br><span class="line">Found linux image: &#x2F;boot&#x2F;vmlinuz-5.4.23-050423-generic</span><br><span class="line">Found initrd image: &#x2F;boot&#x2F;initrd.img-5.4.23-050423-generic</span><br><span class="line">Found linux image: &#x2F;boot&#x2F;vmlinuz-5.3.0-28-generic</span><br><span class="line">Found initrd image: &#x2F;boot&#x2F;initrd.img-5.3.0-28-generic</span><br><span class="line">Found linux image: &#x2F;boot&#x2F;vmlinuz-5.0.0-37-generic</span><br><span class="line">Found initrd image: &#x2F;boot&#x2F;initrd.img-5.0.0-37-generic</span><br><span class="line">Found Manjaro Linux (19.0.1) on &#x2F;dev&#x2F;nvme0n1p3</span><br><span class="line">Adding boot menu entry for EFI firmware configuration</span><br><span class="line">done</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;Downloads&#x2F;kernel$ </span><br><span class="line">qiushao@qiushao-pc:~&#x2F;Downloads&#x2F;kernel$ reboot</span><br></pre></td></tr></table></figure><h2 id="3-重启验证"><a href="#3-重启验证" class="headerlink" title="3. 重启验证"></a>3. 重启验证</h2><p>重启后，查看一下内核的版本信息：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ uname -a</span><br><span class="line">Linux qiushao-pc 5.3.0-28-generic #30~18.04.1-Ubuntu SMP Fri Jan 17 06:14:09 UTC 2020 x86_64 x86_64 x86_64 GNU&#x2F;Linux</span><br><span class="line">qiushao@qiushao-pc:~$</span><br></pre></td></tr></table></figure><p>奇怪了啊，怎么还是 5.3 版本的内核呢！再看一下之前的安装日志也没有任何错误提示啊。又重复安装一遍，发现还是一样的问题。<br>到 /boot 目录下看看：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:&#x2F;boot$ ls</span><br><span class="line">config-5.0.0-37-generic       efi                          initrd.img-5.3.0-28-generic       memtest86+.elf               System.map-5.3.0-28-generic       vmlinuz-5.3.0-28-generic</span><br><span class="line">config-5.3.0-28-generic       grub                         initrd.img-5.4.23-050423-generic  memtest86+_multiboot.bin     System.map-5.4.23-050423-generic  vmlinuz-5.4.23-050423-generic</span><br><span class="line">config-5.4.23-050423-generic  initrd.img-5.0.0-37-generic  memtest86+.bin                    System.map-5.0.0-37-generic  vmlinuz-5.0.0-37-generic</span><br><span class="line">qiushao@qiushao-pc:&#x2F;boot$</span><br></pre></td></tr></table></figure><p>看起来已经有 5.4 的内核在里面了。为什么没有用新的内核来启动呢？<br>不信邪了，再 reboot 一次看看。在 reboot 到 grub 选择要启动的系统时突然猜测到了原因。因为前两天才再另外一个分区上安装的 manjaro, 所以目前系统上的 grub 启动菜单其实是 manjaro 所在的分区的。而系统的启动内核是由 grub 指定的。而我没有在 manjaro 系统里面更新 grub， 所以还是使用旧版本的内核。系统的分区信息如下:<br><img src="/images/pc-partitions.png" alt="partitions"></p><ul><li>partition 1 : uefi 分区， 指向的启动分区为 partition 3</li><li>partition 2 : ubuntu / 分区, 里面有 /boot/grub/grub.cfg 启动菜单配置文件</li><li>partition 3 : manjaro / 分区, 里面也有 /boot/grub/grub.cfg 启动菜单配置文件</li><li>partition 4 : ubuntu &amp; manjaro /home 分区</li></ul><p>因为 manjaro 是后安装的，所以启动菜单使用的是 parition 3 中的 grub.cfg 配置文件，　但我们在安装内核的时候，自动更新的是 partition 2 中的 grub.cfg。<br>我们看看 partition 2　里面的 grub.cfg 里面的配置内容：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">menuentry &#39;Ubuntu&#39; --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option &#39;gnulinux-simple-697f7639-b38d-4e6c-a994-d36c5542dae5&#39; &#123;</span><br><span class="line">        recordfail</span><br><span class="line">        load_video</span><br><span class="line">        gfxmode $linux_gfx_mode</span><br><span class="line">        insmod gzio</span><br><span class="line">        if [ x$grub_platform &#x3D; xxen ]; then insmod xzio; insmod lzopio; fi</span><br><span class="line">        insmod part_gpt</span><br><span class="line">        insmod ext2</span><br><span class="line">        if [ x$feature_platform_search_hint &#x3D; xy ]; then</span><br><span class="line">          search --no-floppy --fs-uuid --set&#x3D;root  697f7639-b38d-4e6c-a994-d36c5542dae5</span><br><span class="line">        else</span><br><span class="line">          search --no-floppy --fs-uuid --set&#x3D;root 697f7639-b38d-4e6c-a994-d36c5542dae5</span><br><span class="line">        fi</span><br><span class="line">        linux   &#x2F;boot&#x2F;vmlinuz-5.4.23-050423-generic root&#x3D;UUID&#x3D;697f7639-b38d-4e6c-a994-d36c5542dae5 ro  quiet splash $vt_handoff</span><br><span class="line">        initrd  &#x2F;boot&#x2F;initrd.img-5.4.23-050423-generic</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们看到最后两行配置是内核相关的，其中指定了使用哪个版本的内核来启动系统。关于 grub 的知识，是 Linux 通用的，任何发行版都是一样的。</p><p>解决方案很简单，先进入 manjaro 系统先，然后执行这个命令更新 grub 启动信息：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo update-grub</span><br></pre></td></tr></table></figure><p>更新完之后， reboot 重启进入 ubuntu 再看看内核的版本：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~$ uname -a</span><br><span class="line">Linux qiushao-pc 5.4.23-050423-generic #202002281329 SMP Fri Feb 28 18:33:31 UTC 2020 x86_64 x86_64 x86_64 GNU&#x2F;Linux</span><br><span class="line">qiushao@qiushao-pc:~$</span><br></pre></td></tr></table></figure><p>嗯，升级到了 5.4。再看看耳机接口，嗯，有声音了，看来的确是新版本的内核对新硬件的支持会好一点。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;目前在用的系统是 ubuntu 18.04.4 版本，内核版本如下：&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;
      
    
    </summary>
    
    
      <category term="Linux" scheme="http://qiushao.net/categories/Linux/"/>
    
    
      <category term="Linux" scheme="http://qiushao.net/tags/Linux/"/>
    
      <category term="Kernel" scheme="http://qiushao.net/tags/Kernel/"/>
    
  </entry>
  
  <entry>
    <title>Android系统开发进阶-BootAnimation添加视频播放功能</title>
    <link href="http://qiushao.net/2020/02/27/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%BF%9B%E9%98%B6/BootAnimation%E6%B7%BB%E5%8A%A0%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E5%8A%9F%E8%83%BD/"/>
    <id>http://qiushao.net/2020/02/27/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%BF%9B%E9%98%B6/BootAnimation%E6%B7%BB%E5%8A%A0%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E5%8A%9F%E8%83%BD/</id>
    <published>2020-02-27T04:00:00.000Z</published>
    <updated>2020-12-27T01:22:23.744Z</updated>
    
    <content type="html"><![CDATA[<p>对于手机用户来说，开机动画接触得比较少，因为用户可能一年也不会关开机一次。但对于 TV 设备跟盒子的用户来说，开机动画就是家常便饭了，每天都要开关机。反正都是要等待开机，看 Android logo 是看，看广告也是看，所以各厂商也就利用这个来顺便赚个广告费。Android 系统默认的开机动画只支持图片形式的动画，是不支持 mp4 等视频类型的，但我们的广告又是以视频为主的。所以我们需要给 BootAnimation 增加播放视频的功能，下面我们就来演示一下怎么实现这个功能。</p><h2 id="1-BootAnimation-类添加-video-播放函数"><a href="#1-BootAnimation-类添加-video-播放函数" class="headerlink" title="1. BootAnimation 类添加 video 播放函数"></a>1. BootAnimation 类添加 video 播放函数</h2><p>BootAnimation.h </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="function"><span class="keyword">bool</span> <span class="title">android</span><span class="params">()</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">movie</span><span class="params">()</span></span>;</span><br><span class="line"><span class="comment">//qiushao patch add start</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">video</span><span class="params">()</span></span>;</span><br><span class="line"><span class="comment">//qiushao patch add end</span></span><br></pre></td></tr></table></figure><p>BootAnimation.cpp</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><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//qiushao patch add start</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;media/mediaplayer.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;media/IMediaHTTPService.h&gt;</span></span></span><br><span class="line"><span class="comment">//qiushao patch add start</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//qiushao patch add start</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">char</span> *videoPath = <span class="string">"/system/media/bootvideo.mp4"</span>;</span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">BootAnimation::video</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">float</span> MAX_FPS = <span class="number">60.0f</span>;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">float</span> CHECK_DELAY = ns2us(s2ns(<span class="number">1</span>) / MAX_FPS);</span><br><span class="line">    eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);</span><br><span class="line">    eglDestroySurface(mDisplay, mSurface);</span><br><span class="line">    sp&lt;MediaPlayer&gt; mp = <span class="keyword">new</span> MediaPlayer();</span><br><span class="line">    mp-&gt;reset();</span><br><span class="line">    mp-&gt;setDataSource(<span class="literal">NULL</span>, videoPath, <span class="literal">NULL</span>);</span><br><span class="line">    mp-&gt;setVideoSurfaceTexture(mFlingerSurface-&gt;getIGraphicBufferProducer());</span><br><span class="line">    mp-&gt;<span class="built_in">prepare</span>();</span><br><span class="line">    mp-&gt;start();</span><br><span class="line">    <span class="comment">//设置音量</span></span><br><span class="line">    mp-&gt;setVolume(<span class="number">1.0</span>, <span class="number">1.0</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//等Launcher启动完成</span></span><br><span class="line">    <span class="keyword">while</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span>(exitPending()) &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;   </span><br><span class="line">        usleep(CHECK_DELAY);</span><br><span class="line">        checkExit();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//等开机视频播放完成</span></span><br><span class="line">    <span class="keyword">while</span> (mp-&gt;isPlaying()) &#123;</span><br><span class="line">        usleep(<span class="number">200000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    ALOGD(<span class="string">"bootvideo play finish"</span>);</span><br><span class="line"></span><br><span class="line">    mp-&gt;<span class="built_in">stop</span>();</span><br><span class="line">    mp-&gt;<span class="built_in">disconnect</span>();</span><br><span class="line">    mp.<span class="built_in">clear</span>();</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//qiushao patch add end</span></span><br><span class="line"><span class="function"><span class="keyword">bool</span> <span class="title">BootAnimation::threadLoop</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">//qiushao patch modify start</span></span><br><span class="line">    <span class="keyword">if</span> (access(videoPath, R_OK) == <span class="number">0</span>) &#123;</span><br><span class="line">        video();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (!mZipFileName.isEmpty()) &#123;</span><br><span class="line">        movie();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        android();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//qiushao patch modify end</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="2-添加依赖"><a href="#2-添加依赖" class="headerlink" title="2. 添加依赖"></a>2. 添加依赖</h2><p>因为我们要用到 MediaPlayer, 所以需要添加 libmedia 依赖：<br>Android.bp</p><figure class="highlight"><table><tr><td class="gutter"><pre><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">cc_library_shared &#123;</span><br><span class="line">    name: "libbootanimation",</span><br><span class="line">    defaults: ["bootanimation_defaults"],</span><br><span class="line"></span><br><span class="line">    srcs: ["BootAnimation.cpp"],</span><br><span class="line"></span><br><span class="line">    shared_libs: [</span><br><span class="line">        "libui",</span><br><span class="line">        "libhwui",</span><br><span class="line">        "libEGL",</span><br><span class="line">        "libGLESv1_CM",</span><br><span class="line">        "libgui",</span><br><span class="line">        "libtinyalsa",</span><br><span class="line">        <span class="comment">//qiushao patch add start</span></span><br><span class="line">        "libmedia",</span><br><span class="line">        <span class="comment">//qiushao patch add end</span></span><br><span class="line">    ],</span><br><span class="line"></span><br><span class="line">    product_variables: &#123;</span><br><span class="line">        product_is_iot: &#123;</span><br><span class="line">            init_rc: ["iot/bootanim_iot.rc"],</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><h2 id="3-selinux-设置"><a href="#3-selinux-设置" class="headerlink" title="3. selinux 设置"></a>3. selinux 设置</h2><p>selinux 的配置都是在执行后，看报什么错误，就加什么权限，最后需要给 bootanim 服务和 mediaserver 服务添加权限，直接看 diff 吧:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><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></pre></td><td class="code"><pre><span class="line">qiushao@qiushao-pc:~&#x2F;source&#x2F;android-10&#x2F;system&#x2F;sepolicy$ git diff .</span><br><span class="line">diff --git a&#x2F;prebuilts&#x2F;api&#x2F;29.0&#x2F;public&#x2F;bootanim.te b&#x2F;prebuilts&#x2F;api&#x2F;29.0&#x2F;public&#x2F;bootanim.te</span><br><span class="line">index e8cb98bbc..0b1ffd3ee 100644</span><br><span class="line">--- a&#x2F;prebuilts&#x2F;api&#x2F;29.0&#x2F;public&#x2F;bootanim.te</span><br><span class="line">+++ b&#x2F;prebuilts&#x2F;api&#x2F;29.0&#x2F;public&#x2F;bootanim.te</span><br><span class="line">@@ -8,6 +8,7 @@ hal_client_domain(bootanim, hal_graphics_composer)</span><br><span class="line"> </span><br><span class="line"> binder_use(bootanim)</span><br><span class="line"> binder_call(bootanim, surfaceflinger)</span><br><span class="line">+binder_call(bootanim, mediaserver)</span><br><span class="line"> binder_call(bootanim, audioserver)</span><br><span class="line"> </span><br><span class="line"> hwbinder_use(bootanim)</span><br><span class="line">@@ -23,6 +24,7 @@ allow bootanim audio_device:chr_file rw_file_perms;</span><br><span class="line"> </span><br><span class="line"> allow bootanim audioserver_service:service_manager find;</span><br><span class="line"> allow bootanim surfaceflinger_service:service_manager find;</span><br><span class="line">+allow bootanim mediaserver_service:service_manager find;</span><br><span class="line"> </span><br><span class="line"> # Allow access to ion memory allocation device</span><br><span class="line"> allow bootanim ion_device:chr_file rw_file_perms;</span><br><span class="line">diff --git a&#x2F;prebuilts&#x2F;api&#x2F;29.0&#x2F;public&#x2F;mediaserver.te b&#x2F;prebuilts&#x2F;api&#x2F;29.0&#x2F;public&#x2F;mediaserver.te</span><br><span class="line">index 70d0a55b2..5a9d8fef0 100644</span><br><span class="line">--- a&#x2F;prebuilts&#x2F;api&#x2F;29.0&#x2F;public&#x2F;mediaserver.te</span><br><span class="line">+++ b&#x2F;prebuilts&#x2F;api&#x2F;29.0&#x2F;public&#x2F;mediaserver.te</span><br><span class="line">@@ -24,6 +24,7 @@ userdebug_or_eng(&#96;</span><br><span class="line"> binder_use(mediaserver)</span><br><span class="line"> binder_call(mediaserver, binderservicedomain)</span><br><span class="line"> binder_call(mediaserver, appdomain)</span><br><span class="line">+binder_call(mediaserver, bootanim)</span><br><span class="line"> binder_service(mediaserver)</span><br><span class="line"> </span><br><span class="line"> allow mediaserver media_data_file:dir create_dir_perms;</span><br><span class="line">diff --git a&#x2F;public&#x2F;bootanim.te b&#x2F;public&#x2F;bootanim.te</span><br><span class="line">index e8cb98bbc..0b1ffd3ee 100644</span><br><span class="line">--- a&#x2F;public&#x2F;bootanim.te</span><br><span class="line">+++ b&#x2F;public&#x2F;bootanim.te</span><br><span class="line">@@ -8,6 +8,7 @@ hal_client_domain(bootanim, hal_graphics_composer)</span><br><span class="line"> </span><br><span class="line"> binder_use(bootanim)</span><br><span class="line"> binder_call(bootanim, surfaceflinger)</span><br><span class="line">+binder_call(bootanim, mediaserver)</span><br><span class="line"> binder_call(bootanim, audioserver)</span><br><span class="line"> </span><br><span class="line"> hwbinder_use(bootanim)</span><br><span class="line">@@ -23,6 +24,7 @@ allow bootanim audio_device:chr_file rw_file_perms;</span><br><span class="line"> </span><br><span class="line"> allow bootanim audioserver_service:service_manager find;</span><br><span class="line"> allow bootanim surfaceflinger_service:service_manager find;</span><br><span class="line">+allow bootanim mediaserver_service:service_manager find;</span><br><span class="line"> </span><br><span class="line"> # Allow access to ion memory allocation device</span><br><span class="line"> allow bootanim ion_device:chr_file rw_file_perms;</span><br><span class="line">diff --git a&#x2F;public&#x2F;mediaserver.te b&#x2F;public&#x2F;mediaserver.te</span><br><span class="line">index 70d0a55b2..5a9d8fef0 100644</span><br><span class="line">--- a&#x2F;public&#x2F;mediaserver.te</span><br><span class="line">+++ b&#x2F;public&#x2F;mediaserver.te</span><br><span class="line">@@ -24,6 +24,7 @@ userdebug_or_eng(&#96;</span><br><span class="line"> binder_use(mediaserver)</span><br><span class="line"> binder_call(mediaserver, binderservicedomain)</span><br><span class="line"> binder_call(mediaserver, appdomain)</span><br><span class="line">+binder_call(mediaserver, bootanim)</span><br><span class="line"> binder_service(mediaserver)</span><br><span class="line"> </span><br><span class="line"> allow mediaserver media_data_file:dir create_dir_perms;</span><br><span class="line">qiushao@qiushao-pc:~&#x2F;source&#x2F;android-10&#x2F;system&#x2F;sepolicy$</span><br></pre></td></tr></table></figure><h2 id="4-bootvideo-预置"><a href="#4-bootvideo-预置" class="headerlink" title="4. bootvideo 预置"></a>4. bootvideo 预置</h2><p>我们把 bootvideo.mp4 视频文件放到 device/qiushao/pure/bootvideo.mp4，　我测试用的 <a href="/images/bootvideo.mp4">bootvideo.mp4</a><br>然后在 pure.mk 中添加以下配置：</p><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PRODUCT_COPY_FILES += device/qiushao/pure/bootvideo.mp4:system/media/bootvideo.mp4</span><br></pre></td></tr></table></figure><h2 id="5-编译验证"><a href="#5-编译验证" class="headerlink" title="5. 编译验证"></a>5. 编译验证</h2><p>以上就完成了 BootAnimation 播放视频的功能，其实也很简单，就是调用一下 MediaPlayer 的 c++ 接口而已。音量设置也是可以的。<br>我们编译验证一下，图像声音都是正常的。</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;对于手机用户来说，开机动画接触得比较少，因为用户可能一年也不会关开机一次。但对于 TV 设备跟盒子的用户来说，开机动画就是家常便饭了，每天都要开关机。反正都是要等待开机，看 Android logo 是看，看广告也是看，所以各厂商也就利用这个来顺便赚个广告费。Android
      
    
    </summary>
    
    
      <category term="Android系统开发进阶" scheme="http://qiushao.net/categories/Android%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E8%BF%9B%E9%98%B6/"/>
    
    
      <category term="Android" scheme="http://qiushao.net/tags/Android/"/>
    
  </entry>
  
</feed>
