<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://alonealive.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://alonealive.github.io/" rel="alternate" type="text/html" /><updated>2026-05-18T03:25:14+00:00</updated><id>https://alonealive.github.io/feed.xml</id><title type="html">WIZZIE BLOG</title><subtitle>android framework blogs.</subtitle><author><name>sunwengang</name><email>1332963488@qq.com</email></author><entry><title type="html">Android系统启动流程—2 init.rc文件启动流程</title><link href="https://alonealive.github.io/android/android_android%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B_2_initrc%E6%96%87%E4%BB%B6%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/" rel="alternate" type="text/html" title="Android系统启动流程—2 init.rc文件启动流程" /><published>2025-03-01T05:40:02+00:00</published><updated>2025-03-01T05:40:02+00:00</updated><id>https://alonealive.github.io/android/android_android%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B_2_initrc%E6%96%87%E4%BB%B6%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B</id><content type="html" xml:base="https://alonealive.github.io/android/android_android%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B_2_initrc%E6%96%87%E4%BB%B6%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/"><![CDATA[<blockquote>
  <p>接着上篇，init进程启动的SecondStageMain阶段中，最后也是最重要的就是init.rc以及其他各个目录rc的解析和触发，本篇主要针对这块进行详细梳理。</p>
</blockquote>

<h1 id="1-概述">1. 概述</h1>

<p><strong>init启动的SecondStageMain阶段中，针对rc的解析和触发的整体流程大致如下：</strong></p>
<ol>
  <li>解析/init.rc，然后递归地解析它的每个导入(此处递归是import的递归，不是文件夹的递归，文件夹不支持递归);</li>
  <li>解析/system/etc/init/目录的内容，按字母顺序排列并按顺序解析，在每个文件解析后递归地进行导入;</li>
  <li>步骤2重复/vendor/etc/init</li>
  <li>通过action机制触发器执行rc文件，从代码中看执行顺序是：”on early-init” -&gt; “on init” -&gt; “on late-init”</li>
</ol>

<hr />

<h1 id="2-initrc语言格式说明">2. init.rc语言格式说明</h1>

<p><strong>整体来说：</strong></p>
<ol>
  <li>init.rc文件是以section为单位组织的，一个section可以包含多行，section可以分为两大类，一类是<strong>action</strong>，另一类是<strong>service</strong>；</li>
  <li>action以关键字on开始，表示一组命令的集合；service以关键字service开始，表示启动某个进程的方式和参数；</li>
  <li>section以on或service开始，直到下一个on或者service结束，中间的所有行都属于这一个section（空行或者注释不具有分割作用）</li>
  <li>无论是action还是service，并不是按照文件的编排顺序执行的，他们只是一份定义，至于执行与否以及什么时候执行取决于init在运行时的操作</li>
</ol>

<p><strong>主要包含五个类型语句：</strong></p>
<ol>
  <li>Action：序列commands的集合，每个actions都有一个trigger，它用于决定action的执行时机，当一个符合action触发条件的事件发生了，此action会加入到执行队列的末尾，除非它已经在队列里</li>
  <li>Command：actions的命令列表中的命令，或者是service的选项参数命令</li>
  <li>Service：表示启动某个进程的方式和参数，如果退出了可以由系统重启（可选），可通过start command执行</li>
  <li>Option：services的修饰项，它们决定一个services何时以及如何运行</li>
  <li>Import：扩展当前配置。如果path是一个目录，该目录中的每个文件都被解析为一个配置文件。它不是递归的，嵌套的目录将不会被解析</li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># --------- action</span>
on &lt;trigger&gt; <span class="o">[&amp;&amp;</span> &lt;trigger&gt;]<span class="k">*</span>    <span class="c">#设置触发器</span>
   &lt;<span class="nb">command</span><span class="o">&gt;</span>                    <span class="c">#动作触发之后要执行的命令</span>
   &lt;<span class="nb">command</span><span class="o">&gt;</span>
   &lt;<span class="nb">command</span><span class="o">&gt;</span>

on early-init                               <span class="c">#Action类型语句</span>
    <span class="c"># Set init and its forked children's oom_adj.</span>
    write /proc/1/oom_score_adj <span class="nt">-1000</span>       <span class="c">#Command语句，设置init进程为最高优先级</span>

<span class="c"># --------- service</span>
service &lt;name&gt; &lt;pathname&gt; <span class="o">[</span> &lt;argument&gt; <span class="o">]</span><span class="k">*</span>   <span class="c"># &lt;service的名字&gt;&lt;执行程序路径&gt;&lt;传递参数</span>
   &lt;option&gt;                                 <span class="c"># option是service的修饰词，影响什么时候，如何启动service</span>
   &lt;option&gt;
   ...
</code></pre></div></div>

<p><strong>关于触发Trigger：</strong>
Triggers是一个用于匹配某种事件类型的字符串，它将使对应的actions执行。触发器分为事件触发器和属性触发器：（一个操作可以有多个属性触发器）</p>
<ol>
  <li>事件触发器：是由<code class="language-plaintext highlighter-rouge">trigger</code>命令或init可执行文件中的<code class="language-plaintext highlighter-rouge">QueueEventTrigger()</code>函数触发的字符串。它们采用简单字符串的形式，如<code class="language-plaintext highlighter-rouge">boot</code>或<code class="language-plaintext highlighter-rouge">late-init</code></li>
  <li>属性触发器：是指属性值更改为给定的新值，或指定属性将值更改为任何新值时触发的字符串。它们分别采用<code class="language-plaintext highlighter-rouge">property:=</code>和<code class="language-plaintext highlighter-rouge">property:=\*</code>的形式。属性触发器将在init的初始引导阶段额外计算并相应地触发。</li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 表示当trigger early-init或QueueEventTrigger("early-init")调用时触发</span>
on early-init 
    ...

<span class="c"># 表示当sys.boot_from_charger_mode的值通过property_set设置为1时触发 （条件可以是多个，用&amp;&amp;连接）</span>
on property:sys.boot_from_charger_mode<span class="o">=</span>1 
    class_stop charger
    trigger late-init

<span class="c"># *表示任意值触发</span>
on property:sys.init_log_level<span class="o">=</span><span class="k">*</span>  
    loglevel <span class="k">${</span><span class="nv">sys</span><span class="p">.init_log_level</span><span class="k">}</span>
</code></pre></div></div>

<hr />

<h1 id="3-initrc解析流程">3. init.rc解析流程</h1>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">system/core/init/init.cpp</code>的SecondStageMain方法中，首先会按顺序加载init.rc、/system/etc/init的RC文件、/vendor/etc/init的RC文件（其中会包含一些import导入其他RC文件）</p>

  <p>加载完成后会通过调用QueueEventTrigger事件触发RC文件的执行。总体的顺序是<code class="language-plaintext highlighter-rouge">"on early-init" -&gt; "on init" -&gt; "on late-init"</code>三个阶段。我们通过init.rc文件逐步顺序解析（其他也有一些比如芯片厂商的RC文件也会包含这三个Action，但是不是主线内容，此处以init.rc分析为主）</p>

  <p>Android系统中RC文件有很多，最主要的是以init.rc，其他通常接触的还有一些service服务会通过rc服务启动。在init.rc中我们需要重点关注的是zygote和servicemanager两个服务的拉起。</p>
</blockquote>

<h2 id="31-import导入rc文件">3.1. import导入RC文件</h2>

<p>以system/core/rootdir/init.rc为例，如果有些RC文件不在加载的目录列表中，会通过import方式导入。但是实际执行顺序仍旧按触发器为准。</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">import</span><span class="w"> </span><span class="o">/</span><span class="n">init.environ.rc</span><span class="w">                             </span><span class="c1"># 导入全局环境变量的相关配置</span><span class="w">
</span><span class="n">import</span><span class="w"> </span><span class="o">/</span><span class="n">init.usb.rc</span><span class="w">                                 </span><span class="c1"># 导入usb相关的rc</span><span class="w">
</span><span class="n">import</span><span class="w"> </span><span class="o">/</span><span class="n">init.</span><span class="o">$</span><span class="p">{</span><span class="n">ro.hardware</span><span class="p">}</span><span class="n">.rc</span><span class="w">                      </span><span class="c1"># 导入硬件平台的rc，不同厂商的ro.hardware值不同</span><span class="w">
</span><span class="n">import</span><span class="w"> </span><span class="o">/</span><span class="n">vendor</span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">hw</span><span class="o">/</span><span class="n">init.</span><span class="o">$</span><span class="p">{</span><span class="n">ro.hardware</span><span class="p">}</span><span class="n">.rc</span><span class="w">   </span><span class="c1"># 导入硬件平台的rc，不同厂商的ro.hardware值不同</span><span class="w">
</span><span class="n">import</span><span class="w"> </span><span class="o">/</span><span class="n">init.usb.configfs.rc</span><span class="w">                        </span><span class="c1"># 导入usb相关的rc</span><span class="w">
</span><span class="n">import</span><span class="w"> </span><span class="o">/</span><span class="n">init.</span><span class="o">$</span><span class="p">{</span><span class="n">ro.zygote</span><span class="p">}</span><span class="n">.rc</span><span class="w">                        </span><span class="c1"># 导入zygote的rc，以ro.zygote属性值为准</span><span class="w">
</span></code></pre></div></div>

<h2 id="32-on-early-init阶段">3.2. on early-init阶段</h2>

<p>这个是init初始化的早期阶段，此处init.rc中主要是一些节点的赋值、创建、权限配置加载等。其中我们关注的是ueventd服务的启动。</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 代码文件：system/core/rootdir/init.rc </span><span class="w">
</span><span class="c1"># Cgroups are mounted right before early-init using list from /etc/cgroups.json</span><span class="w">
</span><span class="n">on</span><span class="w"> </span><span class="n">early</span><span class="o">-</span><span class="n">init</span><span class="w">
    </span><span class="c1"># Disable sysrq from keyboard</span><span class="w">
    </span><span class="n">write</span><span class="w"> </span><span class="o">/</span><span class="n">proc</span><span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">kernel</span><span class="o">/</span><span class="n">sysrq</span><span class="w"> </span><span class="m">0</span><span class="w">
    </span><span class="c1"># Set the security context of /adb_keys if present.</span><span class="w">
    </span><span class="n">restorecon</span><span class="w"> </span><span class="o">/</span><span class="n">adb_keys</span><span class="w">
    </span><span class="c1"># Set the security context of /postinstall if present.</span><span class="w">
    </span><span class="n">restorecon</span><span class="w"> </span><span class="o">/</span><span class="n">postinstall</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">acct</span><span class="o">/</span><span class="n">uid</span><span class="w">

    </span><span class="c1"># memory.pressure_level used by lmkd</span><span class="w">
    </span><span class="n">chown</span><span class="w"> </span><span class="n">root</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">memcg</span><span class="o">/</span><span class="n">memory.pressure_level</span><span class="w">
    </span><span class="n">chmod</span><span class="w"> </span><span class="m">0040</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">memcg</span><span class="o">/</span><span class="n">memory.pressure_level</span><span class="w">
    </span><span class="c1"># app mem cgroups, used by activity manager, lmkd and zygote</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">memcg</span><span class="o">/</span><span class="n">apps</span><span class="o">/</span><span class="w"> </span><span class="m">0755</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w">
    </span><span class="c1"># cgroup for system_server and surfaceflinger</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">memcg</span><span class="o">/</span><span class="n">system</span><span class="w"> </span><span class="m">0550</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w">
    </span><span class="c1"># 启动ueventd服务！！</span><span class="w">
    </span><span class="n">start</span><span class="w"> </span><span class="n">ueventd</span><span class="w">

    </span><span class="n">exec_start</span><span class="w"> </span><span class="n">apexd</span><span class="o">-</span><span class="n">bootstrap</span><span class="w">

</span><span class="n">service</span><span class="w"> </span><span class="n">ueventd</span><span class="w"> </span><span class="o">/</span><span class="n">system</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">ueventd</span><span class="w">
    </span><span class="n">class</span><span class="w"> </span><span class="n">core</span><span class="w">
    </span><span class="n">critical</span><span class="w">
    </span><span class="n">seclabel</span><span class="w"> </span><span class="n">u</span><span class="o">:</span><span class="n">r</span><span class="o">:</span><span class="n">ueventd</span><span class="o">:</span><span class="n">s0</span><span class="w">
    </span><span class="n">shutdown</span><span class="w"> </span><span class="n">critical</span><span class="w">
</span></code></pre></div></div>

<hr />

<h3 id="321-ueventd服务启动">3.2.1. ueventd服务启动</h3>

<h4 id="3211-ueventd编译配置分析">3.2.1.1. ueventd编译配置分析</h4>

<p>从编译结果看，uevented是软链接的init：</p>

<pre><code class="language-shellell">out/target/product/{product}/system/bin$ ls -al|grep init
-rwxr-xr-x  1 usunw074 ubuntu     1173 Jan 23 18:03 fsverity_init
-rwxr-xr-x  1 usunw074 ubuntu   743008 Jun 19 10:05 init
lrwxrwxrwx  1 usunw074 ubuntu        4 Jan 23 18:15 ueventd -&gt; init
</code></pre>

<p>从代码编译配置看，init进程的Android.bp中，ueventd是Init进程的软连接：</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">cc_binary</span> <span class="err">{</span>
    <span class="nl">name</span><span class="o">:</span> <span class="nf">"init_second_stage"</span><span class="p">,</span>
    <span class="nl">recovery_available</span><span class="o">:</span> <span class="nf">true</span><span class="p">,</span>
    <span class="nl">stem</span><span class="o">:</span> <span class="nf">"init"</span><span class="p">,</span>
    <span class="nl">defaults</span><span class="o">:</span> <span class="nf">["init_defaults"]</span><span class="p">,</span>
    <span class="nl">static_libs</span><span class="o">:</span> <span class="nf">["libinit"]</span><span class="p">,</span>
    <span class="nl">required</span><span class="o">:</span> <span class="nf">[</span>
        <span class="s2">"e2fsdroid"</span><span class="err">,</span>
        <span class="s2">"mke2fs"</span><span class="err">,</span>
        <span class="s2">"sload_f2fs"</span><span class="err">,</span>
        <span class="s2">"make_f2fs"</span><span class="err">,</span>
    <span class="err">],</span>
    <span class="nl">srcs</span><span class="o">:</span> <span class="nf">["main.cpp"]</span><span class="p">,</span>
    <span class="c">#软链接
</span>    <span class="nl">symlinks</span><span class="o">:</span> <span class="nf">["ueventd"]</span><span class="p">,</span>
    <span class="nl">target</span><span class="o">:</span> <span class="nf">{</span>
        <span class="nl">recovery</span><span class="o">:</span> <span class="nf">{</span>
            <span class="nl">cflags</span><span class="o">:</span> <span class="nf">["-DRECOVERY"]</span><span class="p">,</span>
            <span class="nl">exclude_shared_libs</span><span class="o">:</span> <span class="nf">["libbinder"</span><span class="p">,</span><span class="nf"> "libutils"]</span><span class="p">,</span>
        <span class="err">},</span>
    <span class="err">},</span>
    <span class="nl">ldflags</span><span class="o">:</span> <span class="nf">["-Wl</span><span class="p">,</span><span class="nf">--rpath</span><span class="p">,</span><span class="nf">/system/${LIB}/bootstrap"]</span><span class="p">,</span>
<span class="err">}</span>
</code></pre></div></div>

<hr />

<h4 id="3212-ueventd代码流程">3.2.1.2. ueventd代码流程</h4>

<blockquote>
  <p>接着上面RC触发ueventd服务启动，代码中函数入口是<code class="language-plaintext highlighter-rouge">system/core/init/main.cpp -- main</code>，入参ueventd</p>

  <p>接着调用ueventd_main（源码路径：system/core/init/ueventd.cpp）</p>
</blockquote>

<p><strong>主要三个部分：</strong></p>
<ol>
  <li>解析uevented.rc相关RC文件（/ueventd.rc，/vendor/ueventd.rc，/odm/ueventd.rc，/ueventd.{ro.hardware}.rc这四个相关文件）</li>
  <li>cold_boot，递归扫描/sys目录，根据uevent文件，fork子进程，静态创建设备节点。</li>
  <li>Hot Plug，获取内核uevent事件，动态创建设备节点</li>
</ol>

<p><strong>PS</strong>：Android根文件系统的镜像中不存在<code class="language-plaintext highlighter-rouge">/dev</code>目录，该目录是init进程启动后动态创建的。因此，建立Android中设备节点文件的重任，也落在了init进程身上。为此，init进程创建子进程ueventd，并将创建设备节点文件的工作托付给ueventd。</p>

<p><strong>ueventd通过两种方式创建设备节点文件（冷启动和热启动），用来管理设备，如果有新设备插入，就会在/dev创建对应的设备文件。两种方式：</strong></p>
<ol>
  <li>第一种方式对应“冷插拔”（Cold Plug），即以预先定义的设备信息为基础，当ueventd启动后，统一创建设备节点文件。这一类设备节点文件也被称为静态节点文件</li>
  <li>第二种方式对应“热插拔”（Hot Plug），即在系统运行中，当有设备插入USB端口时，ueventd就会接收到这一事件，为插入的设备动态创建设备节点文件。这一类设备节点文件也被称为动态节点文件</li>
</ol>

<p>以下是代码梳理，不详细展开详细流程：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">ueventd_main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">//设置新建文件的默认值,这个与chmod相反,这里相当于新建文件后的权限为666</span>
    <span class="n">umask</span><span class="p">(</span><span class="mo">000</span><span class="p">);</span>

    <span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">InitLogging</span><span class="p">(</span><span class="n">argv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">KernelLogger</span><span class="p">);</span>

    <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"ueventd started!"</span><span class="p">;</span>

    <span class="n">SelinuxSetupKernelLogging</span><span class="p">();</span>
    <span class="n">SelabelInitialize</span><span class="p">();</span>

    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">UeventHandler</span><span class="o">&gt;&gt;</span> <span class="n">uevent_handlers</span><span class="p">;</span>

    <span class="c1">//获取硬件平台</span>
    <span class="k">auto</span> <span class="n">hardware</span> <span class="o">=</span> <span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">GetProperty</span><span class="p">(</span><span class="s">"ro.hardware"</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
    <span class="c1">//Step 1：加载解析四个相关的ueventd.rc文件</span>
    <span class="k">auto</span> <span class="n">ueventd_configuration</span> <span class="o">=</span> <span class="n">ParseConfig</span><span class="p">({</span><span class="s">"/ueventd.rc"</span><span class="p">,</span> <span class="s">"/vendor/ueventd.rc"</span><span class="p">,</span>
                                              <span class="s">"/odm/ueventd.rc"</span><span class="p">,</span> <span class="s">"/ueventd."</span> <span class="o">+</span> <span class="n">hardware</span> <span class="o">+</span> <span class="s">".rc"</span><span class="p">});</span>

    <span class="n">uevent_handlers</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">DeviceHandler</span><span class="o">&gt;</span><span class="p">(</span>
            <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">ueventd_configuration</span><span class="p">.</span><span class="n">dev_permissions</span><span class="p">),</span>
            <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">ueventd_configuration</span><span class="p">.</span><span class="n">sysfs_permissions</span><span class="p">),</span>
            <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">ueventd_configuration</span><span class="p">.</span><span class="n">subsystems</span><span class="p">),</span> <span class="n">android</span><span class="o">::</span><span class="n">fs_mgr</span><span class="o">::</span><span class="n">GetBootDevices</span><span class="p">(),</span> <span class="nb">true</span><span class="p">));</span>
    <span class="n">uevent_handlers</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">FirmwareHandler</span><span class="o">&gt;</span><span class="p">(</span>
            <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">ueventd_configuration</span><span class="p">.</span><span class="n">firmware_directories</span><span class="p">)));</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">ueventd_configuration</span><span class="p">.</span><span class="n">enable_modalias_handling</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">uevent_handlers</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">ModaliasHandler</span><span class="o">&gt;</span><span class="p">());</span>
    <span class="p">}</span>
    <span class="n">UeventListener</span> <span class="n">uevent_listener</span><span class="p">(</span><span class="n">ueventd_configuration</span><span class="p">.</span><span class="n">uevent_socket_rcvbuf_size</span><span class="p">);</span>
    <span class="c1">//Step 2：递归扫描/sys目录，根据uevent文件，静态创建设备节点</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="n">COLDBOOT_DONE</span><span class="p">,</span> <span class="n">F_OK</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">ColdBoot</span> <span class="n">cold_boot</span><span class="p">(</span><span class="n">uevent_listener</span><span class="p">,</span> <span class="n">uevent_handlers</span><span class="p">);</span>
        <span class="n">cold_boot</span><span class="p">.</span><span class="n">Run</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span><span class="o">&amp;</span> <span class="n">uevent_handler</span> <span class="o">:</span> <span class="n">uevent_handlers</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">uevent_handler</span><span class="o">-&gt;</span><span class="n">ColdbootDone</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="c1">//wangsl</span>
    <span class="n">setpriority</span><span class="p">(</span><span class="n">PRIO_PROCESS</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="c1">//wangsl</span>

    <span class="c1">// We use waitpid() in ColdBoot, so we can't ignore SIGCHLD until now.</span>
    <span class="n">signal</span><span class="p">(</span><span class="n">SIGCHLD</span><span class="p">,</span> <span class="n">SIG_IGN</span><span class="p">);</span>
    <span class="c1">// Reap and pending children that exited between the last call to waitpid() and setting SIG_IGN</span>
    <span class="c1">// for SIGCHLD above.</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">waitpid</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="n">WNOHANG</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">}</span>
    <span class="c1">//Step 3：获取内核uevent事件，动态创建设备节点</span>
    <span class="n">uevent_listener</span><span class="p">.</span><span class="n">Poll</span><span class="p">([</span><span class="o">&amp;</span><span class="n">uevent_handlers</span><span class="p">](</span><span class="k">const</span> <span class="n">Uevent</span><span class="o">&amp;</span> <span class="n">uevent</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="k">auto</span><span class="o">&amp;</span> <span class="n">uevent_handler</span> <span class="o">:</span> <span class="n">uevent_handlers</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">uevent_handler</span><span class="o">-&gt;</span><span class="n">HandleUevent</span><span class="p">(</span><span class="n">uevent</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">ListenerAction</span><span class="o">::</span><span class="n">kContinue</span><span class="p">;</span>
    <span class="p">});</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">ColdBoot</span><span class="o">::</span><span class="n">Run</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">Timer</span> <span class="n">cold_boot_timer</span><span class="p">;</span>
    <span class="c1">// 递归扫/sys目录，对uevent写入add，重新生成uevent</span>
    <span class="n">RegenerateUevents</span><span class="p">();</span>
    <span class="c1">// 对应生成子进程处理每一个uevent（最终调用mknod处理节点）</span>
    <span class="n">ForkSubProcesses</span><span class="p">();</span>
    <span class="c1">// 重新生成selinux context</span>
    <span class="n">DoRestoreCon</span><span class="p">();</span>
    <span class="c1">// 等待子进程处理完成</span>
    <span class="n">WaitForSubProcesses</span><span class="p">();</span>

    <span class="n">close</span><span class="p">(</span><span class="n">open</span><span class="p">(</span><span class="n">COLDBOOT_DONE</span><span class="p">,</span> <span class="n">O_WRONLY</span> <span class="o">|</span> <span class="n">O_CREAT</span> <span class="o">|</span> <span class="n">O_CLOEXEC</span><span class="p">,</span> <span class="mo">0000</span><span class="p">));</span>
    <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Coldboot took "</span> <span class="o">&lt;&lt;</span> <span class="n">cold_boot_timer</span><span class="p">.</span><span class="n">duration</span><span class="p">().</span><span class="n">count</span><span class="p">()</span> <span class="o">/</span> <span class="mf">1000.0</span><span class="n">f</span> <span class="o">&lt;&lt;</span> <span class="s">" seconds"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="33-on-init阶段">3.3. on init阶段</h2>

<p><strong>在init初始化阶段，主要工作有：</strong></p>
<ol>
  <li>底层一系列进程、cpu、blkio、runtime等的节点创建，并进行cgroup组和读写权限配置、赋予默认值</li>
  <li>启动logd服务</li>
  <li>启动servicemanager</li>
  <li>启动hwservicemanager</li>
  <li>启动vndservicemanager</li>
</ol>

<p><strong>PS</strong>：</p>
<ol>
  <li>这个阶段我们关注的就是以上四个服务的启动，其中主要的是logd和servicemanager</li>
  <li>我们可以通过抓取bootchart查看系统启动时进程的启动时许，从图中我们会看到启动最早的是init、ueventd、logd、servicemanager、hwservicemanager、vndservicemanager这些。</li>
</ol>

<hr />

<p><strong>截取主要代码分析：</strong></p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">on</span><span class="w"> </span><span class="n">init</span><span class="w">
    </span><span class="n">...</span><span class="w">
    </span><span class="c1"># 软链接一些节点和目录</span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">proc</span><span class="o">/</span><span class="n">self</span><span class="o">/</span><span class="n">fd</span><span class="o">/</span><span class="m">0</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stdin</span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">proc</span><span class="o">/</span><span class="n">self</span><span class="o">/</span><span class="n">fd</span><span class="o">/</span><span class="m">1</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stdout</span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">proc</span><span class="o">/</span><span class="n">self</span><span class="o">/</span><span class="n">fd</span><span class="o">/</span><span class="m">2</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stderr</span><span class="w">
    </span><span class="c1"># 将/system的bin和etc软链接到根目录</span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">system</span><span class="o">/</span><span class="n">bin</span><span class="w"> </span><span class="o">/</span><span class="n">bin</span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">system</span><span class="o">/</span><span class="n">etc</span><span class="w"> </span><span class="o">/</span><span class="n">etc</span><span class="w">
    </span><span class="c1"># Backward compatibility.</span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">kernel</span><span class="o">/</span><span class="n">debug</span><span class="w"> </span><span class="o">/</span><span class="n">d</span><span class="w">
    </span><span class="c1"># 将/vendor软链接到/system/vendor </span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">system</span><span class="o">/</span><span class="n">vendor</span><span class="w"> </span><span class="o">/</span><span class="n">vendor</span><span class="w">

    </span><span class="c1"># Create energy-aware scheduler tuning nodes</span><span class="w">
    </span><span class="c1"># 创建一些调度节点并赋权限</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stune</span><span class="o">/</span><span class="n">foreground</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stune</span><span class="o">/</span><span class="n">background</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stune</span><span class="o">/</span><span class="n">top</span><span class="o">-</span><span class="n">app</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stune</span><span class="o">/</span><span class="n">rt</span><span class="w">
    </span><span class="n">chown</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stune</span><span class="w">
    </span><span class="n">chown</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stune</span><span class="o">/</span><span class="n">foreground</span><span class="w">
    </span><span class="n">chmod</span><span class="w"> </span><span class="m">0664</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stune</span><span class="o">/</span><span class="n">tasks</span><span class="w">
    </span><span class="n">chmod</span><span class="w"> </span><span class="m">0664</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">stune</span><span class="o">/</span><span class="n">foreground</span><span class="o">/</span><span class="n">tasks</span><span class="w">
    </span><span class="n">......</span><span class="w">
    </span><span class="c1"># Create blkio group and apply initial settings.</span><span class="w">
    </span><span class="c1"># This feature needs kernel to support it, and the</span><span class="w">
    </span><span class="c1"># device's init.rc must actually set the correct values.</span><span class="w">
    </span><span class="c1"># Block IO Controller（块输入输出子系统）</span><span class="w">
    </span><span class="c1"># 由存储层级下一些各式各样的IO控制策略叶节点和中间节点组成</span><span class="w">
    </span><span class="c1"># 基于cgroup的管理结构，用于用户在后台切换IO策略</span><span class="w">
    </span><span class="c1"># 此处创建节点并赋予权限和默认值</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">blkio</span><span class="o">/</span><span class="n">background</span><span class="w">
    </span><span class="n">chown</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">blkio</span><span class="w">
    </span><span class="n">chown</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">blkio</span><span class="o">/</span><span class="n">background</span><span class="w">
    </span><span class="n">chown</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">blkio</span><span class="o">/</span><span class="n">tasks</span><span class="w">
    </span><span class="n">chown</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">blkio</span><span class="o">/</span><span class="n">background</span><span class="o">/</span><span class="n">tasks</span><span class="w">
    </span><span class="n">chmod</span><span class="w"> </span><span class="m">0664</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">blkio</span><span class="o">/</span><span class="n">tasks</span><span class="w">
    </span><span class="n">......</span><span class="w">
    </span><span class="c1"># mnt是一个挂载点目录，用于挂载外部存储设备或文件系统</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">mnt</span><span class="o">/</span><span class="n">secure</span><span class="w"> </span><span class="m">0700</span><span class="w"> </span><span class="n">root</span><span class="w"> </span><span class="n">root</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">mnt</span><span class="o">/</span><span class="n">secure</span><span class="o">/</span><span class="n">asec</span><span class="w"> </span><span class="m">0700</span><span class="w"> </span><span class="n">root</span><span class="w"> </span><span class="n">root</span><span class="w">
    </span><span class="n">......</span><span class="w">
    </span><span class="c1"># Storage views to support runtime permissions</span><span class="w">
    </span><span class="c1"># 创建运行态的节点和权限赋予</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">mnt</span><span class="o">/</span><span class="n">runtime</span><span class="w"> </span><span class="m">0700</span><span class="w"> </span><span class="n">root</span><span class="w"> </span><span class="n">root</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">mnt</span><span class="o">/</span><span class="n">runtime</span><span class="o">/</span><span class="n">default</span><span class="w"> </span><span class="m">0755</span><span class="w"> </span><span class="n">root</span><span class="w"> </span><span class="n">root</span><span class="w">
    </span><span class="n">mkdir</span><span class="w"> </span><span class="o">/</span><span class="n">mnt</span><span class="o">/</span><span class="n">runtime</span><span class="o">/</span><span class="n">default</span><span class="o">/</span><span class="n">self</span><span class="w"> </span><span class="m">0755</span><span class="w"> </span><span class="n">root</span><span class="w"> </span><span class="n">root</span><span class="w">
    </span><span class="n">......</span><span class="w">
    </span><span class="c1"># Symlink to keep legacy apps working in multi-user world</span><span class="w">
    </span><span class="c1"># 软链接保证APP在多用户环境持续运行</span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">storage</span><span class="o">/</span><span class="n">self</span><span class="o">/</span><span class="n">primary</span><span class="w"> </span><span class="o">/</span><span class="n">sdcard</span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">storage</span><span class="o">/</span><span class="n">self</span><span class="o">/</span><span class="n">primary</span><span class="w"> </span><span class="o">/</span><span class="n">mnt</span><span class="o">/</span><span class="n">sdcard</span><span class="w">
    </span><span class="n">symlink</span><span class="w"> </span><span class="o">/</span><span class="n">mnt</span><span class="o">/</span><span class="n">user</span><span class="o">/</span><span class="m">0</span><span class="o">/</span><span class="n">primary</span><span class="w"> </span><span class="o">/</span><span class="n">mnt</span><span class="o">/</span><span class="n">runtime</span><span class="o">/</span><span class="n">default</span><span class="o">/</span><span class="n">self</span><span class="o">/</span><span class="n">primary</span><span class="w">

    </span><span class="n">write</span><span class="w"> </span><span class="o">/</span><span class="n">proc</span><span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">kernel</span><span class="o">/</span><span class="n">panic_on_oops</span><span class="w"> </span><span class="m">1</span><span class="w">
    </span><span class="n">write</span><span class="w"> </span><span class="o">/</span><span class="n">proc</span><span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">kernel</span><span class="o">/</span><span class="n">hung_task_timeout_secs</span><span class="w"> </span><span class="m">0</span><span class="w">
    </span><span class="n">write</span><span class="w"> </span><span class="o">/</span><span class="n">proc</span><span class="o">/</span><span class="n">cpu</span><span class="o">/</span><span class="n">alignment</span><span class="w"> </span><span class="m">4</span><span class="w">

    </span><span class="c1"># 基本都是一些节点创建、权限赋予，不详细展开</span><span class="w">
    </span><span class="n">......</span><span class="w">

    </span><span class="c1"># Start logd before any other services run to ensure we capture all of their logs.</span><span class="w">
    </span><span class="c1"># 在其他服务之前启动，确保捕获所有服务的日志信息</span><span class="w">
    </span><span class="n">start</span><span class="w"> </span><span class="n">logd</span><span class="w">

    </span><span class="c1"># Start essential services.</span><span class="w">
    </span><span class="n">start</span><span class="w"> </span><span class="n">servicemanager</span><span class="w">
    </span><span class="n">start</span><span class="w"> </span><span class="n">hwservicemanager</span><span class="w">
    </span><span class="n">start</span><span class="w"> </span><span class="n">vndservicemanager</span><span class="w">
</span></code></pre></div></div>

<hr />

<h3 id="331-启动logd服务">3.3.1. 启动logd服务</h3>

<blockquote>
  <p>模块路径：system/core/logd</p>

  <p>logd守护进程是日志系统的管家，内部维持三个日志Socket: <code class="language-plaintext highlighter-rouge">logd、logdr、logdw</code>来与客户端进行通信。</p>

  <p>同时负责维护几个环形缓冲区，用于存放系统中的各种日志，缓冲区包含main、system、events、radio、crash、kernel</p>

  <p>在Android 5.0之前，logd进程并不存在，日志是保留在/dev/log/main、/dev/log/system、/dev/log/radio、/dev/log/event等节点中，但是这样面临的一个问题就是当Android系统大版本升级时，linux kernel需要升级对应的日志驱动，因此在后续的版本中就有了logd进程</p>
</blockquote>

<p>log系统大概分三个部分（不管是应用层，还是Native层，读写日志都是通过liblog提供的接口，访问logd的两个socket buffer：logdr、logdw来实现读写）：</p>
<ol>
  <li>上层接口。例如ALOGD、log.d、Slog.d等</li>
  <li>liblog</li>
  <li>logd</li>
</ol>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># system/core/logd/logd.rc </span><span class="w">
</span><span class="n">service</span><span class="w"> </span><span class="n">logd</span><span class="w"> </span><span class="o">/</span><span class="n">system</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">logd</span><span class="w">
    </span><span class="n">socket</span><span class="w"> </span><span class="n">logd</span><span class="w"> </span><span class="n">stream</span><span class="w"> </span><span class="m">0666</span><span class="w"> </span><span class="n">logd</span><span class="w"> </span><span class="n">logd</span><span class="w">
    </span><span class="n">socket</span><span class="w"> </span><span class="n">logdr</span><span class="w"> </span><span class="n">seqpacket</span><span class="w"> </span><span class="m">0666</span><span class="w"> </span><span class="n">logd</span><span class="w"> </span><span class="n">logd</span><span class="w">
    </span><span class="n">socket</span><span class="w"> </span><span class="n">logdw</span><span class="w"> </span><span class="n">dgram</span><span class="o">+</span><span class="n">passcred</span><span class="w"> </span><span class="m">0222</span><span class="w"> </span><span class="n">logd</span><span class="w"> </span><span class="n">logd</span><span class="w">
    </span><span class="n">file</span><span class="w"> </span><span class="o">/</span><span class="n">proc</span><span class="o">/</span><span class="n">kmsg</span><span class="w"> </span><span class="n">r</span><span class="w">
    </span><span class="n">file</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">kmsg</span><span class="w"> </span><span class="n">w</span><span class="w">
    </span><span class="n">user</span><span class="w"> </span><span class="n">logd</span><span class="w">
    </span><span class="n">group</span><span class="w"> </span><span class="n">logd</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">package_info</span><span class="w"> </span><span class="n">readproc</span><span class="w">
    </span><span class="n">capabilities</span><span class="w"> </span><span class="n">SYSLOG</span><span class="w"> </span><span class="n">AUDIT_CONTROL</span><span class="w"> </span><span class="n">SETGID</span><span class="w">
    </span><span class="n">writepid</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">cpuset</span><span class="o">/</span><span class="n">system</span><span class="o">-</span><span class="n">background</span><span class="o">/</span><span class="n">tasks</span><span class="w">
</span></code></pre></div></div>

<p>在设备系统中可以看到三个节点：</p>

<pre><code class="language-shellell">/dev/socket # ls -al
total 0
drwxr-xr-x  3 root        root         460 2021-12-12 00:00 .
drwxr-xr-x 20 root        root        3840 1970-01-01 03:00 ..
srw-rw-rw-  1 logd        logd           0 1970-01-01 03:00 logd
srw-rw-rw-  1 logd        logd           0 1970-01-01 03:00 logdr
s-w--w--w-  1 logd        logd           0 1970-01-01 03:00 logdw
</code></pre>

<hr />

<h3 id="332-启动servicemanager">3.3.2. 启动servicemanager</h3>

<blockquote>
  <p>模块路径：frameworks/native/cmds/servicemanager</p>

  <p>servicemanager守护进程负责管理系统服务，允许应用程序和系统组件将自己注册为服务</p>
</blockquote>

<p>RC文件看到，该服务会一直运行，正常不会退出，否则服务重启会导致一系列其他服务重启，比如zygote、surfaceflinger这些核心服务。</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># frameworks/native/cmds/servicemanager/servicemanager.rc</span><span class="w">
</span><span class="n">service</span><span class="w"> </span><span class="n">servicemanager</span><span class="w"> </span><span class="o">/</span><span class="n">system</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">servicemanager</span><span class="w">
    </span><span class="n">class</span><span class="w"> </span><span class="n">core</span><span class="w"> </span><span class="n">animation</span><span class="w">
    </span><span class="n">user</span><span class="w"> </span><span class="n">system</span><span class="w">
    </span><span class="n">group</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">readproc</span><span class="w">
    </span><span class="n">critical</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">healthd</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">zygote</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">audioserver</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">media</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">surfaceflinger</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">inputflinger</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">drm</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">cameraserver</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">keystore</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">gatekeeperd</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">restart</span><span class="w"> </span><span class="n">thermalservice</span><span class="w">
    </span><span class="n">writepid</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">cpuset</span><span class="o">/</span><span class="n">system</span><span class="o">-</span><span class="n">background</span><span class="o">/</span><span class="n">tasks</span><span class="w">
    </span><span class="n">shutdown</span><span class="w"> </span><span class="n">critical</span><span class="w">
</span></code></pre></div></div>

<p><strong>main函数中主要是三个关键函数：</strong></p>
<ol>
  <li>binder_open</li>
  <li>binder_become_context_manager</li>
  <li>binder_loop</li>
</ol>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//frameworks/native/cmds/servicemanager/service_manager.c </span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="nc">binder_state</span> <span class="o">*</span><span class="n">bs</span><span class="p">;</span>
    <span class="k">union</span> <span class="n">selinux_callback</span> <span class="n">cb</span><span class="p">;</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">driver</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">driver</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="c1">//此时要使用的binder驱动为 "/dev/binder"，同样这个路径也是android设备上的路径</span>
        <span class="n">driver</span> <span class="o">=</span> <span class="s">"/dev/binder"</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//Step 1： 打开底层binder驱动节点，并映射内存块大小为128KB</span>
    <span class="n">bs</span> <span class="o">=</span> <span class="n">binder_open</span><span class="p">(</span><span class="n">driver</span><span class="p">,</span> <span class="mi">128</span><span class="o">*</span><span class="mi">1024</span><span class="p">);</span>
    <span class="p">....</span>
    <span class="c1">//Step 2： 将将自己注册成Binder进程的上下文，设置为 Binder “DNS” 管理者</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">binder_become_context_manager</span><span class="p">(</span><span class="n">bs</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">ALOGE</span><span class="p">(</span><span class="s">"cannot become context manager (%s)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
        <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="p">....</span>
    <span class="c1">//Step 3： 进入循环，等待binder驱动发来消息，传入svcmgr_handler方法作为回调函数</span>
    <span class="n">binder_loop</span><span class="p">(</span><span class="n">bs</span><span class="p">,</span> <span class="n">svcmgr_handler</span><span class="p">);</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>svcmgr_handler方法会接受不同的消息执行不同的处理，其中主要的是两点：</p>
<ol>
  <li>SVC_MGR_GET_SERVICE和SVC_MGR_CHECK_SERVICE：收到消息，调用do_find_service找到一个Binder服务</li>
  <li>SVC_MGR_ADD_SERVICE：收到消息，调用do_add_service添加一个Binder服务</li>
</ol>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">svcmgr_handler</span><span class="p">(</span><span class="k">struct</span> <span class="nc">binder_state</span> <span class="o">*</span><span class="n">bs</span><span class="p">,</span>
                   <span class="k">struct</span> <span class="nc">binder_transaction_data_secctx</span> <span class="o">*</span><span class="n">txn_secctx</span><span class="p">,</span>
                   <span class="k">struct</span> <span class="nc">binder_io</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span>
                   <span class="k">struct</span> <span class="nc">binder_io</span> <span class="o">*</span><span class="n">reply</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="nc">svcinfo</span> <span class="o">*</span><span class="n">si</span><span class="p">;</span>
    <span class="kt">uint16_t</span> <span class="o">*</span><span class="n">s</span><span class="p">;</span>
    <span class="kt">size_t</span> <span class="n">len</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">handle</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">strict_policy</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">allow_isolated</span><span class="p">;</span>
    <span class="kt">uint32_t</span> <span class="n">dumpsys_priority</span><span class="p">;</span>

    <span class="k">struct</span> <span class="nc">binder_transaction_data</span> <span class="o">*</span><span class="n">txn</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">txn_secctx</span><span class="o">-&gt;</span><span class="n">transaction_data</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">txn</span><span class="o">-&gt;</span><span class="n">target</span><span class="p">.</span><span class="n">ptr</span> <span class="o">!=</span> <span class="n">BINDER_SERVICE_MANAGER</span><span class="p">)</span>
        <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">txn</span><span class="o">-&gt;</span><span class="n">code</span> <span class="o">==</span> <span class="n">PING_TRANSACTION</span><span class="p">)</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">.....</span>
    <span class="k">switch</span><span class="p">(</span><span class="n">txn</span><span class="o">-&gt;</span><span class="n">code</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">SVC_MGR_GET_SERVICE</span><span class="p">:</span>
    <span class="k">case</span> <span class="n">SVC_MGR_CHECK_SERVICE</span><span class="p">:</span>
        <span class="n">s</span> <span class="o">=</span> <span class="n">bio_get_string16</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">len</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">s</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="c1">//根据UID、PIDP查询服务</span>
        <span class="n">handle</span> <span class="o">=</span> <span class="n">do_find_service</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">len</span><span class="p">,</span> <span class="n">txn</span><span class="o">-&gt;</span><span class="n">sender_euid</span><span class="p">,</span> <span class="n">txn</span><span class="o">-&gt;</span><span class="n">sender_pid</span><span class="p">,</span>
                                 <span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">)</span> <span class="n">txn_secctx</span><span class="o">-&gt;</span><span class="n">secctx</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">handle</span><span class="p">)</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="n">bio_put_ref</span><span class="p">(</span><span class="n">reply</span><span class="p">,</span> <span class="n">handle</span><span class="p">);</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">case</span> <span class="n">SVC_MGR_ADD_SERVICE</span><span class="p">:</span>
        <span class="n">s</span> <span class="o">=</span> <span class="n">bio_get_string16</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">len</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">s</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">handle</span> <span class="o">=</span> <span class="n">bio_get_ref</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
        <span class="n">allow_isolated</span> <span class="o">=</span> <span class="n">bio_get_uint32</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">dumpsys_priority</span> <span class="o">=</span> <span class="n">bio_get_uint32</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
        <span class="c1">//添加服务</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">do_add_service</span><span class="p">(</span><span class="n">bs</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="n">len</span><span class="p">,</span> <span class="n">handle</span><span class="p">,</span> <span class="n">txn</span><span class="o">-&gt;</span><span class="n">sender_euid</span><span class="p">,</span> <span class="n">allow_isolated</span><span class="p">,</span> <span class="n">dumpsys_priority</span><span class="p">,</span>
                           <span class="n">txn</span><span class="o">-&gt;</span><span class="n">sender_pid</span><span class="p">,</span> <span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">)</span> <span class="n">txn_secctx</span><span class="o">-&gt;</span><span class="n">secctx</span><span class="p">))</span>
            <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
        <span class="k">break</span><span class="p">;</span>
    <span class="p">.....</span>
    <span class="nl">default:</span>
        <span class="n">ALOGE</span><span class="p">(</span><span class="s">"unknown code %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">txn</span><span class="o">-&gt;</span><span class="n">code</span><span class="p">);</span>
        <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">bio_put_uint32</span><span class="p">(</span><span class="n">reply</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="333-启动hwservicemanager">3.3.3. 启动hwservicemanager</h3>

<blockquote>
  <p>非HAL进程之间使用binder通信，servicemanager进程会作为它们之间的服务管理者，servicemanager进程在打开binder驱动时使用/dev/binder</p>

  <p>非HAL进程和HAL进程之间使用hwbinder通信（Hardware Binder），hwservicemanager作为它们之间的服务管理者，hwservicemanager进程在打开binder驱动时使用/dev/hwbinder。
wServiceManager是HAL服务管理中心，负责管理系统中的所有HAL服务，由init进程启动。
HwServiceManager 的主要工作就是收集各个硬件服务，当有进程需要服务时由HwServiceManager提供特定的硬件服务</p>
</blockquote>

<p>RC文件启动服务：</p>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># system/hwservicemanager/hwservicemanager.rc</span><span class="w">
</span><span class="n">service</span><span class="w"> </span><span class="n">hwservicemanager</span><span class="w"> </span><span class="o">/</span><span class="n">system</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">hwservicemanager</span><span class="w">
    </span><span class="n">user</span><span class="w"> </span><span class="n">system</span><span class="w">
    </span><span class="n">disabled</span><span class="w">
    </span><span class="n">group</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">readproc</span><span class="w">
    </span><span class="n">critical</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">setprop</span><span class="w"> </span><span class="n">hwservicemanager.ready</span><span class="w"> </span><span class="n">false</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">class_restart</span><span class="w"> </span><span class="n">main</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">class_restart</span><span class="w"> </span><span class="n">hal</span><span class="w">
    </span><span class="n">onrestart</span><span class="w"> </span><span class="n">class_restart</span><span class="w"> </span><span class="n">early_hal</span><span class="w">
    </span><span class="n">writepid</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">cpuset</span><span class="o">/</span><span class="n">system</span><span class="o">-</span><span class="n">background</span><span class="o">/</span><span class="n">tasks</span><span class="w">
    </span><span class="n">class</span><span class="w"> </span><span class="n">animation</span><span class="w">
    </span><span class="n">shutdown</span><span class="w"> </span><span class="n">critical</span><span class="w">
</span></code></pre></div></div>

<hr />

<h4 id="3331-服务启动流程梳理">3.3.3.1. 服务启动流程梳理</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/hwservicemanager/service.cpp</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// If hwservicemanager crashes, the system may be unstable and hard to debug. This is both why</span>
    <span class="c1">// we log this and why we care about this at all.</span>
    <span class="n">setProcessHidlReturnRestriction</span><span class="p">(</span><span class="n">HidlReturnRestriction</span><span class="o">::</span><span class="n">ERROR_IF_UNCHECKED</span><span class="p">);</span>
    <span class="c1">//Step 1： 创建ServiceManager对象，继承自IServiceManager，采用sp强指针来进行内存自动释放</span>
    <span class="n">sp</span><span class="o">&lt;</span><span class="n">ServiceManager</span><span class="o">&gt;</span> <span class="n">manager</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ServiceManager</span><span class="p">();</span>
    <span class="n">setRequestingSid</span><span class="p">(</span><span class="n">manager</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
    <span class="c1">//Step 1.1: 将ServiceManager对象自身注册到mServiceMap表中</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">manager</span><span class="o">-&gt;</span><span class="n">add</span><span class="p">(</span><span class="n">serviceName</span><span class="p">,</span> <span class="n">manager</span><span class="p">).</span><span class="n">withDefault</span><span class="p">(</span><span class="nb">false</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">ALOGE</span><span class="p">(</span><span class="s">"Failed to register hwservicemanager with itself."</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">//Step 2: 创建TokenManager对象</span>
    <span class="n">sp</span><span class="o">&lt;</span><span class="n">TokenManager</span><span class="o">&gt;</span> <span class="n">tokenManager</span> <span class="o">=</span> <span class="k">new</span> <span class="n">TokenManager</span><span class="p">();</span>
    <span class="c1">//Step 2.1: 将TokenManager对象自身注册到mServiceMap表中</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">manager</span><span class="o">-&gt;</span><span class="n">add</span><span class="p">(</span><span class="n">serviceName</span><span class="p">,</span> <span class="n">tokenManager</span><span class="p">).</span><span class="n">withDefault</span><span class="p">(</span><span class="nb">false</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">ALOGE</span><span class="p">(</span><span class="s">"Failed to register ITokenManager with hwservicemanager."</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Tell IPCThreadState we're the service manager</span>
    <span class="c1">//Step 3: 通知IPCThreadState，我们是ServiceManager</span>
    <span class="c1">//根据ServiceManager找到为Binder对象，得到的Binder对象为BnHwServiceManager</span>
    <span class="n">sp</span><span class="o">&lt;</span><span class="n">IBinder</span><span class="o">&gt;</span> <span class="n">binder</span> <span class="o">=</span> <span class="n">toBinder</span><span class="o">&lt;</span><span class="n">IServiceManager</span><span class="o">&gt;</span><span class="p">(</span><span class="n">manager</span><span class="p">);</span>
    <span class="c1">//把binder对象BnHwServiceManager转换为BHwBinder 本地binder对象</span>
    <span class="n">sp</span><span class="o">&lt;</span><span class="n">BHwBinder</span><span class="o">&gt;</span> <span class="n">service</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">BHwBinder</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">binder</span><span class="p">.</span><span class="n">get</span><span class="p">());</span> <span class="c1">// local binder object</span>
    <span class="c1">//把服务对象传给IPCThreadState,作为Context Object</span>
    <span class="n">IPCThreadState</span><span class="o">::</span><span class="n">self</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">setTheContextObject</span><span class="p">(</span><span class="n">service</span><span class="p">);</span>
    <span class="c1">// Then tell the kernel</span>
    <span class="c1">//Step 4: 通知 binder 驱动，这个进程是服务管理器进程</span>
    <span class="n">ProcessState</span><span class="o">::</span><span class="n">self</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">becomeContextManager</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">);</span>
    <span class="c1">//设置属性：hwservicemanager.ready为true</span>
    <span class="kt">int</span> <span class="n">rc</span> <span class="o">=</span> <span class="n">property_set</span><span class="p">(</span><span class="s">"hwservicemanager.ready"</span><span class="p">,</span> <span class="s">"true"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">rc</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">ALOGE</span><span class="p">(</span><span class="s">"Failed to set </span><span class="se">\"</span><span class="s">hwservicemanager.ready</span><span class="se">\"</span><span class="s"> (error %d). "</span>\
              <span class="s">"HAL services will not start!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">rc</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">//Step 5: 其他进程通过hwservicemanager.ready属性得知HwServiceManager进程是否准备完毕</span>
    <span class="n">sp</span><span class="o">&lt;</span><span class="n">Looper</span><span class="o">&gt;</span> <span class="n">looper</span> <span class="o">=</span> <span class="n">Looper</span><span class="o">::</span><span class="n">prepare</span><span class="p">(</span><span class="mi">0</span> <span class="cm">/* opts */</span><span class="p">);</span>
    <span class="c1">//Step 6: 把HwBinderCallback作为回调，注册进入Lopper,用来做后续的lopper处理</span>
    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">HwBinderCallback</span><span class="o">::</span><span class="n">setupTo</span><span class="p">(</span><span class="n">looper</span><span class="p">);</span>
    <span class="c1">//Step 7: 把ClientCallbackCallback作为回调，注册进入Lopper，其中创建了一个定时器对象，5秒跑一次</span>
    <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">ClientCallbackCallback</span><span class="o">::</span><span class="n">setupTo</span><span class="p">(</span><span class="n">looper</span><span class="p">,</span> <span class="n">manager</span><span class="p">);</span>

    <span class="n">ALOGI</span><span class="p">(</span><span class="s">"hwservicemanager is ready now."</span><span class="p">);</span>
    <span class="c1">//Step 8: 进入死循环，调用 epoll_wait()等待消息</span>
    <span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">looper</span><span class="o">-&gt;</span><span class="n">pollAll</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="cm">/* timeoutMillis */</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<p><img src="../../assets/post/2025/2025-03-01-android_android系统启动流程_2_initrc文件启动流程/hwservicemanager调用栈.png" alt="" /></p>

<hr />

<h4 id="3332-hwbinder通信原理">3.3.3.2. HWBinder通信原理</h4>

<p><img src="../../assets/post/2025/2025-03-01-android_android系统启动流程_2_initrc文件启动流程/HWbinder通信原理.jpeg" alt="" /></p>

<hr />

<h4 id="3333-hwbinder架构">3.3.3.3. HWBinder架构</h4>

<p><img src="../../assets/post/2025/2025-03-01-android_android系统启动流程_2_initrc文件启动流程/HWBinder架构.jpeg" alt="" /></p>

<hr />

<h4 id="3334-序列图">3.3.3.4. 序列图</h4>

<p><img src="../../assets/post/2025/2025-03-01-android_android系统启动流程_2_initrc文件启动流程/hwservicemanager启动序列图.jpeg" alt="" /></p>

<hr />

<h3 id="334-启动vndservicemanager">3.3.4. 启动vndservicemanager</h3>

<blockquote>
  <p>该服务同servicemanager，只是binder节点是/dev/vndbinder</p>

  <p>vndservicemanager是servicemanager的一个扩展，用于管理供应商特定的服务。通常是供应商（vendor）特定的、定制化的服务，例如硬件厂商提供的驱动程序或服务。</p>
</blockquote>

<div class="language-r highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># frameworks/native/cmds/servicemanager/vndservicemanager.rc</span><span class="w">
</span><span class="n">service</span><span class="w"> </span><span class="n">vndservicemanager</span><span class="w"> </span><span class="o">/</span><span class="n">vendor</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">vndservicemanager</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">vndbinder</span><span class="w">
    </span><span class="n">class</span><span class="w"> </span><span class="n">core</span><span class="w">
    </span><span class="n">user</span><span class="w"> </span><span class="n">system</span><span class="w">
    </span><span class="n">group</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">readproc</span><span class="w">
    </span><span class="n">writepid</span><span class="w"> </span><span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">cpuset</span><span class="o">/</span><span class="n">system</span><span class="o">-</span><span class="n">background</span><span class="o">/</span><span class="n">tasks</span><span class="w">
    </span><span class="n">shutdown</span><span class="w"> </span><span class="n">critical</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="34-on-late-init阶段">3.4. on late-init阶段</h2>

<blockquote>
  <p>这个阶段的内容比较多，而且还通过trigger触发器先后划分了几个不同阶段的顺序</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Mount filesystems and start core system services.</span>
on late-init
    <span class="c"># 启动vold服务</span>
    trigger early-fs

    <span class="c"># Mount fstab in init.{$device}.rc by mount_all command. Optional parameter</span>
    <span class="c"># '--early' can be specified to skip entries with 'latemount'.</span>
    <span class="c"># /system and /vendor must be mounted by the end of the fs stage,</span>
    <span class="c"># while /data is optional.</span>
    trigger fs
    trigger post-fs

    <span class="c"># Mount fstab in init.{$device}.rc by mount_all with '--late' parameter</span>
    <span class="c"># to only mount entries with 'latemount'. This is needed if '--early' is</span>
    <span class="c"># specified in the previous mount_all command on the fs stage.</span>
    <span class="c"># With /system mounted and properties form /system + /factory available,</span>
    <span class="c"># some services can be started.</span>
    trigger late-fs

    <span class="c"># Now we can mount /data. File encryption requires keymaster to decrypt</span>
    <span class="c"># /data, which in turn can only be loaded when system properties are present.</span>
    trigger post-fs-data

    <span class="c"># Load persist properties and override properties (if enabled) from /data.</span>
    trigger load_persist_props_action

    <span class="c"># Now we can start zygote for devices with file based encryption</span>
    trigger zygote-start

    <span class="c"># Remove a file to wake up anything waiting for firmware.</span>
    trigger firmware_mounts_complete

    trigger early-boot
    trigger boot
</code></pre></div></div>

<p><strong>PS：</strong>在这里面提到一个”–early”和”–late”，是表示可以在RC中使用”mount_all /vendor/etc/fstab.${ro.hardware} –early”的方式挂载某些分区，体现一个时序先后，实际暂未使用过。</p>

<hr />

<h3 id="341-on-early-fs阶段vold">3.4.1. on early-fs阶段（vold）</h3>

<blockquote>
  <p>该阶段的唯一工作就是启动vold服务<code class="language-plaintext highlighter-rouge">system/vold/vold.rc</code>，vold（volume deamon）主要负责管理和控制Android平台外部存储设备，包括SD插拨、挂载、卸载、格式化等。</p>
</blockquote>

<p>在main方法里,主要做以下几件事情：</p>
<ol>
  <li>初始化VolumeManager ,CommandListener ,NetlinkManager 三个类的实例；</li>
  <li>给VolumeManager 和NetlinkManager 设置CommandListener 实例,用作后续监听两个Socket,用得是设计模式中的Command(命令)模式；</li>
  <li>启动VolumeManager ,CommandListener ,NetlinkManager；</li>
  <li>解析Vold的配置文件fstab。如果vold.fstab解析无误，VolueManager将创建具体的Volume，若vold.fstab解析不存在或者打开失败，Vold将会读取Linux内核中的参数，此时如果参数中存在SDCARD(也就是SD的默认路径)，VolumeManager则会创建AutoVolume，如果不存在这个默认路径那么就不会创建</li>
  <li>做一次冷启动</li>
</ol>

<h4 id="3411-vold启动时序图">3.4.1.1. vold启动时序图</h4>

<p><img src="../../assets/post/2025/2025-03-01-android_android系统启动流程_2_initrc文件启动流程/vold启动时序图.png" alt="" /></p>

<hr />

<h3 id="342-on-fs阶段">3.4.2. on fs阶段</h3>

<blockquote>
  <p>Android原生这阶段RC文件中未做实际的动作，仅在<code class="language-plaintext highlighter-rouge">system/core/logd/logd.rc</code>中有一段，是给<code class="language-plaintext highlighter-rouge">/dev/event-log-tags</code>赋予权限。
从Google的注释看在这阶段结束后system和vendor分区会被挂载，但是data分区未完成。这段的实现在<code class="language-plaintext highlighter-rouge">system/core/init/builtins.cpp —— do_mount_all</code>代码中。</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#system/core/logd/logd.rc</span>
on fs
    write /dev/event-log-tags <span class="s2">"# content owned by logd
"</span>
    <span class="nb">chown </span>logd logd /dev/event-log-tags
    <span class="nb">chmod </span>0644 /dev/event-log-tags
</code></pre></div></div>

<hr />

<h3 id="343-post-fs阶段">3.4.3. post-fs阶段</h3>

<blockquote>
  <p>该阶段可以对system、vendor等一些分区进行操作（data还未完全可以），例如以下init.rc中是做了一些创建、挂载等操作</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># system/core/rootdir/init.rc </span>
on post-fs
    <span class="nb">exec</span> - system system <span class="nt">--</span> /system/bin/vdc checkpoint markBootAttempt

    <span class="c"># Once everything is setup, no need to modify /.</span>
    <span class="c"># The bind+remount combination allows this to work in containers.</span>
    mount rootfs rootfs / remount <span class="nb">bind </span>ro nodev
    <span class="c"># Mount default storage into root namespace</span>
    mount none /mnt/runtime/default /storage <span class="nb">bind </span>rec
    mount none none /storage slave rec

    <span class="c"># Make sure /sys/kernel/debug (if present) is labeled properly</span>
    <span class="c"># Note that tracefs may be mounted under debug, so we need to cross filesystems</span>
    restorecon <span class="nt">--recursive</span> <span class="nt">--cross-filesystems</span> /sys/kernel/debug

    <span class="c"># We chown/chmod /cache again so because mount is run as root + defaults</span>
    <span class="nb">chown </span>system cache /cache
    <span class="nb">chmod </span>0770 /cache
    <span class="c"># We restorecon /cache in case the cache partition has been reset.</span>
    restorecon_recursive /cache

    <span class="c"># Create /cache/recovery in case it's not there. It'll also fix the odd</span>
    <span class="c"># permissions if created by the recovery system.</span>
    <span class="nb">mkdir</span> /cache/recovery 0770 system cache

    <span class="c"># Backup/restore mechanism uses the cache partition</span>
    <span class="nb">mkdir</span> /cache/backup_stage 0700 system system
    <span class="nb">mkdir</span> /cache/backup 0700 system system

    <span class="c">#change permissions on vmallocinfo so we can grab it from bugreports</span>
    <span class="nb">chown </span>root log /proc/vmallocinfo
    <span class="nb">chmod </span>0440 /proc/vmallocinfo

    <span class="nb">chown </span>root log /proc/slabinfo
    <span class="nb">chmod </span>0440 /proc/slabinfo

    <span class="c">#change permissions on kmsg &amp; sysrq-trigger so bugreports can grab kthread stacks</span>
    <span class="nb">chown </span>root system /proc/kmsg
    <span class="nb">chmod </span>0440 /proc/kmsg
    <span class="nb">chown </span>root system /proc/sysrq-trigger
    <span class="nb">chmod </span>0220 /proc/sysrq-trigger
    <span class="nb">chown </span>system log /proc/last_kmsg
    <span class="nb">chmod </span>0440 /proc/last_kmsg

    <span class="c"># make the selinux kernel policy world-readable</span>
    <span class="nb">chmod </span>0444 /sys/fs/selinux/policy

    <span class="c"># create the lost+found directories, so as to enforce our permissions</span>
    <span class="nb">mkdir</span> /cache/lost+found 0770 root root

    restorecon_recursive /metadata
    <span class="nb">mkdir</span> /metadata/vold
    <span class="nb">chmod </span>0700 /metadata/vold
    <span class="nb">mkdir</span> /metadata/password_slots 0771 root system

    <span class="nb">mkdir</span> /metadata/apex 0700 root system
    <span class="nb">mkdir</span> /metadata/apex/sessions 0700 root system
</code></pre></div></div>

<hr />

<h3 id="344-on-late-fs阶段">3.4.4. on late-fs阶段</h3>

<blockquote>
  <p>这里主要是启动rc文件中class标注的是”early_hal”的HAL服务。</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># system/core/rootdir/init.rc </span>
on late-fs
    <span class="c"># Ensure that tracefs has the correct permissions.</span>
    <span class="c"># This does not work correctly if it is called in post-fs.</span>
    <span class="nb">chmod </span>0755 /sys/kernel/debug/tracing

    <span class="c"># HALs required before storage encryption can get unlocked (FBE/FDE)</span>
    class_start early_hal

<span class="c"># hardware/interfaces/keymaster/3.0/default/android.hardware.keymaster@3.0-service.rc</span>
service vendor.keymaster-3-0 /vendor/bin/hw/android.hardware.keymaster@3.0-service
    class early_hal
    user nobody
    group drmrpc
</code></pre></div></div>

<hr />

<h3 id="345-on-post-fs-data阶段">3.4.5. on post-fs-data阶段</h3>

<blockquote>
  <p>此阶段主要是data分区的配置，此外我们客制化的分区也可以在此阶段进行权限赋予、创建文件等操作。</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># system/core/rootdir/init.rc </span>
on post-fs-data
    mark_post_data

    <span class="c"># Start checkpoint before we touch data</span>
    start vold
    <span class="nb">exec</span> - system system <span class="nt">--</span> /system/bin/vdc checkpoint prepareCheckpoint

    <span class="c"># We chown/chmod /data again so because mount is run as root + defaults</span>
    <span class="nb">chown </span>system system /data
    <span class="nb">chmod </span>0771 /data

    <span class="c"># We restorecon /data in case the userdata partition has been reset.</span>
    restorecon /data

    <span class="c"># Make sure we have the device encryption key.</span>
    installkey /data

    <span class="c"># Start bootcharting as soon as possible after the data partition is</span>
    <span class="c"># mounted to collect more data.</span>
    <span class="nb">mkdir</span> /data/bootchart 0755 shell shell
    bootchart start

    <span class="c"># Load fsverity keys. This needs to happen before apexd, as post-install of</span>
    <span class="c"># APEXes may rely on keys.</span>
    <span class="nb">exec</span> <span class="nt">--</span> /system/bin/fsverity_init

    <span class="c"># Make sure that apexd is started in the default namespace</span>
    enter_default_mount_ns

    <span class="c"># /data/apex is now available. Start apexd to scan and activate APEXes.</span>
    <span class="nb">mkdir</span> /data/apex 0750 root system
    <span class="nb">mkdir</span> /data/apex/active 0750 root system
    <span class="nb">mkdir</span> /data/apex/backup 0700 root system
    <span class="nb">mkdir</span> /data/apex/sessions 0700 root system
    <span class="nb">mkdir</span> /data/app-staging 0750 system system
    start apexd

    <span class="c"># Avoid predictable entropy pool. Carry over entropy from previous boot.</span>
    copy /data/system/entropy.dat /dev/urandom

    <span class="c"># create basic filesystem structure</span>
    <span class="nb">mkdir</span> /data/misc 01771 system misc
    <span class="nb">mkdir</span> /data/misc/recovery 0770 system log
    copy /data/misc/recovery/ro.build.fingerprint /data/misc/recovery/ro.build.fingerprint.1
    ......
</code></pre></div></div>

<hr />

<h3 id="346-on-load_persist_props_action阶段">3.4.6. on load_persist_props_action阶段</h3>

<blockquote>
  <p>未做实际操作，暂不关注</p>
</blockquote>

<hr />

<h3 id="347-on-zygote-start阶段">3.4.7. on zygote-start阶段</h3>

<blockquote>
  <p>此阶段主要是启动netd，zygote，zygote_secondary服务</p>

  <p>PS：ro.crypto.state属性值参考<a href="https://blog.csdn.net/shift_wwx/article/details/78550380">Android加密之全盘加密</a>， 在车载项目未使用到，一般值是unsupported</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># system/core/rootdir/init.rc </span>
<span class="c"># ro.crypto.state属性值是在文件挂载的时候赋值（Android设置是否加密）</span>
<span class="c"># It is recommended to put unnecessary data/ initialization from post-fs-data</span>
<span class="c"># to start-zygote in device's init.rc to unblock zygote start.</span>
on zygote-start <span class="o">&amp;&amp;</span> property:ro.crypto.state<span class="o">=</span>unencrypted
    <span class="c"># A/B update verifier that marks a successful boot.</span>
    exec_start update_verifier_nonencrypted
    start netd
    start zygote
    start zygote_secondary

on zygote-start <span class="o">&amp;&amp;</span> property:ro.crypto.state<span class="o">=</span>unsupported
    <span class="c"># A/B update verifier that marks a successful boot.</span>
    exec_start update_verifier_nonencrypted
    start netd
    start zygote
    start zygote_secondary

on zygote-start <span class="o">&amp;&amp;</span> property:ro.crypto.state<span class="o">=</span>encrypted <span class="o">&amp;&amp;</span> property:ro.crypto.type<span class="o">=</span>file
    <span class="c"># A/B update verifier that marks a successful boot.</span>
    exec_start update_verifier_nonencrypted
    start netd
    start zygote
    start zygote_secondary

<span class="c"># 非加密情况会启动class标注两个main和late_start的服务</span>
on nonencrypted
    class_start main
    class_start late_start
</code></pre></div></div>

<hr />

<h4 id="3471-netd服务">3.4.7.1. netd服务</h4>

<blockquote>
  <p>‌Netd（Network Daemon）是Android系统中负责网络管理和控制的后台daemon程序‌。其主要功能包括设置防火墙、网络地址转换（NAT）、带宽控制、无线网卡软接入点控制、网络设备绑定、DNS信息缓存和管理以及网络服务搜索（NSD）等功能‌</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># system/netd/server/netd.rc </span>
service netd /system/bin/netd
    class main
    socket dnsproxyd stream 0660 root inet
    socket mdns stream 0660 root system
    socket fwmarkd stream 0660 root inet
    onrestart restart zygote
    onrestart restart zygote_secondary
    <span class="c"># b/121354779: netd itself is not updatable, but on startup it dlopen()s the resolver library</span>
    <span class="c"># from the DNS resolver APEX. Mark it as updatable so init won't start it until all APEX</span>
    <span class="c"># packages are ready.</span>
    updatable
</code></pre></div></div>

<hr />

<h4 id="3472-zygote和zygote_secondary服务">3.4.7.2. zygote和zygote_secondary服务</h4>

<blockquote>
  <p>在init.rc中会导入zygote的rc文件<code class="language-plaintext highlighter-rouge">import /init.${ro.zygote}.rc</code>，根据ro.zygote属性值，比如Android 10上值是zygote64_32，即启动<code class="language-plaintext highlighter-rouge">system/core/rootdir/init.zygote64_32.rc</code>（主要64位，次要32位）</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ps <span class="nt">-A</span>|grep zygote
root          2483     1 3177712 137916 0                   0 S zygote64
root          2484     1 1610124  93784 0                   0 S zygote
</code></pre></div></div>

<p>启动两个zygote服务：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ###### system/core/rootdir/init.zygote64_32.rc</span>
<span class="c"># zygote：service的名称，注意这里并非是进程名称，而是init进程内部标识service的name</span>
<span class="c"># /system/bin/app_process6：可执行文件的路径</span>
<span class="c"># -Xzygote：是传递给虚拟机的参数，告诉虚拟机是否已zygote模式启动。这里实际影响的是虚拟机启动时是否加载boot.art内存映射文件，即预加载常用的系统类</span>
<span class="c"># --zygote：app_process64内部有两种启动模式，--zygote指定启动类为com.android.internal.os.ZygoteInit。另一种则启动com.android.internal.os.RuntimeInit。</span>
<span class="c"># --start-system-server：指明启动system server</span>
<span class="c"># --socket-name：指定作为客户端要连接的服务端zygote名称。首先zygote service在启动时，先根据下面option的声明`socket zygote stream 660 root system`创建一个服务端的socket，名称为zygote。随后再启动作为zygote的app_process64进程。启动进程的过程中，会根据--socket-name参数指定的名称，创建socket客户端去连接服务端。zygote是app进程的孵化器，被孵化的子进程将继承这个客户端的socket，与服务端通信。</span>
service zygote /system/bin/app_process64 <span class="nt">-Xzygote</span> /system/bin <span class="nt">--zygote</span> <span class="nt">--start-system-server</span> <span class="nt">--socket-name</span><span class="o">=</span>zygote
    <span class="c"># // 指定class为main。可通过class_start main来启动所有class为main的service</span>
    class main
    <span class="c"># 优先级</span>
    priority <span class="nt">-20</span>
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

service zygote_secondary /system/bin/app_process32 <span class="nt">-Xzygote</span> /system/bin <span class="nt">--zygote</span> <span class="nt">--socket-name</span><span class="o">=</span>zygote_secondary <span class="nt">--enable-lazy-preload</span>
    class main
    priority <span class="nt">-20</span>
    user root
    group root readproc reserved_disk
    socket zygote_secondary stream 660 root system
    socket usap_pool_secondary stream 660 root system
    onrestart restart zygote
    writepid /dev/cpuset/foreground/tasks
</code></pre></div></div>

<p><strong>PS：</strong>关于zygote和system_server的启动会单独展开，主要入口是在<code class="language-plaintext highlighter-rouge">frameworks/base/cmds/app_process/app_main.cpp</code>， 入口为main函数</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># frameworks/base/cmds/app_process/Android.mk 
</span><span class="nv">LOCAL_MODULE</span><span class="o">:=</span> app_process
<span class="nv">LOCAL_MULTILIB</span> <span class="o">:=</span> both
<span class="nv">LOCAL_MODULE_STEM_32</span> <span class="o">:=</span> app_process32
<span class="nv">LOCAL_MODULE_STEM_64</span> <span class="o">:=</span> app_process64
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/system/bin <span class="c"># ls -l app_*</span>
lrwxr-xr-x 1 root shell    13 2009-01-01 03:00 app_process -&gt; app_process64
<span class="nt">-rwxr-xr-x</span> 1 root shell 25088 2009-01-01 03:00 app_process32
<span class="nt">-rwxr-xr-x</span> 1 root shell 33424 2009-01-01 03:00 app_process64
</code></pre></div></div>

<hr />

<h3 id="348-on-firmware_mounts_complete阶段">3.4.8. on firmware_mounts_complete阶段</h3>

<blockquote>
  <p>未做实际操作，暂不关注</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># system/core/rootdir/init.rc </span>
<span class="c"># Indicate to fw loaders that the relevant mounts are up.</span>
on firmware_mounts_complete
    <span class="nb">rm</span> /dev/.booting
</code></pre></div></div>

<hr />

<h3 id="349-on-early-boot阶段">3.4.9. on early-boot阶段</h3>

<blockquote>
  <p>Android原生未做实际操作，暂不关注</p>
</blockquote>

<hr />

<h3 id="3410-on-boot阶段">3.4.10. on boot阶段</h3>

<blockquote>
  <p>前面阶段启动的服务的一些节点值赋值、权限赋予等操作。最主要的是后面两个启动class标注”hal”和”core”的服务</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>on boot
    <span class="c"># basic network init</span>
    ifup lo
    <span class="nb">hostname </span>localhost
    domainname localdomain

    <span class="c"># IPsec SA default expiration length</span>
    write /proc/sys/net/core/xfrm_acq_expires 3600

    <span class="c"># Memory management.  Basic kernel parameters, and allow the high</span>
    <span class="c"># level system server to be able to adjust the kernel OOM driver</span>
    <span class="c"># parameters to match how it is managing things.</span>
    write /proc/sys/vm/overcommit_memory 1
    write /proc/sys/vm/min_free_order_shift 4
    <span class="nb">chown </span>root system /sys/module/lowmemorykiller/parameters/adj
    <span class="nb">chmod </span>0664 /sys/module/lowmemorykiller/parameters/adj
    <span class="nb">chown </span>root system /sys/module/lowmemorykiller/parameters/minfree
    <span class="nb">chmod </span>0664 /sys/module/lowmemorykiller/parameters/minfree

    <span class="c"># System server manages zram writeback</span>
    <span class="nb">chown </span>root system /sys/block/zram0/idle
    <span class="nb">chmod </span>0664 /sys/block/zram0/idle
    <span class="nb">chown </span>root system /sys/block/zram0/writeback
    <span class="nb">chmod </span>0664 /sys/block/zram0/writeback

    <span class="c"># Tweak background writeout</span>
    write /proc/sys/vm/dirty_expire_centisecs 200
    write /proc/sys/vm/dirty_background_ratio  5
    .....
    <span class="c"># Define default initial receive window size in segments.</span>
    setprop net.tcp.default_init_rwnd 60

    <span class="c"># Start standard binderized HAL daemons</span>
    class_start hal

    class_start core
</code></pre></div></div>

<hr />

<hr />

<h1 id="4-参考">4. 参考</h1>

<ul>
  <li><a href="https://blog.csdn.net/tkwxty/article/details/106339987">Android 9 (P)之init进程启动源码分析指南之三</a></li>
  <li><a href="https://blog.csdn.net/u010783226/article/details/119810208">Android init 启动进程分析</a></li>
  <li><a href="https://sunwengang.top/android/android_rcFile_study/">Android RC文件分析</a></li>
  <li><a href="https://blog.csdn.net/temp7695/article/details/135542202">Android启动过程 - init.rc处理过程</a></li>
  <li><a href="https://www.jianshu.com/p/446682d2c482">Android13 系统启动流程-3 init.rc解析</a></li>
  <li><a href="https://www.jianshu.com/p/8a34ba82ac1f">AndroidP之Ueventd</a></li>
  <li><a href="https://zhuanlan.zhihu.com/p/410053058">blkio cgroup</a></li>
  <li><a href="https://www.jianshu.com/p/1ad03018b5ea">Android 日志系统分析(二)：logd</a></li>
  <li><a href="https://www.jianshu.com/p/2cb7c12d31e2">Android 日志系统–02：logd、logcat架构分析</a></li>
  <li><a href="https://blog.csdn.net/shift_wwx/article/details/89138117">Android中 logd 详解</a></li>
  <li><a href="https://blog.csdn.net/iriczhao/article/details/137523677">一文总结Android系统服务大管家-ServiceManager</a></li>
  <li><a href="https://www.cnblogs.com/linhaostudy/p/18301854">android系统启动流程- ServiceManager进程启动流程</a></li>
  <li><a href="https://blog.csdn.net/iriczhao/article/details/137523677">总结Android系统服务大管家-ServiceManager</a></li>
  <li><a href="https://blog.csdn.net/shulianghan/article/details/120345228">Native 层 Binder 机制分析 </a></li>
  <li><a href="https://blog.csdn.net/niurenwo/article/details/139588887">Android ServiceManager和它的兄弟们</a></li>
  <li><a href="https://blog.csdn.net/liujun3512159/article/details/122355244">Android10.0 HwBinder通信原理</a></li>
  <li><a href="https://blog.csdn.net/shift_wwx/article/details/78550380">Android加密之全盘加密</a></li>
  <li><a href="https://blog.csdn.net/temp7695/article/details/135856437">Android启动过程 - Zygote</a></li>
</ul>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="android" /><summary type="html"><![CDATA[接着上篇，init进程启动的SecondStageMain阶段中，最后也是最重要的就是init.rc以及其他各个目录rc的解析和触发，本篇主要针对这块进行详细梳理。]]></summary></entry><entry><title type="html">Android系统启动流程—1 init进程启动流程</title><link href="https://alonealive.github.io/android/android_android%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B_1_init%E8%BF%9B%E7%A8%8B%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/" rel="alternate" type="text/html" title="Android系统启动流程—1 init进程启动流程" /><published>2025-02-27T05:40:02+00:00</published><updated>2025-02-27T05:40:02+00:00</updated><id>https://alonealive.github.io/android/android_android%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B_1_init%E8%BF%9B%E7%A8%8B%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B</id><content type="html" xml:base="https://alonealive.github.io/android/android_android%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B_1_init%E8%BF%9B%E7%A8%8B%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B/"><![CDATA[<blockquote>
  <p>本篇介绍Android系统启动流程的开始阶段，从kernel内核空间启动第一个用户空间init进程，然后梳理init进程启动的几个阶段做了哪些任务。</p>
</blockquote>

<hr />

<h1 id="1-android系统启动整体流程概述">1. Android系统启动整体流程概述</h1>

<blockquote>
  <p>首先了解下Android系统启动的整体流程</p>
</blockquote>

<p><img src="../../assets/post/2025/2025-02-27-android_android系统启动流程_1_init进程启动流程/android启动整体流程.png" alt="" /></p>

<p><img src="../../assets/post/2025/2025-02-27-android_android系统启动流程_1_init进程启动流程/android启动流程新.png" alt="" /></p>

<ol>
  <li><strong>Boot Rom阶段</strong>：启动电源时，引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到RAM，然后执行。</li>
  <li><strong>Bootloader阶段</strong>：Bootloader（引导加载程序）是在Android操作系统开始运行前的一个小程序，负责加载和启动Linux内核。Bootloader的实现在不同设备和制造商之间可能有所不同，可能是开源实现如U-Boot或LK，或者厂商定制的版本，源码目录在<code class="language-plaintext highlighter-rouge">/bootable/</code></li>
  <li><strong>Linux Kernel启动阶段</strong>：在Bootloader的帮助下，Linux内核开始启动。内核启动时，会进行系统缓存、内存保护、任务调度等设置，并加载必要的驱动程序。当内核完成系统设置，它首先在系统文件中寻找 “init” 文件，然后启动系统的第一个进程（init进程）</li>
  <li><strong>Init进程启动阶段</strong>：Init进程是Android系统中用户空间的第一个进程，进程号固定为1。Init进程的主要职责包括创建和挂载必要的文件目录、初始化属性服务、解析init.rc配置文件并启动Zygote进程等</li>
  <li><strong>启动Android系统阶段</strong>：Init进程根据<code class="language-plaintext highlighter-rouge">init.rc</code>文件的内容执行更多初始化任务，如启动Android的关键守护进程和Zygote进程。Zygote进程是所有应用进程的父进程，负责创建其他系统进程</li>
  <li><strong>启动Launcher App阶段</strong>：最后，系统启动用户空间的第一个App——Launcher App，标志着Android系统启动流程的完成</li>
</ol>

<hr />

<p><strong>PS：</strong> 前面两个步骤不在此处详细说明，关于uboot启动以及uboot如何跳转到kernel内核，可以参阅这几篇文章了解：</p>
<ul>
  <li><a href="https://zhuanlan.zhihu.com/p/669600615">uboot启动流程源码分析</a></li>
  <li><a href="https://zhuanlan.zhihu.com/p/669599871">理清UBoot如何跳转Kernel—uboot与linux交界</a></li>
  <li><a href="https://zhuanlan.zhihu.com/p/631104181">linux与安卓的交界</a></li>
</ul>

<p>我们在kernel中只关注init进程的启动流程，所以本篇主题以kernel内核启动init进程开始梳理（以<a href="http://aospxref.com/">Android 10 AOSP源码</a>为基准）</p>

<hr />

<h1 id="2-kernel内核启动init进程">2. kernel内核启动init进程</h1>

<blockquote>
  <p>我们从内核启动的<code class="language-plaintext highlighter-rouge">start_kernel</code>开始梳理（代码文件<code class="language-plaintext highlighter-rouge">kernel/init/main.c</code>），该内核入口函数的最后调用rest_init，然后<code class="language-plaintext highlighter-rouge">rest_init(void)</code>函数创建两个内核子线程，一个是kernel_init，另一个kthreadd</p>
</blockquote>

<ul>
  <li>kernel_init线程：主要创建Android init一号进程</li>
  <li>kthreadd线程：通过kernel_thread创建，并始终运行在内核空间, 负责所有内核线程的调度和管理（代码文件：<code class="language-plaintext highlighter-rouge">kernel/kernel/kthread.c</code>）</li>
</ul>

<p><img src="../../assets/post/2025/2025-02-27-android_android系统启动流程_1_init进程启动流程/1-kernel内核启动init进程.png" alt="" /></p>

<p>在内核代码<code class="language-plaintext highlighter-rouge">kernel/init/main.c -- kernel_init()</code>函数最后会通过调用<code class="language-plaintext highlighter-rouge">run_init_process</code>启动init进程。（Android系统一般会在根目录下放一个init的可执行文件，也就是说Linux系统的init进程在内核初始化完成后，就直接执行init这个文件）</p>

<p>其中<code class="language-plaintext highlighter-rouge">kernel_init_freeable</code>函数中有两点要关注：</p>
<ol>
  <li>定义了ramdisk_execute_command变量的值为”/init”，用于寻找所要启动的init文件</li>
  <li>调用prepare_namespace()方法，该方法内部调用mount_root()，挂载根文件系统</li>
</ol>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//代码文件kernel/init/main.c</span>
<span class="k">static</span> <span class="n">noinline</span> <span class="kt">void</span> <span class="n">__init</span> <span class="nf">kernel_init_freeable</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="p">....</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ramdisk_execute_command</span><span class="p">)</span>
		<span class="n">ramdisk_execute_command</span> <span class="o">=</span> <span class="s">"/init"</span><span class="p">;</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">sys_access</span><span class="p">((</span><span class="k">const</span> <span class="kt">char</span> <span class="n">__user</span> <span class="o">*</span><span class="p">)</span> <span class="n">ramdisk_execute_command</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">ramdisk_execute_command</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
		<span class="n">prepare_namespace</span><span class="p">();</span>
	<span class="p">}</span>
    <span class="p">....</span>
<span class="p">}</span>

<span class="c1">//在rest_init中创建子线程用于生成init进程，函数名就是kernel_init</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">__ref</span> <span class="nf">kernel_init</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">unused</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
    <span class="c1">//进行init进程的一些初始化操作</span>
	<span class="n">kernel_init_freeable</span><span class="p">();</span>
    <span class="p">.....</span>
    <span class="c1">//ramdisk_execute_command的值为"/init"</span>
    <span class="c1">//运行根目录下的init程序</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">ramdisk_execute_command</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">ret</span> <span class="o">=</span> <span class="n">run_init_process</span><span class="p">(</span><span class="n">ramdisk_execute_command</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ret</span><span class="p">)</span>
			<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
		<span class="n">pr_err</span><span class="p">(</span><span class="s">"Failed to execute %s (error %d)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
		       <span class="n">ramdisk_execute_command</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
	<span class="p">}</span>
    <span class="p">....</span>
<span class="p">}</span>
</code></pre></div></div>

<p>关于此处的<code class="language-plaintext highlighter-rouge">/init</code>进程，我们可以看下<code class="language-plaintext highlighter-rouge">/system/core/init/Android.bp</code>的模块定义</p>

<p><strong>PS：</strong> stem字段定义了输出的基本文件名，是用于重命名输出的选项，比如此处编译模块名是<code class="language-plaintext highlighter-rouge">init_second_stage</code>，编译结束后<code class="language-plaintext highlighter-rouge">init_second_stage</code>会命名成<code class="language-plaintext highlighter-rouge">init</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cc_binary <span class="o">{</span>
    name: <span class="s2">"init_second_stage"</span>,
    recovery_available: <span class="nb">true</span>,
    stem: <span class="s2">"init"</span>,
    defaults: <span class="o">[</span><span class="s2">"init_defaults"</span><span class="o">]</span>,
    static_libs: <span class="o">[</span><span class="s2">"libinit"</span><span class="o">]</span>,
    required: <span class="o">[</span>
        <span class="s2">"e2fsdroid"</span>,
        <span class="s2">"init.rc"</span>,
        <span class="s2">"mke2fs"</span>,
        <span class="s2">"sload_f2fs"</span>,
        <span class="s2">"make_f2fs"</span>,
        <span class="s2">"ueventd.rc"</span>,
    <span class="o">]</span>,
    srcs: <span class="o">[</span><span class="s2">"main.cpp"</span><span class="o">]</span>,
    ....
</code></pre></div></div>

<p>而在adb shell进入文件系统看到根目录的<code class="language-plaintext highlighter-rouge">init</code>就是软链接指向<code class="language-plaintext highlighter-rouge">/system/bin/init</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"># ls -thl |grep init</span>
lrwxr-x---   1 root   shell    16 2009-01-01 08:00 init -&gt; /system/bin/init
</code></pre></div></div>

<h2 id="21-序列图">2.1. 序列图</h2>

<p><img src="../../assets/post/2025/2025-02-27-android_android系统启动流程_1_init进程启动流程/1-kernel启动init进程序列图.svg" alt="" /></p>

<hr />

<h1 id="3-init进程">3. init进程</h1>

<blockquote>
  <p>Android系统底层基于Linux Kernel, 当Kernel启动过程会创建init进程, 该进程是所有用户空间的鼻祖, 是Linux系统中用户空间的第一个进程。</p>
</blockquote>

<hr />

<h2 id="31-init进程启动入口main函数">3.1. init进程启动入口main函数</h2>

<p>init是Linux系统中用户空间的第一个进程(pid=1), 从上面<code class="language-plaintext highlighter-rouge">/system/core/init/Android.bp</code>的定义看，模块入口是<code class="language-plaintext highlighter-rouge">system/core/init/main.cpp -- main()</code>方法。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ps <span class="nt">-A</span>
USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME
root             1     0   49852  10816 0                   0 S init
root             2     0       0      0 0                   0 S <span class="o">[</span>kthreadd]
....
</code></pre></div></div>

<p><strong>代码如下：</strong></p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//第一个参数argc表示参数个数，第二个参数是参数列表，也就是具体的参数</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
<span class="cp">#if __has_feature(address_sanitizer)
</span>    <span class="n">__asan_set_error_report_callback</span><span class="p">(</span><span class="n">AsanReportCallback</span><span class="p">);</span>
<span class="cp">#endif
</span>    <span class="c1">//修改当前进程优先级</span>
    <span class="n">setpriority</span><span class="p">(</span><span class="n">PRIO_PROCESS</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">20</span><span class="p">);</span>
    <span class="cm">/*
    * 1.strcmp是String的一个函数，比较字符串，相等返回0
    * 2.C++中0也可以表示false
    * 3.basename是C库中的一个函数，得到特定的路径中的最后一个'/'后面的内容，
    * 比如/sdcard/miui_recovery/backup，得到的结果是backup
    */</span>
    <span class="c1">//当argv[0]的内容为ueventd时，strcmp的值为0,!strcmp为1</span>
    <span class="c1">//ueventd主要是负责设备节点的创建、权限设定等一系列工作</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">basename</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="s">"ueventd"</span><span class="p">))</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">ueventd_main</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">//当传入的参数个数大于1时，执行下面的几个操作</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">//参数为subcontext，初始化日志系统</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">"subcontext"</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">InitLogging</span><span class="p">(</span><span class="n">argv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">KernelLogger</span><span class="p">);</span>
            <span class="k">const</span> <span class="n">BuiltinFunctionMap</span><span class="o">&amp;</span> <span class="n">function_map</span><span class="p">;</span>

            <span class="k">return</span> <span class="n">SubcontextMain</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">function_map</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="c1">//参数为“selinux_setup”,启动Selinux安全策略</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">"selinux_setup"</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">SetupSelinux</span><span class="p">(</span><span class="n">argv</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="c1">//参数为“second_stage”,启动init进程第二阶段</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s">"second_stage"</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">SecondStageMain</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">//默认启动init进程第一阶段</span>
    <span class="k">return</span> <span class="n">FirstStageMain</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>单从main函数的代码内容看，init进程启动后主要做了以下工作（非程序执行顺序）：</strong></p>
<ul>
  <li>ueventd_main：创建子进程ueventd，并将创建设备节点文件的工作交给veventd。ueventd通过两种方式创建设备节点文件（冷启动和热启动），用来管理设备，如果有新设备插入，就会在/dev创建对应的设备文件。</li>
  <li>FirstStageMain：启动init进程第一阶段（加载文件系统、创建目录、节点）</li>
  <li>InitLogging：初始化日志系统</li>
  <li>SetupSelinux：加载Selinux规则，并设置Selinux日志，完成Selinux相关工</li>
  <li>SecondStageMain：启动init进程第二阶段（初始化属性、处理信号、解析init.rc文件等）</li>
</ul>

<p><img src="../../assets/post/2025/2025-02-27-android_android系统启动流程_1_init进程启动流程/init进程入口main函数流程.png" alt="" /></p>

<hr />

<h3 id="311-main函数执行顺序kernel日志示例">3.1.1. main函数执行顺序（kernel日志示例）</h3>

<p>以下是Android 10版本抓取的开机kernel日志，我们结合代码分析init进程的main函数执行顺序。从日志看，main函数的执行顺序是：</p>
<ol>
  <li>执行FirstStageMain</li>
  <li>执行SetupSelinux</li>
  <li>执行SecondStageMain</li>
  <li>执行ueventd_main（此部分是在SecondStageMain的时候解析init.rc中调用，后续梳理）</li>
</ol>

<pre><code class="language-log">//-------------Step 1: 执行FirstStageMain
14,737,1909438,-;init: init first stage started!
14,738,1910109,-;init: Switching root to '/first_stage_ramdisk'
//挂载文件系统节点
6,744,2012037,-;EXT4-fs (mmcblk0p35): mounted filesystem with ordered data mode. Opts: 
//挂载system分区
14,745,2012182,-;init: [libfs_mgr]__mount(source=/dev/block/by-name/system_b,target=/system,type=ext4)=0: Success
14,746,2012616,-;init: Switching root to '/system'
14,747,2019657,-;init: [libfs_mgr]superblock s_max_mnt_count:65535,/dev/block/by-name/vendor_b
6,748,2024936,-;EXT4-fs (mmcblk0p37): mounted filesystem with ordered data mode. Opts: 
//挂载vendor分区
14,749,2025030,-;init: [libfs_mgr]__mount(source=/dev/block/by-name/vendor_b,target=/vendor,type=ext4)=0: Success
14,750,2061016,-;init: Skipped setting INIT_AVB_VERSION (not in recovery mode)
//-------------Step 2: 执行SetupSelinux初始化selinux权限
14,751,2212653,-;init: Loading SELinux policy
7,752,2238189,-;SELinux: 8192 avtab hash slots, 23703 rules.
7,753,2265557,-;SELinux: 8192 avtab hash slots, 23703 rules.
7,754,2265585,-;SELinux:  1 users, 4 roles, 1419 types, 0 bools, 1 sens, 1024 cats
7,755,2265606,-;SELinux:  97 classes, 23703 rules
6,756,2270004,-;SELinux:  Class rpmsg_socket not defined in policy.
6,757,2270022,-;SELinux: the above unknown classes and permissions will be denied
6,758,2270054,-;SELinux:  policy capability network_peer_controls=1
6,759,2270061,-;SELinux:  policy capability open_perms=1
6,760,2270068,-;SELinux:  policy capability extended_socket_class=1
6,761,2270075,-;SELinux:  policy capability always_check_network=0
6,762,2270081,-;SELinux:  policy capability cgroup_seclabel=0
6,763,2270088,-;SELinux:  policy capability nnp_nosuid_transition=1
7,764,2270095,-;SELinux:  Completing initialization.
7,765,2270101,-;SELinux:  Setting up existing superblocks.
6,766,2338067,-;[K] tp_enable=[0] i2c_master_send1: 0x4 0x1 
5,767,2495244,-;audit: type=1403 audit(5.136:2): policy loaded auid=4294967295 ses=4294967295
14,768,2495539,-;selinux: SELinux: Loaded policy from /vendor/etc/selinux/precompiled_sepolicy\x0a
14,769,2503260,-;selinux: SELinux: Loaded file_contexts\x0a
//-------------Step 3: 执行SecondStageMain
14,770,2560465,-;init: init second stage started!
14,771,2591159,-;init: Using Android DT directory /proc/device-tree/firmware/android/
14,772,2596877,-;selinux: SELinux: Loaded file_contexts\x0a
//SelinuxRestoreContext函数打印
14,773,2596914,-;init: Running restorecon...
12,774,2606364,-;init: Couldn't load property file '/system/etc/prop.default': open() failed: No such file or directory: No such file or directory
12,775,2606444,-;init: Couldn't load property file '/prop.default': open() failed: No such file or directory: No such file or directory
12,776,2610584,-;init: Overriding previous 'ro.' property 'pm.dexopt.first-boot':'quicken' with new value 'speed'
12,777,2610921,-;init: Couldn't load property file '/vendor/default.prop': open() failed: No such file or directory: No such file or directory
12,778,2614148,-;init: Couldn't load property file '/product_services/build.prop': open() failed: No such file or directory: No such file or directory
12,779,2614210,-;init: Couldn't load property file '/factory/factory.prop': open() failed: No such file or directory: No such file or directory
//-------------Step 4:执行ueventd_main
14,783,2866884,-;ueventd: ueventd started!
14,784,2870766,-;selinux: SELinux: Loaded file_contexts\x0a
//解析uevent.rc文件
14,785,2871030,-;ueventd: Parsing file /ueventd.rc...
14,786,2872643,-;ueventd: Parsing file /vendor/ueventd.rc...
14,787,2872693,-;ueventd: Unable to read config file '/vendor/ueventd.rc': open() failed: No such file or directory
14,788,2872755,-;ueventd: Parsing file /odm/ueventd.rc...
14,789,2872798,-;ueventd: Unable to read config file '/odm/ueventd.rc': open() failed: No such file or directory
14,790,2872922,-;ueventd: Parsing file /ueventd.{product}.rc...
14,791,2876994,-;apexd: Bootstrap subcommand detected
14,792,2897540,-;apexd: Scanning /system/apex for embedded keys
14,793,2899450,-;apexd: Scanning /product/apex for embedded keys
14,794,2899967,-;apexd: ... does not exist. Skipping
14,795,2900655,-;apexd: Scanning /system/apex looking for APEX packages.
14,796,2901208,-;apexd: Found /system/apex/com.android.conscrypt
14,797,2901966,-;apexd: Skipped when bootstrapping
14,798,2902448,-;apexd: Found /system/apex/com.android.media
14,799,2903155,-;apexd: Skipped when bootstrapping
14,800,2903594,-;apexd: Found /system/apex/com.android.media.swcodec
4,801,2919017,-;apexd: 9 output lines suppressed due to ratelimiting
5,802,2935927,-;audit: type=1400 audit(5.576:3): avc:  denied  { nosuid_transition } for  pid=1904 comm="init" scontext=u:r:init:s0 tcontext=u:r:init-insmod-sh:s0 tclass=process2 permissive=1
5,803,2947227,-;audit: type=1400 audit(5.588:4): avc:  denied  { nosuid_transition } for  pid=1917 comm="init" scontext=u:r:init:s0 tcontext=u:r:hal_graphics_allocator_default:s0 tclass=process2 permissive=1
//ueventd执行结束
14,804,3223055,-;ueventd: Coldboot took 0.347 seconds
....
</code></pre>

<h2 id="32-1firststagemain--启动init进程第一阶段">3.2. （1）FirstStageMain – 启动init进程第一阶段</h2>

<blockquote>
  <p>第一阶段主要进行创建设备节点和一些关键目录、初始化kernel日志输出系统、挂载分区、AVB初始化。</p>
</blockquote>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/first_stage_init.cpp</span>
<span class="kt">int</span> <span class="nf">FirstStageMain</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">//init crash时重启引导加载程序（panic）</span>
    <span class="c1">//这个函数主要作用将各种信号量，如SIGABRT,SIGBUS等的行为设置为SA_RESTART,一旦监听到这些信号即执行重启系统</span>
    <span class="c1">//REBOOT_BOOTLOADER_ON_PANIC在system/core/init/Android.mk中定义，userdebug和eng版本的固件中，则会打开该选项</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">REBOOT_BOOTLOADER_ON_PANIC</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">//调用到system/core/init/reboot_utils.cpp方法，然后调用InitFatalReboot()，再调用RebootSystem重启</span>
        <span class="n">InstallRebootSignalHandlers</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="p">.....</span>
    <span class="c1">// Clear the umask.清空文件权限</span>
    <span class="n">umask</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="p">....</span>
    <span class="c1">//1.挂载tmpsfs、devpts、proc、sysfs和selinuxfs五种文件系统，这些都是系统运行时目录，系统停止时会消失</span>
    <span class="c1">//2.创建目录/dev/pts，/dev/socket</span>
    <span class="c1">//3.创建节点/dev/kmsg，/dev/kmsg_debug，/dev/random，/dev/urandom，/dev/ptmx，/dev/null</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"tmpfs"</span><span class="p">,</span> <span class="s">"/dev"</span><span class="p">,</span> <span class="s">"tmpfs"</span><span class="p">,</span> <span class="n">MS_NOSUID</span><span class="p">,</span> <span class="s">"mode=0755"</span><span class="p">));</span> <span class="c1">//节点目录</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mkdir</span><span class="p">(</span><span class="s">"/dev/pts"</span><span class="p">,</span> <span class="mo">0755</span><span class="p">));</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mkdir</span><span class="p">(</span><span class="s">"/dev/socket"</span><span class="p">,</span> <span class="mo">0755</span><span class="p">));</span>
    <span class="c1">//devpts文件系统用于为android终端设备提供模拟终端会话界面，类似于使用telnet或ssh与一个传统的UNIX服务器进行连接</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"devpts"</span><span class="p">,</span> <span class="s">"/dev/pts"</span><span class="p">,</span> <span class="s">"devpts"</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">));</span>
<span class="cp">#define MAKE_STR(x) __STRING(x)
</span>    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"proc"</span><span class="p">,</span> <span class="s">"/proc"</span><span class="p">,</span> <span class="s">"proc"</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="s">"hidepid=2,gid="</span> <span class="n">MAKE_STR</span><span class="p">(</span><span class="n">AID_READPROC</span><span class="p">)));</span><span class="c1">//进程信息</span>
<span class="cp">#undef MAKE_STR
</span>    <span class="c1">//存储内核启动保存的一些参数，比如AB分区的运行</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">chmod</span><span class="p">(</span><span class="s">"/proc/cmdline"</span><span class="p">,</span> <span class="mo">0440</span><span class="p">));</span>
    <span class="n">gid_t</span> <span class="n">groups</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="n">AID_READPROC</span><span class="p">};</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">setgroups</span><span class="p">(</span><span class="n">arraysize</span><span class="p">(</span><span class="n">groups</span><span class="p">),</span> <span class="n">groups</span><span class="p">));</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"sysfs"</span><span class="p">,</span> <span class="s">"/sys"</span><span class="p">,</span> <span class="s">"sysfs"</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">));</span> <span class="c1">//设备系统目录</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"selinuxfs"</span><span class="p">,</span> <span class="s">"/sys/fs/selinux"</span><span class="p">,</span> <span class="s">"selinuxfs"</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">));</span><span class="c1">//selinuxfs文件系统</span>

    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mknod</span><span class="p">(</span><span class="s">"/dev/kmsg"</span><span class="p">,</span> <span class="n">S_IFCHR</span> <span class="o">|</span> <span class="mo">0600</span><span class="p">,</span> <span class="n">makedev</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">11</span><span class="p">)));</span><span class="c1">//内核日志节点</span>

    <span class="k">if</span> <span class="k">constexpr</span> <span class="p">(</span><span class="n">WORLD_WRITABLE_KMSG</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mknod</span><span class="p">(</span><span class="s">"/dev/kmsg_debug"</span><span class="p">,</span> <span class="n">S_IFCHR</span> <span class="o">|</span> <span class="mo">0622</span><span class="p">,</span> <span class="n">makedev</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">11</span><span class="p">)));</span>
    <span class="p">}</span>

    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mknod</span><span class="p">(</span><span class="s">"/dev/random"</span><span class="p">,</span> <span class="n">S_IFCHR</span> <span class="o">|</span> <span class="mo">0666</span><span class="p">,</span> <span class="n">makedev</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">)));</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mknod</span><span class="p">(</span><span class="s">"/dev/urandom"</span><span class="p">,</span> <span class="n">S_IFCHR</span> <span class="o">|</span> <span class="mo">0666</span><span class="p">,</span> <span class="n">makedev</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">9</span><span class="p">)));</span>

    <span class="c1">// This is needed for log wrapper, which gets called before ueventd runs.</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mknod</span><span class="p">(</span><span class="s">"/dev/ptmx"</span><span class="p">,</span> <span class="n">S_IFCHR</span> <span class="o">|</span> <span class="mo">0666</span><span class="p">,</span> <span class="n">makedev</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">)));</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mknod</span><span class="p">(</span><span class="s">"/dev/null"</span><span class="p">,</span> <span class="n">S_IFCHR</span> <span class="o">|</span> <span class="mo">0666</span><span class="p">,</span> <span class="n">makedev</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">)));</span>

    <span class="c1">//mnt挂载点目录，一般用于挂载外部存储设备、U盘、SD卡等外部存储设备</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"tmpfs"</span><span class="p">,</span> <span class="s">"/mnt"</span><span class="p">,</span> <span class="s">"tmpfs"</span><span class="p">,</span> <span class="n">MS_NOEXEC</span> <span class="o">|</span> <span class="n">MS_NOSUID</span> <span class="o">|</span> <span class="n">MS_NODEV</span><span class="p">,</span>
                    <span class="s">"mode=0755,uid=0,gid=1000"</span><span class="p">));</span>
    <span class="c1">//创建/mnt/vendor和/mnt/product目录</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mkdir</span><span class="p">(</span><span class="s">"/mnt/vendor"</span><span class="p">,</span> <span class="mo">0755</span><span class="p">));</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mkdir</span><span class="p">(</span><span class="s">"/mnt/product"</span><span class="p">,</span> <span class="mo">0755</span><span class="p">));</span>
    <span class="c1">//挂载APEX，这在Android 10.0中特殊引入，用来解决碎片化问题，类似一种组件方式，对Treble的增强，</span>
    <span class="c1">//不写谷歌特殊更新不需要完整升级整个系统版本，只需要像升级APK一样，进行APEX组件升级</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"tmpfs"</span><span class="p">,</span> <span class="s">"/apex"</span><span class="p">,</span> <span class="s">"tmpfs"</span><span class="p">,</span> <span class="n">MS_NOEXEC</span> <span class="o">|</span> <span class="n">MS_NOSUID</span> <span class="o">|</span> <span class="n">MS_NODEV</span><span class="p">,</span>
                    <span class="s">"mode=0755,uid=0,gid=0"</span><span class="p">));</span>
    <span class="c1">// /debug_ramdisk is used to preserve additional files from the debug ramdisk</span>
    <span class="n">CHECKCALL</span><span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"tmpfs"</span><span class="p">,</span> <span class="s">"/debug_ramdisk"</span><span class="p">,</span> <span class="s">"tmpfs"</span><span class="p">,</span> <span class="n">MS_NOEXEC</span> <span class="o">|</span> <span class="n">MS_NOSUID</span> <span class="o">|</span> <span class="n">MS_NODEV</span><span class="p">,</span>
                    <span class="s">"mode=0755,uid=0,gid=0"</span><span class="p">));</span>
<span class="cp">#undef CHECKCALL
</span>    <span class="c1">//调用system/core/init/util.cpp的方法</span>
    <span class="c1">//把标准输入、标准输出和标准错误(即stdin/stdout/stderr)重定向到空设备文件"/dev/null"</span>
    <span class="n">SetStdioToDevNull</span><span class="p">(</span><span class="n">argv</span><span class="p">);</span>
    <span class="c1">//调用system/core/init/util.cpp的方法，然后调用InitLogging入参KernelLogger</span>
    <span class="c1">//上面已经在/dev目录下挂载好tmpfs以及kmsg节点，此处可以初始化/kernel Log系统，供用户打印log</span>
    <span class="n">InitKernelLogging</span><span class="p">(</span><span class="n">argv</span><span class="p">);</span>

    <span class="c1">//此处会在内核kernel中打印日志！</span>
    <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"init first stage started!"</span><span class="p">;</span>

    <span class="k">auto</span> <span class="n">old_root_dir</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="kt">DIR</span><span class="p">,</span> <span class="k">decltype</span><span class="p">(</span><span class="o">&amp;</span><span class="n">closedir</span><span class="p">)</span><span class="o">&gt;</span><span class="p">{</span><span class="n">opendir</span><span class="p">(</span><span class="s">"/"</span><span class="p">),</span> <span class="n">closedir</span><span class="p">};</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">old_root_dir</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Could not opendir(</span><span class="se">\"</span><span class="s">/</span><span class="se">\"</span><span class="s">), not freeing ramdisk"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">struct</span> <span class="nc">stat</span> <span class="n">old_root_info</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">stat</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">old_root_info</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Could not stat(</span><span class="se">\"</span><span class="s">/</span><span class="se">\"</span><span class="s">), not freeing ramdisk"</span><span class="p">;</span>
        <span class="n">old_root_dir</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="c1">//从/proc/cmdline中读取androidboot.force_normal_boot=1则执行</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ForceNormalBoot</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">mkdir</span><span class="p">(</span><span class="s">"/first_stage_ramdisk"</span><span class="p">,</span> <span class="mo">0755</span><span class="p">);</span>
        <span class="c1">// SwitchRoot() must be called with a mount point as the target, so we bind mount the</span>
        <span class="c1">// target directory to itself here.</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">mount</span><span class="p">(</span><span class="s">"/first_stage_ramdisk"</span><span class="p">,</span> <span class="s">"/first_stage_ramdisk"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="n">MS_BIND</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">LOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Could not bind mount /first_stage_ramdisk to itself"</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="c1">//调用system/core/init/switch_root.cpp方法</span>
        <span class="c1">//以装载点为目标进行调用，因此我们在此处将装载目标目录绑定到其自身(节点/proc/mounts)</span>
        <span class="n">SwitchRoot</span><span class="p">(</span><span class="s">"/first_stage_ramdisk"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">//如果存在此文件，则第二阶段init将使用userdebug sepolicy并加载adb_debug.prop以允许adb root（如果设备已解锁）</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="s">"/force_debuggable"</span><span class="p">,</span> <span class="n">F_OK</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">error_code</span> <span class="n">ec</span><span class="p">;</span>  <span class="c1">// to invoke the overloaded copy_file() that won't throw.</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fs</span><span class="o">::</span><span class="n">copy_file</span><span class="p">(</span><span class="s">"/adb_debug.prop"</span><span class="p">,</span> <span class="n">kDebugRamdiskProp</span><span class="p">,</span> <span class="n">ec</span><span class="p">)</span> <span class="o">||</span>
            <span class="o">!</span><span class="n">fs</span><span class="o">::</span><span class="n">copy_file</span><span class="p">(</span><span class="s">"/userdebug_plat_sepolicy.cil"</span><span class="p">,</span> <span class="n">kDebugRamdiskSEPolicy</span><span class="p">,</span> <span class="n">ec</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to setup debug ramdisk"</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// setenv for second-stage init to read above kDebugRamdisk* files.</span>
            <span class="n">setenv</span><span class="p">(</span><span class="s">"INIT_FORCE_DEBUGGABLE"</span><span class="p">,</span> <span class="s">"true"</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">//调用system/core/init/first_stage_mount.cpp</span>
    <span class="c1">//初始化一些必须的分区，主要作用是去解析/proc/device-tree/firmware/android/fstab,然后得到"/system", "/vendor", "/odm"三个目录的挂载信息</span>
    <span class="c1">//此处会调用到system/core/fs_mgr/fs_mgr_fstab.cpp，文件系统的分区挂载信息读取解析，可后续补充梳理关注！！！</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">DoFirstStageMount</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to mount required partitions early ..."</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">struct</span> <span class="nc">stat</span> <span class="n">new_root_info</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">stat</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">new_root_info</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Could not stat(</span><span class="se">\"</span><span class="s">/</span><span class="se">\"</span><span class="s">), not freeing ramdisk"</span><span class="p">;</span>
        <span class="n">old_root_dir</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">old_root_dir</span> <span class="o">&amp;&amp;</span> <span class="n">old_root_info</span><span class="p">.</span><span class="n">st_dev</span> <span class="o">!=</span> <span class="n">new_root_info</span><span class="p">.</span><span class="n">st_dev</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">FreeRamdisk</span><span class="p">(</span><span class="n">old_root_dir</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="n">old_root_info</span><span class="p">.</span><span class="n">st_dev</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">//初始化安全框架AVB（Android Verified Boot）</span>
    <span class="c1">//AVB主要用于防止系统文件本身被篡改，还包含了防止系统回滚的功能，以免有人试图回滚系统并利用以前的漏洞</span>
    <span class="n">SetInitAvbVersionInRecovery</span><span class="p">();</span>

    <span class="k">static</span> <span class="k">constexpr</span> <span class="kt">uint32_t</span> <span class="n">kNanosecondsPerMillisecond</span> <span class="o">=</span> <span class="mf">1e6</span><span class="p">;</span>
    <span class="kt">uint64_t</span> <span class="n">start_ms</span> <span class="o">=</span> <span class="n">start_time</span><span class="p">.</span><span class="n">time_since_epoch</span><span class="p">().</span><span class="n">count</span><span class="p">()</span> <span class="o">/</span> <span class="n">kNanosecondsPerMillisecond</span><span class="p">;</span>
    <span class="n">setenv</span><span class="p">(</span><span class="s">"INIT_STARTED_AT"</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">to_string</span><span class="p">(</span><span class="n">start_ms</span><span class="p">).</span><span class="n">c_str</span><span class="p">(),</span> <span class="mi">1</span><span class="p">);</span>

    <span class="c1">//此处将执行init启动的第二个步骤，就是传入参数"selinux_setup"，执行SetupSelinux</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">path</span> <span class="o">=</span> <span class="s">"/system/bin/init"</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">args</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="n">path</span><span class="p">,</span> <span class="s">"selinux_setup"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">};</span>
    <span class="n">execv</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="k">const_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">**&gt;</span><span class="p">(</span><span class="n">args</span><span class="p">));</span>
    <span class="p">....</span>
    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="321-序列图">3.2.1. 序列图</h3>

<p><img src="../../assets/post/2025/2025-02-27-android_android系统启动流程_1_init进程启动流程/2-init进程启动第一阶段.svg" alt="" /></p>

<hr />

<h3 id="322-11挂载文件系统创建目录节点">3.2.2. （1.1）挂载文件系统/创建目录/节点</h3>

<blockquote>
  <p>这些操作使用mount、mkdir、mknod命令直接实现</p>
</blockquote>

<ul>
  <li><strong>挂载文件系统：</strong></li>
</ul>

<table>
  <thead>
    <tr>
      <th style="text-align: center">设备</th>
      <th style="text-align: center">类型</th>
      <th style="text-align: center">挂载目录</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">tmpfs</td>
      <td style="text-align: center">tmpfs</td>
      <td style="text-align: center">/dev</td>
    </tr>
    <tr>
      <td style="text-align: center">devpts</td>
      <td style="text-align: center">devpts</td>
      <td style="text-align: center">/dev/pts</td>
    </tr>
    <tr>
      <td style="text-align: center">proc</td>
      <td style="text-align: center">proc</td>
      <td style="text-align: center">/proc</td>
    </tr>
    <tr>
      <td style="text-align: center">sysfs</td>
      <td style="text-align: center">sysfs</td>
      <td style="text-align: center">/sys</td>
    </tr>
    <tr>
      <td style="text-align: center">selinuxfs</td>
      <td style="text-align: center">selinuxfs</td>
      <td style="text-align: center">/sys/fs/selinux</td>
    </tr>
    <tr>
      <td style="text-align: center">tmpfs</td>
      <td style="text-align: center">tmpfs</td>
      <td style="text-align: center">/mnt</td>
    </tr>
    <tr>
      <td style="text-align: center">tmpfs</td>
      <td style="text-align: center">tmpfs</td>
      <td style="text-align: center">/apex</td>
    </tr>
    <tr>
      <td style="text-align: center">tmpfs</td>
      <td style="text-align: center">tmpfs</td>
      <td style="text-align: center">/debug_ramdisk</td>
    </tr>
  </tbody>
</table>

<ul>
  <li><strong>创建文件夹：</strong></li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/dev/pts
/dev/socket
/mnt/vendor
/mnt/product
</code></pre></div></div>

<ul>
  <li><strong>创建字符设备文件：</strong></li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/dev/kmsg
/dev/kmsg_debug
/dev/random
/dev/urandom
/dev/ptmx
/dev/null
</code></pre></div></div>

<hr />

<h3 id="323-12initkernellogging日志初始化">3.2.3. （1.2）InitKernelLogging日志初始化</h3>

<h4 id="3231-流程梳理">3.2.3.1. 流程梳理</h4>

<pre><code class="language-log">1.system/core/init/first_stage_init.cpp - FirstStageMain先调用SetStdioToDevNull，然后调用InitKernelLogging
2.system/core/init/util.cpp - InitKernelLogging调用android::base::InitLogging入参KernelLogger
3.system/core/base/logging.cpp - InitLogging
---&gt; SetLogger
    ---&gt; KernelLogger
</code></pre>

<h4 id="3232-代码说明">3.2.3.2. 代码说明</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/util.cpp</span>
<span class="kt">void</span> <span class="nf">SetStdioToDevNull</span><span class="p">(</span><span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">//将stdin/stdout/stderr重定向到/dev/null，关闭默认控制台输出</span>
    <span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/dev/null"</span><span class="p">,</span> <span class="n">O_RDWR</span><span class="p">);</span>
    <span class="p">....</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">fd</span> <span class="o">&gt;</span> <span class="n">STDERR_FILENO</span><span class="p">)</span> <span class="n">close</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">//初始化内核log</span>
<span class="kt">void</span> <span class="n">InitKernelLogging</span><span class="p">(</span><span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">SetFatalRebootTarget</span><span class="p">();</span>
    <span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">InitLogging</span><span class="p">(</span><span class="n">argv</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">KernelLogger</span><span class="p">,</span> <span class="n">InitAborter</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>入参android::base::KernelLogger，即此处LogFunction类型的logger。查看SetLogger代码定义，此处就是执行KernelLogger方法</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/base/logging.cpp</span>
<span class="k">static</span> <span class="kt">bool</span> <span class="n">gInitialized</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">InitLogging</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[],</span> <span class="n">LogFunction</span><span class="o">&amp;&amp;</span> <span class="n">logger</span><span class="p">,</span> <span class="n">AbortFunction</span><span class="o">&amp;&amp;</span> <span class="n">aborter</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">SetLogger</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">forward</span><span class="o">&lt;</span><span class="n">LogFunction</span><span class="o">&gt;</span><span class="p">(</span><span class="n">logger</span><span class="p">));</span>
  <span class="n">SetAborter</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">forward</span><span class="o">&lt;</span><span class="n">AbortFunction</span><span class="o">&gt;</span><span class="p">(</span><span class="n">aborter</span><span class="p">));</span>
  <span class="c1">//默认值false</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">gInitialized</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="n">gInitialized</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>

  <span class="p">.....</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">KernelLogger</span><span class="p">(</span><span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">LogId</span><span class="p">,</span> <span class="n">android</span><span class="o">::</span><span class="n">base</span><span class="o">::</span><span class="n">LogSeverity</span> <span class="n">severity</span><span class="p">,</span>
                  <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">tag</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">msg</span><span class="p">)</span> <span class="p">{</span>
  <span class="p">.....</span>
  <span class="c1">//打开/dev/kmsg节点</span>
  <span class="k">static</span> <span class="kt">int</span> <span class="n">klog_fd</span> <span class="o">=</span> <span class="n">OpenKmsg</span><span class="p">();</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">klog_fd</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>

  <span class="p">....</span>
  <span class="c1">// The kernel's printk buffer is only 1024 bytes.</span>
  <span class="c1">// TODO: should we automatically break up long lines into multiple lines?</span>
  <span class="c1">// Or we could log but with something like "..." at the end?</span>
  <span class="c1">//内核的printk缓冲区只有1024个字节</span>
  <span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">1024</span><span class="p">];</span>
  <span class="kt">size_t</span> <span class="n">size</span> <span class="o">=</span> <span class="n">snprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="s">"&lt;%d&gt;%s: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">level</span><span class="p">,</span> <span class="n">tag</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&gt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">))</span> <span class="p">{</span>
    <span class="n">size</span> <span class="o">=</span> <span class="n">snprintf</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="s">"&lt;%d&gt;%s: %zu-byte message too long for printk</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
                    <span class="n">level</span><span class="p">,</span> <span class="n">tag</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="n">iovec</span> <span class="n">iov</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
  <span class="n">iov</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">iov_base</span> <span class="o">=</span> <span class="n">buf</span><span class="p">;</span>
  <span class="n">iov</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">iov_len</span> <span class="o">=</span> <span class="n">size</span><span class="p">;</span>
  <span class="c1">//日志信息写入/dev/kmsg节点</span>
  <span class="n">TEMP_FAILURE_RETRY</span><span class="p">(</span><span class="n">writev</span><span class="p">(</span><span class="n">klog_fd</span><span class="p">,</span> <span class="n">iov</span><span class="p">,</span> <span class="mi">1</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="3233-log打印输出原理流程">3.2.3.3. log打印输出原理流程</h4>

<blockquote>
  <p>在前面的分析中，我们讲解了init log系统的初始化。关于log的输出流程，可以以下面的输出例子说明：</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">LOG(INFO) &lt;&lt; "init first stage started!";</code></p>

<p>内核打印：<code class="language-plaintext highlighter-rouge">14,1169,11337125,-;init: init first stage started!</code></p>

<p>该流程大致如下（此处不详细说明，参考文章《<a href="https://blog.csdn.net/tkwxty/article/details/106020050">init进程启动源码分析指南之一</a>》）：</p>

<p><img src="../../assets/post/2025/2025-02-27-android_android系统启动流程_1_init进程启动流程/log输出流程.png" alt="" /></p>

<p>在<code class="language-plaintext highlighter-rouge">system/core/base/include/android-base/logging.h</code>中看到<code class="language-plaintext highlighter-rouge">LOG(INFO)</code>的定义：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//severity是log等级</span>
<span class="cp">#define LOG(severity) LOG_TO(DEFAULT, severity)
#define LOG_TO(dest, severity) LOGGING_PREAMBLE(severity) &amp;&amp; LOG_STREAM_TO(dest, severity)
</span>
<span class="c1">//WOULD_LOG是判断log等级是否小于设定值，以决定是否输出</span>
<span class="c1">//此处默认值是static LogSeverity gMinimumLogSeverity = INFO;</span>
<span class="cp">#define LOGGING_PREAMBLE(severity)                                                         \
  (WOULD_LOG(severity) &amp;&amp;                                                                  \
   ABORT_AFTER_LOG_EXPR_IF((SEVERITY_LAMBDA(severity)) == ::android::base::FATAL, true) &amp;&amp; \
   ::android::base::ErrnoRestorer())
</span>
<span class="c1">//LogMessage方法</span>
<span class="cp">#define LOG_STREAM_TO(dest, severity)                                           \
  ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::dest,        \
                              SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, -1) \
      .stream()
</span></code></pre></div></div>

<p>LogMessage的析构函数会调用到LogLine，从而调用到logger：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/base/logging.cpp</span>
<span class="c1">//创建LogMessageData对象，保存log message（行数、文件名等）</span>
<span class="n">LogMessage</span><span class="o">::</span><span class="n">LogMessage</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">file</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">line</span><span class="p">,</span> <span class="n">LogId</span> <span class="n">id</span><span class="p">,</span> <span class="n">LogSeverity</span> <span class="n">severity</span><span class="p">,</span>
                       <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">tag</span><span class="p">,</span> <span class="kt">int</span> <span class="n">error</span><span class="p">)</span>
    <span class="o">:</span> <span class="n">data_</span><span class="p">(</span><span class="k">new</span> <span class="nf">LogMessageData</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="n">severity</span><span class="p">,</span> <span class="n">tag</span><span class="p">,</span> <span class="n">error</span><span class="p">))</span> <span class="p">{}</span>

<span class="n">LogMessage</span><span class="o">::~</span><span class="n">LogMessage</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM.</span>
  <span class="c1">//检测log等级</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">WOULD_LOG</span><span class="p">(</span><span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetSeverity</span><span class="p">()))</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>
    <span class="p">....</span>
    <span class="c1">// Do the actual logging with the lock held.</span>
    <span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">lock</span><span class="p">(</span><span class="n">LoggingLock</span><span class="p">());</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="sc">'\n'</span><span class="p">)</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">LogLine</span><span class="p">(</span><span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetFile</span><span class="p">(),</span> <span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetLineNumber</span><span class="p">(),</span> <span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetId</span><span class="p">(),</span> <span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetSeverity</span><span class="p">(),</span>
              <span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetTag</span><span class="p">(),</span> <span class="n">msg</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="n">msg</span> <span class="o">+=</span> <span class="sc">'\n'</span><span class="p">;</span>
      <span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="k">while</span> <span class="p">(</span><span class="n">i</span> <span class="o">&lt;</span> <span class="n">msg</span><span class="p">.</span><span class="n">size</span><span class="p">())</span> <span class="p">{</span>
        <span class="kt">size_t</span> <span class="n">nl</span> <span class="o">=</span> <span class="n">msg</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="sc">'\n'</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
        <span class="n">msg</span><span class="p">[</span><span class="n">nl</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>
        <span class="n">LogLine</span><span class="p">(</span><span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetFile</span><span class="p">(),</span> <span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetLineNumber</span><span class="p">(),</span> <span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetId</span><span class="p">(),</span> <span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetSeverity</span><span class="p">(),</span>
                <span class="n">data_</span><span class="o">-&gt;</span><span class="n">GetTag</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">msg</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
        <span class="c1">// Undo the zero-termination so we can give the complete message to the aborter.</span>
        <span class="n">msg</span><span class="p">[</span><span class="n">nl</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\n'</span><span class="p">;</span>
        <span class="n">i</span> <span class="o">=</span> <span class="n">nl</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
    <span class="p">....</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">LogMessage</span><span class="o">::</span><span class="n">LogLine</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">file</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">line</span><span class="p">,</span> <span class="n">LogId</span> <span class="n">id</span><span class="p">,</span> <span class="n">LogSeverity</span> <span class="n">severity</span><span class="p">,</span>
                         <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">tag</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">message</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">tag</span> <span class="o">==</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="c1">//调用Logger，而参考其定义，此处就是KernelLogger</span>
    <span class="n">Logger</span><span class="p">()(</span><span class="n">id</span><span class="p">,</span> <span class="n">severity</span><span class="p">,</span> <span class="n">gDefaultTag</span><span class="o">-&gt;</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">file</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">message</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">Logger</span><span class="p">()(</span><span class="n">id</span><span class="p">,</span> <span class="n">severity</span><span class="p">,</span> <span class="n">tag</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">message</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="324-13dofirststagemount挂载分区">3.2.4. （1.3）DoFirstStageMount挂载分区</h3>

<h4 id="3241-流程梳理">3.2.4.1. 流程梳理</h4>

<pre><code class="language-log">1.system/core/init/first_stage_init.cpp - FirstStageMain调用DoFirstStageMount
2.system/core/init/first_stage_mount.cpp - DoFirstStageMount
    ---&gt; 2.1调用FirstStageMount类Create方法
        ---&gt; 调用ReadFirstStageFstab 读取fstab文件
            ---&gt; system/core/fs_mgr/fs_mgr_fstab.cpp - ReadFstabFromDt()会打印日志读取失败，然后调用ReadDefaultFstab
            ---&gt; GetFstabPath获取配置fstab文件路径
            ---&gt; ReadFstabFromFile读取fstab文件，解析fstab文件

    ---&gt; 2.2调用FirstStageMount类DoFirstStageMount方法
        ---&gt; 执行FirstStageMount::MountPartitions()方法遍历fstab的分区
        ---&gt; 执行FirstStageMount::MountPartition挂载单个分区
        ---&gt;  system/core/fs_mgr/fs_mgr.cpp - fs_mgr_do_mount_one执行mount操作挂载
            ---&gt; 调用__mount然后执行mount命令实现挂载
</code></pre>

<h4 id="3242-代码说明">3.2.4.2. 代码说明</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/first_stage_mount.cpp</span>
<span class="c1">// Public functions</span>
<span class="c1">// ----------------</span>
<span class="c1">//公共方法，在设备树中挂载fstab指定的分区</span>
<span class="kt">bool</span> <span class="nf">DoFirstStageMount</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">//recovery模式则跳过不执行，此判断就是看是否存在/system/bin/recovery文件</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">IsRecoveryMode</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"First stage mount skipped (recovery mode)"</span><span class="p">;</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//1.创建FirstStageMount类，调用构造函数和Create方法</span>
    <span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">FirstStageMount</span><span class="o">&gt;</span> <span class="n">handle</span> <span class="o">=</span> <span class="n">FirstStageMount</span><span class="o">::</span><span class="n">Create</span><span class="p">();</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">handle</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to create FirstStageMount"</span><span class="p">;</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//2.调用FirstStageMount类的DoFirstStageMount</span>
    <span class="k">return</span> <span class="n">handle</span><span class="o">-&gt;</span><span class="n">DoFirstStageMount</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>按该方法分两部分梳理：</strong></p>

<h5 id="32421-获取fstab文件并解析">3.2.4.2.1. 获取fstab文件并解析</h5>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/first_stage_mount.cpp</span>
<span class="c1">// Class Definitions</span>
<span class="c1">// -----------------</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">FirstStageMount</span><span class="o">&gt;</span> <span class="n">FirstStageMount</span><span class="o">::</span><span class="n">Create</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">//读取fastab文件</span>
    <span class="k">auto</span> <span class="n">fstab</span> <span class="o">=</span> <span class="n">ReadFirstStageFstab</span><span class="p">();</span>
    <span class="p">.....</span>
<span class="p">}</span>

<span class="k">static</span> <span class="n">Fstab</span> <span class="n">ReadFirstStageFstab</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">Fstab</span> <span class="n">fstab</span><span class="p">;</span>
    <span class="c1">//Step 1 先从设备树文件中读取Fstab文件，如果失败了则调用ReadDefaultFstab读取默认的fstab</span>
    <span class="c1">//从本地抓取的日志看，是直接调用的ReadDefaultFstab</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadFstabFromDt</span><span class="p">(</span><span class="o">&amp;</span><span class="n">fstab</span><span class="p">))</span> <span class="p">{</span>
        <span class="c1">//Step 2 读取fstab文件</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">ReadDefaultFstab</span><span class="p">(</span><span class="o">&amp;</span><span class="n">fstab</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">fstab</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">remove_if</span><span class="p">(</span><span class="n">fstab</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">fstab</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span>
                                       <span class="p">[](</span><span class="k">const</span> <span class="k">auto</span><span class="o">&amp;</span> <span class="n">entry</span><span class="p">)</span> <span class="p">{</span>
                                           <span class="k">return</span> <span class="o">!</span><span class="n">entry</span><span class="p">.</span><span class="n">fs_mgr_flags</span><span class="p">.</span><span class="n">first_stage_mount</span><span class="p">;</span>
                                       <span class="p">}),</span>
                        <span class="n">fstab</span><span class="p">.</span><span class="n">end</span><span class="p">());</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to fstab for first stage mount"</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">//返回结果fstab对象</span>
    <span class="k">return</span> <span class="n">fstab</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/fs_mgr/fs_mgr_fstab.cpp</span>
<span class="c1">// fstab.&lt;hardware.platform&gt; in folders /odm/etc, vendor/etc, or /.</span>
<span class="c1">//在/odm/etc, vendor/etc, or /目录寻找fstab.&lt;hardware.platform&gt;文件！！！</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="nf">GetFstabPath</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">//硬件平台</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">prop</span> <span class="o">:</span> <span class="p">{</span><span class="s">"hardware"</span><span class="p">,</span> <span class="s">"hardware.platform"</span><span class="p">})</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">hw</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">fs_mgr_get_boot_config</span><span class="p">(</span><span class="n">prop</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">hw</span><span class="p">))</span> <span class="k">continue</span><span class="p">;</span>
        <span class="c1">//编译搜寻fstab文件</span>
        <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">prefix</span> <span class="o">:</span> <span class="p">{</span><span class="s">"/odm/etc/fstab."</span><span class="p">,</span> <span class="s">"/vendor/etc/fstab."</span><span class="p">,</span> <span class="s">"/fstab."</span><span class="p">})</span> <span class="p">{</span>
            <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">fstab_path</span> <span class="o">=</span> <span class="n">prefix</span> <span class="o">+</span> <span class="n">hw</span><span class="p">;</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="n">fstab_path</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">F_OK</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">return</span> <span class="n">fstab_path</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="s">""</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Loads the fstab file and combines with fstab entries passed in from device tree.</span>
<span class="kt">bool</span> <span class="n">ReadDefaultFstab</span><span class="p">(</span><span class="n">Fstab</span><span class="o">*</span> <span class="n">fstab</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">default_fstab_path</span><span class="p">;</span>
    <span class="c1">// Use different fstab paths for normal boot and recovery boot, respectively</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="s">"/system/bin/recovery"</span><span class="p">,</span> <span class="n">F_OK</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">default_fstab_path</span> <span class="o">=</span> <span class="s">"/etc/recovery.fstab"</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>  <span class="c1">// normal boot</span>
        <span class="c1">//Step 1:正常启动，获取fastab的路径</span>
        <span class="n">default_fstab_path</span> <span class="o">=</span> <span class="n">GetFstabPath</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="n">Fstab</span> <span class="n">default_fstab</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">default_fstab_path</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
        <span class="c1">//Step 2: 从文件中读取fstab分区，解析fstab文件</span>
        <span class="n">ReadFstabFromFile</span><span class="p">(</span><span class="n">default_fstab_path</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">default_fstab</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">LINFO</span> <span class="o">&lt;&lt;</span> <span class="n">__FUNCTION__</span> <span class="o">&lt;&lt;</span> <span class="s">"(): failed to find device default fstab"</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//将读取的fstab文件结果push到fstab对象中！</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span><span class="o">&amp;&amp;</span> <span class="n">entry</span> <span class="o">:</span> <span class="n">default_fstab</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">fstab</span><span class="o">-&gt;</span><span class="n">emplace_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">entry</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="o">!</span><span class="n">fstab</span><span class="o">-&gt;</span><span class="n">empty</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h5 id="32422-挂载fstab分区">3.2.4.2.2. 挂载fstab分区</h5>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/first_stage_mount.cpp</span>
<span class="kt">bool</span> <span class="n">FirstStageMount</span><span class="o">::</span><span class="n">DoFirstStageMount</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">IsDmLinearEnabled</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">fstab_</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
        <span class="c1">// Nothing to mount.</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"First stage mount skipped (missing/incompatible/empty fstab in device tree)"</span><span class="p">;</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">InitDevices</span><span class="p">())</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">CreateLogicalPartitions</span><span class="p">())</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="c1">//挂载分区</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">MountPartitions</span><span class="p">())</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">FirstStageMount</span><span class="o">::</span><span class="n">MountPartitions</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">TrySwitchSystemAsRoot</span><span class="p">())</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">SkipMountingPartitions</span><span class="p">(</span><span class="o">&amp;</span><span class="n">fstab_</span><span class="p">))</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="c1">//遍历fstab文件的分区列表</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="n">current</span> <span class="o">=</span> <span class="n">fstab_</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span> <span class="n">current</span> <span class="o">!=</span> <span class="n">fstab_</span><span class="p">.</span><span class="n">end</span><span class="p">();)</span> <span class="p">{</span>
        <span class="c1">// We've already mounted /system above.</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">current</span><span class="o">-&gt;</span><span class="n">mount_point</span> <span class="o">==</span> <span class="s">"/system"</span><span class="p">)</span> <span class="p">{</span>
            <span class="o">++</span><span class="n">current</span><span class="p">;</span>
            <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">Fstab</span><span class="o">::</span><span class="n">iterator</span> <span class="n">end</span><span class="p">;</span>
        <span class="c1">//挂载分区</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">MountPartition</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="nb">false</span> <span class="cm">/* erase_same_mounts */</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">end</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">current</span><span class="o">-&gt;</span><span class="n">fs_mgr_flags</span><span class="p">.</span><span class="n">no_fail</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to mount "</span> <span class="o">&lt;&lt;</span> <span class="n">current</span><span class="o">-&gt;</span><span class="n">mount_point</span>
                          <span class="o">&lt;&lt;</span> <span class="s">", ignoring mount for no_fail partition"</span><span class="p">;</span>
            <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">current</span><span class="o">-&gt;</span><span class="n">fs_mgr_flags</span><span class="p">.</span><span class="n">formattable</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to mount "</span> <span class="o">&lt;&lt;</span> <span class="n">current</span><span class="o">-&gt;</span><span class="n">mount_point</span>
                          <span class="o">&lt;&lt;</span> <span class="s">", ignoring mount for formattable partition"</span><span class="p">;</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="n">PLOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to mount "</span> <span class="o">&lt;&lt;</span> <span class="n">current</span><span class="o">-&gt;</span><span class="n">mount_point</span><span class="p">;</span>
                <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="n">current</span> <span class="o">=</span> <span class="n">end</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="p">.....</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">FirstStageMount</span><span class="o">::</span><span class="n">MountPartition</span><span class="p">(</span><span class="k">const</span> <span class="n">Fstab</span><span class="o">::</span><span class="n">iterator</span><span class="o">&amp;</span> <span class="n">begin</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">erase_same_mounts</span><span class="p">,</span>
                                     <span class="n">Fstab</span><span class="o">::</span><span class="n">iterator</span><span class="o">*</span> <span class="n">end</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="kt">bool</span> <span class="n">mounted</span> <span class="o">=</span> <span class="p">(</span><span class="n">fs_mgr_do_mount_one</span><span class="p">(</span><span class="o">*</span><span class="n">begin</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>

    <span class="c1">// Try other mounts with the same mount point.</span>
    <span class="n">Fstab</span><span class="o">::</span><span class="n">iterator</span> <span class="n">current</span> <span class="o">=</span> <span class="n">begin</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(;</span> <span class="n">current</span> <span class="o">!=</span> <span class="n">fstab_</span><span class="p">.</span><span class="n">end</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">current</span><span class="o">-&gt;</span><span class="n">mount_point</span> <span class="o">==</span> <span class="n">begin</span><span class="o">-&gt;</span><span class="n">mount_point</span><span class="p">;</span> <span class="n">current</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mounted</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// blk_device is already updated to /dev/dm-&lt;N&gt; by SetUpDmVerity() above.</span>
            <span class="c1">// Copy it from the begin iterator.</span>
            <span class="n">current</span><span class="o">-&gt;</span><span class="n">blk_device</span> <span class="o">=</span> <span class="n">begin</span><span class="o">-&gt;</span><span class="n">blk_device</span><span class="p">;</span>
            <span class="c1">//挂载分区</span>
            <span class="n">mounted</span> <span class="o">=</span> <span class="p">(</span><span class="n">fs_mgr_do_mount_one</span><span class="p">(</span><span class="o">*</span><span class="n">current</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">erase_same_mounts</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">current</span> <span class="o">=</span> <span class="n">fstab_</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">begin</span><span class="p">,</span> <span class="n">current</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">end</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">*</span><span class="n">end</span> <span class="o">=</span> <span class="n">current</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="n">mounted</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/fs_mgr/fs_mgr.cpp</span>
<span class="kt">int</span> <span class="nf">fs_mgr_do_mount_one</span><span class="p">(</span><span class="k">const</span> <span class="n">FstabEntry</span><span class="o">&amp;</span> <span class="n">entry</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">mount_point</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Run fsck if needed</span>
    <span class="n">prepare_fs_for_mount</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">blk_device</span><span class="p">,</span> <span class="n">entry</span><span class="p">);</span>

    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span>
    <span class="c1">//挂载</span>
            <span class="n">__mount</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="n">blk_device</span><span class="p">,</span> <span class="n">mount_point</span><span class="p">.</span><span class="n">empty</span><span class="p">()</span> <span class="o">?</span> <span class="n">entry</span><span class="p">.</span><span class="n">mount_point</span> <span class="o">:</span> <span class="n">mount_point</span><span class="p">,</span> <span class="n">entry</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">ret</span> <span class="o">=</span> <span class="p">(</span><span class="n">errno</span> <span class="o">==</span> <span class="n">EBUSY</span><span class="p">)</span> <span class="o">?</span> <span class="n">FS_MGR_DOMNT_BUSY</span> <span class="o">:</span> <span class="n">FS_MGR_DOMNT_FAILED</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="n">__mount</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">source</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">target</span><span class="p">,</span> <span class="k">const</span> <span class="n">FstabEntry</span><span class="o">&amp;</span> <span class="n">entry</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// We need this because sometimes we have legacy symlinks that are</span>
    <span class="c1">// lingering around and need cleaning up.</span>
    <span class="k">struct</span> <span class="nc">stat</span> <span class="n">info</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">lstat</span><span class="p">(</span><span class="n">target</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">info</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">info</span><span class="p">.</span><span class="n">st_mode</span> <span class="o">&amp;</span> <span class="n">S_IFMT</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_IFLNK</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">unlink</span><span class="p">(</span><span class="n">target</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
    <span class="p">}</span>
    <span class="n">mkdir</span><span class="p">(</span><span class="n">target</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="mo">0755</span><span class="p">);</span>
    <span class="n">errno</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">mountflags</span> <span class="o">=</span> <span class="n">entry</span><span class="p">.</span><span class="n">flags</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">save_errno</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">do</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">save_errno</span> <span class="o">==</span> <span class="n">EAGAIN</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">PINFO</span> <span class="o">&lt;&lt;</span> <span class="s">"Retrying mount (source="</span> <span class="o">&lt;&lt;</span> <span class="n">source</span> <span class="o">&lt;&lt;</span> <span class="s">",target="</span> <span class="o">&lt;&lt;</span> <span class="n">target</span>
                  <span class="o">&lt;&lt;</span> <span class="s">",type="</span> <span class="o">&lt;&lt;</span> <span class="n">entry</span><span class="p">.</span><span class="n">fs_type</span> <span class="o">&lt;&lt;</span> <span class="s">")="</span> <span class="o">&lt;&lt;</span> <span class="n">ret</span> <span class="o">&lt;&lt;</span> <span class="s">"("</span> <span class="o">&lt;&lt;</span> <span class="n">save_errno</span> <span class="o">&lt;&lt;</span> <span class="s">")"</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="c1">//执行挂载命令</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">mount</span><span class="p">(</span><span class="n">source</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">target</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">entry</span><span class="p">.</span><span class="n">fs_type</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">mountflags</span><span class="p">,</span>
                    <span class="n">entry</span><span class="p">.</span><span class="n">fs_options</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
        <span class="n">save_errno</span> <span class="o">=</span> <span class="n">errno</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&amp;&amp;</span> <span class="n">save_errno</span> <span class="o">==</span> <span class="n">EAGAIN</span><span class="p">);</span>
    <span class="p">.....</span>
    <span class="c1">//日志打印，可在内核日志中查看</span>
    <span class="n">PINFO</span> <span class="o">&lt;&lt;</span> <span class="n">__FUNCTION__</span> <span class="o">&lt;&lt;</span> <span class="s">"(source="</span> <span class="o">&lt;&lt;</span> <span class="n">source</span> <span class="o">&lt;&lt;</span> <span class="n">source_missing</span> <span class="o">&lt;&lt;</span> <span class="s">",target="</span> <span class="o">&lt;&lt;</span> <span class="n">target</span>
          <span class="o">&lt;&lt;</span> <span class="n">target_missing</span> <span class="o">&lt;&lt;</span> <span class="s">",type="</span> <span class="o">&lt;&lt;</span> <span class="n">entry</span><span class="p">.</span><span class="n">fs_type</span> <span class="o">&lt;&lt;</span> <span class="s">")="</span> <span class="o">&lt;&lt;</span> <span class="n">ret</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="33-2setupselinux--权限策略初始化配置">3.3. （2）SetupSelinux – 权限策略初始化配置</h2>

<blockquote>
  <p>在上一个步骤执行的最后，会调用”/system/bin/init”入参selinux_setup，进入第二个步骤selinux的初始化设置，调用SetupSelinux方法。
在执行结束后入参second_stage，调用SecondStageMain方法</p>
</blockquote>

<h3 id="331-序列图">3.3.1. 序列图</h3>

<p><img src="../../assets/post/2025/2025-02-27-android_android系统启动流程_1_init进程启动流程/3-init进程启动第二阶段.svg" alt="" /></p>

<hr />

<h3 id="332-编译selinux方式">3.3.2. 编译SElinux方式</h3>

<p>编译命令：<code class="language-plaintext highlighter-rouge">make sepolicy -j48</code>或<code class="language-plaintext highlighter-rouge">make selinux_policy -j48</code></p>

<p>如果make编译过了，可使用ninja编译，不到一分钟就可以编译完成:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">time prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-XXXX.ninja -j48 selinux_policy</code></li>
  <li><code class="language-plaintext highlighter-rouge">time prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-XXXX.ninja -j48 sepolicy.recovery</code></li>
</ul>

<p>编译结果会把生成的文件<code class="language-plaintext highlighter-rouge">\out\target\product\XXXX\obj\ETC\vendor_sepolicy.cil_intermediates\vendor_sepolicy.cil</code>push到<code class="language-plaintext highlighter-rouge">out\target\product\XXXX\vendor\etc\selinux\vendor_sepolicy.cil</code>(文件系统<code class="language-plaintext highlighter-rouge">/vendor/etc/selinux</code>) 目录下</p>

<hr />

<h3 id="333-流程说明">3.3.3. 流程说明</h3>

<blockquote>
  <p>参考：<a href="https://blog.csdn.net/lfcaolibin/article/details/43674569">SEAndroid安全机制框架分析</a>
参考：<a href="https://www.jb51.net/article/277418.htm">Android selinux策略文件的编译与加载</a></p>
</blockquote>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mf">1.</span><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">first_stage_init</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span> <span class="n">FirstStageMain</span><span class="err">调用</span><span class="n">init</span><span class="err">程序，入参</span><span class="n">selinux_setup</span>
<span class="mf">2.</span><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">main</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span> <span class="n">main</span><span class="err">函数执行</span><span class="n">SetupSelinux</span>
<span class="mf">3.</span><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">selinux</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span> <span class="n">SetupSelinux</span>
    <span class="o">---&gt;</span> <span class="mf">3.1</span> <span class="err">调用</span><span class="n">SelinuxInitialize</span><span class="p">()</span>
        <span class="o">---&gt;</span> <span class="mf">3.1.1</span> <span class="err">调用</span><span class="n">LoadPolicy</span><span class="p">()</span>
            <span class="o">---&gt;</span> <span class="err">调用</span><span class="n">IsSplitPolicyDevice</span><span class="p">()</span><span class="err">判断是否存在</span><span class="s">"/system/etc/selinux/plat_sepolicy.cil"</span><span class="err">文件，正常都存在该文件</span>
            <span class="o">---&gt;</span> <span class="err">调用</span><span class="n">LoadSplitPolicy</span><span class="p">()</span><span class="err">，此方法最主要的就是调用</span><span class="n">FindPrecompiledSplitPolicy</span>
            <span class="o">---&gt;</span> <span class="n">FindPrecompiledSplitPolicy</span><span class="err">依次从</span><span class="o">/</span><span class="n">vendor</span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">selinux</span><span class="o">/</span><span class="n">precompiled_sepolicy</span><span class="err">和</span><span class="o">/</span><span class="n">odm</span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">selinux</span><span class="o">/</span><span class="n">precompiled_sepolicy</span><span class="err">中寻找</span><span class="n">selinux</span><span class="err">预编译文件，一般是从</span><span class="o">/</span><span class="n">vendor</span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">selinux</span><span class="o">/</span>   <span class="n">precompiled_sepolicy</span><span class="err">中获取</span><span class="n">selinux</span><span class="err">文件！</span>
            <span class="o">---&gt;</span> <span class="err">调用</span><span class="n">selinux_android_load_policy_from_fd</span><span class="err">方法，入参</span><span class="n">selinux</span><span class="err">文件</span>
            <span class="o">---&gt;</span> <span class="n">external</span><span class="o">/</span><span class="n">selinux</span><span class="o">/</span><span class="n">libselinux</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">android</span><span class="o">/</span><span class="n">android_platform</span><span class="p">.</span><span class="n">c</span> <span class="o">-</span> <span class="n">selinux_android_load_policy_from_fd</span>
            <span class="o">---&gt;</span> <span class="n">external</span><span class="o">/</span><span class="n">selinux</span><span class="o">/</span><span class="n">libselinux</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">load_policy</span><span class="p">.</span><span class="n">c</span> <span class="o">-</span> <span class="n">security_load_policy</span><span class="err">将</span><span class="n">sepolicy</span><span class="err">写入到</span><span class="n">selinuxfs</span><span class="err">文件系统的</span><span class="n">load</span><span class="err">文件中</span>

        <span class="o">---&gt;</span> <span class="mf">3.1.2</span> <span class="err">调用</span><span class="n">IsEnforcing</span><span class="err">，然后调用</span><span class="n">StatusFromCmdline</span><span class="p">()</span><span class="err">获取</span><span class="n">android</span><span class="err">自定义的</span><span class="n">selinux</span><span class="err">权限状态，即从</span><span class="o">/</span><span class="n">proc</span><span class="o">/</span><span class="n">cmdline</span><span class="err">中读取</span><span class="n">androidboot</span><span class="p">.</span><span class="n">selinux</span><span class="err">的值</span>
            <span class="o">---&gt;</span> <span class="err">调用</span><span class="n">security_setenforce</span><span class="err">，入参</span><span class="n">IsEnforcing</span><span class="err">，设置当前</span><span class="n">selinux</span><span class="err">状态，将其值</span><span class="mi">0</span><span class="o">/</span><span class="mi">1</span><span class="err">写入</span><span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">fs</span><span class="o">/</span><span class="n">selinux</span><span class="o">/</span><span class="n">enforce</span><span class="err">节点</span>
            <span class="o">---&gt;</span> <span class="err">将</span><span class="mi">0</span><span class="err">写入</span><span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">fs</span><span class="o">/</span><span class="n">selinux</span><span class="o">/</span><span class="n">checkreqprot</span><span class="err">节点，该值决定</span><span class="n">selinux</span><span class="err">通过程序</span><span class="s">"1"</span><span class="err">还是内核</span><span class="s">"0"</span><span class="err">响应进行安全检查</span>
    <span class="o">---&gt;</span> <span class="mf">3.2</span> <span class="err">执行</span><span class="s">"/system/bin/init"</span><span class="err">，入参</span><span class="n">second_stage</span><span class="err">，进入下一个步骤</span>
</code></pre></div></div>

<hr />

<h3 id="334-代码说明">3.3.4. 代码说明</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/first_stage_init.cpp</span>
<span class="kt">int</span> <span class="nf">FirstStageMain</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="c1">//此处将执行init启动的第二个步骤，就是传入参数"selinux_setup"，执行SetupSelinux</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">path</span> <span class="o">=</span> <span class="s">"/system/bin/init"</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">args</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="n">path</span><span class="p">,</span> <span class="s">"selinux_setup"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">};</span>
    <span class="n">execv</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="k">const_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">**&gt;</span><span class="p">(</span><span class="n">args</span><span class="p">));</span>
    <span class="p">....</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/selinux.cpp</span>
<span class="c1">// This function initializes SELinux then execs init to run in the init SELinux context.</span>
<span class="kt">int</span> <span class="nf">SetupSelinux</span><span class="p">(</span><span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">//初始化kernel内核日志</span>
    <span class="n">InitKernelLogging</span><span class="p">(</span><span class="n">argv</span><span class="p">);</span>
    <span class="c1">//异常重启</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">REBOOT_BOOTLOADER_ON_PANIC</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">InstallRebootSignalHandlers</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="c1">// Set up SELinux, loading the SELinux policy.</span>
    <span class="c1">//将selinux日志重定向到内核日志输出(dev/kmsg)</span>
    <span class="n">SelinuxSetupKernelLogging</span><span class="p">();</span>
    <span class="c1">//Step 1：初始化selinux策略</span>
    <span class="n">SelinuxInitialize</span><span class="p">();</span>
    <span class="p">.....</span>
    <span class="c1">//Step 2：继续下一个init启动步骤，入参second_stage，调用SecondStageMain</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">path</span> <span class="o">=</span> <span class="s">"/system/bin/init"</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">args</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="n">path</span><span class="p">,</span> <span class="s">"second_stage"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">};</span>
    <span class="n">execv</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="k">const_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">**&gt;</span><span class="p">(</span><span class="n">args</span><span class="p">));</span>

    <span class="c1">// execv() only returns if an error happened, in which case we</span>
    <span class="c1">// panic and never return from this function.</span>
    <span class="n">PLOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"execv(</span><span class="se">\"</span><span class="s">"</span> <span class="o">&lt;&lt;</span> <span class="n">path</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\"</span><span class="s">) failed"</span><span class="p">;</span>

    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">//IsSplitPolicyDevice()判断是否存在"/system/etc/selinux/plat_sepolicy.cil"文件，正常都存在该文件，执行前者</span>
<span class="c1">//LoadSplitPolicy最主要的就是调用FindPrecompiledSplitPolicy：</span>
<span class="c1">//依次从/vendor/etc/selinux/precompiled_sepolicy"和"/odm/etc/selinux/precompiled_sepolicy"中寻找selinux预编译文件</span>
<span class="c1">//一般是从"/vendor/etc/selinux/precompiled_sepolicy"中获取selinux文件！</span>
<span class="k">constexpr</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">plat_policy_cil_file</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"/system/etc/selinux/plat_sepolicy.cil"</span><span class="p">;</span>

<span class="kt">bool</span> <span class="n">IsSplitPolicyDevice</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">access</span><span class="p">(</span><span class="n">plat_policy_cil_file</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">bool</span> <span class="n">LoadPolicy</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="n">IsSplitPolicyDevice</span><span class="p">()</span> <span class="o">?</span> <span class="n">LoadSplitPolicy</span><span class="p">()</span> <span class="o">:</span> <span class="n">LoadMonolithicPolicy</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">SelinuxInitialize</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">Timer</span> <span class="n">t</span><span class="p">;</span>

    <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Loading SELinux policy"</span><span class="p">;</span>
    <span class="c1">//Step 1：加载policy文件</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">LoadPolicy</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Unable to load SELinux policy"</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//获取内核selinux状态</span>
    <span class="kt">bool</span> <span class="n">kernel_enforcing</span> <span class="o">=</span> <span class="p">(</span><span class="n">security_getenforce</span><span class="p">()</span> <span class="o">==</span> <span class="mi">1</span><span class="p">);</span>
    <span class="c1">//获取Android自定义selinux状态</span>
    <span class="kt">bool</span> <span class="n">is_enforcing</span> <span class="o">=</span> <span class="n">IsEnforcing</span><span class="p">();</span>
    <span class="c1">//判断是否一样，设置当前selinux状态，就是将值0/1写入到/sys/fs/selinux/enforce节点</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">kernel_enforcing</span> <span class="o">!=</span> <span class="n">is_enforcing</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">security_setenforce</span><span class="p">(</span><span class="n">is_enforcing</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">PLOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"security_setenforce(%s) failed"</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="n">is_enforcing</span> <span class="o">?</span> <span class="s">"true"</span> <span class="o">:</span> <span class="s">"false"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">//设置节点0，该值决定selinux通过程序"1"还是内核"0"响应进行安全检查</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">WriteFile</span><span class="p">(</span><span class="s">"/sys/fs/selinux/checkreqprot"</span><span class="p">,</span> <span class="s">"0"</span><span class="p">);</span> <span class="o">!</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Unable to write to /sys/fs/selinux/checkreqprot: "</span> <span class="o">&lt;&lt;</span> <span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="c1">// init's first stage can't set properties, so pass the time to the second stage.</span>
    <span class="c1">//获取selinux策略加载时间并设置到环境遍历INIT_SELINUX_TOOK</span>
    <span class="n">setenv</span><span class="p">(</span><span class="s">"INIT_SELINUX_TOOK"</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">to_string</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">duration</span><span class="p">().</span><span class="n">count</span><span class="p">()).</span><span class="n">c_str</span><span class="p">(),</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">LoadSplitPolicy</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// 在adb shell查看echo $INIT_FORCE_DEBUGGABLE值为空，所以use_userdebug_policy是false</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">force_debuggable_env</span> <span class="o">=</span> <span class="n">getenv</span><span class="p">(</span><span class="s">"INIT_FORCE_DEBUGGABLE"</span><span class="p">);</span>
    <span class="kt">bool</span> <span class="n">use_userdebug_policy</span> <span class="o">=</span>
            <span class="p">((</span><span class="n">force_debuggable_env</span> <span class="o">&amp;&amp;</span> <span class="s">"true"</span><span class="n">s</span> <span class="o">==</span> <span class="n">force_debuggable_env</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
             <span class="n">AvbHandle</span><span class="o">::</span><span class="n">IsDeviceUnlocked</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">access</span><span class="p">(</span><span class="n">kDebugRamdiskSEPolicy</span><span class="p">,</span> <span class="n">F_OK</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">use_userdebug_policy</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">WARNING</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Using userdebug system sepolicy"</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">precompiled_sepolicy_file</span><span class="p">;</span>
    <span class="c1">//核心函数FindPrecompiledSplitPolicy</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">use_userdebug_policy</span> <span class="o">&amp;&amp;</span> <span class="n">FindPrecompiledSplitPolicy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">precompiled_sepolicy_file</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">unique_fd</span> <span class="n">fd</span><span class="p">(</span><span class="n">open</span><span class="p">(</span><span class="n">precompiled_sepolicy_file</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">O_RDONLY</span> <span class="o">|</span> <span class="n">O_CLOEXEC</span> <span class="o">|</span> <span class="n">O_BINARY</span><span class="p">));</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">fd</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">//核心函数selinux_android_load_policy_from_fd</span>
            <span class="c1">//入参是打开/vendor/etc/selinux/precompiled_sepolicy的fd节点</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">selinux_android_load_policy_from_fd</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">precompiled_sepolicy_file</span><span class="p">.</span><span class="n">c_str</span><span class="p">())</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to load SELinux policy from "</span> <span class="o">&lt;&lt;</span> <span class="n">precompiled_sepolicy_file</span><span class="p">;</span>
                <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="c1">//返回</span>
            <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">// No suitable precompiled policy could be loaded</span>
    <span class="p">.....</span>
    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">//主要是对比两对文件的sha256值，如果一致则返回true（一般返回true）</span>
<span class="kt">bool</span> <span class="n">FindPrecompiledSplitPolicy</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">*</span> <span class="n">file</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">file</span><span class="o">-&gt;</span><span class="n">clear</span><span class="p">();</span>
    <span class="c1">//依次从/vendor/etc/selinux/precompiled_sepolicy和/odm/etc/selinux/precompiled_sepolicy中寻找selinux预编译文件，</span>
    <span class="c1">//一般是从/vendor/etc/selinux/precompiled_sepolicy中获取selinux文件</span>
    <span class="c1">//file值是/vendor/etc/selinux/precompiled_sepolicy</span>
    <span class="k">static</span> <span class="k">constexpr</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">vendor_precompiled_sepolicy</span><span class="p">[]</span> <span class="o">=</span>
        <span class="s">"/vendor/etc/selinux/precompiled_sepolicy"</span><span class="p">;</span>
    <span class="k">static</span> <span class="k">constexpr</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">odm_precompiled_sepolicy</span><span class="p">[]</span> <span class="o">=</span>
        <span class="s">"/odm/etc/selinux/precompiled_sepolicy"</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="n">odm_precompiled_sepolicy</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">*</span><span class="n">file</span> <span class="o">=</span> <span class="n">odm_precompiled_sepolicy</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="n">vendor_precompiled_sepolicy</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">*</span><span class="n">file</span> <span class="o">=</span> <span class="n">vendor_precompiled_sepolicy</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"No precompiled sepolicy"</span><span class="p">;</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//此处对比/system/etc/selinux/plat_sepolicy_and_mapping.sha256和/vendor/etc/selinux/precompiled_sepolicy.plat_sepolicy_and_mapping.sha256的值</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">actual_plat_id</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadFirstLine</span><span class="p">(</span><span class="s">"/system/etc/selinux/plat_sepolicy_and_mapping.sha256"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">actual_plat_id</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to read "</span>
                      <span class="s">"/system/etc/selinux/plat_sepolicy_and_mapping.sha256"</span><span class="p">;</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">//此处对比/product/etc/selinux/product_sepolicy_and_mapping.sha256和/vendor/etc/selinux/precompiled_sepolicy.product_sepolicy_and_mapping.sha256的值</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">actual_product_id</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadFirstLine</span><span class="p">(</span><span class="s">"/product/etc/selinux/product_sepolicy_and_mapping.sha256"</span><span class="p">,</span>
                       <span class="o">&amp;</span><span class="n">actual_product_id</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to read "</span>
                      <span class="s">"/product/etc/selinux/product_sepolicy_and_mapping.sha256"</span><span class="p">;</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">precompiled_plat_id</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">precompiled_plat_sha256</span> <span class="o">=</span> <span class="o">*</span><span class="n">file</span> <span class="o">+</span> <span class="s">".plat_sepolicy_and_mapping.sha256"</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadFirstLine</span><span class="p">(</span><span class="n">precompiled_plat_sha256</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">precompiled_plat_id</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to read "</span> <span class="o">&lt;&lt;</span> <span class="n">precompiled_plat_sha256</span><span class="p">;</span>
        <span class="n">file</span><span class="o">-&gt;</span><span class="n">clear</span><span class="p">();</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">precompiled_product_id</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">precompiled_product_sha256</span> <span class="o">=</span> <span class="o">*</span><span class="n">file</span> <span class="o">+</span> <span class="s">".product_sepolicy_and_mapping.sha256"</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadFirstLine</span><span class="p">(</span><span class="n">precompiled_product_sha256</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">precompiled_product_id</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to read "</span> <span class="o">&lt;&lt;</span> <span class="n">precompiled_product_sha256</span><span class="p">;</span>
        <span class="n">file</span><span class="o">-&gt;</span><span class="n">clear</span><span class="p">();</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">actual_plat_id</span><span class="p">.</span><span class="n">empty</span><span class="p">()</span> <span class="o">||</span> <span class="n">actual_plat_id</span> <span class="o">!=</span> <span class="n">precompiled_plat_id</span> <span class="o">||</span>
        <span class="n">actual_product_id</span><span class="p">.</span><span class="n">empty</span><span class="p">()</span> <span class="o">||</span> <span class="n">actual_product_id</span> <span class="o">!=</span> <span class="n">precompiled_product_id</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">file</span><span class="o">-&gt;</span><span class="n">clear</span><span class="p">();</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>本地在设备的文件目录下查看这两对文件的内容值：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Product:/system/etc/selinux <span class="c"># more plat_sepolicy_and_mapping.sha256</span>
ee0de9c3569fc8123fdbe7ab09cbf241cbf14933b745cbed1e5d689608890258
Product:/system/etc/selinux <span class="c">#</span>
Product:/ <span class="c"># cd vendor/etc/selinux/</span>
Product:/vendor/etc/selinux <span class="c"># more precompiled_sepolicy.plat_sepolicy_and_mapping.sha256</span>
ee0de9c3569fc8123fdbe7ab09cbf241cbf14933b745cbed1e5d689608890258
Product:/vendor/etc/selinux <span class="c">#</span>
Product:/vendor/etc/selinux <span class="c"># more precompiled_sepolicy.product_sepolicy_and_mapping.sha256</span>
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Product:/vendor/etc/selinux <span class="c"># more /pro</span>
proc/             product/          product_services
Product:/vendor/etc/selinux <span class="c"># more /produ</span>
product/          product_services
Product:/vendor/etc/selinux <span class="c"># more /product/etc/selinux/product_sepolicy_and_mapping.sha256</span>
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
</code></pre></div></div>

<p>在之后就是调用selinux_android_load_policy_from_fd方法，入参就是FindPrecompiledSplitPolicy方法查找到的”/vendor/etc/selinux/precompiled_sepolicy”文件。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//external/selinux/libselinux/src/android/android_platform.c</span>
<span class="kt">int</span> <span class="nf">selinux_android_load_policy_from_fd</span><span class="p">(</span><span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">description</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">rc</span><span class="p">;</span>
	<span class="k">struct</span> <span class="nc">stat</span> <span class="n">sb</span><span class="p">;</span>
	<span class="kt">void</span> <span class="o">*</span><span class="n">map</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="k">static</span> <span class="kt">int</span> <span class="n">load_successful</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">...</span>
	<span class="n">set_selinuxmnt</span><span class="p">(</span><span class="n">SELINUXMNT</span><span class="p">);</span>
    <span class="c1">//将fd文件状态复制到sb对象</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">fstat</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sb</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">selinux_log</span><span class="p">(</span><span class="n">SELINUX_ERROR</span><span class="p">,</span> <span class="s">"SELinux:  Could not stat %s:  %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
				<span class="n">description</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>
    <span class="c1">//文件映射到内存</span>
	<span class="n">map</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">sb</span><span class="p">.</span><span class="n">st_size</span><span class="p">,</span> <span class="n">PROT_READ</span><span class="p">,</span> <span class="n">MAP_PRIVATE</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">map</span> <span class="o">==</span> <span class="n">MAP_FAILED</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">selinux_log</span><span class="p">(</span><span class="n">SELINUX_ERROR</span><span class="p">,</span> <span class="s">"SELinux:  Could not map %s:  %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
				<span class="n">description</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>
    <span class="c1">//该函数由selinux.h定义，它将此map即sepolicy文件加载到/sys/fs/selinux/load内核中</span>
    <span class="c1">//第一个步骤中创建了selinuxfs文件系统，目录就是/sys/fs/selinux</span>
    <span class="c1">//由于/sys/fs/selinux是由内核空间的SELinux LSM模块导出来的文件系统接口，因此当我们将安全策略写入到位于该文件系统中的load文件时，就相当于是将安全策略从用户空间加载到SELinux LSM模块中去了。以后SELinux LSM模块中的Security Server就可以通过它来进行安全检查</span>
    <span class="c1">//external/selinux/libselinux/src/load_policy.c</span>
	<span class="n">rc</span> <span class="o">=</span> <span class="n">security_load_policy</span><span class="p">(</span><span class="n">map</span><span class="p">,</span> <span class="n">sb</span><span class="p">.</span><span class="n">st_size</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">rc</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">selinux_log</span><span class="p">(</span><span class="n">SELINUX_ERROR</span><span class="p">,</span> <span class="s">"SELinux:  Could not load policy:  %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
				<span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
		<span class="n">munmap</span><span class="p">(</span><span class="n">map</span><span class="p">,</span> <span class="n">sb</span><span class="p">.</span><span class="n">st_size</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">munmap</span><span class="p">(</span><span class="n">map</span><span class="p">,</span> <span class="n">sb</span><span class="p">.</span><span class="n">st_size</span><span class="p">);</span>
	<span class="n">selinux_log</span><span class="p">(</span><span class="n">SELINUX_INFO</span><span class="p">,</span> <span class="s">"SELinux: Loaded policy from %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">description</span><span class="p">);</span>
	<span class="n">load_successful</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="34-3secondstagemain--启动init进程第三阶段">3.4. （3）SecondStageMain – 启动init进程第三阶段</h2>

<h3 id="341-流程说明">3.4.1. 流程说明</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mf">1.</span><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">selinux</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span> <span class="n">SetupSelinux</span><span class="err">调用</span><span class="n">init</span><span class="err">程序，执行</span><span class="s">"/system/bin/init"</span><span class="err">，入参</span><span class="n">second_stage</span><span class="err">，进入下一个步骤</span>
<span class="mf">2.</span><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">main</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span> <span class="n">main</span><span class="err">函数执行</span><span class="n">SecondStageMain</span>
<span class="mf">3.</span><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">init</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span> <span class="n">SecondStageMain</span>
    <span class="o">---&gt;</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">property_init</span> <span class="err">读取属性文件写入到</span><span class="s">"/dev/__properties__/property_info"</span><span class="err">然后加载到共享内存</span>
    <span class="o">---&gt;</span> <span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="n">process_kernel_dt</span> <span class="err">处理设备树中定义的属性，设置并赋值为</span><span class="s">"ro.boot.***"</span><span class="err">属性</span>
    <span class="o">---&gt;</span> <span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="n">process_kernel_cmdline</span> <span class="err">读取</span><span class="n">proc</span><span class="o">/</span><span class="n">cmdline</span><span class="err">并将</span><span class="s">"androidboot.***"</span><span class="err">的值设置为</span><span class="s">"ro.boot.***"</span><span class="err">属性</span>
    <span class="o">---&gt;</span> <span class="p">(</span><span class="mi">4</span><span class="p">)</span> <span class="n">SelabelInitialize</span><span class="p">()</span><span class="err">初始化</span><span class="n">selinux</span><span class="err">，</span><span class="n">SelinuxRestoreContext</span><span class="p">()</span><span class="err">恢复</span><span class="n">selinux</span><span class="err">上下文信息</span>
    <span class="o">---&gt;</span> <span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="n">InstallSignalFdHandler</span><span class="err">信号处理</span>
    <span class="o">---&gt;</span> <span class="p">(</span><span class="mi">6</span><span class="p">)</span> <span class="n">property_load_boot_defaults</span><span class="err">和</span><span class="n">StartPropertyService</span><span class="err">加载开机默认属性配置以及启动属性服务</span>
    <span class="o">---&gt;</span> <span class="p">(</span><span class="mi">7</span><span class="p">)</span> <span class="n">LoadBootScripts</span><span class="err">加载解析</span><span class="n">init</span><span class="p">.</span><span class="n">rc</span><span class="err">等</span><span class="n">rc</span><span class="err">文件</span>
    <span class="o">---&gt;</span> <span class="p">(</span><span class="mi">8</span><span class="p">)</span> <span class="err">通过</span><span class="n">action</span><span class="err">机制按</span><span class="n">rc</span><span class="err">文件的</span><span class="n">session</span><span class="err">顺序运行</span><span class="n">rc</span><span class="err">文件</span><span class="s">"on early-init -&gt; on init -&gt; on late-init"</span>
</code></pre></div></div>

<h3 id="342-序列图">3.4.2. 序列图</h3>

<p><img src="../../assets/post/2025/2025-02-27-android_android系统启动流程_1_init进程启动流程/4-init进程启动第三阶段.svg" alt="" /></p>

<hr />

<h3 id="343-入口函数代码说明">3.4.3. 入口函数代码说明</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/selinux.cpp</span>
<span class="kt">int</span> <span class="nf">SetupSelinux</span><span class="p">(</span><span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="c1">//此处将执行init启动的第三个步骤，就是传入参数"second_stage"</span>
    <span class="c1">//然后执行system/core/init/main.cpp - main函数的SecondStageMain</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">path</span> <span class="o">=</span> <span class="s">"/system/bin/init"</span><span class="p">;</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">args</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="n">path</span><span class="p">,</span> <span class="s">"second_stage"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">};</span>
    <span class="n">execv</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="k">const_cast</span><span class="o">&lt;</span><span class="kt">char</span><span class="o">**&gt;</span><span class="p">(</span><span class="n">args</span><span class="p">));</span>

    <span class="c1">// execv() only returns if an error happened, in which case we</span>
    <span class="c1">// panic and never return from this function.</span>
    <span class="n">PLOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"execv(</span><span class="se">\"</span><span class="s">"</span> <span class="o">&lt;&lt;</span> <span class="n">path</span> <span class="o">&lt;&lt;</span> <span class="s">"</span><span class="se">\"</span><span class="s">) failed"</span><span class="p">;</span>

    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>执行system/core/init/main.cpp - main函数的SecondStageMain：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/init.cpp</span>
<span class="kt">int</span> <span class="nf">SecondStageMain</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="c1">//内核日志打印</span>
    <span class="n">InitKernelLogging</span><span class="p">(</span><span class="n">argv</span><span class="p">);</span>
    <span class="n">LOG</span><span class="p">(</span><span class="n">INFO</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"init second stage started!"</span><span class="p">;</span>

    <span class="c1">//设置init进程及fork的子进程的优先级（-1000最高）</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">WriteFile</span><span class="p">(</span><span class="s">"/proc/1/oom_score_adj"</span><span class="p">,</span> <span class="s">"-1000"</span><span class="p">);</span> <span class="o">!</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Unable to write -1000 to /proc/1/oom_score_adj: "</span> <span class="o">&lt;&lt;</span> <span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="c1">//从/proc/cmdline中读取androidboot.seccomp是否是global值以启动全局seccomp</span>
    <span class="c1">//Secure Computing mode是Linux内核提供的一种安全机制，用于在用户态应用程序执行系统调用时进行过滤和限制。</span>
    <span class="c1">//其原理是基于对系统调用号的过滤和限制，以及对系统调用参数的校验</span>
    <span class="n">GlobalSeccomp</span><span class="p">();</span>
    <span class="c1">//设置所有进程都可以访问的会话密钥环</span>
    <span class="n">keyctl_get_keyring_ID</span><span class="p">(</span><span class="n">KEY_SPEC_SESSION_KEYRING</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="c1">//创建 /dev/.booting 文件，就是个标记，表示booting进行中</span>
    <span class="n">close</span><span class="p">(</span><span class="n">open</span><span class="p">(</span><span class="s">"/dev/.booting"</span><span class="p">,</span> <span class="n">O_WRONLY</span> <span class="o">|</span> <span class="n">O_CREAT</span> <span class="o">|</span> <span class="n">O_CLOEXEC</span><span class="p">,</span> <span class="mo">0000</span><span class="p">));</span>

    <span class="c1">//--------Step 1: 初始化属性系统，并从指定文件读取属性</span>
    <span class="n">property_init</span><span class="p">();</span>

    <span class="c1">// If arguments are passed both on the command line and in DT,</span>
    <span class="c1">// properties set in DT always have priority over the command-line ones.</span>
    <span class="c1">//处理DT属性，DT即device-tree设备树，这里面记录自己的硬件配置和系统运行参数</span>
    <span class="n">process_kernel_dt</span><span class="p">();</span>
    <span class="c1">//处理命令行属性，即/proc/cmdline</span>
    <span class="n">process_kernel_cmdline</span><span class="p">();</span>

    <span class="c1">// Propagate the kernel variables to internal variables</span>
    <span class="c1">// used by init as well as the current required properties.</span>
    <span class="c1">//将内核变量传播到init使用的内部变量以及当前所需的属性</span>
    <span class="c1">// 处理一些其他的属性(ro属性)</span>
    <span class="n">export_kernel_boot_props</span><span class="p">();</span>

    <span class="c1">// Make the time that init started available for bootstat to log.</span>
    <span class="n">property_set</span><span class="p">(</span><span class="s">"ro.boottime.init"</span><span class="p">,</span> <span class="n">getenv</span><span class="p">(</span><span class="s">"INIT_STARTED_AT"</span><span class="p">));</span>
    <span class="n">property_set</span><span class="p">(</span><span class="s">"ro.boottime.init.selinux"</span><span class="p">,</span> <span class="n">getenv</span><span class="p">(</span><span class="s">"INIT_SELINUX_TOOK"</span><span class="p">));</span>

    <span class="c1">// Set libavb version for Framework-only OTA match in Treble build.</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">avb_version</span> <span class="o">=</span> <span class="n">getenv</span><span class="p">(</span><span class="s">"INIT_AVB_VERSION"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">avb_version</span><span class="p">)</span> <span class="n">property_set</span><span class="p">(</span><span class="s">"ro.boot.avb_version"</span><span class="p">,</span> <span class="n">avb_version</span><span class="p">);</span>

    <span class="c1">// 在adb shell查看echo $INIT_FORCE_DEBUGGABLE值为空，所以force_debuggable_env是false，所以load_debug_prop是默认值false</span>
    <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">force_debuggable_env</span> <span class="o">=</span> <span class="n">getenv</span><span class="p">(</span><span class="s">"INIT_FORCE_DEBUGGABLE"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">force_debuggable_env</span> <span class="o">&amp;&amp;</span> <span class="n">AvbHandle</span><span class="o">::</span><span class="n">IsDeviceUnlocked</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">load_debug_prop</span> <span class="o">=</span> <span class="s">"true"</span><span class="n">s</span> <span class="o">==</span> <span class="n">force_debuggable_env</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// Clean up our environment.</span>
    <span class="c1">//上面将环境变量设置到ro属性中，此处清空环境变量</span>
    <span class="n">unsetenv</span><span class="p">(</span><span class="s">"INIT_STARTED_AT"</span><span class="p">);</span>
    <span class="n">unsetenv</span><span class="p">(</span><span class="s">"INIT_SELINUX_TOOK"</span><span class="p">);</span>
    <span class="n">unsetenv</span><span class="p">(</span><span class="s">"INIT_AVB_VERSION"</span><span class="p">);</span>
    <span class="n">unsetenv</span><span class="p">(</span><span class="s">"INIT_FORCE_DEBUGGABLE"</span><span class="p">);</span>

    <span class="c1">//-----Step 2:进行Selinux第二阶段工作</span>
    <span class="c1">//将selinux日志重定向到内核日志输出(dev/kmsg)</span>
    <span class="n">SelinuxSetupKernelLogging</span><span class="p">();</span>
    <span class="c1">//第二阶段初始化selinux</span>
    <span class="n">SelabelInitialize</span><span class="p">();</span>
    <span class="c1">//恢复安全上下文，进行SELinux第二阶段并恢复一些文件安全上下文 </span>
    <span class="c1">//恢复相关文件的安全上下文,因为这些文件是在SELinux安全机制初始化前创建的，所以需要重新恢复上下文</span>
    <span class="n">SelinuxRestoreContext</span><span class="p">();</span>

    <span class="c1">//-----Step 3: 创建epoll句柄并初始化子进程终止信号处理函数</span>
    <span class="c1">//要是创建handler处理子进程终止信号，注册一个signal到epoll进行监听，进行子继承处理</span>
    <span class="n">Epoll</span> <span class="n">epoll</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">epoll</span><span class="p">.</span><span class="n">Open</span><span class="p">();</span> <span class="o">!</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="n">InstallSignalFdHandler</span><span class="p">(</span><span class="o">&amp;</span><span class="n">epoll</span><span class="p">);</span>

    <span class="c1">//-----Step 4:加载开机默认属性配置以及启动属性服务（同第一步都是系统属性相关）</span>
    <span class="n">property_load_boot_defaults</span><span class="p">(</span><span class="n">load_debug_prop</span><span class="p">);</span>
    <span class="n">UmountDebugRamdisk</span><span class="p">();</span>
    <span class="n">fs_mgr_vendor_overlay_mount_all</span><span class="p">();</span>
    <span class="n">export_oem_lock_status</span><span class="p">();</span>
    <span class="c1">// 设置其他系统属性并开启系统属性服务</span>
    <span class="n">StartPropertyService</span><span class="p">(</span><span class="o">&amp;</span><span class="n">epoll</span><span class="p">);</span>
    <span class="n">MountHandler</span> <span class="n">mount_handler</span><span class="p">(</span><span class="o">&amp;</span><span class="n">epoll</span><span class="p">);</span>
    <span class="c1">//为USB存储设置udc Contorller, sys/class/udc</span>
    <span class="n">set_usb_controller</span><span class="p">();</span>
    <span class="c1">//匹配命令和函数之间的对应关系</span>
    <span class="k">const</span> <span class="n">BuiltinFunctionMap</span> <span class="n">function_map</span><span class="p">;</span>
    <span class="n">Action</span><span class="o">::</span><span class="n">set_function_map</span><span class="p">(</span><span class="o">&amp;</span><span class="n">function_map</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">SetupMountNamespaces</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"SetupMountNamespaces failed"</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">// 初始化文件上下文</span>
    <span class="n">subcontexts</span> <span class="o">=</span> <span class="n">InitializeSubcontexts</span><span class="p">();</span>

    <span class="c1">//-----Step 5: 解析init.rc等文件，建立rc文件的action 、service，启动其他进程</span>
    <span class="n">ActionManager</span><span class="o">&amp;</span> <span class="n">am</span> <span class="o">=</span> <span class="n">ActionManager</span><span class="o">::</span><span class="n">GetInstance</span><span class="p">();</span>
    <span class="n">ServiceList</span><span class="o">&amp;</span> <span class="n">sm</span> <span class="o">=</span> <span class="n">ServiceList</span><span class="o">::</span><span class="n">GetInstance</span><span class="p">();</span>
    <span class="n">LoadBootScripts</span><span class="p">(</span><span class="n">am</span><span class="p">,</span> <span class="n">sm</span><span class="p">);</span>

    <span class="c1">// Make the GSI status available before scripts start running.</span>
    <span class="c1">//当GSI脚本running时，确保GSI状态可用</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">android</span><span class="o">::</span><span class="n">gsi</span><span class="o">::</span><span class="n">IsGsiRunning</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">property_set</span><span class="p">(</span><span class="s">"ro.gsid.image_running"</span><span class="p">,</span> <span class="s">"1"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">property_set</span><span class="p">(</span><span class="s">"ro.gsid.image_running"</span><span class="p">,</span> <span class="s">"0"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">//Action机制，添加action</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">SetupCgroupsAction</span><span class="p">,</span> <span class="s">"SetupCgroups"</span><span class="p">);</span>
    <span class="c1">// 执行rc文件中触发器为 on early-init 的语句</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueEventTrigger</span><span class="p">(</span><span class="s">"early-init"</span><span class="p">);</span>
    <span class="c1">// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...</span>
    <span class="c1">// 等冷插拔设备初始化完成</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">wait_for_coldboot_done_action</span><span class="p">,</span> <span class="s">"wait_for_coldboot_done"</span><span class="p">);</span>
    <span class="c1">// ... so that we can start queuing up actions that require stuff from /dev.</span>
    <span class="c1">//开始查询来自 /dev的 action</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">MixHwrngIntoLinuxRngAction</span><span class="p">,</span> <span class="s">"MixHwrngIntoLinuxRng"</span><span class="p">);</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">SetMmapRndBitsAction</span><span class="p">,</span> <span class="s">"SetMmapRndBits"</span><span class="p">);</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">SetKptrRestrictAction</span><span class="p">,</span> <span class="s">"SetKptrRestrict"</span><span class="p">);</span>
    <span class="c1">//设备组合键的初始化操作</span>
    <span class="n">Keychords</span> <span class="n">keychords</span><span class="p">;</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span>
        <span class="p">[</span><span class="o">&amp;</span><span class="n">epoll</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">keychords</span><span class="p">](</span><span class="k">const</span> <span class="n">BuiltinArguments</span><span class="o">&amp;</span> <span class="n">args</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Result</span><span class="o">&lt;</span><span class="n">Success</span><span class="o">&gt;</span> <span class="p">{</span>
            <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="k">auto</span><span class="o">&amp;</span> <span class="n">svc</span> <span class="o">:</span> <span class="n">ServiceList</span><span class="o">::</span><span class="n">GetInstance</span><span class="p">())</span> <span class="p">{</span>
                <span class="n">keychords</span><span class="p">.</span><span class="n">Register</span><span class="p">(</span><span class="n">svc</span><span class="o">-&gt;</span><span class="n">keycodes</span><span class="p">());</span>
            <span class="p">}</span>
            <span class="n">keychords</span><span class="p">.</span><span class="n">Start</span><span class="p">(</span><span class="o">&amp;</span><span class="n">epoll</span><span class="p">,</span> <span class="n">HandleKeychord</span><span class="p">);</span>
            <span class="k">return</span> <span class="n">Success</span><span class="p">();</span>
        <span class="p">},</span>
        <span class="s">"KeychordInit"</span><span class="p">);</span>
    <span class="c1">//在屏幕上显示Android 静态LOGO</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">console_init_action</span><span class="p">,</span> <span class="s">"console_init"</span><span class="p">);</span>
    <span class="c1">// Trigger all the boot actions to get us started.</span>
    <span class="c1">//执行rc文件中触发器为on init的语句</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueEventTrigger</span><span class="p">(</span><span class="s">"init"</span><span class="p">);</span>

    <span class="c1">// Starting the BoringSSL self test, for NIAP certification compliance.</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">StartBoringSslSelfTest</span><span class="p">,</span> <span class="s">"StartBoringSslSelfTest"</span><span class="p">);</span>

    <span class="c1">// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random</span>
    <span class="c1">// wasn't ready immediately after wait_for_coldboot_done</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">MixHwrngIntoLinuxRngAction</span><span class="p">,</span> <span class="s">"MixHwrngIntoLinuxRng"</span><span class="p">);</span>

    <span class="c1">// Initialize binder before bringing up other system services</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">InitBinder</span><span class="p">,</span> <span class="s">"InitBinder"</span><span class="p">);</span>

    <span class="c1">// Don't mount filesystems or start core system services in charger mode.</span>
    <span class="c1">//当设备处于充电模式时，不需要mount文件系统或者启动系统服务</span>
    <span class="c1">//充电模式下，将charger假如执行队列，否则把late-init加入执行队列</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">bootmode</span> <span class="o">=</span> <span class="n">GetProperty</span><span class="p">(</span><span class="s">"ro.bootmode"</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">bootmode</span> <span class="o">==</span> <span class="s">"charger"</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">am</span><span class="p">.</span><span class="n">QueueEventTrigger</span><span class="p">(</span><span class="s">"charger"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">am</span><span class="p">.</span><span class="n">QueueEventTrigger</span><span class="p">(</span><span class="s">"late-init"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Run all property triggers based on current state of the properties.</span>
    <span class="c1">// 基于属性当前状态 运行所有的属性触发器.</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueBuiltinAction</span><span class="p">(</span><span class="n">queue_property_triggers_action</span><span class="p">,</span> <span class="s">"queue_property_triggers"</span><span class="p">);</span>

    <span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// By default, sleep until something happens.</span>
        <span class="k">auto</span> <span class="n">epoll_timeout</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">milliseconds</span><span class="o">&gt;</span><span class="p">{};</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">do_shutdown</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">shutting_down</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">do_shutdown</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">HandlePowerctlMessage</span><span class="p">(</span><span class="n">shutdown_command</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">shutting_down</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="c1">//依次执行每个action中携带command对应的执行函数</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">waiting_for_prop</span> <span class="o">||</span> <span class="n">Service</span><span class="o">::</span><span class="n">is_exec_service_running</span><span class="p">()))</span> <span class="p">{</span>
            <span class="n">am</span><span class="p">.</span><span class="n">ExecuteOneCommand</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">waiting_for_prop</span> <span class="o">||</span> <span class="n">Service</span><span class="o">::</span><span class="n">is_exec_service_running</span><span class="p">()))</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">shutting_down</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">auto</span> <span class="n">next_process_action_time</span> <span class="o">=</span> <span class="n">HandleProcessActions</span><span class="p">();</span>

                <span class="c1">// If there's a process that needs restarting, wake up in time for that.</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">next_process_action_time</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">epoll_timeout</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">ceil</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">milliseconds</span><span class="o">&gt;</span><span class="p">(</span>
                            <span class="o">*</span><span class="n">next_process_action_time</span> <span class="o">-</span> <span class="n">boot_clock</span><span class="o">::</span><span class="n">now</span><span class="p">());</span>
                    <span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="n">epoll_timeout</span> <span class="o">&lt;</span> <span class="mx">0ms</span><span class="p">)</span> <span class="n">epoll_timeout</span> <span class="o">=</span> <span class="mx">0ms</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>

            <span class="c1">// If there's more work to do, wake up again immediately.</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">am</span><span class="p">.</span><span class="n">HasMoreCommands</span><span class="p">())</span> <span class="n">epoll_timeout</span> <span class="o">=</span> <span class="mx">0ms</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="c1">// 循环等待事件发生</span>
        <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">epoll</span><span class="p">.</span><span class="n">Wait</span><span class="p">(</span><span class="n">epoll_timeout</span><span class="p">);</span> <span class="o">!</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="344-step-1property_init初始化属性系统并从指定文件读取属性">3.4.4. Step 1：property_init初始化属性系统，并从指定文件读取属性</h3>

<blockquote>
  <p>参考：<a href="https://juejin.cn/post/7274932704453672972">Android 系统属性（SystemProperties）介绍</a></p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>system/core/init/property_service.cpp - property_init
    <span class="nt">---</span><span class="o">&gt;</span>  mkdir创建/dev/__properties__文件目录
    <span class="nt">---</span><span class="o">&gt;</span>  调用CreateSerializedPropertyInfo读取各个目录下的属性权限property_contexts文件，解析写到property_infos容器中，然后写到/dev/__properties__/property_info
        <span class="nt">---</span><span class="o">&gt;</span>  调用__system_property_area_init初始化属性内存共享区域
            <span class="nt">---</span><span class="o">&gt;</span> bionic/libc/bionic/system_property_api.cpp - __system_property_area_init
            <span class="nt">---</span><span class="o">&gt;</span> bionic/libc/system_properties/system_properties.cpp - AreaInit
            <span class="nt">---</span><span class="o">&gt;</span> bionic/libc/system_properties/contexts_serialized.cpp - Initialize然后调用InitializeProperties<span class="o">()</span>，然后调用property_info_area_file_.LoadDefaultPath<span class="o">()</span>
            <span class="nt">---</span><span class="o">&gt;</span> system/core/property_service/libpropertyinfoparser/property_info_parser.cpp - LoadDefaultPath
                <span class="nt">---</span><span class="o">&gt;</span> 调用LoadPath<span class="o">(</span><span class="s2">"/dev/__properties__/property_info"</span><span class="o">)</span>
                <span class="nt">---</span><span class="o">&gt;</span> 通过mmap将<span class="s2">"/dev/__properties__/property_info"</span>加载到共享内存
        <span class="nt">---</span><span class="o">&gt;</span> 调用selinux_android_restorecon恢复/dev/__properties__/property_info的安全上下文信息
</code></pre></div></div>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/property_service.cpp</span>
<span class="kt">void</span> <span class="nf">property_init</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">//创建/dev/__properties__文件目录</span>
    <span class="n">mkdir</span><span class="p">(</span><span class="s">"/dev/__properties__"</span><span class="p">,</span> <span class="n">S_IRWXU</span> <span class="o">|</span> <span class="n">S_IXGRP</span> <span class="o">|</span> <span class="n">S_IXOTH</span><span class="p">);</span>
    <span class="n">CreateSerializedPropertyInfo</span><span class="p">();</span>
    <span class="c1">//bionic/libc/bionic/system_property_api.cpp</span>
    <span class="c1">//初始化属性内存共享区域</span>
    <span class="c1">//该方法经过一系列的方法调用，最终通过mmap()将/dev/__properties__/property_info加载到共享内存</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">__system_property_area_init</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to initialize property area"</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">property_info_area</span><span class="p">.</span><span class="n">LoadDefaultPath</span><span class="p">())</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to load serialized property info file"</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">//读取各个目录下的属性权限property_contexts文件，解析写到property_infos容器中</span>
<span class="kt">void</span> <span class="n">CreateSerializedPropertyInfo</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">auto</span> <span class="n">property_infos</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">PropertyInfoEntry</span><span class="o">&gt;</span><span class="p">();</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="s">"/system/etc/selinux/plat_property_contexts"</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/system/etc/selinux/plat_property_contexts"</span><span class="p">,</span>
                                      <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="c1">// Don't check for failure here, so we always have a sane list of properties.</span>
        <span class="c1">// E.g. In case of recovery, the vendor partition will not have mounted and we</span>
        <span class="c1">// still need the system / platform properties to function.</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/vendor/etc/selinux/vendor_property_contexts"</span><span class="p">,</span>
                                      <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">))</span> <span class="p">{</span>
            <span class="c1">// Fallback to nonplat_* if vendor_* doesn't exist.</span>
            <span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/vendor/etc/selinux/nonplat_property_contexts"</span><span class="p">,</span>
                                     <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="s">"/product/etc/selinux/product_property_contexts"</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/product/etc/selinux/product_property_contexts"</span><span class="p">,</span>
                                     <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="s">"/odm/etc/selinux/odm_property_contexts"</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/odm/etc/selinux/odm_property_contexts"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/plat_property_contexts"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/vendor_property_contexts"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">))</span> <span class="p">{</span>
            <span class="c1">// Fallback to nonplat_* if vendor_* doesn't exist.</span>
            <span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/nonplat_property_contexts"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/product_property_contexts"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">);</span>
        <span class="n">LoadPropertyInfoFromFile</span><span class="p">(</span><span class="s">"/odm_property_contexts"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">property_infos</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">auto</span> <span class="n">serialized_contexts</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">();</span>
    <span class="k">auto</span> <span class="n">error</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">();</span>
    <span class="c1">//property_infos写到serialized_contexts中</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">BuildTrie</span><span class="p">(</span><span class="n">property_infos</span><span class="p">,</span> <span class="s">"u:object_r:default_prop:s0"</span><span class="p">,</span> <span class="s">"string"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">serialized_contexts</span><span class="p">,</span>
                   <span class="o">&amp;</span><span class="n">error</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Unable to serialize property contexts: "</span> <span class="o">&lt;&lt;</span> <span class="n">error</span><span class="p">;</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//将serialized_contexts写到/dev/__properties__/property_info</span>
    <span class="k">constexpr</span> <span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">kPropertyInfosPath</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"/dev/__properties__/property_info"</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">WriteStringToFile</span><span class="p">(</span><span class="n">serialized_contexts</span><span class="p">,</span> <span class="n">kPropertyInfosPath</span><span class="p">,</span> <span class="mo">0444</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">false</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Unable to write serialized property infos to file"</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//同下一个步骤SelinuxRestoreContext的流程</span>
    <span class="n">selinux_android_restorecon</span><span class="p">(</span><span class="n">kPropertyInfosPath</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="345-step-2进行selinux第二阶段工作">3.4.5. Step 2：进行Selinux第二阶段工作</h3>

<p>从上面的入口函数看，继初始化SElinux之后的第二阶段调用到的是以下几个函数，其中第一个SelinuxSetupKernelLogging是日志输出。主要是后面两个函数：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/init.cpp</span>
<span class="kt">int</span> <span class="nf">SecondStageMain</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="c1">//-----Step 2:进行Selinux第二阶段工作</span>
    <span class="c1">//将selinux日志重定向到内核日志输出(dev/kmsg)</span>
    <span class="n">SelinuxSetupKernelLogging</span><span class="p">();</span>
    <span class="c1">//第二阶段初始化selinux</span>
    <span class="n">SelabelInitialize</span><span class="p">();</span>
    <span class="c1">//恢复安全上下文，进行SELinux第二阶段并恢复一些文件安全上下文 </span>
    <span class="c1">//恢复相关文件的安全上下文,因为这些文件是在SELinux安全机制初始化前创建的，所以需要重新恢复上下文</span>
    <span class="n">SelinuxRestoreContext</span><span class="p">();</span>
    <span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/selinux.cpp</span>
<span class="c1">//selinux_android_file_context_handle()的运行时间约为10+ms，因此我们希望缓存其值。</span>
<span class="c1">//selinux_android_restorecon()还需要一个sehandle来查找文件上下文。它将创建并存储自己的副本，但selinux_android_set_sehandle()可用于提供一个副本，从而消除了对selinux_adroid_file_context_handle()的额外调用。</span>
<span class="kt">void</span> <span class="nf">SelabelInitialize</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">//均是external/selinux/libselinux/src/android/android_platform.c的函数</span>
    <span class="n">sehandle</span> <span class="o">=</span> <span class="n">selinux_android_file_context_handle</span><span class="p">();</span>
    <span class="c1">//只是做了保存上面sehandle作为android_platform.c里面的全局变量</span>
    <span class="n">selinux_android_set_sehandle</span><span class="p">(</span><span class="n">sehandle</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="3451-selabelinitialize-selinux_android_file_context_handle流程">3.4.5.1. SelabelInitialize-&gt;selinux_android_file_context_handle流程</h4>

<blockquote>
  <p>详细的分析参考：<a href="https://blog.csdn.net/qq_27061049/article/details/130152818">SEAndroid流程分析</a>
<a href="https://blog.csdn.net/Invoker123/article/details/78999058">SEAndroid流程分析</a></p>

  <p>selinux_android_file_context_handle()返回的是一个保存了/file_conetxts各种信息及相关处理函数的struct selabel_handle指针，并将其保存在静态全局指针fc_sehandle中，这部分系统中的文件安全上下文有关</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>system/core/init/init.cpp - SecondStageMain
<span class="nt">---</span><span class="o">&gt;</span> system/core/init/selinux.cpp - SelabelInitialize
<span class="nt">---</span><span class="o">&gt;</span> external/selinux/libselinux/src/android/android_platform.c - selinux_android_file_context_handle 将四个目录下的file_contexts文件添加到seopts_file容器中
        （/system/etc/selinux/plat_file_contexts、/product/etc/selinux/product_file_contexts、/vendor/etc/selinux/vendor_file_contexts、/odm/etc/selinux/odm_file_contexts）
    <span class="nt">---</span><span class="o">&gt;</span> 调用selinux_android_file_context
<span class="nt">---</span><span class="o">&gt;</span> external/selinux/libselinux/src/label.c - selabel_open 分配一块内存，将入参即file_context文件列表内容写到这块内存中，用于后面调用方法入参
    <span class="nt">---</span><span class="o">&gt;</span> 调用initfuncs[backend]<span class="o">)(</span>rec, opts, nopts<span class="o">)</span>，其中backend入参是0，所以会调用到下面的第一个方法CONFIG_FILE_BACKEND<span class="o">(</span>selabel_file_init<span class="o">)</span>
<span class="nt">---</span><span class="o">&gt;</span> external/selinux/libselinux/src/label_file.c - selabel_file_init
    <span class="nt">---</span><span class="o">&gt;</span> 调用init<span class="o">(</span>rec, opts, nopts<span class="o">)</span>
    <span class="nt">---</span><span class="o">&gt;</span> 遍历file_context文件列表调用process_file
        <span class="nt">---</span><span class="o">&gt;</span> （1）调用open_file打开file_contexts文件
        <span class="nt">---</span><span class="o">&gt;</span> （2）调用process_text_file，然后while循环逐行读取调用process_line
            <span class="nt">---</span><span class="o">&gt;</span> external/selinux/libselinux/src/label_file.h - process_line解析file_context文件内容保存上下文
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//external/selinux/libselinux/src/android/android_platform.c</span>
<span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="k">const</span> <span class="n">sepolicy_file</span> <span class="o">=</span> <span class="s">"/sepolicy"</span><span class="p">;</span>

<span class="c1">//此处的selinux_opt是结构体对象（external/selinux/libselinux/include/selinux/selinux.h）</span>
<span class="k">struct</span> <span class="n">selinux_opt</span> <span class="p">{</span>
	<span class="kt">int</span> <span class="n">type</span><span class="p">;</span>
	<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">value</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="n">seopts_file_plat</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/system/etc/selinux/plat_file_contexts"</span> <span class="p">},</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/plat_file_contexts"</span> <span class="p">}</span>
<span class="p">};</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="n">seopts_file_product</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/product/etc/selinux/product_file_contexts"</span> <span class="p">},</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/product_file_contexts"</span> <span class="p">}</span>
<span class="p">};</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="n">seopts_file_vendor</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/vendor/etc/selinux/vendor_file_contexts"</span> <span class="p">},</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/vendor_file_contexts"</span> <span class="p">},</span>
    <span class="c1">// TODO: remove nonplat* when no need to retain backward compatibility.</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/vendor/etc/selinux/nonplat_file_contexts"</span> <span class="p">},</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/nonplat_file_contexts"</span> <span class="p">}</span>
<span class="p">};</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="n">seopts_file_odm</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/odm/etc/selinux/odm_file_contexts"</span> <span class="p">},</span>
    <span class="p">{</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">,</span> <span class="s">"/odm_file_contexts"</span> <span class="p">}</span>
<span class="p">};</span>

<span class="c1">//此函数就是将四个目录下的file_contexts文件添加到seopts_file变量中</span>
<span class="k">struct</span> <span class="n">selabel_handle</span><span class="o">*</span> <span class="nf">selinux_android_file_context_handle</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="n">seopts_file</span><span class="p">[</span><span class="n">MAX_FILE_CONTEXT_SIZE</span><span class="p">];</span><span class="c1">//值为4</span>
    <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">seopts_file_plat</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="n">seopts_file_plat</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">value</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">seopts_file</span><span class="p">[</span><span class="n">size</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">seopts_file_plat</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">seopts_file_product</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="n">seopts_file_product</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">value</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">seopts_file</span><span class="p">[</span><span class="n">size</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">seopts_file_product</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">seopts_file_vendor</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="n">seopts_file_vendor</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">value</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">seopts_file</span><span class="p">[</span><span class="n">size</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">seopts_file_vendor</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">seopts_file_odm</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">access</span><span class="p">(</span><span class="n">seopts_file_odm</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">value</span><span class="p">,</span> <span class="n">R_OK</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">seopts_file</span><span class="p">[</span><span class="n">size</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">seopts_file_odm</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">//将填充好的变量作为入参，调用selinux_android_file_context</span>
    <span class="k">return</span> <span class="n">selinux_android_file_context</span><span class="p">(</span><span class="n">seopts_file</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="k">struct</span> <span class="n">selabel_handle</span><span class="o">*</span> <span class="nf">selinux_android_file_context</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="o">*</span><span class="n">opts</span><span class="p">,</span>
                                                    <span class="kt">unsigned</span> <span class="n">nopts</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">selabel_handle</span> <span class="o">*</span><span class="n">sehandle</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="n">fc_opts</span><span class="p">[</span><span class="n">nopts</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span>
    <span class="c1">//将入参opts拷贝到fc_opts变量中</span>
    <span class="n">memcpy</span><span class="p">(</span><span class="n">fc_opts</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="n">nopts</span><span class="o">*</span><span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">selinux_opt</span><span class="p">));</span>
    <span class="n">fc_opts</span><span class="p">[</span><span class="n">nopts</span><span class="p">].</span><span class="n">type</span> <span class="o">=</span> <span class="n">SELABEL_OPT_BASEONLY</span><span class="p">;</span>
    <span class="n">fc_opts</span><span class="p">[</span><span class="n">nopts</span><span class="p">].</span><span class="n">value</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="mi">1</span><span class="p">;</span>
    <span class="c1">//宏定义#define SELABEL_CTX_FILE	0</span>
    <span class="n">sehandle</span> <span class="o">=</span> <span class="n">selabel_open</span><span class="p">(</span><span class="n">SELABEL_CTX_FILE</span><span class="p">,</span> <span class="n">fc_opts</span><span class="p">,</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">fc_opts</span><span class="p">));</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sehandle</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">selinux_log</span><span class="p">(</span><span class="n">SELINUX_ERROR</span><span class="p">,</span> <span class="s">"%s: Error getting file context handle (%s)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
                <span class="n">__FUNCTION__</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">compute_file_contexts_hash</span><span class="p">(</span><span class="n">fc_digest</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="n">nopts</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">selabel_close</span><span class="p">(</span><span class="n">sehandle</span><span class="p">);</span>
        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">selinux_log</span><span class="p">(</span><span class="n">SELINUX_INFO</span><span class="p">,</span> <span class="s">"SELinux: Loaded file_contexts</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">sehandle</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//external/selinux/libselinux/src/label.c</span>
<span class="k">struct</span> <span class="n">selabel_handle</span> <span class="o">*</span><span class="nf">selabel_open</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">backend</span><span class="p">,</span>
				    <span class="k">const</span> <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="o">*</span><span class="n">opts</span><span class="p">,</span>
				    <span class="kt">unsigned</span> <span class="n">nopts</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">struct</span> <span class="n">selabel_handle</span> <span class="o">*</span><span class="n">rec</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">backend</span> <span class="o">&gt;=</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">initfuncs</span><span class="p">))</span> <span class="p">{</span>
		<span class="n">errno</span> <span class="o">=</span> <span class="n">EINVAL</span><span class="p">;</span>
		<span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">initfuncs</span><span class="p">[</span><span class="n">backend</span><span class="p">])</span> <span class="p">{</span>
		<span class="n">errno</span> <span class="o">=</span> <span class="n">ENOTSUP</span><span class="p">;</span>
		<span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">rec</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">selabel_handle</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">rec</span><span class="p">));</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">rec</span><span class="p">)</span>
		<span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
    <span class="c1">//分配一块内存，将入参即file_context文件列表内容写到rec中</span>
	<span class="n">memset</span><span class="p">(</span><span class="n">rec</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">rec</span><span class="p">));</span>
	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">backend</span> <span class="o">=</span> <span class="n">backend</span><span class="p">;</span>
	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">validating</span> <span class="o">=</span> <span class="n">selabel_is_validate_set</span><span class="p">(</span><span class="n">opts</span><span class="p">,</span> <span class="n">nopts</span><span class="p">);</span>

	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">digest</span> <span class="o">=</span> <span class="n">selabel_is_digest_set</span><span class="p">(</span><span class="n">opts</span><span class="p">,</span> <span class="n">nopts</span><span class="p">,</span> <span class="n">rec</span><span class="o">-&gt;</span><span class="n">digest</span><span class="p">);</span>
    <span class="c1">//调用initfuncs，其中backend入参是0，所以会调用到下面的第一个方法CONFIG_FILE_BACKEND(selabel_file_init)</span>
	<span class="k">if</span> <span class="p">((</span><span class="o">*</span><span class="n">initfuncs</span><span class="p">[</span><span class="n">backend</span><span class="p">])(</span><span class="n">rec</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="n">nopts</span><span class="p">))</span> <span class="p">{</span>
		<span class="n">selabel_close</span><span class="p">(</span><span class="n">rec</span><span class="p">);</span>
		<span class="n">rec</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="p">}</span>
<span class="nl">out:</span>
	<span class="k">return</span> <span class="n">rec</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="n">selabel_initfunc</span> <span class="n">initfuncs</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
	<span class="n">CONFIG_FILE_BACKEND</span><span class="p">(</span><span class="n">selabel_file_init</span><span class="p">),</span>
	<span class="n">CONFIG_MEDIA_BACKEND</span><span class="p">(</span><span class="n">selabel_media_init</span><span class="p">),</span>
	<span class="n">CONFIG_X_BACKEND</span><span class="p">(</span><span class="n">selabel_x_init</span><span class="p">),</span>
	<span class="n">CONFIG_DB_BACKEND</span><span class="p">(</span><span class="n">selabel_db_init</span><span class="p">),</span>
	<span class="n">CONFIG_ANDROID_BACKEND</span><span class="p">(</span><span class="n">selabel_property_init</span><span class="p">),</span>
	<span class="n">CONFIG_ANDROID_BACKEND</span><span class="p">(</span><span class="n">selabel_service_init</span><span class="p">),</span>
<span class="p">};</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//external/selinux/libselinux/src/label_file.c</span>
<span class="kt">int</span> <span class="nf">selabel_file_init</span><span class="p">(</span><span class="k">struct</span> <span class="n">selabel_handle</span> <span class="o">*</span><span class="n">rec</span><span class="p">,</span>
				    <span class="k">const</span> <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="o">*</span><span class="n">opts</span><span class="p">,</span>
				    <span class="kt">unsigned</span> <span class="n">nopts</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">struct</span> <span class="n">saved_data</span> <span class="o">*</span><span class="n">data</span><span class="p">;</span>

	<span class="n">data</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">saved_data</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">data</span><span class="p">));</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">data</span><span class="p">)</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="n">memset</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">data</span><span class="p">));</span>

	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">;</span>
	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">func_close</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">closef</span><span class="p">;</span>
	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">func_stats</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">stats</span><span class="p">;</span>
	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">func_lookup</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">lookup</span><span class="p">;</span>
	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">func_partial_match</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">partial_match</span><span class="p">;</span>
	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">func_hash_all_partial_matches</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">hash_all_partial_matches</span><span class="p">;</span>
	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">func_lookup_best_match</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">lookup_best_match</span><span class="p">;</span>
	<span class="n">rec</span><span class="o">-&gt;</span><span class="n">func_cmp</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">cmp</span><span class="p">;</span>
    <span class="c1">//调用init进行data的初始化</span>
	<span class="k">return</span> <span class="n">init</span><span class="p">(</span><span class="n">rec</span><span class="p">,</span> <span class="n">opts</span><span class="p">,</span> <span class="n">nopts</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">//init会解析/file_context文件的内容，将相关信息填充到入参rec的data成员中。</span>
<span class="c1">//填充过程会分为两次进行，第一次是计算安全上下文信息数量（spec_t的数量）及处理一些认证工作。</span>
<span class="c1">//第二次会依次关联安全上下文信息（spec_t）和根文件名信息（stem_t）,保存regex信息和context信息和确保没有重复的安全上下文信息（spec_t）。</span>
<span class="c1">//这些安全上下文信息（spec_t）会被保存在数组data-&gt;spec_arr中，根文件信息（stem_t）会被保存在数组data-&gt;stem_arr中。</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">init</span><span class="p">(</span><span class="k">struct</span> <span class="n">selabel_handle</span> <span class="o">*</span><span class="n">rec</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">selinux_opt</span> <span class="o">*</span><span class="n">opts</span><span class="p">,</span>
		<span class="kt">unsigned</span> <span class="n">n</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">struct</span> <span class="n">saved_data</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">saved_data</span> <span class="o">*</span><span class="p">)</span><span class="n">rec</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>
	<span class="kt">size_t</span> <span class="n">num_paths</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="kt">char</span> <span class="o">**</span><span class="n">path</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">prefix</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="kt">int</span> <span class="n">status</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="kt">size_t</span> <span class="n">i</span><span class="p">;</span>
	<span class="n">bool</span> <span class="n">baseonly</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
	<span class="n">bool</span> <span class="n">path_provided</span><span class="p">;</span>

	<span class="cm">/* Process arguments */</span>
    <span class="c1">//这里n为1，进入到case SELABEL_OPT_PATH的逻辑中，使得path为"/file_contexts"</span>
	<span class="n">i</span> <span class="o">=</span> <span class="n">n</span><span class="p">;</span>
	<span class="k">while</span> <span class="p">(</span><span class="n">i</span><span class="o">--</span><span class="p">)</span>
		<span class="k">switch</span><span class="p">(</span><span class="n">opts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">type</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">case</span> <span class="n">SELABEL_OPT_PATH</span><span class="p">:</span>
			<span class="n">num_paths</span><span class="o">++</span><span class="p">;</span>
			<span class="k">break</span><span class="p">;</span>
		<span class="k">case</span> <span class="n">SELABEL_OPT_SUBSET</span><span class="p">:</span>
			<span class="n">prefix</span> <span class="o">=</span> <span class="n">opts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">value</span><span class="p">;</span>
			<span class="k">break</span><span class="p">;</span>
		<span class="k">case</span> <span class="n">SELABEL_OPT_BASEONLY</span><span class="p">:</span>
			<span class="n">baseonly</span> <span class="o">=</span> <span class="o">!!</span><span class="n">opts</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">value</span><span class="p">;</span>
			<span class="k">break</span><span class="p">;</span>
		<span class="p">}</span>
    <span class="p">.....</span>
	<span class="cm">/*
	 * Do detailed validation of the input and fill the spec array
	 */</span>
	<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">num_paths</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">//解析文件</span>
		<span class="n">status</span> <span class="o">=</span> <span class="n">process_file</span><span class="p">(</span><span class="n">rec</span><span class="o">-&gt;</span><span class="n">spec_files</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">rec</span><span class="p">,</span> <span class="n">prefix</span><span class="p">,</span> <span class="n">rec</span><span class="o">-&gt;</span><span class="n">digest</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">status</span><span class="p">)</span>
			<span class="k">goto</span> <span class="n">finish</span><span class="p">;</span>

		<span class="k">if</span> <span class="p">(</span><span class="n">rec</span><span class="o">-&gt;</span><span class="n">validating</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">status</span> <span class="o">=</span> <span class="n">nodups_specs</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">rec</span><span class="o">-&gt;</span><span class="n">spec_files</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
			<span class="k">if</span> <span class="p">(</span><span class="n">status</span><span class="p">)</span>
				<span class="k">goto</span> <span class="n">finish</span><span class="p">;</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">baseonly</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">status</span> <span class="o">=</span> <span class="n">process_file</span><span class="p">(</span><span class="n">rec</span><span class="o">-&gt;</span><span class="n">spec_files</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="s">"homedirs"</span><span class="p">,</span> <span class="n">rec</span><span class="p">,</span> <span class="n">prefix</span><span class="p">,</span>
							    <span class="n">rec</span><span class="o">-&gt;</span><span class="n">digest</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">&amp;&amp;</span> <span class="n">errno</span> <span class="o">!=</span> <span class="n">ENOENT</span><span class="p">)</span>
			<span class="k">goto</span> <span class="n">finish</span><span class="p">;</span>

		<span class="n">status</span> <span class="o">=</span> <span class="n">process_file</span><span class="p">(</span><span class="n">rec</span><span class="o">-&gt;</span><span class="n">spec_files</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="s">"local"</span><span class="p">,</span> <span class="n">rec</span><span class="p">,</span> <span class="n">prefix</span><span class="p">,</span>
							    <span class="n">rec</span><span class="o">-&gt;</span><span class="n">digest</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">status</span> <span class="o">&amp;&amp;</span> <span class="n">errno</span> <span class="o">!=</span> <span class="n">ENOENT</span><span class="p">)</span>
			<span class="k">goto</span> <span class="n">finish</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">digest_gen_hash</span><span class="p">(</span><span class="n">rec</span><span class="o">-&gt;</span><span class="n">digest</span><span class="p">);</span>

	<span class="n">status</span> <span class="o">=</span> <span class="n">sort_specs</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>

<span class="nl">finish:</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">status</span><span class="p">)</span>
		<span class="n">closef</span><span class="p">(</span><span class="n">rec</span><span class="p">);</span>

	<span class="k">return</span> <span class="n">status</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">process_file</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">path</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">suffix</span><span class="p">,</span>
			  <span class="k">struct</span> <span class="n">selabel_handle</span> <span class="o">*</span><span class="n">rec</span><span class="p">,</span>
			  <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">prefix</span><span class="p">,</span> <span class="k">struct</span> <span class="n">selabel_digest</span> <span class="o">*</span><span class="n">digest</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">rc</span><span class="p">;</span>
	<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
	<span class="k">struct</span> <span class="n">stat</span> <span class="n">sb</span><span class="p">;</span>
	<span class="kt">FILE</span> <span class="o">*</span><span class="n">fp</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="kt">char</span> <span class="n">found_path</span><span class="p">[</span><span class="n">PATH_MAX</span><span class="p">];</span>

	<span class="cm">/*
	 * On the first pass open the newest modified file. If it fails to
	 * process, then the second pass shall open the oldest file. If both
	 * passes fail, then it's a fatal error.
	 */</span>
	<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">fp</span> <span class="o">=</span> <span class="n">open_file</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">suffix</span><span class="p">,</span> <span class="n">found_path</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">found_path</span><span class="p">),</span>
			<span class="o">&amp;</span><span class="n">sb</span><span class="p">,</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">fp</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
			<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
        <span class="c1">//调用process_text_file解析文件内容</span>
		<span class="n">rc</span> <span class="o">=</span> <span class="n">fcontext_is_binary</span><span class="p">(</span><span class="n">fp</span><span class="p">)</span> <span class="o">?</span>
				<span class="n">load_mmap</span><span class="p">(</span><span class="n">fp</span><span class="p">,</span> <span class="n">sb</span><span class="p">.</span><span class="n">st_size</span><span class="p">,</span> <span class="n">rec</span><span class="p">,</span> <span class="n">found_path</span><span class="p">)</span> <span class="o">:</span>
				<span class="n">process_text_file</span><span class="p">(</span><span class="n">fp</span><span class="p">,</span> <span class="n">prefix</span><span class="p">,</span> <span class="n">rec</span><span class="p">,</span> <span class="n">found_path</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">rc</span><span class="p">)</span>
			<span class="n">rc</span> <span class="o">=</span> <span class="n">digest_add_specfile</span><span class="p">(</span><span class="n">digest</span><span class="p">,</span> <span class="n">fp</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">sb</span><span class="p">.</span><span class="n">st_size</span><span class="p">,</span>
				<span class="n">found_path</span><span class="p">);</span>

		<span class="n">fclose</span><span class="p">(</span><span class="n">fp</span><span class="p">);</span>

		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">rc</span><span class="p">)</span>
			<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">process_text_file</span><span class="p">(</span><span class="kt">FILE</span> <span class="o">*</span><span class="n">fp</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">prefix</span><span class="p">,</span>
			     <span class="k">struct</span> <span class="n">selabel_handle</span> <span class="o">*</span><span class="n">rec</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">path</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">rc</span><span class="p">;</span>
	<span class="kt">size_t</span> <span class="n">line_len</span><span class="p">;</span>
	<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">lineno</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="kt">char</span> <span class="o">*</span><span class="n">line_buf</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>

	<span class="k">while</span> <span class="p">(</span><span class="n">getline</span><span class="p">(</span><span class="o">&amp;</span><span class="n">line_buf</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">line_len</span><span class="p">,</span> <span class="n">fp</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">//解析每行内容</span>
		<span class="n">rc</span> <span class="o">=</span> <span class="n">process_line</span><span class="p">(</span><span class="n">rec</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">prefix</span><span class="p">,</span> <span class="n">line_buf</span><span class="p">,</span> <span class="o">++</span><span class="n">lineno</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">rc</span><span class="p">)</span>
			<span class="k">goto</span> <span class="n">out</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="n">rc</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="nl">out:</span>
	<span class="n">free</span><span class="p">(</span><span class="n">line_buf</span><span class="p">);</span>
	<span class="k">return</span> <span class="n">rc</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h4 id="3452-selinuxrestorecontext流程">3.4.5.2. SelinuxRestoreContext流程</h4>

<blockquote>
  <p>此处selinux初始化这块流程内容较复杂，后续再详细梳理代码，此处只大致梳理流程</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>system/core/init/init.cpp - SecondStageMain
<span class="nt">---</span><span class="o">&gt;</span> system/core/init/selinux.cpp - SelinuxRestoreContext<span class="o">()</span>多次调用selinux_android_restorecon入参一系列目录（比如<span class="s2">"/dev/__properties__"</span>、<span class="s2">"/dev/kmsg"</span>等）
<span class="nt">---</span><span class="o">&gt;</span> external/selinux/libselinux/src/android/android_platform.c - selinux_android_restorecon
    <span class="nt">---</span><span class="o">&gt;</span> selinux_android_restorecon_common
    <span class="nt">---</span><span class="o">&gt;</span> restorecon_sb 将安全上下文恢复
    <span class="nt">---</span><span class="o">&gt;</span> pkgdir_selabel_lookup 获取app文件的安全上下文信息，保存在secontext中<span class="o">(</span>即seapp_contexts用于为应用进程和/data/data目录的上下文文件<span class="o">)</span>
    <span class="nt">---</span><span class="o">&gt;</span> seapp_context_lookup 根据UID，isSystemServer，seinfo,pkgname等信息匹配/seapp_contexts的规则，完全符合的项会被用来设置新的context_t，保存在最后一个入参ctx中。seinfo就是“platform”或“android”,根据apk是否拥有系统签名而定
</code></pre></div></div>

<hr />

<h3 id="346-step-3信号处理">3.4.6. Step 3：信号处理</h3>

<blockquote>
  <p>参考：<a href="https://blog.csdn.net/u010783226/article/details/119810208">Android init 启动进程分析</a></p>

  <p>InstallSignalFdHandler函数用于设置子进程信号处理函数。主要防止init进程的子进程成为僵尸进程(zombie process)，为了防止僵尸进程出现，系统会在子进程暂停和终止的时候发出SIGCHLD信号，InstallSignalFdHandler函数就是用来接收SIGCHLD 信号的(内部只处理进程终止的SIGCHLD 信号)。</p>

  <p>假设init进程的某个子进程终止了，InstallSignalFdHandler函数调用HandleSignalFd函数，层层调用处理，找到终止的子进程服务并移除它。再重启子进程服务的启动脚本中带有onrestart的服务。</p>
</blockquote>

<p><strong>PS：</strong></p>
<ol>
  <li>EPOLL类似于POLL，是Linux中用来做事件触发的，跟EventBus功能差不多。linux很长的时间都在使用select来做事件触发，它是通过轮询来处理的，轮询的fd数目越多，自然耗时越多，对于大量的描述符处理，EPOLL更有优势</li>
  <li>在Unix系统中，当一个子进程终止时，父进程会收到一个SIGCHLD信号。SIGCHLD是子进程状态改变时发送给父进程的信号。父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况，并通过调用wait()或waitpid()等函数来获取子进程的退出状态、终止原因等信息。<strong>InstallSignalFdHandler函数的作用就是，接收到SIGCHLD信号时触发HandleSignalFd进行信号处理</strong></li>
  <li>init进程如何处理这个SIGCHLD信号：
    <ul>
      <li>
        <ul>
          <li>新建一个sigaction结构体，sa_handler是信号处理函数，指向内核指定的函数指针SIG_DFL和Android 9.0及之前的版本不同，这里不再通过socket的读写句柄进行接收信号，改成了内核的信号处理函数SIG_DFL。</li>
        </ul>
      </li>
      <li>
        <ul>
          <li>然后，sigaction(SIGCHLD, &amp;act, nullptr) 这个是建立信号绑定关系，也就是说当监听到SIGCHLD信号时，由act这个sigaction结构体处理</li>
        </ul>
      </li>
      <li>
        <ul>
          <li>最后，RegisterHandler 的作用就是signal_read_fd（之前的s[1]）收到信号，触发handle_signal</li>
        </ul>
      </li>
    </ul>
  </li>
</ol>

<pre><code class="language-log">system/core/init/init.cpp - SecondStageMain
---&gt; InstallSignalFdHandler
    (1) ---&gt; system/core/init/epoll.cpp - RegisterHandler 信号注册,把fd句柄加入到 epoll_fd_的监听队列中(epoll_ctl)
    (2) ---&gt; system/core/init/init.cpp - HandleSignalFd 监控SIGCHLD信号，调用ReapAnyOutstandingChildren来终止出现问题的子进程
            ---&gt; system/core/init/sigchld_handler.cpp - ReapAnyOutstandingChildren
            ---&gt; system/core/init/sigchld_handler.cpp - ReapOneProcess 是最终的处理函数，这个函数先用waitpid找出挂掉进程的pid,然后根据pid找到对应Service，最后调用Service的Reap方法清除资源,根据进程对应的类型，决定是否重启机器或重启进程
</code></pre>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/init.cpp</span>
<span class="kt">int</span> <span class="nf">SecondStageMain</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="c1">//-----Step 3: 创建epoll句柄并初始化子进程终止信号处理函数</span>
    <span class="c1">//要是创建handler处理子进程终止信号，注册一个signal到epoll进行监听，进行子继承处理</span>
    <span class="n">Epoll</span> <span class="n">epoll</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">epoll</span><span class="p">.</span><span class="n">Open</span><span class="p">();</span> <span class="o">!</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="c1">//子进程信号处理函数，如果紫禁城异常退出，init进程会调用该函数设定的信号处理函数进行处理</span>
    <span class="n">InstallSignalFdHandler</span><span class="p">(</span><span class="o">&amp;</span><span class="n">epoll</span><span class="p">);</span>
    <span class="p">.....</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="n">InstallSignalFdHandler</span><span class="p">(</span><span class="n">Epoll</span><span class="o">*</span> <span class="n">epoll</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">//新建一个sigaction结构体，sa_handler是信号处理函数</span>
    <span class="k">const</span> <span class="k">struct</span> <span class="nc">sigaction</span> <span class="n">act</span> <span class="p">{</span> <span class="p">.</span><span class="n">sa_handler</span> <span class="o">=</span> <span class="n">SIG_DFL</span><span class="p">,</span> <span class="p">.</span><span class="n">sa_flags</span> <span class="o">=</span> <span class="n">SA_NOCLDSTOP</span> <span class="p">};</span>
    <span class="c1">//建立信号绑定关系，也就是说当监听到SIGCHLD信号时，由act这个sigaction结构体处理</span>
    <span class="n">sigaction</span><span class="p">(</span><span class="n">SIGCHLD</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">act</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">);</span>

    <span class="n">sigset_t</span> <span class="n">mask</span><span class="p">;</span>
    <span class="n">sigemptyset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">mask</span><span class="p">);</span>
    <span class="n">sigaddset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">mask</span><span class="p">,</span> <span class="n">SIGCHLD</span><span class="p">);</span>
    <span class="p">......</span>
    <span class="c1">// Register a handler to unblock signals in the child processes.</span>
    <span class="c1">//注册处理程序以解除对子进程中的信号的阻止</span>
    <span class="k">const</span> <span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">pthread_atfork</span><span class="p">(</span><span class="nb">nullptr</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">UnblockSignals</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Failed to register a fork handler: "</span> <span class="o">&lt;&lt;</span> <span class="n">strerror</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">//创建信号句柄</span>
    <span class="n">signal_fd</span> <span class="o">=</span> <span class="n">signalfd</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mask</span><span class="p">,</span> <span class="n">SFD_CLOEXEC</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">signal_fd</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"failed to create signalfd"</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//信号注册，当signal_fd收到信号时，触发HandleSignalFd</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">epoll</span><span class="o">-&gt;</span><span class="n">RegisterHandler</span><span class="p">(</span><span class="n">signal_fd</span><span class="p">,</span> <span class="n">HandleSignalFd</span><span class="p">);</span> <span class="o">!</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">LOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="347-step-4加载开机默认属性配置以及启动属性服务继step-1">3.4.7. Step 4：加载开机默认属性配置以及启动属性服务（继Step 1）</h3>

<blockquote>
  <p>这部分接Step 1 property_init初始化系统之后，通过property_load_boot_defaults加载开机默认属性配置，通过StartPropertyService启动属性服务</p>
</blockquote>

<h4 id="3471-流程梳理">3.4.7.1. 流程梳理</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">init</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span> <span class="n">property_load_boot_defaults</span>
<span class="o">---&gt;</span> <span class="mf">1.</span><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">property_service</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span> <span class="n">property_load_boot_defaults</span>
    <span class="o">---&gt;</span> <span class="mf">1.1</span> <span class="err">通过调用</span><span class="n">load_properties_from_file</span><span class="err">加载各个目录下的属性文件，入参</span><span class="n">prop</span><span class="err">文件，以及</span><span class="n">properties</span><span class="err">键值对变量</span>
        <span class="o">---&gt;</span> <span class="n">LoadProperties</span><span class="err">加载属性，将</span><span class="n">prop</span><span class="err">文件的属性遍历写到</span><span class="n">properties</span><span class="err">键值对变量中</span>
    <span class="o">---&gt;</span> <span class="mf">1.2</span> <span class="err">遍历</span><span class="n">properties</span><span class="err">，对每个属性调用</span><span class="n">PropertySet</span><span class="err">方法存入属性到系统中</span>
        <span class="o">---&gt;</span> <span class="err">最终调用到</span><span class="n">bionic</span><span class="o">/</span><span class="n">libc</span><span class="err">库的</span><span class="n">Update</span><span class="o">/</span><span class="n">Add</span><span class="err">方法（</span><span class="n">__system_property_update</span><span class="err">更新</span><span class="o">/</span><span class="n">__system_property_add</span><span class="err">新增）</span>
    <span class="o">---&gt;</span> <span class="mf">1.3</span> <span class="err">调用</span><span class="n">property_initialize_ro_product_props</span><span class="err">初始化</span><span class="n">ro</span><span class="p">.</span><span class="n">product</span><span class="p">.</span><span class="err">属性（遍历调用</span><span class="n">PropertySet</span><span class="err">）</span>
    <span class="o">---&gt;</span> <span class="mf">1.4</span> <span class="err">调用</span><span class="n">property_derive_build_fingerprint</span><span class="err">设置编译相关属性（遍历调用</span><span class="n">PropertySet</span><span class="err">）</span>
    <span class="o">---&gt;</span> <span class="mf">1.5</span> <span class="err">调用</span><span class="n">update_sys_usb_config</span><span class="err">设置</span><span class="n">usb</span><span class="err">属性</span><span class="n">persist</span><span class="p">.</span><span class="n">sys</span><span class="p">.</span><span class="n">usb</span><span class="p">.</span><span class="n">config</span><span class="err">（调用</span><span class="n">property_set</span><span class="err">常规设置属性方法）</span>

<span class="o">---&gt;</span> <span class="mf">2.</span><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">init</span><span class="o">/</span><span class="n">property_service</span><span class="p">.</span><span class="n">cpp</span> <span class="o">-</span> <span class="n">StartPropertyService</span>
    <span class="o">---&gt;</span> <span class="mf">2.1</span> <span class="err">调用</span><span class="n">CreateSocket</span> <span class="o">&amp;</span> <span class="n">listen</span><span class="err">，创建</span><span class="n">property_service</span><span class="err">的</span><span class="n">socket</span><span class="err">连接，监听系统属性请求</span>
    <span class="o">---&gt;</span> <span class="mf">2.2</span> <span class="n">handle_property_set_fd</span>
        <span class="o">---&gt;</span> <span class="n">HandlePropertySet</span>
            <span class="o">---&gt;</span> <span class="n">PropertySet</span>
                <span class="p">(</span><span class="err">同上</span><span class="p">)</span><span class="o">---&gt;</span> <span class="err">最终调用到</span><span class="n">bionic</span><span class="o">/</span><span class="n">libc</span><span class="err">库的</span><span class="n">Update</span><span class="o">/</span><span class="n">Add</span><span class="err">方法（</span><span class="n">__system_property_update</span><span class="err">更新</span><span class="o">/</span><span class="n">__system_property_add</span><span class="err">新增）</span>
</code></pre></div></div>

<h4 id="3472-property_load_boot_defaults">3.4.7.2. property_load_boot_defaults</h4>

<blockquote>
  <p>该方法从多个属性文件.prop中系统属性加载至properties变量，再通过PropertySet()将properties添加到系统中，并初始化只读、编译、usb相关属性值</p>
</blockquote>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/property_service.cpp</span>
<span class="c1">//入参load_debug_prop=false</span>
<span class="kt">void</span> <span class="nf">property_load_boot_defaults</span><span class="p">(</span><span class="kt">bool</span> <span class="n">load_debug_prop</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">map</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&gt;</span> <span class="n">properties</span><span class="p">;</span>
    <span class="c1">//加载各个目录下的属性文件到properties对象中</span>
    <span class="c1">//此处一般是加载/default.prop</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/system/etc/prop.default"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">))</span> <span class="p">{</span>
        <span class="c1">// Try recovery path</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/prop.default"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">))</span> <span class="p">{</span>
            <span class="c1">// Try legacy path</span>
            <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/default.prop"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/system/build.prop"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
    <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/vendor/default.prop"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
    <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/vendor/build.prop"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">SelinuxGetVendorAndroidVersion</span><span class="p">()</span> <span class="o">&gt;=</span> <span class="n">__ANDROID_API_Q__</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">//Android 10版本以上加载该prop文件</span>
        <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/odm/etc/build.prop"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/odm/default.prop"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
        <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/odm/build.prop"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/product/build.prop"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
    <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/product_services/build.prop"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
    <span class="n">load_properties_from_file</span><span class="p">(</span><span class="s">"/factory/factory.prop"</span><span class="p">,</span> <span class="s">"ro.*"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">properties</span><span class="p">);</span>
    <span class="p">....</span>
    <span class="c1">//将properties中属性通过PropertySet存入属性</span>
    <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="k">auto</span><span class="o">&amp;</span> <span class="p">[</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">]</span> <span class="o">:</span> <span class="n">properties</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">error</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">PropertySet</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">error</span><span class="p">)</span> <span class="o">!=</span> <span class="n">PROP_SUCCESS</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Could not set '"</span> <span class="o">&lt;&lt;</span> <span class="n">name</span> <span class="o">&lt;&lt;</span> <span class="s">"' to '"</span> <span class="o">&lt;&lt;</span> <span class="n">value</span>
                       <span class="o">&lt;&lt;</span> <span class="s">"' while loading .prop files"</span> <span class="o">&lt;&lt;</span> <span class="n">error</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">//初始化ro_product前缀的只读属性</span>
    <span class="n">property_initialize_ro_product_props</span><span class="p">();</span>
    <span class="c1">//初始化编译相关属性</span>
    <span class="n">property_derive_build_fingerprint</span><span class="p">();</span>
    <span class="c1">//设置persist.sys.usb.config属性，用于控制USB调试和文件传输功能(比如值为adb)</span>
    <span class="n">update_sys_usb_config</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h4 id="3473-startpropertyservice">3.4.7.3. StartPropertyService</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/property_service.cpp</span>
<span class="kt">void</span> <span class="nf">StartPropertyService</span><span class="p">(</span><span class="n">Epoll</span><span class="o">*</span> <span class="n">epoll</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">selinux_callback</span> <span class="n">cb</span><span class="p">;</span>
    <span class="n">cb</span><span class="p">.</span><span class="n">func_audit</span> <span class="o">=</span> <span class="n">SelinuxAuditCallback</span><span class="p">;</span>
    <span class="n">selinux_set_callback</span><span class="p">(</span><span class="n">SELINUX_CB_AUDIT</span><span class="p">,</span> <span class="n">cb</span><span class="p">);</span>
    <span class="n">property_set</span><span class="p">(</span><span class="s">"ro.property_service.version"</span><span class="p">,</span> <span class="s">"2"</span><span class="p">);</span>
    <span class="c1">//创建PROP_SERVICE_NAME（"property_service"） socket连接，用于监听系统属性的请求</span>
    <span class="n">property_set_fd</span> <span class="o">=</span> <span class="n">CreateSocket</span><span class="p">(</span><span class="n">PROP_SERVICE_NAME</span><span class="p">,</span> <span class="n">SOCK_STREAM</span> <span class="o">|</span> <span class="n">SOCK_CLOEXEC</span> <span class="o">|</span> <span class="n">SOCK_NONBLOCK</span><span class="p">,</span>
                                   <span class="nb">false</span><span class="p">,</span> <span class="mo">0666</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">property_set_fd</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"start_property_service socket creation failed"</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//设置Socket连接数为8</span>
    <span class="n">listen</span><span class="p">(</span><span class="n">property_set_fd</span><span class="p">,</span> <span class="mi">8</span><span class="p">);</span>
    <span class="c1">//注册epoll,监听property_set_fd改变时调用handle_property_set_fd</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="n">result</span> <span class="o">=</span> <span class="n">epoll</span><span class="o">-&gt;</span><span class="n">RegisterHandler</span><span class="p">(</span><span class="n">property_set_fd</span><span class="p">,</span> <span class="n">handle_property_set_fd</span><span class="p">);</span> <span class="o">!</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">PLOG</span><span class="p">(</span><span class="n">FATAL</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="n">result</span><span class="p">.</span><span class="n">error</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="n">handle_property_set_fd</span><span class="p">()</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">cmd</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">PROP_MSG_SETPROP</span><span class="p">:</span> <span class="p">{</span>
        <span class="p">.....</span>
        <span class="c1">//设置属性</span>
        <span class="kt">uint32_t</span> <span class="n">result</span> <span class="o">=</span>
            <span class="n">HandlePropertySet</span><span class="p">(</span><span class="n">prop_name</span><span class="p">,</span> <span class="n">prop_value</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">source_context</span><span class="p">(),</span> <span class="n">cr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">error</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">!=</span> <span class="n">PROP_SUCCESS</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Unable to set property '"</span> <span class="o">&lt;&lt;</span> <span class="n">prop_name</span> <span class="o">&lt;&lt;</span> <span class="s">"' to '"</span> <span class="o">&lt;&lt;</span> <span class="n">prop_value</span>
                       <span class="o">&lt;&lt;</span> <span class="s">"' from uid:"</span> <span class="o">&lt;&lt;</span> <span class="n">cr</span><span class="p">.</span><span class="n">uid</span> <span class="o">&lt;&lt;</span> <span class="s">" gid:"</span> <span class="o">&lt;&lt;</span> <span class="n">cr</span><span class="p">.</span><span class="n">gid</span> <span class="o">&lt;&lt;</span> <span class="s">" pid:"</span> <span class="o">&lt;&lt;</span> <span class="n">cr</span><span class="p">.</span><span class="n">pid</span> <span class="o">&lt;&lt;</span> <span class="s">": "</span>
                       <span class="o">&lt;&lt;</span> <span class="n">error</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="k">case</span> <span class="n">PROP_MSG_SETPROP2</span><span class="p">:</span> <span class="p">{</span>
        <span class="p">.....</span>
        <span class="c1">//设置属性</span>
        <span class="kt">uint32_t</span> <span class="n">result</span> <span class="o">=</span> <span class="n">HandlePropertySet</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">source_context</span><span class="p">(),</span> <span class="n">cr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">error</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">!=</span> <span class="n">PROP_SUCCESS</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">LOG</span><span class="p">(</span><span class="n">ERROR</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">"Unable to set property '"</span> <span class="o">&lt;&lt;</span> <span class="n">name</span> <span class="o">&lt;&lt;</span> <span class="s">"' to '"</span> <span class="o">&lt;&lt;</span> <span class="n">value</span>
                       <span class="o">&lt;&lt;</span> <span class="s">"' from uid:"</span> <span class="o">&lt;&lt;</span> <span class="n">cr</span><span class="p">.</span><span class="n">uid</span> <span class="o">&lt;&lt;</span> <span class="s">" gid:"</span> <span class="o">&lt;&lt;</span> <span class="n">cr</span><span class="p">.</span><span class="n">gid</span> <span class="o">&lt;&lt;</span> <span class="s">" pid:"</span> <span class="o">&lt;&lt;</span> <span class="n">cr</span><span class="p">.</span><span class="n">pid</span> <span class="o">&lt;&lt;</span> <span class="s">": "</span>
                       <span class="o">&lt;&lt;</span> <span class="n">error</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">socket</span><span class="p">.</span><span class="n">SendUint32</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">....</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kt">uint32_t</span> <span class="n">HandlePropertySet</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">name</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">value</span><span class="p">,</span>
                           <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">source_context</span><span class="p">,</span> <span class="k">const</span> <span class="n">ucred</span><span class="o">&amp;</span> <span class="n">cr</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">*</span> <span class="n">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">//检查prop权限</span>
    <span class="c1">//1.检查属性名称是否正确：1、属性名长度需不小于1；2、开头与结尾不能为.；3、字符:0~9+a~z+A~Z+'.'+'@'+'-'+'_'+':'</span>
    <span class="c1">//2.检查ctl控制属性selinux权限</span>
    <span class="c1">//3.检查selinux权限，property_service对该属性是否有set权限</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">auto</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">CheckPermissions</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">source_context</span><span class="p">,</span> <span class="n">cr</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span> <span class="n">ret</span> <span class="o">!=</span> <span class="n">PROP_SUCCESS</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="p">.....</span>
    <span class="c1">//设置属性</span>
    <span class="k">return</span> <span class="n">PropertySet</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">uint32_t</span> <span class="n">PropertySet</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">name</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">value</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">*</span> <span class="n">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">size_t</span> <span class="n">valuelen</span> <span class="o">=</span> <span class="n">value</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
    <span class="c1">//检测属性合法性</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">IsLegalPropertyName</span><span class="p">(</span><span class="n">name</span><span class="p">))</span> <span class="p">{</span>
        <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="s">"Illegal property name"</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">PROP_ERROR_INVALID_NAME</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="c1">//不是ro的属性的值不能超过92</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">valuelen</span> <span class="o">&gt;=</span> <span class="n">PROP_VALUE_MAX</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">StartsWith</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="s">"ro."</span><span class="p">))</span> <span class="p">{</span>
        <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="s">"Property value too long"</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">PROP_ERROR_INVALID_VALUE</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="p">.....</span>
    <span class="c1">//检测属性是否已存在</span>
    <span class="n">prop_info</span><span class="o">*</span> <span class="n">pi</span> <span class="o">=</span> <span class="p">(</span><span class="n">prop_info</span><span class="o">*</span><span class="p">)</span> <span class="n">__system_property_find</span><span class="p">(</span><span class="n">name</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">pi</span> <span class="o">!=</span> <span class="nb">nullptr</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// ro.* properties are actually "write-once".</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">StartsWith</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="s">"ro."</span><span class="p">))</span> <span class="p">{</span>
            <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="s">"Read-only property was already set"</span><span class="p">;</span>
            <span class="k">return</span> <span class="n">PROP_ERROR_READ_ONLY_PROPERTY</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="c1">//1. 属性已存在,并且非ro只读属性，更新属性值</span>
        <span class="n">__system_property_update</span><span class="p">(</span><span class="n">pi</span><span class="p">,</span> <span class="n">value</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">valuelen</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="c1">//2. 属性不存在,添加属性值</span>
        <span class="kt">int</span> <span class="n">rc</span> <span class="o">=</span> <span class="n">__system_property_add</span><span class="p">(</span><span class="n">name</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">name</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">value</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">valuelen</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">rc</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="o">*</span><span class="n">error</span> <span class="o">=</span> <span class="s">"__system_property_add failed"</span><span class="p">;</span>
            <span class="k">return</span> <span class="n">PROP_ERROR_SET_FAILED</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">//避免在load所有属性之前将属性写入disk，防止属性值被覆盖。</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">persistent_properties_loaded</span> <span class="o">&amp;&amp;</span> <span class="n">StartsWith</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="s">"persist."</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">WritePersistentProperty</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="c1">//特殊属性值(如sys.powerctl)改变后系统需要立即处理</span>
    <span class="n">property_changed</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">PROP_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>此处比较重要的两个方法就是，这两个方法最终调用到bionic/libc/system_properties/system_properties.cpp的Update/Add方法（此处不再往下标准库详细梳理）：</strong></p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">__system_property_update</code>：更新属性值</li>
  <li><code class="language-plaintext highlighter-rouge">__system_property_add</code>：添加属性值</li>
</ol>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//bionic/libc/bionic/system_property_api.cpp</span>
<span class="k">static</span> <span class="n">SystemProperties</span> <span class="n">system_properties</span><span class="p">;</span>
<span class="kt">int</span> <span class="nf">__system_property_update</span><span class="p">(</span><span class="n">prop_info</span><span class="o">*</span> <span class="n">pi</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">value</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">len</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="n">system_properties</span><span class="p">.</span><span class="n">Update</span><span class="p">(</span><span class="n">pi</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span><span class="c1">//更新属性值</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">__system_property_add</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">namelen</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">value</span><span class="p">,</span>
                          <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">valuelen</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="n">system_properties</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">namelen</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">valuelen</span><span class="p">);</span><span class="c1">//添加属性值</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="348-step-5解析initrc等文件由action触发运行rc文件">3.4.8. Step 5：解析init.rc等文件，由action触发运行rc文件</h3>

<blockquote>
  <p>在下一篇详细梳理init.rc及其他目录rc的启动，此处简单说明下加载和触发。</p>
</blockquote>

<p><strong>整体流程：</strong></p>
<ol>
  <li>解析/init.rc，然后递归地解析它的每个导入(此处递归是import的递归，不是文件夹的递归，文件夹不支持递归);</li>
  <li>解析/system/etc/init/目录的内容，按字母顺序排列并按顺序解析，在每个文件解析后递归地进行导入;</li>
  <li>步骤2重复/vendor/etc/init</li>
  <li>通过action机制触发器执行rc文件，从代码中看执行顺序是：”on early-init” -&gt; “on init” -&gt; “on late-init”</li>
</ol>

<hr />

<h4 id="3481-代码说明">3.4.8.1. 代码说明</h4>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//system/core/init/init.cpp</span>
<span class="kt">int</span> <span class="nf">SecondStageMain</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">.....</span>
    <span class="c1">//-----Step 5: 解析init.rc等文件，建立rc文件的action 、service，启动其他进程</span>
    <span class="n">ActionManager</span><span class="o">&amp;</span> <span class="n">am</span> <span class="o">=</span> <span class="n">ActionManager</span><span class="o">::</span><span class="n">GetInstance</span><span class="p">();</span>
    <span class="n">ServiceList</span><span class="o">&amp;</span> <span class="n">sm</span> <span class="o">=</span> <span class="n">ServiceList</span><span class="o">::</span><span class="n">GetInstance</span><span class="p">();</span>
    <span class="n">LoadBootScripts</span><span class="p">(</span><span class="n">am</span><span class="p">,</span> <span class="n">sm</span><span class="p">);</span>
    <span class="p">...</span>
    <span class="c1">//Action机制，添加action</span>
    <span class="c1">// 执行rc文件中触发器为 on early-init 的语句</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueEventTrigger</span><span class="p">(</span><span class="s">"early-init"</span><span class="p">);</span>
    <span class="p">.....</span>
    <span class="c1">// Trigger all the boot actions to get us started.</span>
    <span class="c1">//执行rc文件中触发器为on init的语句</span>
    <span class="n">am</span><span class="p">.</span><span class="n">QueueEventTrigger</span><span class="p">(</span><span class="s">"init"</span><span class="p">);</span>
    <span class="p">.....</span>
    <span class="c1">//正常情况该属性值是normal，执行on late-init</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">bootmode</span> <span class="o">=</span> <span class="n">GetProperty</span><span class="p">(</span><span class="s">"ro.bootmode"</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">bootmode</span> <span class="o">==</span> <span class="s">"charger"</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">am</span><span class="p">.</span><span class="n">QueueEventTrigger</span><span class="p">(</span><span class="s">"charger"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">am</span><span class="p">.</span><span class="n">QueueEventTrigger</span><span class="p">(</span><span class="s">"late-init"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="p">....</span>
    <span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">......</span>
        <span class="c1">//依次执行每个action中携带command对应的执行函数</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">waiting_for_prop</span> <span class="o">||</span> <span class="n">Service</span><span class="o">::</span><span class="n">is_exec_service_running</span><span class="p">()))</span> <span class="p">{</span>
            <span class="n">am</span><span class="p">.</span><span class="n">ExecuteOneCommand</span><span class="p">();</span>
        <span class="p">}</span>
        <span class="p">.....</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="n">LoadBootScripts</span><span class="p">(</span><span class="n">ActionManager</span><span class="o">&amp;</span> <span class="n">action_manager</span><span class="p">,</span> <span class="n">ServiceList</span><span class="o">&amp;</span> <span class="n">service_list</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">//创建Parser解析对象，例如service、on、import对象</span>
    <span class="n">Parser</span> <span class="n">parser</span> <span class="o">=</span> <span class="n">CreateParser</span><span class="p">(</span><span class="n">action_manager</span><span class="p">,</span> <span class="n">service_list</span><span class="p">);</span>
    <span class="c1">//bootscript为空</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">bootscript</span> <span class="o">=</span> <span class="n">GetProperty</span><span class="p">(</span><span class="s">"ro.boot.init_rc"</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
    <span class="c1">//主要解析三个部分的rc文件：</span>
    <span class="c1">//1.根目录/init.rc （一般车机根目录其他的rc文件都会在init.rc中使用import导入加载）</span>
    <span class="c1">//2./system/etc/init</span>
    <span class="c1">//3./vendor/etc/init</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">bootscript</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
        <span class="c1">//通过ParseConfig然后调用ParseConfigFile()解析(代码：system/core/init/parser.cpp)</span>
        <span class="c1">//ParseConfig 函数传入需要解析的rc文件的路径：</span>
        <span class="c1">//      1. 如果是目录，则遍历该目录取出所有的rc文件并调用ParseConfigFile函数进行解析</span>
        <span class="c1">//      2. 如果是文件路径，则直接调用 ParseConfigFile 函数进行解析。</span>
        <span class="c1">//init解析rc文件的过程中，首先调用ReadFile函数将rc文件的内容全部保存为字符串，存在data中，</span>
        <span class="c1">//然后调用ParseData进行解析。ParseData函数会根据关键字解析出service和action，最终挂在到service_list与action_manager 的向量(vector)上</span>
        <span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/init.rc"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/system/etc/init"</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">late_import_paths</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="s">"/system/etc/init"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/product/etc/init"</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">late_import_paths</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="s">"/product/etc/init"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/product_services/etc/init"</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">late_import_paths</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="s">"/product_services/etc/init"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/odm/etc/init"</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">late_import_paths</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="s">"/odm/etc/init"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="s">"/vendor/etc/init"</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">late_import_paths</span><span class="p">.</span><span class="n">emplace_back</span><span class="p">(</span><span class="s">"/vendor/etc/init"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">parser</span><span class="p">.</span><span class="n">ParseConfig</span><span class="p">(</span><span class="n">bootscript</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">Parser</span> <span class="n">CreateParser</span><span class="p">(</span><span class="n">ActionManager</span><span class="o">&amp;</span> <span class="n">action_manager</span><span class="p">,</span> <span class="n">ServiceList</span><span class="o">&amp;</span> <span class="n">service_list</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">Parser</span> <span class="n">parser</span><span class="p">;</span>
    <span class="n">parser</span><span class="p">.</span><span class="n">AddSectionParser</span><span class="p">(</span><span class="s">"service"</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">ServiceParser</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">service_list</span><span class="p">,</span> <span class="n">subcontexts</span><span class="p">));</span>
    <span class="n">parser</span><span class="p">.</span><span class="n">AddSectionParser</span><span class="p">(</span><span class="s">"on"</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">ActionParser</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">action_manager</span><span class="p">,</span> <span class="n">subcontexts</span><span class="p">));</span>
    <span class="n">parser</span><span class="p">.</span><span class="n">AddSectionParser</span><span class="p">(</span><span class="s">"import"</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">ImportParser</span><span class="o">&gt;</span><span class="p">(</span><span class="o">&amp;</span><span class="n">parser</span><span class="p">));</span>
    <span class="k">return</span> <span class="n">parser</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h1 id="4-总结">4. 总结</h1>

<p>本篇主要讲述init进程的启动，以及init进程启动过程的主要工作内容，这部分主要还是集中在init进程中。下一步开始梳理init.rc及其他目录rc文件的触发执行，将会开始扩散到各个子进程中。</p>

<hr />

<h1 id="5-参考">5. 参考</h1>

<ul>
  <li><a href="https://sunwengang.top/android/android_init_service/">Android init进程</a></li>
  <li><a href="https://blog.csdn.net/tkwxty/article/details/106020050">Android 9(P)之init进程启动源码分析指南之一</a></li>
  <li><a href="https://cloud.tencent.com/developer/article/2357443">init进程启动过程</a></li>
  <li><a href="https://sunwengang.top/android/android_zygote_systemserver/">Android zygote和SystemServer进程</a></li>
  <li><a href="https://blog.csdn.net/w346665682/article/details/112205539">安卓11 init初始化以及init.rc的解析执行过程详解</a></li>
  <li><a href="https://blog.csdn.net/u010783226/article/details/119810208">Android init 启动进程分析</a></li>
  <li><a href="https://zhuanlan.zhihu.com/p/631104181">Kernel怎么跳转到Android：linux与安卓的交界</a></li>
  <li><a href="https://zhuanlan.zhihu.com/p/555739442">一文了解Linux下2号进程的kthreadd</a></li>
  <li><a href="https://juejin.cn/post/7251833062618071098">Android进程管理1—进程优先级adj</a></li>
  <li><a href="https://blog.51cto.com/u_16099243/6918155">Android直接从init产生进程 安卓系统init进程号</a></li>
  <li><a href="https://www.jianshu.com/p/84bda2be2274">Android O 启动过程init log，和遇到问题</a></li>
  <li><a href="https://blog.csdn.net/stephen_curry300/article/details/126110983">安卓FirstStageMount阶段解析【连载】（一）创建设备Create</a></li>
  <li><a href="https://blog.csdn.net/andlee/article/details/134768876">Sec-comp机制简介及编程案例</a></li>
  <li><a href="https://blog.csdn.net/lfcaolibin/article/details/43674569">SEAndroid安全机制框架分析</a></li>
  <li><a href="https://www.jb51.net/article/277418.htm">Android selinux策略文件的编译与加载</a></li>
  <li><a href="https://blog.csdn.net/2301_80105928/article/details/139012725">epoll详解</a></li>
</ul>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="android" /><summary type="html"><![CDATA[本篇介绍Android系统启动流程的开始阶段，从kernel内核空间启动第一个用户空间init进程，然后梳理init进程启动的几个阶段做了哪些任务。]]></summary></entry><entry><title type="html">Android 12编译系统-5 ninja介绍</title><link href="https://alonealive.github.io/android/android12_BuildSystem_5/" rel="alternate" type="text/html" title="Android 12编译系统-5 ninja介绍" /><published>2024-03-26T06:40:02+00:00</published><updated>2024-03-26T06:40:02+00:00</updated><id>https://alonealive.github.io/android/android12_BuildSystem_5</id><content type="html" xml:base="https://alonealive.github.io/android/android12_BuildSystem_5/"><![CDATA[<blockquote>
  <p>最开始，Ninja是用于Chromium浏览器中，Android在SDK7.0中也引入了Ninja。Ninja其实就是一个编译系统，如同make，使用Ninja主要目的就是因为其编译速度快</p>
</blockquote>

<h1 id="1-ninja简介">1. Ninja简介</h1>

<p>Ninja主要是一个注重速度的小型编译系统，Ninja加入其他编译系统的海洋之中，主要目的就是更加的快速。</p>

<p>主要有两个有别与其他编译系统的特点：</p>
<ul>
  <li>Ninja的input files是由高级别的编译系统生成而来；</li>
  <li>Ninja设计主要让编译尽可能的快；</li>
</ul>

<p>Ninja包含描述任意依赖关系的最基本的功能。由于语法简单而无法表达复杂的决策。为此，Ninja用一个单独的程序来生成<code class="language-plaintext highlighter-rouge">input files</code>。该程序能分析系统的依赖关系，而且尽可能提前多做出策略，以便增加编译速度。</p>

<hr />

<h2 id="11-makefile与ninja区别">1.1. Makefile与Ninja区别</h2>

<p>Makefile：</p>
<ul>
  <li>默认文件名为Makefile或makefile，也常用<code class="language-plaintext highlighter-rouge">.make</code>或<code class="language-plaintext highlighter-rouge">.mk</code>作为文件后缀</li>
  <li>执行Makefile的程序，默认是GNU make，也有一些其它的实现</li>
</ul>

<p>Ninja：</p>
<ul>
  <li>默认文件名是<code class="language-plaintext highlighter-rouge">build.ninja</code>，其它文件也以<code class="language-plaintext highlighter-rouge">.ninja</code>为后缀</li>
  <li>Ninja的执行程序，就是ninja命令</li>
</ul>

<hr />

<h2 id="12-代码位置">1.2. 代码位置</h2>

<p>在Android项目中，make需要编译主机上安装，作为环境的一部分。 而<code class="language-plaintext highlighter-rouge">ninja</code>命令则是Android平台代码自带：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>android/prebuilts<span class="nv">$ </span>find <span class="nt">-name</span> ninja
./build-tools/linux-x86/asan/bin/ninja
./build-tools/linux-x86/bin/ninja
./build-tools/darwin-x86/bin/ninja
</code></pre></div></div>

<hr />

<h1 id="2-ninja命令">2. Ninja命令</h1>

<p>很多参数，和make是比较类似的，比如-f、-j等，不再赘述。 有趣的是-t、-d、-w这三个参数，最有用的是-t</p>

<p><code class="language-plaintext highlighter-rouge">ninja -t clean</code>是清理产物，是自带的，而<code class="language-plaintext highlighter-rouge">make clean</code>往往需要自己实现。 其它都是查看编译过程信息的工具，各有作用，可以进行复杂的编译依赖分析</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>android/prebuilts/build-tools/linux-x86/bin<span class="nv">$ </span>./ninja <span class="nt">-h</span>
usage: ninja <span class="o">[</span>options] <span class="o">[</span>targets...]

<span class="k">if </span>targets are unspecified, builds the <span class="s1">'default'</span> target <span class="o">(</span>see manual<span class="o">)</span><span class="nb">.</span>

options:
  <span class="nt">--version</span>      print ninja version <span class="o">(</span><span class="s2">"1.9.0.git"</span><span class="o">)</span>
  <span class="nt">-v</span>, <span class="nt">--verbose</span>  show all <span class="nb">command </span>lines <span class="k">while </span>building

  <span class="nt">-C</span> DIR   change to DIR before doing anything <span class="k">else</span>
  <span class="nt">-f</span> FILE  specify input build file <span class="o">[</span><span class="nv">default</span><span class="o">=</span>build.ninja]输入构建.ninja文件

  <span class="nt">-j</span> N     run N <span class="nb">jobs </span><span class="k">in </span>parallel <span class="o">(</span>0 means infinity<span class="o">)</span> <span class="o">[</span><span class="nv">default</span><span class="o">=</span>14 on this system]类似make <span class="nt">-j8</span>
  <span class="nt">-k</span> N     keep going <span class="k">until </span>N <span class="nb">jobs </span>fail <span class="o">(</span>0 means infinity<span class="o">)</span> <span class="o">[</span><span class="nv">default</span><span class="o">=</span>1]
  <span class="nt">-l</span> N     <span class="k">do </span>not start new <span class="nb">jobs </span><span class="k">if </span>the load average is greater than N
  <span class="nt">-n</span>       dry run <span class="o">(</span>don not run commands but act like they succeeded<span class="o">)</span>

  <span class="nt">-d</span> MODE  <span class="nb">enable </span>debugging <span class="o">(</span>use <span class="s1">'-d list'</span> to list modes<span class="o">)</span>
  <span class="nt">-t</span> TOOL  run a subtool <span class="o">(</span>use <span class="s1">'-t list'</span> to list subtools<span class="o">)</span>
    terminates toplevel options<span class="p">;</span> further flags are passed to the tool
  <span class="nt">-o</span> FLAG  adjust options <span class="o">(</span>use <span class="s1">'-o list'</span> to list options<span class="o">)</span>
  <span class="nt">-w</span> FLAG  adjust warnings <span class="o">(</span>use <span class="s1">'-w list'</span> to list warnings<span class="o">)</span>

  <span class="nt">--frontend</span> COMMAND    execute COMMAND and pass serialized build output to it
  <span class="nt">--frontend_file</span> FILE  write serialized build output to FILE
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>android/prebuilts/build-tools/linux-x86/bin<span class="nv">$ </span>./ninja <span class="nt">-t</span> list
ninja subtools:
    browse  browse dependency graph <span class="k">in </span>a web browser 在web浏览器中浏览依赖关系图
     clean  clean built files 清除生成的文件
  commands  list all commands required to rebuild given targets 列出重建给定目标所需的所有命令
      deps  show dependencies stored <span class="k">in </span>the deps log 显示存储在deps日志中的依赖项
     graph  output graphviz dot file <span class="k">for </span>targets 输出目标的graphviz点文件
    inputs  show all <span class="o">(</span>recursive<span class="o">)</span> inputs <span class="k">for </span>a target 显示目标的所有（递归）输入
      path  find dependency path between two targets 查找依赖项两个目标之间的路径
     query  show inputs/outputs <span class="k">for </span>a path 显示路径的输入/输出
   targets  list targets by their rule or depth <span class="k">in </span>the DAG 在DAG中按规则或深度列出目标
    compdb  dump JSON compilation database to stdout 将JSON编译数据库转储到stdout
 recompact  recompacts ninja-internal data structures 重组ninja-internal内部数据结构
</code></pre></div></div>

<hr />

<h1 id="3-android中的ninja">3. Android中的ninja</h1>

<p>参考Android 12源码，Android编译中会生成：</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code></li>
  <li><code class="language-plaintext highlighter-rouge">out/build-&lt;product_name&gt;.ninja</code></li>
  <li><code class="language-plaintext highlighter-rouge">out/build-&lt;product_name&gt;-package.ninja</code></li>
</ul>

<p>最终合成为<code class="language-plaintext highlighter-rouge">out/out/combined-&lt;product_name&gt;.ninja</code></p>

<h2 id="31-build-ninja文件">3.1. build-*.ninja文件</h2>

<p>out根目录通常非常大，几十到几百MB。 对make全编译，命名是<code class="language-plaintext highlighter-rouge">build-&lt;product_name&gt;.ninja</code>。 如果Makefile发生修改，需要重新产生Ninja文件。</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">ckati</code>将<code class="language-plaintext highlighter-rouge">Android.mk</code>编译成<code class="language-plaintext highlighter-rouge">out/build-&lt;product_name&gt;.ninja</code></li>
  <li><code class="language-plaintext highlighter-rouge">ckati</code>将<code class="language-plaintext highlighter-rouge">Android.mk</code>编译成<code class="language-plaintext highlighter-rouge">out/build-&lt;product_name&gt;-package.ninja</code></li>
</ol>

<h2 id="32-outsoongbuildninja文件">3.2. out/soong/build.ninja文件</h2>

<p><code class="language-plaintext highlighter-rouge">soong+blueprint</code>将<code class="language-plaintext highlighter-rouge">Android.bp</code>编译成<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code></p>

<p>它是从所有的Android.bp转换过来的。</p>

<p><code class="language-plaintext highlighter-rouge">build-*.ninja</code>是从所有的Makefile，用Kati转换过来的，包括<code class="language-plaintext highlighter-rouge">build/core/*.mk</code>和所有的<code class="language-plaintext highlighter-rouge">Android.mk</code>。 所以，在不使用Soong时，它是唯一入口。在使用了Soong以后，会新增源于<code class="language-plaintext highlighter-rouge">Android.bp</code>的<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code>，所以最终会需要<code class="language-plaintext highlighter-rouge">combined-*.ninja</code>来组合一下</p>

<p><strong>PS：</strong>可以通过命令，单独产生全编译的Ninja文件：<code class="language-plaintext highlighter-rouge">make nothing</code></p>

<hr />

<h2 id="33-combined-ninja-文件">3.3. combined-*.ninja 文件</h2>

<p>运行Ninja命令（代码入口：<code class="language-plaintext highlighter-rouge">build/soong/ui/build/ninja.go</code>）， 解析上面的<code class="language-plaintext highlighter-rouge">.ninja</code>文件合成最终的<code class="language-plaintext highlighter-rouge">out/combined-&lt;product_name&gt;.ninja</code></p>

<p><strong>文件内容示例如下：</strong></p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builddir</span> <span class="o">=</span> <span class="n">out</span>
<span class="n">pool</span> <span class="n">local_pool</span>
 <span class="n">depth</span> <span class="o">=</span> <span class="m">42</span>
<span class="n">build</span> <span class="n">_kati_always_build_</span><span class="o">:</span> <span class="n">phony</span>
<span class="n">subninja</span> <span class="n">out</span><span class="o">/</span><span class="n">build</span><span class="o">-&lt;</span><span class="n">product_name</span><span class="o">&gt;.</span><span class="n">ninja</span>
<span class="n">subninja</span> <span class="n">out</span><span class="o">/</span><span class="n">build</span><span class="o">-&lt;</span><span class="n">product_name</span><span class="o">&gt;-</span><span class="k">package</span><span class="o">.</span><span class="n">ninja</span>
<span class="n">subninja</span> <span class="n">out</span><span class="o">/</span><span class="n">soong</span><span class="o">/</span><span class="n">build</span><span class="o">.</span><span class="n">ninja</span>
</code></pre></div></div>

<p>使用Soong后，<code class="language-plaintext highlighter-rouge">combined-*.ninja</code>是Ninja编译执行的真正入口！</p>

<p><img src="../../assets/post/2024/2024-03-26-android12_BuildSystem_5/Android编译模块_Ninja文件关系图.png" alt="" /></p>

<hr />

<h1 id="4-ninja编译">4. Ninja编译</h1>

<p>在产生全编译的Ninja文件后，可以绕过Makefile，单独使用ninja进行编译：</p>

<ul>
  <li>全编译（7.0版本），相当于make：<code class="language-plaintext highlighter-rouge">ninja -f out/build-&lt;product_name&gt;.ninja</code></li>
  <li>单独编译模块，比如Settings，相当于make Settings：<code class="language-plaintext highlighter-rouge">ninja -f out/build-&lt;product_name&gt;.ninja Settings</code></li>
</ul>

<p>在8.0以上，上面的文件应该替换为<code class="language-plaintext highlighter-rouge">out/combined-&lt;product_name&gt;.ninja</code>，否则可能找不到某些Target。</p>

<p>另外，还有办法不用输入<code class="language-plaintext highlighter-rouge">-f</code>参数。 如前所述，如同Makefile之于make，ninja默认的编译文件是<code class="language-plaintext highlighter-rouge">build.ninja</code>。 所以，使用软链接，可以避免每次输入繁琐的<code class="language-plaintext highlighter-rouge">-f</code>。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ln</span> <span class="nt">-s</span> out/combined-&lt;product_name&gt;.ninja build.ninja
ninja Settings
</code></pre></div></div>

<p>用ninja进行单模块编译的好处，除了更快以外，还不用生成单模块的Ninja文件，省了四五分钟</p>

<hr />

<h1 id="5-参考">5. 参考</h1>

<ul>
  <li><a href="https://justinwei.blog.csdn.net/article/details/84770716">Android中Ninja简介</a></li>
  <li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&amp;mid=2652469394&amp;idx=1&amp;sn=7a9d619da1fe969a7894e9f29dceff69&amp;chksm=bd12e9798a65606f2ba7648aedf5baa0d1757c77a090454dc93772e0bad5cf74be808b1a25eb&amp;scene=178&amp;cur_album_id=1552818877418487808#rd">Ninja简介-Android10.0编译系统（九）</a></li>
</ul>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="android" /><category term="build" /><summary type="html"><![CDATA[最开始，Ninja是用于Chromium浏览器中，Android在SDK7.0中也引入了Ninja。Ninja其实就是一个编译系统，如同make，使用Ninja主要目的就是因为其编译速度快]]></summary></entry><entry><title type="html">Android 12编译系统-4 kati介绍</title><link href="https://alonealive.github.io/android/android12_BuildSystem_4/" rel="alternate" type="text/html" title="Android 12编译系统-4 kati介绍" /><published>2024-03-22T09:40:02+00:00</published><updated>2024-03-22T09:40:02+00:00</updated><id>https://alonealive.github.io/android/android12_BuildSystem_4</id><content type="html" xml:base="https://alonealive.github.io/android/android12_BuildSystem_4/"><![CDATA[<blockquote>
  <p>Kati是为了提高Android编译速度而产生的实验性的GNU make克隆的工具。本身没有提供快速编译，而是将Makefile文件转换为Ninja文件，再通过Ninja进行编译提速。
目前Android 12上，Kati（即<code class="language-plaintext highlighter-rouge">prebuilts/build-tools/linux-x86/bin/ckati</code>）的作用就是生成<code class="language-plaintext highlighter-rouge">build.ninja</code>文件</p>
</blockquote>

<h1 id="1-kati简介">1. kati简介</h1>

<p>Kati最开始在Go中实现的，期初开发者认为Go可以获取足够的性能，但是最终却是使得Kati的Go版本变慢了。所以后来Kati用C++重写实现，即ckati，除了C++实现，还有部分的Go和sh，这应该是考虑之后的最好性能。</p>

<p>kati刚开始是使用Golang编写的，但是后来验证下来发现编译速度不行，于是改成C++编写，所以现在存在两个版本：kati、ckati。在Android10.0编译过程中，是通过ckati来把makefile文件转换成ninja文件的</p>

<p>从Android 11开始，不再释放源码，而是直接作为prebuilts预编译资源，Android 10之前源码在<code class="language-plaintext highlighter-rouge">android/build/kati</code>，现在是直接调用预编译<code class="language-plaintext highlighter-rouge">prebuilts/build-tools/linux-x86/bin/ckati</code>工具，使用ckati工具，以及传入的一系列参数，编译出<code class="language-plaintext highlighter-rouge">out/build-aosp_arm.ninja</code></p>

<h2 id="11-整体架构">1.1. 整体架构</h2>

<p>Kati由几部分组成：</p>
<ul>
  <li>解析器（Parser）</li>
  <li>评估器（Evaluator）：输出构建规则(build rules)和变量表(variable table)的列表</li>
  <li>依赖构建器（Dependency builder）：从构建规则列表中创建依赖图(dependency graph)</li>
  <li>执行器（Executor）</li>
  <li>Ninja生成器（Ninja generator）</li>
</ul>

<hr />

<h2 id="12-代码位置">1.2. 代码位置</h2>

<p>Android N、O、P代码目录在：<code class="language-plaintext highlighter-rouge">android/build/kati/</code></p>

<p>Android 11开始，提供预编译结果文件使用：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>find prebuilts/ <span class="nt">-name</span> ckati
prebuilts/build-tools/linux-x86/asan/bin/ckati
prebuilts/build-tools/linux-x86/bin/ckati
prebuilts/build-tools/darwin-x86/bin/ckati
</code></pre></div></div>

<p>github的source code路径为：<code class="language-plaintext highlighter-rouge">https://github.com/google/kati</code></p>

<p>可以通过下面命令获取source code：<code class="language-plaintext highlighter-rouge">git clone https://github.com/google/kati.git</code></p>

<hr />

<h2 id="13-编译">1.3. 编译</h2>

<p>在项目编译的时候会直接使用<code class="language-plaintext highlighter-rouge">prebuilts/build-tools</code>这里的ckati，详细看<code class="language-plaintext highlighter-rouge">prebuilts/build-tools/build-prebuilts.sh</code>，下面节选部分内容：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Use toybox and other prebuilts even outside of the build (test running, go, etc)</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="k">${</span><span class="nv">TOP</span><span class="k">}</span>/prebuilts/build-tools/path/<span class="k">${</span><span class="nv">OS</span><span class="k">}</span><span class="nt">-x86</span>:<span class="nv">$PATH</span>

<span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="k">${</span><span class="nv">build_soong</span><span class="k">}</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">SOONG_OUT</span><span class="o">=</span><span class="k">${</span><span class="nv">OUT_DIR</span><span class="k">}</span>/soong
    <span class="c"># 路径：out/soong/host/linux-x86/bin</span>
    <span class="nv">SOONG_HOST_OUT</span><span class="o">=</span><span class="k">${</span><span class="nv">OUT_DIR</span><span class="k">}</span>/soong/host/<span class="k">${</span><span class="nv">OS</span><span class="k">}</span><span class="nt">-x86</span>
    <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="k">${</span><span class="nv">clean</span><span class="k">}</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">||</span> <span class="nb">rm</span> <span class="nt">-rf</span> <span class="k">${</span><span class="nv">SOONG_OUT</span><span class="k">}</span>
    <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="k">${</span><span class="nv">SOONG_OUT</span><span class="k">}</span>
    <span class="nb">rm</span> <span class="nt">-rf</span> <span class="k">${</span><span class="nv">SOONG_OUT</span><span class="k">}</span>/dist <span class="k">${</span><span class="nv">SOONG_OUT</span><span class="k">}</span>/dist-common
    <span class="nb">cat</span> <span class="o">&gt;</span> <span class="k">${</span><span class="nv">SOONG_OUT</span><span class="k">}</span>/soong.variables <span class="o">&lt;&lt;</span> <span class="no">EOF</span><span class="sh">
{
    "Allow_missing_dependencies": true,
    "HostArch":"x86_64",
    "VendorVars": {
        "cpython3": {
            "force_build_host": "true"
        }
    }
}

 SOONG_BINARIES=(
        acp
        aidl
        .....
        ckati
        hidl-gen
        hidl-lint
        m4
        make
        ninja
        ....
        }

binaries="</span><span class="k">${</span><span class="nv">SOONG_BINARIES</span><span class="p">[@]/#/</span><span class="k">${</span><span class="nv">SOONG_HOST_OUT</span><span class="k">}</span><span class="p">/bin/</span><span class="k">}</span><span class="sh">"
</span></code></pre></div></div>

<hr />

<h1 id="2-kati用法">2. kati用法</h1>

<p>在Android的编译过程中，ckati会自动被使用，无须开发者担心。</p>

<p>单独使用时，在包含Makefile的目录下，执行ckati，效果与make基本相同。执行<code class="language-plaintext highlighter-rouge">ckati --ninja</code>，可以根据Makefile生成<code class="language-plaintext highlighter-rouge">build.ninja</code>文件，并且附带<code class="language-plaintext highlighter-rouge">env-aosp_arm.sh</code>和<code class="language-plaintext highlighter-rouge">ninja-aosp_arm.sh</code>（在out根目录）。通过env-aosp_arm.sh来配置环境，通过执行<code class="language-plaintext highlighter-rouge">./ninja-aosp_arm.sh</code>来启动Ninja、使用build.ninja编译。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># （1）文件env-aosp_arm.sh内容</span>
out<span class="nv">$ </span><span class="nb">cat </span>env-aosp_arm.sh
<span class="c">#!/bin/sh</span>
<span class="c"># Generated by kati unknown</span>

<span class="c"># （2）文件ninja-aosp_arm.sh内容</span>
out<span class="nv">$ </span><span class="nb">cat </span>ninja-aosp_arm.sh
<span class="c">#!/bin/sh</span>
<span class="c"># Generated by kati unknown</span>

<span class="nb">.</span> out/env-aosp_arm.sh
<span class="nb">exec </span>ninja <span class="nt">-f</span> out/build-aosp_arm.ninja <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div></div>

<p>除了<code class="language-plaintext highlighter-rouge">--ninja</code>以外，ckati支持很多其它参数。比如，和make一样，可以通过<code class="language-plaintext highlighter-rouge">-f</code>指定Makefile位置，通过<code class="language-plaintext highlighter-rouge">-j</code>指定线程数。</p>

<hr />

<h1 id="3-参考">3. 参考</h1>

<ul>
  <li><a href="https://justinwei.blog.csdn.net/article/details/84784567">Android中Kati简介</a></li>
  <li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&amp;mid=2652469368&amp;idx=1&amp;sn=baed055a7301d4f7407e554daf9779de&amp;chksm=bd12e9938a65608550c4362c117454fe56fff4d22de6b22803369dff47ae16d6921d81a42086&amp;scene=178&amp;cur_album_id=1552818877418487808#rd">Kati详解-Android10.0编译系统（五）</a></li>
</ul>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="android" /><category term="build" /><summary type="html"><![CDATA[Kati是为了提高Android编译速度而产生的实验性的GNU make克隆的工具。本身没有提供快速编译，而是将Makefile文件转换为Ninja文件，再通过Ninja进行编译提速。 目前Android 12上，Kati（即prebuilts/build-tools/linux-x86/bin/ckati）的作用就是生成build.ninja文件]]></summary></entry><entry><title type="html">Android 12编译系统-3 打包image镜像流程</title><link href="https://alonealive.github.io/android/android12_BuildSystem_3/" rel="alternate" type="text/html" title="Android 12编译系统-3 打包image镜像流程" /><published>2024-03-09T10:50:02+00:00</published><updated>2024-03-09T10:50:02+00:00</updated><id>https://alonealive.github.io/android/android12_BuildSystem_3</id><content type="html" xml:base="https://alonealive.github.io/android/android12_BuildSystem_3/"><![CDATA[<blockquote>
  <p>在Android 12 AOSP源码的<code class="language-plaintext highlighter-rouge">build/core/main.mk</code>中定义了很多伪目标，我们可以直接通过<code class="language-plaintext highlighter-rouge">make 目标名称</code>进行编译，镜像的生成定义也在该文件中。本篇主要以system镜像为例，进行流程梳理分析。</p>
</blockquote>

<h1 id="1-buildcoremainmk系统镜像定义">1. build/core/main.mk系统镜像定义</h1>

<p>这些镜像有：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">伪目标名称</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">.PHONY: ramdisk</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_RAMDISK_TARGET)</code>，生成ramdisk.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: ramdisk_debug</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_DEBUG_RAMDISK_TARGET)</code>，生成ramdisk-debug.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: ramdisk_test_harness</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)</code>，生成ramdisk-test-harness.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: userdataimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_USERDATAIMAGE_TARGET)</code>，生成userdata.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: cacheimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_CACHEIMAGE_TARGET)</code>，生成cache.img（但是A/B系统不包含recovery.img和cache.img，即Android O及之后）</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendorimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VENDORIMAGE_TARGET)</code>，生成vendor.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendorbootimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VENDOR_BOOTIMAGE_TARGET)</code>，生成vendor_boot.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendorbootimage_debug</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET)</code>，生成vendor_boot-debug.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendorbootimage_test_harness</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET)</code>，生成vendor_boot-test-harness.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: systemextimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_SYSTEM_EXTIMAGE_TARGET)</code>，生成system_ext.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: superimage_empty</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_SUPERIMAGE_EMPTY_TARGET)</code>，生成super_empty.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: bootimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_BOOTIMAGE_TARGET)</code>，生成boot.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vbmetaimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VBMETAIMAGE_TARGET)</code>，生成vbmeta.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: droidcore-unbundled</td>
      <td style="text-align: left">执行完整系统构建所需的目标子集，包含上面一系列的<code class="language-plaintext highlighter-rouge"> $(INSTALLED_***)</code>，<strong>其中重要的是定义了<code class="language-plaintext highlighter-rouge">$(INSTALLED_SYSTEMIMAGE_TARGET)</code>，生成system.img</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: droidcore</td>
      <td style="text-align: left">包含上面的droidcore-unbundled，构建一系列镜像</td>
    </tr>
  </tbody>
</table>

<p>在该文件的目标<code class="language-plaintext highlighter-rouge">droidcore-unbundled</code>中，定义了<code class="language-plaintext highlighter-rouge">$(INSTALLED_SYSTEMIMAGE_TARGET)</code>，该宏会生成system.img</p>

<hr />

<h1 id="2-打包system镜像">2. 打包system镜像</h1>

<p>而真正的打包实现是在<code class="language-plaintext highlighter-rouge">build/core/Makefile</code>中，make伪目标定义如下，所以我们可以通过<code class="language-plaintext highlighter-rouge">make systemimage</code>编译打包system镜像。</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 表示在打包system.img之前，要根据依赖规则重新生成所有要进行打包的文件
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">systemimage</span>
<span class="nl">systemimage</span><span class="o">:</span>

<span class="nl">systemimage</span><span class="o">:</span> <span class="nf">$(INSTALLED_SYSTEMIMAGE_TARGET)</span>

<span class="c"># 不需要根据依赖规则重新生成所有需要打包的文件而直接打包system.img文件
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">systemimage-nodeps snod</span>
<span class="nl">systemimage-nodeps snod</span><span class="o">:</span> <span class="nf">$(filter-out systemimage-nodeps snod</span><span class="p">,</span><span class="nf">$(MAKECMDGOALS)) </span>\
<span class="nf">	            | $(INTERNAL_USERIMAGES_DEPS)</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"make </span><span class="nv">$@</span><span class="s2">: ignoring dependencies"</span>
	<span class="nf">$(</span><span class="nb">call</span> build-systemimage-target,<span class="nv">$(INSTALLED_SYSTEMIMAGE_TARGET)</span><span class="nf">)</span>
	<span class="nv">$(hide)</span> <span class="nf">$(</span><span class="nb">call</span> assert-max-image-size,<span class="nv">$(INSTALLED_SYSTEMIMAGE_TARGET)</span>,<span class="nv">$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)</span><span class="nf">)</span>
</code></pre></div></div>

<p>从上面截取的代码看，systemimage依赖于<code class="language-plaintext highlighter-rouge">$(INSTALLED_SYSTEMIMAGE_TARGET)</code></p>

<hr />

<h2 id="21-make-systemimage流程">2.1. make systemimage流程</h2>

<p>在Android 12 AOSP源码中，执行<code class="language-plaintext highlighter-rouge">make systemimage</code>编译打包system.img，结合日志和代码，流程大致如下：</p>

<ol>
  <li>build.go-&gt;dumpvars.go - Build()函数执行<code class="language-plaintext highlighter-rouge">runMakeProductConfig</code>：主要配置编译参数、环境变量，同时打印到终端</li>
  <li>build.go-&gt;soong.go - Build()函数执行<code class="language-plaintext highlighter-rouge">runSoong</code>：对工具进行编译，编译出blueprint等编译工具, 把<code class="language-plaintext highlighter-rouge">*.bp</code>编译成<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code></li>
  <li>build.go-&gt;kati.go - Build()函数执行<code class="language-plaintext highlighter-rouge">runKatiBuild</code>：加载<code class="language-plaintext highlighter-rouge">build/make/core/main.mk</code>，搜集所有的Android.mk文件生成ninja文件：<code class="language-plaintext highlighter-rouge">out/build-aosp_arm.ninja</code>，执行Makefile的<code class="language-plaintext highlighter-rouge">systemimage</code>目标Target</li>
  <li>build.go - createCombinedBuildNinjaFile：将<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code> 、<code class="language-plaintext highlighter-rouge">out/build-aosp_arm.ninja</code>和<code class="language-plaintext highlighter-rouge">out/build-aosp_arm-package.ninja</code>， 合成为<code class="language-plaintext highlighter-rouge">out/out/combined-AOSP.ninja</code></li>
  <li>build.go-&gt;ninja.go - Build()函数执行<code class="language-plaintext highlighter-rouge">runNinjaForBuild</code>：使用ninja进行完整的编译过程，此处会执行<code class="language-plaintext highlighter-rouge">build/make/core/Makefile</code>中system镜像打包和拷贝的逻辑</li>
</ol>

<h3 id="211-make-systemimage编译日志">2.1.1. make systemimage编译日志</h3>

<pre><code class="language-log">13:24:19 PROPRIETARY_HAS_SOURCECODE false
MyDebugLog ---- Not Use Bazel!
// 执行RunProductConfig
MyDebugLog ****************9 RunProductConfig!
.....
============================================
// 执行runSoong
MyDebugLog ------------------- runSoong!
// --- 执行bootstrapBlueprint
MyDebugLog runsoong****************bootstrapBlueprint Start
MyDebugLog runsoong****************bootstrapBlueprint ---&gt; call bootstrap.RunBlueprint Start
MyDebugLog runsoong****************bootstrapBlueprint ---&gt; call bootstrap.RunBlueprint END
// --- 构建bpglob
MyDebugLog runsoong****************Build bpglob ....
MyDebugLog runsoong****************Command ....[  2% 1/48] regenerate globs shard 189 of 1024
........
Clang SA is not enabled
// 执行runsoong结束
MyDebugLog runsoong****************END !!! ....No need to regenerate ninja file
// 执行runKatiBuild
MyDebugLog ------------------- runKatiBuild!
MyDebugLog runKatiBuild****************START !!! ....
MyDebugLog runKatiBuild****************CALL runKati START ....build/make/core/Makefile was modified, regenerating...
//加载main.mk
build/make/core/main.mk:7: warning: *******************MyDebugLog*****************Call main.mk START......
[100% 49/49] initializing build system ...
......
[ 99% 424/426] including out/soong/late-&lt;product_name&gt;.mk ...
//代码位置：build/core/main.mk
//$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] finishing build rules ...)
[ 99% 425/426] finishing build rules ...
//执行Makefile的make systemimage
build/make/core/Makefile:703: warning: *******************MyDebugLog*****************make systemimage START......
build/make/core/Makefile:1286: warning: *******************MyDebugLog*****************PRODUCT_NOTICE_SPLIT is true START......
build/make/core/Makefile:2736: warning: *******************MyDebugLog*****************INTERNAL_SYSTEMIMAGE_FILES START......
build/make/core/Makefile:2770: warning: *******************MyDebugLog*****************if BUILDING_SYSTEM_IMAGE is true START......
build/make/core/Makefile:2773: warning: *******************MyDebugLog*****************if BUILDING_SYSTEM_IMAGE is true INNER START......
build/make/core/Makefile:2825: warning: *******************MyDebugLog*****************BUILT_SYSTEMIMAGE to  build-systemimage-target......
.....
//执行runKati结束
MyDebugLog runKatiBuild****************CALL runKati END ....
MyDebugLog runKatiBuild****************CALL distGzipFile END ....
MyDebugLog runKatiBuild****************END !!! ..No need to regenerate ninja file
//执行createCombinedBuildNinjaFile
MyDebugLogw074 ------------------- createCombinedBuildNinjaFile!
//执行distGzipFile
MyDebugLog ------------------- distGzipFile!
//执行runNinjaForBuild
MyDebugLog ------------------- runNinjaForBuild!
Starting ninja...
[ 97% 427/436] build out/target/product/&lt;product_name&gt;/obj/NOTICE_PRODUCT.txt
[ 98% 428/436] build out/target/product/&lt;product_name&gt;/obj/NOTICE_PRODUCT.xml.gz
[ 98% 429/436] build out/target/product/&lt;product_name&gt;/system/product/etc/NOTICE.xml.gz
[ 98% 430/436] build out/target/product/&lt;product_name&gt;/obj/NOTICE.txt
[ 98% 431/436] build out/target/product/&lt;product_name&gt;/obj/NOTICE.xml.gz
[ 99% 432/436] build out/target/product/&lt;product_name&gt;/system/etc/NOTICE.xml.gz
[ 99% 433/436] build out/target/product/&lt;product_name&gt;/system/etc/linker.config.pb
[ 99% 434/436] Installed file list: out/target/product/&lt;product_name&gt;/installed-files.txt
// --- 创建target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates目录，存放system.img
[ 99% 435/436] Target system fs image: out/target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates/system.img

// --- 把system.img文件从out/target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates拷贝到该目录out/target/product/&lt;product_name&gt;中
[100% 436/436] Install system fs image: out/target/product/&lt;product_name&gt;/system.img
MyDebugLog ***** Starting ninja....... END

#### build completed successfully (07:24 (mm:ss)) ####
</code></pre>

<hr />

<h3 id="212-makefile执行流程图">2.1.2. Makefile执行流程图</h3>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">make systemimage</code>的流程和其他镜像都定义在<code class="language-plaintext highlighter-rouge">build/core/main.mk</code>不同，而是在<code class="language-plaintext highlighter-rouge">build/core/Makefile</code>中定义的target目标，主要是通过<code class="language-plaintext highlighter-rouge">$(INSTALLED_SYSTEMIMAGE_TARGET)</code>实现镜像打包。</p>
</blockquote>

<p>Makefile中的流程如下：</p>

<p><img src="../../assets/post/2024/2024-03-09-android12_BuildSystem_3/system镜像打包.png" alt="" /></p>

<hr />

<h2 id="22-installed_systemimage_target实现">2.2. $(INSTALLED_SYSTEMIMAGE_TARGET)实现</h2>

<p><code class="language-plaintext highlighter-rouge">INSTALLED_SYSTEMIMAGE_TARGET</code>的处理实现代码：</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 最终生成的目标
</span><span class="nv">INSTALLED_SYSTEMIMAGE_TARGET</span> <span class="o">:=</span> <span class="nv">$(PRODUCT_OUT)</span>/system.img
<span class="nv">SYSTEMIMAGE_SOURCE_DIR</span> <span class="o">:=</span> <span class="nv">$(TARGET_OUT)</span>

<span class="c"># INSTALLED_SYSTEMIMAGE_TARGE在此处被命名为INSTALLED_StYSTEMIMAGE
# 目的是为向后兼容性创建一个别名，以防特定于设备的Makefile仍然引用旧名称
</span><span class="nv">INSTALLED_SYSTEMIMAGE</span> <span class="o">:=</span> <span class="nv">$(INSTALLED_SYSTEMIMAGE_TARGET)</span>

<span class="c"># INSTALLED_SYSTEMIMAGE_TARGET依赖于BUILT_SYSTEMIMAGE
</span><span class="nl">$(INSTALLED_SYSTEMIMAGE_TARGET)</span><span class="o">:</span> <span class="nf">$(BUILT_SYSTEMIMAGE)</span>
    <span class="c"># 此处$@的值是：out/target/product/&lt;product_name&gt;/system.img
</span>	<span class="nl">@echo "Install system fs image</span><span class="o">:</span> <span class="nf">$@"</span>
    <span class="c"># 然后再调用了函数copy-file-to-target进行文件拷贝
</span>	<span class="err">$(copy-file-to-target)</span>
	<span class="err">$(hide)</span> <span class="err">$(call</span> <span class="err">assert-max-image-size,$@,$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))</span>

<span class="nl">systemimage</span><span class="o">:</span> <span class="nf">$(INSTALLED_SYSTEMIMAGE_TARGET)</span>
</code></pre></div></div>

<p><strong>按步骤顺序说明INSTALLED_SYSTEMIMAGE_TARGET的工作：</strong></p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">BUILT_SYSTEMIMAGE</code>：生成<code class="language-plaintext highlighter-rouge">out/target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates/system.img</code>镜像</li>
  <li>拷贝函数<code class="language-plaintext highlighter-rouge">copy-file-to-target</code>：该函数定义在<code class="language-plaintext highlighter-rouge">build/make/core/definitions.mk</code>，主要用于是拷贝文件，并且在拷贝的过程中会保留文件的权限和覆盖已有的文件。<strong>它会首次创建<code class="language-plaintext highlighter-rouge">/out/target/product/&lt;product_name&gt;</code>目录，然后把文件拷贝到该目录中</strong></li>
</ol>

<p>其中<code class="language-plaintext highlighter-rouge">BOARD_SYSTEMIMAGE_PARTITION_SIZE</code>：即system分区的大小定义（一般在指定product的BoardConfig.mk）</p>

<hr />

<h3 id="221-step-1生成systemimage_intermediatessystemimg">2.2.1. Step 1：生成systemimage_intermediates/system.img</h3>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">BUILT_SYSTEMIMAGE</code>最终会把system.img编译到<code class="language-plaintext highlighter-rouge">out/target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates/system.img</code>中。</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">BUILT_SYSTEMIMAGE</code>依赖于<code class="language-plaintext highlighter-rouge">FULL_SYSTEMIMAGE_DEPS</code>、<code class="language-plaintext highlighter-rouge">INSTALLED_FILES_FILE</code>，然后通过调用函数<code class="language-plaintext highlighter-rouge">build-systemimage-target</code>来编译<code class="language-plaintext highlighter-rouge">systemimage</code></p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 目录路径out/target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates
</span><span class="nv">systemimage_intermediates</span> <span class="o">:=</span> <span class="se">\</span>
    <span class="nf">$(</span><span class="nb">call</span> intermediates-dir-for,PACKAGING,systemimage<span class="nf">)</span>
<span class="c"># BUILT_SYSTEMIMAGE最终结果是生成out/target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates/system.img镜像
</span><span class="nv">BUILT_SYSTEMIMAGE</span> <span class="o">:=</span> <span class="nv">$(systemimage_intermediates)</span>/system.img

<span class="c"># $(1): output file
</span><span class="err">define</span> <span class="err">build-systemimage-target</span>
  <span class="c"># 此处$(1)值：out/target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates/system.img
</span>  <span class="nl">@echo "Target system fs image</span><span class="o">:</span> <span class="nf">$(1)"</span>
  <span class="c"># 1. 创建target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates目录并删除这个目录下的system_image_info.txt文件
</span>  <span class="err">@mkdir</span> <span class="err">-p</span> <span class="err">$(dir</span> <span class="err">$(1))</span> <span class="err">$(systemimage_intermediates)</span> <span class="err">&amp;&amp;</span> <span class="err">rm</span> <span class="err">-rf</span> <span class="err">$(systemimage_intermediates)/system_image_info.txt</span>

  <span class="c"># 2. generate-userimage-prop-dictionary作用：生成system.img的信息，保存到system_image_info.txt中
</span>  <span class="err">$(call</span> <span class="err">generate-image-prop-dictionary,</span> <span class="err">$(systemimage_intermediates)/system_image_info.txt,system,</span> <span class="err">\</span>
      <span class="nv">skip_fsck</span><span class="o">=</span><span class="nb">true</span><span class="o">)</span>
  <span class="c"># 3. BUILD_IMAGE即out/host/linux-x86/bin/build_image可执行文件（build/make/tools/releasetools/build_image.py）
</span>  <span class="c"># 生成system.img镜像文件
</span>  <span class="nv">PATH</span><span class="o">=</span><span class="nv">$(INTERNAL_USERIMAGES_BINARY_PATHS)</span>:<span class="nv">$$</span>PATH <span class="se">\</span>
      <span class="nv">$(BUILD_IMAGE)</span> <span class="se">\</span>
          <span class="nv">$(TARGET_OUT)</span> <span class="nv">$(systemimage_intermediates)</span>/system_image_info.txt <span class="nv">$(1)</span> <span class="nv">$(TARGET_OUT)</span> <span class="se">\</span>
          <span class="o">||</span> <span class="o">(</span> <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="err">$</span><span class="nv">${DIST_DIR}</span><span class="p">;</span> <span class="se">\</span>
               <span class="nb">cp</span> <span class="nv">$(INSTALLED_FILES_FILE)</span> <span class="err">$</span><span class="nv">${DIST_DIR}</span>/installed-files-rescued.txt<span class="p">;</span> <span class="se">\</span>
               <span class="nb">exit </span>1 <span class="o">)</span>
<span class="err">endef</span>

<span class="c"># 如果AB系统，则赋值BOARD_AVB_SYSTEM_KEY_PATH（一般Android O及之后均执行）
</span><span class="k">ifeq</span> <span class="nv">($(BOARD_AVB_ENABLE),true)</span>
<span class="c"># BOARD_AVB_SYSTEM_KEY_PATH在Android 12默认key是external/avb/test/data/testkey_rsa2048.pem
</span><span class="nl">$(BUILT_SYSTEMIMAGE)</span><span class="o">:</span> <span class="nf">$(BOARD_AVB_SYSTEM_KEY_PATH)</span>
<span class="k">endif</span>

<span class="c"># INSTALLED_FILES_FILE即$(PRODUCT_OUT)/installed-files.txt文件
</span><span class="nl">$(BUILT_SYSTEMIMAGE)</span><span class="o">:</span> <span class="nf">$(FULL_SYSTEMIMAGE_DEPS) $(INSTALLED_FILES_FILE)</span>
	<span class="nf">$(</span><span class="nb">call</span> build-systemimage-target,<span class="nv">$@</span><span class="nf">)</span>
</code></pre></div></div>

<hr />

<h4 id="2211-full_systemimage_deps说明">2.2.1.1. $(FULL_SYSTEMIMAGE_DEPS)说明</h4>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># step 1：INTERNAL_SYSTEMIMAGE_FILES定义赋值，赋值的包含：
#   (1)TARGET_OUT：out/target/product/&lt;product_name&gt;/system/（build/make/core/envsetup.mk），其实就是system/目录
#   (2)ALL_GENERATED_SOURCES：某些工具生成的所有文件的完整路径，要拷贝到目标设备上去的由工具自动生成的源代码文件（build/make/core/binary.mk）
#   (3)ALL_DEFAULT_INSTALLED_MODULES：应添加到已安装目标的“make-droid”集合中的目标的完整路径（要安装要目标设备上的所有的模块文件）
</span><span class="nv">INTERNAL_SYSTEMIMAGE_FILES</span> <span class="o">:=</span> <span class="nf">$(</span><span class="nb">sort</span> <span class="nf">$(</span><span class="nb">filter</span> <span class="nv">$(TARGET_OUT)</span>/%, <span class="se">\</span>
    <span class="nv">$(ALL_GENERATED_SOURCES)</span> <span class="se">\ </span>
    <span class="nv">$(ALL_DEFAULT_INSTALLED_MODULES)</span><span class="nf">))</span>

<span class="c"># (4)如果BOARD_USES_VENDORIMAGE为true，则/system/vendor创建软链接/vendor，作为vendor.img
</span><span class="k">ifdef</span> <span class="nv">BOARD_USES_VENDORIMAGE</span>
  <span class="nv">INTERNAL_SYSTEMIMAGE_FILES</span> <span class="o">+=</span> <span class="nf">$(</span><span class="nb">call</span> create-partition-compat-symlink,<span class="nv">$(TARGET_OUT)</span>/vendor,/vendor,vendor.img<span class="nf">)</span>
<span class="k">endif</span>
<span class="c"># (5)如果BOARD_USES_PRODUCTIMAGE为true，则/system/product创建软链接/product，作为product.img
</span><span class="k">ifdef</span> <span class="nv">BOARD_USES_PRODUCTIMAGE</span>
  <span class="nv">INTERNAL_SYSTEMIMAGE_FILES</span> <span class="o">+=</span> <span class="nf">$(</span><span class="nb">call</span> create-partition-compat-symlink,<span class="nv">$(TARGET_OUT)</span>/product,/product,product.img<span class="nf">)</span>
<span class="k">endif</span>
<span class="c"># (6)如果BOARD_USES_SYSTEM_EXTIMAGE为true，则/system/system_ext创建软链接/system_ext，作为system_ext.img
</span><span class="k">ifdef</span> <span class="nv">BOARD_USES_SYSTEM_EXTIMAGE</span>
  <span class="nv">INTERNAL_SYSTEMIMAGE_FILES</span> <span class="o">+=</span> <span class="nf">$(</span><span class="nb">call</span> create-partition-compat-symlink,<span class="nv">$(TARGET_OUT)</span>/system_ext,/system_ext,system_ext.img<span class="nf">)</span>
<span class="k">endif</span>

<span class="c"># step 2：赋值给FULL_SYSTEMIMAGE_DEPS
#   INTERNAL_SYSTEMIMAGE_FILES：就是上面得到的最终值
#   INTERNAL_USERIMAGES_DEPS：制作system.img镜像所依赖的工具
</span><span class="nv">FULL_SYSTEMIMAGE_DEPS</span> <span class="o">:=</span> <span class="nv">$(INTERNAL_SYSTEMIMAGE_FILES)</span> <span class="nv">$(INTERNAL_USERIMAGES_DEPS)</span>

<span class="c"># 系统映像中的ASAN库-添加依赖项
</span><span class="nv">ASAN_IN_SYSTEM_INSTALLED</span> <span class="o">:=</span> <span class="nv">$(TARGET_OUT)</span>/asan.tar.bz2
<span class="k">ifneq</span> <span class="nv">(,$(filter address, $(SANITIZE_TARGET)))</span>
  <span class="k">ifeq</span> <span class="nv">(true,$(SANITIZE_TARGET_SYSTEM))</span>
    <span class="nv">FULL_SYSTEMIMAGE_DEPS</span> <span class="o">+=</span> <span class="nv">$(ASAN_IN_SYSTEM_INSTALLED)</span>
  <span class="k">endif</span>
<span class="k">endif</span>

<span class="c"># $(INTERNAL_ROOT_FILES)：目录out/target/product/&lt;product_name&gt;/root/
# $(INSTALLED_FILES_FILE_ROOT)：文件out/target/product/&lt;product_name&gt;/installed-files-root.txt
</span><span class="nv">FULL_SYSTEMIMAGE_DEPS</span> <span class="o">+=</span> <span class="nv">$(INTERNAL_ROOT_FILES)</span> <span class="nv">$(INSTALLED_FILES_FILE_ROOT)</span>
</code></pre></div></div>

<hr />

<h4 id="2212-installed_files_file说明">2.2.1.2. $(INSTALLED_FILES_FILE)说明</h4>

<p><code class="language-plaintext highlighter-rouge">INSTALLED_FILES_FILE</code>依赖的是文件<code class="language-plaintext highlighter-rouge">$(PRODUCT_OUT)/installed-files.txt</code>，这是已安装的文件列表，这些文件要打包到system.img中，他也依赖于<code class="language-plaintext highlighter-rouge">FULL_SYSTEMIMAGE_DEPS</code></p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># installed file list已安装列表
# 取决于$（BUILT_SYSTEMIMAGE）所依赖的任何内容。
# 在依赖关系图中，我们将installed-files.txt放在映像本身之前，这样即使由于系统映像过大而导致构建失败，我们也可以获得大小统计。
</span><span class="nv">INSTALLED_FILES_FILE</span> <span class="o">:=</span> <span class="nv">$(PRODUCT_OUT)</span>/installed-files.txt
<span class="nv">INSTALLED_FILES_JSON</span> <span class="o">:=</span> <span class="nv">$(INSTALLED_FILES_FILE:.txt=.json)</span>
<span class="nl">$(INSTALLED_FILES_FILE)</span><span class="o">:</span> <span class="nf">.KATI_IMPLICIT_OUTPUTS := $(INSTALLED_FILES_JSON)</span>
<span class="nl">$(INSTALLED_FILES_FILE)</span><span class="o">:</span> <span class="nf">$(FULL_SYSTEMIMAGE_DEPS) $(FILESLIST) $(FILESLIST_UTIL)</span>
	<span class="p">@</span><span class="nb">echo </span>Installed file list: <span class="nv">$@</span>
	<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nf">$(</span><span class="nb">dir</span> <span class="nv">$@</span><span class="nf">)</span>
	<span class="nb">rm</span> <span class="nt">-f</span> <span class="nv">$@</span>
	<span class="nv">$(FILESLIST)</span> <span class="nv">$(TARGET_OUT)</span> <span class="o">&gt;</span> <span class="err">$</span><span class="o">(</span>@:.txt<span class="o">=</span>.json<span class="o">)</span>
	<span class="nv">$(FILESLIST_UTIL)</span> <span class="nt">-c</span> <span class="err">$</span><span class="o">(</span>@:.txt<span class="o">=</span>.json<span class="o">)</span> <span class="o">&gt;</span> <span class="nv">$@</span>

<span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">installed-file-list</span>
<span class="nl">installed-file-list</span><span class="o">:</span> <span class="nf">$(INSTALLED_FILES_FILE)</span>

<span class="err">$(call</span> <span class="err">dist-for-goals,</span> <span class="err">sdk</span> <span class="err">win_sdk</span> <span class="err">sdk_addon,</span> <span class="err">$(INSTALLED_FILES_FILE))</span>
</code></pre></div></div>

<hr />

<h3 id="222-step-2copy-file-to-target拷贝">2.2.2. Step 2：copy-file-to-target拷贝</h3>

<p>拷贝函数<code class="language-plaintext highlighter-rouge">copy-file-to-target</code>：该函数定义在<code class="language-plaintext highlighter-rouge">build/make/core/definitions.mk</code>，主要用于是拷贝文件，并且在拷贝的过程中会保留文件的权限和覆盖已有的文件。<strong>它会创建<code class="language-plaintext highlighter-rouge">/out/target/product/&lt;product_name&gt;</code>目录，然后把system.img文件从<code class="language-plaintext highlighter-rouge">out/target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates</code>拷贝到该目录<code class="language-plaintext highlighter-rouge">out/target/product/&lt;product_name&gt;</code>中</strong></p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 将单个文件从一个位置复制到另一个位置，
# 保留权限并覆盖任何现有的文件
</span><span class="err">define</span> <span class="err">copy-file-to-target</span>
<span class="err">@mkdir</span> <span class="err">-p</span> <span class="err">$(dir</span> <span class="err">$@)</span>
<span class="err">$(hide)</span> <span class="err">rm</span> <span class="err">-f</span> <span class="err">$@</span>
<span class="err">$(hide)</span> <span class="err">cp</span> <span class="s2">"$&lt;"</span> <span class="s2">"$@"</span>
<span class="err">endef</span>
</code></pre></div></div>

<p>通过对比这两个目录的system.img文件的md5值，可以发现是一样的。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>android/out/target/product/&lt;product_name&gt;/obj/PACKAGING/systemimage_intermediates<span class="nv">$ </span><span class="nb">md5sum </span>system.img 
c87d3ca1aa1462b2d97102dedf6fca28  system.img

android/out/target/product/&lt;product_name&gt;<span class="nv">$ </span><span class="nb">md5sum </span>system.img 
c87d3ca1aa1462b2d97102dedf6fca28  system.img
</code></pre></div></div>

<hr />

<h1 id="3-参考">3. 参考</h1>

<ul>
  <li><a href="https://mp.weixin.qq.com/s?__biz=MjM5NDk5ODQwNA==&amp;mid=2652469367&amp;idx=1&amp;sn=820ce34bf318976777fb7e75ccb92013&amp;chksm=bd12e99c8a65608ad003c9f35b36152966e037a4e5a441fe12ced5c93cee24a8be2125eb06d2&amp;scene=178&amp;cur_album_id=1552818877418487808#rd">Image打包流程-Android10.0编译系统（四）</a></li>
  <li><a href="https://blog.csdn.net/u011913612/article/details/52503318">Android编译系统分析五：system.img的生成过程</a></li>
</ul>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="android" /><category term="build" /><summary type="html"><![CDATA[在Android 12 AOSP源码的build/core/main.mk中定义了很多伪目标，我们可以直接通过make 目标名称进行编译，镜像的生成定义也在该文件中。本篇主要以system镜像为例，进行流程梳理分析。]]></summary></entry><entry><title type="html">Android 12编译系统-2 编译三部曲介绍（source&amp;amp;lunch&amp;amp;make）</title><link href="https://alonealive.github.io/android/android12_BuildSystem_2/" rel="alternate" type="text/html" title="Android 12编译系统-2 编译三部曲介绍（source&amp;amp;lunch&amp;amp;make）" /><published>2024-02-23T10:50:02+00:00</published><updated>2024-02-23T10:50:02+00:00</updated><id>https://alonealive.github.io/android/android12_BuildSystem_2</id><content type="html" xml:base="https://alonealive.github.io/android/android12_BuildSystem_2/"><![CDATA[<blockquote>
  <p>本篇我们详细的说明Android编译命令的内部流程，讲述<code class="language-plaintext highlighter-rouge">source build/envsetup.sh;lunch;make</code>这三个主要编译命令的处理逻辑。</p>
</blockquote>

<h1 id="1-source-buildenvsetupsh">1. source build/envsetup.sh</h1>

<p>从build代码目录中使用<code class="language-plaintext highlighter-rouge">ll</code>命令能看到，<code class="language-plaintext highlighter-rouge">build/envsetup.sh</code>文件是link到<code class="language-plaintext highlighter-rouge">build/make/envsetup.sh</code></p>

<p>阅读该shell脚本，主要做了以下几件事情：</p>

<ol>
  <li>确认当前shell环境</li>
  <li>从<code class="language-plaintext highlighter-rouge">device/vendor/product</code>目录遍历搜索<code class="language-plaintext highlighter-rouge">vendorsetup.sh</code>脚本，并source</li>
  <li>定义一些函数到编译环境中，比如<code class="language-plaintext highlighter-rouge">lunch/m/mm/mma/cgrep</code>等，通过hmm查看帮助</li>
  <li>将adb、fastboot、asuite.sh添加到当前环境</li>
</ol>

<hr />

<h2 id="11-envsetupsh内容思维导图">1.1. envsetup.sh内容思维导图</h2>

<p><strong>该脚本只有序执行了其中三个函数，其他定义了很多函数用于编译及后续使用。其中就包括lunch、hmm、mma、mm等</strong></p>

<p><img src="../../assets/post/2024/2024-02-23-android12_BuildSystem_2/source_envsetup.sh.png" alt="" /></p>

<hr />

<h2 id="12-envsetupsh脚本执行流程分析">1.2. envsetup.sh脚本执行流程分析</h2>

<p><strong>该脚本先后执行了三个函数：</strong></p>
<ol>
  <li>validate_current_shell：确定当前shell环境</li>
  <li>source_vendorsetup：执行我们能找到的任何vendorsetup.sh文件的内容，加载已批准的vendorsetup.sh文件</li>
  <li>addcompletions：将adb、fastboot、asuite.sh添加到当前环境</li>
</ol>

<hr />

<h3 id="121-step-1-validate_current_shell">1.2.1. Step 1: validate_current_shell</h3>

<p>该函数主要是确认当前的shell环境</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>validate_current_shell<span class="o">()</span> <span class="o">{</span>
    <span class="c"># 执行"ps -o command -p $$"命令查看当前shell的名称，赋值给current_sh</span>
    <span class="nb">local </span><span class="nv">current_sh</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>ps <span class="nt">-o</span> <span class="nb">command</span> <span class="nt">-p</span> <span class="nv">$$</span><span class="si">)</span><span class="s2">"</span>
    <span class="k">case</span> <span class="s2">"</span><span class="nv">$current_sh</span><span class="s2">"</span> <span class="k">in</span>
        <span class="c"># 此处我本地的shell环境是bash，执行此分支</span>
        <span class="k">*</span>bash<span class="k">*</span><span class="p">)</span>
            <span class="c"># 执行type -t bash，此处返回file</span>
            <span class="c"># 打印alias,keyword,function,built-in,file这5种类型</span>
            <span class="k">function </span>check_type<span class="o">()</span> <span class="o">{</span> <span class="nb">type</span> <span class="nt">-t</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span><span class="p">;</span> <span class="o">}</span>
            <span class="p">;;</span>
        <span class="k">*</span>zsh<span class="k">*</span><span class="p">)</span>
            <span class="k">function </span>check_type<span class="o">()</span> <span class="o">{</span> <span class="nb">type</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span><span class="p">;</span> <span class="o">}</span>
            enable_zsh_completion <span class="p">;;</span>
        <span class="k">*</span><span class="p">)</span>
            <span class="nb">echo</span> <span class="nt">-e</span> <span class="s2">"WARNING: Only bash and zsh are supported.</span><span class="se">\n</span><span class="s2">Use of other shell would lead to erroneous results."</span>
            <span class="p">;;</span>
    <span class="k">esac</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>PS:</strong></p>

<p>例如在本地测试执行：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 获取shell本身的pid信息</span>
<span class="c"># $$：shell本身的pid</span>
<span class="nv">$ </span>ps <span class="nt">-p</span> <span class="nv">$$</span>
  PID TTY          TIME CMD
 5189 pts/6    00:00:04 bash

<span class="c"># -o command: ps命令的-o参数允许用户指定显示信息的格式，command指示只显示CMD对应的信息，这里就是"bash"</span>
<span class="nv">$ </span>ps <span class="nt">-o</span> <span class="nb">command</span> <span class="nt">-p</span> <span class="nv">$$</span>
COMMAND
bash

<span class="nv">$ </span><span class="nb">type </span>bash
bash is /bin/bash

<span class="nv">$ </span><span class="nb">type</span> <span class="nt">-t</span> bash
file
</code></pre></div></div>

<hr />

<h3 id="122-step-2-source_vendorsetup">1.2.2. Step 2: source_vendorsetup</h3>

<p>从<code class="language-plaintext highlighter-rouge">device\vendor\product</code>目录（搜索最大深度为4）遍历查询<code class="language-plaintext highlighter-rouge">vendorsetup.sh</code>文件，并且使用<code class="language-plaintext highlighter-rouge">. *.sh</code>命令进行source加载</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>source_vendorsetup<span class="o">()</span> <span class="o">{</span>
    <span class="nb">unset </span>VENDOR_PYTHONPATH
    <span class="c"># 获取项目根目录</span>
    <span class="nb">local </span><span class="nv">T</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>gettop<span class="si">)</span><span class="s2">"</span>
    <span class="nv">allowed</span><span class="o">=</span>
    <span class="c"># 执行source命令后，此处循环实际未搜索到</span>
    <span class="c"># cd到根目录</span>
    <span class="c"># 查询device/vendor/product最大深度4，名字包含allowed-vendorsetup_sh-files的文件（即vendorsetup.sh）</span>
    <span class="c"># find -L 找到符号链接的时候，所有的属性来自文件本身，而不是符号链接</span>
    <span class="k">for </span>f <span class="k">in</span> <span class="si">$(</span><span class="nb">cd</span> <span class="s2">"</span><span class="nv">$T</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> find <span class="nt">-L</span> device vendor product <span class="nt">-maxdepth</span> 4 <span class="nt">-name</span> <span class="s1">'allowed-vendorsetup_sh-files'</span> 2&gt;/dev/null | <span class="nb">sort</span><span class="si">)</span><span class="p">;</span> <span class="k">do
        if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$allowed</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
            </span><span class="nb">echo</span> <span class="s2">"More than one 'allowed_vendorsetup_sh-files' file found, not including any vendorsetup.sh files:"</span>
            <span class="nb">echo</span> <span class="s2">"  </span><span class="nv">$allowed</span><span class="s2">"</span>
            <span class="nb">echo</span> <span class="s2">"  </span><span class="nv">$f</span><span class="s2">"</span>
            <span class="k">return
        fi
        </span><span class="nv">allowed</span><span class="o">=</span><span class="s2">"</span><span class="nv">$T</span><span class="s2">/</span><span class="nv">$f</span><span class="s2">"</span>
    <span class="k">done</span>
    <span class="c"># 此处是实际查询到的，遍历查询三个目录的vendorsetup.sh文件</span>
    <span class="nv">allowed_files</span><span class="o">=</span>
    <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$allowed</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">allowed_files</span><span class="o">=</span><span class="si">$(</span><span class="nb">cat</span> <span class="s2">"</span><span class="nv">$allowed</span><span class="s2">"</span><span class="si">)</span>
    <span class="k">for </span><span class="nb">dir </span><span class="k">in </span>device vendor product<span class="p">;</span> <span class="k">do
        for </span>f <span class="k">in</span> <span class="si">$(</span><span class="nb">cd</span> <span class="s2">"</span><span class="nv">$T</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="nb">test</span> <span class="nt">-d</span> <span class="nv">$dir</span> <span class="o">&amp;&amp;</span> <span class="se">\</span>
            find <span class="nt">-L</span> <span class="nv">$dir</span> <span class="nt">-maxdepth</span> 4 <span class="nt">-name</span> <span class="s1">'vendorsetup.sh'</span> 2&gt;/dev/null | <span class="nb">sort</span><span class="si">)</span><span class="p">;</span> <span class="k">do

            if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$allowed</span><span class="s2">"</span> <span class="o">||</span> <span class="s2">"</span><span class="nv">$allowed_files</span><span class="s2">"</span> <span class="o">=</span>~ <span class="nv">$f</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
                <span class="c"># 此处先打印日志，然后执行" . *.sh"就是相当于source该脚本</span>
                <span class="nb">echo</span> <span class="s2">"including </span><span class="nv">$f</span><span class="s2">"</span><span class="p">;</span> <span class="nb">.</span> <span class="s2">"</span><span class="nv">$T</span><span class="s2">/</span><span class="nv">$f</span><span class="s2">"</span>
            <span class="k">else
                </span><span class="nb">echo</span> <span class="s2">"ignoring </span><span class="nv">$f</span><span class="s2">, not in </span><span class="nv">$allowed</span><span class="s2">"</span>
            <span class="k">fi
        done
    done</span>
<span class="o">}</span>
</code></pre></div></div>

<p>归根到底，其实就是执行了以下三个命令：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>find <span class="nt">-L</span> device <span class="nt">-maxdepth</span> 4 <span class="nt">-name</span> <span class="s1">'vendorsetup.sh'</span> 2&gt;/dev/null | <span class="nb">sort</span><span class="p">;</span>
find <span class="nt">-L</span> vendor <span class="nt">-maxdepth</span> 4 <span class="nt">-name</span> <span class="s1">'vendorsetup.sh'</span> 2&gt;/dev/null | <span class="nb">sort</span><span class="p">;</span>
find <span class="nt">-L</span> product <span class="nt">-maxdepth</span> 4 <span class="nt">-name</span> <span class="s1">'vendorsetup.sh'</span> 2&gt;/dev/null | <span class="nb">sort</span>
</code></pre></div></div>

<hr />

<h3 id="123-step-3-addcompletions">1.2.3. Step 3: addcompletions</h3>

<p>主要将adb、fastboot、asuite这几个bash添加到编译环境中</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>addcompletions<span class="o">()</span>
<span class="o">{</span>
    <span class="nb">local </span><span class="nv">f</span><span class="o">=</span>
    <span class="c"># 不要试图在既不是bash也不是zsh的地方运行</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$BASH_VERSION</span><span class="s2">"</span> <span class="nt">-a</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$ZSH_VERSION</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        return
    fi</span>

    <span class="c"># Keep us from trying to run in bash that's too old.</span>
    <span class="c"># bash版本不要太老</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$BASH_VERSION</span><span class="s2">"</span> <span class="nt">-a</span> <span class="k">${</span><span class="nv">BASH_VERSINFO</span><span class="p">[0]</span><span class="k">}</span> <span class="nt">-lt</span> 3 <span class="o">]</span><span class="p">;</span> <span class="k">then
        return
    fi</span>
    <span class="c"># adb、fastboot、asuite</span>
    <span class="nb">local </span><span class="nv">completion_files</span><span class="o">=(</span>
      system/core/adb/adb.bash
      system/core/fastboot/fastboot.bash
      tools/asuite/asuite.sh
    <span class="o">)</span>
    <span class="c"># Completion can be disabled selectively to allow users to use non-standard completion.</span>
    <span class="c"># e.g.</span>
    <span class="c"># ENVSETUP_NO_COMPLETION=adb # -&gt; disable adb completion</span>
    <span class="c"># ENVSETUP_NO_COMPLETION=adb:bit # -&gt; disable adb and bit completion</span>
    <span class="k">for </span>f <span class="k">in</span> <span class="k">${</span><span class="nv">completion_files</span><span class="p">[*]</span><span class="k">}</span><span class="p">;</span> <span class="k">do</span>
        <span class="c"># 遍历*.bash文件列表，并将这些*.bash文件包含进来</span>
        <span class="c"># 检查是否在ENVSETUP_NO_COMPLETION中</span>
        <span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$f</span><span class="s2">"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> should_add_completion <span class="s2">"</span><span class="nv">$f</span><span class="s2">"</span><span class="p">;</span> <span class="k">then</span>
            <span class="c"># 对*.bash文件执行'.'操作</span>
            <span class="nb">.</span> <span class="nv">$f</span>
        <span class="k">fi
    done

    if </span>should_add_completion bit <span class="p">;</span> <span class="k">then
        </span><span class="nb">complete</span> <span class="nt">-C</span> <span class="s2">"bit --tab"</span> bit
    <span class="k">fi
    if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$ZSH_VERSION</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
        <span class="c"># Doesn't work in zsh.</span>
        <span class="nb">complete</span> <span class="nt">-o</span> nospace <span class="nt">-F</span> _croot croot
    <span class="k">fi</span>
    <span class="c"># complete自动补全命令</span>
    <span class="c"># 表示当执行lunch命令时，_lunch会计算自动补全</span>
    <span class="nb">complete</span> <span class="nt">-F</span> _lunch lunch

    <span class="nb">complete</span> <span class="nt">-F</span> _complete_android_module_names pathmod
    <span class="nb">complete</span> <span class="nt">-F</span> _complete_android_module_names gomod
    <span class="nb">complete</span> <span class="nt">-F</span> _complete_android_module_names outmod
    <span class="nb">complete</span> <span class="nt">-F</span> _complete_android_module_names installmod
    <span class="nb">complete</span> <span class="nt">-F</span> _complete_android_module_names m
<span class="o">}</span>
</code></pre></div></div>

<hr />

<h2 id="13-hmm查看可支持的编译命令">1.3. hmm查看可支持的编译命令</h2>

<p>在envsetup.sh的hmm函数中可以看到一些编译命令会被加载到环境中，这些命令都是在该shell脚本中定义的函数方法。</p>

<p>各个命令的实现方法都不一样，有的简单到一条正则命令，有的则是牵扯到编译系统。</p>

<p>我们比较关注的是lunch、m、mm、mma等这些和编译相关的，具体实现细节后续会梳理到，此处只需要知道函数入口在这个脚本中就可以。</p>

<p><strong>我们可以使用<code class="language-plaintext highlighter-rouge">hmm</code>查看这些命令说明：</strong></p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">命令</th>
      <th style="text-align: left">说明</th>
      <th> </th>
      <th> </th>
      <th> </th>
      <th> </th>
      <th> </th>
      <th> </th>
      <th> </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">lunch</td>
      <td style="text-align: left">lunch <product_name>-<build_variant> 选择<product_name>作为要构建的产品，<build_variant>作为要构建的变体，并将这些选择存储在环境中，以便后续调用“m”等读取</build_variant></product_name></build_variant></product_name></td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">tapas</td>
      <td style="text-align: left">交互方式：tapas [<App1> <App2> ...] [arm</App2></App1></td>
      <td>x86</td>
      <td>mips</td>
      <td>arm64</td>
      <td>x86_64</td>
      <td>mips64] [eng</td>
      <td>userdebug</td>
      <td>user]</td>
    </tr>
    <tr>
      <td style="text-align: left">banchan</td>
      <td style="text-align: left">banchan <module1> [<module2> ...] [arm</module2></module1></td>
      <td>x86</td>
      <td>arm64</td>
      <td>x86_64] [eng</td>
      <td>userdebug</td>
      <td>user]</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">croot</td>
      <td style="text-align: left">将目录更改到树的顶部或其子目录</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">m</td>
      <td style="text-align: left">编译整个源码，可以不用切换到根目录</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">mm</td>
      <td style="text-align: left">编译当前目录下的源码，不包含他们的依赖模块</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">mmm</td>
      <td style="text-align: left">编译指定目录下的所有模块，不包含他们的依赖模块。例如：mmm dir/:target1,target2.</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">mma</td>
      <td style="text-align: left">编译当前目录下的源码，包含他们的依赖模块</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">mmma</td>
      <td style="text-align: left">编译指定目录下的所模块，包含他们的依赖模块</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">provision</td>
      <td style="text-align: left">具有所有必需分区的闪存设备。选项将传递给fastboot</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">cgrep</td>
      <td style="text-align: left">对系统本地所有的C/C++ 文件执行grep命令</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">ggrep</td>
      <td style="text-align: left">对系统本地所有的Gradle文件执行grep命令</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">gogrep</td>
      <td style="text-align: left">对系统本地所有的go文件执行grep命令（Android 11新增）</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">jgrep</td>
      <td style="text-align: left">对系统本地所有的Java文件执行grep命令</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">ktgrep</td>
      <td style="text-align: left">对系统本地所有的Kotlin文件执行grep命令（Android 12新增）</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">resgrep</td>
      <td style="text-align: left">对系统本地所有的res目录下的xml文件执行grep命令</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">mangrep</td>
      <td style="text-align: left">对系统本地所有的AndroidManifest.xml文件执行grep命令</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">mgrep</td>
      <td style="text-align: left">对系统本地所有的Makefiles文件执行grep命令</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">owngrep</td>
      <td style="text-align: left">对系统本地所有的OWNERS文件执行grep命令（Android 11新增）</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">rsgrep</td>
      <td style="text-align: left">对系统本地所有的Rust文件执行grep命令（Android 12新增）</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">sepgrep</td>
      <td style="text-align: left">对系统本地所有的sepolicy文件执行grep命令</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">sgrep</td>
      <td style="text-align: left">对系统本地所有的source文件执行grep命令</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">godir</td>
      <td style="text-align: left">根据godir后的参数文件名在整个目录下查找，并且切换目录</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">allmod</td>
      <td style="text-align: left">列出所有模块</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">gomod</td>
      <td style="text-align: left">转到包含模块的目录</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">pathmod</td>
      <td style="text-align: left">获取包含模块的目录</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">outmod</td>
      <td style="text-align: left">获取具有特定扩展名的模块的已安装输出的位置（Android 12新增）</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">dirmods</td>
      <td style="text-align: left">获取定义模块的目录（Android 12新增）</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">installmod</td>
      <td style="text-align: left">adb安装一个编译APK的模块（Android 12新增）</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">refreshmod</td>
      <td style="text-align: left">刷新allmod/gomod的模块列表</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td style="text-align: left">syswrite</td>
      <td style="text-align: left">将分区（例如system.img）重新remount为可写分区，必要时重新启动（Android 12新增）</td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
      <td> </td>
    </tr>
  </tbody>
</table>

<hr />

<h1 id="2-lunch">2. lunch</h1>

<blockquote>
  <p>在执行<code class="language-plaintext highlighter-rouge">source build/envsetup.sh</code>后，我们需要选择一个编译目标。<br />
<code class="language-plaintext highlighter-rouge">lunch</code>的主要作用是根据用户输入或者选择的产品目标来设置对应的环境变量。</p>
</blockquote>

<h2 id="21-lunch执行日志分析">2.1. lunch执行日志分析</h2>

<p>首先我们执行下<code class="language-plaintext highlighter-rouge">lunch</code>命令看下终端打印的日志，会列出所有的产品目标，然后我们选择其中一个。或者我们也可以执行<code class="language-plaintext highlighter-rouge">lunch + 产品目标名称/数字序列</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 执行lunch命令选择其中一个，比如7</span>
<span class="c"># 或者执行 lunch aosp_car_arm64-userdebug</span>
<span class="c"># 或者执行 lunch 7</span>
<span class="nv">$ </span>lunch

You<span class="s1">'re building on Linux

Lunch menu... pick a combo:
     1. aosp_arm-eng
     2. aosp_arm64-eng
     3. aosp_blueline_car-userdebug
     4. aosp_bonito_car-userdebug
     5. aosp_bramble_car-userdebug
     6. aosp_car_arm-userdebug
     7. aosp_car_arm64-userdebug
     8. aosp_car_x86-userdebug
     9. aosp_car_x86_64-userdebug
    ......
</span></code></pre></div></div>

<p><strong>选择产品目标后，控制台输出结果，生成一些环境变量，并且会生成out目录，并生成一些中间文件：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">============================================</span>
<span class="c"># 表示平台版本的名称</span>
<span class="nv">PLATFORM_VERSION_CODENAME</span><span class="o">=</span>REL
<span class="c"># Android平台版本号，当前是Android 12</span>
<span class="nv">PLATFORM_VERSION</span><span class="o">=</span>12
<span class="c"># 选择编译的产品目标</span>
<span class="nv">TARGET_PRODUCT</span><span class="o">=</span>aosp_car_arm64
<span class="c"># 选择编译的产品类型，是userdebug</span>
<span class="nv">TARGET_BUILD_VARIANT</span><span class="o">=</span>userdebug
<span class="c"># 编译的类型，debug和release</span>
<span class="nv">TARGET_BUILD_TYPE</span><span class="o">=</span>release
<span class="c"># 表示编译目标的CPU架构</span>
<span class="nv">TARGET_ARCH</span><span class="o">=</span>arm64
<span class="c"># 表示编译目标的CPU架构版本</span>
<span class="nv">TARGET_ARCH_VARIANT</span><span class="o">=</span>armv8-a
<span class="c"># 表示编译目标的CPU代号</span>
<span class="nv">TARGET_CPU_VARIANT</span><span class="o">=</span>generic
<span class="nv">TARGET_2ND_ARCH_VARIANT</span><span class="o">=</span>armv8-a
<span class="nv">TARGET_2ND_CPU_VARIANT</span><span class="o">=</span>generic
<span class="c"># 表示编译平台的架构</span>
<span class="nv">HOST_ARCH</span><span class="o">=</span>x86_64
<span class="nv">HOST_2ND_ARCH</span><span class="o">=</span>x86
<span class="c"># 表示编译平台的操作系统</span>
<span class="nv">HOST_OS</span><span class="o">=</span>linux
<span class="c"># 编译平台的操作系统系统的额外信息</span>
<span class="nv">HOST_OS_EXTRA</span><span class="o">=</span>Linux-5.4.0-146-generic-x86_64-Ubuntu-18.04.6-LTS
<span class="nv">HOST_CROSS_OS</span><span class="o">=</span>windows
<span class="nv">HOST_CROSS_ARCH</span><span class="o">=</span>x86
<span class="nv">HOST_CROSS_2ND_ARCH</span><span class="o">=</span>x86_64
<span class="nv">HOST_BUILD_TYPE</span><span class="o">=</span>release
<span class="c"># BUILD_ID会出现在版本信息中，可以利用</span>
<span class="nv">BUILD_ID</span><span class="o">=</span>SQ3A.220705.003.A1
<span class="c"># 编译结果输出的路径</span>
<span class="nv">OUT_DIR</span><span class="o">=</span>out
<span class="o">============================================</span>
</code></pre></div></div>

<hr />

<h2 id="22-lunch函数执行流程分析">2.2. lunch函数执行流程分析</h2>

<p>lunch函数在<code class="language-plaintext highlighter-rouge">envsetup.sh</code>脚本中，我们从该函数进行内部实现的流程分析。</p>

<p><strong>以Android 12 AOSP源码为例：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>lunch<span class="o">()</span>
<span class="o">{</span>
    <span class="nb">local </span>answer

    <span class="k">if</span> <span class="o">[[</span> <span class="nv">$# </span><span class="nt">-gt</span> 1 <span class="o">]]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"usage: lunch [target]"</span> <span class="o">&gt;</span>&amp;2
        <span class="k">return </span>1
    <span class="k">fi</span>
    <span class="c"># Step 1 ：</span>
    <span class="c"># 获取lunch操作的参数，如果参数不为空，参数则为指定要编译的设备型号和编译类型</span>
    <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">answer</span><span class="o">=</span><span class="nv">$1</span>
    <span class="k">else</span>
    <span class="c"># 如果参数为空，会调用print_lunch_menu来显示Lunch菜单项，读取用户的输入，存入answer</span>
    <span class="c"># 该函数就是获取所有的AndroidProducts.mk中定义的COMMON_LUNCH_CHOICES宏的值</span>
    <span class="c"># 比如device/generic/car/AndroidProducts.mk中：</span>
    <span class="c">#       COMMON_LUNCH_CHOICES := \</span>
    <span class="c">#           aosp_car_arm-userdebug \</span>
    <span class="c">#           aosp_car_arm64-userdebug \</span>
        print_lunch_menu
        <span class="nb">echo</span> <span class="nt">-n</span> <span class="s2">"Which would you like? [aosp_arm-eng] "</span>
        <span class="nb">read </span>answer
    <span class="k">fi

    </span><span class="nb">local </span><span class="nv">selection</span><span class="o">=</span>

    <span class="c"># -z表示字符串为空</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$answer</span><span class="s2">"</span> <span class="o">]</span>
    <span class="k">then</span>
        <span class="c"># Step 2 ：如果answer为空，则默认选项值是aosp_arm-eng</span>
        <span class="c"># （例如用户直接在lunch要求输入时回车的情况）</span>
        <span class="nv">selection</span><span class="o">=</span>aosp_arm-eng
    <span class="c"># Step 3：如果lunch操作得到的输入是数字，则将数字转换为LUNCH_MENU_CHOICES中的字符串</span>
    <span class="k">elif</span> <span class="o">(</span><span class="nb">echo</span> <span class="nt">-n</span> <span class="nv">$answer</span> | <span class="nb">grep</span> <span class="nt">-q</span> <span class="nt">-e</span> <span class="s2">"^[0-9][0-9]*$"</span><span class="o">)</span>
    <span class="k">then
        </span><span class="nb">local </span><span class="nv">choices</span><span class="o">=(</span><span class="si">$(</span><span class="nv">TARGET_BUILD_APPS</span><span class="o">=</span> get_build_var COMMON_LUNCH_CHOICES<span class="si">)</span><span class="o">)</span>
        <span class="k">if</span> <span class="o">[</span> <span class="nv">$answer</span> <span class="nt">-le</span> <span class="k">${#</span><span class="nv">choices</span><span class="p">[@]</span><span class="k">}</span> <span class="o">]</span>
        <span class="k">then</span>
            <span class="c"># array in zsh starts from 1 instead of 0.</span>
            <span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$ZSH_VERSION</span><span class="s2">"</span> <span class="o">]</span>
            <span class="k">then</span>
            <span class="c"># 结果存入selection</span>
                <span class="nv">selection</span><span class="o">=</span><span class="k">${</span><span class="nv">choices</span><span class="p">[</span><span class="k">$((</span><span class="nv">$answer</span><span class="k">))</span><span class="p">]</span><span class="k">}</span>
            <span class="k">else
                </span><span class="nv">selection</span><span class="o">=</span><span class="k">${</span><span class="nv">choices</span><span class="p">[</span><span class="k">$((</span><span class="nv">$answer</span><span class="o">-</span><span class="m">1</span><span class="k">))</span><span class="p">]</span><span class="k">}</span>
            <span class="k">fi
        fi
    else
        </span><span class="nv">selection</span><span class="o">=</span><span class="nv">$answer</span>
    <span class="k">fi

    </span><span class="nb">export </span><span class="nv">TARGET_BUILD_APPS</span><span class="o">=</span>

    <span class="nb">local </span>product variant_and_version variant version
    <span class="c"># 比如此处选择的是aosp_car_arm64-userdebug</span>
    <span class="c"># product=aosp_car_arm64</span>
    <span class="nv">product</span><span class="o">=</span><span class="k">${</span><span class="nv">selection</span><span class="p">%%-*</span><span class="k">}</span> <span class="c"># Trim everything after first dash</span>
    <span class="c"># variant_and_version=userdebug</span>
    <span class="nv">variant_and_version</span><span class="o">=</span><span class="k">${</span><span class="nv">selection</span><span class="p">#*-</span><span class="k">}</span> <span class="c"># Trim everything up to first dash</span>
    <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$variant_and_version</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"</span><span class="nv">$selection</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">variant</span><span class="o">=</span><span class="k">${</span><span class="nv">variant_and_version</span><span class="p">%%-*</span><span class="k">}</span>
        <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$variant</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"</span><span class="nv">$variant_and_version</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
            </span><span class="nv">version</span><span class="o">=</span><span class="k">${</span><span class="nv">variant_and_version</span><span class="p">#*-</span><span class="k">}</span>
        <span class="k">fi
    fi

    if</span> <span class="o">[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$product</span><span class="s2">"</span> <span class="o">]</span>
    <span class="k">then
        </span><span class="nb">echo
        echo</span> <span class="s2">"Invalid lunch combo: </span><span class="nv">$selection</span><span class="s2">"</span>
        <span class="k">return </span>1
    <span class="k">fi</span>
    <span class="c"># Step 4： 设置TARGET_PRODUCT和TARGET_BUILD_VARIANT</span>
    <span class="c"># TARGET_PRODUCT=aosp_car_arm64</span>
    <span class="nv">TARGET_PRODUCT</span><span class="o">=</span><span class="nv">$product</span> <span class="se">\</span>
    <span class="c"># TARGET_BUILD_VARIANT=userdebug</span>
    <span class="nv">TARGET_BUILD_VARIANT</span><span class="o">=</span><span class="nv">$variant</span> <span class="se">\</span>
    <span class="nv">TARGET_PLATFORM_VERSION</span><span class="o">=</span><span class="nv">$version</span> <span class="se">\</span>
    <span class="c"># 获取此脚本所需的所有构建变量在对构建系统的一次单独的调用中</span>
    <span class="c"># Step 5：更新编译环境相关变量</span>
    build_build_var_cache
    <span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-ne</span> 0 <span class="o">]</span>
    <span class="k">then
        return </span>1
    <span class="k">fi</span>
    <span class="c"># Step 6：设置环境变量</span>
    <span class="c"># 例如打印：$ echo $TARGET_PRODUCT</span>
    <span class="c"># 显示结果：aosp_car_arm64</span>
    <span class="c"># 例如打印：$ echo $TARGET_BUILD_VARIANT</span>
    <span class="c"># 显示结果：userdebug</span>
    <span class="nb">export </span><span class="nv">TARGET_PRODUCT</span><span class="o">=</span><span class="si">$(</span>get_build_var TARGET_PRODUCT<span class="si">)</span>
    <span class="nb">export </span><span class="nv">TARGET_BUILD_VARIANT</span><span class="o">=</span><span class="si">$(</span>get_build_var TARGET_BUILD_VARIANT<span class="si">)</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$version</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
      </span><span class="nb">export </span><span class="nv">TARGET_PLATFORM_VERSION</span><span class="o">=</span><span class="si">$(</span>get_build_var TARGET_PLATFORM_VERSION<span class="si">)</span>
    <span class="k">else
      </span><span class="nb">unset </span>TARGET_PLATFORM_VERSION
    <span class="k">fi</span>
    <span class="c"># TARGET_BUILD_TYPE赋值release</span>
    <span class="nb">export </span><span class="nv">TARGET_BUILD_TYPE</span><span class="o">=</span>release

    <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="k">${</span><span class="nv">ANDROID_QUIET_BUILD</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">||</span> <span class="nb">echo</span>
    <span class="c"># Step 7：设置其他环境变量，如PROMPT_COMMAND，编译toolchain和tools相关的路径等</span>
    set_stuff_for_environment
    <span class="c"># Step 8：调用printconfig 来输出当前的设置选项</span>
    <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="k">${</span><span class="nv">ANDROID_QUIET_BUILD</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">||</span> printconfig
    <span class="c"># Step 9：删除构建var缓存，这样我们仍然可以调用构建系统来获取未在该scrip中列出的构建变量</span>
    destroy_build_var_cache
<span class="o">}</span>
</code></pre></div></div>

<hr />

<h3 id="221-print_lunch_menu打印产品目标列表">2.2.1. print_lunch_menu打印产品目标列表</h3>

<p>旧版本的<code class="language-plaintext highlighter-rouge">add_lunch_combo</code>添加产品目标的方式已经过时，现在的Android版本都是使用<code class="language-plaintext highlighter-rouge">AndroidProducts.mk</code>中的<code class="language-plaintext highlighter-rouge">COMMON_LUNCH_CHOICES</code>替代。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>print_lunch_menu<span class="o">()</span>
<span class="o">{</span>
    <span class="c"># 调用linux uname命令</span>
    <span class="c"># uname命令用于显示操作系统信息，例如内核版本、主机名、处理器类型等</span>
    <span class="c"># uname -s 或--sysname：显示操作系统名称</span>
    <span class="nb">local uname</span><span class="o">=</span><span class="si">$(</span><span class="nb">uname</span><span class="si">)</span>
    <span class="nb">local </span>choices
    <span class="c"># 调用get_build_var获取所有COMMON_LUNCH_CHOICES的值（定义在AndroidProducts.mk）</span>
    <span class="nv">choices</span><span class="o">=</span><span class="si">$(</span><span class="nv">TARGET_BUILD_APPS</span><span class="o">=</span> <span class="nv">TARGET_PRODUCT</span><span class="o">=</span> <span class="nv">TARGET_BUILD_VARIANT</span><span class="o">=</span> get_build_var COMMON_LUNCH_CHOICES 2&gt;/dev/null<span class="si">)</span>
    <span class="nb">local </span><span class="nv">ret</span><span class="o">=</span><span class="nv">$?</span>

    <span class="nb">echo</span>
    <span class="c"># 此处显示打印： You're building on Linux</span>
    <span class="nb">echo</span> <span class="s2">"You're building on"</span> <span class="nv">$uname</span>
    <span class="nb">echo

    </span><span class="k">if</span> <span class="o">[</span> <span class="nv">$ret</span> <span class="nt">-ne</span> 0 <span class="o">]</span>
    <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"Warning: Cannot display lunch menu."</span>
        <span class="nb">echo
        echo</span> <span class="s2">"Note: You can invoke lunch with an explicit target:"</span>
        <span class="nb">echo
        echo</span> <span class="s2">"  usage: lunch [target]"</span> <span class="o">&gt;</span>&amp;2
        <span class="nb">echo
        </span><span class="k">return
    fi

    </span><span class="nb">echo</span> <span class="s2">"Lunch menu... pick a combo:"</span>

    <span class="nb">local </span><span class="nv">i</span><span class="o">=</span>1
    <span class="nb">local </span>choice
    <span class="c"># 遍历打印所有产品目标</span>
    <span class="k">for </span>choice <span class="k">in</span> <span class="si">$(</span><span class="nb">echo</span> <span class="nv">$choices</span><span class="si">)</span>
    <span class="k">do
        </span><span class="nb">echo</span> <span class="s2">"     </span><span class="nv">$i</span><span class="s2">. </span><span class="nv">$choice</span><span class="s2">"</span>
        <span class="nv">i</span><span class="o">=</span><span class="k">$((</span><span class="nv">$i</span><span class="o">+</span><span class="m">1</span><span class="k">))</span>
    <span class="k">done

    </span><span class="nb">echo</span>
<span class="o">}</span>
</code></pre></div></div>

<hr />

<h3 id="222-build_build_var_cache更新环境变量">2.2.2. build_build_var_cache更新环境变量</h3>

<p>更新环境变量主要通过执行<code class="language-plaintext highlighter-rouge">build/soong/soong_ui.bash --dumpvars-mode</code>完成，最终执行的是<code class="language-plaintext highlighter-rouge">./out/soog_ui  --dumpvars-mode</code></p>

<p>关于该命令的执行流程：
1.<code class="language-plaintext highlighter-rouge">build/soong/cmd/soong_ui/main.go</code> – commands数组中定义该flag选项，对应执行dumpVars函数
2.dumpVars函数会调用执行<code class="language-plaintext highlighter-rouge">build/soong/ui/build/dumpvars.go</code>中的DumpMakeVars函数，再调用dumpMakeVars函数
3.调用<code class="language-plaintext highlighter-rouge">build/soong/ui/build/kati.go</code>的ckati命令：<code class="language-plaintext highlighter-rouge">ckati -f build/make/core/config.mk --color_warnings --kati_stats dump-many-vars MAKECMDGOALS=goals</code></p>

<p><strong>PS:</strong> 这部分go脚本的详细逻辑暂未梳理，后续先看下<code class="language-plaintext highlighter-rouge">build/make/core/config.mk</code>这个makefile文件的内容。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Get all the build variables needed by this script in a single call to the build system.</span>
<span class="k">function </span>build_build_var_cache<span class="o">()</span>
<span class="o">{</span>
    <span class="nb">local </span><span class="nv">T</span><span class="o">=</span><span class="si">$(</span>gettop<span class="si">)</span>
    <span class="c"># 从脚本中grep获取到变量名</span>
    <span class="nv">cached_vars</span><span class="o">=(</span><span class="sb">`</span><span class="nb">cat</span> <span class="nv">$T</span>/build/envsetup.sh | <span class="nb">tr</span> <span class="s1">'()'</span> <span class="s1">'  '</span> | <span class="nb">awk</span> <span class="s1">'{for(i=1;i&lt;=NF;i++) if($i~/get_build_var/) print $(i+1)}'</span> | <span class="nb">sort</span> <span class="nt">-u</span> | <span class="nb">tr</span> <span class="s1">'\n'</span> <span class="s1">' '</span><span class="sb">`</span><span class="o">)</span>
    <span class="nv">cached_abs_vars</span><span class="o">=(</span><span class="sb">`</span><span class="nb">cat</span> <span class="nv">$T</span>/build/envsetup.sh | <span class="nb">tr</span> <span class="s1">'()'</span> <span class="s1">'  '</span> | <span class="nb">awk</span> <span class="s1">'{for(i=1;i&lt;=NF;i++) if($i~/get_abs_build_var/) print $(i+1)}'</span> | <span class="nb">sort</span> <span class="nt">-u</span> | <span class="nb">tr</span> <span class="s1">'\n'</span> <span class="s1">' '</span><span class="sb">`</span><span class="o">)</span>
    <span class="c"># Call the build system to dump the "&lt;val&gt;=&lt;value&gt;" pairs as a shell script.</span>
    <span class="nv">build_dicts_script</span><span class="o">=</span><span class="sb">`</span><span class="se">\b</span>uiltin <span class="nb">cd</span> <span class="nv">$T</span><span class="p">;</span> build/soong/soong_ui.bash <span class="nt">--dumpvars-mode</span> <span class="se">\</span>
                        <span class="nt">--vars</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">cached_vars</span><span class="p">[*]</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
                        <span class="nt">--abs-vars</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">cached_abs_vars</span><span class="p">[*]</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
                        <span class="nt">--var-prefix</span><span class="o">=</span>var_cache_ <span class="se">\</span>
                        <span class="nt">--abs-var-prefix</span><span class="o">=</span>abs_var_cache_<span class="sb">`</span>
    <span class="nb">local </span><span class="nv">ret</span><span class="o">=</span><span class="nv">$?</span>
    <span class="c"># 如果不等于0则执行，正常ret的值均为0</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nv">$ret</span> <span class="nt">-ne</span> 0 <span class="o">]</span>
    <span class="k">then
        </span><span class="nb">unset </span>build_dicts_script
        <span class="k">return</span> <span class="nv">$ret</span>
    <span class="k">fi</span>
    <span class="c"># 执行脚本以将“&lt;val&gt;=&lt;value&gt;”对存储为shell变量</span>
    <span class="nb">eval</span> <span class="s2">"</span><span class="nv">$build_dicts_script</span><span class="s2">"</span>
    <span class="nv">ret</span><span class="o">=</span><span class="nv">$?</span>
    <span class="nb">unset </span>build_dicts_script
    <span class="k">if</span> <span class="o">[</span> <span class="nv">$ret</span> <span class="nt">-ne</span> 0 <span class="o">]</span>
    <span class="k">then
        return</span> <span class="nv">$ret</span>
    <span class="k">fi
    </span><span class="nv">BUILD_VAR_CACHE_READY</span><span class="o">=</span><span class="s2">"true"</span>
<span class="o">}</span>
</code></pre></div></div>

<hr />

<h3 id="223-set_stuff_for_environment设置其他环境变量">2.2.3. set_stuff_for_environment设置其他环境变量</h3>

<p>该函数主要就是设置了一些环境变量：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>set_stuff_for_environment<span class="o">()</span>
<span class="o">{</span>
    <span class="c"># 该函数设置了一系列的环境变量</span>
    setpaths
    set_sequence_number

    <span class="nb">export </span><span class="nv">ANDROID_BUILD_TOP</span><span class="o">=</span><span class="si">$(</span>gettop<span class="si">)</span>
    <span class="c"># With this environment variable new GCC can apply colors to warnings/errors</span>
    <span class="nb">export </span><span class="nv">GCC_COLORS</span><span class="o">=</span><span class="s1">'error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'</span>
<span class="o">}</span>

<span class="k">function </span>set_sequence_number<span class="o">()</span>
<span class="o">{</span>
    <span class="c"># 固定值13</span>
    <span class="nb">export </span><span class="nv">BUILD_ENV_SEQUENCE_NUMBER</span><span class="o">=</span>13
<span class="o">}</span>

<span class="k">function </span>setpaths<span class="o">()</span>
<span class="o">{</span>
    <span class="c"># 项目top根目录</span>
    <span class="nb">local </span><span class="nv">T</span><span class="o">=</span><span class="si">$(</span>gettop<span class="si">)</span>
    <span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="s2">"</span><span class="nv">$T</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"Couldn't locate the top of the tree.  Try setting TOP."</span>
        <span class="k">return
    fi</span>

    <span class="c"># 此函数将ANDROID_BUILD_PATHS设置为它要添加到PATH中的内容，下次运行时，它会将其从PATH中删除。这是必需的，这样lunch可以多次运行，并且仍然有工作路径</span>
    <span class="c"># 注意：在windows/cygwin上，由于路径中有“C:\Program Files”，ANDROID_BUILD_PATHS将包含空格</span>

    <span class="c"># out with the old</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$ANDROID_BUILD_PATHS</span><span class="s2">"</span> <span class="o">]</span> <span class="p">;</span> <span class="k">then
        </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="k">${</span><span class="nv">PATH</span><span class="p">/</span><span class="nv">$ANDROID_BUILD_PATHS</span><span class="p">/</span><span class="k">}</span>
    <span class="k">fi
    if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$ANDROID_PRE_BUILD_PATHS</span><span class="s2">"</span> <span class="o">]</span> <span class="p">;</span> <span class="k">then
        </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="k">${</span><span class="nv">PATH</span><span class="p">/</span><span class="nv">$ANDROID_PRE_BUILD_PATHS</span><span class="p">/</span><span class="k">}</span>
        <span class="c"># strip leading ':', if any</span>
        <span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="k">${</span><span class="nv">PATH</span><span class="p">/</span>:%/<span class="k">}</span>
    <span class="k">fi</span>

    <span class="c"># and in with the new</span>
    <span class="nb">local </span><span class="nv">prebuiltdir</span><span class="o">=</span><span class="si">$(</span>getprebuilt<span class="si">)</span>
    <span class="c"># （1）ANDROID_GCC_PREBUILTS定义在build/make/core/dumpvar.mk中</span>
    <span class="c">#   代码：ANDROID_GCC_PREBUILTS := prebuilts/gcc/$(HOST_PREBUILT_TAG)</span>
    <span class="c"># （2）HOST_PREBUILT_TAG定义在build/make/core/envsetup.mk中</span>
    <span class="c">#   代码：HOST_PREBUILT_TAG := $(BUILD_OS)-$(HOST_PREBUILT_ARCH)</span>
    <span class="c"># # -- BUILD_OS就是HOST_OS，编译的操作系统，此处是linux</span>
    <span class="c"># # -- HOST_PREBUILT_ARCH = x86</span>
    <span class="c"># （3）调用获取构建变量的值作为绝对路径</span>
    <span class="c">#  所以此处gccprebuiltdir = "绝对路径"/prebuilts/gcc/Linux-x86</span>
    <span class="nb">local </span><span class="nv">gccprebuiltdir</span><span class="o">=</span><span class="si">$(</span>get_abs_build_var ANDROID_GCC_PREBUILTS<span class="si">)</span>

    <span class="c"># 这些值定义在core/config.mk</span>
    <span class="nb">local </span><span class="nv">targetgccversion</span><span class="o">=</span><span class="si">$(</span>get_build_var TARGET_GCC_VERSION<span class="si">)</span>
    <span class="nb">local </span><span class="nv">targetgccversion2</span><span class="o">=</span><span class="si">$(</span>get_build_var 2ND_TARGET_GCC_VERSION<span class="si">)</span>
    <span class="c"># 设置环境变量TARGET_GCC_VERSION，此处是4.9</span>
    <span class="nb">export </span><span class="nv">TARGET_GCC_VERSION</span><span class="o">=</span><span class="nv">$targetgccversion</span>

    <span class="c"># The gcc toolchain does not exists for windows/cygwin. In this case, do not reference it.</span>
    <span class="nb">export </span><span class="nv">ANDROID_TOOLCHAIN</span><span class="o">=</span>
    <span class="nb">export </span><span class="nv">ANDROID_TOOLCHAIN_2ND_ARCH</span><span class="o">=</span>
    <span class="c"># 获取TARGET_ARCH值，在上面可以看到编译目标的CPU架构是arm64</span>
    <span class="nb">local </span><span class="nv">ARCH</span><span class="o">=</span><span class="si">$(</span>get_build_var TARGET_ARCH<span class="si">)</span>
    <span class="nb">local </span>toolchaindir <span class="nv">toolchaindir2</span><span class="o">=</span>
    <span class="k">case</span> <span class="nv">$ARCH</span> <span class="k">in
        </span>x86<span class="p">)</span> <span class="nv">toolchaindir</span><span class="o">=</span>x86/x86_64-linux-android-<span class="nv">$targetgccversion</span>/bin
            <span class="p">;;</span>
        x86_64<span class="p">)</span> <span class="nv">toolchaindir</span><span class="o">=</span>x86/x86_64-linux-android-<span class="nv">$targetgccversion</span>/bin
            <span class="p">;;</span>
        arm<span class="p">)</span> <span class="nv">toolchaindir</span><span class="o">=</span>arm/arm-linux-androideabi-<span class="nv">$targetgccversion</span>/bin
            <span class="p">;;</span>
        <span class="c"># 执行此处</span>
        arm64<span class="p">)</span> <span class="nv">toolchaindir</span><span class="o">=</span>aarch64/aarch64-linux-android-<span class="nv">$targetgccversion</span>/bin<span class="p">;</span>
               <span class="nv">toolchaindir2</span><span class="o">=</span>arm/arm-linux-androideabi-<span class="nv">$targetgccversion2</span>/bin
            <span class="p">;;</span>
        <span class="k">*</span><span class="p">)</span>
            <span class="nb">echo</span> <span class="s2">"Can't find toolchain for unknown architecture: </span><span class="nv">$ARCH</span><span class="s2">"</span>
            <span class="nv">toolchaindir</span><span class="o">=</span>xxxxxxxxx
            <span class="p">;;</span>
    <span class="k">esac</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-d</span> <span class="s2">"</span><span class="nv">$gccprebuiltdir</span><span class="s2">/</span><span class="nv">$toolchaindir</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
        <span class="c"># 设置环境变量ANDROID_TOOLCHAIN</span>
        <span class="nb">export </span><span class="nv">ANDROID_TOOLCHAIN</span><span class="o">=</span><span class="nv">$gccprebuiltdir</span>/<span class="nv">$toolchaindir</span>
    <span class="k">fi

    if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$toolchaindir2</span><span class="s2">"</span> <span class="nt">-a</span> <span class="nt">-d</span> <span class="s2">"</span><span class="nv">$gccprebuiltdir</span><span class="s2">/</span><span class="nv">$toolchaindir2</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">export </span><span class="nv">ANDROID_TOOLCHAIN_2ND_ARCH</span><span class="o">=</span><span class="nv">$gccprebuiltdir</span>/<span class="nv">$toolchaindir2</span>
    <span class="k">fi</span>
    <span class="c"># 设置环境变量ANDROID_DEV_SCRIPTS</span>
    <span class="nb">export </span><span class="nv">ANDROID_DEV_SCRIPTS</span><span class="o">=</span><span class="nv">$T</span>/development/scripts:<span class="nv">$T</span>/prebuilts/devtools/tools:<span class="nv">$T</span>/external/selinux/prebuilts/bin

    <span class="c"># add kernel specific binaries</span>
    <span class="k">case</span> <span class="si">$(</span><span class="nb">uname</span> <span class="nt">-s</span><span class="si">)</span> <span class="k">in
        </span>Linux<span class="p">)</span>
            <span class="nb">export </span><span class="nv">ANDROID_DEV_SCRIPTS</span><span class="o">=</span><span class="nv">$ANDROID_DEV_SCRIPTS</span>:<span class="nv">$T</span>/prebuilts/misc/linux-x86/dtc:<span class="nv">$T</span>/prebuilts/misc/linux-x86/libufdt
            <span class="p">;;</span>
        <span class="k">*</span><span class="p">)</span>
            <span class="p">;;</span>
    <span class="k">esac</span>

    <span class="nv">ANDROID_BUILD_PATHS</span><span class="o">=</span><span class="si">$(</span>get_build_var ANDROID_BUILD_PATHS<span class="si">)</span>:<span class="nv">$ANDROID_TOOLCHAIN</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$ANDROID_TOOLCHAIN_2ND_ARCH</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">ANDROID_BUILD_PATHS</span><span class="o">=</span><span class="nv">$ANDROID_BUILD_PATHS</span>:<span class="nv">$ANDROID_TOOLCHAIN_2ND_ARCH</span>
    <span class="k">fi
    </span><span class="nv">ANDROID_BUILD_PATHS</span><span class="o">=</span><span class="nv">$ANDROID_BUILD_PATHS</span>:<span class="nv">$ANDROID_DEV_SCRIPTS</span>

    <span class="c"># 将llvm binutils prebuilts路径附加到ANDROID_BUILD_PATHS</span>
    <span class="c"># ANDROID_CLANG_PREBUILTS定义在build/make/core/dumpvar.mk中</span>
    <span class="c"># 代码：ANDROID_CLANG_PREBUILTS := prebuilts/clang/host/$(HOST_PREBUILT_TAG)</span>
    <span class="nb">local </span><span class="nv">ANDROID_LLVM_BINUTILS</span><span class="o">=</span><span class="si">$(</span>get_abs_build_var ANDROID_CLANG_PREBUILTS<span class="si">)</span>/llvm-binutils-stable
    <span class="nv">ANDROID_BUILD_PATHS</span><span class="o">=</span><span class="nv">$ANDROID_BUILD_PATHS</span>:<span class="nv">$ANDROID_LLVM_BINUTILS</span>

    <span class="c"># Set up ASAN_SYMBOLIZER_PATH for SANITIZE_HOST=address builds.</span>
    <span class="c"># 设置环境变量ASAN_SYMBOLIZER_PATH</span>
    <span class="nb">export </span><span class="nv">ASAN_SYMBOLIZER_PATH</span><span class="o">=</span><span class="nv">$ANDROID_LLVM_BINUTILS</span>/llvm-symbolizer

    <span class="c"># If prebuilts/android-emulator/&lt;system&gt;/ exists, prepend it to our PATH</span>
    <span class="c"># to ensure that the corresponding 'emulator' binaries are used.</span>
    <span class="k">case</span> <span class="si">$(</span><span class="nb">uname</span> <span class="nt">-s</span><span class="si">)</span> <span class="k">in
        </span>Darwin<span class="p">)</span>
            <span class="nv">ANDROID_EMULATOR_PREBUILTS</span><span class="o">=</span><span class="nv">$T</span>/prebuilts/android-emulator/darwin-x86_64
            <span class="p">;;</span>
        Linux<span class="p">)</span>
            <span class="c"># 此处是linxu系统，根目录+/prebuilts/...</span>
            <span class="nv">ANDROID_EMULATOR_PREBUILTS</span><span class="o">=</span><span class="nv">$T</span>/prebuilts/android-emulator/linux-x86_64
            <span class="p">;;</span>
        <span class="k">*</span><span class="p">)</span>
            <span class="nv">ANDROID_EMULATOR_PREBUILTS</span><span class="o">=</span>
            <span class="p">;;</span>
    <span class="k">esac</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$ANDROID_EMULATOR_PREBUILTS</span><span class="s2">"</span> <span class="nt">-a</span> <span class="nt">-d</span> <span class="s2">"</span><span class="nv">$ANDROID_EMULATOR_PREBUILTS</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">ANDROID_BUILD_PATHS</span><span class="o">=</span><span class="nv">$ANDROID_BUILD_PATHS</span>:<span class="nv">$ANDROID_EMULATOR_PREBUILTS</span>
        <span class="c"># 设置环境变量ANDROID_EMULATOR_PREBUILTS</span>
        <span class="nb">export </span>ANDROID_EMULATOR_PREBUILTS
    <span class="k">fi</span>

    <span class="c"># 将asuite预编译路径附加到ANDROID_BUILD_PATHS</span>
    <span class="nb">local </span><span class="nv">os_arch</span><span class="o">=</span><span class="si">$(</span>get_build_var HOST_PREBUILT_TAG<span class="si">)</span>
    <span class="nb">local </span><span class="nv">ACLOUD_PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$T</span><span class="s2">/prebuilts/asuite/acloud/</span><span class="nv">$os_arch</span><span class="s2">"</span>
    <span class="nb">local </span><span class="nv">AIDEGEN_PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$T</span><span class="s2">/prebuilts/asuite/aidegen/</span><span class="nv">$os_arch</span><span class="s2">"</span>
    <span class="nb">local </span><span class="nv">ATEST_PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$T</span><span class="s2">/prebuilts/asuite/atest/</span><span class="nv">$os_arch</span><span class="s2">"</span>
    <span class="nb">export </span><span class="nv">ANDROID_BUILD_PATHS</span><span class="o">=</span><span class="nv">$ANDROID_BUILD_PATHS</span>:<span class="nv">$ACLOUD_PATH</span>:<span class="nv">$AIDEGEN_PATH</span>:<span class="nv">$ATEST_PATH</span>:

    <span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$ANDROID_BUILD_PATHS$PATH</span>

    <span class="c"># out with the duplicate old</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="nv">$ANDROID_PYTHONPATH</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
        <span class="c"># 设置环境变量PYTHONPATH和ANDROID_PYTHONPATH，python路径</span>
        <span class="nb">export </span><span class="nv">PYTHONPATH</span><span class="o">=</span><span class="k">${</span><span class="nv">PYTHONPATH</span><span class="p">//</span><span class="nv">$ANDROID_PYTHONPATH</span><span class="p">/</span><span class="k">}</span>
    <span class="k">fi</span>
    <span class="c"># and in with the new</span>
    <span class="nb">export </span><span class="nv">ANDROID_PYTHONPATH</span><span class="o">=</span><span class="nv">$T</span>/development/python-packages:
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="nv">$VENDOR_PYTHONPATH</span>  <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nv">ANDROID_PYTHONPATH</span><span class="o">=</span><span class="nv">$ANDROID_PYTHONPATH$VENDOR_PYTHONPATH</span>
    <span class="k">fi
    </span><span class="nb">export </span><span class="nv">PYTHONPATH</span><span class="o">=</span><span class="nv">$ANDROID_PYTHONPATH$PYTHONPATH</span>
    <span class="c"># 设置java环境变量</span>
    <span class="nb">export </span><span class="nv">ANDROID_JAVA_HOME</span><span class="o">=</span><span class="si">$(</span>get_abs_build_var ANDROID_JAVA_HOME<span class="si">)</span>
    <span class="nb">export </span><span class="nv">JAVA_HOME</span><span class="o">=</span><span class="nv">$ANDROID_JAVA_HOME</span>
    <span class="nb">export </span><span class="nv">ANDROID_JAVA_TOOLCHAIN</span><span class="o">=</span><span class="si">$(</span>get_abs_build_var ANDROID_JAVA_TOOLCHAIN<span class="si">)</span>
    <span class="nb">export </span><span class="nv">ANDROID_PRE_BUILD_PATHS</span><span class="o">=</span><span class="nv">$ANDROID_JAVA_TOOLCHAIN</span>:
    <span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$ANDROID_PRE_BUILD_PATHS$PATH</span>
    <span class="c"># 设置一些out目录相关的环境变量</span>
    <span class="nb">unset </span>ANDROID_PRODUCT_OUT
    <span class="c"># 值 .../out/target/product/emulator_arm64</span>
    <span class="nb">export </span><span class="nv">ANDROID_PRODUCT_OUT</span><span class="o">=</span><span class="si">$(</span>get_abs_build_var PRODUCT_OUT<span class="si">)</span>
    <span class="c"># 值 .../out/target/product/emulator_arm64</span>
    <span class="nb">export </span><span class="nv">OUT</span><span class="o">=</span><span class="nv">$ANDROID_PRODUCT_OUT</span>

    <span class="nb">unset </span>ANDROID_HOST_OUT
    <span class="c"># 值 .../out/host/linux-x86</span>
    <span class="nb">export </span><span class="nv">ANDROID_HOST_OUT</span><span class="o">=</span><span class="si">$(</span>get_abs_build_var HOST_OUT<span class="si">)</span>

    <span class="nb">unset </span>ANDROID_SOONG_HOST_OUT
    <span class="c"># 值 .../out/soong/host/linux-x86</span>
    <span class="nb">export </span><span class="nv">ANDROID_SOONG_HOST_OUT</span><span class="o">=</span><span class="si">$(</span>get_abs_build_var SOONG_HOST_OUT<span class="si">)</span>

    <span class="nb">unset </span>ANDROID_HOST_OUT_TESTCASES
    <span class="c"># 值 .../out/host/linux-x86/testcases</span>
    <span class="nb">export </span><span class="nv">ANDROID_HOST_OUT_TESTCASES</span><span class="o">=</span><span class="si">$(</span>get_abs_build_var HOST_OUT_TESTCASES<span class="si">)</span>

    <span class="nb">unset </span>ANDROID_TARGET_OUT_TESTCASES
    <span class="c"># 值 .../out/target/product/emulator_arm64/testcases</span>
    <span class="nb">export </span><span class="nv">ANDROID_TARGET_OUT_TESTCASES</span><span class="o">=</span><span class="si">$(</span>get_abs_build_var TARGET_OUT_TESTCASES<span class="si">)</span>

    <span class="c"># needed for building linux on MacOS</span>
    <span class="c"># TODO: fix the path</span>
    <span class="c">#export HOST_EXTRACFLAGS="-I "$T/system/kernel_headers/host_include</span>
<span class="o">}</span>
</code></pre></div></div>

<hr />

<h3 id="224-printconfig打印环境变量">2.2.4. printconfig打印环境变量</h3>

<p>该函数的执行流程类似于<code class="language-plaintext highlighter-rouge">build_build_var_cache</code>，只是一个是<code class="language-plaintext highlighter-rouge">--dumpvars-mode</code>，此处是<code class="language-plaintext highlighter-rouge">--dumpvar-mode</code></p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">build/soong/cmd/soong_ui/main.go</code> – commands数组中定义<code class="language-plaintext highlighter-rouge">--dumpvar-mode</code>该flag选项，对应执行<code class="language-plaintext highlighter-rouge">dumpVar</code>函数</li>
  <li><code class="language-plaintext highlighter-rouge">dumpVar</code>函数根据入参<code class="language-plaintext highlighter-rouge">report_config</code>，然后调用执行<code class="language-plaintext highlighter-rouge">build/soong/ui/build/dumpvars.go</code>中的<code class="language-plaintext highlighter-rouge">build.DumpMakeVars(ctx, config, nil, build.BannerVars)</code>，此处build.BannerVars定义在<code class="language-plaintext highlighter-rouge">build/soong/ui/build/dumpvars.go</code>中，就是需要打印的一些环境变量</li>
  <li>调用<code class="language-plaintext highlighter-rouge">build/soong/ui/build/kati.go</code>的ckati命令：<code class="language-plaintext highlighter-rouge">ckati -f build/make/core/config.mk --color_warnings --kati_stats dump-many-vars MAKECMDGOALS=goals</code></li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># build/make/envsetup.sh</span>
<span class="k">function </span>printconfig<span class="o">()</span>
<span class="o">{</span>
    <span class="nb">local </span><span class="nv">T</span><span class="o">=</span><span class="si">$(</span>gettop<span class="si">)</span>
    <span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="s2">"</span><span class="nv">$T</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"Couldn't locate the top of the tree.  Try setting TOP."</span> <span class="o">&gt;</span>&amp;2
        <span class="k">return
    fi</span>
    <span class="c"># 调用get_build_var输入参数report_config</span>
    get_build_var report_config
<span class="o">}</span>

<span class="c"># Get the exact value of a build variable.</span>
<span class="k">function </span>get_build_var<span class="o">()</span>
<span class="o">{</span>
    <span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$BUILD_VAR_CACHE_READY</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"true"</span> <span class="o">]</span>
    <span class="k">then
        </span><span class="nb">eval</span> <span class="s2">"echo </span><span class="se">\"\$</span><span class="s2">{var_cache_</span><span class="nv">$1</span><span class="s2">}</span><span class="se">\"</span><span class="s2">"</span>
        <span class="k">return </span>0
    <span class="k">fi

    </span><span class="nb">local </span><span class="nv">T</span><span class="o">=</span><span class="si">$(</span>gettop<span class="si">)</span>
    <span class="k">if</span> <span class="o">[</span> <span class="o">!</span> <span class="s2">"</span><span class="nv">$T</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"Couldn't locate the top of the tree.  Try setting TOP."</span> <span class="o">&gt;</span>&amp;2
        <span class="k">return </span>1
    <span class="k">fi</span>
    <span class="c"># 调用--dumpvar-mode，入参report_config</span>
    <span class="o">(</span><span class="se">\c</span>d <span class="nv">$T</span><span class="p">;</span> build/soong/soong_ui.bash <span class="nt">--dumpvar-mode</span> <span class="nv">$1</span><span class="o">)</span>
<span class="o">}</span>

<span class="c"># build/soong/cmd/soong_ui/main.go</span>
var commands <span class="o">[]</span><span class="nb">command</span> <span class="o">=</span> <span class="o">[]</span><span class="nb">command</span><span class="o">{</span>
	.... <span class="o">{</span>
		flag:         <span class="s2">"--dumpvar-mode"</span>,
		description:  <span class="s2">"print the value of the legacy make variable VAR to stdout"</span>,
		simpleOutput: <span class="nb">true</span>,
		logsPrefix:   <span class="s2">"dumpvars-"</span>,
		config:       dumpVarConfig,
		stdio:        customStdio,
        <span class="c"># 执行函数</span>
		run:          dumpVar,
	<span class="o">}</span>,....

func dumpVar<span class="o">(</span>ctx build.Context, config build.Config, args <span class="o">[]</span>string, _ string<span class="o">)</span> <span class="o">{</span>
    ....
    <span class="k">if </span>varName <span class="o">==</span> <span class="s2">"report_config"</span> <span class="o">{</span>
        <span class="c"># 执行此处函数，入参build.BannerVars</span>
		varData, err :<span class="o">=</span> build.DumpMakeVars<span class="o">(</span>ctx, config, nil, build.BannerVars<span class="o">)</span>
		<span class="k">if </span>err <span class="o">!=</span> nil <span class="o">{</span>
			ctx.Fatal<span class="o">(</span>err<span class="o">)</span>
		<span class="o">}</span>

		fmt.Println<span class="o">(</span>build.Banner<span class="o">(</span>varData<span class="o">))</span>
	<span class="o">}</span>
    ....
<span class="o">}</span>

<span class="c"># build/soong/ui/build/dumpvars.go</span>
<span class="c"># 需要打印的环境变量列表，就是lunch最终终端显示的</span>
var BannerVars <span class="o">=</span> <span class="o">[]</span>string<span class="o">{</span>
	<span class="s2">"PLATFORM_VERSION_CODENAME"</span>,
	<span class="s2">"PLATFORM_VERSION"</span>,
	<span class="s2">"TARGET_PRODUCT"</span>,
	<span class="s2">"TARGET_BUILD_VARIANT"</span>,
	<span class="s2">"TARGET_BUILD_TYPE"</span>,
	<span class="s2">"TARGET_BUILD_APPS"</span>,
	<span class="s2">"TARGET_BUILD_UNBUNDLED"</span>,
	<span class="s2">"TARGET_ARCH"</span>,
	<span class="s2">"TARGET_ARCH_VARIANT"</span>,
	<span class="s2">"TARGET_CPU_VARIANT"</span>,
	<span class="s2">"TARGET_2ND_ARCH"</span>,
	<span class="s2">"TARGET_2ND_ARCH_VARIANT"</span>,
	<span class="s2">"TARGET_2ND_CPU_VARIANT"</span>,
	<span class="s2">"HOST_ARCH"</span>,
	<span class="s2">"HOST_2ND_ARCH"</span>,
	<span class="s2">"HOST_OS"</span>,
	<span class="s2">"HOST_OS_EXTRA"</span>,
	<span class="s2">"HOST_CROSS_OS"</span>,
	<span class="s2">"HOST_CROSS_ARCH"</span>,
	<span class="s2">"HOST_CROSS_2ND_ARCH"</span>,
	<span class="s2">"HOST_BUILD_TYPE"</span>,
	<span class="s2">"BUILD_ID"</span>,
	<span class="s2">"OUT_DIR"</span>,
	<span class="s2">"AUX_OS_VARIANT_LIST"</span>,
	<span class="s2">"PRODUCT_SOONG_NAMESPACES"</span>,
	<span class="s2">"SOONG_SDK_SNAPSHOT_PREFER"</span>,
	<span class="s2">"SOONG_SDK_SNAPSHOT_VERSION"</span>,
<span class="o">}</span>
</code></pre></div></div>

<hr />

<h3 id="225-lunch执行流程总结">2.2.5. lunch执行流程总结</h3>

<p>对于上面代码的流程分析，lunch的操作流程大致如下：</p>

<ol>
  <li>首先<code class="language-plaintext highlighter-rouge">lunch()</code>函数会获取lunch命令携带的输入参数：
    <ul>
      <li>如果参数不为空，参数则为指定要编译的设备型号和编译类型；</li>
      <li>如果参数为空，会调用<code class="language-plaintext highlighter-rouge">print_lunch_menu</code>来显示所有产品目标（AndroidProducts.mk中COMMON_LUNCH_CHOICES定义），读取用户的输入，存入变量answer：
        <ul>
          <li>如果answer为空，即在lunch菜单显示后，用户只敲了一个回车。则设置默认选项aosp_arm-eng，结果存入变量selection</li>
          <li>如果lunch操作得到的输入是数字，则将数字转换为LUNCH_MENU_CHOICES中的字符串，结果存入selection</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>解析selection的值，比如此处选择的是<code class="language-plaintext highlighter-rouge">aosp_car_arm64-userdebug</code>，则得到<code class="language-plaintext highlighter-rouge">product=aosp_car_arm64</code>和<code class="language-plaintext highlighter-rouge">variant_and_version=userdebug</code>, 分别保存到<code class="language-plaintext highlighter-rouge">TARGET_PRODUCT</code>和<code class="language-plaintext highlighter-rouge">TARGET_BUILD_VARIANT</code>中</li>
  <li>根据前面的设置，调用<code class="language-plaintext highlighter-rouge">build_build_var_cache</code>来更新编译环境相关变量</li>
  <li>export将<code class="language-plaintext highlighter-rouge">TARGET_PRODUCT</code>, <code class="language-plaintext highlighter-rouge">TARGET_BUILD_VARIANT</code>和<code class="language-plaintext highlighter-rouge">TARGET_BUILD_TYPE</code>位置为环境变量</li>
  <li>调用<code class="language-plaintext highlighter-rouge">set_stuff_for_environment</code>来设置其他环境变量，如PROMPT_COMMAND，编译toolchain和tools相关的路径等</li>
  <li>调用<code class="language-plaintext highlighter-rouge">printconfig</code>来输出当前的设置选项</li>
  <li>调用<code class="language-plaintext highlighter-rouge">destroy_build_var_cache</code>删除构建var缓存，这样我们仍然可以调用构建系统来获取未在该scrip中列出的构建变量</li>
</ol>

<hr />

<h2 id="23-lunch执行流程图">2.3. lunch执行流程图</h2>

<p><img src="../../assets/post/2024/2024-02-23-android12_BuildSystem_2\Android编译模块_lunch执行流程图.png" alt="" /></p>

<hr />

<h1 id="3-make">3. make</h1>

<blockquote>
  <p>执行make进行Android编译的函数入口是<code class="language-plaintext highlighter-rouge">build/make/envsetup.sh的make()</code>，我们从这里开始进行编译流程的分析。</p>
</blockquote>

<h2 id="31-整体流程图">3.1. 整体流程图</h2>

<p>make函数调用get_make_command，从方法内容中看到，真正编译的入口是<code class="language-plaintext highlighter-rouge">build/soong/soong_ui.bash</code>，传入参数<code class="language-plaintext highlighter-rouge">--make-mode</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># build/make/envsetup.sh</span>
<span class="k">function </span>make<span class="o">()</span>
<span class="o">{</span>
    _wrap_build <span class="si">$(</span>get_make_command <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span><span class="si">)</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
<span class="o">}</span>

<span class="k">function </span>get_make_command<span class="o">()</span>
<span class="o">{</span>
    <span class="c"># If we're in the top of an Android tree, use soong_ui.bash instead of make</span>
    <span class="c"># 如果soong_ui.bash存在且是一个普通文件则为真，此处结果是true</span>
    <span class="k">if</span> <span class="o">[</span> <span class="nt">-f</span> build/soong/soong_ui.bash <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
        <span class="c"># Always use the real make if -C is passed in</span>
        <span class="k">for </span>arg <span class="k">in</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span><span class="p">;</span> <span class="k">do
            if</span> <span class="o">[[</span> <span class="nv">$arg</span> <span class="o">==</span> <span class="nt">-C</span><span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
                </span><span class="nb">echo command </span>make
                <span class="k">return
            fi
        done</span>
        <span class="c"># 真正执行编译的入口</span>
        <span class="nb">echo </span>build/soong/soong_ui.bash <span class="nt">--make-mode</span>
    <span class="k">else
        </span><span class="nb">echo command </span>make
    <span class="k">fi</span>
<span class="o">}</span>
</code></pre></div></div>

<p>首先我们先看下整体流程图，了解整体make的流程，然后再进行细化梳理：</p>

<p><img src="../../assets/post/2024/2024-02-23-android12_BuildSystem_2\Android编译模块_make执行流程.png" alt="" /></p>

<hr />

<h2 id="32-构建soong_ui">3.2. 构建soong_ui</h2>

<h3 id="321-流程分析">3.2.1. 流程分析</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>执行make进行Android编译
<span class="nt">---</span><span class="o">&gt;</span> build/make/envsetup.sh <span class="nt">--</span> make<span class="o">()</span>
    <span class="nt">---</span><span class="o">&gt;</span> get_make_command<span class="o">()</span>调用build/soong/soong_ui.bash <span class="nt">--make-mode</span>

<span class="nt">---</span><span class="o">&gt;</span> build/soong/soong_ui.bash 该脚本首先会source build/soong/scripts/microfactory.bash
    <span class="nt">---</span><span class="o">&gt;</span> microfactory.bash脚本做了两件事情：
        （1）定义soong_build_go函数命令（入参<span class="nv">$1</span><span class="o">=</span>所需二进制文件名称 <span class="nv">$2</span><span class="o">=</span>package包名）
        （2）source build/blueprint/microfactory/microfactory.bash脚本，定义了build_go函数命令
    <span class="nt">---</span><span class="o">&gt;</span> 执行命令soong_build_go soong_ui android/soong/cmd/soong_ui，编译sonng_ui模块，可通过
            查看build/soong/cmd/soong_ui/Android.bp发现，主要就是编译build/soong/cmd/soong_ui/main.go，
            编译结果生成二进制文件out/soong_ui
    <span class="nt">---</span><span class="o">&gt;</span> 执行<span class="s2">"</span><span class="si">$(</span>getoutdir<span class="si">)</span><span class="s2">/soong_ui"</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>命令，即<span class="s2">"./out/soong_ui --make-mode"</span>，这样会开始执行soong_ui模块

</code></pre></div></div>

<hr />

<h3 id="322-调用栈图">3.2.2. 调用栈图</h3>

<p><img src="../../assets/post/2024/2024-02-23-android12_BuildSystem_2\Android编译模块_soong_ui构建调用栈.png" alt="" /></p>

<hr />

<h3 id="323-buildsoongsoong_uibash">3.2.3. build/soong/soong_ui.bash</h3>

<p>soong_ui.bash用来配置一些资源环境，得到一些函数命令，例如：soong_build_go，最终回退到根目录，执行<code class="language-plaintext highlighter-rouge">out/soong_ui --make-mode</code>进行真正的构建编译。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>......
<span class="c"># Save the current PWD for use in soong_ui</span>
<span class="nb">export </span><span class="nv">ORIGINAL_PWD</span><span class="o">=</span><span class="k">${</span><span class="nv">PWD</span><span class="k">}</span>
<span class="nb">export </span><span class="nv">TOP</span><span class="o">=</span><span class="si">$(</span>gettop<span class="si">)</span>
<span class="c"># 此处会得到soong_build_go函数命令、build_go函数命令</span>
<span class="nb">source</span> <span class="k">${</span><span class="nv">TOP</span><span class="k">}</span>/build/soong/scripts/microfactory.bash
<span class="c"># 编译soong_ui模块</span>
soong_build_go soong_ui android/soong/cmd/soong_ui
<span class="c"># 返回项目根目录</span>
<span class="nb">cd</span> <span class="k">${</span><span class="nv">TOP</span><span class="k">}</span>
<span class="c"># 执行编译命令，即./out/soong_ui --make-mode</span>
<span class="nb">exec</span> <span class="s2">"</span><span class="si">$(</span>getoutdir<span class="si">)</span><span class="s2">/soong_ui"</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div></div>

<hr />

<h3 id="324-buildsoongscriptsmicrofactorybash">3.2.4. build/soong/scripts/microfactory.bash</h3>

<p>该文件主要是得到build_go的函数命令,并提供soong_build_go的函数执行方法</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>....
<span class="c"># 定义soong_build_go命令，编译二进制文件</span>
<span class="c"># Arguments:</span>
<span class="c">#  $1: 所需的二进制文件名称(soong_ui)</span>
<span class="c">#  $2: 包名(android/soong/cmd/soong_ui)</span>
<span class="k">function </span>soong_build_go
<span class="o">{</span>
    <span class="nv">BUILDDIR</span><span class="o">=</span><span class="si">$(</span>getoutdir<span class="si">)</span> <span class="se">\</span>
      <span class="nv">SRCDIR</span><span class="o">=</span><span class="k">${</span><span class="nv">TOP</span><span class="k">}</span> <span class="se">\</span>
      <span class="nv">BLUEPRINTDIR</span><span class="o">=</span><span class="k">${</span><span class="nv">TOP</span><span class="k">}</span>/build/blueprint <span class="se">\</span>
      <span class="nv">EXTRA_ARGS</span><span class="o">=</span><span class="s2">"-pkg-path android/soong=</span><span class="k">${</span><span class="nv">TOP</span><span class="k">}</span><span class="s2">/build/soong -pkg-path github.com/golang/protobuf=</span><span class="k">${</span><span class="nv">TOP</span><span class="k">}</span><span class="s2">/external/golang-protobuf"</span> <span class="se">\</span>
      build_go <span class="nv">$@</span>
<span class="o">}</span>
<span class="c"># source blueprint下面的同名脚本</span>
<span class="nb">source</span> <span class="k">${</span><span class="nv">TOP</span><span class="k">}</span>/build/blueprint/microfactory/microfactory.bash
</code></pre></div></div>

<hr />

<h3 id="325-buildblueprintmicrofactorymicrofactorybash">3.2.5. build/blueprint/microfactory/microfactory.bash</h3>

<p>build_go主要目的就是用来构建生成 out/soong_ui这个可执行程序，用于参与最终的编译。</p>

<p><strong>该函数主要做了两件事：</strong></p>
<ol>
  <li>通过<code class="language-plaintext highlighter-rouge">/build/blueprint/microfactory/microfactory.go</code>编译出<code class="language-plaintext highlighter-rouge">/out/microfactory_Linux</code></li>
  <li>使用<code class="language-plaintext highlighter-rouge">/out/microfactory_Linux</code>来编译<code class="language-plaintext highlighter-rouge">soong_ui</code></li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 定义build_go命令，编译二进制文件</span>
<span class="c"># Arguments:</span>
<span class="c">#  $1: 所需的二进制文件名称(soong_ui)</span>
<span class="c">#  $2: 包名(android/soong/cmd/soong_ui)</span>
<span class="k">function </span>build_go
<span class="o">{</span>
    .......
    <span class="nb">local </span>mf_cmd
    <span class="k">if</span> <span class="o">[</span> <span class="nv">$from_src</span> <span class="nt">-eq</span> 1 <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
        <span class="c"># `go run` requires a single main package, so create one</span>
        <span class="nb">local </span><span class="nv">gen_src_dir</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">BUILDDIR</span><span class="k">}</span><span class="s2">/.microfactory_</span><span class="si">$(</span><span class="nb">uname</span><span class="si">)</span><span class="s2">_intermediates/src"</span>
        <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="k">${</span><span class="nv">gen_src_dir</span><span class="k">}</span><span class="s2">"</span>
        <span class="nb">sed</span> <span class="s2">"s/^package microfactory/package main/"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">mf_src</span><span class="k">}</span><span class="s2">/microfactory.go"</span> <span class="o">&gt;</span><span class="s2">"</span><span class="k">${</span><span class="nv">gen_src_dir</span><span class="k">}</span><span class="s2">/microfactory.go"</span>
	    <span class="c"># 编译microfactory.go，得到结果赋值mf_cmd用于下面编译</span>
        <span class="nv">mf_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">GOROOT</span><span class="k">}</span><span class="s2">/bin/go run </span><span class="k">${</span><span class="nv">gen_src_dir</span><span class="k">}</span><span class="s2">/microfactory.go"</span>
    <span class="k">else
        </span><span class="nv">mf_cmd</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">mf_bin</span><span class="k">}</span><span class="s2">"</span>
    <span class="k">fi

    </span><span class="nb">rm</span> <span class="nt">-f</span> <span class="s2">"</span><span class="k">${</span><span class="nv">BUILDDIR</span><span class="k">}</span><span class="s2">/.</span><span class="nv">$1</span><span class="s2">.trace"</span>
    <span class="c"># GOROOT must be absolute because `go run` changes the local directory</span>
    <span class="nv">GOROOT</span><span class="o">=</span><span class="si">$(</span><span class="nb">cd</span> <span class="nv">$GOROOT</span><span class="p">;</span> <span class="nb">pwd</span><span class="si">)</span> <span class="k">${</span><span class="nv">mf_cmd</span><span class="k">}</span> <span class="nt">-b</span> <span class="s2">"</span><span class="k">${</span><span class="nv">mf_bin</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
            <span class="nt">-pkg-path</span> <span class="s2">"github.com/google/blueprint=</span><span class="k">${</span><span class="nv">BLUEPRINTDIR</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
            <span class="nt">-trimpath</span> <span class="s2">"</span><span class="k">${</span><span class="nv">SRCDIR</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
            <span class="k">${</span><span class="nv">EXTRA_ARGS</span><span class="k">}</span> <span class="se">\</span>
            <span class="nt">-o</span> <span class="s2">"</span><span class="k">${</span><span class="nv">built_bin</span><span class="k">}</span><span class="s2">"</span> <span class="nv">$2</span>

    <span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-eq</span> 0 <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> <span class="nv">$from_src</span> <span class="nt">-eq</span> 1 <span class="o">]</span><span class="p">;</span> <span class="k">then
        </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">mf_version</span><span class="k">}</span><span class="s2">"</span> <span class="o">&gt;</span><span class="s2">"</span><span class="k">${</span><span class="nv">mf_version_file</span><span class="k">}</span><span class="s2">"</span>
    <span class="k">fi</span>
<span class="o">}</span>
</code></pre></div></div>

<p><strong>从该脚本中看到soong_ui最终的编译命令展开是：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="si">$(</span><span class="nb">cd</span> /prebuilts/go/linux-x86/<span class="p">;</span> <span class="nb">pwd</span><span class="si">)</span> /out/microfactory_Linux 
  <span class="nt">-b</span> <span class="s2">"/out/microfactory_Linux"</span> <span class="se">\</span>
  <span class="nt">-pkg-path</span> <span class="s2">"github.com/google/blueprint=/build/blueprint"</span> <span class="se">\</span>
  <span class="nt">-trimpath</span> <span class="s2">"./"</span> <span class="se">\</span>
  <span class="nt">-pkg-path</span> android/soong<span class="o">=</span>/build/soong 
  <span class="nt">-pkg-path</span> github.com/golang/protobuf<span class="o">=</span>/external/golang-protobuf<span class="o">}</span> <span class="se">\</span>
  <span class="nt">-o</span> <span class="s2">"out/soong_ui"</span> android/soong/cmd/soong_ui
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">microfactory</code>是一个增量编译go程序的工具。它类似于Go语言中的<code class="language-plaintext highlighter-rouge">go install</code>命令，但不需要GOPATH。</p>

<p><code class="language-plaintext highlighter-rouge">包-&gt;路径</code>映射可以指定为命令行选项：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">-pkg-path</span> android/soong<span class="o">=</span>build/soong
<span class="nt">-pkg-path</span> github.com/google/blueprint<span class="o">=</span>build/blueprint
</code></pre></div></div>

<p>其实microfactory就是一个高级一点的go命令，它自己由go编出来，又代替了一部分go的部分功能。</p>

<hr />

<h2 id="33-soong_ui启动编译">3.3. soong_ui启动编译</h2>

<p>在<code class="language-plaintext highlighter-rouge">build/soong/soong_ui.bash</code>脚本的最后会执行<code class="language-plaintext highlighter-rouge">"$(getoutdir)/soong_ui" "$@"</code>命令，即<code class="language-plaintext highlighter-rouge">./out/soong_ui --make-mode</code>，此处从soong_ui模块(build/soong/cmd/soong_ui)的main.go入口开始执行进行编译。</p>

<p><strong>soong_ui.bash最后内容：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>....
<span class="nb">cd</span> <span class="k">${</span><span class="nv">TOP</span><span class="k">}</span>
<span class="nb">exec</span> <span class="s2">"</span><span class="si">$(</span>getoutdir<span class="si">)</span><span class="s2">/soong_ui"</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div></div>

<h3 id="331-流程分析">3.3.1. 流程分析</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>build/soong/soong_ui.bash <span class="nt">--</span> 执行./out/soong_ui <span class="nt">--make-mode</span>
<span class="nt">----</span><span class="o">&gt;</span> build/soong/cmd/soong_ui/main.go <span class="nt">--</span> 执行soong_ui模块，从main函数入口开始执行
        <span class="nt">---</span><span class="o">&gt;</span> main函数执行了一系列操作后，最终通过该flag调用run元素的runMake函数
</code></pre></div></div>

<p>在该main函数中的一系列操作会涉及到<code class="language-plaintext highlighter-rouge">build/soong/ui/</code>的所有模块，但是主要还是作一些准备工作，最后在runMake函数中才会调用到soong-ui-build模块(即<code class="language-plaintext highlighter-rouge">build/soong/ui/build/</code>)进行实际编译</p>

<ul>
  <li>build</li>
  <li>logger</li>
  <li>metrics</li>
  <li>status</li>
  <li>terminal</li>
  <li>tracer</li>
</ul>

<h3 id="332-调用栈图">3.3.2. 调用栈图</h3>

<p><img src="../../assets/post/2024/2024-02-23-android12_BuildSystem_2\Android编译模块_soong_ui启动编译调用栈.png" alt="" /></p>

<hr />

<h3 id="333-maingo---main方法分析">3.3.3. main.go - main()方法分析</h3>

<p>从main.go的main()函数入口看到解释，soong_ui的主要执行。命令格式如下：
<code class="language-plaintext highlighter-rouge">soong_ui＜command＞〔＜arg 1＞＜arg 2＞…＜arg n＞〕</code></p>

<p>该命令是soong_ui执行的类型。只指定了一种执行类型，参数是特定于命令的。</p>

<p>所以此处调用<code class="language-plaintext highlighter-rouge">./out/soong_ui --make-mode</code>就会先从main()函数开始执行，参数则是<code class="language-plaintext highlighter-rouge">--make-mode</code></p>

<p><strong>下面来分析下main函数的代码：</strong></p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">getCommand</span><span class="p">(</span><span class="n">args</span> <span class="p">[]</span><span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="n">command</span><span class="p">,</span> <span class="p">[]</span><span class="kt">string</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="o">&lt;</span> <span class="m">2</span> <span class="p">{</span>
		<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="no">nil</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"Too few arguments: %q"</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span>
	<span class="p">}</span>
    <span class="c">//从commands数组中遍历查询参数</span>
    <span class="c">//此处查询到的结果如下，然后以&amp;c返回这一组数据</span>
    <span class="c">//var commands []command = []command{</span>
    <span class="c">//  .....</span>
    <span class="c">//	{</span>
	<span class="c">//	flag:        "--make-mode",</span>
	<span class="c">//	description: "build the modules by the target name (i.e. soong_docs)",</span>
	<span class="c">//	config: func(ctx build.Context, args ...string) build.Config {</span>
	<span class="c">//		return build.NewConfig(ctx, args...)</span>
	<span class="c">//	},</span>
	<span class="c">//	stdio: stdio,</span>
	<span class="c">//	run:   runMake,</span>
	<span class="c">//    }</span>
	<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">c</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">commands</span> <span class="p">{</span>
		<span class="k">if</span> <span class="n">c</span><span class="o">.</span><span class="n">flag</span> <span class="o">==</span> <span class="n">args</span><span class="p">[</span><span class="m">1</span><span class="p">]</span> <span class="p">{</span>
            <span class="c">//返回值</span>
			<span class="k">return</span> <span class="o">&amp;</span><span class="n">c</span><span class="p">,</span> <span class="n">args</span><span class="p">[</span><span class="m">2</span><span class="o">:</span><span class="p">],</span> <span class="no">nil</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="c">// command not found</span>
	<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="no">nil</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"Command not found: %q"</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">shared</span><span class="o">.</span><span class="n">ReexecWithDelveMaybe</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"SOONG_UI_DELVE"</span><span class="p">),</span> <span class="n">shared</span><span class="o">.</span><span class="n">ResolveDelveBinary</span><span class="p">())</span>
    <span class="c">//获取当前时间</span>
	<span class="n">buildStarted</span> <span class="o">:=</span> <span class="n">time</span><span class="o">.</span><span class="n">Now</span><span class="p">()</span>
    <span class="c">//此处调用getCommand函数</span>
    <span class="c">//getCommand根据args[1]标志查找适当的命令（此处是--make-mode），args[0]是soong_ui文件名</span>
    <span class="c">//  参数c就是查询到的数据</span>
    <span class="c">//  参数args就是入参--make-mode</span>
    <span class="c">//  参数err是返回值，此处是nil，nil在go语言表示一个零值</span>
	<span class="n">c</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">getCommand</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Args</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintf</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Stderr</span><span class="p">,</span> <span class="s">"Error parsing `soong` args: %s.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
		<span class="n">os</span><span class="o">.</span><span class="n">Exit</span><span class="p">(</span><span class="m">1</span><span class="p">)</span>
	<span class="p">}</span>
    <span class="c">//创建一个Ninja的终端输出，代码参考build/soong/ui/terminal/status.go</span>
    <span class="c">//NewStatusOutput返回表示当前构建状态的StatusOutput，类似于Ninja的内置终端输出</span>
	<span class="n">output</span> <span class="o">:=</span> <span class="n">terminal</span><span class="o">.</span><span class="n">NewStatusOutput</span><span class="p">(</span><span class="n">c</span><span class="o">.</span><span class="n">stdio</span><span class="p">()</span><span class="o">.</span><span class="n">Stdout</span><span class="p">(),</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"NINJA_STATUS"</span><span class="p">),</span> <span class="n">c</span><span class="o">.</span><span class="n">simpleOutput</span><span class="p">,</span>
		<span class="n">build</span><span class="o">.</span><span class="n">OsEnvironment</span><span class="p">()</span><span class="o">.</span><span class="n">IsEnvTrue</span><span class="p">(</span><span class="s">"ANDROID_QUIET_BUILD"</span><span class="p">))</span>

    <span class="c">//创建一个logger实例连接到终端输出，即go编译的日志系统</span>
    <span class="c">//代码参考build/soong/ui/logger/logger.go</span>
	<span class="n">log</span> <span class="o">:=</span> <span class="n">logger</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">log</span><span class="o">.</span><span class="n">Cleanup</span><span class="p">()</span>

	<span class="c">//创建一个上下文以简化程序终止过程</span>
	<span class="n">ctx</span><span class="p">,</span> <span class="n">cancel</span> <span class="o">:=</span> <span class="n">context</span><span class="o">.</span><span class="n">WithCancel</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">())</span>
	<span class="k">defer</span> <span class="n">cancel</span><span class="p">()</span>

    <span class="c">//创建一个新的trace文件写入对象，使得将时间写入到日志对象中</span>
    <span class="c">//参考build/soong/ui/tracer/tracer.go</span>
	<span class="n">trace</span> <span class="o">:=</span> <span class="n">tracer</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">log</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">trace</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

    <span class="c">//创建并启动一个新的度量记录，用于记录此次构建编译</span>
    <span class="c">//参考build/soong/ui/metrics/metrics.go</span>
	<span class="n">met</span> <span class="o">:=</span> <span class="n">metrics</span><span class="o">.</span><span class="n">New</span><span class="p">()</span>
    <span class="c">//设置构建的时间</span>
	<span class="n">met</span><span class="o">.</span><span class="n">SetBuildDateTime</span><span class="p">(</span><span class="n">buildStarted</span><span class="p">)</span>
    <span class="c">//编译命令，即此次入参--make-mode</span>
	<span class="n">met</span><span class="o">.</span><span class="n">SetBuildCommand</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Args</span><span class="p">)</span>

    <span class="c">//创建一个新的Status实例，用于管理操作计数和事件输出通道</span>
    <span class="c">//参考build/soong/ui/status/status.go</span>
	<span class="n">stat</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">status</span><span class="o">.</span><span class="n">Status</span><span class="p">{}</span>
	<span class="k">defer</span> <span class="n">stat</span><span class="o">.</span><span class="n">Finish</span><span class="p">()</span>
    <span class="c">//将终端输出和跟踪器连接到状态status</span>
	<span class="n">stat</span><span class="o">.</span><span class="n">AddOutput</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
	<span class="n">stat</span><span class="o">.</span><span class="n">AddOutput</span><span class="p">(</span><span class="n">trace</span><span class="o">.</span><span class="n">StatusTracer</span><span class="p">())</span>

    <span class="c">//设置一个清理程序，以防正常的终止过程不起作用</span>
    <span class="c">//防止所有子进程被终止的时候，日志/trace缓冲区被刷新到磁盘里面</span>
    <span class="c">//参考代码build/soong/ui/build/build.go</span>
	<span class="n">build</span><span class="o">.</span><span class="n">SetupSignals</span><span class="p">(</span><span class="n">log</span><span class="p">,</span> <span class="n">cancel</span><span class="p">,</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">trace</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
		<span class="n">log</span><span class="o">.</span><span class="n">Cleanup</span><span class="p">()</span>
		<span class="n">stat</span><span class="o">.</span><span class="n">Finish</span><span class="p">()</span>
	<span class="p">})</span>
    <span class="c">//设置context对象，组合了Context.Context、logger.logger和terminal.Writer</span>
    <span class="c">//这些都与当前构建无关，可以用于多个构建，而Config对象包含每个构建的信息。</span>
    <span class="c">//参考代码build/soong/ui/build/context.go</span>
	<span class="n">buildCtx</span> <span class="o">:=</span> <span class="n">build</span><span class="o">.</span><span class="n">Context</span><span class="p">{</span><span class="n">ContextImpl</span><span class="o">:</span> <span class="o">&amp;</span><span class="n">build</span><span class="o">.</span><span class="n">ContextImpl</span><span class="p">{</span>
		<span class="n">Context</span><span class="o">:</span> <span class="n">ctx</span><span class="p">,</span>
		<span class="n">Logger</span><span class="o">:</span>  <span class="n">log</span><span class="p">,</span>
		<span class="n">Metrics</span><span class="o">:</span> <span class="n">met</span><span class="p">,</span>
		<span class="n">Tracer</span><span class="o">:</span>  <span class="n">trace</span><span class="p">,</span>
		<span class="n">Writer</span><span class="o">:</span>  <span class="n">output</span><span class="p">,</span>
		<span class="n">Status</span><span class="o">:</span>  <span class="n">stat</span><span class="p">,</span>
	<span class="p">}}</span>
    <span class="c">//调用"--make-mode"的config元素</span>
    <span class="c">//buildCtx就是上面的变量</span>
    <span class="c">//args就是此次入参--make-mode</span>
	<span class="n">config</span> <span class="o">:=</span> <span class="n">c</span><span class="o">.</span><span class="n">config</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="n">args</span><span class="o">...</span><span class="p">)</span>

    <span class="c">//调用loadEnvConfig方法，加载环境变量配置</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">loadEnvConfig</span><span class="p">();</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintf</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Stderr</span><span class="p">,</span> <span class="s">"failed to parse env config files: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
		<span class="n">os</span><span class="o">.</span><span class="n">Exit</span><span class="p">(</span><span class="m">1</span><span class="p">)</span>
	<span class="p">}</span>

    <span class="c">//调用build/soong/ui/build/build.go方法</span>
    <span class="c">//确保out目录存在，并且有适当的文件防止kati递归到其中</span>
	<span class="n">build</span><span class="o">.</span><span class="n">SetupOutDir</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>

    <span class="c">//此处是Android 12新增，主要针对Bazel</span>
    <span class="c">//参考代码build/soong/ui/build/config.go</span>
    <span class="c">//如果使用了bazel，并且dist存在</span>
    <span class="c">//Android 12并未使用Bazel</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">UseBazel</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">config</span><span class="o">.</span><span class="n">Dist</span><span class="p">()</span> <span class="p">{</span>
        <span class="c">//对于Bazel支持，如有必要，这会将文件和目录从例如out/dist/$f移动到dist_DIR/$f</span>
		<span class="k">defer</span> <span class="n">populateExternalDistDir</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="c">//TODO ------------------------------------------</span>
    <span class="c">//此处是我添加的debug代码，在lunch后可以看到日志打印：Not Use Bazel!</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">UseBazel</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintf</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Stderr</span><span class="p">,</span> <span class="s">" ---- Use Bazel!"</span><span class="p">)</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintf</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Stderr</span><span class="p">,</span> <span class="s">" ---- Not Use Bazel!"</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="c">//TODO ------------------------------------------</span>

    <span class="c">//设置要在日志目录中输出的文件夹目录</span>
    <span class="c">//LogsDir返回生成日志和度量文件所在的日志目录。默认情况下，logs目录是out目录。</span>
    <span class="c">//如果指定了参数dist，则logs目录//为＜dist_dir＞/logs。</span>
    <span class="c">//参考build/soong/ui/build/config.go代码，此处是直接使用的OutDir()的返回值out，即out目录</span>
	<span class="n">logsDir</span> <span class="o">:=</span> <span class="n">config</span><span class="o">.</span><span class="n">LogsDir</span><span class="p">()</span>

    <span class="c">//度量文件定义的通用列表</span>
	<span class="n">buildErrorFile</span> <span class="o">:=</span> <span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">logsDir</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">logsPrefix</span><span class="o">+</span><span class="s">"build_error"</span><span class="p">)</span>
	<span class="n">rbeMetricsFile</span> <span class="o">:=</span> <span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">logsDir</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">logsPrefix</span><span class="o">+</span><span class="s">"rbe_metrics.pb"</span><span class="p">)</span>
	<span class="n">soongMetricsFile</span> <span class="o">:=</span> <span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">logsDir</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">logsPrefix</span><span class="o">+</span><span class="s">"soong_metrics"</span><span class="p">)</span>

	<span class="n">build</span><span class="o">.</span><span class="n">PrintOutDirWarning</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
    <span class="c">//创建文件夹（MkdirAll相当于mkdir -p循环创建多级目录）</span>
	<span class="n">os</span><span class="o">.</span><span class="n">MkdirAll</span><span class="p">(</span><span class="n">logsDir</span><span class="p">,</span> <span class="m">0777</span><span class="p">)</span>
    <span class="c">//设置对应的日志、trace输出到的文件，此处均是输出到out/目录下面</span>
	<span class="n">log</span><span class="o">.</span><span class="n">SetOutput</span><span class="p">(</span><span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">logsDir</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">logsPrefix</span><span class="o">+</span><span class="s">"soong.log"</span><span class="p">))</span>
	<span class="n">trace</span><span class="o">.</span><span class="n">SetOutput</span><span class="p">(</span><span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">logsDir</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">logsPrefix</span><span class="o">+</span><span class="s">"build.trace"</span><span class="p">))</span>
	<span class="n">stat</span><span class="o">.</span><span class="n">AddOutput</span><span class="p">(</span><span class="n">status</span><span class="o">.</span><span class="n">NewVerboseLog</span><span class="p">(</span><span class="n">log</span><span class="p">,</span> <span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">logsDir</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">logsPrefix</span><span class="o">+</span><span class="s">"verbose.log"</span><span class="p">)))</span>
	<span class="n">stat</span><span class="o">.</span><span class="n">AddOutput</span><span class="p">(</span><span class="n">status</span><span class="o">.</span><span class="n">NewErrorLog</span><span class="p">(</span><span class="n">log</span><span class="p">,</span> <span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">logsDir</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">logsPrefix</span><span class="o">+</span><span class="s">"error.log"</span><span class="p">)))</span>
	<span class="n">stat</span><span class="o">.</span><span class="n">AddOutput</span><span class="p">(</span><span class="n">status</span><span class="o">.</span><span class="n">NewProtoErrorLog</span><span class="p">(</span><span class="n">log</span><span class="p">,</span> <span class="n">buildErrorFile</span><span class="p">))</span>
	<span class="n">stat</span><span class="o">.</span><span class="n">AddOutput</span><span class="p">(</span><span class="n">status</span><span class="o">.</span><span class="n">NewCriticalPath</span><span class="p">(</span><span class="n">log</span><span class="p">))</span>
	<span class="n">stat</span><span class="o">.</span><span class="n">AddOutput</span><span class="p">(</span><span class="n">status</span><span class="o">.</span><span class="n">NewBuildProgressLog</span><span class="p">(</span><span class="n">log</span><span class="p">,</span> <span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">logsDir</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">logsPrefix</span><span class="o">+</span><span class="s">"build_progress.pb"</span><span class="p">)))</span>

	<span class="n">buildCtx</span><span class="o">.</span><span class="n">Verbosef</span><span class="p">(</span><span class="s">"Detected %.3v GB total RAM"</span><span class="p">,</span> <span class="kt">float32</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">TotalRAM</span><span class="p">())</span><span class="o">/</span><span class="p">(</span><span class="m">1024</span><span class="o">*</span><span class="m">1024</span><span class="o">*</span><span class="m">1024</span><span class="p">))</span>
	<span class="n">buildCtx</span><span class="o">.</span><span class="n">Verbosef</span><span class="p">(</span><span class="s">"Parallelism (local/remote/highmem): %v/%v/%v"</span><span class="p">,</span>
		<span class="n">config</span><span class="o">.</span><span class="n">Parallel</span><span class="p">(),</span> <span class="n">config</span><span class="o">.</span><span class="n">RemoteParallel</span><span class="p">(),</span> <span class="n">config</span><span class="o">.</span><span class="n">HighmemParallel</span><span class="p">())</span>

	<span class="p">{</span>
        <span class="c">//函数调用的顺序很重要。最后一个defer函数调用是执行的第一个调用，用于将rbe度量保存到protobuf文件中。</span>
        <span class="c">//接下来是soongmetrics文件。</span>
        <span class="c">//Bazel配置文件是在调用uploadMetrics之前编写的。如果启用了指标的上载，则会上载写入的文件</span>
		<span class="n">files</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span>
			<span class="n">buildErrorFile</span><span class="p">,</span>           <span class="c">// build error strings</span>
			<span class="n">rbeMetricsFile</span><span class="p">,</span>           <span class="c">// high level metrics related to remote build execution.</span>
			<span class="n">soongMetricsFile</span><span class="p">,</span>         <span class="c">// high level metrics related to this build system.</span>
			<span class="n">config</span><span class="o">.</span><span class="n">BazelMetricsDir</span><span class="p">(),</span> <span class="c">// directory that contains a set of bazel metrics.</span>
		<span class="p">}</span>
		<span class="k">defer</span> <span class="n">build</span><span class="o">.</span><span class="n">UploadMetrics</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">c</span><span class="o">.</span><span class="n">simpleOutput</span><span class="p">,</span> <span class="n">buildStarted</span><span class="p">,</span> <span class="n">files</span><span class="o">...</span><span class="p">)</span>
		<span class="k">defer</span> <span class="n">met</span><span class="o">.</span><span class="n">Dump</span><span class="p">(</span><span class="n">soongMetricsFile</span><span class="p">)</span>
		<span class="k">defer</span> <span class="n">build</span><span class="o">.</span><span class="n">DumpRBEMetrics</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">rbeMetricsFile</span><span class="p">)</span>
	<span class="p">}</span>

    <span class="c">//读取起点的时间</span>
	<span class="k">if</span> <span class="n">start</span><span class="p">,</span> <span class="n">ok</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">LookupEnv</span><span class="p">(</span><span class="s">"TRACE_BEGIN_SOONG"</span><span class="p">);</span> <span class="n">ok</span> <span class="p">{</span>
        <span class="c">//soong_ui.bash在获取开始时间时使用date命令的%N（nanosec）标志，Darwin不支持这一点。</span>
        <span class="c">//在分析该值之前，请检查它是否已正确执行。</span>
		<span class="k">if</span> <span class="o">!</span><span class="n">strings</span><span class="o">.</span><span class="n">HasSuffix</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="s">"N"</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">if</span> <span class="n">start_time</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">strconv</span><span class="o">.</span><span class="n">ParseUint</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="m">10</span><span class="p">,</span> <span class="m">64</span><span class="p">);</span> <span class="n">err</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
				<span class="n">log</span><span class="o">.</span><span class="n">Verbosef</span><span class="p">(</span><span class="s">"Took %dms to start up."</span><span class="p">,</span>
					<span class="n">time</span><span class="o">.</span><span class="n">Since</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">Unix</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="kt">int64</span><span class="p">(</span><span class="n">start_time</span><span class="p">)))</span><span class="o">.</span><span class="n">Nanoseconds</span><span class="p">()</span><span class="o">/</span><span class="n">time</span><span class="o">.</span><span class="n">Millisecond</span><span class="o">.</span><span class="n">Nanoseconds</span><span class="p">())</span>
				<span class="n">buildCtx</span><span class="o">.</span><span class="n">CompleteTrace</span><span class="p">(</span><span class="n">metrics</span><span class="o">.</span><span class="n">RunSetupTool</span><span class="p">,</span> <span class="s">"startup"</span><span class="p">,</span> <span class="n">start_time</span><span class="p">,</span> <span class="kt">uint64</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">Now</span><span class="p">()</span><span class="o">.</span><span class="n">UnixNano</span><span class="p">()))</span>
			<span class="p">}</span>
		<span class="p">}</span>

		<span class="k">if</span> <span class="n">executable</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Executable</span><span class="p">();</span> <span class="n">err</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">trace</span><span class="o">.</span><span class="n">ImportMicrofactoryLog</span><span class="p">(</span><span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">filepath</span><span class="o">.</span><span class="n">Dir</span><span class="p">(</span><span class="n">executable</span><span class="p">),</span> <span class="s">"."</span><span class="o">+</span><span class="n">filepath</span><span class="o">.</span><span class="n">Base</span><span class="p">(</span><span class="n">executable</span><span class="p">)</span><span class="o">+</span><span class="s">".trace"</span><span class="p">))</span>
		<span class="p">}</span>
	<span class="p">}</span>

    <span class="c">//修复由于repo错误导致的源树，该错误不会删除已删除的链接文件</span>
	<span class="n">fixBadDanglingLink</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="s">"hardware/qcom/sdm710/Android.bp"</span><span class="p">)</span>
	<span class="n">fixBadDanglingLink</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="s">"hardware/qcom/sdm710/Android.mk"</span><span class="p">)</span>

	<span class="c">// Create a source finder.</span>
    <span class="c">//创建一个source finder</span>
    <span class="c">//参考代码build/soong/ui/build/finder.go</span>
    <span class="c">//该文件为soong_ui的Finder类型提供了一个接口。</span>
    <span class="c">//Finder用于递归遍历源树以收集文件的路径，如Android.bp、Android.mk、AndroidProducts.mk、Blueprints、CleanSpec.mk、.git、.repo等</span>
    <span class="c">//并将路径列表/数据库存储在`$OUT_DIR/.module_paths'下的文件中。此目录也可以是dist'd</span>
    <span class="c">//NewSourceFinder返回一个配置为搜索源文件的新Finder。完成后，NewSourceFinde的调用方应调用&lt;f.Shutdown（</span>
	<span class="n">f</span> <span class="o">:=</span> <span class="n">build</span><span class="o">.</span><span class="n">NewSourceFinder</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">f</span><span class="o">.</span><span class="n">Shutdown</span><span class="p">()</span>
    <span class="c">//FindSources搜索&lt;f&gt;已知的源文件，并将它们写入文件系统以供以后使用</span>
	<span class="n">build</span><span class="o">.</span><span class="n">FindSources</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span>
    <span class="c">//此处调用command的run元素</span>
    <span class="c">//此次入参是--make-mode，对应的run元素是runMake方法</span>
	<span class="n">c</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">buildCtx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">logsDir</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h3 id="334-maingo---runmake方法分析">3.3.4. main.go - runMake()方法分析</h3>

<p>该函数最主要的就是最后一行，执行到<code class="language-plaintext highlighter-rouge">build.Build(ctx, config)</code>，此处就会执行到<code class="language-plaintext highlighter-rouge">build/soong/ui/build/build.go</code>脚本</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">runMake</span><span class="p">(</span><span class="n">ctx</span> <span class="n">build</span><span class="o">.</span><span class="n">Context</span><span class="p">,</span> <span class="n">config</span> <span class="n">build</span><span class="o">.</span><span class="n">Config</span><span class="p">,</span> <span class="n">_</span> <span class="p">[]</span><span class="kt">string</span><span class="p">,</span> <span class="n">logsDir</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">IsVerbose</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">writer</span> <span class="o">:=</span> <span class="n">ctx</span><span class="o">.</span><span class="n">Writer</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"! The argument `showcommands` is no longer supported."</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"! Instead, the verbose log is always written to a compressed file in the output dir:"</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"!"</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintf</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"!   gzip -cd %s/verbose.log.gz | less -R</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">logsDir</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"!"</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"! Older versions are saved in verbose.log.#.gz files"</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
		<span class="k">select</span> <span class="p">{</span>
		<span class="k">case</span> <span class="o">&lt;-</span><span class="n">time</span><span class="o">.</span><span class="n">After</span><span class="p">(</span><span class="m">5</span> <span class="o">*</span> <span class="n">time</span><span class="o">.</span><span class="n">Second</span><span class="p">)</span><span class="o">:</span>
		<span class="k">case</span> <span class="o">&lt;-</span><span class="n">ctx</span><span class="o">.</span><span class="n">Done</span><span class="p">()</span><span class="o">:</span>
			<span class="k">return</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">ok</span> <span class="o">:=</span> <span class="n">config</span><span class="o">.</span><span class="n">Environment</span><span class="p">()</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"ONE_SHOT_MAKEFILE"</span><span class="p">);</span> <span class="n">ok</span> <span class="p">{</span>
		<span class="n">writer</span> <span class="o">:=</span> <span class="n">ctx</span><span class="o">.</span><span class="n">Writer</span>
        <span class="c">//Android 12中ONE_SHOT_MAKEFILE已经很过时</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"! The variable `ONE_SHOT_MAKEFILE` is obsolete."</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"!"</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"! If you're using `mm`, you'll need to run `source build/envsetup.sh` to update."</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"!"</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">"! Otherwise, either specify a module name with m, or use mma / MODULES-IN-..."</span><span class="p">)</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">writer</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="s">"done"</span><span class="p">)</span>
	<span class="p">}</span>
    <span class="c">//调用到soong-ui-build模块，即build/soong/ui/build/build.go的Build函数</span>
    <span class="c">//  参数1：ctx即context.go的Context对象，存入Context、Logger、Tracer等一系列对象</span>
    <span class="c">//  参数2：config就是此次构建的入参、以及一些环境变量等配置</span>
	<span class="n">build</span><span class="o">.</span><span class="n">Build</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="34-soong-ui-build构建编译">3.4. soong-ui-build构建编译</h2>

<blockquote>
  <p>在soong_ui中会调用soong-ui-build模块（build/soong/ui/build）的build.go脚本中的Build方法，从而开始编译构建。</p>
</blockquote>

<hr />

<h3 id="341-buildgo---build方法分析">3.4.1. build.go - Build()方法分析</h3>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// 构建tree</span>
<span class="c">// “what”参数可用于通过检查各种位掩码来选择运行构建的哪些组件</span>
<span class="k">func</span> <span class="n">Build</span><span class="p">(</span><span class="n">ctx</span> <span class="n">Context</span><span class="p">,</span> <span class="n">config</span> <span class="n">Config</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">ctx</span><span class="o">.</span><span class="n">Verboseln</span><span class="p">(</span><span class="s">"Starting build with args:"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Arguments</span><span class="p">())</span>
	<span class="n">ctx</span><span class="o">.</span><span class="n">Verboseln</span><span class="p">(</span><span class="s">"Environment:"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Environment</span><span class="p">()</span><span class="o">.</span><span class="n">Environ</span><span class="p">())</span>
    <span class="c">//抓取trace</span>
	<span class="n">ctx</span><span class="o">.</span><span class="n">BeginTrace</span><span class="p">(</span><span class="n">metrics</span><span class="o">.</span><span class="n">Total</span><span class="p">,</span> <span class="s">"total"</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">ctx</span><span class="o">.</span><span class="n">EndTrace</span><span class="p">()</span>

	<span class="k">if</span> <span class="n">inList</span><span class="p">(</span><span class="s">"help"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Arguments</span><span class="p">())</span> <span class="p">{</span>
		<span class="n">help</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
		<span class="k">return</span>
	<span class="p">}</span>

    <span class="c">//确保当前没有其他的soong进程运行在相同的out目录</span>
    <span class="c">//方法见build/soong/ui/build/proc_sync.go，该文件提供了跨进程同步方法，即确保给定输出目录中只有一个Soong进程在运行</span>
	<span class="n">buildLock</span> <span class="o">:=</span> <span class="n">BecomeSingletonOrFail</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">buildLock</span><span class="o">.</span><span class="n">Unlock</span><span class="p">()</span>

	<span class="k">if</span> <span class="n">inList</span><span class="p">(</span><span class="s">"clean"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Arguments</span><span class="p">())</span> <span class="o">||</span> <span class="n">inList</span><span class="p">(</span><span class="s">"clobber"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Arguments</span><span class="p">())</span> <span class="p">{</span>
		<span class="n">clean</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
		<span class="k">return</span>
	<span class="p">}</span>

    <span class="c">//如果在树的根上找到Android.mk或CleanSpec.mk，checkProbematicFiles将中止生成</span>
	<span class="n">checkProblematicFiles</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span>
    <span class="c">//checkRAM警告是否可能没有足够的RAM来完成构建</span>
	<span class="n">checkRAM</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
    <span class="c">//SetupOutDir确保out目录存在，并具有适当的文件以防止kati重复出现在其中</span>
	<span class="n">SetupOutDir</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>

    <span class="c">//如果正在使用不区分大小写的文件系统，checkCaseSensitivity将发出警告</span>
	<span class="n">checkCaseSensitivity</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
    <span class="c">//确保给定的目录存在并且为空</span>
    <span class="c">//代码参考build/soong/ui/build/util.go</span>
	<span class="n">ensureEmptyDirectoriesExist</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">TempDir</span><span class="p">())</span>
    <span class="c">//SetupPath使用path_interposer拦截对$path二进制文件的调用，并与中介器通信，以在运行时使用日志作为介质验证允许的$path二进制代码。</span>
    <span class="c">//这导致$PATH中的密封目录只包含用于构建的允许的主机工具，并将$PATH替换为只包含这些目录，并在不破坏现有用例的情况下对$PATH中允许的工具进行增量限制</span>
    <span class="c">//代码参考build/soong/ui/build/path.go</span>
	<span class="n">SetupPath</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>

    <span class="c">//此处RunAll就是一个判断，需要去编译构建其中的哪些组件</span>
    <span class="c">//代码：RunAll = RunProductConfig | RunSoong | RunKati | RunKatiNinja | RunNinja</span>
	<span class="n">what</span> <span class="o">:=</span> <span class="n">RunAll</span>

    <span class="c">//这部分导入fmt，然后使用fmt.Fprintf(os.Stderr, "Use Bazel!")打印日志查看是否执行</span>
    <span class="c">//Android 12未使用Bazel，不执行</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">UseBazel</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">what</span> <span class="o">=</span> <span class="n">RunAllWithBazel</span>
	<span class="p">}</span>
    <span class="c">//未执行</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">Checkbuild</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">what</span> <span class="o">|=</span> <span class="n">RunBuildTests</span>
	<span class="p">}</span>
    <span class="c">//未执行</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">SkipConfig</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Verboseln</span><span class="p">(</span><span class="s">"Skipping Config as requested"</span><span class="p">)</span>
		<span class="n">what</span> <span class="o">=</span> <span class="n">what</span> <span class="o">&amp;^</span> <span class="n">RunProductConfig</span>
	<span class="p">}</span>
    <span class="c">//未执行</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">SkipKati</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Verboseln</span><span class="p">(</span><span class="s">"Skipping Kati as requested"</span><span class="p">)</span>
		<span class="n">what</span> <span class="o">=</span> <span class="n">what</span> <span class="o">&amp;^</span> <span class="n">RunKati</span>
	<span class="p">}</span>
    <span class="c">//未执行</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">SkipKatiNinja</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Verboseln</span><span class="p">(</span><span class="s">"Skipping use of Kati ninja as requested"</span><span class="p">)</span>
		<span class="n">what</span> <span class="o">=</span> <span class="n">what</span> <span class="o">&amp;^</span> <span class="n">RunKatiNinja</span>
	<span class="p">}</span>
    <span class="c">//未执行</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">SkipNinja</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Verboseln</span><span class="p">(</span><span class="s">"Skipping Ninja as requested"</span><span class="p">)</span>
		<span class="n">what</span> <span class="o">=</span> <span class="n">what</span> <span class="o">&amp;^</span> <span class="n">RunNinja</span>
	<span class="p">}</span>
    <span class="c">//未执行</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">StartGoma</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">startGoma</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
	<span class="p">}</span>
    <span class="c">//未执行</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">StartRBE</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">startRBE</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
	<span class="p">}</span>

    <span class="c">//以下是构建的重点，具体可以从make的日志调试查看</span>
    <span class="c">//*****************************************************</span>
    <span class="c">//Step 1 ：执行此处逻辑，关于product产品的config配置加载</span>
	<span class="k">if</span> <span class="n">what</span><span class="o">&amp;</span><span class="n">RunProductConfig</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
		<span class="n">runMakeProductConfig</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="c">//下面的一切都取决于产品配置</span>

	<span class="k">if</span> <span class="n">inList</span><span class="p">(</span><span class="s">"installclean"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Arguments</span><span class="p">())</span> <span class="o">||</span>
		<span class="n">inList</span><span class="p">(</span><span class="s">"install-clean"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Arguments</span><span class="p">())</span> <span class="p">{</span>
		<span class="n">installClean</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Deleted images and staging directories."</span><span class="p">)</span>
		<span class="k">return</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="n">inList</span><span class="p">(</span><span class="s">"dataclean"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Arguments</span><span class="p">())</span> <span class="o">||</span>
		<span class="n">inList</span><span class="p">(</span><span class="s">"data-clean"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Arguments</span><span class="p">())</span> <span class="p">{</span>
		<span class="n">dataClean</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Deleted data files."</span><span class="p">)</span>
		<span class="k">return</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="n">what</span><span class="o">&amp;</span><span class="n">RunSoong</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
        <span class="c">//Step 2 ：执行runSoong函数，将Android.bp编译成out/soong/build.ninja</span>
		<span class="n">runSoong</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>

		<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">bazelBuildMode</span><span class="p">()</span> <span class="o">==</span> <span class="n">generateBuildFiles</span> <span class="p">{</span>
			<span class="c">// Return early, if we're using Soong as solely the generator of BUILD files.</span>
			<span class="k">return</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="n">what</span><span class="o">&amp;</span><span class="n">RunKati</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
		<span class="n">genKatiSuffix</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
		<span class="n">runKatiCleanSpec</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
        <span class="c">//Step 3：调用kati的构建函数，将Android.mk文件生成ninja文件:out/build-aosp_arm.ninja</span>
		<span class="n">runKatiBuild</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
        <span class="c">//Step 4: 加载build/make/packaging/main.mk，编译生成out/build-aosp_arm-package.ninja</span>
		<span class="n">runKatiPackage</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>

		<span class="n">ioutil</span><span class="o">.</span><span class="n">WriteFile</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">LastKatiSuffixFile</span><span class="p">(),</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">KatiSuffix</span><span class="p">()),</span> <span class="m">0666</span><span class="p">)</span> <span class="c">// a+rw</span>
	<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">what</span><span class="o">&amp;</span><span class="n">RunKatiNinja</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
		<span class="c">// Load last Kati Suffix if it exists</span>
		<span class="k">if</span> <span class="n">katiSuffix</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">ioutil</span><span class="o">.</span><span class="n">ReadFile</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">LastKatiSuffixFile</span><span class="p">());</span> <span class="n">err</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">ctx</span><span class="o">.</span><span class="n">Verboseln</span><span class="p">(</span><span class="s">"Loaded previous kati config:"</span><span class="p">,</span> <span class="kt">string</span><span class="p">(</span><span class="n">katiSuffix</span><span class="p">))</span>
			<span class="n">config</span><span class="o">.</span><span class="n">SetKatiSuffix</span><span class="p">(</span><span class="kt">string</span><span class="p">(</span><span class="n">katiSuffix</span><span class="p">))</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="c">// Write combined ninja file</span>
    <span class="c">// Step 5: 将前面三步生成的.ninja合成为out/combined-AOSP.ninja</span>
	<span class="n">createCombinedBuildNinjaFile</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>

	<span class="n">distGzipFile</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">CombinedNinjaFile</span><span class="p">())</span>

	<span class="k">if</span> <span class="n">what</span><span class="o">&amp;</span><span class="n">RunBuildTests</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
		<span class="n">testForDanglingRules</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="n">what</span><span class="o">&amp;</span><span class="n">RunNinja</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
		<span class="k">if</span> <span class="n">what</span><span class="o">&amp;</span><span class="n">RunKati</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
			<span class="n">installCleanIfNecessary</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
		<span class="p">}</span>
        <span class="c">//Step 6：runNinjaForBuild重点，调用Ninja的构建函数</span>
		<span class="n">runNinjaForBuild</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="c">// Currently, using Bazel requires Kati and Soong to run first, so check whether to run Bazel last.</span>
	<span class="k">if</span> <span class="n">what</span><span class="o">&amp;</span><span class="n">RunBazel</span> <span class="o">!=</span> <span class="m">0</span> <span class="p">{</span>
		<span class="n">runBazel</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="342-build整体调用栈图">3.4.2. Build整体调用栈图</h3>

<p><img src="../../assets/post/2024/2024-02-23-android12_BuildSystem_2\Android编译模块_Build整体调用栈.png" alt="" /></p>

<p><strong>其中最主要的以下六个步骤</strong>：</p>

<ol>
  <li>runMakeProductConfig 主要配置编译参数</li>
  <li>runSoong 对工具进行编译，编译出blueprint等编译工具, 把*.bp 编译成out/soong/build.ninja</li>
  <li>runKatiBuild, 加载 build/make/core/main.mk， 搜集所有的Android.mk文件生成ninja文件:out/build-aosp_arm.ninja</li>
  <li>runKatiPackage, 加载build/make/packaging/main.mk, 编译生成out/build-aosp_arm-package.ninja</li>
  <li>createCombinedBuildNinjaFile,将out/soong/build.ninja 、out/build-aosp_arm.ninja和out/build-aosp_arm-package.ninja， 合成为out/combined-aosp_arm.ninja</li>
  <li>runNinja，运行Ninja命令， 解析combined-aosp_arm.ninja，执行编译过程</li>
</ol>

<hr />

<h4 id="3421-step-1-runmakeproductconfig">3.4.2.1. Step 1: runMakeProductConfig</h4>

<blockquote>
  <p>该函数主要配置编译参数、环境变量，同时打印到终端。</p>

  <p>代码参考：build/soong/ui/build/dumpvars.go</p>
</blockquote>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">runMakeProductConfig</span><span class="p">(</span><span class="n">ctx</span> <span class="n">Context</span><span class="p">,</span> <span class="n">config</span> <span class="n">Config</span><span class="p">)</span> <span class="p">{</span>
	<span class="c">//要导出到Kati/Ninja环境中的变量</span>
	<span class="n">exportEnvVars</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span>
        <span class="c">//这样，如果它被buildspec.mk修改，我们就可以使用正确的TARGET_PRODUCT</span>
		<span class="s">"TARGET_PRODUCT"</span><span class="p">,</span>
		<span class="s">"TARGET_BUILD_VARIANT"</span><span class="p">,</span>
		<span class="s">"TARGET_BUILD_APPS"</span><span class="p">,</span>
		<span class="s">"TARGET_BUILD_UNBUNDLED"</span><span class="p">,</span>
        <span class="c">//make设置的编译器包装器</span>
		<span class="s">"CC_WRAPPER"</span><span class="p">,</span>
		<span class="s">"CXX_WRAPPER"</span><span class="p">,</span>
		<span class="s">"RBE_WRAPPER"</span><span class="p">,</span>
		<span class="s">"JAVAC_WRAPPER"</span><span class="p">,</span>
		<span class="s">"R8_WRAPPER"</span><span class="p">,</span>
		<span class="s">"D8_WRAPPER"</span><span class="p">,</span>
		<span class="c">// ccache settings设置</span>
		<span class="s">"CCACHE_COMPILERCHECK"</span><span class="p">,</span>
		<span class="s">"CCACHE_SLOPPINESS"</span><span class="p">,</span>
		<span class="s">"CCACHE_BASEDIR"</span><span class="p">,</span>
		<span class="s">"CCACHE_CPP2"</span><span class="p">,</span>
	<span class="p">}</span>

	<span class="n">allVars</span> <span class="o">:=</span> <span class="nb">append</span><span class="p">(</span><span class="nb">append</span><span class="p">([]</span><span class="kt">string</span><span class="p">{</span>
		<span class="c">//被用来执行Kati和Ninja</span>
		<span class="s">"NINJA_GOALS"</span><span class="p">,</span>
		<span class="s">"KATI_GOALS"</span><span class="p">,</span>
		<span class="c">//用来查询target/product/&lt;DEVICE&gt;</span>
		<span class="s">"TARGET_DEVICE"</span><span class="p">,</span>
		<span class="c">//这样以后Kati可以更快地找到BoardConfig.mk</span>
		<span class="s">"TARGET_DEVICE_DIR"</span><span class="p">,</span>
        <span class="c">//--werror_overriding_commands是否有效</span>
		<span class="s">"BUILD_BROKEN_DUP_RULES"</span><span class="p">,</span>
        <span class="c">//是否在构建编译期间启用网络</span>
		<span class="s">"BUILD_BROKEN_USES_NETWORK"</span><span class="p">,</span>
		<span class="c">//要导出到ninja的额外环境变量</span>
		<span class="s">"BUILD_BROKEN_NINJA_USES_ENV_VARS"</span><span class="p">,</span>
		<span class="c">//未使用，但是在soong.log中是有需要的</span>
		<span class="s">"BOARD_VNDK_VERSION"</span><span class="p">,</span>
        <span class="o">......</span>
    <span class="c">//BannerVars就是lunch后打印的一些环境变量</span>
	<span class="p">},</span> <span class="n">exportEnvVars</span><span class="o">...</span><span class="p">),</span> <span class="n">BannerVars</span><span class="o">...</span><span class="p">)</span>

    <span class="c">//上面所有的环境变量allVars作为dumpMakeVars的入参</span>
	<span class="n">makeVars</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">dumpMakeVars</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">Arguments</span><span class="p">(),</span> <span class="n">allVars</span><span class="p">,</span> <span class="no">true</span><span class="p">,</span> <span class="s">""</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="s">"Error dumping make vars:"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="n">env</span> <span class="o">:=</span> <span class="n">config</span><span class="o">.</span><span class="n">Environment</span><span class="p">()</span>
	<span class="c">// Print the banner like make does</span>
	<span class="k">if</span> <span class="o">!</span><span class="n">env</span><span class="o">.</span><span class="n">IsEnvTrue</span><span class="p">(</span><span class="s">"ANDROID_QUIET_BUILD"</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Fprintln</span><span class="p">(</span><span class="n">ctx</span><span class="o">.</span><span class="n">Writer</span><span class="p">,</span> <span class="n">Banner</span><span class="p">(</span><span class="n">makeVars</span><span class="p">))</span>
	<span class="p">}</span>

	<span class="c">// 填充环境变量</span>
	<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">name</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">exportEnvVars</span> <span class="p">{</span>
		<span class="k">if</span> <span class="n">makeVars</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span>
			<span class="n">env</span><span class="o">.</span><span class="n">Unset</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
		<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
			<span class="n">env</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">makeVars</span><span class="p">[</span><span class="n">name</span><span class="p">])</span>
		<span class="p">}</span>
	<span class="p">}</span>
    <span class="o">.....</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h4 id="3422-step-2-runsoong">3.4.2.2. Step 2: runSoong</h4>

<blockquote>
  <p>runSoong对工具进行编译，编译出blueprint等编译工具, 把<code class="language-plaintext highlighter-rouge">*.bp</code>编译成<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code></p>

  <p>代码参考：build/soong/ui/build/soong.go</p>
</blockquote>

<p><strong>主要功能是生成soong工具链，其所生成的soong工具链，都放在<code class="language-plaintext highlighter-rouge">out/soong</code>目录下。通过blueprint将所有的Android.bp最终转义为build.ninja</strong></p>

<p><strong>runSoong主要完成如下的工作：</strong></p>

<ul>
  <li>生成Android-<product-name>.mk，将所有的Android.mk汇集起来</product-name></li>
  <li>生成blueprint库</li>
  <li>生成soong工具集</li>
  <li>生成minibp</li>
  <li>将所有的Android.bp最终转义为build.ninja</li>
</ul>

<p><strong>主要的工作：</strong></p>
<ol>
  <li>执行build/blueprint/bootstrap.bash</li>
  <li>执行ninja，输入是out/soong/.minibootstrap/build.ninja</li>
  <li>执行ninja，输入是out/soong/.bootstrap/build.ninja</li>
  <li>使用soong_build，来生成build.ninja</li>
  <li><code class="language-plaintext highlighter-rouge">andorid/out$ vim .ninja_log</code>里面详细记录了ninja的执行日志</li>
</ol>

<h5 id="34221-runsoong调用栈图">3.4.2.2.1. runSoong调用栈图</h5>

<p><strong>这部分详细的还没完全理清，后续单独梳理Blueprint</strong></p>

<p><img src="../../assets/post/2024/2024-02-23-android12_BuildSystem_2\Android编译模块_runSoong调用栈.png" alt="" /></p>

<ol>
  <li>runSoong执行bootstrap.bash和blueprint_impl.bash，最终生成minibp和bpglob进程，建立<code class="language-plaintext highlighter-rouge">/out/soong/.minibootstrap/build.ninja</code> 和<code class="language-plaintext highlighter-rouge">/out/soong/.bootstrap/build.ninja</code>两个文件</li>
  <li>再通过<code class="language-plaintext highlighter-rouge">out/soong/.bootstrap/bin/soong_build</code>， 编译<code class="language-plaintext highlighter-rouge">out/.module_paths/Android.bp.list</code>及<code class="language-plaintext highlighter-rouge">out/soong/.bootstrap/build-globs.ninja</code></li>
  <li>生成<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code>，参与最终的ninja编译</li>
  <li>创建out/soong文件夹</li>
  <li>调用bootstrapBlueprint函数内容：
    <ul>
      <li>在out/soong下面创建文件build.ninja，<code class="language-plaintext highlighter-rouge">.bootstrap/soong-build-globs.ninja</code>，<code class="language-plaintext highlighter-rouge">.bootstrap/build-globs.ninja</code>，<code class="language-plaintext highlighter-rouge">.bootstrap/build.ninja.d</code>，<code class="language-plaintext highlighter-rouge">.bootstrap/build.ninja</code>，<code class="language-plaintext highlighter-rouge">.bootstrap/bp2build_workspace_marker</code>并分别创建对象用于后面使用</li>
      <li>在<code class="language-plaintext highlighter-rouge">out/.module_paths</code>下面创建<code class="language-plaintext highlighter-rouge">Android.bp.list</code>文件用于后面使用</li>
      <li>将输入（Android.bp数组）、输出（out/soong/build.ninja）、参数（soongBuildArgs）存入<code class="language-plaintext highlighter-rouge">build/blueprint/bootstrap/config.go</code>的<code class="language-plaintext highlighter-rouge">PrimaryBuilderInvocation</code>结构体对象中</li>
      <li>调用<code class="language-plaintext highlighter-rouge">build/blueprint/bootstrap/command.go</code>的<code class="language-plaintext highlighter-rouge">RunBlueprint</code>函数，返回发出的Ninja文件所具有的依赖项列表</li>
    </ul>
  </li>
  <li>调用<code class="language-plaintext highlighter-rouge">writeEnvironmentFile</code>将soong构建获取到的环境写入到<code class="language-plaintext highlighter-rouge">out/soong/soong.environment.available</code>文件中</li>
  <li>调用<code class="language-plaintext highlighter-rouge">checkEnvironmentFile</code>将<code class="language-plaintext highlighter-rouge">out/soong/soong.environment.used</code>使用的环境和获取到的环境<code class="language-plaintext highlighter-rouge">out/soong/soong.environment.available</code>进行对比检查</li>
  <li>构建<code class="language-plaintext highlighter-rouge">bpglob</code>，源码<code class="language-plaintext highlighter-rouge">build/blueprint/bootstrap/bpglob/bpglob.go</code>，结果存放在<code class="language-plaintext highlighter-rouge">out/soong/.minibootstrap/</code></li>
  <li>构建<code class="language-plaintext highlighter-rouge">build.ninja</code>，源码<code class="language-plaintext highlighter-rouge">build/blueprint/bootstrap/build.ninja</code>，结果存放在<code class="language-plaintext highlighter-rouge">out/soong/.bootstrap/.bootstrap/build.ninja</code></li>
</ol>

<p>主要构建结果是<code class="language-plaintext highlighter-rouge">out/soong/.bootstrap/bin/soong_build</code>和<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code></p>

<hr />

<h4 id="3423-step-3-runkatibuild">3.4.2.3. Step 3: runKatiBuild</h4>

<p>runKatiBuild主要加载<code class="language-plaintext highlighter-rouge">build/make/core/main.mk</code>，搜集所有的Android.mk文件生成ninja文件：<code class="language-plaintext highlighter-rouge">out/build-&lt;product-name&gt;.ninja</code></p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span> <span class="n">build</span><span class="o">/</span><span class="n">soong</span><span class="o">/</span><span class="n">ui</span><span class="o">/</span><span class="n">build</span><span class="o">/</span><span class="n">kati</span><span class="o">.</span><span class="k">go</span>
<span class="k">func</span> <span class="n">runKatiBuild</span><span class="p">(</span><span class="n">ctx</span> <span class="n">Context</span><span class="p">,</span> <span class="n">config</span> <span class="n">Config</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">ctx</span><span class="o">.</span><span class="n">BeginTrace</span><span class="p">(</span><span class="n">metrics</span><span class="o">.</span><span class="n">RunKati</span><span class="p">,</span> <span class="s">"kati build"</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">ctx</span><span class="o">.</span><span class="n">EndTrace</span><span class="p">()</span>

	<span class="n">args</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span>
		<span class="c">// 输出目录</span>
		<span class="s">"--writable"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">OutDir</span><span class="p">()</span> <span class="o">+</span> <span class="s">"/"</span><span class="p">,</span>
		<span class="s">"--werror_implicit_rules"</span><span class="p">,</span>
		<span class="c">// Kati Ninja文件生成的入口点，主要加载的文件</span>
		<span class="s">"-f"</span><span class="p">,</span> <span class="s">"build/make/core/main.mk"</span><span class="p">,</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="o">!</span><span class="n">config</span><span class="o">.</span><span class="n">BuildBrokenDupRules</span><span class="p">()</span> <span class="p">{</span>
		<span class="c">// Fail when redefining / duplicating a target.</span>
		<span class="n">args</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="s">"--werror_overriding_commands"</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="n">args</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">KatiArgs</span><span class="p">()</span><span class="o">...</span><span class="p">)</span>

	<span class="n">args</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">args</span><span class="p">,</span>
		<span class="c">// 生成的Make-vars.mk文件的位置</span>
        <span class="c">//即out/soong/make_vars-***product.mk</span>
		<span class="s">"SOONG_MAKEVARS_MK="</span><span class="o">+</span><span class="n">config</span><span class="o">.</span><span class="n">SoongMakeVarsMk</span><span class="p">(),</span>
        <span class="c">// 被soong构建的Android.mk文件位置</span>
        <span class="c">//文件包含表示为Kati模块的Soong模块，允许Kati模块依赖于Soong模块</span>
        <span class="c">//即out/soong/Android-***product.mk（所有Android.mk模块总和）</span>
		<span class="s">"SOONG_ANDROID_MK="</span><span class="o">+</span><span class="n">config</span><span class="o">.</span><span class="n">SoongAndroidMk</span><span class="p">(),</span>
        <span class="c">//含目标设备输出的目录，即TARGET_DEVICE_DIR宏的值</span>
		<span class="s">"TARGET_DEVICE_DIR="</span><span class="o">+</span><span class="n">config</span><span class="o">.</span><span class="n">TargetDeviceDir</span><span class="p">(),</span>
        <span class="c">//包含用于打包目的的.mk文件的目录，例如dist.mk文件，其中包含目标数据的dist</span>
        <span class="c">//out/target/product/***product/obj/CONFIG/kati_packaging目录</span>
		<span class="s">"KATI_PACKAGE_MK_DIR="</span><span class="o">+</span><span class="n">config</span><span class="o">.</span><span class="n">KatiPackageMkDir</span><span class="p">())</span>
    <span class="c">//核心函数：执行runKati函数，入参</span>
    <span class="c">//用于构造和运行带有附加参数的Kati命令行的基本函数，以及用于更改Kati运行环境的自定义函数闭包</span>
	<span class="n">runKati</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">katiBuildSuffix</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">env</span> <span class="o">*</span><span class="n">Environment</span><span class="p">)</span> <span class="p">{})</span>
    <span class="c">//压缩和dist主构建ninja文件</span>
    <span class="c">//此处KatiBuildNinjaFile就是out/build-***product.ninja(比如build-aosp_arm.ninja)</span>
    <span class="c">//build/soong/ui/build/kati.go的genKatiSuffix中设置，build-TARGET_PRODUCT.ninja</span>
    <span class="n">distGzipFile</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">KatiBuildNinjaFile</span><span class="p">())</span>
    <span class="o">......</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">runKati</span><span class="p">(</span><span class="n">ctx</span> <span class="n">Context</span><span class="p">,</span> <span class="n">config</span> <span class="n">Config</span><span class="p">,</span> <span class="n">extraSuffix</span> <span class="kt">string</span><span class="p">,</span> <span class="n">args</span> <span class="p">[]</span><span class="kt">string</span><span class="p">,</span> <span class="n">envFunc</span> <span class="k">func</span><span class="p">(</span><span class="o">*</span><span class="n">Environment</span><span class="p">))</span> <span class="p">{</span>
    <span class="c">//prebuilts/build-tools路径，见build/soong/ui/build/config.go</span>
	<span class="n">executable</span> <span class="o">:=</span> <span class="n">config</span><span class="o">.</span><span class="n">PrebuiltBuildTool</span><span class="p">(</span><span class="s">"ckati"</span><span class="p">)</span>
    <span class="o">...</span>
    <span class="n">cmd</span> <span class="o">:=</span> <span class="n">Command</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="s">"ckati"</span><span class="p">,</span> <span class="n">executable</span><span class="p">,</span> <span class="n">args</span><span class="o">...</span><span class="p">)</span>
    <span class="o">...</span>
    <span class="c">//调用build/soong/ui/build/exec.go的StartOrFatal函数执行命令</span>
    <span class="n">cmd</span><span class="o">.</span><span class="n">StartOrFatal</span><span class="p">()</span>
	<span class="c">// Set up the ToolStatus command line reader for Kati for a consistent UI</span>
	<span class="c">// for the user.</span>
	<span class="n">status</span><span class="o">.</span><span class="n">KatiReader</span><span class="p">(</span><span class="n">ctx</span><span class="o">.</span><span class="n">Status</span><span class="o">.</span><span class="n">StartTool</span><span class="p">(),</span> <span class="n">pipe</span><span class="p">)</span>
	<span class="n">cmd</span><span class="o">.</span><span class="n">WaitOrFatal</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>runKati工作内容：</strong></p>
<ol>
  <li>直接调用预编译<code class="language-plaintext highlighter-rouge">prebuilts/build-tools/linux-x86/bin/ckati</code>工具（从Android 11开始，不再释放源码，而是直接作为prebuilts预编译资源，Android 10之前在android/build/kati）</li>
  <li>使用ckati工具，以及传入的一系列参数，其中比较重要的参数有<code class="language-plaintext highlighter-rouge">"--ninja"</code>和<code class="language-plaintext highlighter-rouge">"-f", "build/make/core/main.mk"</code>，编译出<code class="language-plaintext highlighter-rouge">out/build-&lt;product-name&gt;.ninja</code></li>
</ol>

<hr />

<h4 id="3424-step-4-runkatipackage">3.4.2.4. Step 4: runKatiPackage</h4>

<p>runKatiPackage, 加载<code class="language-plaintext highlighter-rouge">build/make/packaging/main.mk</code>, 编译生成<code class="language-plaintext highlighter-rouge">out/build-&lt;product-name&gt;-package.ninja</code></p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">//build/soong/ui/build/kati.go</span>
<span class="c">//生成包含dist-dir的打包命令行的Ninja文件</span>
<span class="k">func</span> <span class="n">runKatiPackage</span><span class="p">(</span><span class="n">ctx</span> <span class="n">Context</span><span class="p">,</span> <span class="n">config</span> <span class="n">Config</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">ctx</span><span class="o">.</span><span class="n">BeginTrace</span><span class="p">(</span><span class="n">metrics</span><span class="o">.</span><span class="n">RunKati</span><span class="p">,</span> <span class="s">"kati package"</span><span class="p">)</span>
	<span class="k">defer</span> <span class="n">ctx</span><span class="o">.</span><span class="n">EndTrace</span><span class="p">()</span>

	<span class="n">args</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span>
		<span class="c">// Mark the dist dir as writable.</span>
		<span class="s">"--writable"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">DistDir</span><span class="p">()</span> <span class="o">+</span> <span class="s">"/"</span><span class="p">,</span>
		<span class="c">// Fail when encountering implicit rules. e.g.</span>
		<span class="s">"--werror_implicit_rules"</span><span class="p">,</span>
		<span class="c">// Fail when redefining / duplicating a target.</span>
		<span class="s">"--werror_overriding_commands"</span><span class="p">,</span>
		<span class="c">// Entry point.</span>
		<span class="s">"-f"</span><span class="p">,</span> <span class="s">"build/make/packaging/main.mk"</span><span class="p">,</span>
		<span class="c">// Directory containing .mk files for packaging purposes, such as</span>
		<span class="c">// the dist.mk file, containing dist-for-goals data.</span>
		<span class="s">"KATI_PACKAGE_MK_DIR="</span> <span class="o">+</span> <span class="n">config</span><span class="o">.</span><span class="n">KatiPackageMkDir</span><span class="p">(),</span>
	<span class="p">}</span>

    <span class="c">//针对一组受限制的环境变量运行Kati</span>
	<span class="n">runKati</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">katiPackageSuffix</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="k">func</span><span class="p">(</span><span class="n">env</span> <span class="o">*</span><span class="n">Environment</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">env</span><span class="o">.</span><span class="n">Allow</span><span class="p">([]</span><span class="kt">string</span><span class="p">{</span>
			<span class="c">// Some generic basics</span>
			<span class="s">"LANG"</span><span class="p">,</span>
			<span class="s">"LC_MESSAGES"</span><span class="p">,</span>
			<span class="s">"PATH"</span><span class="p">,</span>
			<span class="s">"PWD"</span><span class="p">,</span>
			<span class="s">"TMPDIR"</span><span class="p">,</span>

			<span class="c">// Tool configs</span>
			<span class="s">"ASAN_SYMBOLIZER_PATH"</span><span class="p">,</span>
			<span class="s">"JAVA_HOME"</span><span class="p">,</span>
			<span class="s">"PYTHONDONTWRITEBYTECODE"</span><span class="p">,</span>

			<span class="c">// Build configuration</span>
			<span class="s">"ANDROID_BUILD_SHELL"</span><span class="p">,</span>
			<span class="s">"DIST_DIR"</span><span class="p">,</span>
			<span class="s">"OUT_DIR"</span><span class="p">,</span>
		<span class="p">}</span><span class="o">...</span><span class="p">)</span>

		<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">Dist</span><span class="p">()</span> <span class="p">{</span>
			<span class="n">env</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"DIST"</span><span class="p">,</span> <span class="s">"true"</span><span class="p">)</span>
			<span class="n">env</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"DIST_DIR"</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">DistDir</span><span class="p">())</span>
		<span class="p">}</span>
	<span class="p">})</span>

    <span class="c">//压缩和dist主构建ninja文件</span>
    <span class="c">//此处KatiBuildNinjaFile就是out/build-***product-package.ninja(比如build-aosp_arm-package.ninja)</span>
    <span class="c">//build/soong/ui/build/kati.go的genKatiSuffix中设置，build-TARGET_PRODUCT-package.ninja</span>
	<span class="n">distGzipFile</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">config</span><span class="o">.</span><span class="n">KatiPackageNinjaFile</span><span class="p">())</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h4 id="3425-step-5-createcombinedbuildninjafile">3.4.2.5. Step 5: createCombinedBuildNinjaFile</h4>

<p>将<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code> 、<code class="language-plaintext highlighter-rouge">out/build-&lt;product-name&gt;.ninja</code>和<code class="language-plaintext highlighter-rouge">out/build-&lt;product-name&gt;-package.ninja</code>， 合成为<code class="language-plaintext highlighter-rouge">out/out/combined-&lt;product-name&gt;.ninja</code></p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">//build/soong/ui/build/build.go</span>
<span class="k">func</span> <span class="n">createCombinedBuildNinjaFile</span><span class="p">(</span><span class="n">ctx</span> <span class="n">Context</span><span class="p">,</span> <span class="n">config</span> <span class="n">Config</span><span class="p">)</span> <span class="p">{</span>
    <span class="c">//如果我们处于SkipKati模式，但想运行kati，请跳过创建此文件（如果它已经存在）</span>
	<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">SkipKati</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">config</span><span class="o">.</span><span class="n">SkipKatiNinja</span><span class="p">()</span> <span class="p">{</span>
		<span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Stat</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">CombinedNinjaFile</span><span class="p">());</span> <span class="n">err</span> <span class="o">==</span> <span class="no">nil</span> <span class="o">||</span> <span class="o">!</span><span class="n">os</span><span class="o">.</span><span class="n">IsNotExist</span><span class="p">(</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">return</span>
		<span class="p">}</span>
	<span class="p">}</span>
    <span class="c">//创建combined-***product.ninja</span>
	<span class="n">file</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Create</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">CombinedNinjaFile</span><span class="p">())</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="s">"Failed to create combined ninja file:"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="k">defer</span> <span class="n">file</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
    <span class="c">//combinedBuildNinjaTemplate并运行，将KatiBuildNinjaFile、KatiPackageNinjaFile、SoongNinjaFile写入进文件</span>
    <span class="c">//这三个分别就是：</span>
    <span class="c">//1.out/build-aosp_arm.ninja</span>
    <span class="c">//2.out/build-aosp_arm-package.ninja</span>
    <span class="c">//3.out/soong/build.ninja</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">combinedBuildNinjaTemplate</span><span class="o">.</span><span class="n">Execute</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="n">config</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">ctx</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="s">"Failed to write combined ninja file:"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h4 id="3426-step-6-runninjaforbuild">3.4.2.6. Step 6: runNinjaForBuild</h4>

<p>运行Ninja命令（代码入口：<code class="language-plaintext highlighter-rouge">build/soong/ui/build/ninja.go</code>）， 解析上面合成的最终的<code class="language-plaintext highlighter-rouge">out/combined-&lt;product-name&gt;.ninja</code>，执行编译过程</p>

<p><strong>文件内容示例如下：</strong></p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">builddir</span> <span class="o">=</span> <span class="n">out</span>
<span class="n">pool</span> <span class="n">local_pool</span>
 <span class="n">depth</span> <span class="o">=</span> <span class="m">42</span>
<span class="n">build</span> <span class="n">_kati_always_build_</span><span class="o">:</span> <span class="n">phony</span>
<span class="n">subninja</span> <span class="n">out</span><span class="o">/</span><span class="n">build</span><span class="o">-</span><span class="n">aosp_arm</span><span class="o">.</span><span class="n">ninja</span>
<span class="n">subninja</span> <span class="n">out</span><span class="o">/</span><span class="n">build</span><span class="o">-</span><span class="n">aosp_arm</span><span class="o">-</span><span class="k">package</span><span class="o">.</span><span class="n">ninja</span>
<span class="n">subninja</span> <span class="n">out</span><span class="o">/</span><span class="n">soong</span><span class="o">/</span><span class="n">build</span><span class="o">.</span><span class="n">ninja</span>
</code></pre></div></div>

<p><strong>PS</strong>: Android 10是<code class="language-plaintext highlighter-rouge">runNinja</code>函数，在Android 12该函数改名为<code class="language-plaintext highlighter-rouge">runNinjaForBuild</code>，该函数开始使用ninja进行完整的编译过程</p>

<p>该函数的执行方式类似<code class="language-plaintext highlighter-rouge">runKatiBuild</code>，直接调用预编译<code class="language-plaintext highlighter-rouge">prebuilts/build-tools</code>下面的<code class="language-plaintext highlighter-rouge">ninja</code>工具</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>android/prebuilts<span class="nv">$ </span>find <span class="nt">-name</span> ninja
./build-tools/linux-x86/asan/bin/ninja
./build-tools/linux-x86/bin/ninja
./build-tools/darwin-x86/bin/ninja
</code></pre></div></div>

<hr />

<h2 id="35-核心文件buildcoremainmk">3.5. 核心文件build/core/main.mk</h2>

<blockquote>
  <p>在Android源码的根目录有一个Makefile文件，有效内容只有一行：<code class="language-plaintext highlighter-rouge">include build/core/main.mk</code>，但是在Android 12中，该文件不是由此处加载，而是在上面<code class="language-plaintext highlighter-rouge">soong-ui-build</code>构建到<code class="language-plaintext highlighter-rouge">build/soong/ui/build/kati.go</code>的<code class="language-plaintext highlighter-rouge">runKatiBuild</code>函数中引入加载。</p>
</blockquote>

<blockquote>
  <p>所有的编译规则都定义在<code class="language-plaintext highlighter-rouge">build/core/main.mk</code>文件中，最终所有的Makefile片段都将汇聚在这一个文件中。</p>
</blockquote>

<blockquote>
  <p>main.mk文件是Android Build系统的主控文件。从main.mk开始，将通过include命令将其所有需要的.mk文件包含进来，最终在内存中形成一个包括所有编译脚本的集合，这个相当于一个巨大Makefile文件。Makefile文件看上去很庞大，其实主要由三种内容构成: 变量定义、函数定义和目标依赖规则，此外mk文件之间的包含也很重要。</p>
</blockquote>

<hr />

<h3 id="351-makefile基本语法了解">3.5.1. Makefile基本语法了解</h3>

<blockquote>
  <p>参考：<a href="https://betheme.net/xiaochengxu/126408.html?action=onClick">【Android构建篇】MakeFile语法</a>
在阅读理解核心文件<code class="language-plaintext highlighter-rouge">build/core/main.mk</code>之前，先了解下Makefile的基本语法。
Make是最常用的构建工具，诞生于1977年，主要用于C语言的项目。但是实际上，任何只要某个文件有变化，就要重新构建的项目，都可以用Make构建。</p>
</blockquote>

<ul>
  <li>编译（compile）：代码变成可执行文件</li>
  <li>构建（build）：先编译这个，还是先编译那个呢？（即编译的安排）比如Android Studio的Gradle就是一个代码构建工具。</li>
</ul>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 规则语法                                  # 示例
</span><span class="nl">TARGET... </span><span class="o">:</span> <span class="nf">PREREQUISITES...               main.o: main.c main.h</span>
    <span class="err">COMMAND</span> <span class="err">1</span>                                  <span class="err">gcc</span> <span class="err">-c</span> <span class="err">main.c</span>
    <span class="err">...</span>                                        <span class="err">...</span>
    <span class="err">COMMAND</span> <span class="err">N</span>                                  <span class="err">echo</span> <span class="s2">"Compile finished"</span>
</code></pre></div></div>

<ul>
  <li>TARGET： 表示当前规则要达成的目标，通常为最终生成的文件，或者为了最终生产的文件而产生的中间文件。譬如示例中的main.o就是一个中间文件</li>
  <li>PREREQUISITES： 表示当前规则所依赖的前提条件，只有前提条件满足了，才能够生成目标。这些前提条件通常为另外的规则所定义的目标，这就决定了编译顺序，只有优先编译完所依赖的目标，才能编译当前规则所定义的目标。譬如示例中所依赖的的 main.c 和 main.h 两个文件都必须存在，才能开始编译main.o</li>
  <li>COMMAND：<strong>必须由一个tab键起首，后面跟着命令</strong>。表示当前规则的执行命令。一个规则下，可以有多条命令。当所依赖的条件满足后，就会依次执行这些命令序列，譬如调用gcc、文件拷贝、输出日志等。</li>
</ul>

<p><strong>PS：</strong>“目标”是必需的，不可省略；”前置条件”和”命令”都是可选的，但是两者之中必须至少存在一个。</p>

<hr />

<h4 id="3511-目标target">3.5.1.1. 目标（target）</h4>

<blockquote>
  <p>一个目标（target）就构成一条规则。目标通常是文件名，指明Make命令所要构建的对象。目标可以是一个文件名，也可以是多个文件名，之间用空格分隔。</p>
</blockquote>

<blockquote>
  <p>除了文件名，目标还可以是某个操作的名字，这称为<code class="language-plaintext highlighter-rouge">伪目标（phony target）</code></p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">clean:rm *.o</code>：此处代码的target目标是clean，它不是文件名，而是一个操作的名字，属于”伪目标”。这句代码作用是删除对象文件<code class="language-plaintext highlighter-rouge">rm *.o</code>，执行方式：<code class="language-plaintext highlighter-rouge">make clean</code></p>

<p>但是，如果当前目录中，正好有一个文件叫做clean，那么这个命令不会执行。因为Make发现clean文件已经存在，就认为没有必要重新构建了，就不会执行指定的<code class="language-plaintext highlighter-rouge">rm *.o</code>命令。</p>

<p><strong>所以为了避免这种情况，可以明确声明clean是”伪目标”，写法如下：</strong></p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">clean</span>
<span class="nl">clean</span><span class="o">:</span><span class="nf">rm *.o temp</span>
</code></pre></div></div>

<p>声明clean是”伪目标”之后，make就不会去检查是否存在一个叫做clean的文件，而是每次运行都执行对应的命令。像<code class="language-plaintext highlighter-rouge">.PHONY</code>这样的内置目标名还有不少，可以查看手册。</p>

<p>如果Make命令运行时没有指定目标，默认会执行Makefile文件的第一个目标。</p>

<p><strong>反推之，可以通过<code class="language-plaintext highlighter-rouge">make 目标名</code>编译某个目标。</strong></p>

<h5 id="35111-phony伪目标">3.5.1.1.1. .PHONY伪目标</h5>

<p>有一个在 Makefile 文件中常见的关键字，那就是<code class="language-plaintext highlighter-rouge">.PHONY</code></p>

<p><code class="language-plaintext highlighter-rouge">.PHONY</code>是一个特殊的目标（target），用于声明一组“伪目标”，这些伪目标不代表真正的文件或者动作，而是代表需要运行的操作或任务名称。</p>

<p>例如再main.mk中定义了伪目标<code class="language-plaintext highlighter-rouge">vendorimage</code>，可以通过<code class="language-plaintext highlighter-rouge">make vendorimage</code>编译运行:</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># build/core/main.mk
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">vendorimage</span>
<span class="nl">vendorimage</span><span class="o">:</span> <span class="nf">$(INSTALLED_VENDORIMAGE_TARGET)</span>
</code></pre></div></div>

<p>需要注意的是，定义了伪目标后，并不会检查对应的文件是否存在或是否需要更新。相反，Makefile将直接执行对应的命令，而不管是否已经有对应的文件存在。因此，定义伪目标需要通过其他方式确保其正确执行，例如在规则中加入必要的依赖项或者命令验证。</p>

<p>总之，<code class="language-plaintext highlighter-rouge">.PHONY</code>关键字是用于定义伪目标的常用技巧，可以让Makefile文件更加清晰和易于维护。</p>

<h5 id="35112-buildcoremainmk中的伪目标定义">3.5.1.1.2. build/core/main.mk中的伪目标定义</h5>

<blockquote>
  <p>在<code class="language-plaintext highlighter-rouge">build/core/main.mk</code>中就定义了很多伪目标，我们可以直接通过<code class="language-plaintext highlighter-rouge">make 目标名称</code>进行编译，镜像的生成定义也在该文件中。</p>
</blockquote>

<p>下面列举其中重要的一些：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">伪目标名称</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">.PHONY: droid</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">droid</code>目标将编译出整个系统的镜像，默认目标，<code class="language-plaintext highlighter-rouge">make droid</code>同<code class="language-plaintext highlighter-rouge">make</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: droid_targets</td>
      <td style="text-align: left">编译target</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: clean-dex-files</td>
      <td style="text-align: left">清除所有dex文件（Android系统的可执行文件，包含应用程序的全部操作指令以及运行时数据）</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: files</td>
      <td style="text-align: left">（1）执行<code class="language-plaintext highlighter-rouge">$(modules_to_install)</code>，包含了当前配置下所有会被安装的模块（一个模块是否会被安装依赖于该产品的配置文件，模块的标签等信息），因此该目标将导致所有会被安装的模块的编译</td>
    </tr>
    <tr>
      <td style="text-align: left">同上</td>
      <td style="text-align: left">（2）执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_ANDROID_INFO_TXT_TARGET)</code>，生成一个关于当前Build配置的设备信息的文件，该文件的生成路径是<code class="language-plaintext highlighter-rouge">out/target/product/product_name&gt;/android-info.txt</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: checkbuild</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(modules_to_check)</code>，用来确保我们定义的构建模块是没有冗余的</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: ramdisk</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_RAMDISK_TARGET)</code>，生成ramdisk.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: ramdisk_debug</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_DEBUG_RAMDISK_TARGET)</code>，生成ramdisk-debug.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: ramdisk_test_harness</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_TEST_HARNESS_RAMDISK_TARGET)</code>，生成ramdisk-test-harness.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: userdataimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_USERDATAIMAGE_TARGET)</code>，生成userdata.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: cacheimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_CACHEIMAGE_TARGET)</code>，生成cache.img（但是A/B系统不包含recovery.img和cache.img，即Android O及之后）</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendorimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VENDORIMAGE_TARGET)</code>，生成vendor.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendorbootimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VENDOR_BOOTIMAGE_TARGET)</code>，生成vendor_boot.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendorbootimage_debug</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VENDOR_DEBUG_BOOTIMAGE_TARGET)</code>，生成vendor_boot-debug.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendorbootimage_test_harness</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VENDOR_TEST_HARNESS_BOOTIMAGE_TARGET)</code>，生成vendor_boot-test-harness.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: systemextimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_SYSTEM_EXTIMAGE_TARGET)</code>，生成system_ext.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: odmimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_ODMIMAGE_TARGET)</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: superimage_empty</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_SUPERIMAGE_EMPTY_TARGET)</code>，生成super_empty.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: bootimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_BOOTIMAGE_TARGET)</code>，生成boot.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vbmetaimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_VBMETAIMAGE_TARGET)</code>，生成vbmeta.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: droidcore-unbundled</td>
      <td style="text-align: left">执行完整系统构建所需的目标子集，包含上面一系列的<code class="language-plaintext highlighter-rouge"> $(INSTALLED_***)</code>，<strong>其中重要的是定义了<code class="language-plaintext highlighter-rouge">$(INSTALLED_SYSTEMIMAGE_TARGET)</code>，生成system.img</strong></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: droidcore</td>
      <td style="text-align: left">包含上面的droidcore-unbundled，构建一系列镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: dist_files</td>
      <td style="text-align: left">该目标用来拷贝文件到<code class="language-plaintext highlighter-rouge">/out/dist</code>目录</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: apps_only</td>
      <td style="text-align: left">该目标将编译出当前配置下不包含user，userdebug，eng标签的应用程序</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: sdk win_sdk winsdk-tools sdk_addon</td>
      <td style="text-align: left">编译一系列sdk工具</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: modules</td>
      <td style="text-align: left">从ALL_MODULE_NAME_TAGS中获取，所有可获得的子模块</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: ndk</td>
      <td style="text-align: left">ndk工具</td>
    </tr>
  </tbody>
</table>

<h5 id="35113-buildcoremakefile中的伪目标定义">3.5.1.1.3. build/core/Makefile中的伪目标定义</h5>

<p>Makefile中也定义了很多伪目标，列举一些重要的：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">伪目标名称</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">.PHONY: check-elf-prebuilt-product-copy-files</td>
      <td style="text-align: left">检查PRODUCT_COPY_FILES</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: apkcerts-list</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(APKCERTS_FILE)</code>，所有package的签名密钥输出，如果为某个PACKAGE设置了key，那么<code class="language-plaintext highlighter-rouge">$(PACKAGES.$(p).EXTERNAL_KEY</code>将为true。输出位于<code class="language-plaintext highlighter-rouge">out/target/product/find5/obj/PACKAGING/apkcerts_intermediates/</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: systemimage</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INSTALLED_SYSTEMIMAGE_TARGET)</code>，生成system.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: event-log-tags</td>
      <td style="text-align: left">生成out/target/common/obj/all-event-log-tags.txt</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: notice_files</td>
      <td style="text-align: left">生成notice_files文件</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: recoveryimage</td>
      <td style="text-align: left">A/B系统不包含recovery.img和cache.img，即Android O及之后</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: installed-file-list</td>
      <td style="text-align: left">依赖目标<code class="language-plaintext highlighter-rouge">$(INSTALLED_FILES_FILE)</code>，最终生成<code class="language-plaintext highlighter-rouge">out/target/product/i9100/installed-files.txt</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: systemimage-nodeps snod</td>
      <td style="text-align: left">和伪目标snod一起使用,如果目标里同时含有伪目标systemimage-nodeps和snod，那么将不做全编译，直接将现有的system目录下的文件打包生成<code class="language-plaintext highlighter-rouge">system.img</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: sync syncsys</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INTERNAL_SYSTEMIMAGE_FILES)</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: platform</td>
      <td style="text-align: left">已过时</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: platform-java</td>
      <td style="text-align: left">已过时</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: userdataimage-nodeps</td>
      <td style="text-align: left">依赖于<code class="language-plaintext highlighter-rouge">$(INSTALLED_USERDATAIMAGE_TARGET)</code>，生成userdata.img</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendorimage-nodeps vnod</td>
      <td style="text-align: left">同上面systemimage-nodeps snod，直接将现有的vendor目录下的文件打包生成<code class="language-plaintext highlighter-rouge">vendor.img</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: systemextimage-nodeps senod</td>
      <td style="text-align: left">同上</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: odmimage-nodeps onod</td>
      <td style="text-align: left">同上</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vendor_dlkmimage-nodeps vdnod</td>
      <td style="text-align: left">同上</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: odm_dlkmimage-nodeps odnod</td>
      <td style="text-align: left">同上</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: vbmetaimage-nodeps</td>
      <td style="text-align: left">同上</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: check-vintf-all</td>
      <td style="text-align: left">检查当前版本的所有VINTF</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: check-all-partition-sizes</td>
      <td style="text-align: left">检查所有分区大小</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: check-all-partition-sizes-nodeps</td>
      <td style="text-align: left">检查所有分区大小，通过<code class="language-plaintext highlighter-rouge">out/target/product/&lt;product-name&gt;/misc_info.txt</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: otatools</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(INTERNAL_OTATOOLS_FILES)</code>，生成ota包需要的工具集合</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: otatools-package</td>
      <td style="text-align: left">执行<code class="language-plaintext highlighter-rouge">$(BUILT_OTATOOLS_PACKAGE)</code>，生成ota包的工具包</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: misc_info</td>
      <td style="text-align: left">通过<code class="language-plaintext highlighter-rouge">$(INSTALLED_MISC_INFO_TARGET)</code>，生成<code class="language-plaintext highlighter-rouge">out/target/product/&lt;product-name&gt;/misc_info.txt</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: target-files-package</td>
      <td style="text-align: left">通过<code class="language-plaintext highlighter-rouge">$(BUILT_TARGET_FILES_PACKAGE)</code>，生成target包（可用于制作差分升级包）</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: otapackage</td>
      <td style="text-align: left">通过<code class="language-plaintext highlighter-rouge">$(INTERNAL_OTA_PACKAGE_TARGET)</code>，生成升级包</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: otardppackage</td>
      <td style="text-align: left">通过<code class="language-plaintext highlighter-rouge">$(INTERNAL_OTA_RETROFIT_DYNAMIC_PARTITIONS_PACKAGE_TARGET)</code>，用于动态分区升级，生成<code class="language-plaintext highlighter-rouge">$(product_name)-ota-retrofit-$(FILE_NAME_TAG).zip</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: partialotapackage</td>
      <td style="text-align: left">通过<code class="language-plaintext highlighter-rouge">$(INTERNAL_OTA_PARTIAL_PACKAGE_TARGET)</code>，生成<code class="language-plaintext highlighter-rouge">$(product_name)-partial-ota-$(FILE_NAME_TAG).zip</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: superimage_dist</td>
      <td style="text-align: left">通过<code class="language-plaintext highlighter-rouge">$(INTERNAL_SUPERIMAGE_DIST_TARGET)</code>，对于dist builds，从target文件到一个中间目录，构建super镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: superimage</td>
      <td style="text-align: left">通过<code class="language-plaintext highlighter-rouge">$(INSTALLED_SUPERIMAGE_TARGET)</code>，构建super.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: superimage-nodeps supernod</td>
      <td style="text-align: left">构建不带依赖项的<code class="language-plaintext highlighter-rouge">$（PRODUCT_OUT）/super.img</code></td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: updatepackage</td>
      <td style="text-align: left">依赖<code class="language-plaintext highlighter-rouge">$(INTERNAL_UPDATE_PACKAGE_TARGET)</code>，生成fastboot刷机包，示例：<code class="language-plaintext highlighter-rouge">out/target/product/AOSPProduct/AOSPProduct-img-eng.cloud.zip</code>，解压里面包含的都是img文件，一般用<code class="language-plaintext highlighter-rouge">fastboot flash zip</code>包的方式更新ROM</td>
    </tr>
    <tr>
      <td style="text-align: left">.PHONY: dalvikfiles</td>
      <td style="text-align: left">依赖于<code class="language-plaintext highlighter-rouge">$(INTERNAL_DALVIK_MODULES)</code>，即dalvik的所有模块</td>
    </tr>
  </tbody>
</table>

<hr />

<h4 id="3512-前置条件prerequisites">3.5.1.2. 前置条件（prerequisites）</h4>

<blockquote>
  <p>前置条件通常是一组文件名，之间用空格分隔。它指定了”目标”是否重新构建的判断标准：只要有一个前置文件不存在，或者有过更新（前置文件的last-modification时间戳比目标的时间戳新），”目标”就需要重新构建。</p>
</blockquote>

<p>例如：</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">result.txt: source.txt cp source.txt result.txt</code>含义：构建<code class="language-plaintext highlighter-rouge">result.txt</code>的前置条件是<code class="language-plaintext highlighter-rouge">source.txt</code>。如果当前目录中，<code class="language-plaintext highlighter-rouge">source.txt</code>已经存在，那么<code class="language-plaintext highlighter-rouge">make result.txt</code>可以正常运行，否则必须再写一条规则，来生成<code class="language-plaintext highlighter-rouge">source.txt</code></li>
  <li><code class="language-plaintext highlighter-rouge">source.txt:echo "this is the source" &gt; source.txt</code>含义：此处source.txt后面没有前置条件，就意味着它跟其他文件都无关，只要这个文件还不存在，每次调用<code class="language-plaintext highlighter-rouge">make source.txt</code>，它都会生成。</li>
</ol>

<hr />

<h4 id="3513-命令commands">3.5.1.3. 命令（commands）</h4>

<blockquote>
  <p>命令（commands）表示如何更新目标文件，由一行或多行的Shell命令组成。它是构建”目标”的具体指令，它的运行结果通常就是生成目标文件。
每行命令之前必须有一个tab键。如果想用其他键，可以用内置变量<code class="language-plaintext highlighter-rouge">.RECIPEPREFIX</code>声明。</p>
</blockquote>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">.RECIPEPREFIX</span> <span class="o">=</span> <span class="o">&gt;</span>
<span class="nl">all</span><span class="o">:</span>
<span class="err">&gt;</span> <span class="err">echo</span> <span class="err">Hello,</span> <span class="err">world</span>
</code></pre></div></div>

<p>上面代码用<code class="language-plaintext highlighter-rouge">.RECIPEPREFIX</code>指定，大于号（&gt;）替代tab键。所以，每一行命令的起首变成了大于号，而不是tab键。</p>

<p>在Android中该命令很少使用，基本伪目标都是使用<code class="language-plaintext highlighter-rouge">.PHONY</code>，然后通过定义<code class="language-plaintext highlighter-rouge">$(Name)</code>变量作为前置条件，然后针对该变量进行一系列操作。</p>

<hr />

<h3 id="352-mainmk思维导图">3.5.2. main.mk思维导图</h3>

<p><strong>对于main.mk，主要做了以下事情</strong>：</p>

<p><img src="../../assets/post/2024/2024-02-23-android12_BuildSystem_2/mainMakefile.jpg" alt="" /></p>

<hr />

<h3 id="353-定义编译目标product">3.5.3. 定义编译目标product</h3>

<p>文件开头确认默认的编译模块是droid</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /build/core/main.mk
</span><span class="k">ifndef</span> <span class="nv">KATI</span>
<span class="c"># 不再支持直接调用make
</span><span class="err">$(warning</span> <span class="err">Calling</span> <span class="err">make</span> <span class="err">directly</span> <span class="err">is</span> <span class="err">no</span> <span class="err">longer</span> <span class="err">supported.)</span>
<span class="err">$(warning</span> <span class="err">Either</span> <span class="err">use</span> <span class="s1">'envsetup.sh; m'</span> <span class="err">or</span> <span class="s1">'build/soong/soong_ui.bash --make-mode'</span><span class="err">)</span>
<span class="err">$(error</span> <span class="err">done)</span>
<span class="k">endif</span>

<span class="err">$(info</span> <span class="err">[1/1]</span> <span class="err">initializing</span> <span class="err">build</span> <span class="err">system</span> <span class="err">...)</span>
<span class="c">#当前工作目录的绝对路径
#这会覆盖shell变量$PWD，该变量不一定指向
#源树的顶部，例如在m/mm/mmm中使用“make-C”时
</span><span class="nv">PWD</span> <span class="o">:=</span> <span class="nf">$(</span><span class="nb">shell</span> <span class="nb">pwd</span><span class="nf">)</span>

<span class="c"># This is the default target.  It must be the first declared target.
# 默认伪目标
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">droid</span>
<span class="nv">DEFAULT_GOAL</span> <span class="o">:=</span> droid
<span class="nl">$(DEFAULT_GOAL)</span><span class="o">:</span> <span class="nf">droid_targets</span>

<span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">droid_targets</span>
<span class="nl">droid_targets</span><span class="o">:</span>
<span class="err">......</span>
</code></pre></div></div>

<hr />

<h3 id="354-加载buildmakecoreconfigmk">3.5.4. 加载build/make/core/config.mk</h3>

<blockquote>
  <p>加载config.mk来初始化相关变量，检测编译环境和目标环境,加载clang/config.mk，配置一些编译的环境</p>
</blockquote>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 根据配置和主机信息设置各种标准变量
</span><span class="k">include</span><span class="sx"> build/make/core/config.mk</span>
<span class="c"># 加载out/soong/make_vars-aosp_arm.mk
</span><span class="k">include</span><span class="sx"> $(SOONG_MAKEVARS_MK)</span>
<span class="nv">YACC</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BISON)</span> <span class="nt">-d</span>
<span class="c"># 加载clang编译的一些配置
</span><span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/clang/config.mk</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">build/make/core/config.mk</code>主要内容：</p>

<blockquote>
  <p>根据当前配置和平台设置标准变量，而这些变量并不是正在构建的特定变量</p>
</blockquote>

<p><strong>按顺序依次完成以下工作：</strong></p>
<ul>
  <li>加载build/make/common/core.mk：仅使用ANDROID_BUILD_SHELL包装bash（不要使用其他shell比如zsh）</li>
  <li>加载build/make/core/distdir.mk：帮助将重要文件复制到DIST_DIR的规则和功能</li>
  <li>将soong_ui中应作为环境变量的变量标记为只读</li>
  <li>将一些变量标记为弃用/废弃</li>
  <li>设置make中使用的高效数学函数。因为这个文件包含在envsetup中，也包含在构建过程中。其中加载的mk文件有：</li>
  <li>
    <ul>
      <li>build/make/common/math.mk</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>build/make/common/strings.mk</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>build/make/common/json.mk</li>
    </ul>
  </li>
  <li>加载build/make/core/pathmap.mk： 各种映射以避免到处都是硬编码路径</li>
  <li>加载build/make/core/project_definitions.mk: 允许项目定义自己的全局可用变量</li>
  <li>构建系统内部文件（写Android.mk时会调用include头文件，也就是这些makefile文件）</li>
  <li>加载build/make/core/project_definitions.mk：允许项目定义自己的全局可用变量</li>
  <li>加载build/make/core/envsetup.mk：定义大多数全局变量。这些是特定于用户的构建配置的</li>
  <li>加载build/make/core的ccache.mk、goma.mk、rbe.mk</li>
  <li>GCC版本选择（此处是4.9）</li>
  <li>加载build/make/core/combo/javac.mk：选择一个Java编译器</li>
  <li>列举框架支持的SEPolicy版本列表，除了PLATFORM_SEPOLICY_VERSION（Android 12是26.0/27.0/28.0/29.0/30.0）</li>
</ul>

<p><img src="../../assets/post/2024/2024-02-23-android12_BuildSystem_2/configMakefile.jpg" alt="" /></p>

<hr />

<h4 id="3541-构建系统内部文件">3.5.4.1. 构建系统内部文件</h4>

<blockquote>
  <p>Android源码中包含了许多的模块，模块的类型有很多种，例如：Java 库，C/C++ 库，APK 应用，以及可执行文件等。
并且，Java或者C/C++库还可以分为静态的或者动态的，库或可执行文件既可能是针对设备（此处指的是 Android 系统将被安装的设备，例如某个型号的手机或平板），也可能是针对主机（此处指的是开发Android系统的机器，例如装有Ubuntu操作系统的PC机或装有MacOS的iMac或Macbook）。</p>
</blockquote>

<p>不同类型的模块的编译步骤和方法是不一样，为了能够一致且方便的执行各种类型模块的编译，在config.mk中定义了许多的常量，这其中的每个常量描述了一种类型模块的编译方式。</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ###############################################################
# Build system internal files
# ###############################################################
</span>
<span class="nv">BUILD_COMBOS</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/combo

<span class="nv">CLEAR_VARS</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/clear_vars.mk

<span class="nv">BUILD_HOST_STATIC_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/host_static_library.mk
<span class="nv">BUILD_HOST_SHARED_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/host_shared_library.mk
<span class="nv">BUILD_STATIC_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/static_library.mk
<span class="nv">BUILD_HEADER_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/header_library.mk
<span class="nv">BUILD_SHARED_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/shared_library.mk
<span class="nv">BUILD_EXECUTABLE</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/executable.mk
<span class="nv">BUILD_HOST_EXECUTABLE</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/host_executable.mk
<span class="nv">BUILD_PACKAGE</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/package.mk
<span class="nv">BUILD_PHONY_PACKAGE</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/phony_package.mk
<span class="nv">BUILD_RRO_PACKAGE</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/build_rro_package.mk
<span class="nv">BUILD_HOST_PREBUILT</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/host_prebuilt.mk
<span class="nv">BUILD_PREBUILT</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/prebuilt.mk
<span class="nv">BUILD_MULTI_PREBUILT</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/multi_prebuilt.mk
<span class="nv">BUILD_JAVA_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/java_library.mk
<span class="nv">BUILD_STATIC_JAVA_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/static_java_library.mk
<span class="nv">BUILD_HOST_JAVA_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/host_java_library.mk
<span class="nv">BUILD_COPY_HEADERS</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/copy_headers.mk
<span class="nv">BUILD_NATIVE_TEST</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/native_test.mk
<span class="nv">BUILD_FUZZ_TEST</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/fuzz_test.mk

<span class="nv">BUILD_NOTICE_FILE</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/notice_files.mk
<span class="nv">BUILD_HOST_DALVIK_JAVA_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/host_dalvik_java_library.mk
<span class="nv">BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY</span> <span class="o">:=</span><span class="nv">$=</span> <span class="nv">$(BUILD_SYSTEM)</span>/host_dalvik_static_java_library.mk

<span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/deprecation.mk</span>
</code></pre></div></div>

<p>通过名称大概就可以猜出每个变量所对应的模块类型。（在模块的 Android.mk 文件中，只要包含进这里对应的常量便可以执行相应类型模块的编译）</p>

<p>这些常量的值都是另外一个Make文件的路径，详细的编译方式都是在对应的Make文件中定义的。这些常量和Make文件的是一一对应的，对应规则也很简单：
<code class="language-plaintext highlighter-rouge">常量的名称是Make文件的文件名除去后缀全部改为大写，然后加上“BUILD_”作为前缀</code>。例如:<code class="language-plaintext highlighter-rouge">常量BUILD_HOST_PREBUILT</code>的值对应的文件就是<code class="language-plaintext highlighter-rouge">host_prebuilt.mk</code></p>

<table>
  <tbody>
    <tr>
      <td>常量名</td>
      <td>文件名</td>
      <td>说明</td>
    </tr>
    <tr>
      <td>::</td>
      <td>::</td>
      <td>::</td>
    </tr>
    <tr>
      <td>BUILD_HOST_STATIC_LIBRARY</td>
      <td>host_static_library.mk</td>
      <td>定义了如何编译主机上的静态库</td>
    </tr>
    <tr>
      <td>BUILD_HOST_SHARED_LIBRARY</td>
      <td>host_shared_library.mk</td>
      <td>定义了如何编译主机上的共享库</td>
    </tr>
    <tr>
      <td>BUILD_STATIC_LIBRARY</td>
      <td>static_library.mk</td>
      <td>定义了如何编译设备上的静态库</td>
    </tr>
    <tr>
      <td>BUILD_SHARED_LIBRARY</td>
      <td>shared_library.mk</td>
      <td>定义了如何编译设备上的共享库</td>
    </tr>
    <tr>
      <td>BUILD_EXECUTABLE</td>
      <td>executable.mk</td>
      <td>定义了如何编译设备上的可执行文件</td>
    </tr>
    <tr>
      <td>BUILD_HOST_EXECUTABLE</td>
      <td>host_executable.mk</td>
      <td>定义了如何编译主机上的可执行文件</td>
    </tr>
    <tr>
      <td>BUILD_PACKAGE</td>
      <td>package.mk</td>
      <td>定义了如何编译APK文件</td>
    </tr>
    <tr>
      <td>BUILD_PREBUILT</td>
      <td>prebuilt.mk</td>
      <td>定义了如何处理一个已经编译好的文件 (例如Jar包)</td>
    </tr>
    <tr>
      <td>BUILD_MULTI_PREBUILT</td>
      <td>multi_prebuilt.mk</td>
      <td>定义了如何处理一个或多个已编译文件，该文件的实现依赖prebuilt.mk</td>
    </tr>
    <tr>
      <td>BUILD_HOST_PREBUILT</td>
      <td>host_prebuilt.mk</td>
      <td>处理一个或多个主机上使用的已编译文件，该文件的实现依赖multi_prebuilt.mk</td>
    </tr>
    <tr>
      <td>BUILD_JAVA_LIBRARY</td>
      <td>java_library.mk</td>
      <td>定义了如何编译设备上的共享Java库</td>
    </tr>
    <tr>
      <td>BUILD_STATIC_JAVA_LIBRARY</td>
      <td>static_java_library.mk</td>
      <td>定义了如何编译设备上的静态Java库</td>
    </tr>
    <tr>
      <td>BUILD_HOST_JAVA_LIBRARY</td>
      <td>host_java_library.mk</td>
      <td>定义了如何编译主机上的共享Java库</td>
    </tr>
  </tbody>
</table>

<hr />

<h4 id="3542-加载buildmakecoreenvsetupmk">3.5.4.2. 加载build/make/core/envsetup.mk</h4>

<p>该mk主要加载了product_config.mk和board_config.mk，用来得到TARGET_DEVICE和其他变量</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">...</span>
<span class="c">#设置host和target编译链相关的变量
</span><span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/combo/select.mk</span>
<span class="c">#(1)阅读产品规格，这样我们就可以得到TARGET_DEVICE和其他变量，我们需要找到输出文件
</span><span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/product_config.mk</span>

<span class="c"># 板级可以在$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)下定义，也可以在vendor/*/$(TARGET_DEVICE)下定义
# 真正的板级应始终与OEM vendor相关联
</span><span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/board_config.mk</span>
<span class="err">...</span>
</code></pre></div></div>

<h5 id="35421-androidproductsmk加载product_configmk">3.5.4.2.1. AndroidProducts.mk–加载product_config.mk</h5>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Include the product definitions.
# We need to do this to translate TARGET_PRODUCT into its
# underlying TARGET_DEVICE before we start defining any rules.
#
</span><span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/node_fns.mk</span>
<span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/product.mk</span>
<span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/device.mk</span>

<span class="c"># 获取所有的AndoridProducts.mk文件
</span><span class="nv">all_product_configs</span> <span class="o">:=</span> <span class="err">$</span><span class="o">(</span>get-all-product-makefiles<span class="o">)</span>
<span class="err">···</span>
</code></pre></div></div>

<p>然后加载<code class="language-plaintext highlighter-rouge">build/make/core/product.mk</code></p>

<p>列出所有设置了<code class="language-plaintext highlighter-rouge">PRODUCT_MAKEFILES</code>的<code class="language-plaintext highlighter-rouge">AndroidProducts.mk</code>文件。</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#该函数用于在AndroidProducts.mk文件设置了PRODUCT_MAKEFILES的文件
# PRODUCT_MAKEFILES的格式：&amp;product_name；：&lt;path_to_the_product_makefile&gt;
# 就是定义
#如果＜product_name＞与基本文件名相同（没有目录和.mk后缀），“＜product_name＞：”可以是省略。
#
#返回所有AndroidProducts.mk文件的列表。
</span><span class="err">define</span> <span class="err">_find-android-products-files</span>
<span class="err">$(file</span> <span class="err">&lt;$(OUT_DIR)/.module_paths/AndroidProducts.mk.list)</span> <span class="err">\</span>
  <span class="err">$(SRC_TARGET_DIR)/product/AndroidProducts.mk</span>
<span class="err">endef</span>
</code></pre></div></div>

<p>比如<code class="language-plaintext highlighter-rouge">device/google/sunfish/AndroidProducts.mk</code>文件内容：</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># device/google/sunfish/AndroidProducts.mk
</span><span class="nv">PRODUCT_MAKEFILES</span> <span class="o">:=</span> <span class="se">\</span>
    <span class="nv">$(LOCAL_DIR)</span>/aosp_sunfish.mk <span class="se">\</span>
    <span class="nv">$(LOCAL_DIR)</span>/aosp_sunfish_hwasan.mk <span class="se">\</span>

<span class="nv">COMMON_LUNCH_CHOICES</span> <span class="o">:=</span> <span class="se">\</span>
    aosp_sunfish-userdebug <span class="se">\</span>
</code></pre></div></div>

<hr />

<h5 id="35422-boardconfigmk加载board_configmk">3.5.4.2.2. BoardConfig.mk–加载board_config.mk</h5>

<p>代码<code class="language-plaintext highlighter-rouge">build/make/core/board_config.mk</code>，主要搜索加载BoardConfig.mk文件</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 该Board配置文件可以在
# $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)
# 或vendor/*/$（TARGET_DEVICE）下定义。
# 在两个地方搜索，但确保只有一个存在。真正的电路板应该始终与OEM供应商联系在一起
</span><span class="k">ifdef</span> <span class="nv">TARGET_DEVICE_DIR</span>
  <span class="k">ifneq</span> <span class="nv">($(origin TARGET_DEVICE_DIR),command line)</span>
    <span class="err">$(error</span> <span class="err">TARGET_DEVICE_DIR</span> <span class="err">may</span> <span class="err">not</span> <span class="err">be</span> <span class="err">set</span> <span class="err">manually)</span>
  <span class="k">endif</span>
  <span class="nv">board_config_mk</span> <span class="o">:=</span> <span class="nv">$(TARGET_DEVICE_DIR)</span>/BoardConfig.mk
<span class="k">else</span>
  <span class="nv">board_config_mk</span> <span class="o">:=</span> <span class="se">\</span>
    <span class="nf">$(</span><span class="nb">strip</span> <span class="nf">$(</span><span class="nb">sort</span> <span class="nf">$(</span><span class="nb">wildcard</span> <span class="se">\</span>
      <span class="nv">$(SRC_TARGET_DIR)</span>/board/<span class="nv">$(TARGET_DEVICE)</span>/BoardConfig.mk <span class="se">\</span>
      <span class="nf">$(</span><span class="nb">shell</span> <span class="nb">test</span> <span class="nt">-d</span> device <span class="o">&amp;&amp;</span> find <span class="nt">-L</span> device <span class="nt">-maxdepth</span> 4 <span class="nt">-path</span> <span class="s1">'*/</span><span class="nv">$(TARGET_DEVICE)</span><span class="s1">/BoardConfig.mk'</span><span class="nf">)</span> <span class="se">\</span>
      <span class="nf">$(</span><span class="nb">shell</span> <span class="nb">test</span> <span class="nt">-d</span> vendor <span class="o">&amp;&amp;</span> find <span class="nt">-L</span> vendor <span class="nt">-maxdepth</span> 4 <span class="nt">-path</span> <span class="s1">'*/</span><span class="nv">$(TARGET_DEVICE)</span><span class="s1">/BoardConfig.mk'</span><span class="nf">)</span> <span class="se">\</span>
    <span class="nf">)))</span>
  <span class="k">ifeq</span> <span class="nv">($(board_config_mk),)</span>
    <span class="err">$(error</span> <span class="err">No</span> <span class="err">config</span> <span class="err">file</span> <span class="err">found</span> <span class="err">for</span> <span class="err">TARGET_DEVICE</span> <span class="err">$(TARGET_DEVICE))</span>
  <span class="k">endif</span>
  <span class="k">ifneq</span> <span class="nv">($(words $(board_config_mk)),1)</span>
    <span class="nl">$(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE)</span><span class="o">:</span> <span class="nf">$(board_config_mk))</span>
  <span class="k">endif</span>
  <span class="nv">TARGET_DEVICE_DIR</span> <span class="o">:=</span> <span class="nf">$(</span><span class="nb">patsubst</span> %/,%,<span class="nf">$(</span><span class="nb">dir</span> <span class="nv">$(board_config_mk)</span><span class="nf">))</span>
  <span class="nv">.KATI_READONLY</span> <span class="o">:=</span> TARGET_DEVICE_DIR
<span class="k">endif</span>

<span class="c"># 引用配置文件
</span><span class="k">include</span><span class="sx"> $(board_config_mk)</span>
</code></pre></div></div>

<hr />

<h3 id="355-清除规则清除out目录中的dex文件">3.5.5. 清除规则，清除out目录中的dex文件</h3>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Clean rules
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">clean-dex-files</span>
<span class="nl">clean-dex-files</span><span class="o">:</span>
	<span class="nv">$(hide)</span> find <span class="nv">$(OUT_DIR)</span> <span class="nt">-name</span> <span class="s2">"*.dex"</span> | xargs <span class="nb">rm</span> <span class="nt">-f</span>
	<span class="nv">$(hide)</span> <span class="k">for </span>i <span class="k">in</span> <span class="sb">`</span>find <span class="nv">$(OUT_DIR)</span> <span class="nt">-name</span> <span class="s2">"*.jar"</span> <span class="nt">-o</span> <span class="nt">-name</span> <span class="s2">"*.apk"</span><span class="sb">`</span> <span class="p">;</span> <span class="k">do</span> <span class="o">((</span>unzip <span class="nt">-l</span> <span class="nv">$$</span>i 2&gt; /dev/null | <span class="se">\</span>
				<span class="nb">grep</span> <span class="nt">-q</span> <span class="s2">"</span><span class="se">\.</span><span class="s2">dex</span><span class="nv">$$</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="nb">rm</span> <span class="nt">-f</span> <span class="nv">$$</span>i<span class="o">)</span> <span class="o">||</span> <span class="k">continue</span> <span class="o">)</span> <span class="p">;</span> <span class="k">done</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"All dex files and archives containing dex files have been removed."</span>
</code></pre></div></div>

<h3 id="356-加载definitionsmk">3.5.6. 加载definitions.mk</h3>

<p>加载build/croe/definitions.mk,定义了很多通用函数，供编译过程调用</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Bring in standard build system definitions.
</span><span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/definitions.mk</span>

<span class="c">########## 比如定义了
###########################################################
## Find all of the java files under the named directories.
## Meant to be used like:
##    SRC_FILES := $(call all-java-files-under,src tests)
###########################################################
</span>
<span class="err">define</span> <span class="err">all-java-files-under</span>
<span class="err">$(call</span> <span class="err">all-named-files-under,*.java,$(1))</span>
<span class="err">endef</span>
</code></pre></div></div>

<h3 id="357-加载系统所有的androidmkandroidmklist">3.5.7. 加载系统所有的Android.mk(Android.mk.list)</h3>

<p>加载系统中所有的Android.mk，最终会被存放到<code class="language-plaintext highlighter-rouge">out/.module_paths/Android.mk.list</code></p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#
# Include all of the makefiles in the system
#
# $(SOONG_ANDROID_MK)：即out/soong/Android-***product.mk（所有Android.mk模块总和，参考runKatiBuild流程）
# .module_paths/Android.mk.list：所有Android.mk的相对路径
# $(SOONG_OUT_DIR)/late-$(TARGET_PRODUCT).mk：即out/soong/late-***product.mk，所有mk模块的soong临时文件集合
</span><span class="nv">subdir_makefiles</span> <span class="o">:=</span> <span class="nv">$(SOONG_ANDROID_MK)</span> <span class="nf">$(</span><span class="nb">file</span> &lt;<span class="nv">$(OUT_DIR)</span>/.module_paths/Android.mk.list<span class="nf">)</span> <span class="nv">$(SOONG_OUT_DIR)</span>/late-<span class="nv">$(TARGET_PRODUCT)</span>.mk
<span class="nv">subdir_makefiles_total</span> <span class="o">:=</span> <span class="nf">$(</span><span class="nb">words</span> int <span class="nv">$(subdir_makefiles)</span> post finish<span class="nf">)</span>
<span class="nv">.KATI_READONLY</span> <span class="o">:=</span> subdir_makefiles_total
<span class="c"># 遍历
</span><span class="err">$(foreach</span> <span class="err">mk,$(subdir_makefiles),$(info</span> <span class="err">[$(call</span> <span class="err">inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)]</span> <span class="err">including</span> <span class="err">$(mk)</span> <span class="err">...)$(eval</span> <span class="k">include</span><span class="sx"> $(mk)))</span>
</code></pre></div></div>

<hr />

<h3 id="358-定义product-installed-files">3.5.8. 定义product-installed-files</h3>

<blockquote>
  <p>要为此产品生成的模块的基本列表由相应的产品定义文件指定，这些定义在<code class="language-plaintext highlighter-rouge">build/make/core/product_config.mk</code>中</p>
</blockquote>

<p><strong>主要三个宏：</strong></p>
<ol>
  <li>PRODUCT_PACKAGES：将模块添加到编译</li>
  <li>LOCAL_REQUIRED_MODULES：指定依赖的模块。一旦本模块被编译，通过此变量指定的模块也将被编译</li>
  <li>PRODUCT_COPY_FILES：拷贝文件到车机目录</li>
</ol>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#列出特定产品安装的大多数文件，包括：
#-PRODUCT_PACKAGES及其LOCAL_REQUIRED_MODULES
#-PRODUCT_COPY_FILES
#指定了要为此产品生成的模块的基本列表
#PRODUCT_PACKAGES的名称解析：
#foo:32解析为foo_32；
#foo:64解析为foo；
#foo同时解析为foo和foo_32（如果定义了foo_32）。
#
#LOCAL_REQUIRED_MODULES的名称解析：
#请参阅所需模块的选择位定义。
#$（1）：产品生成文件
</span><span class="err">define</span> <span class="err">product-installed-files</span>
  <span class="nl">$(eval _pif_modules </span><span class="o">:</span><span class="nf">= </span>\
<span class="nf">    $(call get-product-var</span><span class="p">,</span><span class="nf">$(1)</span><span class="p">,</span><span class="nf">PRODUCT_PACKAGES) </span>\
<span class="nf">    $(if $(filter eng</span><span class="p">,</span><span class="nf">$(tags_to_install))</span><span class="p">,</span><span class="nf">$(call get-product-var</span><span class="p">,</span><span class="nf">$(1)</span><span class="p">,</span><span class="nf">PRODUCT_PACKAGES_ENG)) </span>\
<span class="nf">    $(if $(filter debug</span><span class="p">,</span><span class="nf">$(tags_to_install))</span><span class="p">,</span><span class="nf">$(call get-product-var</span><span class="p">,</span><span class="nf">$(1)</span><span class="p">,</span><span class="nf">PRODUCT_PACKAGES_DEBUG)) </span>\
<span class="nf">    $(if $(filter tests</span><span class="p">,</span><span class="nf">$(tags_to_install))</span><span class="p">,</span><span class="nf">$(call get-product-var</span><span class="p">,</span><span class="nf">$(1)</span><span class="p">,</span><span class="nf">PRODUCT_PACKAGES_TESTS)) </span>\
<span class="nf">    $(if $(filter asan</span><span class="p">,</span><span class="nf">$(tags_to_install))</span><span class="p">,</span><span class="nf">$(call get-product-var</span><span class="p">,</span><span class="nf">$(1)</span><span class="p">,</span><span class="nf">PRODUCT_PACKAGES_DEBUG_ASAN)) </span>\
<span class="nf">    $(if $(filter java_coverage</span><span class="p">,</span><span class="nf">$(tags_to_install))</span><span class="p">,</span><span class="nf">$(call get-product-var</span><span class="p">,</span><span class="nf">$(1)</span><span class="p">,</span><span class="nf">PRODUCT_PACKAGES_DEBUG_JAVA_COVERAGE)) </span>\
<span class="nf">    $(call auto-included-modules) </span>\
<span class="nf">  ) </span>\
<span class="nf">  $(eval </span><span class="c">###</span><span class="nf"> Filter out the overridden packages and executables before doing expansion) </span>\
<span class="nf">  $(eval _pif_overrides := $(call module-overrides</span><span class="p">,</span><span class="nf">$(_pif_modules))) </span>\
<span class="nf">  $(eval _pif_modules := $(filter-out $(_pif_overrides)</span><span class="p">,</span><span class="nf"> $(_pif_modules))) </span>\
<span class="nf">  $(eval </span><span class="c">###</span><span class="nf"> Resolve the :32 :64 module name) </span>\
<span class="nf">  $(eval _pif_modules := $(sort $(call resolve-bitness-for-modules</span><span class="p">,</span><span class="nf">TARGET</span><span class="p">,</span><span class="nf">$(_pif_modules)))) </span>\
<span class="nf">  $(call expand-required-modules</span><span class="p">,</span><span class="nf">_pif_modules</span><span class="p">,</span><span class="nf">$(_pif_modules)</span><span class="p">,</span><span class="nf">$(_pif_overrides)) </span>\
<span class="nf">  $(filter-out $(HOST_OUT_ROOT)/%</span><span class="p">,</span><span class="nf">$(call module-installed-files</span><span class="p">,</span><span class="nf"> $(_pif_modules))) </span>\
<span class="nf">  $(call resolve-product-relative-paths</span><span class="p">,</span>\
<span class="nf">    $(foreach cf</span><span class="p">,</span><span class="nf">$(call get-product-var</span><span class="p">,</span><span class="nf">$(1)</span><span class="p">,</span><span class="nf">PRODUCT_COPY_FILES)</span><span class="p">,</span><span class="nf">$(call word-colon</span><span class="p">,</span><span class="nf">2</span><span class="p">,</span><span class="nf">$(cf))))</span>
<span class="err">endef</span>
</code></pre></div></div>

<hr />

<h3 id="359-保存所有模块并加载buildcoremakefie">3.5.9. 保存所有模块并加载build/core/Makefie</h3>

<blockquote>
  <p>将所有要安装的模块都保存在变量<code class="language-plaintext highlighter-rouge">ALL_DEFAULT_INSTALLED_MODULES</code>中，并且将<code class="language-plaintext highlighter-rouge">build/core/Makefie</code>文件加载进来</p>
</blockquote>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">modules_to_install</span> <span class="o">:=</span> <span class="nf">$(</span><span class="nb">sort</span> <span class="se">\</span>
    <span class="nv">$(ALL_DEFAULT_INSTALLED_MODULES)</span> <span class="se">\</span>
    <span class="nv">$(product_target_FILES)</span> <span class="se">\</span>
    <span class="nv">$(product_host_FILES)</span> <span class="se">\</span>
    <span class="nv">$(CUSTOM_MODULES)</span> <span class="se">\</span>
  <span class="nf">)</span>
<span class="err">.....</span>

<span class="c"># build/make/core/Makefile包含额外的东西，我们不想用这些东西污染这个顶级的Makefile
# 它期望ALL_DEFAULT_INSTALLED_MODULES包含在当前生成过程中生成的所有内容，但它也进一步扩展了ALL_DEFAULT_INSTALLLED_MODULES
</span><span class="nv">ALL_DEFAULT_INSTALLED_MODULES</span> <span class="o">:=</span> <span class="nv">$(modules_to_install)</span>
<span class="k">include</span><span class="sx"> $(BUILD_SYSTEM)/Makefile</span>
<span class="nv">modules_to_install</span> <span class="o">:=</span> <span class="nf">$(</span><span class="nb">sort</span> <span class="nv">$(ALL_DEFAULT_INSTALLED_MODULES)</span><span class="nf">)</span>
<span class="nv">ALL_DEFAULT_INSTALLED_MODULES</span> <span class="o">:=</span>
</code></pre></div></div>

<hr />

<h3 id="3510-定义编译的image目标">3.5.10. 定义编译的image目标</h3>

<p>参考上面<code class="language-plaintext highlighter-rouge">build/core/main.mk中的伪目标定义</code>罗列的伪目标定义，定义了编译过程中的所有image目标。可以通过make进行单独编译。</p>

<p>列举重要的一些：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">命令</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">make droid</td>
      <td style="text-align: left">编译出整个系统的镜像，默认目标，同<code class="language-plaintext highlighter-rouge">make</code></td>
    </tr>
    <tr>
      <td style="text-align: left">make ramdisk</td>
      <td style="text-align: left">生成ramdisk.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">make userdataimage</td>
      <td style="text-align: left">生成userdata.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">make cacheimage</td>
      <td style="text-align: left">生成cache.img（但是A/B系统不包含recovery.img和cache.img，即Android O及之后）</td>
    </tr>
    <tr>
      <td style="text-align: left">make vendorimage</td>
      <td style="text-align: left">生成vendor.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">make vendorbootimage</td>
      <td style="text-align: left">生成vendor_boot.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">make systemextimage</td>
      <td style="text-align: left">生成system_ext.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">make superimage_empty</td>
      <td style="text-align: left">生成super_empty.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">make bootimage</td>
      <td style="text-align: left">生成boot.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">make vbmetaimage</td>
      <td style="text-align: left">生成vbmeta.img镜像</td>
    </tr>
    <tr>
      <td style="text-align: left">make droidcore</td>
      <td style="text-align: left">包含上面的droidcore-unbundled，构建文件，然后将其打包成rom格式</td>
    </tr>
  </tbody>
</table>

<h3 id="3511-打包rom包各种刷机包">3.5.11. 打包rom包（各种刷机包）</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">命令</th>
      <th style="text-align: left">说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">make droid</td>
      <td style="text-align: left">编译出整个系统的镜像，默认目标，同<code class="language-plaintext highlighter-rouge">make</code></td>
    </tr>
    <tr>
      <td style="text-align: left">make droidcore</td>
      <td style="text-align: left">构建文件，生成一系列系统镜像文件</td>
    </tr>
    <tr>
      <td style="text-align: left">make updatepackage</td>
      <td style="text-align: left">（build/make/core/Makefile中）生成fastboot刷机包，示例：<code class="language-plaintext highlighter-rouge">out/target/product/AOSPProduct/AOSPProduct-img-eng.cloud.zip</code>，解压里面包含的都是img文件，一般用<code class="language-plaintext highlighter-rouge">fastboot flash zip</code>包的方式更新ROM</td>
    </tr>
    <tr>
      <td style="text-align: left">make target-files-package</td>
      <td style="text-align: left">（build/make/core/Makefile中）生成target包（可用于制作差分升级包）</td>
    </tr>
    <tr>
      <td style="text-align: left">make otapackage</td>
      <td style="text-align: left">（build/make/core/Makefile中）生成升级包</td>
    </tr>
    <tr>
      <td style="text-align: left">make otardppackage</td>
      <td style="text-align: left">（build/make/core/Makefile中）用于动态分区升级，生成<code class="language-plaintext highlighter-rouge">$(product_name)-ota-retrofit-$(FILE_NAME_TAG).zip</code></td>
    </tr>
    <tr>
      <td style="text-align: left">make partialotapackage</td>
      <td style="text-align: left">（build/make/core/Makefile中）生成<code class="language-plaintext highlighter-rouge">$(product_name)-partial-ota-$(FILE_NAME_TAG).zip</code></td>
    </tr>
  </tbody>
</table>

<hr />

<h1 id="4-参考">4. 参考</h1>

<ul>
  <li><a href="https://www.cnblogs.com/mymelody/p/9436620.html">shell条件判断if中的-a到-z的意思</a></li>
  <li><a href="https://blog.csdn.net/zhubosa/article/details/39891611">shell脚本type命令的用法</a></li>
  <li><a href="https://www.cnblogs.com/chaoguo1234/p/5723531.html">shell中的type命令</a></li>
  <li><a href="https://www.cnblogs.com/rockyching2009/p/10111258.html">OpenWrt编译系统(1)之make之前</a></li>
  <li><a href="https://blog.csdn.net/u011913612/article/details/51878356">android编译系统分析一：source build/envsetup.sh与lunch</a></li>
  <li><a href="https://blog.csdn.net/yiranfeng/article/details/109084082">make编译过程-Android10.0编译系统（三）</a></li>
  <li><a href="http://c.biancheng.net/view/122.html">go install命令——编译并安装</a></li>
  <li><a href="https://duanqz.github.io/2015-08-08-build-system-part1-overview">编译系统(1)-概览</a></li>
  <li><a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MjM5NDk5ODQwNA==&amp;action=getalbum&amp;album_id=1552818877418487808&amp;scene=173&amp;from_msgid=2652469359&amp;from_itemidx=1&amp;count=3&amp;nolastread=1#wechat_redirect">编译系统公众号系列文章</a></li>
  <li><a href="https://blog.csdn.net/mingmigndfds/article/details/125279910?spm=1001.2014.3001.5502">Blueprint代码详细分析-Android10.0编译系统（七）</a></li>
  <li><a href="https://blog.csdn.net/shift_wwx/article/details/84784567">Android中Kati简介</a></li>
  <li><a href="https://blog.csdn.net/shift_wwx/category_8466677.html">android —- build</a></li>
  <li><a href="https://betheme.net/xiaochengxu/126408.html?action=onClick">【Android构建篇】MakeFile语法</a></li>
  <li><a href="https://blog.csdn.net/chongyang198999/article/details/46423269">Android编译系统参考手册</a></li>
</ul>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="android" /><category term="build" /><summary type="html"><![CDATA[本篇我们详细的说明Android编译命令的内部流程，讲述source build/envsetup.sh;lunch;make这三个主要编译命令的处理逻辑。]]></summary></entry><entry><title type="html">Android 12编译系统-1 编译框架介绍（源码&amp;amp;out&amp;amp;编译模块结构）</title><link href="https://alonealive.github.io/android/android12_BuildSystem_1/" rel="alternate" type="text/html" title="Android 12编译系统-1 编译框架介绍（源码&amp;amp;out&amp;amp;编译模块结构）" /><published>2024-02-20T09:40:02+00:00</published><updated>2024-02-20T09:40:02+00:00</updated><id>https://alonealive.github.io/android/android12_BuildSystem_1</id><content type="html" xml:base="https://alonealive.github.io/android/android12_BuildSystem_1/"><![CDATA[<blockquote>
  <p>基于Android 12 AOSP源码（android-12.0.0_r3），对Android编译系统框架作学习梳理。<br />
开篇从Android的源码结构入手，让我们先知道Android源码架构包含了哪些内容、Android编译结果目录有哪些内容。然后了解Android编译系统模块的结构组成。</p>
</blockquote>

<hr />

<h1 id="1-android源码目录结构">1. Android源码目录结构</h1>

<p><strong>我们以Android 12 AOSP源码为参照，从Android源码的目录结构了解开始。这是为了先对Android源码系统有个整体框架的概念：</strong></p>
<ul>
  <li>Android包含了哪些目录模块</li>
  <li>编译模块位置是在哪、编译结果在哪</li>
</ul>

<p><strong>这样我们才能继续深入了解：</strong></p>
<ul>
  <li>编译执行的命令是在哪边执行的</li>
  <li>整编或者单独编译执行make是怎么执行</li>
  <li>各个模块编译的结果是什么，编译在哪</li>
  <li>整编的执行流程</li>
  <li>单编的执行流程、单编对应的结果文件是什么、单编结果文件能不能sync、能不能push</li>
  <li>各种Android.mk、Android.bp文件怎么阅读、使用</li>
  <li>怎么将模块添加到编译中</li>
  <li>…..</li>
</ul>

<hr />

<p><strong>Android源码目录：</strong></p>

<blockquote>
  <p>参考AOSP源码网站：http://aospxref.com/</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- art/  <span class="c"># Android runtime运行环境, java虚拟机art源码</span>
- bionic/ <span class="c"># C库</span>
- bootable/	<span class="c"># 启动引导和恢复系统相关代码（bootloader和recovery）</span>
   - bootloader
      - dramk_2712/	
   	- lk/	 <span class="c"># liitle kernel，负责引导Linux的⼀个迷你⼩系统，启动顺序位于fastboot/uboot之后，Linux kernel之前，系统开机之后是进Android还是进recovery，即是这⾥负责</span>
	   - lk_ext_mod/
   - recovery <span class="c"># 负责升级、恢复出⼚等⼀些⽐较重要⼜不是很复杂的⼯作，一般属于升级update子模块</span>

- bootstrap.bash	 <span class="c"># 软链接build/soong/bootstrap.bash</span>
<span class="c"># *****该编译模块需要关注的中心点*******</span>
- build/	<span class="c"># 存放系统编译规则等基础开发包配置</span>
   - blueprint <span class="c"># .bp的全程，由Go语⾔编写，是⽣成、解析Android.bp的⼯具，是Soong的⼀部分。Soong则是专为Android编译⽽设计的⼯具，Blueprint只是解析⽂件的形式，⽽Soong则解释内容的含义</span>
   - kati <span class="c"># 基于Makefile来⽣成ninja.build的⼩项⽬。主要⽤于把Makefile转成成ninja file，⾃⾝没有编译能⼒，转换后使⽤Ninja编译(Android 10之前，Android 11移除)</span>
   - make <span class="c"># android make编译系统，基于Makefile，此系统正在被Soong取代</span>
   - soong <span class="c"># Soong（go语言写的项目）构建系统是在Android 7中引⼊的，旨在取代Make。它利⽤Kati GNU Make克隆⼯具和Ninja构建系统组件来加速Android的构建；Soong在编译时使⽤，解析Android.bp，将之转化为Ninja⽂件</span>
<span class="c"># ***********************************</span>

- compatibility/	<span class="c"># 兼容性计划文档目录，一些markdown文档</span>
- cts/	<span class="c"># cts测试用例源码，但是一般cts跑测是从官网下载官方更新的cts测试包，如果跑测出现case faile，可以根据log在这边阅读源码分析</span>
- dalvik/	<span class="c"># java dalvik虚拟机</span>
- developers/	<span class="c"># demo源码和开发者参考文档</span>
   - demo
   - samples
- development/	<span class="c"># 应用程序开发相关的实例/模板/工具</span>
   - apps  <span class="c"># 开发者选项等app</span>
   - tools

- device/	<span class="c"># 芯片厂商定制代码，包含AOSP自带的多家芯片厂以及芯片平台的代码，android O引入treble后，该目录下大部分关于厂商定制的源码移到vendor目录下</span>
   - common
   - google <span class="c"># 此目录存放Google原生的编译产品，可用于demo参考</span>

- external/	<span class="c"># 开源第三方组件模块，存放着⼤量Google在创造、更新Android过程中，因实现某些功能⽽引⼊的开源第三⽅库，有些库是做了多系统适配（例如适配Android、Win、Linux），有些是only for Android</span>
   - aac/ <span class="c"># aac解码库，AAC是高级音频编码（Advanced Audio Coding）</span>
   - bsdiff/ <span class="c"># diff工具</span>

- frameworks/	<span class="c"># framework层，应用程序框架层</span>
   - av
      - apex <span class="c"># 一种文件格式</span>
      - camera <span class="c"># cameram模块</span>
      - cmds 
         - screenrecord/	<span class="c"># 录屏工具源码</span>
	      - stagefright/	   <span class="c"># 一个多媒体框架</span>
      - drm <span class="c"># 一种图形显示框架</span>
      - media <span class="c"># audio、media源码</span>
      - services <span class="c"># audioflinger、cameraservice、mediacodec等核心CPP源码</span>
      - tools <span class="c"># mainline_hook shell脚本</span>
   - base
      - api <span class="c">#Android对外暴露的API整体存放位置，make update-api命令更新的就是这里的文件</span>
      - cmds <span class="c"># 命令/工具源码路径，例如am、pm、screencap等</span>
         - bootanimation/	<span class="c"># 开机动画</span>
      - core <span class="c"># android java和JNI核心库源码，例如framework.jar,hwbinder.jar,libandroid_runtime等</span>
         - java <span class="c"># 主要java代码</span>
         - jni <span class="c"># libandroid_runtime JNI文件</span>
      - data <span class="c"># Android系统中字体、效果音等资源文件存放目录</span>
      - graphics <span class="c"># graphic基础补充库，属于framework.jar</span>
      - libs <span class="c"># display模块的hwui库位于这个目录下，以及其他的几个模块</span>
      - media <span class="c"># media部分的java实现和jni实现源码，java部分属于framework.jar</span>
      - opengl <span class="c"># opengl java api，隶属于framework.jar</span>
      - packages <span class="c"># aosp core app源码路径，有DocumentsUI、Keyguard、MtpDocumentsProvider、SettingsProvide、SystemUI、Wallpaper等</span>
      - services <span class="c"># service.jar源码，systemServer中启动的多数java service服务代码所在地</span>
      - telecomm/	<span class="c"># tele模块，属于framework.jar</span>
      - telephony/
      - wifi <span class="c"># wifi java api源码目录，属于framework.jar</span>
   - compile
      - libbcc
      - mclinker	
      - slang/
   <span class="c"># 参考 https://blog.csdn.net/benco1986/article/details/126052528</span>
   - ex
      - camera2 <span class="c"># Android摄像头2</span>
      - common
      - framesequence/ <span class="c"># 帧序列</span>
   - hardware/interfaces/ <span class="c"># 各种HAL接口模块，比如automotive、sensorservice、displayservice、cameraservice等</span>
   - native
      - cmds <span class="c"># bugreport、dumpsys等Android debug工具的c实现源码，以及serviceManager</span>
      - libs <span class="c"># binder的Android中间件层实现源码，及libgui、libui、libsensor等中间件库源码</span>
      - opengl <span class="c"># OpenGL的c/c++实现源码</span>
      - services <span class="c"># 一些Android核心C进程的C/C++源码，例如surfaceFlinger、inputflinger、sensorservice等</span>
      - vulkan/ <span class="c"># vulkan图形渲染框架API源码</span>

- hardware/	 <span class="c"># 硬件适配层hal层</span>
   - interfaces/  <span class="c"># Android aosp定义的hidl interface实现，hal service实现源码</span>
   - libhardware
      - moudles
         - audio
         - camera
         - input
   - mediatek/qcom  <span class="c"># 芯片平台 HAL实现代码，上层对接Google HIDL interface</span>

- kernel/ <span class="c"># aosp的kernel适配目录</span>
- libcore/	<span class="c"># Java api核心库实现源码，dalvik、art部分java实现代码也在这里</span>
   - dalvik
- libnativehelper/	<span class="c"># native层helper，即JNJ相关cpp源码</span>
<span class="c"># 整编的入口文件</span>
- Makefile	<span class="c"># 全局makefile，内容就是运行include build/make/core/main.mk</span>
- packages/	<span class="c"># 大部分android apk源码</span>
   - apps
      - Calendar <span class="c">#日历应用</span>
      - Camera2/ <span class="c"># 官方camera</span>
      - Car <span class="c"># car的demo APP，包含日历、仪表、空调、输入法、luancher、设置、收音机、media等</span>
      - Gallery <span class="c"># 图库</span>
      - SoundRecorder   <span class="c"># 录音机</span>
   - providers <span class="c"># 存放着一堆provider的源码，media模块使用的MediaProvider，ContactsProvider等</span>
   - services  <span class="c"># java service源码位置</span>
      - car  <span class="c"># carservice源码</span>

- pdk/	<span class="c"># platform development kit，平台开发工具</span>
- platform_testing/	<span class="c"># 平台一些测试相关的源码</span>
- prebuilts/	<span class="c"># 预编译资源</span>
   - go <span class="c"># go语言环境，比如用于支持soong</span>
- sdk/	<span class="c"># sdk工具源码</span>
- system/	<span class="c"># 底层文件系统库、应用、组件，特殊功能模块</span>
   - bt <span class="c"># 蓝牙相关</span>
   - connectivity <span class="c"># wifi相关库</span>
   - core <span class="c"># Android C/C++部分核心组件源码，有相当一部分是common类的组件</span>
      - adb <span class="c"># adb进程源码</span>
      - fastboot  <span class="c"># fastboot命令工具源码</span>
      - fs_mgr/ <span class="c"># OverlayFS文件系统和adb remount，通过通过adb-disable-verity和adb-remount命令解决一些调试场景</span>
      - healthd   <span class="c"># 电池服务守护进程源码</span>
      - init      <span class="c"># init进程实现源码</span>
      - libcutils <span class="c"># 工具库源码，例如property_set</span>
      - ibion <span class="c"># ion内存管理工具类源码</span>
      - liblog <span class="c"># ALOGX等相关函数实现源码</span>
      - libusbhost <span class="c"># usbhost实现库</span>
      - llkd <span class="c"># Android Live LocK Daemon（llkd），旨在捕获和缓解内核死锁</span>
      - logcat <span class="c"># logcat实现源码</span>
      - logd <span class="c"># 负责输出log的logd守护进程源码</span>
      - sdcard <span class="c"># 外置存储卡挂载时会使用到的工具类</span>
   - hwservicemanager   <span class="c"># hwservicemanager的实现源码，负责管理hidl service的，类似serviceManager</span>
   - libhidl <span class="c"># hidl功能的c/c++层实现源码</span>
   - libhwbinder <span class="c"># hwbinder功能的实现源码</span>
   - memory/ <span class="c"># memory内存信息</span>
      - lmkd <span class="c"># 低内存机制源码</span>
   - netd <span class="c"># 网络守护进程源码</span>
   - nfc
   - sepolicy <span class="c"># aosp selinux配置文件所在目录</span>
   - storaged/
   - update_engine <span class="c"># Android升级模块</span>
   - vold <span class="c"># 用于磁盘管理的守护进程</span>

- <span class="nb">test</span>/	
   - vts <span class="c"># vts测试源码</span>
- toolchain/ <span class="c"># Android工具链</span>
- tools/	<span class="c"># android一些工具的源码，例如apk签名</span>
   - apksig
- vendor/	<span class="c"># 厂商定制模块（芯片厂商以及具体产品项目的定制化功能的实现代码）</span>
</code></pre></div></div>

<hr />

<h2 id="11-编译结果out目录">1.1. 编译结果out目录</h2>

<p>Android编译生成的所有文件都是和源码分离的，所有中间文件和结果都放在根目录out文件夹中。out文件夹结构如下：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span>
├── Android.mk <span class="c"># 空文件</span>
├── build_date.txt <span class="c"># 编译时间，时间戳格式，例如1683536594</span>
├── build-&lt;product_name&gt;.ninja <span class="c"># 编译某个产品的构建列表（针对Android.mk）</span>
├── build-&lt;product_name&gt;-package.ninja
├── casecheck.txt
├── CaseCheck.txt
├── CleanSpec.mk <span class="c"># 空文件</span>
├── combined-&lt;product_name&gt;.ninja <span class="c"># 最终整合Android.bp和Android.mk构建列表的最终文件</span>
├── dist <span class="c"># 包含了为多种分发而准备的包，通过“make disttarget”将文件拷贝到该目录，默认的编译目标不会产生该目录</span>
   - installed-files-rescued.txt <span class="c"># system目录下结果文件，按文件大小从大往小排列</span>
├── dumpvars-error.log
├── dumpvars-soong.log <span class="c"># soong构建日志</span>
├── dumpvars-soong_metrics
├── empty <span class="c"># 空文件夹</span>
├── error.log
├── host <span class="c"># 构建源码需要的工具和库文件，一些在主机上用的工具，有一些是二进制程序,有一些是JAVA的程序</span>
   - common/obj/JAVA_LIBRARIES <span class="c"># 一些系统的jar包，比如bugreport、signapk、apksigner等</span>
   - linux-x86 <span class="c"># 一些依赖lib so库以及资源文件等</span>
      - bin <span class="c"># 可执行二进制程序，比如adb、fastboot、bsdiff、BugReport、e2fsck等</span>
      - framework <span class="c"># JAVA库,＊.jar文件</span>
      - lib <span class="c"># lib动态链接库</span>
      - obj <span class="c"># 中间生成的目标文件cd</span>
├── last_kati_suffix <span class="c"># 最近一次kati编译product产品的后缀</span>

├── .module_paths <span class="c"># 所有编译的模块列表</span>
   ├── Android.bp.list <span class="c"># 所有使用Android.bp的模块完整路径列表</span>
   ├── Android.mk.list <span class="c"># 所有使用Android.mk的模块完整路径列表</span>
   ├── AndroidProducts.mk.list <span class="c"># 所有product的AndroidProducts.mk的完整路径（即lunch显示的所有产品）</span>
   ├── CleanSpec.mk.list <span class="c"># 所有CleanSpec.mk的模块列表（定义每次编译前的特殊清理步骤）</span>
   ├── files.db
   ├── OWNERS.list <span class="c"># OWNERS文件的路径列表</span>
   └── TEST_MAPPING.list <span class="c"># TEST_MAPPING文件的路径列表</span>

├── ninja_build <span class="c"># 空文件</span>
├── soong <span class="c"># soong构建文件夹</span>
   ├── aidl <span class="c"># aidl接口包列表</span>
   ├── Android-product.mk <span class="c"># 所有makefile模块的Android.mk集合</span>
   ├── apex/depsinfo  <span class="c"># 各个包或者activity可允许的最小sdk版本 minSdkVersion</span>
   ├── apexkeys.txt <span class="c"># 各个apex包的key列表</span>
   ├── api_fingerprint.txt <span class="c"># 空</span>
   ├── api_levels.json <span class="c"># api level数组（字母对应数字版本）</span>
   ├── boot-jars-package-check <span class="c"># 空</span>
   ├── build.ninja   <span class="c"># ninja构建列表（针对Android.bp）</span>
   ├── build.ninja.d <span class="c"># out/soong/build.ninja构建的列表（针对Android.bp）</span>
   ├── build_number.txt <span class="c"># 构建版本信息，包含时间和构建用户</span>
   ├── cflags <span class="c"># 列出使用cflags的模块，针对google原生的（-O3、-Wall、-Werror、-Wextra、-Wthread-safety）</span>
   ├── compat_config
      - merged_compat_config.xml
   ├── dexpreopt.config <span class="c"># dexpreopt预编译配置属性</span>
   ├── dexpreopt_soong.config <span class="c"># 针对soong的预编译config</span>
   ├── docs <span class="c"># 各种机制和库的说明文档和使用说明介绍，比较重要（比如aidl、hidl、java、xml等）</span>
      ├── aidl.html
      ├── android.html
      ├── configs.html
      ├── hidl.html
      ├── java.html
      ├── kernel.html
      ├── ......
   ├── framework.aidl   <span class="c"># framework的aidl包名列表</span>
   ├── framework_non_updatable.aidl
   ├── hiddenapi  <span class="c"># 使用hidden注释的api列表</span>
   ├── host <span class="c"># 同out/host结构，包含windows和linux两个子目录</span>
   ├── late-product.mk <span class="c"># 自动生成的文件，soong在解析所有Android.mk文件后读取的值</span>
   ├── make_vars-product.mk
   ├── product <span class="c"># 编译产品的dexpreopt预编译配置</span>
   ├── ndk <span class="c"># ndk各版本的接口库和头文件</span>
   ├── ndk_base.timestamp
   ├── soong_build_metrics.pb
   ├── soong.environment.available <span class="c"># soong的环境变量（宏和路径）</span>
   ├── soong.environment.used 
   ├── soong.variables <span class="c"># soong的变量和值</span>
   ├── system_server_dexjars
   └── vndk <span class="c"># vndk的lib库文件</span>

├── soong.log <span class="c"># soong目录下go脚本的日志</span>
├── soong_metrics 
├── soong_ui <span class="c"># 可执行二进制文件</span>
├── target <span class="c"># 下面单独解释</span>
   ├── common
   │   ├── obj
   │   └── R
   └── product
       └── AOSP
├── vendor-hal <span class="c"># hal模块各个文件的checksum</span>
</code></pre></div></div>

<h3 id="111-outtarget目录结构">1.1.1. out/target目录结构</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── out/target
   ├── common <span class="c"># 包含了针对设备的共通的编译产物，主要是Java应用代码和Java库</span>
      ├── obj <span class="c"># 目标文件</span>
      │   ├── all-event-log-tags.txt <span class="c"># 所有event log的tag列表</span>
      │   ├── api.xml
      │   ├── APPS <span class="c"># 含了JAVA应用程序生成的目标，每个应用程序对应其中一个子目录，将结合每个应用程序的原始文件生成Android应用程序的APK包</span>
      │   ├── ETC
      │   ├── JAVA_LIBRARIES <span class="c"># 包含了所有生成JAVA jar包的模块，可用于APP导入使用</span>
      │   ├── module-lib-api.xml
      │   ├── PACKAGING
      │   ├── system-api.xml
      │   ├── system-server-api.xml
      │   └── test-api.xml
      └── R <span class="c"># 资源文件</span>
          ├── android
          ├── androidx
          ├── com
          └── org

   └── product
       └── XXX_product <span class="c"># 具体编译lunch选择产品名</span>
         ├── abl.elf
         ├── android-info.txt <span class="c"># 当前android board名称</span>
         <span class="c">#  Android系统中，通常会把zImage（内核镜像uImage文件）和ramdisk.img打包到一起，生成一个boot.img镜像文件，放到boot分区，由bootloader来引导启动</span>
         <span class="c"># 其启动过程本质也是和分开的uImage&amp;ramdisk.img类似，只不过把两个镜像按照一定的格式合并为一个镜像而已</span>
         ├── boot.img <span class="c"># Linux内核镜像</span>
         ├── boot.zip
         ├── build_fingerprint.txt <span class="c"># 构建信息</span>
         ├── build_system_stats.txt <span class="c"># 构建信息</span>
         ├── build_thumbprint.txt
         ├── data <span class="c"># 这个目录是用来生成&lt;数据文件系统镜像&gt;（data file system image）userdata.img</span>
         ├── dlkm
         ├── dtb.img
         ├── dtbo.img
         ├── kernel <span class="c"># kernel目录</span>
         ├── metadata
         ├── metadata.img
         ├── misc_info.txt <span class="c"># misc 分区供恢复映像使用，存储空间不能小于4KB</span>
         ├── &lt;product_name&gt;-ota-eng.USER.zip <span class="c"># OTA升级包</span>
         ├── obj <span class="c"># 生成的中间文件,最后都要拷贝到root或system文件夹中，最后生成镜像img文件</span>
         ├── obj_arm
         ├── ota_metadata
         ├── ota_metadata.pb
         ├── otatools.zip
         ├── persist <span class="c"># persist目录</span>
         ├── persist.img <span class="c"># 表示“不可更改”（（仍然可以通过fastboot替换它)），包含在设备发货后不应更改的数据。例如：芯片的校准数据（wifi，bt，摄像机等），证书和其他安全相关文件</span>
         ├── ramdisk
         ├── ramdisk-debug.img
         <span class="c"># 内存磁盘镜像是根文件系统：android启动时首先加载ramdisk.img镜像，并挂载到/目录下，并进行了一系列的初始化动作，包括创建各种需要的目录，初始化console开启服务等</span>
         <span class="c"># 尽管ramdisk.img需要放在Linux内核镜像（boot.img）中，但却属于Android源代码的一部分</span>
         <span class="c"># 内存盘的根文件系统映像， 一个分区影像文件，它会在kernel启动的时候，以只读的方式被mount，</span>
         <span class="c"># 这个文件中只是包含了/init以及一些配置文件，这个ramdisk被用来调用init，以及把真正的root file system mount起来</span>
         <span class="c"># ramdisk.img的内容就是*/out/target/product/xxx/root*目录的压缩</span>
         ├── ramdisk.img 
         ├── ramdisk-recovery.img
         ├── ramdisk-test-harness.img
         <span class="c"># 在正常分区或内核分区被破坏，不能正常启动时，可以进入此分区进行恢复，相当于一个简易的OS或bios，可以认为是一个boot分区的替代品</span>
         <span class="c"># 通过他可以让我们在这一分区进行备份维护和恢复，我们通常说的刷机便指的是此分区</span>
         <span class="c"># 进入此分区方法：1、adb reboot recovery；2、通过组合键，电源键+音量键</span>
         ├── recovery
         ├── root     <span class="c"># 这个目录用来创建&lt;root文件系统&gt;（root file system）,  生成的ramdisk.img是用这个文件夹生成的镜像</span>
         ├── secimage.log
         ├── shareduid_violation_modules.json
         ├── signed
         ├── signed_encrypted
         ├── soong_to_convert.txt
         ├── super_empty.img
         ├── super.img <span class="c"># 动态分区镜像文件</span>
         ├── symbols <span class="c"># 带调试信息，可用于堆栈分析</span>
         ├── system <span class="c"># 用来创建system.img, 大部分的应用程序和库都在system中</span>
         ├── system_ext <span class="c"># system externel编译结果目录</span>
         ├── system_ext.img <span class="c"># system external镜像文件</span>
         <span class="c"># system.img是out/target/product/xxx/system目录的一个映射</span>
         ├── system.img <span class="c"># system镜像文件，包含了整个系统android的framework，app等等，会被挂接到 "/"上，包含了系统中所有的二进制文件</span>
         ├── testcases
         ├── test_harness_ramdisk
         ├── userdata.img <span class="c"># 将会被挂接到 /data 下，包含了所有应用相关的配置文件，以及用户相关的数据</span>
         ├── vbmeta.img <span class="c"># 用于安全验证，bootloader验证vbmeta的签名，再用vbmeta的key以及hash值验证dtbo/boot/system/vendor</span>
         ├── vendor <span class="c"># vendor目录，用于创建vendor.img</span>
         ├── vendor_boot-debug.img
         ├── vendor_boot.img
         ├── vendor_boot-test-harness.img
         ├── vendor_debug_ramdisk
         ├── vendor.img <span class="c"># vendor镜像文件，包含所有不可分发给 Android 开源项目 (AOSP) 的二进制文件</span>
         ├── vendor_ramdisk
         └── wall_werror.txt <span class="c"># 使用wall_werror cflag的模块Android.bp或者Android.mk文件</span>
</code></pre></div></div>

<hr />

<h1 id="2-编译系统三部分">2. 编译系统三部分</h1>

<blockquote>
  <p>整个Build系统中的Make文件可以分为三类：</p>
</blockquote>

<hr />

<h2 id="21-part-1build系统核心模块">2.1. Part 1：Build系统核心模块</h2>

<blockquote>
  <p>第一类是Build系统核心模块。此类文件定义了整个Build系统的框架，而其他所有Make文件都是在这个框架的基础上编写出来的。</p>
</blockquote>

<p>Build系统核心主要位于<code class="language-plaintext highlighter-rouge">/build/core/</code>目录</p>

<p><strong>编译模块目录结构：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>build/
├── bazel <span class="c"># 一个开源的构建和测试工具，类似于Make、Maven和Gradle，目前Google仍在实验开发阶段，暂时不关注</span>
├── blueprint <span class="c"># 一个meta-build系统，它读取描述需要构建的模块的bp文件，解析Blueprint文件翻译成ninja语法文件</span>
├── buildspec.mk.default -&gt; make/buildspec.mk.default
├── CleanSpec.mk -&gt; make/CleanSpec.mk
├── core -&gt; make/core
├── envsetup.sh -&gt; make/envsetup.sh <span class="c"># source加载环境变量</span>
├── make <span class="c"># make编译系统，基于makefile的构建方式正在被soong取代</span>
├── pesto <span class="c"># 一个概念验证脚本，用于使用Soong构建系统构建的目标在Android源代码树中创建模拟的bazel环境</span>
├── soong <span class="c"># go编写的新构建系统</span>
├── target -&gt; make/target
└── tools -&gt; make/tools
</code></pre></div></div>

<hr />

<h2 id="22-part-2针对某个product产品">2.2. Part 2：针对某个Product产品</h2>

<blockquote>
  <p>（一个产品可能是某个型号的手机或者车机）的Make文件，这些文件通常位于<code class="language-plaintext highlighter-rouge">device</code>目录下，该目录下又以芯片名（或者公司名）以及产品名分为两级目录。</p>
</blockquote>

<blockquote>
  <p>例如下面是Android 12 AOSP源码中device目录下子目录的结构。对于一个产品的定义通常需要一组文件，这些文件共同构成了对于这个产品的定义。</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>android/device/linaro/poplar<span class="nv">$ </span>tree <span class="nt">-L</span> 1
<span class="nb">.</span>
├── Android.bp
├── AndroidProducts.mk
├── audio
├── bluetooth
├── BoardConfig.mk
├── device.mk
├── installer
├── manifest.xml
├── media
├── METADATA
├── optee
├── overlay
├── poplar.mk
├── proprietary
├── sepolicy
├── vendor
└── wifi
</code></pre></div></div>

<hr />

<h2 id="23-part-3针对某个module模块">2.3. Part 3：针对某个Module模块</h2>

<p>单个模块的Make文件。整个系统中，包含了大量的模块，每个模块都有一个专门的Make文件，这类文件的名称统一为<code class="language-plaintext highlighter-rouge">Android.mk</code>或者<code class="language-plaintext highlighter-rouge">Android.bp</code>，该文件中定义了如何编译当前模块。Build系统会在整个源码树中扫描名称为<code class="language-plaintext highlighter-rouge">Android.mk</code>和<code class="language-plaintext highlighter-rouge">Android.bp</code>的文件并根据其中的内容执行模块的编译</p>

<hr />

<h1 id="3-android编译系统介绍">3. Android编译系统介绍</h1>

<h2 id="31-编译演变历史">3.1. 编译演变历史</h2>

<blockquote>
  <p>ninja的网址：https://ninja-build.org</p>
</blockquote>

<p>在Android 7.0之前，Android编译系统使用GNU Make描述和shell来构建编译规则，模块定义都使用<code class="language-plaintext highlighter-rouge">Android.mk</code>进行定义，<code class="language-plaintext highlighter-rouge">Android.mk</code>的本质就是Makefile，但是随着Android的工程越来越大，模块越来越多，Makefile组织的项目编译时间越来越长。Android编译系统需要优化。</p>

<p>因此，在Android7.0开始，Google采用<code class="language-plaintext highlighter-rouge">ninja</code>来代取代之前使用的<code class="language-plaintext highlighter-rouge">make</code>，由于之前的<code class="language-plaintext highlighter-rouge">Android.mk</code>数据实在巨大，因此Google加入了一个kati工具，用于将<code class="language-plaintext highlighter-rouge">Android.mk</code>转换成ninja的构建规则文件<code class="language-plaintext highlighter-rouge">buildxxx.ninja</code>，再使用ninja来进行构建工作。</p>

<p>编译速度快了一些，但是既然开始替换make，那最终目标要把make都取代，于是从Android8.0开始，Google为了进一步淘汰Makefile，因此引入了<code class="language-plaintext highlighter-rouge">Android.bp</code>文件来替换之前的<code class="language-plaintext highlighter-rouge">Android.mk</code>。（Android 7.0引入Android.bp，但是未正式使用）</p>

<p>Android.bp只是一个纯粹的配置文件，不包括分支、循环语句等控制流程，本质上就是一个json配置文件。Android.bp通过<code class="language-plaintext highlighter-rouge">Blueprint+soong</code>转换成<code class="language-plaintext highlighter-rouge">ninja</code>的构建规则文件<code class="language-plaintext highlighter-rouge">build.ninja</code>，再使用ninja来进行构建工作。</p>

<p>Android 10.0上，mk和bp编译的列表可以从<code class="language-plaintext highlighter-rouge">\out\.module_paths</code>中的<code class="language-plaintext highlighter-rouge">Android.bp.list</code>、<code class="language-plaintext highlighter-rouge">Android.mk.list</code>中看到，而Android10.0还有400多个mk文件没有被替换完。</p>

<p>Android 12上也没把Android.mk全部替换完….所以kati还得用着…（最新Android 14 AOSP查看仍有不少Android.mk）</p>

<hr />

<p><strong>Android编译演进过程：</strong></p>
<ol>
  <li>Android7.0之前 使用GNU Make</li>
  <li>Android7.0引入ninja、kati、Android.bp和soong构建系统</li>
  <li>Android8.0开始默认开启Android.bp</li>
  <li>Android9.0强制使用Android.bp（但是我们仍然可以使用Android.mk来创建模块）</li>
</ol>

<p>Google在Android 7.0之后，引入了Soong构建系统，旨在取代make，它利用<code class="language-plaintext highlighter-rouge">Kati GNU Make</code>克隆工具和<code class="language-plaintext highlighter-rouge">Ninja构建系统组件</code>来加速Android的构建。</p>

<p>Make构建系统得到了广泛的支持和使用，但在Android层面变得缓慢、容易出错、无法扩展且难以测试。Soong构建系统正好提供了Android build所需的灵活性。</p>

<p>Android S版本下system级别的app源码都采用了Android.bp作为编译配置，Soong作为核心编译系统发挥的作用也越来越大，如果vendor或package目录是自行开发的模块，建议编写Android.bp。
其中对于已有的Android.mk，可使用<code class="language-plaintext highlighter-rouge">out/soong/host/linux-x86/bin</code>目录下的<code class="language-plaintext highlighter-rouge">androidmk</code>工具，将<code class="language-plaintext highlighter-rouge">Android.mk</code>转换成<code class="language-plaintext highlighter-rouge">Android.bp</code></p>

<p><img src="../../assets/post/2024/2024-02-20-android12_BuildSystem_1/Android编译演变历史.jpg" alt="" /></p>

<p><strong>流程图展示：</strong></p>

<p><img src="../../assets/post/2024/2024-02-20-android12_BuildSystem_1\Android编译系统演变历史.png" alt="" /></p>

<hr />

<h2 id="32-编译模块构成">3.2. 编译模块构成</h2>

<p><strong>编译模块目录结构：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── bazel <span class="c"># 一个开源的构建和测试工具，类似于Make、Maven和Gradle，目前Google仍在实验开发阶段，暂时不关注</span>
├── blueprint <span class="c"># 一个meta-build系统，它读取描述需要构建的模块的bp文件，解析Blueprint文件翻译成ninja语法文件</span>
├── buildspec.mk.default -&gt; make/buildspec.mk.default
├── CleanSpec.mk -&gt; make/CleanSpec.mk
├── core -&gt; make/core
├── envsetup.sh -&gt; make/envsetup.sh <span class="c"># source加载环境变量</span>
├── make <span class="c"># make编译系统，基于makefile的构建方式正在被soong取代</span>
├── pesto <span class="c"># 一个概念验证脚本，用于使用Soong构建系统构建的目标在Android源代码树中创建模拟的bazel环境</span>
├── soong <span class="c"># go编写的新构建系统</span>
├── target -&gt; make/target
└── tools -&gt; make/tools
</code></pre></div></div>

<p>从编译模块目录中可以看到core文件夹被link到了<code class="language-plaintext highlighter-rouge">make/core</code>，<code class="language-plaintext highlighter-rouge">envsetup.sh</code>被link到<code class="language-plaintext highlighter-rouge">make/envsetup.sh</code>，这主要是为了对使用者屏蔽切换编译系统的差异</p>

<p><strong>此处可以重点看以下几个文件夹：</strong></p>

<ul>
  <li>bazel：一个开源的构建和测试工具，类似于 Make、Maven和Gradle。目前Google还需要继续完善开发保证工作流程的稳定性</li>
  <li>blueprint：用于处理Android.bp，编译生成*.ninja文件，用于做ninja的处理</li>
  <li>kati：用于处理Android.mk，编译生成*.ninja文件，用于做ninja的处理</li>
  <li>make：文件夹还是原始的make那一套流程，比如envsetup.sh</li>
  <li>soong：构建系统，核心编译为<code class="language-plaintext highlighter-rouge">soong_ui.bash</code></li>
</ul>

<p>其他还有一部分prebuilts目录下的，Google未释放源码的，列举一部分重要的bin可执行文件在build构建时会使用到：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>prebuilts/build-tools/linux-x86/bin<span class="nv">$ </span>tree
<span class="nb">.</span>
├── aidl
├── ckati
├── hidl-gen
├── make
├── ninja
├── nsjail
├── soong_zip
├── toybox
├── xz
├── zip2zip
├── zipalign
├── ziptime
└── ziptool
.....
</code></pre></div></div>

<hr />

<h3 id="321-soong编译步骤">3.2.1. soong编译步骤</h3>

<blockquote>
  <p>关于这小节，大概解释下编译涉及的几个重点对象，以及其中关联的关系、编译流程的步骤。<br />
后续会在编译详细过程中进行深入了解。</p>
</blockquote>

<p><strong>在编译过程中，bp和mk的大致流程如下：</strong></p>
<ol>
  <li>Android.bp会被收集到<code class="language-plaintext highlighter-rouge">out/soong/build.ninja.d</code>，然后blueprint以此为基础，生成<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code></li>
  <li>Android.mk会由<code class="language-plaintext highlighter-rouge">kati/ckati</code>生成为<code class="language-plaintext highlighter-rouge">out/build-&lt;product_name&gt;.ninja</code></li>
  <li>两个ninja文件会被整合进入<code class="language-plaintext highlighter-rouge">out/combined-&lt;product_name&gt;.ninja</code></li>
</ol>

<p>Android.mk文件、Android.bp文件、kati、Soong、Blueprint、Ninja之间的关系如下：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>1<span class="o">)</span> Android.bp <span class="nt">--</span><span class="o">&gt;</span> Blueprint <span class="nt">--</span><span class="o">&gt;</span> Soong <span class="nt">--</span><span class="o">&gt;</span> Ninja 
<span class="o">(</span>2<span class="o">)</span> Makefile or Android.mk <span class="nt">--</span><span class="o">&gt;</span> kati <span class="nt">--</span><span class="o">&gt;</span> Ninja 
<span class="o">(</span>3<span class="o">)</span> <span class="o">(</span>Android.mk <span class="nt">--</span><span class="o">&gt;</span> Soong<span class="o">(</span>androidmk<span class="o">)</span> <span class="nt">--</span><span class="o">&gt;</span> Blueprint <span class="nt">--</span><span class="o">&gt;</span> Android.bp<span class="o">)</span>
</code></pre></div></div>

<ul>
  <li>Blueprint是生成、解析Android.bp的工具，是Soong的一部分</li>
  <li>Soong是专为Android编译而设计的工具，Blueprint只是解析文件的形式，而Soong则解释内容的含义</li>
  <li>Android.mk可以通过Soong提供的<code class="language-plaintext highlighter-rouge">androidmk</code>转换成<code class="language-plaintext highlighter-rouge">Android.bp</code>，但仅限简单配置</li>
  <li>现存的Android.mk文件、既有的Android.bp，都会分别被转换成<strong>Ninja</strong>。从Android.mk与其它Makefile，会生成<code class="language-plaintext highlighter-rouge">out/build-&lt;product_name&gt;.ninja</code>文件。而从Android.bp，则会生成<code class="language-plaintext highlighter-rouge">out/soong/build.ninja</code>。此外，还会生成一个较小的<code class="language-plaintext highlighter-rouge">out/combined-&lt;product_name&gt;.ninja</code>文件，负责把二者组合起来，作为执行入口</li>
  <li>最终，Ninja文件才是真正直接控制源码编译的工具</li>
</ul>

<p><strong>soong编译步骤流程图大致如下，这是针对Android 10的流程，在Android 11开始移除了kati源码目录，ckati仅作为预编译二进制文件命令进行调用：</strong></p>

<p><img src="../../assets/post/2024/2024-02-20-android12_BuildSystem_1/soong构建流程.jpg" alt="" /></p>

<hr />

<p><strong>截取相关的几个文件部分内容：</strong></p>

<ul>
  <li>最终ninja整合的文件out/combined-<product_name>.ninja：</product_name></li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>builddir <span class="o">=</span> out
pool highmem_pool
 depth <span class="o">=</span> 2
<span class="c"># Android.mk模块</span>
subninja out/build-&lt;product_name&gt;.ninja
subninja out/build-&lt;product_name&gt;-package.ninja
<span class="c"># Android.bp模块</span>
subninja out/soong/build.ninja
</code></pre></div></div>

<ul>
  <li>文件out/soong/build.ninja.d：</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>out/soong/build.ninja: <span class="se">\</span>
 out/.module_paths/Android.bp.list <span class="se">\</span>
 Android.bp <span class="se">\</span>
 art/Android.bp <span class="se">\</span>
 art/adbconnection/Android.bp <span class="se">\</span>
 art/benchmark/Android.bp <span class="se">\</span>
 art/build/Android.bp <span class="se">\</span>
......
</code></pre></div></div>

<ul>
  <li>Android.bp最终的构建列表文件out/soong/build.ninja：</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ******************************************************************************</span>
<span class="c"># ***            This file is generated and should not be edited             ***</span>
<span class="c"># ******************************************************************************</span>
<span class="c">#</span>
<span class="c"># This file contains variables, rules, and pools with name prefixes indicating</span>
<span class="c"># they were generated by the following Go packages:</span>
<span class="c">#</span>
<span class="c">#     aidl                      [from Go package android/aidl]</span>
<span class="c">#     android                   [from Go package android/soong/android]</span>
<span class="c">#     android.soong.cc.config   [from Go package android/soong/cc/config]</span>
<span class="c">#     android.soong.java.config [from Go package android/soong/java/config]</span>
<span class="c">#     android.soong.rust.config [from Go package android/soong/rust/config]</span>
<span class="c">#     apex                      [from Go package android/apex]</span>
<span class="c">#     bloaty                    [from Go package android/soong/bloaty]</span>
<span class="c"># bootstrap                 [from Go package</span>
<span class="c"># github.com/google/blueprint/bootstrap]</span>
<span class="c">#     bpf                       [from Go package android/soong/bpf]</span>
<span class="c">#     cc                        [from Go package android/soong/cc]</span>
<span class="c">#     clang                     [from Go package android/soong/clang]</span>
<span class="c">#     configs                   [from Go package android/soong/kernel/configs]</span>
<span class="c">#     genrule                   [from Go package android/soong/genrule]</span>
<span class="c">#     hidl                      [from Go package android/hidl]</span>
<span class="c">#     java                      [from Go package android/soong/java]</span>
<span class="c">#     kernel                    [from Go package android/soong/kernel]</span>
<span class="c"># libchrome                 [from Go package</span>
......
</code></pre></div></div>

<ul>
  <li>Android.mk的构建列表文件out/build-<product_name>.ninja：</product_name></li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Generated by kati unknown</span>

rule rule0
 description <span class="o">=</span> Available sub-modules:
 rspfile <span class="o">=</span> <span class="nv">$out</span>.rsp
 rspfile_content <span class="o">=</span> <span class="nb">echo</span> <span class="s2">"AaptAutoVersionTest AaptBasicTest AaptSymlinkTest AaptTestStaticLib_App AccessibilityEventsLogger AccessoryChat Acces
soryDisplaySink AccessoryDisplaySource AccountManagementApp ActivityContextInstrumentOtherAppTest ActivityContextTest ActivityManagerPerfTests
 ActivityManagerPerfTestsStubApp1 ActivityManagerPerfTestsStubApp2 ActivityManagerPerfTestsStubApp3 ActivityManagerPerfTestsTestApp ActivityMa
nagerPerfTestsUtils ActivityTest AdbBackupApp
......
</span></code></pre></div></div>

<ul>
  <li>文件build-<product_name>-package.ninja：</product_name></li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Generated by kati unknown</span>

build _packaging_default_rule_: phony
 phony_output <span class="o">=</span> <span class="nb">true
</span>build _dist_ANGLE: phony
 phony_output <span class="o">=</span> <span class="nb">true
</span>build _dist_AaptAutoVersionTest: phony
 phony_output <span class="o">=</span> <span class="nb">true
</span>build _dist_AaptBasicTest: phony
......
</code></pre></div></div>

<hr />

<h3 id="322-kati说明android-11后隐藏源码">3.2.2. kati说明（Android 11后隐藏源码）</h3>

<blockquote>
  <p>kati脚本和代码目录：/build/kati，而在Android 11之后，该源码目录被移除（Google直接使用prebuilt预编译工具二进制命令）</p>
</blockquote>

<p>kati是一个基于Makefile来生成<code class="language-plaintext highlighter-rouge">ninja.build</code>的小项目。主要用于把<code class="language-plaintext highlighter-rouge">Makefiel</code>转成成<code class="language-plaintext highlighter-rouge">ninja file</code>，自身没有编译能力，转换后使用<code class="language-plaintext highlighter-rouge">Ninja</code>编译。</p>

<p>在Google Android演变的过程中，逐渐被淘汰。目前在Android 11之后已经没有该目录。（build/soong下面会有关于kati的go脚本文件）</p>

<p><strong>PS：</strong>从Android 11开始，不再释放源码，而是直接作为prebuilts预编译资源，Android 10之前在<code class="language-plaintext highlighter-rouge">android/build/kati</code></p>

<hr />

<h3 id="323-soong构建系统说明">3.2.3. Soong构建系统说明</h3>

<blockquote>
  <p>soong脚本和代码目录：/build/soong</p>
</blockquote>

<p>Soong构建系统是在Android 7.0 (Nougat) 中引入的，旨在取代Make。它利用Kati和Ninja构建系统组件来加速Android的构建。</p>

<p>Soong是由Go语言写的一个项目，从Android 7.0开始，在<code class="language-plaintext highlighter-rouge">prebuilts/go/</code>目录下新增了Go语言所需的运行环境，Soong在编译时使用，解析Android.bp，将之转化为Ninja文件，完成Android的选择编译，解析配置工作等。故Soong相当于Makefile编译系统的核心，即<code class="language-plaintext highlighter-rouge">build/make/core</code>下面的内容。</p>

<p>另外Soong还会编译产生一个<code class="language-plaintext highlighter-rouge">androidmk</code>命令，可以用来手动将<code class="language-plaintext highlighter-rouge">Android.mk</code>转换成<code class="language-plaintext highlighter-rouge">Android.bp</code>文件。不过这只对无选择、循环等复杂流程控制的Android.mk生效。（源码目录：<code class="language-plaintext highlighter-rouge">build/soong/androidmk/</code>，主要是go脚本文件）</p>

<hr />

<h3 id="324-blueprint说明">3.2.4. blueprint说明</h3>

<blockquote>
  <p>blueprint代码目录：/build/blueprint</p>
</blockquote>

<p>Blueprint由Go语言编写，是生成、解析Android.bp的工具，是Soong的一部分。Soong则是专为Android编译而设计的工具，Blueprint只是解析文件的形式，而Soong则解释内容的含义。</p>

<p>在Android编译最开始的准备阶段，会执行<code class="language-plaintext highlighter-rouge">build/soong/soong_ui.bash</code>进行环境准备。</p>

<p>对blueprint项目编译完成之后会在<code class="language-plaintext highlighter-rouge">out/soong/host/linux-x86/bin</code>目录下生成soong编译需要的5个执行文件(bpfix,bpfmt,bpmodify,microfatory,bpmodify)。</p>

<p>Soong是与Android强关联的一个项目，而Blueprint则相对比较独立，可以单独编译、使用。</p>

<hr />

<h3 id="325-ninja">3.2.5. Ninja</h3>

<p>AOSP在源码中已经内置了一个ninja执行文件：<code class="language-plaintext highlighter-rouge">prebuilts/build-tools/linux-x86/bin/ninja</code>
使用ninja，可以不经过make，直接执行ninja文件，完全避免重新生成，以及解析Makefile的运行开销。
使用ninja必须指定一个target，否则是全编译。 可以直接指定模块名，单独执行某个模块。 甚至可以指定具体的<code class="language-plaintext highlighter-rouge">*.o、.jar、.dex文件为target</code>，避免编译整个模块</p>

<p><code class="language-plaintext highlighter-rouge">./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-msmnile_gvmq.ninja DemoModuleName</code></p>

<p><strong>PS：</strong>单独使用ninja命令需要先用<code class="language-plaintext highlighter-rouge">make/mm/mmm</code>等编译过一次才行，如果更改了文件目录结构、增删文件、git pull等操作，或者修改Makefile或Android.bp文件，还需要再重新make一次。记住这个前提，高效运用ninja编译能快速得到精准的结果，可以节省Android.bp/Android.mk生成ninja文件这段流程的耗时。</p>

<hr />

<h3 id="326-androidmk">3.2.6. Android.mk</h3>

<p>Makefile编译系统的一部分，定义了一个模块的必要参数，使模块随着平台编译。通俗来讲就是告诉编译系统，以什么样的规则编译你的源代码，并生成对应的目标文件。</p>

<hr />

<h3 id="327-androidbp">3.2.7. Android.bp</h3>

<p>Android.bp，是用来替换Android.mk的配置文件。</p>

<hr />

<h2 id="33-编译命令步骤">3.3. 编译命令步骤</h2>

<p>Android整个系统的编译命令：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source </span>build/envsetup.sh   <span class="c"># 编译环境初始化，加载常用系统命令，加载平台信息（或. build/envsetup.sh ）</span>
lunch aosp_arm-eng         <span class="c"># 选择编译平台目标</span>
make <span class="nt">-j8</span>                   <span class="c"># 执行编译Android系统</span>
</code></pre></div></div>

<p>从编译命令开始，我们就需要开始详细的梳理了解Android编译系统了。</p>

<p>后面开始详细的针对编译命令进行代码流程梳理。</p>

<hr />

<h1 id="4-参考">4. 参考</h1>

<ul>
  <li><a href="https://xie.infoq.cn/article/16827a1777a8972bbc2669228">Bazel 构建工具介绍</a></li>
  <li><a href="https://bazel.google.cn/docs/android-build-performance?hl=zh-cn">Android 构建性能- Bazel</a></li>
  <li><a href="https://blog.csdn.net/mingmigndfds/article/details/125278741">编译系统入门篇-Android10.0编译系统（一）</a></li>
  <li><a href="https://source.android.google.cn/docs/setup/build?hl=fi">Google官方文档–Soong构建系统</a></li>
</ul>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="android" /><category term="build" /><summary type="html"><![CDATA[基于Android 12 AOSP源码（android-12.0.0_r3），对Android编译系统框架作学习梳理。 开篇从Android的源码结构入手，让我们先知道Android源码架构包含了哪些内容、Android编译结果目录有哪些内容。然后了解Android编译系统模块的结构组成。]]></summary></entry><entry><title type="html">Android IME输入法启动&amp;amp;显示&amp;amp;隐藏流程梳理以及常见问题&amp;amp;调试技巧小结</title><link href="https://alonealive.github.io/android/android_ime_StartShowHide_Tips/" rel="alternate" type="text/html" title="Android IME输入法启动&amp;amp;显示&amp;amp;隐藏流程梳理以及常见问题&amp;amp;调试技巧小结" /><published>2023-05-11T11:29:02+00:00</published><updated>2023-05-11T11:29:02+00:00</updated><id>https://alonealive.github.io/android/android_ime_StartShowHide_Tips</id><content type="html" xml:base="https://alonealive.github.io/android/android_ime_StartShowHide_Tips/"><![CDATA[<blockquote>
  <p>阅读Android AOSP 12版本代码，对输入法IME整体框架模块进行学习梳理，内容包含输入法框架三部分IMM、IMMS、IMS的启动流程、点击弹出流程、显示/隐藏流程，以及常见问题和调试技巧。</p>
</blockquote>

<hr />

<h1 id="1-ime整体框架">1. IME整体框架</h1>

<blockquote>
  <p>IME整体分为三个部分：</p>
</blockquote>

<h2 id="11-输入法客户端imm">1.1. 输入法客户端（IMM）</h2>

<blockquote>
  <p><strong>代码路径：</strong>frameworks/base/core/java/android/view/inputmethod/</p>
</blockquote>

<p><strong>主要指输入法框架的InputMethodManager, 每个app都一个实例, 用来和输入法控制端交互。运行在需要使用输入法的进程</strong></p>

<hr />

<h2 id="12-输入法管理端imms">1.2. 输入法管理端（IMMS）</h2>

<blockquote>
  <p><strong>代码路径：</strong>frameworks/base/services/core/java/com/android/server/inputmethod/</p>
</blockquote>

<p><strong>主要指输入法框架的InputMethodManagerService, 运行在system_server进程，工作内容包含以下：</strong></p>

<ul>
  <li>输入法服务端的绑定</li>
  <li>输入法服务端与输入法客户端的绑定</li>
  <li>输入法启用/关闭</li>
  <li>输入法显示/隐藏</li>
  <li>切换输入法</li>
</ul>

<hr />

<h2 id="13-输入法服务端ims">1.3. 输入法服务端（IMS）</h2>

<blockquote>
  <p><strong>代码路径：</strong>frameworks/base/core/java/android/inputmethodservice/</p>
</blockquote>

<p><strong>主要指输入法框架的InputMethodService, 这是一个输入法服务, 真正实现输入法界面, 控制字符输入的地方。运行在输入法进程, 例如某狗输入法进程</strong></p>

<hr />

<h2 id="14-子模块交互流程图">1.4. 子模块交互流程图</h2>

<p>输入法的整体交互过程如下:</p>

<ol>
  <li>IMM利用IInputMethodManager请求IMMS</li>
  <li>IMMS绑定输入法服务InputMethodService, 得到IInputMethod</li>
  <li>IMMS请求IInputMethod创建交互IInputMethodSession</li>
  <li>IMMS通过IInputMethodClient告知IMM IInputMethodSession</li>
  <li>IMM和IMS通过IInputMethodSession和IInputContext交互</li>
</ol>

<p><img src="../../assets/post/2023/2023-05-11-android_ime_StartShowHide_Tips/IME框架交互图.png" alt="" /></p>

<hr />

<h1 id="2-ime初始化启动流程">2. IME初始化启动流程</h1>

<h2 id="21-ime客户端imm初始化流程">2.1. IME客户端（IMM）初始化流程</h2>

<blockquote>
  <p>涉及代码文件路径：
frameworks/base/core/java/android/view/ViewRootImpl.java
frameworks/base/core/java/android/view/WindowManagerGlobal.java
frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
frameworks/base/core/java/com/android/internal/view/IInputMethodClient.aidl
frameworks/base/core/java/com/android/internal/view/IInputContext.aidl
frameworks/base/core/java/com/android/internal/view/IInputMethodManager.aidl
frameworks/base/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java</p>
</blockquote>

<h3 id="211-函数流程梳理">2.1.1. 函数流程梳理</h3>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 每次新增窗口window时，都会实例化ViewRootImpl，而ViewRootImpl在获取IWindowSession时会检查输入法是否已经初始化</span><span class="w">
</span><span class="n">ViewRootImpl.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">初始化构造函数</span><span class="err">，</span><span class="n">调用WindowManagerGlobal.getWindowSession</span><span class="p">()</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">WindowManagerGlobal.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">getWindowSession</span><span class="p">()</span><span class="n">调用InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary</span><span class="p">()</span><span class="w"> </span><span class="n">实例化全局调用InputMethodManager</span><span class="err">，</span><span class="n">即初始化IMM</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">InputMethodManager.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">ensureDefaultInstanceForDefaultDisplayIfNecessary</span><span class="p">()</span><span class="n">调用forContextInternal</span><span class="p">(</span><span class="n">Display.DEFAULT_DISPLAY</span><span class="p">,</span><span class="w"> </span><span class="n">Looper.getMainLooper</span><span class="p">())</span><span class="err">，</span><span class="n">入参默认displayID和looper</span><span class="w">
        </span><span class="c1"># 此处也说明，对于APP层，IMM有且只有一个实例，每次创建ViewRootImpl都会检查IMM是否实例化完成</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用forContextInternal函数</span><span class="err">，</span><span class="n">先从缓存Map中查询是否有IMM实例</span><span class="err">，</span><span class="n">如果没有则创建IMM实例</span><span class="err">，</span><span class="n">并添加到Map中</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用createInstance创建实例</span><span class="err">，</span><span class="n">然后在三目运算中默认固定调用createRealInstance</span><span class="p">(</span><span class="n">displayId</span><span class="p">,</span><span class="w"> </span><span class="n">looper</span><span class="p">)</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用createRealInstance函数</span><span class="err">，</span><span class="w">   </span><span class="err">（</span><span class="m">1</span><span class="err">）</span><span class="n">获取输入法服务service</span><span class="err">，</span><span class="n">即Context.INPUT_METHOD_SERVICE</span><span class="err">（</span><span class="n">service是AIDL接口文件IInputMethodManager.aidl</span><span class="err">）；</span><span class="w">
                                            </span><span class="err">（</span><span class="m">2</span><span class="err">）</span><span class="n">new</span><span class="w"> </span><span class="n">InputMethodManager</span><span class="p">(</span><span class="n">service</span><span class="p">,</span><span class="w"> </span><span class="n">displayId</span><span class="p">,</span><span class="w"> </span><span class="n">looper</span><span class="p">)</span><span class="n">创建实例</span><span class="w">
                                                    </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">InputMethodManager构造函数</span><span class="w">
                                                    </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="n">IInputConnectionWrapper</span><span class="w"> </span><span class="n">创建虚拟的输入法上下文</span><span class="err">，</span><span class="n">主要用于监听输入法服务的激活状态</span><span class="err">，</span><span class="n">接受输入事件</span><span class="w">
                                             </span><span class="c1"># 添加IMM实例到输入法service服务中</span><span class="w">
                                             </span><span class="c1"># 此处两个入参都是AIDL接口类型的对象</span><span class="w">
                                             </span><span class="c1"># （1）IInputMethodClient.aidl：输入法客户端, 主要用于报告输入法当前的状态, 让APP应用端的IMM做出相应的处理</span><span class="w">
                                             </span><span class="c1"># （2）IInputContext.aidl：输入法上下文, 主要用于操作字符输入操作, 让当前接收字符的view进行处理</span><span class="w">
                                            </span><span class="err">（</span><span class="m">3</span><span class="err">）</span><span class="n">调用service.addClient</span><span class="p">(</span><span class="n">imm.mClient</span><span class="w"> </span><span class="o">//</span><span class="p">[</span><span class="n">AIDL对象</span><span class="err">，</span><span class="n">即IInputMethodClient</span><span class="p">],</span><span class="w"> </span><span class="n">imm.mIInputContext</span><span class="o">//</span><span class="p">[</span><span class="n">AIDL对象</span><span class="err">，</span><span class="n">IInputContext</span><span class="p">],</span><span class="w"> </span><span class="n">displayId</span><span class="p">)</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">IInputMethodManager.aidl</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">调用addClient</span><span class="err">（</span><span class="n">跨进程通信到IMMS</span><span class="err">）</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">服务端InputMethodManagerService.java</span><span class="w"> </span><span class="s2">"extends IInputMethodManager.Stub"</span><span class="w"> </span><span class="o">--</span><span class="w">  </span><span class="n">调用addClient函数</span><span class="err">，</span><span class="n">创建ClientState对象</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用内部静态类ClientState的构造函数</span><span class="err">，</span><span class="n">保存client相关状态属性</span><span class="w">
</span></code></pre></div></div>

<p><strong>综上代码流程梳理，可以看出：</strong></p>
<ol>
  <li>对于每个APP应用，IMM有且只有一个实例，并且每次创建ViewRootImpl时，都会检查IMM是否已经实例化成功</li>
  <li>实例化IMM对象时，会涉及到两个AIDL接口文件，一个用于应用端IMM处理输入法当前状态，一个用于输入法上下文，创建一个虚拟的InputContext代表输入空间，用于监听输入法激活状态</li>
  <li>实例化过程中会有个displayid，用于多屏幕显示（通常情况下默认是default display=0）</li>
  <li>实例化最后，会通过AIDL的addClient接口函数，将IMM添加到IMMS中，如此IMM实例化完成</li>
</ol>

<hr />

<h3 id="212-代码详细说明">2.1.2. 代码详细说明</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//ViewRootImpl.java</span>
    <span class="kd">public</span> <span class="nf">ViewRootImpl</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nc">Display</span> <span class="n">display</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">display</span><span class="o">,</span> <span class="nc">WindowManagerGlobal</span><span class="o">.</span><span class="na">getWindowSession</span><span class="o">(),</span>
                <span class="kc">false</span> <span class="cm">/* useSfChoreographer */</span><span class="o">);</span>
    <span class="o">}</span>

<span class="c1">//WindowManagerGlobal.java</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="nc">IWindowSession</span> <span class="nf">getWindowSession</span><span class="o">()</span> <span class="o">{</span>
        <span class="kd">synchronized</span> <span class="o">(</span><span class="nc">WindowManagerGlobal</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">sWindowSession</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">try</span> <span class="o">{</span>
                    <span class="c1">//调用该函数，初始化IMM</span>
                    <span class="nc">InputMethodManager</span><span class="o">.</span><span class="na">ensureDefaultInstanceForDefaultDisplayIfNecessary</span><span class="o">();</span>
                    <span class="o">......</span>
                <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RemoteException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                    <span class="k">throw</span> <span class="n">e</span><span class="o">.</span><span class="na">rethrowFromSystemServer</span><span class="o">();</span>
                <span class="o">}</span>
            <span class="o">}</span>
            <span class="k">return</span> <span class="n">sWindowSession</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>

<span class="c1">//InputMethodManager.java</span>
   <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">ensureDefaultInstanceForDefaultDisplayIfNecessary</span><span class="o">()</span> <span class="o">{</span>
        <span class="c1">//默认default display</span>
        <span class="n">forContextInternal</span><span class="o">(</span><span class="nc">Display</span><span class="o">.</span><span class="na">DEFAULT_DISPLAY</span><span class="o">,</span> <span class="nc">Looper</span><span class="o">.</span><span class="na">getMainLooper</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="nc">InputMethodManager</span> <span class="nf">forContextInternal</span><span class="o">(</span><span class="kt">int</span> <span class="n">displayId</span><span class="o">,</span> <span class="nc">Looper</span> <span class="n">looper</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">isDefaultDisplay</span> <span class="o">=</span> <span class="n">displayId</span> <span class="o">==</span> <span class="nc">Display</span><span class="o">.</span><span class="na">DEFAULT_DISPLAY</span><span class="o">;</span>
        <span class="kd">synchronized</span> <span class="o">(</span><span class="n">sLock</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">//从缓存Map中查找是否由default display的IMM实例</span>
            <span class="nc">InputMethodManager</span> <span class="n">instance</span> <span class="o">=</span> <span class="n">sInstanceMap</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">displayId</span><span class="o">);</span>
            <span class="c1">//如果存在实例，则直接返回</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">instance</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">return</span> <span class="n">instance</span><span class="o">;</span>
            <span class="o">}</span>
            <span class="c1">//初始化创建实例</span>
            <span class="n">instance</span> <span class="o">=</span> <span class="n">createInstance</span><span class="o">(</span><span class="n">displayId</span><span class="o">,</span> <span class="n">looper</span><span class="o">);</span>
            <span class="c1">//如果是用于default display使用，则存储到sInstance中作为全局单例实例</span>
            <span class="k">if</span> <span class="o">(</span><span class="n">sInstance</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">isDefaultDisplay</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">sInstance</span> <span class="o">=</span> <span class="n">instance</span><span class="o">;</span>
            <span class="o">}</span>
            <span class="c1">//将IMM实例保存到Map中</span>
            <span class="n">sInstanceMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">displayId</span><span class="o">,</span> <span class="n">instance</span><span class="o">);</span>
            <span class="k">return</span> <span class="n">instance</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="nc">InputMethodManager</span> <span class="nf">createInstance</span><span class="o">(</span><span class="kt">int</span> <span class="n">displayId</span><span class="o">,</span> <span class="nc">Looper</span> <span class="n">looper</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">//isInEditMode固定返回false，直接调用createRealInstance</span>
        <span class="k">return</span> <span class="nf">isInEditMode</span><span class="o">()</span> <span class="o">?</span> <span class="n">createStubInstance</span><span class="o">(</span><span class="n">displayId</span><span class="o">,</span> <span class="n">looper</span><span class="o">)</span>
                <span class="o">:</span> <span class="n">createRealInstance</span><span class="o">(</span><span class="n">displayId</span><span class="o">,</span> <span class="n">looper</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="nc">InputMethodManager</span> <span class="nf">createRealInstance</span><span class="o">(</span><span class="kt">int</span> <span class="n">displayId</span><span class="o">,</span> <span class="nc">Looper</span> <span class="n">looper</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">//IInputMethodManager是AIDL接口文件，用于跨进程通信到IMMS（InputMethodManagerService）</span>
        <span class="kd">final</span> <span class="nc">IInputMethodManager</span> <span class="n">service</span><span class="o">;</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="c1">//获取service</span>
            <span class="n">service</span> <span class="o">=</span> <span class="nc">IInputMethodManager</span><span class="o">.</span><span class="na">Stub</span><span class="o">.</span><span class="na">asInterface</span><span class="o">(</span>
                    <span class="nc">ServiceManager</span><span class="o">.</span><span class="na">getServiceOrThrow</span><span class="o">(</span><span class="nc">Context</span><span class="o">.</span><span class="na">INPUT_METHOD_SERVICE</span><span class="o">));</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ServiceNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">//创建IMM实例</span>
        <span class="kd">final</span> <span class="nc">InputMethodManager</span> <span class="n">imm</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">InputMethodManager</span><span class="o">(</span><span class="n">service</span><span class="o">,</span> <span class="n">displayId</span><span class="o">,</span> <span class="n">looper</span><span class="o">);</span>
        <span class="c1">//将PID/UID和每个IME客户端关联，然后作为跨进程服务端IPC使用梳理</span>
        <span class="c1">//如果作为同进程内调用梳理，则需要确保Binder.getCalling{Pid, Uid}()返回Process.my{Pid, Uid}()</span>
        <span class="c1">//无论哪种情况，都要调用Binder的{clear, restore}CallingIdentity()函数，对跨进程没有影响，对同进程可以满足需求实现</span>
        <span class="kd">final</span> <span class="kt">long</span> <span class="n">identity</span> <span class="o">=</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">clearCallingIdentity</span><span class="o">();</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="c1">// 添加 IMM 实例到输入法服务</span>
            <span class="c1">// imm.mClient 是一个aidl对象, mClient即new IInputMethodClient.Stub()，AIDL接口</span>
            <span class="c1">// imm.mIInputContext 是一个aidl对象, IInputContext，AIDL接口</span>
            <span class="n">service</span><span class="o">.</span><span class="na">addClient</span><span class="o">(</span><span class="n">imm</span><span class="o">.</span><span class="na">mClient</span><span class="o">,</span> <span class="n">imm</span><span class="o">.</span><span class="na">mIInputContext</span><span class="o">,</span> <span class="n">displayId</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RemoteException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">e</span><span class="o">.</span><span class="na">rethrowFromSystemServer</span><span class="o">();</span>
        <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
            <span class="nc">Binder</span><span class="o">.</span><span class="na">restoreCallingIdentity</span><span class="o">(</span><span class="n">identity</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="n">imm</span><span class="o">;</span>
    <span class="o">}</span>

<span class="c1">//InputMethodManagerService.java</span>
    <span class="c1">//由每个APP应用进程调用，作为输入法开始与交互的准备</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">addClient</span><span class="o">(</span><span class="nc">IInputMethodClient</span> <span class="n">client</span><span class="o">,</span> <span class="nc">IInputContext</span> <span class="n">inputContext</span><span class="o">,</span>
            <span class="kt">int</span> <span class="n">selfReportedDisplayId</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">//获取调用的uid和pid（即InputMethodManager实际运行所在的UID/PID）</span>
        <span class="c1">//两种情况下调用此方法：</span>
        <span class="c1">//1.IMM正在另一个进程中实例化</span>
        <span class="c1">//2.IMM正在同一个进程中实例化，</span>
        <span class="kd">final</span> <span class="kt">int</span> <span class="n">callerUid</span> <span class="o">=</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">getCallingUid</span><span class="o">();</span>
        <span class="kd">final</span> <span class="kt">int</span> <span class="n">callerPid</span> <span class="o">=</span> <span class="nc">Binder</span><span class="o">.</span><span class="na">getCallingPid</span><span class="o">();</span>
        <span class="kd">synchronized</span> <span class="o">(</span><span class="n">mMethodMap</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// TODO: Optimize this linear search.</span>
            <span class="kd">final</span> <span class="kt">int</span> <span class="n">numClients</span> <span class="o">=</span> <span class="n">mClients</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
            <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">numClients</span><span class="o">;</span> <span class="o">++</span><span class="n">i</span><span class="o">)</span> <span class="o">{</span>
                <span class="kd">final</span> <span class="nc">ClientState</span> <span class="n">state</span> <span class="o">=</span> <span class="n">mClients</span><span class="o">.</span><span class="na">valueAt</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
                <span class="k">if</span> <span class="o">(</span><span class="n">state</span><span class="o">.</span><span class="na">uid</span> <span class="o">==</span> <span class="n">callerUid</span> <span class="o">&amp;&amp;</span> <span class="n">state</span><span class="o">.</span><span class="na">pid</span> <span class="o">==</span> <span class="n">callerPid</span>
                        <span class="o">&amp;&amp;</span> <span class="n">state</span><span class="o">.</span><span class="na">selfReportedDisplayId</span> <span class="o">==</span> <span class="n">selfReportedDisplayId</span><span class="o">)</span> <span class="o">{</span>
                    <span class="k">throw</span> <span class="k">new</span> <span class="nf">SecurityException</span><span class="o">(</span><span class="s">"uid="</span> <span class="o">+</span> <span class="n">callerUid</span> <span class="o">+</span> <span class="s">"/pid="</span> <span class="o">+</span> <span class="n">callerPid</span>
                            <span class="o">+</span> <span class="s">"/displayId="</span> <span class="o">+</span> <span class="n">selfReportedDisplayId</span> <span class="o">+</span> <span class="s">" is already registered."</span><span class="o">);</span>
                <span class="o">}</span>
            <span class="o">}</span>
            <span class="c1">//利用IBinder.deathRecipient监听client存活状态</span>
            <span class="c1">//如果client的Binder死亡，则将Client从缓存Map中移除</span>
            <span class="kd">final</span> <span class="nc">ClientDeathRecipient</span> <span class="n">deathRecipient</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ClientDeathRecipient</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">client</span><span class="o">);</span>
            <span class="k">try</span> <span class="o">{</span>
                <span class="n">client</span><span class="o">.</span><span class="na">asBinder</span><span class="o">().</span><span class="na">linkToDeath</span><span class="o">(</span><span class="n">deathRecipient</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RemoteException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
            <span class="o">}</span>
            <span class="c1">//此处不验证displayID，后续每当客户端需要与指定的交互时，就需要检查displayID</span>
            <span class="c1">//此处创建ClientState对象，将client和inputContext缓存进去，然后将该对象保存到缓存Map mClients中</span>
            <span class="n">mClients</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">client</span><span class="o">.</span><span class="na">asBinder</span><span class="o">(),</span> <span class="k">new</span> <span class="nc">ClientState</span><span class="o">(</span><span class="n">client</span><span class="o">,</span> <span class="n">inputContext</span><span class="o">,</span> <span class="n">callerUid</span><span class="o">,</span>
                    <span class="n">callerPid</span><span class="o">,</span> <span class="n">selfReportedDisplayId</span><span class="o">,</span> <span class="n">deathRecipient</span><span class="o">));</span>
        <span class="o">}</span>
    <span class="o">}</span>
</code></pre></div></div>

<hr />

<h3 id="213-imm初始化序列图">2.1.3. IMM初始化序列图</h3>

<p><img src="../../assets/post/2023/2023-05-11-android_ime_StartShowHide_Tips/IMM初始化流程序列图.svg" alt="" /></p>

<hr />

<h2 id="22-ime管理端imms初始化流程">2.2. IME管理端（IMMS）初始化流程</h2>

<blockquote>
  <p>IMMS运行在system server进程中，属于系统服务的一部分，用于控制输入法的显示/隐藏、切换、绑定等操作。</p>
</blockquote>

<blockquote>
  <p>涉及代码文件路径：
frameworks/base/services/java/com/android/server/SystemServer.java
frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
frameworks/base/core/java/android/os/SystemService.java
frameworks/base/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
frameworks/base/packages/SettingsProvider/res/values/defaults.xml</p>
</blockquote>

<hr />

<h3 id="221-初始化函数流程梳理">2.2.1. 初始化函数流程梳理</h3>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 我们从systemserver的startOtherServices函数开始梳理</span><span class="w">
</span><span class="c1"># 此处需要注意，因为我梳理的是IMMS，而Google还提供了一个MultiClientInputMethodManagerService多客户端输入法服务进程，此处不梳理</span><span class="w">
</span><span class="c1"># PS：从InputMethodManagerService代码文件中可以看到，Lifecycle是里面的一个内部类，继承systemservice</span><span class="w">
</span><span class="n">SystemServer.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">startOtherServices</span><span class="err">，</span><span class="n">然后通过SystemServiceManager的startService启动IMMS</span><span class="err">，</span><span class="n">传入class</span><span class="w"> </span><span class="n">name</span><span class="err">：</span><span class="s2">"InputMethodManagerService.Lifecycle.class"</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">SystemServiceManager.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">startService有好几个重载的方法</span><span class="err">，</span><span class="n">说明下</span><span class="err">：</span><span class="w">
                                            </span><span class="err">（</span><span class="m">1</span><span class="err">）</span><span class="n">第一个startService方法</span><span class="err">，</span><span class="n">入参className即</span><span class="s2">"InputMethodManagerService.Lifecycle.class"</span><span class="err">，</span><span class="n">将其作为入参调用loadClassFromLoader</span><span class="w">
                                            </span><span class="err">（</span><span class="m">2</span><span class="err">）</span><span class="n">loadClassFromLoader会通过反射方法得到具体的Class类</span><span class="err">，</span><span class="n">返回Class</span><span class="o">&lt;</span><span class="n">SystemService</span><span class="o">&gt;</span><span class="n">类型的服务类</span><span class="err">，</span><span class="n">即继承SystemService的Lifecycle</span><span class="w">
                                            </span><span class="err">（</span><span class="m">3</span><span class="err">）</span><span class="n">调用第二个startService方法</span><span class="err">，</span><span class="n">入参即serviceClass</span><span class="w">
                                            </span><span class="err">（</span><span class="m">4</span><span class="err">）</span><span class="n">先通过</span><span class="s2">"SystemService.class.isAssignableFrom(serviceClass)"</span><span class="n">判断该类是否是SysteService的子类</span><span class="w">
                                            </span><span class="err">（</span><span class="m">5</span><span class="err">）</span><span class="n">然后通过反射构造类的实例</span><span class="s2">"service=constructor.newInstance(mContext)"</span><span class="err">，</span><span class="n">即实例化Lifecycle类</span><span class="err">（</span><span class="n">重点</span><span class="err">）</span><span class="w">
                                            </span><span class="err">（</span><span class="m">6</span><span class="err">）</span><span class="n">调用第三个startService方法</span><span class="err">，</span><span class="n">入参该Lifecycle对象</span><span class="w">
                                            </span><span class="err">（</span><span class="m">2</span><span class="err">）</span><span class="n">先将该service添加到mServices列表中</span><span class="err">，</span><span class="n">然后调用SystemService.java的onStart函数</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">InputMethodManagerService.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">通过上面的流程看到</span><span class="err">，</span><span class="n">此处会先调用Lifecycle类的构造函数</span><span class="err">，</span><span class="n">然后调用onStart函数</span><span class="w">
                                            </span><span class="err">（</span><span class="m">1</span><span class="err">）</span><span class="n">构造函数会创建IMMS实例</span><span class="err">，</span><span class="n">即</span><span class="s2">"InputMethodManagerService mService=new InputMethodManagerService(context)"</span><span class="w">
                                            </span><span class="err">（</span><span class="m">2</span><span class="err">）</span><span class="n">onStart函数会将该mService通过publishBinderService方法发布到系统服务中</span><span class="err">，</span><span class="n">以便其他进行可以进行Binder获取到</span><span class="err">（</span><span class="n">即添加到dev</span><span class="o">/</span><span class="n">binder域管理</span><span class="err">）</span><span class="w">
        </span><span class="c1"># 主要讲述IMMS对象被创建，从构造函数梳理</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用构造函数</span><span class="err">，</span><span class="n">主要用于注册一些监听事件</span><span class="p">,</span><span class="w"> </span><span class="n">获取必须的系统服务</span><span class="p">,</span><span class="w"> </span><span class="n">UI相关的组件等</span><span class="w">
</span></code></pre></div></div>

<p><strong>PS：</strong></p>
<ol>
  <li>SystemService启动输入法服务时，会有个判断启动IMMS还是MCIMMS。MULTI_CLIENT_IME_ENABLED（即<code class="language-plaintext highlighter-rouge">persist.debug.multi_client_ime</code>或<code class="language-plaintext highlighter-rouge">ro.sys.multi_client_ime</code>）开启，启动MultiClientInputMethodManagerService服务，否则启动InputMethodManagerService服务</li>
  <li>关于MultiClientInputMethodManagerService就是多会话输入法，支持每屏幕焦点是启用此功能的前提。如果不支持，则无法启用此功能。由于安全限制，每屏幕焦点限制规定只有一小部分设备支持此功能。（详细参考Google官方文档和源码）</li>
</ol>

<hr />

<h3 id="222-systemrunning函数流程梳理">2.2.2. systemRunning函数流程梳理</h3>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 我们从systemserver的startOtherServices函数开始梳理</span><span class="w">
</span><span class="c1"># startBootPhase在服务startservice后执行，该函数将service分段处理，</span><span class="w">
</span><span class="c1"># 例如此处IMMS在SystemService.PHASE_WAIT_FOR_SENSOR_SERVICE（200）和SystemService.PHASE_LOCK_SETTINGS_READY（480）之间</span><span class="w">
</span><span class="n">SystemServer.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">startOtherServices</span><span class="err">，</span><span class="n">然后通过SystemServiceManager的startBootPhase</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">SystemServiceManager.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">startBootPhase遍历两个分段之间的服务</span><span class="err">，</span><span class="n">然后调用对应service的onBootPhase</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">InputMethodManagerService.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">调用Lifecycle类的onBootPhase函数</span><span class="err">，</span><span class="n">然后调用InputMethodManagerService的systemRunning函数</span><span class="err">，</span><span class="n">主要内容</span><span class="err">：</span><span class="w">
                                        </span><span class="err">（</span><span class="m">1</span><span class="err">）</span><span class="n">MyPackageMonitor内部类register注册</span><span class="err">，</span><span class="n">监听安装包的变化</span><span class="err">，</span><span class="n">包含安装</span><span class="err">，</span><span class="n">卸载</span><span class="err">，</span><span class="n">更新等</span><span class="w">
                                        </span><span class="err">（</span><span class="m">2</span><span class="err">）</span><span class="n">SettingsObserver注册</span><span class="err">，</span><span class="n">监听当前用户的各种输入法相关的settingprovider变化</span><span class="err">，</span><span class="n">例如</span><span class="err">：</span><span class="n">默认输入法</span><span class="err">，</span><span class="n">输入法列表</span><span class="err">，</span><span class="n">输入法语言等</span><span class="w">
                                        </span><span class="err">（</span><span class="m">3</span><span class="err">）</span><span class="n">getSelectedInputMethod获取用户设置的输入法default_input_method</span><span class="err">，</span><span class="n">此处是查询settings数据库的默认输入法</span><span class="err">（</span><span class="n">frameworks</span><span class="o">/</span><span class="n">base</span><span class="o">/</span><span class="n">packages</span><span class="o">/</span><span class="n">SettingsProvider</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">com</span><span class="o">/</span><span class="n">android</span><span class="o">/</span><span class="n">providers</span><span class="o">/</span><span class="n">settings</span><span class="o">/</span><span class="n">DatabaseHelper.java</span><span class="err">）</span><span class="w">
                                        </span><span class="err">（</span><span class="m">4</span><span class="err">）</span><span class="n">buildInputMethodListLocked</span><span class="err">，</span><span class="n">如果没有默认输入法则入参false</span><span class="err">，</span><span class="n">该函数内容如下</span><span class="err">：</span><span class="w">
                                                </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">查询输入法服务信息</span><span class="err">，</span><span class="n">然后将信息储存到mMethodList</span><span class="err">，</span><span class="n">mMethodMap</span><span class="err">，</span><span class="n">mMyPackageMonitor中</span><span class="err">；</span><span class="w">
                                                </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用chooseNewDefaultIMELocked选择一个新的输入法</span><span class="err">；</span><span class="w">
                                                </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">updateInputMethodsFromSettingsLocked遍历所有输入法</span><span class="err">，</span><span class="n">如果输入法存在被禁用的组件</span><span class="err">，</span><span class="n">则重新启用调用setInputMethodLocked方法完成对输入法设置</span><span class="err">，</span><span class="n">和输入法发生变化的广播</span><span class="err">（</span><span class="n">ACTION_INPUT_METHOD_CHANGED</span><span class="err">）</span><span class="n">的发送</span><span class="err">（</span><span class="n">该函数中调用setInputMethodLocked</span><span class="err">）</span><span class="w">
</span></code></pre></div></div>

<p><strong>PS:</strong>一般我们修改默认输入法，<code class="language-plaintext highlighter-rouge">packages/SettingsProvider/res/values/defaults.xml</code>
数据库配置添加<code class="language-plaintext highlighter-rouge">def_input_method</code>和<code class="language-plaintext highlighter-rouge">def_enable_input_methods</code>，然后<code class="language-plaintext highlighter-rouge">frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java</code>对应添加loadStringSetting加载引用<code class="language-plaintext highlighter-rouge">DEFAULT_INPUT_METHOD</code>和<code class="language-plaintext highlighter-rouge">ENABLED_INPUT_METHODS</code></p>

<hr />

<h3 id="223-代码详细说明">2.2.3. 代码详细说明</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//SystemServer.java</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">startOtherServices</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="nc">TimingsTraceAndSlog</span> <span class="n">t</span><span class="o">)</span> <span class="o">{</span>
    <span class="o">......</span>
        <span class="c1">// Bring up services needed for UI.</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">mFactoryTestMode</span> <span class="o">!=</span> <span class="nc">FactoryTest</span><span class="o">.</span><span class="na">FACTORY_TEST_LOW_LEVEL</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">t</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="s">"StartInputMethodManagerLifecycle"</span><span class="o">);</span>
            <span class="k">if</span> <span class="o">(</span><span class="nc">InputMethodSystemProperty</span><span class="o">.</span><span class="na">MULTI_CLIENT_IME_ENABLED</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">startService</span><span class="o">(</span>
                        <span class="nc">MultiClientInputMethodManagerService</span><span class="o">.</span><span class="na">Lifecycle</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
                <span class="c1">//启动IMMS服务</span>
                <span class="n">mSystemServiceManager</span><span class="o">.</span><span class="na">startService</span><span class="o">(</span><span class="nc">InputMethodManagerService</span><span class="o">.</span><span class="na">Lifecycle</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="o">}</span>
            <span class="n">t</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">();</span>
            <span class="o">......</span>
        <span class="o">}</span>
    <span class="o">...</span>
<span class="o">}</span>

<span class="c1">//SystemServiceManager.java</span>
    <span class="c1">//第一个startService函数</span>
    <span class="kd">public</span> <span class="nc">SystemService</span> <span class="nf">startService</span><span class="o">(</span><span class="nc">String</span> <span class="n">className</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">//调用loadClassFromLoader</span>
        <span class="kd">final</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="nc">SystemService</span><span class="o">&gt;</span> <span class="n">serviceClass</span> <span class="o">=</span> <span class="n">loadClassFromLoader</span><span class="o">(</span><span class="n">className</span><span class="o">,</span>
                <span class="k">this</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getClassLoader</span><span class="o">());</span>
        <span class="k">return</span> <span class="nf">startService</span><span class="o">(</span><span class="n">serviceClass</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="nc">SystemService</span><span class="o">&gt;</span> <span class="nf">loadClassFromLoader</span><span class="o">(</span><span class="nc">String</span> <span class="n">className</span><span class="o">,</span>
            <span class="nc">ClassLoader</span> <span class="n">classLoader</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="c1">//通过反射方法得到具体的Class类，返回Class&lt;SystemService&gt;类型的服务类，即继承SystemService的Lifecycle</span>
            <span class="k">return</span> <span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="nc">SystemService</span><span class="o">&gt;)</span> <span class="nc">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="n">className</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="n">classLoader</span><span class="o">);</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">ClassNotFoundException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
            <span class="o">.......</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="c1">//第二个startService函数</span>
    <span class="kd">public</span> <span class="o">&lt;</span><span class="no">T</span> <span class="kd">extends</span> <span class="nc">SystemService</span><span class="o">&gt;</span> <span class="no">T</span> <span class="nf">startService</span><span class="o">(</span><span class="nc">Class</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">serviceClass</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="kd">final</span> <span class="nc">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">serviceClass</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span>
            <span class="nc">Slog</span><span class="o">.</span><span class="na">i</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Starting "</span> <span class="o">+</span> <span class="n">name</span><span class="o">);</span>
            <span class="nc">Trace</span><span class="o">.</span><span class="na">traceBegin</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_SYSTEM_SERVER</span><span class="o">,</span> <span class="s">"StartService "</span> <span class="o">+</span> <span class="n">name</span><span class="o">);</span>

            <span class="c1">// 判断该class该类是否是SysteService的子类</span>
            <span class="k">if</span> <span class="o">(!</span><span class="nc">SystemService</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">isAssignableFrom</span><span class="o">(</span><span class="n">serviceClass</span><span class="o">))</span> <span class="o">{</span>
                <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"Failed to create "</span> <span class="o">+</span> <span class="n">name</span>
                        <span class="o">+</span> <span class="s">": service must extend "</span> <span class="o">+</span> <span class="nc">SystemService</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
            <span class="o">}</span>
            <span class="kd">final</span> <span class="no">T</span> <span class="n">service</span><span class="o">;</span>
            <span class="k">try</span> <span class="o">{</span>
                <span class="c1">//通过反射构造类的实例，即实例化Lifecycle类</span>
                <span class="nc">Constructor</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="n">constructor</span> <span class="o">=</span> <span class="n">serviceClass</span><span class="o">.</span><span class="na">getConstructor</span><span class="o">(</span><span class="nc">Context</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
                <span class="c1">//newInstance实例化</span>
                <span class="n">service</span> <span class="o">=</span> <span class="n">constructor</span><span class="o">.</span><span class="na">newInstance</span><span class="o">(</span><span class="n">mContext</span><span class="o">);</span>
            <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InstantiationException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"Failed to create service "</span> <span class="o">+</span> <span class="n">name</span>
                        <span class="o">+</span> <span class="s">": service could not be instantiated"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span>
            <span class="o">}</span> <span class="o">......</span>
            <span class="o">......</span>
            <span class="c1">//调用第三个startService</span>
            <span class="n">startService</span><span class="o">(</span><span class="n">service</span><span class="o">);</span>
            <span class="k">return</span> <span class="n">service</span><span class="o">;</span>
        <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
            <span class="nc">Trace</span><span class="o">.</span><span class="na">traceEnd</span><span class="o">(</span><span class="nc">Trace</span><span class="o">.</span><span class="na">TRACE_TAG_SYSTEM_SERVER</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="c1">//第三个startService函数</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">startService</span><span class="o">(</span><span class="nd">@NonNull</span> <span class="kd">final</span> <span class="nc">SystemService</span> <span class="n">service</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// Register it.将service注册到mServices列表中</span>
        <span class="n">mServices</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">service</span><span class="o">);</span>
        <span class="c1">// Start it.</span>
        <span class="kt">long</span> <span class="n">time</span> <span class="o">=</span> <span class="nc">SystemClock</span><span class="o">.</span><span class="na">elapsedRealtime</span><span class="o">();</span>
        <span class="k">try</span> <span class="o">{</span>
            <span class="c1">//调用该service的onStart函数</span>
            <span class="n">service</span><span class="o">.</span><span class="na">onStart</span><span class="o">();</span>
        <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RuntimeException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">RuntimeException</span><span class="o">(</span><span class="s">"Failed to start service "</span> <span class="o">+</span> <span class="n">service</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getName</span><span class="o">()</span>
                    <span class="o">+</span> <span class="s">": onStart threw an exception"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="n">warnIfTooLong</span><span class="o">(</span><span class="nc">SystemClock</span><span class="o">.</span><span class="na">elapsedRealtime</span><span class="o">()</span> <span class="o">-</span> <span class="n">time</span><span class="o">,</span> <span class="n">service</span><span class="o">,</span> <span class="s">"onStart"</span><span class="o">);</span>
    <span class="o">}</span>

<span class="c1">//InputMethodManagerService.java</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">Lifecycle</span> <span class="kd">extends</span> <span class="nc">SystemService</span> <span class="o">{</span>
        <span class="kd">private</span> <span class="nc">InputMethodManagerService</span> <span class="n">mService</span><span class="o">;</span>
        <span class="c1">//实例化时调用构造函数</span>
        <span class="kd">public</span> <span class="nf">Lifecycle</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
            <span class="kd">super</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
            <span class="c1">//创建InputMethodManagerService IMMS对象，然后调用IMMS构造函数</span>
            <span class="n">mService</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">InputMethodManagerService</span><span class="o">(</span><span class="n">context</span><span class="o">);</span>
        <span class="o">}</span>
        
        <span class="c1">//在startService中调用到此处</span>
        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onStart</span><span class="o">()</span> <span class="o">{</span>
            <span class="c1">//将IMMS service添加到LocalServices</span>
            <span class="nc">LocalServices</span><span class="o">.</span><span class="na">addService</span><span class="o">(</span><span class="nc">InputMethodManagerInternal</span><span class="o">.</span><span class="na">class</span><span class="o">,</span>
                    <span class="k">new</span> <span class="nf">LocalServiceImpl</span><span class="o">(</span><span class="n">mService</span><span class="o">));</span>
            <span class="c1">//发布到系统服务中，以便其他进行可以进行Binder获取到（即添加到dev/binder域管理）</span>
            <span class="n">publishBinderService</span><span class="o">(</span><span class="nc">Context</span><span class="o">.</span><span class="na">INPUT_METHOD_SERVICE</span><span class="o">,</span> <span class="n">mService</span><span class="o">,</span> <span class="kc">false</span> <span class="cm">/*allowIsolated*/</span><span class="o">,</span>
                    <span class="no">DUMP_FLAG_PRIORITY_CRITICAL</span> <span class="o">|</span> <span class="no">DUMP_FLAG_PRIORITY_NORMAL</span> <span class="o">|</span> <span class="no">DUMP_FLAG_PROTO</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="o">.......</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kd">class</span> <span class="nc">InputMethodManagerService</span> <span class="kd">extends</span> <span class="nc">IInputMethodManager</span><span class="o">.</span><span class="na">Stub</span>
        <span class="kd">implements</span> <span class="nc">ServiceConnection</span><span class="o">,</span> <span class="nc">Handler</span><span class="o">.</span><span class="na">Callback</span> <span class="o">{</span>
            <span class="o">....</span>
        <span class="c1">//IMMS构造函数</span>
        <span class="kd">public</span> <span class="nf">InputMethodManagerService</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">mIPackageManager</span> <span class="o">=</span> <span class="nc">AppGlobals</span><span class="o">.</span><span class="na">getPackageManager</span><span class="o">();</span>
            <span class="n">mContext</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
            <span class="n">mRes</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="na">getResources</span><span class="o">();</span>
            <span class="n">mHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Handler</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
            <span class="c1">// Note: SettingsObserver doesn't register observers in its constructor.</span>
            <span class="c1">// SettingsObserver类型，用于监听来自设置的输入法配置, 比如默认输入法, 启用的输入法, 选择的输入法等</span>
            <span class="n">mSettingsObserver</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SettingsObserver</span><span class="o">(</span><span class="n">mHandler</span><span class="o">);</span>
            <span class="n">mIWindowManager</span> <span class="o">=</span> <span class="nc">IWindowManager</span><span class="o">.</span><span class="na">Stub</span><span class="o">.</span><span class="na">asInterface</span><span class="o">(</span>
                    <span class="nc">ServiceManager</span><span class="o">.</span><span class="na">getService</span><span class="o">(</span><span class="nc">Context</span><span class="o">.</span><span class="na">WINDOW_SERVICE</span><span class="o">));</span>
            <span class="n">mWindowManagerInternal</span> <span class="o">=</span> <span class="nc">LocalServices</span><span class="o">.</span><span class="na">getService</span><span class="o">(</span><span class="nc">WindowManagerInternal</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="n">mPackageManagerInternal</span> <span class="o">=</span> <span class="nc">LocalServices</span><span class="o">.</span><span class="na">getService</span><span class="o">(</span><span class="nc">PackageManagerInternal</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="n">mInputManagerInternal</span> <span class="o">=</span> <span class="nc">LocalServices</span><span class="o">.</span><span class="na">getService</span><span class="o">(</span><span class="nc">InputManagerInternal</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
            <span class="n">mImeDisplayValidator</span> <span class="o">=</span> <span class="n">displayId</span> <span class="o">-&gt;</span> <span class="n">mWindowManagerInternal</span><span class="o">.</span><span class="na">getDisplayImePolicy</span><span class="o">(</span><span class="n">displayId</span><span class="o">);</span>
            <span class="o">.....</span>
            <span class="c1">// 状态栏输入法图标名称, 会根据这个名称设置输入法的图标显示</span>
            <span class="n">mSlotIme</span> <span class="o">=</span> <span class="n">mContext</span><span class="o">.</span><span class="na">getString</span><span class="o">(</span><span class="n">com</span><span class="o">.</span><span class="na">android</span><span class="o">.</span><span class="na">internal</span><span class="o">.</span><span class="na">R</span><span class="o">.</span><span class="na">string</span><span class="o">.</span><span class="na">status_bar_ime</span><span class="o">);</span>
            <span class="n">mIsLowRam</span> <span class="o">=</span> <span class="nc">ActivityManager</span><span class="o">.</span><span class="na">isLowRamDeviceStatic</span><span class="o">();</span>
            <span class="c1">// 切换输入法时的通知</span>
            <span class="nc">Bundle</span> <span class="n">extras</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Bundle</span><span class="o">();</span>
            <span class="n">extras</span><span class="o">.</span><span class="na">putBoolean</span><span class="o">(</span><span class="nc">Notification</span><span class="o">.</span><span class="na">EXTRA_ALLOW_DURING_SETUP</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
            <span class="o">.....</span>
            <span class="c1">//获取UID</span>
            <span class="kt">int</span> <span class="n">userId</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
            <span class="k">try</span> <span class="o">{</span>
                <span class="n">userId</span> <span class="o">=</span> <span class="nc">ActivityManager</span><span class="o">.</span><span class="na">getService</span><span class="o">().</span><span class="na">getCurrentUser</span><span class="o">().</span><span class="na">id</span><span class="o">;</span>
            <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">RemoteException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
                <span class="nc">Slog</span><span class="o">.</span><span class="na">w</span><span class="o">(</span><span class="no">TAG</span><span class="o">,</span> <span class="s">"Couldn't get current user ID; guessing it's 0"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
            <span class="o">}</span>
            <span class="c1">// 最近切换的UID</span>
            <span class="n">mLastSwitchUserId</span> <span class="o">=</span> <span class="n">userId</span><span class="o">;</span>

            <span class="c1">//应在buildInputMethodListLocked之前创建mSettings</span>
            <span class="c1">//类型InputMethodSettings，输入法设置对象</span>
            <span class="n">mSettings</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">InputMethodSettings</span><span class="o">(</span>
                    <span class="n">mRes</span><span class="o">,</span> <span class="n">context</span><span class="o">.</span><span class="na">getContentResolver</span><span class="o">(),</span> <span class="n">mMethodMap</span><span class="o">,</span> <span class="n">userId</span><span class="o">,</span> <span class="o">!</span><span class="n">mSystemReady</span><span class="o">);</span>

            <span class="n">updateCurrentProfileIds</span><span class="o">();</span>
            <span class="nc">AdditionalSubtypeUtils</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="n">mAdditionalSubtypeMap</span><span class="o">,</span> <span class="n">userId</span><span class="o">);</span>
            <span class="n">mSwitchingController</span> <span class="o">=</span> <span class="nc">InputMethodSubtypeSwitchingController</span><span class="o">.</span><span class="na">createInstanceLocked</span><span class="o">(</span>
                    <span class="n">mSettings</span><span class="o">,</span> <span class="n">context</span><span class="o">);</span>
            <span class="n">mMenuController</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">InputMethodMenuController</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
            <span class="o">}</span>
    <span class="o">......</span>
        <span class="o">}</span>
</code></pre></div></div>

<p>IMMS.java中几个重要的变量：</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">String mCurMethodId</code>：系统当前默认的输入法id, 可能为空, 与<code class="language-plaintext highlighter-rouge">Settings.Secure#DEFAULT_INPUT_METHOD</code>值保持一致，在setInputMethodLocked中赋值</li>
  <li><code class="language-plaintext highlighter-rouge">String mCurId</code>：当前已经绑定的输入法id, 如果没有输入法绑定上的话, 值为null</li>
  <li><code class="language-plaintext highlighter-rouge">ClientState mCurClient</code>：用于当前激活的IME, 只有持有这个令牌的IME才被系统认可</li>
  <li><code class="language-plaintext highlighter-rouge">IInputMethod mCurMethod</code>：当前已经绑定的输入法接口, 如果为null, 说明没有任何输入法连接上</li>
</ol>

<hr />

<h3 id="224-序列图">2.2.4. 序列图</h3>

<p><img src="../../assets/post/2023/2023-05-11-android_ime_StartShowHide_Tips/IMMS初始化流程序列图.svg" alt="" /></p>

<hr />

<h2 id="23-ime服务端ims初始化流程">2.3. IME服务端（IMS）初始化流程</h2>

<blockquote>
  <p>IMS运行在输入法进程, 是一种特殊的输入法后台服务，继承结构为:<code class="language-plaintext highlighter-rouge">InputMethodService extends AbstractInputMethodServiceService</code>
输入法服务本质上是一个服务, 使用时需要IMMS通过<code class="language-plaintext highlighter-rouge">bindService</code>的方式绑定。
初始化过程在Service的<code class="language-plaintext highlighter-rouge">onCreate</code>方法中, 绑定方法在<code class="language-plaintext highlighter-rouge">onBind</code>方法中。</p>
</blockquote>

<blockquote>
  <p>涉及代码文件路径：
frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
frameworks/base/core/java/com/android/internal/view/IInputMethodManager.aidl
frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java</p>
</blockquote>

<h3 id="231-函数流程梳理">2.3.1. 函数流程梳理</h3>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 我们从InputMethodManager.java开始梳理</span><span class="w">
</span><span class="n">InputMethodManager.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">startInput</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">startInputInner</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">IInputMethodManager.aidl</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">startInputOrWindowGainedFocus</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">InputMethodManagerService.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">startInputOrWindowGainedFocus</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">startInputOrWindowGainedFocusInternal</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">startInputOrWindowGainedFocusInternalLocked</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">startInputUncheckedLocked</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用bindCurrentInputMethodServiceLocked</span><span class="err">，</span><span class="n">启动当前默认的输入法的服务</span><span class="err">（</span><span class="n">启动在设置的输入法进程中</span><span class="err">）</span><span class="w">
        </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用bindServiceAsUser</span><span class="err">，</span><span class="n">例如AOSP提供的packages</span><span class="o">/</span><span class="n">inputmethods</span><span class="o">/</span><span class="n">LatinIME输入法</span><span class="err">，</span><span class="n">可以在AndroidManifest.xml看到输入法service</span><span class="err">，</span><span class="n">即此处的绑定的服务</span><span class="w">
                </span><span class="c1"># 参考packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/SystemBroadcastReceiver.java</span><span class="w">
                </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">输入法APP中会调用InputMethodManager的setAdditionalInputMethodSubtypes</span><span class="err">，</span><span class="n">然后调用到InputMethodManagerService.java对应函数</span><span class="w">
                </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用buildInputMethodListLocked检查当前默认的输入法</span><span class="p">(</span><span class="n">LatinIME</span><span class="p">)</span><span class="n">服务是否存在</span><span class="w">
                </span><span class="o">---</span><span class="err">》</span><span class="w"> </span><span class="n">调用setInputMethodEnabledLocked检查默认的LatinIME是否是可用的可用的输入法</span><span class="err">，</span><span class="n">如果不可用</span><span class="err">，</span><span class="n">则设置为可用</span><span class="err">（</span><span class="n">即检查settingprovider数据库的enabled_input_methods信息</span><span class="err">）</span><span class="w">

</span><span class="c1"># 输入法应用会继承InputMethodService，比如packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/LatinIME.java</span><span class="w">
</span><span class="c1"># 因此会实现InputMethodService的onCreate函数</span><span class="w">

</span><span class="c1"># 从onTouchEvent开始，viewClicked流程</span><span class="w">
</span><span class="m">1</span><span class="w"> </span><span class="n">viewClicked</span><span class="w">
</span><span class="m">2</span><span class="w"> </span><span class="n">checkFocus</span><span class="w">
</span><span class="m">3</span><span class="w"> </span><span class="n">startInputInner</span><span class="w">
</span><span class="m">4</span><span class="w"> </span><span class="n">startInputOrWindowGainedFocus</span><span class="w">
</span><span class="m">5</span><span class="w"> </span><span class="n">startInputLocked</span><span class="w">
</span><span class="m">6</span><span class="w"> </span><span class="n">startInputUncheckedLocked</span><span class="w">
</span><span class="m">7</span><span class="w"> </span><span class="n">attachNewInputLocked</span><span class="w">
</span></code></pre></div></div>

<hr />

<h1 id="3-输入法弹出流程">3. 输入法弹出流程</h1>

<h2 id="31-点击弹出输入法流程">3.1. 点击弹出输入法流程</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 点击界面输入框应用，然后弹出输入法，在点击onTouchEvent事件后</span>
<span class="c"># 一般应用会继承TextView</span>
frameworks/base/core/java/android/widget/TextView.java <span class="nt">--</span> onTouchEvent
    <span class="nt">---</span>》 viewClicked<span class="o">(</span>imm<span class="o">)</span> 入参InputMethodManager对象，如果IMM对象不为空，则会调用IMM的同名函数

<span class="c"># 此处会判断服务的view是否相同，当两次点击在同一个输入框时，两者相同；否则不同</span>
<span class="c"># 此处以首次点击某个输入框为例</span>
<span class="nt">---</span>》  InputMethodManager.java <span class="nt">--</span> 调用viewClicked<span class="o">(</span>View view<span class="o">)</span>
    <span class="nt">---</span>》 调用checkFocus<span class="o">()</span>检查焦点，然后调用ImeFocusController的同名函数 controller.checkFocus<span class="o">(</span><span class="nb">false</span> /<span class="k">*</span> forceNewFocus <span class="k">*</span>/, <span class="nb">true</span> /<span class="k">*</span> startInput <span class="k">*</span>/<span class="o">)</span>
            <span class="c"># 具体调用代码immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */, 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */)</span>
            <span class="c"># 入参说明：</span>
            <span class="c"># -startInputReason：START_INPUT_REASON_CHECK_FOCUS，标明本次调用的目的</span>
            <span class="c"># -windowGainingFocus:会影响IMMS中startInputOrWindowGainedFocus的调用逻辑</span>
            <span class="c"># -controlFlags，softInputMode，windowFlags = 0</span>
        <span class="nt">---</span>》 ImeFocusController.java <span class="nt">--</span> checkFocus创建获取接口类InputMethodManagerDelegate对象，其实就是IMM.java的内部类DelegateImpl（实现该接口类）对象，然后调用该类的startInput函数

<span class="nt">---</span>》 InputMethodManager.java <span class="nt">--</span> 调用类DelegateImpl的startInput函数
        <span class="nt">---</span>》 调用startInputInner函数
            （1）---》 创建new IInputConnectionWrapper对象
            （2）---》 调用IInputMethodManager.aidl的startInputOrWindowGainedFocus函数

<span class="nt">---</span>》 InputMethodManagerService.java <span class="nt">--</span> 跨进程调用startInputOrWindowGainedFocus
        <span class="nt">---</span>》 调用startInputOrWindowGainedFocusInternal
        <span class="nt">---</span>》 调用startInputOrWindowGainedFocusInternalLocked
        <span class="nt">---</span>》 调用startInputUncheckedLocked
        <span class="c"># NOTE：此处返回结果给上层InputMethodManager.java的startInputOrWindowGainedFocus函数</span>
        <span class="nt">---</span>》 调用attachNewInputLocked函数，发送MSG_START_INPUT消息，触发handleMessage，调用startInput （---该函数返回对象InputBindResult，输入法数据结果信息---）

代码说明：
<span class="c"># session是SessionState静态类，method是IInputMethod对象</span>
session.method.startInput<span class="o">(</span>startInputToken, inputContext, missingMethods, editorInfo, restarting<span class="o">)</span><span class="p">;</span>

<span class="nt">---</span>》 IInputMethod.aidl <span class="nt">--</span> startInput
<span class="nt">---</span>》 IInputMethodWrapper.java实现IInputMethod <span class="nt">--</span> startInput，发送DO_START_INPUT消息，然后在executeMessage中调用InputMethod的dispatchStartInputWithToken

<span class="c"># 抽象类AbstractInputMethodImpl实现了InputMethod接口类</span>
<span class="c"># 静态类InputMethodSessionCallbackWrapper实现了InputMethod.SessionCallback类，主要实现sessionCreated建立session连接</span>
<span class="nt">---</span>》 InputMethod.java 接口类InputMethod方法dispatchStartInputWithToken，然后调用startInput（如果是重启则调用restartInput）

<span class="c"># InputMethodService.java中的类InputMethodImpl继承了抽象类AbstractInputMethodImpl</span>
<span class="nt">---</span>》 InputMethodService.java <span class="nt">--</span> 调用类InputMethodImpl的startInput方法
        <span class="nt">---</span>》 调用doStartInput
        <span class="c"># 调用以通知输入方法文本输入已在编辑器中开始。您应该使用此回调来初始化输入的状态，以匹配给定给它的编辑器的状态</span>
        <span class="c"># 此处由具体输入法APP继承InputMethodService类然后来实现</span>
        <span class="c"># NOTE：比如packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/LatinIME.java</span>
        <span class="c"># 入参attibute：开始输入的编辑器的属性</span>
        <span class="c"># 入参restarting：如果输入在同一编辑器中重新启动，例如因为应用程序更改了编辑器中的文本，则设置为true。否则将为false，表示这是一个带有编辑的新会话</span>
        <span class="nt">---</span>》 调用onStartInput<span class="o">(</span>EditorInfo attribute, boolean restarting<span class="o">)</span>
</code></pre></div></div>

<hr />

<h3 id="311-序列图">3.1.1. 序列图</h3>

<p><img src="../../assets/post/2023/2023-05-11-android_ime_StartShowHide_Tips/输入法点击弹出流程.svg" alt="" /></p>

<hr />

<h2 id="32-输入法显示流程隐藏">3.2. 输入法显示流程（隐藏）</h2>

<blockquote>
  <p>梳理WMS部分流程。
可参考：<a href="https://www.jianshu.com/p/d0b001a055c1">Android输入法弹出流程</a></p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 点击界面输入框应用，然后弹出输入法，在点击onTouchEvent事件后</span>
<span class="c"># 一般应用会继承TextView</span>
frameworks/base/core/java/android/widget/TextView.java <span class="nt">--</span> onTouchEvent
    <span class="nt">---</span>》 创建InputMethodManager对象，调用showSoftInput（先执行上面点击弹出输入法viewClicked的流程，紧接着执行此处流程）

<span class="nt">----</span>》 InputMethodManager.java <span class="nt">---</span> showSoftInput（hideSoftInput隐藏输入法）

<span class="nt">----</span>》 IInputMethodManager.aidl 跨进程showSoftInput

<span class="nt">----</span>》IMMS.java extends IInputMethodManager.Stub 调用showSoftInput
    <span class="nt">---</span>》 showCurrentInputLocked 发送MSG_SHOW_SOFT_INPUT消息，然后在handleMessage调用IInputMethod的showSoftInput

<span class="nt">---</span>》 IInputMethod.aidl 跨进程showSoftInput

<span class="nt">---</span>》 IInputMethodWrapper extends IInputMethod.Stub 调用showSoftInput，发送DO_SHOW_SOFT_INPUT消息，然后在executeMessage调用InputMethod的showSoftInputWithToken函数

<span class="c"># 抽象类AbstractInputMethodImpl实现了InputMethod接口类</span>
<span class="c"># 静态类InputMethodSessionCallbackWrapper实现了InputMethod.SessionCallback类，主要实现sessionCreated建立session连接</span>
<span class="nt">---</span>》 InputMethod.java 接口类InputMethod方法showSoftInputWithToke，调用showSoftInput

<span class="c"># InputMethodService.java中的类InputMethodImpl继承了抽象类AbstractInputMethodImpl</span>
<span class="nt">---</span>》 InputMethodService.java <span class="nt">--</span> 调用类InputMethodImpl的方法showSoftInput，显示输入法（hideSoftInput隐藏输入法）
    （1）----》 dispatchOnShowInputRequested 调用onShowInputRequested
    （2）----》 showWindow<span class="o">()</span>
            <span class="nt">---</span>》 调用startViews<span class="o">(</span>prepareWindow<span class="o">(</span>showInput<span class="o">))</span>
                <span class="c"># prepareWindow函数调用</span>
                （1）---》 initialize<span class="o">()</span>
                （2）---》 updateFullscreenMode<span class="o">()</span>
                （3）---》 updateInputViewShown<span class="o">(()</span> 先调用onCreateInputView，然后调用setInputView
                （4）---》 如果mViewsCreated未创建，即为false，调用initialize<span class="o">()</span>，然后调用onCreateCandidatesView<span class="o">()</span>，再调用setCandidatesView<span class="o">(</span>v<span class="o">)</span>
                <span class="c"># startViews函数</span>
                （1）---》 onStartInputView
                （2）---》 onStartCandidatesView
                （3）---》 startExtractingText
    （3）----》 applyVisibilityInInsetsConsumerIfNecessary<span class="o">(</span>boolean setVisible<span class="o">)</span> 如果条件满足，申请显示输入法

<span class="nt">-----</span>》 InputMethodManagerService.java <span class="nt">---</span> applyImeVisibilityAsync通过Binder机制调用IMMS类的applyImeVisibility函数，使申请IME可见

<span class="c"># 隐藏输入法函数是hideIme</span>
<span class="nt">-----</span>》 WMS.java <span class="nt">---</span> showImePostLayout<span class="o">(</span>IBinder imeTargetWindowToken<span class="o">)</span>
<span class="c"># NOTE：该问题代码修改的地方，在Android 13上已修复</span>
<span class="o">(</span>1<span class="o">)</span><span class="nt">-----</span>》 windowState.java <span class="nt">---</span> <span class="sb">`</span>InsetsControlTarget controlTarget <span class="o">=</span> getImeControlTarget<span class="sb">`</span> 此处会获取displaycontent，还有他的parentWindow<span class="o">()</span> <span class="o">======</span>》 调用 DisplayContent.java <span class="nt">---</span> InsetsControlTarget getImeHostOrFallback<span class="o">(</span>WindowState target<span class="o">)</span>
<span class="o">(</span>2<span class="o">)</span><span class="nt">-----</span>》 ImeInsetsSourceProvider.java <span class="nt">---</span> scheduleShowImePostLayout<span class="o">(</span>controlTarget<span class="o">)</span>，该函数主要通过Target的值处理输入法显示逻辑，如果显示则调用setImeShowing将mImeShowing全局变量设置为true，然后外部通过isImeShowing<span class="o">()</span>调用获取该值

<span class="nt">-----</span>》 checkShowImePostLayout<span class="o">()</span> 判断是否显示输入法，有多个逻辑判断，包含
    <span class="o">(</span>1<span class="o">)</span> mWin <span class="o">!=</span> null （即windowtoken - imeTargetWindowToken）
    <span class="o">(</span>2<span class="o">)</span> mWin.isDrawn<span class="o">()</span>
    <span class="o">(</span>3<span class="o">)</span> <span class="o">!</span>mWin.mGivenInsetsPending
    <span class="o">(</span>4<span class="o">)</span> mIsImeLayoutDrawn <span class="o">=</span> <span class="nb">true</span>
    <span class="o">(</span>5<span class="o">)</span> 调用isReadyToShowIme<span class="o">()</span>函数，判断两个target的内容（目标窗口的target和请求的target）

<span class="nt">----</span>》 setImeShowing<span class="o">(</span><span class="nb">true</span><span class="o">)</span> 设置输入法可显示 （和isImeShowing<span class="o">()</span>成对，用于外部获取）
</code></pre></div></div>

<h3 id="321-序列图">3.2.1. 序列图</h3>

<p><img src="../../assets/post/2023/2023-05-11-android_ime_StartShowHide_Tips/输入法点击显示隐藏流程.svg" alt="" /></p>

<hr />

<h1 id="4-输入法常见问题小结">4. 输入法常见问题小结</h1>

<h2 id="41-配置默认输入法">4.1. 配置默认输入法</h2>

<h3 id="411-查看已安装的输入法">4.1.1. 查看已安装的输入法</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell ime list <span class="nt">-a</span>

<span class="c"># 结果</span>
com.android.inputmethod.latin/.CarLatinIME:
  <span class="c"># mId就是配置项def_input_method</span>
  <span class="nv">mId</span><span class="o">=</span>com.android.inputmethod.latin/.CarLatinIME <span class="nv">mSettingsActivityName</span><span class="o">=</span>null <span class="nv">mIsVrOnly</span><span class="o">=</span><span class="nb">false </span><span class="nv">mSupportsSwitchingToNextInputMethod</span><span class="o">=</span><span class="nb">false </span><span class="nv">mInlineSuggestionsEnabled</span><span class="o">=</span><span class="nb">false </span><span class="nv">mSuppressesSpellChecker</span><span class="o">=</span><span class="nb">false </span><span class="nv">mShowInInputMethodPicker</span><span class="o">=</span><span class="nb">true
  </span><span class="nv">mIsDefaultResId</span><span class="o">=</span>0x0
  Service:
    <span class="nv">priority</span><span class="o">=</span>0 <span class="nv">preferredOrder</span><span class="o">=</span>0 <span class="nv">match</span><span class="o">=</span>0x108000 <span class="nv">specificIndex</span><span class="o">=</span><span class="nt">-1</span> <span class="nv">isDefault</span><span class="o">=</span><span class="nb">false
    </span>ServiceInfo:
      <span class="c"># 输入法服务名</span>
      <span class="nv">name</span><span class="o">=</span>com.android.inputmethod.latin.CarLatinIME
      <span class="c"># 输入法包名</span>
      <span class="nv">packageName</span><span class="o">=</span>com.android.inputmethod.latin
      <span class="nv">labelRes</span><span class="o">=</span>0x7f08000c <span class="nv">nonLocalizedLabel</span><span class="o">=</span>null <span class="nv">icon</span><span class="o">=</span>0x0 <span class="nv">banner</span><span class="o">=</span>0x0
      <span class="nv">enabled</span><span class="o">=</span><span class="nb">true </span><span class="nv">exported</span><span class="o">=</span><span class="nb">true </span><span class="nv">directBootAware</span><span class="o">=</span><span class="nb">true</span>
      <span class="c"># 权限</span>
      <span class="nv">permission</span><span class="o">=</span>android.permission.BIND_INPUT_METHOD
      <span class="nv">flags</span><span class="o">=</span>0x0
      ApplicationInfo:
        <span class="nv">packageName</span><span class="o">=</span>com.android.inputmethod.latin
        <span class="nv">labelRes</span><span class="o">=</span>0x7f08000c <span class="nv">nonLocalizedLabel</span><span class="o">=</span>null <span class="nv">icon</span><span class="o">=</span>0x7f04000c <span class="nv">banner</span><span class="o">=</span>0x0
        <span class="nv">processName</span><span class="o">=</span>com.android.inputmethod.latin
        <span class="nv">taskAffinity</span><span class="o">=</span>com.android.inputmethod.latin
        <span class="nv">uid</span><span class="o">=</span>10058 <span class="nv">flags</span><span class="o">=</span>0x38c8be45 <span class="nv">privateFlags</span><span class="o">=</span>0xa4400040 <span class="nv">theme</span><span class="o">=</span>0x103013e
        <span class="nv">requiresSmallestWidthDp</span><span class="o">=</span>0 <span class="nv">compatibleWidthLimitDp</span><span class="o">=</span>0 <span class="nv">largestWidthLimitDp</span><span class="o">=</span>0
        <span class="c"># APK目录</span>
        <span class="nv">sourceDir</span><span class="o">=</span>/system/app/CarLatinIME/CarLatinIME.apk
        <span class="nv">seinfo</span><span class="o">=</span>default:targetSdkVersion<span class="o">=</span>23
        <span class="nv">seinfoUser</span><span class="o">=</span>:complete
        <span class="c"># data数据目录</span>
        <span class="nv">dataDir</span><span class="o">=</span>/data/user/0/com.android.inputmethod.latin
        <span class="nv">deviceProtectedDataDir</span><span class="o">=</span>/data/user_de/0/com.android.inputmethod.latin
        <span class="nv">credentialProtectedDataDir</span><span class="o">=</span>/data/user/0/com.android.inputmethod.latin
        <span class="nv">sharedLibraryFiles</span><span class="o">=[</span>/system/framework/android.test.base.jar, /system/framework/android.hidl.manager-V1.0-java.jar, /system/framework/android.hidl.base-V1.0-java.jar, /system/framework/org.apache.http.legacy.jar]
        <span class="nv">enabled</span><span class="o">=</span><span class="nb">true </span><span class="nv">minSdkVersion</span><span class="o">=</span>32 <span class="nv">targetSdkVersion</span><span class="o">=</span>23 <span class="nv">versionCode</span><span class="o">=</span>32 <span class="nv">targetSandboxVersion</span><span class="o">=</span>1
        <span class="nv">supportsRtl</span><span class="o">=</span><span class="nb">true
        </span><span class="nv">fullBackupContent</span><span class="o">=</span><span class="nb">true
        </span><span class="nv">crossProfile</span><span class="o">=</span><span class="nb">false
        </span><span class="nv">HiddenApiEnforcementPolicy</span><span class="o">=</span>0
        <span class="nv">usesNonSdkApi</span><span class="o">=</span><span class="nb">true
        </span><span class="nv">allowsPlaybackCapture</span><span class="o">=</span><span class="nb">false
        </span><span class="nv">nativeHeapZeroInitialized</span><span class="o">=</span>0
</code></pre></div></div>

<h3 id="412-切换输入法">4.1.2. 切换输入法</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell ime <span class="nb">set </span>com.android.inputmethod.latin/.CarLatinIME
</code></pre></div></div>

<h3 id="413-android代码配置新默认输入法">4.1.3. Android代码配置新默认输入法</h3>

<p>Android默认输入法的配置和两个关键词相关，都保存在SettingsProvider或者settings_secure.xml里面</p>
<ol>
  <li>enabled_input_methods:表示配置的系统允许使用的输入法的id字符串，中间以冒号分隔，比如com.android.inputmethod.latin/.LatinIME:com.xxxx.inputmethod.remote/.RemoteIME</li>
  <li>default_input_method:表示配置的系统默认使用的输入法</li>
</ol>

<p>在InputMethodService启动时，一方面会从packagemanager那边获取InputMethodService的应用信息，另一方面也会settings数据库里面读取<code class="language-plaintext highlighter-rouge">enabled_input_methods</code>和<code class="language-plaintext highlighter-rouge">default_input_method</code>对应的输入法信息。</p>

<ul>
  <li>如果后者是空的，则会把前者保存起来并enable，并通过<code class="language-plaintext highlighter-rouge">InputMethodUtils.getMostApplicableDefaultIME</code>方法来获取最适合当前系统的输入法，并设置为默认的输入法</li>
  <li>如果后者不是空的，则会读取settings数据库的default_input_method信息，如果是有效的输入法就会把它设置为默认的输入法，如果不是有效的是空的则还是会通过<code class="language-plaintext highlighter-rouge">getMostApplicableDefaultIME</code>方法来获取最适合当前系统的输入法</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//frameworks/base/core/java/android/provider/Settings.java</span>
      <span class="cm">/**
         * List of input methods that are currently enabled.  This is a string
         * containing the IDs of all enabled input methods, each ID separated
         * by ':'.
         *
         * Format like "ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0"
         * where imeId is ComponentName and subtype is int32.
         */</span>
        <span class="nd">@Readable</span>
        <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">ENABLED_INPUT_METHODS</span> <span class="o">=</span> <span class="s">"enabled_input_methods"</span><span class="o">;</span>

        <span class="cm">/**
         * Setting to record the input method used by default, holding the ID
         * of the desired method.
         */</span>
        <span class="nd">@Readable</span>
        <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">DEFAULT_INPUT_METHOD</span> <span class="o">=</span> <span class="s">"default_input_method"</span><span class="o">;</span>
</code></pre></div></div>

<p><strong>配置修改方法：</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//frameworks/base/packages/SettingsProvider/res/values/defaults.xml</span>
<span class="c1">//包名+输入法 ID（服务名）， 可通过ime list -s查看</span>
<span class="o">+</span>    <span class="o">&lt;!--</span> <span class="n">set</span> <span class="k">default</span> <span class="n">input</span> <span class="n">method</span><span class="o">--&gt;</span>
<span class="o">+</span>    <span class="o">&lt;</span><span class="n">string</span> <span class="n">name</span><span class="o">=</span><span class="s">"def_input_method"</span> <span class="n">translatable</span><span class="o">=</span><span class="s">"false"</span><span class="o">&gt;</span><span class="n">com</span><span class="o">.</span><span class="na">test</span><span class="o">.</span><span class="na">inputmethod</span><span class="o">.</span><span class="na">pinyin</span><span class="o">/.</span><span class="na">PinyinIME</span><span class="o">&lt;/</span><span class="n">string</span><span class="o">&gt;</span>
<span class="o">+</span>    <span class="o">&lt;</span><span class="n">string</span> <span class="n">name</span><span class="o">=</span><span class="s">"enabled_input_methods"</span> <span class="n">translatable</span><span class="o">=</span><span class="s">"false"</span><span class="o">&gt;</span><span class="n">com</span><span class="o">.</span><span class="na">test</span><span class="o">.</span><span class="na">inputmethod</span><span class="o">.</span><span class="na">pinyin</span><span class="o">/.</span><span class="na">PinyinIME</span><span class="o">:</span><span class="n">com</span><span class="o">.</span><span class="na">android</span><span class="o">.</span><span class="na">inputmethod</span><span class="o">.</span><span class="na">pinyin</span><span class="o">/.</span><span class="na">PinyinIME</span><span class="o">&lt;/</span><span class="n">string</span><span class="o">&gt;</span>

<span class="c1">//frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java</span>

<span class="o">+</span>            <span class="n">loadStringSetting</span><span class="o">(</span><span class="n">stmt</span><span class="o">,</span> <span class="nc">Settings</span><span class="o">.</span><span class="na">Secure</span><span class="o">.</span><span class="na">DEFAULT_INPUT_METHOD</span><span class="o">,</span>
<span class="o">+</span>                    <span class="no">R</span><span class="o">.</span><span class="na">string</span><span class="o">.</span><span class="na">def_input_method</span><span class="o">);</span>
<span class="o">+</span>            <span class="n">loadStringSetting</span><span class="o">(</span><span class="n">stmt</span><span class="o">,</span> <span class="nc">Settings</span><span class="o">.</span><span class="na">Secure</span><span class="o">.</span><span class="na">ENABLED_INPUT_METHODS</span><span class="o">,</span>
<span class="o">+</span>                    <span class="no">R</span><span class="o">.</span><span class="na">string</span><span class="o">.</span><span class="na">enabled_input_methods</span><span class="o">);</span>
</code></pre></div></div>

<p>其中配置项的字符串获取方式是：</p>
<ol>
  <li>安装上apk，在settings界面选中该输入法（或者命令ime set），然后命令行执行：<code class="language-plaintext highlighter-rouge">settings get secure def_input_method</code>和<code class="language-plaintext highlighter-rouge">settings get secure enabled_input_method</code></li>
  <li>通过<code class="language-plaintext highlighter-rouge">ime list -s </code>查看输入法ID的信息，然后修改此处配置项</li>
</ol>

<hr />

<h2 id="42-android-12输入法无法在虚拟屏显示">4.2. Android 12输入法无法在虚拟屏显示</h2>

<p><strong>Android 12移除了Android 11上portalToDisplayId的部分代码，导致Android 12上创建Virtual Display后，无法在虚拟屏弹出输入法。回退代码如下：</strong></p>

<p><strong>备注：</strong>android-12.0.0_r3仍未移除，可在AOSP源码上查看到</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">core/jni/android_hardware_input_InputWindowHandle.cpp</code>：添加portalToDisplayId，通过JNI获取JVM层portalToDisplayId的值和FIELD_ID</li>
</ol>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gi">+ jfieldID portalToDisplayId;
</span>
+    mInfo.portalToDisplayId = env-&gt;GetIntField(obj,
<span class="gi">+            gInputWindowHandleClassInfo.portalToDisplayId);
</span>
+     GET_FIELD_ID(gInputWindowHandleClassInfo.portalToDisplayId, clazz,
<span class="gi">+            "portalToDisplayId", "I");
</span></code></pre></div></div>

<ol>
  <li><code class="language-plaintext highlighter-rouge">services/surfaceflinger/Layer.cpp</code>：在fillInputInfo函数中，由于当前虚拟屏Touch实现的方案，getLayerStack会返回0，会覆盖掉WMS传递给SF的DisplayID的值。由于这里会更新InputHandleInfo，传递给InputManager，如果DisplayID的值不正确，会影响InputManager内部关于Focus的计算逻辑</li>
</ol>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">WindowInfo Layer::fillInputInfo(const sp&lt;DisplayDevice&gt;&amp; display) {
</span>    if (!hasInputInfo()) {
        mDrawingState.inputInfo.name = getName();
        mDrawingState.inputInfo.ownerUid = mOwnerUid;
        mDrawingState.inputInfo.ownerPid = mOwnerPid;
        mDrawingState.inputInfo.inputFeatures = WindowInfo::Feature::NO_INPUT_CHANNEL;
        mDrawingState.inputInfo.flags = WindowInfo::Flag::NOT_TOUCH_MODAL;
        mDrawingState.inputInfo.displayId = getLayerStack();
    }

    WindowInfo info = mDrawingState.inputInfo;
    info.id = sequence;

+   if (info.displayId == ADISPLAY_ID_NONE) {
        info.displayId = getLayerStack();
<span class="gi">+   }
</span>    ......
<span class="err">}</span>
</code></pre></div></div>

<hr />

<p><strong>流程如下，从SurfaceFlinger开始合成流程开始：</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">frameworks</span><span class="o">/</span><span class="kd">native</span><span class="o">/</span><span class="n">services</span><span class="o">/</span><span class="n">surfaceflinger</span><span class="o">/</span><span class="nc">SurfaceFlinger</span><span class="o">.</span><span class="na">cpp</span> <span class="o">--</span> <span class="n">onMessageReceived开始触发合成</span>

<span class="o">---&gt;</span> <span class="n">onMessageInvalidate</span>

<span class="o">---&gt;</span> <span class="n">updateInputFlinger</span><span class="o">()</span><span class="n">更新InputFlinger</span>

<span class="o">---&gt;</span> <span class="n">updateInputWindowInfo</span><span class="o">()</span><span class="n">更新input窗口信息</span>

<span class="o">---&gt;</span> <span class="n">frameworks</span><span class="o">/</span><span class="kd">native</span><span class="o">/</span><span class="n">services</span><span class="o">/</span><span class="n">surfaceflinger</span><span class="o">/</span><span class="nc">Layer</span><span class="o">.</span><span class="na">cpp</span> <span class="o">--</span> <span class="n">fillInputInfo</span><span class="o">(</span><span class="nc">DisplayDevice</span> <span class="n">display</span><span class="o">)</span> <span class="n">入参display信息</span>

<span class="c1">// 代码内容</span>
<span class="nc">InputWindowInfo</span> <span class="nl">Layer:</span><span class="o">:</span><span class="n">fillInputInfo</span><span class="o">(</span><span class="kd">const</span> <span class="n">sp</span><span class="o">&lt;</span><span class="nc">DisplayDevice</span><span class="o">&gt;&amp;</span> <span class="n">display</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">//绘制的inputinfo不为空，mDrawingState.inputInfo.token</span>
    <span class="k">if</span> <span class="o">(!</span><span class="n">hasInputInfo</span><span class="o">())</span> <span class="o">{</span>
        <span class="n">mDrawingState</span><span class="o">.</span><span class="na">inputInfo</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">getName</span><span class="o">();</span>
        <span class="n">mDrawingState</span><span class="o">.</span><span class="na">inputInfo</span><span class="o">.</span><span class="na">ownerUid</span> <span class="o">=</span> <span class="n">mOwnerUid</span><span class="o">;</span>
        <span class="n">mDrawingState</span><span class="o">.</span><span class="na">inputInfo</span><span class="o">.</span><span class="na">ownerPid</span> <span class="o">=</span> <span class="n">mOwnerPid</span><span class="o">;</span>
        <span class="n">mDrawingState</span><span class="o">.</span><span class="na">inputInfo</span><span class="o">.</span><span class="na">inputFeatures</span> <span class="o">=</span> <span class="nl">InputWindowInfo:</span><span class="o">:</span><span class="nl">Feature:</span><span class="o">:</span><span class="no">NO_INPUT_CHANNEL</span><span class="o">;</span>
        <span class="n">mDrawingState</span><span class="o">.</span><span class="na">inputInfo</span><span class="o">.</span><span class="na">flags</span> <span class="o">=</span> <span class="nl">InputWindowInfo:</span><span class="o">:</span><span class="nl">Flag:</span><span class="o">:</span><span class="no">NOT_TOUCH_MODAL</span><span class="o">;</span>
        <span class="n">mDrawingState</span><span class="o">.</span><span class="na">inputInfo</span><span class="o">.</span><span class="na">displayId</span> <span class="o">=</span> <span class="n">getLayerStack</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="nc">InputWindowInfo</span> <span class="n">info</span> <span class="o">=</span> <span class="n">mDrawingState</span><span class="o">.</span><span class="na">inputInfo</span><span class="o">;</span>
    <span class="n">info</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="n">sequence</span><span class="o">;</span>
    <span class="c1">//此处注意输入窗口的display id</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">info</span><span class="o">.</span><span class="na">displayId</span> <span class="o">==</span> <span class="no">ADISPLAY_ID_NONE</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">info</span><span class="o">.</span><span class="na">displayId</span> <span class="o">=</span> <span class="n">getLayerStack</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="c1">// Transform that goes from "logical(rotated)" display to physical/unrotated display.</span>
    <span class="c1">// This is for when inputflinger operates in physical display-space.</span>
    <span class="nl">ui:</span><span class="o">:</span><span class="nc">Transform</span> <span class="n">toPhysicalDisplay</span><span class="o">;</span>
    <span class="k">if</span> <span class="o">(</span><span class="n">display</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">toPhysicalDisplay</span> <span class="o">=</span> <span class="n">display</span><span class="o">-&gt;</span><span class="n">getTransform</span><span class="o">();</span>
        <span class="n">info</span><span class="o">.</span><span class="na">displayWidth</span> <span class="o">=</span> <span class="n">display</span><span class="o">-&gt;</span><span class="n">getWidth</span><span class="o">();</span>
        <span class="n">info</span><span class="o">.</span><span class="na">displayHeight</span> <span class="o">=</span> <span class="n">display</span><span class="o">-&gt;</span><span class="n">getHeight</span><span class="o">();</span>
    <span class="o">}</span>
    <span class="n">fillInputFrameInfo</span><span class="o">(</span><span class="n">info</span><span class="o">,</span> <span class="n">toPhysicalDisplay</span><span class="o">);</span>
    <span class="o">.....</span>
<span class="o">}</span>


<span class="o">---&gt;</span> <span class="n">fillInputFrameInfo</span><span class="o">(</span><span class="n">info</span><span class="o">,</span> <span class="n">toPhysicalDisplay</span><span class="o">)</span><span class="n">计算input窗口显示合成区域</span>
</code></pre></div></div>

<hr />

<h2 id="43-android-12输入法无法显示在虚拟屏内部">4.3. Android 12输入法无法显示在虚拟屏内部</h2>

<p><strong>修改方法：</strong>
<code class="language-plaintext highlighter-rouge">packages/SettingsProvider/res/values/defaults.xml</code>和<code class="language-plaintext highlighter-rouge">packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java</code>：开启强制桌面模式，可以解决输入法显示在虚拟屏内部</p>

<ul>
  <li>通过<code class="language-plaintext highlighter-rouge">settings get global force_desktop_mode_on_external_displays</code>获取该值（默认是null，一般是通过开发者选项中进行开启/关闭）</li>
  <li>手动命令设置设置桌面模式：<code class="language-plaintext highlighter-rouge">settings put global force_desktop_mode_on_external_displays 1</code></li>
</ul>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//defaults.xml</span>
+    &lt;!-- Initial value for the Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS --&gt;
<span class="gi">+    &lt;integer name="def_force_desktop_mode_on_external_displays"&gt;1&lt;/integer&gt;
</span>
//DatabaseHelper.java
<span class="gi">+            // set global value of DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+            loadIntegerSetting(stmt, Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS,
+
</span></code></pre></div></div>

<hr />

<h3 id="431-开发者选项桌面模式">4.3.1. 开发者选项桌面模式</h3>

<blockquote>
  <p>开发者选项中桌面模式的开关，主要就是DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS值的控制</p>
</blockquote>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//packages/apps/Settings/src/com/android/settings/development/DesktopModePreferenceController.java</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">android</span><span class="o">.</span><span class="na">provider</span><span class="o">.</span><span class="na">Settings</span><span class="o">.</span><span class="na">Global</span><span class="o">.</span><span class="na">DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DesktopModePreferenceController</span> <span class="kd">extends</span> <span class="nc">DeveloperOptionsPreferenceController</span>
        <span class="kd">implements</span> <span class="nc">Preference</span><span class="o">.</span><span class="na">OnPreferenceChangeListener</span><span class="o">,</span> <span class="nc">PreferenceControllerMixin</span> <span class="o">{</span>
        <span class="o">.......</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">onPreferenceChange</span><span class="o">(</span><span class="nc">Preference</span> <span class="n">preference</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">newValue</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="kt">boolean</span> <span class="n">isEnabled</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Boolean</span><span class="o">)</span> <span class="n">newValue</span><span class="o">;</span>
        <span class="nc">Settings</span><span class="o">.</span><span class="na">Global</span><span class="o">.</span><span class="na">putInt</span><span class="o">(</span><span class="n">mContext</span><span class="o">.</span><span class="na">getContentResolver</span><span class="o">(),</span>
                <span class="no">DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS</span><span class="o">,</span>
                <span class="n">isEnabled</span> <span class="o">?</span> <span class="no">SETTING_VALUE_ON</span> <span class="o">:</span> <span class="no">SETTING_VALUE_OFF</span><span class="o">);</span>
        <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">updateState</span><span class="o">(</span><span class="nc">Preference</span> <span class="n">preference</span><span class="o">)</span> <span class="o">{</span>
        <span class="kd">final</span> <span class="kt">int</span> <span class="n">mode</span> <span class="o">=</span> <span class="nc">Settings</span><span class="o">.</span><span class="na">Global</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="n">mContext</span><span class="o">.</span><span class="na">getContentResolver</span><span class="o">(),</span>
                <span class="no">DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS</span><span class="o">,</span> <span class="no">SETTING_VALUE_OFF</span><span class="o">);</span>
        <span class="o">((</span><span class="nc">SwitchPreference</span><span class="o">)</span> <span class="n">mPreference</span><span class="o">).</span><span class="na">setChecked</span><span class="o">(</span><span class="n">mode</span> <span class="o">!=</span> <span class="no">SETTING_VALUE_OFF</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onDeveloperOptionsSwitchDisabled</span><span class="o">()</span> <span class="o">{</span>
        <span class="kd">super</span><span class="o">.</span><span class="na">onDeveloperOptionsSwitchDisabled</span><span class="o">();</span>
        <span class="nc">Settings</span><span class="o">.</span><span class="na">Global</span><span class="o">.</span><span class="na">putInt</span><span class="o">(</span><span class="n">mContext</span><span class="o">.</span><span class="na">getContentResolver</span><span class="o">(),</span>
                <span class="no">DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS</span><span class="o">,</span> <span class="no">SETTING_VALUE_OFF</span><span class="o">);</span>
        <span class="o">((</span><span class="nc">SwitchPreference</span><span class="o">)</span> <span class="n">mPreference</span><span class="o">).</span><span class="na">setChecked</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<hr />

<h3 id="432-桌面模式属性流程">4.3.2. 桌面模式属性流程</h3>

<h4 id="4321-修改方法">4.3.2.1. 修改方法</h4>

<p>在WMS.java中获取值，并监听变化，赋值给变量mForceDesktopModeOnExternalDisplays</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java</span>
<span class="kn">import</span> <span class="nn">static</span> <span class="n">android</span><span class="o">.</span><span class="na">provider</span><span class="o">.</span><span class="na">Settings</span><span class="o">.</span><span class="na">Global</span><span class="o">.</span><span class="na">DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">WindowManagerService</span> <span class="kd">extends</span> <span class="nc">IWindowManager</span><span class="o">.</span><span class="na">Stub</span>
        <span class="kd">implements</span> <span class="nc">Watchdog</span><span class="o">.</span><span class="na">Monitor</span><span class="o">,</span> <span class="nc">WindowManagerPolicy</span><span class="o">.</span><span class="na">WindowManagerFuncs</span> <span class="o">{</span>
            <span class="o">......</span>

    <span class="c1">//强制桌面模式标志</span>
    <span class="c1">//通过监听updateForceDesktopModeOnExternalDisplays()</span>
    <span class="kt">boolean</span> <span class="n">mForceDesktopModeOnExternalDisplays</span><span class="o">;</span>

<span class="c1">//构造函数时获取该值</span>
        <span class="n">mForceDesktopModeOnExternalDisplays</span> <span class="o">=</span> <span class="nc">Settings</span><span class="o">.</span><span class="na">Global</span><span class="o">.</span><span class="na">getInt</span><span class="o">(</span><span class="n">resolver</span><span class="o">,</span>
                <span class="no">DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS</span><span class="o">,</span> <span class="mi">0</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">;</span>

<span class="c1">//通过监听设置应用上开关的变化，然后在onChange调用updateForceDesktopModeOnExternalDisplays()函数更新mForceDesktopModeOnExternalDisplays值</span>
        <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Uri</span> <span class="n">mForceDesktopModeOnExternalDisplaysUri</span> <span class="o">=</span> <span class="nc">Settings</span><span class="o">.</span><span class="na">Global</span><span class="o">.</span><span class="na">getUriFor</span><span class="o">(</span>
                        <span class="nc">Settings</span><span class="o">.</span><span class="na">Global</span><span class="o">.</span><span class="na">DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS</span><span class="o">);</span>
</code></pre></div></div>

<h4 id="4322-mforcedesktopmodeonexternaldisplays值流程">4.3.2.2. mForceDesktopModeOnExternalDisplays值流程</h4>

<p><strong>第一处：</strong></p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">frameworks</span><span class="o">/</span><span class="n">base</span><span class="o">/</span><span class="n">services</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">jni</span><span class="o">/</span><span class="n">com_android_server_input_InputManagerService.cpp</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">setDisplayViewports输入管理服务的函数</span><span class="err">，</span><span class="n">用来设置输入系统需要的显示器的显示视图信息</span><span class="w">
</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">JNI函数getPointerDisplayId</span><span class="p">()</span><span class="n">调用InputManagerService的对应函数</span><span class="w">

</span><span class="n">frameworks</span><span class="o">/</span><span class="n">base</span><span class="o">/</span><span class="n">services</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">java</span><span class="o">/</span><span class="n">com</span><span class="o">/</span><span class="n">android</span><span class="o">/</span><span class="n">server</span><span class="o">/</span><span class="n">input</span><span class="o">/</span><span class="n">InputManagerService.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">getPointerDisplayId</span><span class="p">()</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">frameworks</span><span class="o">/</span><span class="n">base</span><span class="o">/</span><span class="n">services</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">java</span><span class="o">/</span><span class="n">com</span><span class="o">/</span><span class="n">android</span><span class="o">/</span><span class="n">server</span><span class="o">/</span><span class="n">wm</span><span class="o">/</span><span class="n">InputManagerCallback.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">getPointerDisplayId</span><span class="p">()</span><span class="n">如果桌面模式打开则displayid</span><span class="o">=</span><span class="n">DEFAULT_DISPLAY</span><span class="err">，</span><span class="n">否则从WMS对象的displaycontent中获取displayId</span><span class="w">
</span></code></pre></div></div>

<p><strong>第二处：</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">DisplayContent</span><span class="o">.</span><span class="na">java</span> <span class="o">--</span> 

<span class="n">此处三个地方会调用到updateImeControlTarget函数</span><span class="err">：</span>
<span class="o">(</span><span class="mi">1</span><span class="o">)</span><span class="n">setImeLayeringTargetInner</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">WindowState</span> <span class="n">target</span><span class="o">)</span><span class="n">由computeImeTarget</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">updateImeTarget</span><span class="o">)</span><span class="n">计算IME</span> <span class="nc">Target的函数调用</span>
<span class="o">(</span><span class="mi">2</span><span class="o">)</span><span class="n">updateImeInputAndControlTarget</span><span class="o">(</span><span class="nc">WindowState</span> <span class="n">target</span><span class="o">)</span><span class="n">由WindowState</span><span class="o">.</span><span class="na">java或者WMS</span><span class="o">.</span><span class="na">java中调用</span>
<span class="o">(</span><span class="mi">3</span><span class="o">)</span><span class="n">setInputMethodWindowLocked</span><span class="o">(</span><span class="nc">WindowState</span> <span class="n">win</span><span class="o">)</span><span class="n">由RootWindowContainer</span><span class="o">.</span><span class="na">java或者WMS</span><span class="o">.</span><span class="na">java调用</span>

<span class="o">---&gt;</span> <span class="n">updateImeControlTarget</span><span class="o">()</span>
<span class="o">---&gt;</span> <span class="n">computeImeControlTarget</span><span class="o">()</span>
<span class="o">---&gt;</span> <span class="n">getImeHostOrFallback</span><span class="o">(</span><span class="nc">WindowState</span> <span class="n">target</span><span class="o">)</span>
<span class="o">---&gt;</span> <span class="n">getImePolicy</span><span class="o">()</span>
<span class="o">---&gt;</span> <span class="n">forceDesktopMode</span><span class="o">()</span><span class="n">判断是否开启桌面模式</span> <span class="o">&amp;&amp;</span> <span class="n">非defalut</span> <span class="n">display</span> <span class="o">&amp;&amp;</span> <span class="n">非private的Flag</span><span class="err">，</span><span class="n">返回布尔值</span>
</code></pre></div></div>

<hr />

<h2 id="44-android-12输入法无法在多个虚拟屏切换显示">4.4. Android 12输入法无法在多个虚拟屏切换显示</h2>

<h3 id="441-修改方法">4.4.1. 修改方法</h3>

<blockquote>
  <p>参考：<a href="https://source.android.google.cn/docs/core/display/multi_display/displays?hl=zh-cn">Google官方文档-屏幕支持</a></p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">overlay/packages/services/Car/car_product/overlay/frameworks/base/core/res/res/values/config.xml</code>：关闭多屏焦点，为了解决输入法无法在多个虚拟屏切换显示的问题</p>

<p>该配置项阅读官方文档，意思就是为了同时支持多个以单个屏幕为目标的输入源，可以将Android配置为支持多个聚焦窗口（每个屏幕最多支持一个）。</p>

<p>由Android 10引入，在Android 9即更低版本中，系统中一次最多只有一个窗口具有焦点。</p>

<p>然而在多个Virtual Display虚拟屏中，开启该配置项，输入法只会将焦点聚焦在一个虚拟屏中，无法切换虚拟屏点击弹出输入法，所以关闭该配置项。</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">&lt;resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"&gt;
</span>
+    &lt;!-- disable multi display focus --&gt;
<span class="gi">+    &lt;!-- Whether the system enables per-display focus. If the system has the input method for each
+         display, this value should be true. --&gt;
+    &lt;bool name="config_perDisplayFocusEnabled"&gt;false&lt;/bool&gt;
</span><span class="gd">&lt;/resources&gt;
</span></code></pre></div></div>

<hr />

<h3 id="442-多屏焦点流程">4.4.2. 多屏焦点流程</h3>

<ol>
  <li>InputDispatcher现在可以有多个聚焦窗口（每个屏幕一个）。如果某个输入事件特定于屏幕，则该事件会被分派到相应屏幕中的聚焦窗口。否则，它会被分派到聚焦屏幕（即用户最近与之交互的屏幕）中的聚焦窗口。参阅 InputDispatcher::setFocusedDisplay()。聚焦应用也会通过NativeInputManager::setFocusedApplication()在InputManagerService中分别更新</li>
  <li>在WindowManager中，系统还会单独跟踪聚焦窗口。参阅DisplayContent#mCurrentFocus和DisplayContent#mFocusedApp以及各自的用途。相关的焦点跟踪和更新方法已从WindowManagerService移至DisplayContent</li>
</ol>

<p><strong>以下是WMS焦点更新时，多屏焦点的流程：</strong></p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">WindowManagerService.java构造函数中获取config_perDisplayFocusEnabled属性值</span><span class="err">，</span><span class="n">将其赋值给全局变量mPerDisplayFocusEnabled</span><span class="err">，</span><span class="n">用于控制此功能的可用性</span><span class="w">

</span><span class="n">InsetsSourceProvider.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">setWindow更新当前支持此源的窗口</span><span class="err">，</span><span class="n">此处只关注IME输入法</span><span class="w">
</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">updateControlForTarget更新Target</span><span class="w">
</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">setClientVisible</span><span class="p">(</span><span class="n">boolean</span><span class="w"> </span><span class="n">clientVisible</span><span class="p">)</span><span class="n">设置Client端可见</span><span class="err">，</span><span class="n">发送LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED消息给WMS</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">WMS.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">handleMessage处理LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED消息</span><span class="err">，</span><span class="n">调用layoutAndAssignWindowLayersIfNeeded函数</span><span class="w">

</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">DisplayContent.java</span><span class="w"> </span><span class="o">--</span><span class="w"> </span><span class="n">layoutAndAssignWindowLayersIfNeeded</span><span class="p">()</span><span class="w"> </span><span class="n">每当对层次结构进行视觉更改</span><span class="err">（</span><span class="n">如移动容器或调整容器大小</span><span class="err">）</span><span class="n">时</span><span class="err">，</span><span class="n">都可能调用此方法</span><span class="w">
</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">updateFocusedWindowLocked更新焦点窗口</span><span class="err">，</span><span class="n">此处会通过返回的newFocus和mCurrentFocus比较</span><span class="w">
</span><span class="o">---&gt;</span><span class="w"> </span><span class="n">findFocusedWindowIfNeeded</span><span class="p">(</span><span class="n">int</span><span class="w"> </span><span class="n">topFocusedDisplayId</span><span class="p">)</span><span class="w"> </span><span class="n">判断多屏焦点属性mPerDisplayFocusEnabled的值</span><span class="err">，</span><span class="n">如果开启则在该显示器上查询聚焦窗口</span><span class="err">，</span><span class="n">否则返回NULL</span><span class="w">
</span></code></pre></div></div>

<hr />

<h3 id="443-ps多屏异显">4.4.3. PS：多屏异显</h3>

<p><strong>PS：</strong>多屏异显（物理屏）的修改是将下面的属性改成false</p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">    </span><span class="o">&lt;!--</span><span class="w"> </span><span class="n">When</span><span class="w"> </span><span class="n">true</span><span class="p">,</span><span class="w"> </span><span class="n">local</span><span class="w"> </span><span class="n">displays</span><span class="w"> </span><span class="n">that</span><span class="w"> </span><span class="n">do</span><span class="w"> </span><span class="n">not</span><span class="w"> </span><span class="n">contain</span><span class="w"> </span><span class="n">any</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">their</span><span class="w"> </span><span class="n">own</span><span class="w"> </span><span class="n">content</span><span class="w"> </span><span class="n">will</span><span class="w"> </span><span class="n">automatically</span><span class="w">
         </span><span class="n">mirror</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">content</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">default</span><span class="w"> </span><span class="n">display.</span><span class="w"> </span><span class="o">--&gt;</span><span class="w">
    </span><span class="o">&lt;</span><span class="n">bool</span><span class="w"> </span><span class="n">name</span><span class="o">=</span><span class="s2">"config_localDisplaysMirrorContent"</span><span class="o">&gt;</span><span class="n">false</span><span class="o">&lt;/</span><span class="n">bool</span><span class="o">&gt;</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="45-android-12输入法未显示在对应displayid上面">4.5. Android 12输入法未显示在对应DisplayID上面</h2>

<h3 id="451-修改方法">4.5.1. 修改方法</h3>

<p><code class="language-plaintext highlighter-rouge">services/core/java/com/android/server/wm/WindowState.java</code>：修改getImeControlTarget的返回值，Android 13上此处已修改。修复IME不显示在对应的display id上。</p>

<p>通过dumpsys window windows查看：显示的display id（是正常实际点击到的虚拟屏display id）(未修改时会一直指向display id=0）:</p>

<p><code class="language-plaintext highlighter-rouge">imeLayeringTarget in display# 3 Window{d8d67ca u0 com.example.test/com.example.test.MainActivity}</code></p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    InsetsControlTarget getImeControlTarget() {
        final DisplayContent dc = getDisplayContent();
        final WindowState parentWindow = dc.getParentWindow();

+        // If target's display has a parent, IME is displayed in the parent display.
<span class="gi">+        // return dc.getImeHostOrFallback(parentWindow != null ? parentWindow : this);
+        // fix ime show on current virtual display
+        // use dump window to see imeLayeringTarget in correct display
+        return dc.getImeHostOrFallback(this);
</span>    }
</code></pre></div></div>

<hr />

<h1 id="5-调试技巧">5. 调试技巧</h1>

<h2 id="51-adb-shell-ime">5.1. adb shell ime</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 列出所有输入法服务</span>
adb shell ime list <span class="nt">-a</span> <span class="nt">-s</span>
<span class="c"># 设置输入法</span>
adb shell ime <span class="nb">set </span>com.sohu.inputmethod.sogouoem/.SogouIME
<span class="c"># 启用输入法</span>
adb shell ime <span class="nb">enable </span>com.sohu.inputmethod.sogouoem/.SogouIME
<span class="c"># 不启用输入法</span>
adb shell ime disable com.sohu.inputmethod.sogouoem/.SogouIME
<span class="c"># 重置为默认输入法</span>
adb shell ime reset
<span class="c"># 从设置获取默认输入法</span>
adb shell settings get secure default_input_method
</code></pre></div></div>

<hr />

<h2 id="52-dump获取信息">5.2. Dump获取信息</h2>

<h3 id="521-获取到输入法的各种信息">5.2.1. 获取到输入法的各种信息</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell dumpsys input_method

<span class="c"># 可以获取到输入法的各种信息, 并且可以过滤window信息</span>
adb shell dumpsys input_method | <span class="nb">grep</span> <span class="nt">-i</span> window
</code></pre></div></div>

<h3 id="522-获取输入法的窗口状态信息">5.2.2. 获取输入法的窗口状态信息</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 获取输入法的窗口状态信息</span>
adb shell dumpsys window | <span class="nb">grep</span> <span class="nt">-i</span> input
</code></pre></div></div>

<h3 id="523-获取输入法的窗口层级信息">5.2.3. 获取输入法的窗口层级信息</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 获取输入法的窗口层级信息</span>
adb shell dumpsys SurfaceFlinger
</code></pre></div></div>

<h3 id="524-获取当前存在几个display">5.2.4. 获取当前存在几个Display</h3>

<p>通过dumpsys display查看当前有几个display：</p>

<pre><code class="language-log">adb shell dumpsys display

Display States: size=3
  Display Id=0
  Display State=ON
  Display Brightness=0.05
  Display SdrBrightness=0.05
  Display Id=2
  Display State=ON
  Display Brightness=0.0
  Display SdrBrightness=0.0
  Display Id=3
  Display State=ON
  Display Brightness=0.0
  Display SdrBrightness=0.0
</code></pre>

<h3 id="525-通过adb命令将app启动在指定的屏幕">5.2.5. 通过adb命令将app启动在指定的屏幕</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 命令参考</span>
adb shell am start <span class="nt">-n</span> com.android.demo/com.android.demo.MainActivity <span class="nt">--display</span> 1
adb shell am start <span class="nt">-n</span> com.android.demo/com.android.demo.MainActivity <span class="nt">--user</span> 0 <span class="nt">--display</span> 1

<span class="c"># Android 12原生setting</span>
adb shell
<span class="c"># 原生设置界面</span>
am start <span class="nt">-n</span> com.android.car.settings/.Settings_Launcher_Homepage <span class="nt">--display</span> 2
</code></pre></div></div>

<ul>
  <li>参数–display指定屏幕， display 0，表示第一块屏幕； display 1，表示第2块屏幕</li>
  <li>参数–user可以启动指定的用户，在多用户下有效，系统默认是–user 0</li>
</ul>

<hr />

<h2 id="53-应用使用输入法">5.3. 应用使用输入法</h2>

<h3 id="531-获取输入法打开的状态">5.3.1. 获取输入法打开的状态</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//获取输入法打开的状态</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isShowing</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">InputMethodManager</span> <span class="n">imm</span> <span class="o">=</span> <span class="o">(</span><span class="nc">InputMethodManager</span><span class="o">)</span> <span class="n">context</span><span class="o">.</span><span class="na">getSystemService</span><span class="o">(</span><span class="nc">Context</span><span class="o">.</span><span class="na">INPUT_METHOD_SERVICE</span><span class="o">);</span>
    <span class="k">return</span> <span class="n">imm</span><span class="o">.</span><span class="na">isActive</span><span class="o">();</span><span class="c1">//isOpen若返回true，则表示输入法打开</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="532-调用显示系统默认的输入法">5.3.2. 调用显示系统默认的输入法</h3>

<ul>
  <li>方法一：</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">InputMethodManager</span> <span class="n">imm</span> <span class="o">=</span> <span class="o">(</span><span class="nc">InputMethodManager</span><span class="o">)</span> <span class="n">getSystemService</span><span class="o">(</span><span class="nc">Context</span><span class="o">.</span><span class="na">INPUT_METHOD_SERVICE</span><span class="o">);</span>
<span class="n">imm</span><span class="o">.</span><span class="na">showSoftInput</span><span class="o">(</span><span class="n">m_receiverView</span> <span class="cm">/*接受软键盘输入的视图(View)*/</span><span class="o">,</span> <span class="nc">InputMethodManager</span><span class="o">.</span><span class="na">SHOW_FORCED</span> <span class="cm">/*提供当前操作的标记，SHOW_FORCED表示强制显示*/</span><span class="o">);</span>

<span class="c1">//view为接受软键盘输入的视图，SHOW_FORCED表示强制显示</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">showOrHide</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nc">View</span> <span class="n">view</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">InputMethodManager</span> <span class="n">imm</span> <span class="o">=</span> <span class="o">(</span><span class="nc">InputMethodManager</span><span class="o">)</span> <span class="n">context</span><span class="o">.</span><span class="na">getSystemService</span><span class="o">(</span><span class="nc">Context</span><span class="o">.</span><span class="na">INPUT_METHOD_SERVICE</span><span class="o">);</span>
    <span class="c1">//imm.showSoftInput(view, InputMethodManager.SHOW_FORCED); //SHOW_FORCED表示强制显示</span>
    <span class="c1">//imm.hideSoftInputFromWindow(view.getWindowToken(), 0); //强制隐藏键盘</span>
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li>方法二:</li>
</ul>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//如果输入法在窗口上已经显示，则隐藏，反之则显示</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">showOrHide</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">InputMethodManager</span> <span class="n">imm</span> <span class="o">=</span> <span class="o">(</span><span class="nc">InputMethodManager</span><span class="o">)</span> <span class="n">context</span><span class="o">.</span><span class="na">getSystemService</span><span class="o">(</span><span class="nc">Context</span><span class="o">.</span><span class="na">INPUT_METHOD_SERVICE</span><span class="o">);</span>
    <span class="c1">//这个方法可以实现输入法在窗口上切换显示，如果输入法在窗口上已经显示，则隐藏；如果隐藏，则显示输入法到窗口上</span>
    <span class="c1">//该方法在IMM中最终会调用hideSoftInputFromWindow或者showSoftInput</span>
    <span class="n">imm</span><span class="o">.</span><span class="na">toggleSoftInput</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="nc">InputMethodManager</span><span class="o">.</span><span class="na">HIDE_NOT_ALWAYS</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="533-调用隐藏系统默认的输入法">5.3.3. 调用隐藏系统默认的输入法</h3>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//调用隐藏系统默认的输入法</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">showOrHide</span><span class="o">(</span><span class="nc">Context</span> <span class="n">context</span><span class="o">,</span> <span class="nc">Activity</span> <span class="n">activity</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">//activity为当前activity</span>
    <span class="o">((</span><span class="nc">InputMethodManager</span><span class="o">)</span> <span class="n">context</span><span class="o">.</span><span class="na">getSystemService</span><span class="o">(</span><span class="nc">Context</span><span class="o">.</span><span class="na">INPUT_METHOD_SERVICE</span><span class="o">))</span>
            <span class="o">.</span><span class="na">hideSoftInputFromWindow</span><span class="o">(</span><span class="n">activity</span><span class="o">.</span><span class="na">getCurrentFocus</span><span class="o">().</span><span class="na">getWindowToken</span><span class="o">(),</span>
                    <span class="nc">InputMethodManager</span><span class="o">.</span><span class="na">HIDE_NOT_ALWAYS</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<hr />

<h1 id="6-参考文章">6. 参考文章</h1>

<ul>
  <li><a href="http://aospxref.com/android-12.0.0_r3/">Android 12 AOSP源码网址</a></li>
  <li><a href="https://blog.csdn.net/weixin_37444276/article/details/103114855">Android 输入法框架流程整理</a></li>
  <li><a href="https://www.jianshu.com/p/cfc2e7e91c43">Android输入法IMMS服务启动流程(1)（服务注册）</a></li>
  <li><a href="https://www.jianshu.com/p/730a290e4144">Android输入法IMMS服务启动流程(2)(systemRunning)</a></li>
  <li><a href="https://www.jianshu.com/p/e439d0c65205">Android输入法IMMS服务启动流程(3)(启动IMS应用)</a></li>
  <li><a href="https://www.jianshu.com/p/71b494e188a6">Android输入法IMMS服务启动流程(4)(启动IMS应用2)</a></li>
  <li><a href="https://www.jianshu.com/p/18117c527bca">Android输入法IMMS服务启动流程(5)(onUnlockUser过程)</a></li>
  <li><a href="https://www.jianshu.com/p/e1c79f686f4f">Android 输入法窗口焦点获取流程(1),窗口和Session创建</a></li>
  <li><a href="https://www.jianshu.com/p/d7f6051e2b92">Android 输入法窗口焦点获取流程(2) ,输入法窗口和应用窗口绑定</a></li>
  <li><a href="https://www.jianshu.com/p/d0b001a055c1">Android输入法弹出流程</a></li>
  <li><a href="https://www.jianshu.com/p/de1d68148388">Android 输入法，字符输入和显示过程流程</a></li>
  <li><a href="https://source.android.google.cn/docs/core/display/multi_display/ime-support?hl=zh-cn#ime-multi">Google官方文档-输入法支持</a></li>
  <li><a href="https://blog.csdn.net/qq_23452385/article/details/127585823">InputMethodManagerService启动-Android12</a></li>
  <li><a href="https://juejin.cn/post/7021793703513571364">Android S 输入法相关新特性整理</a></li>
  <li><a href="https://blog.csdn.net/qq_23452385/article/details/118770853">IMS:InputDispatcher的焦点设置</a></li>
  <li><a href="https://blog.51cto.com/u_2982693/3355296">第十七篇 –ANDROID DisplayManager 服务解析一</a></li>
</ul>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="android" /><category term="ime" /><summary type="html"><![CDATA[阅读Android AOSP 12版本代码，对输入法IME整体框架模块进行学习梳理，内容包含输入法框架三部分IMM、IMMS、IMS的启动流程、点击弹出流程、显示/隐藏流程，以及常见问题和调试技巧。]]></summary></entry><entry><title type="html">Android Automotive Framework调试技巧</title><link href="https://alonealive.github.io/android/android_debug/" rel="alternate" type="text/html" title="Android Automotive Framework调试技巧" /><published>2022-12-01T05:58:02+00:00</published><updated>2022-12-01T05:58:02+00:00</updated><id>https://alonealive.github.io/android/android_debug</id><content type="html" xml:base="https://alonealive.github.io/android/android_debug/"><![CDATA[<blockquote>
  <p>Android系统调试技巧积累笔记，主要包含Android Framework，以及日常接触的git、adb、linux系统等调试技巧。</p>
</blockquote>

<h1 id="1-android调试技巧">1. Android调试技巧</h1>

<h2 id="11-查看socket链接状态">1.1. 查看socket链接状态：</h2>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">adb</span><span class="w"> </span><span class="n">shell</span><span class="w">
</span><span class="c1"># netstat -ap |grep 7777</span><span class="w">
</span><span class="n">tcp</span><span class="w">        </span><span class="m">0</span><span class="w">      </span><span class="m">0</span><span class="w"> </span><span class="m">0.0.0.0</span><span class="o">:</span><span class="m">7777</span><span class="w">            </span><span class="m">0.0.0.0</span><span class="o">:*</span><span class="w">               </span><span class="n">LISTEN</span><span class="w">      </span><span class="m">2596</span><span class="o">/</span><span class="n">test_service</span><span class="w">
</span><span class="n">tcp</span><span class="w">        </span><span class="m">0</span><span class="w">      </span><span class="m">0</span><span class="w"> </span><span class="n">localhost</span><span class="o">:</span><span class="m">7777</span><span class="w">          </span><span class="n">localhost</span><span class="o">:</span><span class="m">45634</span><span class="w">         </span><span class="n">ESTABLISHED</span><span class="w"> </span><span class="m">2596</span><span class="o">/</span><span class="n">test_service</span><span class="w">
</span><span class="n">tcp6</span><span class="w">       </span><span class="m">0</span><span class="w">      </span><span class="m">0</span><span class="w"> </span><span class="n">localhost</span><span class="o">:</span><span class="m">45634</span><span class="w">         </span><span class="n">localhost</span><span class="o">:</span><span class="m">7777</span><span class="w">          </span><span class="n">ESTABLISHED</span><span class="w"> </span><span class="m">5336</span><span class="o">/</span><span class="n">com.android.test</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="12-socket套接字">1.2. socket套接字</h2>

<p><strong>socket的三次握手：</strong></p>

<ol>
  <li>第一次握手：客户端需要发送一个syn j 包，试着去链接服务器端，于是客户端我们需要提供一个链接函数</li>
  <li>第二次握手：服务器端需要接收客户端发送过来的syn J+1 包，然后在发送ack包，所以我们需要有服务器端接受处理函数</li>
  <li>第三次握手：客户端的处理函数和服务器端的处理函数</li>
</ol>

<p>三次握手只是一个数据传输的过程，但是，我们传输前需要一些准备工作，比如将创建一个套接字，收集一些计算机的资源，将一些资源绑定套接字里面，以及接受和发送数据的函数等等，这些功能接口在一起构成了socket的编程</p>

<p><strong>server服务端：</strong></p>
<ol>
  <li>socket()：创建socket</li>
  <li>bind():绑定socket和端口号</li>
  <li>listen():监听该端口号</li>
  <li>accept():接收来自客户端的连接请求（阻塞等待，使用循环）</li>
  <li>recv():从socket中读取字符（接收socket客户端的消息，可使用子线程控制多个连接）</li>
  <li>close():关闭socket</li>
</ol>

<p><strong>client客户端：</strong></p>
<ol>
  <li>socket()：创建socket</li>
  <li>connect()：连接指定计算机的端口（<strong>和服务端的accept()连接</strong>）</li>
  <li>send()：向socket中写入信息（<strong>和服务端的recv()连接</strong>）</li>
  <li>close()：关闭socket</li>
</ol>

<hr />

<h3 id="121-网络字节顺序与本地字节顺序之间的转换函数">1.2.1. 网络字节顺序与本地字节顺序之间的转换函数</h3>

<ul>
  <li>参考：<a href="https://blog.csdn.net/zhuguorong11/article/details/52300680">htons(), ntohl(), ntohs()，htons()这4个函数</a></li>
</ul>

<p>在C/C++写网络程序的时候，往往会遇到字节的网络顺序和主机顺序的问题。这是就可能用到<code class="language-plaintext highlighter-rouge">htons()</code>, <code class="language-plaintext highlighter-rouge">ntohl()</code>, <code class="language-plaintext highlighter-rouge">ntohs()</code>，<code class="language-plaintext highlighter-rouge">htons()</code>这4个函数。</p>

<p><strong>网络字节顺序与本地字节顺序之间的转换函数：</strong></p>

<ul>
  <li>htonl()–“Host to Network Long”</li>
  <li>ntohl()–“Network to Host Long”</li>
  <li>htons()–“Host to Network Short”</li>
  <li>ntohs()–“Network to Host Short”</li>
</ul>

<p>之所以需要这些函数是因为计算机数据表示存在两种字节顺序：<code class="language-plaintext highlighter-rouge">NBO与HBO</code></p>

<ul>
  <li>网络字节顺序NBO(Network Byte Order): 按从高到低的顺序存储，在网络上使用统一的网络字节顺序，可以避免兼容性问题。</li>
  <li>主机字节顺序(HBO，Host Byte Order): 不同的机器HBO不相同，与CPU设计有关，数据的顺序是由cpu决定的,而与操作系统无关。</li>
  <li>
    <ul>
      <li>如Intel x86结构下, short型数0x1234表示为34 12, int型数0x12345678表示为78 56 34 12</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>如IBM power PC结构下, short型数0x1234表示为12 34, int型数0x12345678表示为12 34 56 78</li>
    </ul>
  </li>
</ul>

<hr />

<h2 id="13-android签名">1.3. Android签名</h2>

<blockquote>
  <p>Android的签名机制是一种Android保证系统安全的方式。Android中的签名就是对我们的应用或者系统加上特定的一些信息，防止被恶意篡改，而加上的这些特定信息就是签名。</p>
</blockquote>

<h3 id="131-签名作用">1.3.1. 签名作用</h3>

<ol>
  <li>确保应用开发者身份的唯一性。因为Android应用包名可以随意替换，如果没有签名保证应用的唯一性，可能会造成其他的应用替换本身。</li>
  <li>保证应用在信息传输中的的完整性，签名对于包中的每个文件进行处理，以此确保包中内容不被替换或者被篡改。如果没有签名保护，则有可能被恶意程序盗取个人信息。</li>
</ol>

<h3 id="132-签名的组成">1.3.2. 签名的组成</h3>

<blockquote>
  <p>Android源码中的系统签名统一存放路径：build/target/product/security</p>
</blockquote>

<ul>
  <li><code class="language-plaintext highlighter-rouge">.pem类型文件</code>：在android对apk签名的时候，.pem这种文件就是一个X.509的数字证书，里面有用户的公钥等信息，是用来解密的。文件格式里面不仅可以存储数字证书，还能存各种key,这个可以公开，主要用于验证某个App或者其它的是否由相应的私钥签名。</li>
  <li><code class="language-plaintext highlighter-rouge">.pk8类型文件</code>：以.pk8为扩展名的文件，应该和PKCS 8是对应的，用来保存private key,并且这个私钥需要保密保存，不能公开。</li>
</ul>

<p>Android系统签名路径下一共有5种key：</p>
<ol>
  <li>testkey：平台默认key。如果没有做特殊变更的话，系统编译默认使用该key。由于testkey是公开的，任何人都可以获取，所以存在一定的风险。如果项目由特殊需求，则一般使用 自己创建key作为系统默认key。</li>
  <li>platform：平台的核心应用签名之一，签名的apk是完成系统的核心功能。这些apk所在的进程UID是system。manifest节点中有添加sharedUserId=”android.uid.system”。</li>
  <li>shared：这个签名的apk可以和home/contacts进程共享数据。manifest节点中有添加android:sharedUserId=”android.uid.shared”。 一般用在与tel相关的apk。</li>
  <li>media:这个签名的apk是media/download的一部分。manifest节点中有添加android:sharedUserId=”android.media”。 一般应用在media相关的一些apk。每个Apk包会在其对应的Android.mk中设置LOCAL_CERTIFICATE属性，指定其中一个密钥。（如果没有设置此变量,则默认使用testkey）</li>
  <li>verity：一种特殊的系统签名。在系统编译时会对系统进行编译处理。需要单独生成</li>
</ol>

<h3 id="133-签名的生成">1.3.3. 签名的生成</h3>

<blockquote>
  <p>README中介绍了怎么通过内置工具生成和替换系统默认预置的各种key，以及各种key的简单使用实例</p>
</blockquote>

<p>在Android 根目录下执行命令生成releasekey、platform、shared、media。以platform为例，其他key以相同方式生成</p>

<p><code class="language-plaintext highlighter-rouge">development/tools/make_key platform'/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'</code></p>

<p><strong>释义：</strong></p>
<ul>
  <li>所在国家 (Country) 简称：C ，只能是国家字母缩写，如中国：CN</li>
  <li>所在省份 (State/Provice) 简称S</li>
  <li>所在城市 (Locality) 简称L</li>
  <li>单位名称 (Organization Name) ：简称O</li>
  <li>部门名称（ Organizational Unit Name (eg, section) ）：简称OU</li>
  <li>公用名称 (Common Name) ：简称CN。代码签名证书申请单位名称或网站域名。</li>
  <li>邮箱（Email）</li>
</ul>

<p>系统会提示让你输入生成该key所需要的密码，输入回车即可。如果设置key密码，则编译不通过。</p>

<h3 id="134-验证生成的key">1.3.4. 验证生成的key</h3>

<p>生成的key存在于Android根目录下，把key移动到<code class="language-plaintext highlighter-rouge">/build/target/product/security</code>下并替换之前的key，同时需要使用OpenSSL的工具来验证一下生成的key是否正常。以releaseKey为例，执行以下命令：
<code class="language-plaintext highlighter-rouge">openssl x509 -noout -subject -issuer -in releaseKey.x509.pem</code></p>

<p>输出：</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">subject</span><span class="o">=</span> /C<span class="o">=</span>CN/ST<span class="o">=</span>NanJing/L<span class="o">=</span>NanJing/O<span class="o">=</span>13/OU<span class="o">=</span>123/CN<span class="o">=</span>123/emailAddress<span class="o">=</span><span class="nb">test</span>@123.com
<span class="nv">issuer</span><span class="o">=</span> /C<span class="o">=</span>CN/ST<span class="o">=</span>NanJing/L<span class="o">=</span>NanJing/O<span class="o">=</span>123/OU<span class="o">=</span>13/CN<span class="o">=</span>123/emailAddress<span class="o">=</span><span class="nb">test</span>@123.com
</code></pre></div></div>

<p>输出的内容与生成该key时输入的内容一致，则代表key验证成功</p>

<h3 id="135-生成verity_key">1.3.5. 生成verity_key</h3>

<ol>
  <li>编译verity：在android根目录下编译verity。<code class="language-plaintext highlighter-rouge">make generate_verity_key (mmm system/extras/verity/)</code></li>
  <li>生成veritykey签名：<code class="language-plaintext highlighter-rouge">development/tools/make_key veritykey '/C=CN/ST=NanJing/L=NanJing /O=123/OU=13/CN=123/emailAddress=test@123.com'</code></li>
  <li>执行以下命令生成.pk8 .pem文件：<code class="language-plaintext highlighter-rouge">out/host/linux-x86/bin/generate_verity_key -convert veritykey.x509.pem verity_key</code></li>
</ol>

<h3 id="136-签名的使用和配置">1.3.6. 签名的使用和配置</h3>

<p>签名的使用分为两种情况：</p>
<ol>
  <li>不区分user和debug版本，仅替换当前设备编译是所使用的key。</li>
  <li>分区user和debug版本，编译不同版本的时候使用不同的key。</li>
</ol>

<h4 id="1361-不区分user和debug版本">1.3.6.1. 不区分user和debug版本</h4>

<ol>
  <li>将android根目录下生成的各种.pk8和.pem文件copy到<code class="language-plaintext highlighter-rouge">build/target/product/security/</code>目录下，覆盖之前所有的默认key。将veritykey.pk8，veritykey.x509.pem，verity_key.pub重命名:verity.pk8，verity.x509.pem，verity_key</li>
  <li>修改android配置：</li>
</ol>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/build/core/config.mk</code>中定义的变量：<code class="language-plaintext highlighter-rouge">DEFAULT_SYSTEM_DEV_CERTIFICATE := build/target/product/security/testkey</code></li>
</ul>

<p><strong>PS:</strong><code class="language-plaintext highlighter-rouge">PRODUCT_DEFAULT_DEV_CERTIFICATE</code>：mk文件中定义的<code class="language-plaintext highlighter-rouge">LOCAL_CERTIFICATE</code>变量，如果mk文件中定义了<code class="language-plaintext highlighter-rouge">LOCAL_CERTIFICATE</code>，编译则使用所定义的签名文件，如果没有定义LOCAL_CERTIFICATE 变量，则进入else流程</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/build/core/Makefile</code>中定义变量：<code class="language-plaintext highlighter-rouge">ifeq ($(DEFAULT_SYSTEM_DEV_CERTIFICATE),build/target/product/security/releasekey) BUILD_VERSION_TAGS += release-keys</code></li>
</ul>

<p><strong>PS:</strong>这段code主要是判断在config.mk中定义的<code class="language-plaintext highlighter-rouge">DEFAULT_SYSTEM_DEV_CERTIFICATE</code>变量，并在编译的时候使用对应的build_key。在刷机后可以通过<code class="language-plaintext highlighter-rouge">getprop build.tags</code>来查看</p>

<ul>
  <li>修改SELinux全新啊：<code class="language-plaintext highlighter-rouge">system/sepolicy/private/keys.conf system/sepolicy/prebuilts/api/{apilevel}/private/keys.conf</code></li>
</ul>

<hr />

<h4 id="1362-分区user和debug版本">1.3.6.2. 分区user和debug版本</h4>

<p>以testkey作为示例：</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">device/project/</code>路径下新建一个用于存放新建key文件的文件夹security。仅用于存在key文件。后续步骤中会对路径做条件判断。</li>
  <li>在<code class="language-plaintext highlighter-rouge">device/project/product.mk</code>中进行条件判断：获取TARGET_BUILD_VARIANT，如果<code class="language-plaintext highlighter-rouge">TARGET_BUILD_VARIANT为userdebug/eng</code>，则使用<code class="language-plaintext highlighter-rouge">build/make/target/product/security/testkey</code>；否则使用我们自定义的文件夹中的key</li>
</ol>

<h3 id="137-android设备判断系统签名key">1.3.7. Android设备判断系统签名key</h3>

<p>通过以下命令验证打包编译好的系统使用的签名，这种方式只适用与不区分user和debug版本的第一种修改：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb root<span class="p">;</span>adb remount
adb shell
<span class="nb">cd </span>system
getprop | <span class="nb">grep </span>ro.build.tags

<span class="c">#可以看到一行 ro.build.tags=release-keys</span>
</code></pre></div></div>

<p>如果要区分user和debug版本的key，则可以使用下面两个方法：</p>
<ol>
  <li>在Android系统<code class="language-plaintext highlighter-rouge">adb install -r test.apk</code>安装进行判断</li>
  <li>从设备中随意pull一个apk出来。然后将后缀名.apk修改为.zip，接着进行解压操作，这样会在META-INF中看到CERT.RSA文件，通过<code class="language-plaintext highlighter-rouge">keytool -printcert -file \...\path\CERT.RSA</code>可以查看此apk的签名内容，如果签名内容与自己生成的key内容一致，则说明当前系统签名使用的是自己生成的key</li>
</ol>

<hr />

<h3 id="138-生成三方app使用的签名文件">1.3.8. 生成三方APP使用的签名文件</h3>

<p>在三方App应用中，因为不用经过Android系统编译，所以如果没有签名文件的情况下用到特殊权限则无法安装使用。所以可以提供对应的.keystore文件供其使用</p>

<p>在自己生成的key所在的路径下：</p>
<ol>
  <li>生成platform.pem文件：platform.pk8生成了<code class="language-plaintext highlighter-rouge">.pem</code>文件:<code class="language-plaintext highlighter-rouge">openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out platform.priv.pem -nocrypt</code></li>
  <li>生成platform.p12文件: <code class="language-plaintext highlighter-rouge">openssl pkcs12 -export -in platform.x509.pem -out platform.p12 -inkey platform.pem -password pass:pwd -name name</code>，其中name为alias name，pwd为alias pwd</li>
  <li>生成platform.keystore：<code class="language-plaintext highlighter-rouge">keytool -importkeystore -deststorepass pwd-destkeystore ./platform.keystore -srckeystore ./platform.p12 -srcstoretype PKCS12 -srcstorepass pwd</code></li>
</ol>

<p>上面三个步骤可以在该路径下生成一个<code class="language-plaintext highlighter-rouge">platform.keystore</code>文件。该文件可用于apk签名使用。</p>

<hr />

<h2 id="14-bootchart性能工具使用方式">1.4. Bootchart性能工具使用方式</h2>

<blockquote>
  <p>参考<a href="https://blog.csdn.net/c_z_w/article/details/83544687">性能分析工具—bootchart工具使用</a></p>
</blockquote>

<p>bootchart是一个用于linux启动过程性能分析的开源工具软件，在系统启动过程中自动收集CPU占用率、磁盘吞吐率、进程等信息，并以图形方式显示分析结果，可用作指导优化系统启动过程。</p>

<p>bootchart让用户可以很直观的查看系统启动的过程和各个过程耗费的时间，以便让用户能够分析启动过程，从而进行优化以提高启动时间。它由bootchartd服务和bootchart-render两部分组成，后者主要负责生成启动流程的分析结果图。</p>

<p>Android系统源码中有bootchart的实现，路径在<code class="language-plaintext highlighter-rouge">system/core/init/bootchart.cpp</code>中, bootchart通过内嵌在init进程中实现，在后台执行测量。<strong>不过bootchart的测量时段是 init进程启动之后，不包含uboot和kernel的启动时间</strong></p>

<h3 id="141-bootchart在android平台的使用步骤复杂方式old">1.4.1. bootchart在android平台的使用步骤（复杂方式&amp;old）</h3>

<ol>
  <li>使能调试设备的bootchart程序，进行设备必要的开机log:<code class="language-plaintext highlighter-rouge">adb shell 'touch /data/bootchart/enabled'</code></li>
  <li>在reboot调试设备后，进入<code class="language-plaintext highlighter-rouge">/data/bootchart</code>目录，先行删除enabled, 再执行<code class="language-plaintext highlighter-rouge">tar -zcf bootchart.tgz *</code>， 接着<code class="language-plaintext highlighter-rouge">adb pull /data/bootchart/bootchart.tgz</code>到本地，拷贝到ubuntu</li>
  <li>ubuntu机安装bootchart工具:<code class="language-plaintext highlighter-rouge">sudo apt-get install bootchart</code>和<code class="language-plaintext highlighter-rouge">sudo apt-get install pybootchartgui</code></li>
  <li>生成bootchart图表：<code class="language-plaintext highlighter-rouge">bootchart bootchart.tg</code></li>
</ol>

<hr />

<h3 id="142-bootchart在android平台的使用步骤方便方式new">1.4.2. bootchart在android平台的使用步骤（方便方式&amp;new）</h3>

<ol>
  <li>使能调试设备的bootchart程序，进行设备必要的开机log:<code class="language-plaintext highlighter-rouge">adb shell 'touch /data/bootchart/enabled'</code></li>
  <li>在<code class="language-plaintext highlighter-rouge">adb reboot</code>调试设备后，ubuntu电脑连接调试设备，终端android根目录执行命令：<code class="language-plaintext highlighter-rouge">android$ ./system/core/init/grab-bootchart.sh</code></li>
  <li>bootchart的图标即生成在android根目录</li>
</ol>

<hr />

<h3 id="143-修改bootchart抓取的停止时间">1.4.3. 修改bootchart抓取的停止时间</h3>

<blockquote>
  <p>android高版本上不支持简单的设置方式调整bootchart的结束时间，只能在init.rc中修改，bootchart的启动和结束方式如下：</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Start bootcharting as soon as possible after the data partition is</span>
 <span class="c"># mounted to collect more data.</span>
 <span class="nb">mkdir</span> /data/bootchart 0755 shell shell <span class="nv">encryption</span><span class="o">=</span>Require
 bootchart start
 ...
on property:sys.boot_completed<span class="o">=</span>1
bootchart stop
</code></pre></div></div>

<p>原生的逻辑中，在开机完成时，系统会设置<code class="language-plaintext highlighter-rouge">sys.boot_completed=1</code>，此时bootchart停止抓取信息；</p>

<p>我们可以更改stop的条件，自定义一个属性来实现停止，自己实现可控停止方式如下，在开机后，手动去设置这个属性值=1:</p>

<hr />

<h3 id="144-bootchart图形查看方式">1.4.4. bootchart图形查看方式</h3>

<p>整个图表以时间线为横轴，图标上方为CPU和磁盘的利用情况，下方是各进程的运行状态条，显示各个进程的开始时间与结束时间以及对CPU、I/O的利用情况，各个进程的运行时间以及CPU的使用情况，进而优化系统。</p>

<p>可以通过Laucher的启动完成时间判断开机完成完成时间，也就是开机动画结束的时间</p>

<hr />

<h2 id="15-命令切横竖屏">1.5. 命令切横竖屏：</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#切横屏:</span>
su
settings put system user_rotation 3
settings put system accelerometer_rotation 0
<span class="c">#切竖屏:</span>
settings put system accelerometer_rotation  1/2/ 3 随便改
</code></pre></div></div>

<hr />

<h2 id="16-doze和app-standby模式下的android应用适配">1.6. Doze和App Standby模式下的Android应用适配</h2>

<blockquote>
  <p>参考：<a href="https://www.jianshu.com/p/f044ce3f5913">Doze和App Standby模式下的Android应用适配</a></p>
</blockquote>

<p>从Android6.0(API23)开始, Google为Android加入了两种省电特性，通过管理Android应用(以下简称应用)在非充电状态下的设备中的运行策略来达到延长用户的Android设备使用时间的目的。这两种特性存在一定的差别，Doze模式通过延缓应用在设备长时间待机状态下对于CPU和网络资源的使用来实现节能；而App Standby则是通过延缓最近未被使用的后台应用对于网络的请求来达到同样的目的。</p>

<p>Doze和App Standby在Android6.0及以上的Android设备中可以影响所有运行状态下的Android应用，无论这些应用的Target API是否是指定为API23。为了确保用户获得在不同Android版本下的应用体验一致性，开发者需要对应用在Doze及App Standby模式下做相应的适配。</p>

<h2 id="17-fastboot解锁">1.7. fastboot解锁</h2>

<p>短接后重新重启，然后执行<code class="language-plaintext highlighter-rouge">fastboot flashing unlock</code></p>

<ol>
  <li>短接下车机然后重启</li>
  <li>在window 命令行输入<code class="language-plaintext highlighter-rouge">fastboot unlock flashing</code></li>
  <li>重启后执行接下来的adb root和remount操作</li>
</ol>

<hr />

<h2 id="18-查看make-makefilemk文件执行的代码指令">1.8. 查看make makefile（mk文件）执行的代码指令</h2>

<p><code class="language-plaintext highlighter-rouge">make -n</code></p>

<p>例如：<code class="language-plaintext highlighter-rouge">make android -n</code></p>

<h2 id="19-查看android设备分区大小">1.9. 查看Android设备分区大小</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell
<span class="c"># 同时适用于linux系统</span>
<span class="nb">df</span> <span class="nt">-ah</span>
<span class="c">#或者</span>
<span class="nb">df</span> <span class="nt">-h</span>
</code></pre></div></div>

<hr />

<h2 id="110-kernel-log命令">1.10. kernel log命令</h2>

<p><code class="language-plaintext highlighter-rouge">adb shell cat proc/kmsg</code></p>

<hr />

<h2 id="111-git-push-drafts">1.11. git push drafts</h2>

<p><code class="language-plaintext highlighter-rouge">git push origin HEAD:refs/drafts/master</code></p>

<hr />

<h2 id="112-git-push">1.12. git push</h2>

<p><code class="language-plaintext highlighter-rouge">git push origin HEAD:refs/for/master</code></p>

<p>或者使用<code class="language-plaintext highlighter-rouge">git remote -v</code>查看远程仓库push地址，替代origin</p>

<ul>
  <li>git commit如何修改默认编辑器为vim，修改<code class="language-plaintext highlighter-rouge">~/.gitconfig</code>：</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>core]
    <span class="nv">editor</span><span class="o">=</span>vim
</code></pre></div></div>

<hr />

<h2 id="113-git补丁解决冲突方法">1.13. git补丁解决冲突方法</h2>

<p>这种情况下可以使用下面的方法解决冲突：</p>

<ol>
  <li>执行命令<code class="language-plaintext highlighter-rouge">git am xxxx.patch</code>尝试直接打入补丁。patch可能会报错并中断（注意，虽然命令停止执行了，但依然处于git am命令的运行环境中，可以通过<code class="language-plaintext highlighter-rouge">git status</code>命令查看到当前的状态）</li>
  <li>执行命令<code class="language-plaintext highlighter-rouge">git apply --reject xxxx.patch</code>自动合入patch中不冲突的代码改动，同时保留冲突的部分。这些存在冲突的改动内容会被单独存储到目标源文件的相应目录下，以后缀为<code class="language-plaintext highlighter-rouge">.rej</code>的文件进行保存。比如对<code class="language-plaintext highlighter-rouge">./test/someDeviceDriver.c</code>文件中的某些行合入代码改动失败，则会将这些发生冲突的行数及内容都保存在<code class="language-plaintext highlighter-rouge">./test/someDeviceDriver.c.rej</code>文件中。我们可以在执行<code class="language-plaintext highlighter-rouge">git am</code>命令的目录下执行<code class="language-plaintext highlighter-rouge">find  -name  *.rej</code>命令以查看所有存在冲突的源文件位置</li>
  <li>依据步骤2中生成的<code class="language-plaintext highlighter-rouge">*.rej</code>文件内容逐个手动解决冲突，然后删除这些<code class="language-plaintext highlighter-rouge">*.rej</code>文件。完成这一步骤的操作后，就可以继续执行<code class="language-plaintext highlighter-rouge">git am</code>的过程了</li>
  <li>执行命令<code class="language-plaintext highlighter-rouge">git status</code>查看当前改动过的以及新增的文件，确保没有多添加或少添加文件</li>
  <li>执行命令<code class="language-plaintext highlighter-rouge">git add .</code>将所有改动都添加到暂存区（注意，关键字add后有一个小数点.作为参数，表示当前路径）</li>
  <li>执行命令<code class="language-plaintext highlighter-rouge">git am --resolved</code>继续步骤1中被中断的patch合入操作。合入完成后，会有提示信息输出</li>
  <li>执行命令<code class="language-plaintext highlighter-rouge">git  log</code>确认合入状态</li>
</ol>

<hr />

<h2 id="114-报错is-locked-by-another-process">1.14. 报错…is locked by another process</h2>

<p><strong>控制台输出：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~<span class="nv">$ </span><span class="nb">sudo </span>dpkg-reconfigure dash
debconf: DbDriver <span class="s2">"config"</span>: /var/cache/debconf/config.dat is locked by another process: Resource temporarily unavailable
</code></pre></div></div>

<p><strong>解决方式：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>查询进程：
<span class="nb">sudo </span>lsof /var/cache/debconf/config.dat

杀进程：
<span class="nb">sudo kill </span>PID进程号
</code></pre></div></div>

<hr />

<h2 id="115-报错hidl库编译">1.15. 报错HIDL库编译</h2>

<pre><code class="language-log">******************************************************
error: VNDK library: android.hardware.light@2.0's ABI has INCOMPATIBLE CHANGES Please check compatiblity report at : out/soong/.intermediates/hardware/interfaces/light/2.0/android.hardware.light@2.0/android_arm64_armv8-a_cortex-a53_vendor_shared/android.hardware.light@2.0.so.abidiff
******************************************************
 ---- Please update abi references by running platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l android.hardware.light@2.0 ----

//执行该脚本后报错：
Traceback (most recent call last):
  File "development/vndk/tools/header-checker/utils/create_reference_dumps.py", line 224, in &lt;module&gt;
    main()
  File "development/vndk/tools/header-checker/utils/create_reference_dumps.py", line 210, in main
    targets = [Target(True, product), Target(False, product)]
  File "development/vndk/tools/header-checker/utils/create_reference_dumps.py", line 29, in __init__
    self.arch = build_vars[1]
IndexError: list index out of range
</code></pre>

<p><strong>解决方式：</strong></p>

<p>（1）环境判断：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>执行: <span class="nb">ls</span> <span class="nt">-l</span> /bin/sh，查看用的是什么来解释执行Python脚本

<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span> /bin/sh
lrwxrwxrwx 1 root root 4 4月   2  2019 /bin/sh -&gt; dash

如果用dash来解释执行Python脚本，得切换成bash
执行： <span class="nb">sudo </span>dpkg-reconfigure dash  选择no
</code></pre></div></div>

<p><strong>控制台输出：</strong></p>

<pre><code class="language-log">Android$ development/vndk/tools/header-checker/utils/create_reference_dumps.py -l android.hardware.light@2.0
Removing reference dumps...
removing /home/workspace/Android/prebuilts/abi-dumps/vndk/28/32/arm_armv7-a-neon/source-based/android.hardware.light@2.0.so.lsdump.gz
removing /home/workspace/Android/prebuilts/abi-dumps/ndk/28/32/arm_armv7-a-neon/source-based/android.hardware.light@2.0.so.lsdump.gz
making libs for product: aosp_arm_ab
</code></pre>

<p><strong>再次执行：</strong></p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">//</span><span class="n">即自己编译的product</span><span class="w"> </span><span class="n">name</span><span class="err">（</span><span class="n">out</span><span class="o">/</span><span class="n">target</span><span class="o">/</span><span class="n">product</span><span class="o">/</span><span class="n">test_product</span><span class="err">）</span><span class="w">
</span><span class="n">Android</span><span class="o">$</span><span class="w"> </span><span class="n">development</span><span class="o">/</span><span class="n">vndk</span><span class="o">/</span><span class="n">tools</span><span class="o">/</span><span class="n">header</span><span class="o">-</span><span class="n">checker</span><span class="o">/</span><span class="n">utils</span><span class="o">/</span><span class="n">create_reference_dumps.py</span><span class="w"> </span><span class="o">-</span><span class="n">l</span><span class="w"> </span><span class="n">android.hardware.light</span><span class="o">@</span><span class="m">2.0</span><span class="w"> </span><span class="o">-</span><span class="n">product</span><span class="w"> </span><span class="n">test_product</span><span class="w">
</span><span class="n">Removing</span><span class="w"> </span><span class="n">reference</span><span class="w"> </span><span class="n">dumps...</span><span class="w">
</span><span class="n">removing</span><span class="w"> </span><span class="o">/</span><span class="n">home</span><span class="o">//</span><span class="n">workspace</span><span class="o">/</span><span class="n">Android</span><span class="o">/</span><span class="n">prebuilts</span><span class="o">/</span><span class="n">abi</span><span class="o">-</span><span class="n">dumps</span><span class="o">/</span><span class="n">vndk</span><span class="o">/</span><span class="m">28</span><span class="o">/</span><span class="m">64</span><span class="o">/</span><span class="n">arm_armv8</span><span class="o">-</span><span class="n">a</span><span class="o">/</span><span class="n">source</span><span class="o">-</span><span class="n">based</span><span class="o">/</span><span class="n">android.hardware.light</span><span class="o">@</span><span class="m">2.0</span><span class="n">.so.lsdump.gz</span><span class="w">
</span><span class="n">removing</span><span class="w"> </span><span class="o">/</span><span class="n">home</span><span class="o">//</span><span class="n">workspace</span><span class="o">/</span><span class="n">Android</span><span class="o">/</span><span class="n">prebuilts</span><span class="o">/</span><span class="n">abi</span><span class="o">-</span><span class="n">dumps</span><span class="o">/</span><span class="n">ndk</span><span class="o">/</span><span class="m">28</span><span class="o">/</span><span class="m">64</span><span class="o">/</span><span class="n">arm_armv8</span><span class="o">-</span><span class="n">a</span><span class="o">/</span><span class="n">source</span><span class="o">-</span><span class="n">based</span><span class="o">/</span><span class="n">android.hardware.light</span><span class="o">@</span><span class="m">2.0</span><span class="n">.so.lsdump.gz</span><span class="w">
</span><span class="n">removing</span><span class="w"> </span><span class="o">/</span><span class="n">home</span><span class="o">//</span><span class="n">workspace</span><span class="o">/</span><span class="n">Android</span><span class="o">/</span><span class="n">prebuilts</span><span class="o">/</span><span class="n">abi</span><span class="o">-</span><span class="n">dumps</span><span class="o">/</span><span class="n">vndk</span><span class="o">/</span><span class="m">28</span><span class="o">/</span><span class="m">64</span><span class="o">/</span><span class="n">arm64_armv8</span><span class="o">-</span><span class="n">a</span><span class="o">/</span><span class="n">source</span><span class="o">-</span><span class="n">based</span><span class="o">/</span><span class="n">android.hardware.light</span><span class="o">@</span><span class="m">2.0</span><span class="n">.so.lsdump.gz</span><span class="w">
</span><span class="n">removing</span><span class="w"> </span><span class="o">/</span><span class="n">home</span><span class="o">//</span><span class="n">workspace</span><span class="o">/</span><span class="n">Android</span><span class="o">/</span><span class="n">prebuilts</span><span class="o">/</span><span class="n">abi</span><span class="o">-</span><span class="n">dumps</span><span class="o">/</span><span class="n">ndk</span><span class="o">/</span><span class="m">28</span><span class="o">/</span><span class="m">64</span><span class="o">/</span><span class="n">arm64_armv8</span><span class="o">-</span><span class="n">a</span><span class="o">/</span><span class="n">source</span><span class="o">-</span><span class="n">based</span><span class="o">/</span><span class="n">android.hardware.light</span><span class="o">@</span><span class="m">2.0</span><span class="n">.so.lsdump.gz</span><span class="w">
</span><span class="n">making</span><span class="w"> </span><span class="n">libs</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">product</span><span class="o">:</span><span class="w"> </span><span class="n">test_product</span><span class="w">
</span><span class="n">Creating</span><span class="w"> </span><span class="n">dumps</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">target_arch</span><span class="o">:</span><span class="w"> </span><span class="n">arm</span><span class="w"> </span><span class="n">and</span><span class="w"> </span><span class="n">variant</span><span class="w">  </span><span class="n">armv8</span><span class="o">-</span><span class="n">a</span><span class="w">
</span><span class="n">Created</span><span class="w"> </span><span class="n">abi</span><span class="w"> </span><span class="n">dump</span><span class="w"> </span><span class="n">at</span><span class="w">  </span><span class="o">/</span><span class="n">home</span><span class="o">//</span><span class="n">workspace</span><span class="o">/</span><span class="n">Android</span><span class="o">/</span><span class="n">prebuilts</span><span class="o">/</span><span class="n">abi</span><span class="o">-</span><span class="n">dumps</span><span class="o">/</span><span class="n">vndk</span><span class="o">/</span><span class="m">28</span><span class="o">/</span><span class="m">64</span><span class="o">/</span><span class="n">arm_armv8</span><span class="o">-</span><span class="n">a</span><span class="o">/</span><span class="n">source</span><span class="o">-</span><span class="n">based</span><span class="o">/</span><span class="n">android.hardware.light</span><span class="o">@</span><span class="m">2.0</span><span class="n">.so.lsdump.gz</span><span class="w">
</span><span class="n">Creating</span><span class="w"> </span><span class="n">dumps</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">target_arch</span><span class="o">:</span><span class="w"> </span><span class="n">arm64</span><span class="w"> </span><span class="n">and</span><span class="w"> </span><span class="n">variant</span><span class="w">  </span><span class="n">armv8</span><span class="o">-</span><span class="n">a</span><span class="w">
</span><span class="n">Created</span><span class="w"> </span><span class="n">abi</span><span class="w"> </span><span class="n">dump</span><span class="w"> </span><span class="n">at</span><span class="w">  </span><span class="o">/</span><span class="n">home</span><span class="o">//</span><span class="n">workspace</span><span class="o">/</span><span class="n">Android</span><span class="o">/</span><span class="n">prebuilts</span><span class="o">/</span><span class="n">abi</span><span class="o">-</span><span class="n">dumps</span><span class="o">/</span><span class="n">vndk</span><span class="o">/</span><span class="m">28</span><span class="o">/</span><span class="m">64</span><span class="o">/</span><span class="n">arm64_armv8</span><span class="o">-</span><span class="n">a</span><span class="o">/</span><span class="n">source</span><span class="o">-</span><span class="n">based</span><span class="o">/</span><span class="n">android.hardware.light</span><span class="o">@</span><span class="m">2.0</span><span class="n">.so.lsdump.gz</span><span class="w">

</span><span class="n">msg</span><span class="o">:</span><span class="w"> </span><span class="n">Processed</span><span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="n">libraries</span><span class="w"> </span><span class="k">in</span><span class="w">  </span><span class="m">2.2664194742838544</span><span class="w">  </span><span class="n">minutes</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="116-android源码改动不大时使用sync">1.16. Android源码改动不大时使用sync</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 项目代码根目录</span>
adb root<span class="p">;</span>adb remount
adb <span class="nb">sync
</span>adb shell <span class="nb">sync
</span>adb reboot
</code></pre></div></div>

<h3 id="1161-adb-sync命令">1.16.1. adb sync命令</h3>

<ul>
  <li>命令意思：同步更新/data/或/system/下的数据（也会包含vendor）</li>
  <li>命令用法：<code class="language-plaintext highlighter-rouge">adb sync [directory]</code></li>
</ul>

<p>如果不指定目录，将同步更新/data/和/system/</p>

<h3 id="1162-adb-shell-sync">1.16.2. adb shell sync</h3>

<ol>
  <li>在shell中执行</li>
  <li>将内存缓冲区中的数据写入到磁盘</li>
</ol>

<p><strong>PS：</strong>为了避免对硬盘的频繁读写，数据一般存放在缓冲区。什么情况下会把缓冲区的数据写入到磁盘中：</p>
<ol>
  <li>通过调用fflush函数刷新缓冲区</li>
  <li>缓冲区已满（8k）</li>
  <li>正常关闭文件，如下：
    <ul>
      <li>调用fclose</li>
      <li>主函数调用return</li>
      <li>调用exit函数</li>
    </ul>
  </li>
</ol>

<hr />

<h2 id="117-修改selinux后make编译">1.17. 修改selinux后make编译</h2>

<ol>
  <li><code class="language-plaintext highlighter-rouge">make selinux_policy</code>：这条命令可以编译所有selinux⽂件，50s左右即可完成⼀次make</li>
  <li><code class="language-plaintext highlighter-rouge">make ⽂件名</code>：<strong>例如：</strong></li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make plat_file_contexts <span class="nt">-j9</span><span class="p">;</span>
make plat_sepolicy.cil <span class="nt">-j9</span><span class="p">;</span>
make vendor_sepolicy.cil <span class="nt">-j9</span><span class="p">;</span>
make vendor_property_contexts <span class="nt">-j9</span><span class="p">;</span>
</code></pre></div></div>

<h3 id="1171-如何同步selinux修改到device">1.17.1. 如何同步selinux修改到device</h3>

<p>如果使⽤make出来的selinux⽂件，建议<code class="language-plaintext highlighter-rouge">adb sync</code>命令同步到device中</p>

<p>或以下命令：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb push out/target/product/product/vendor/etc/selinux/<span class="k">*</span> vendor/etc/selinux/<span class="k">*</span>
adb push out/target/product/product/system/etc/selinux/<span class="k">*</span> system/etc/selinux/<span class="k">*</span>
</code></pre></div></div>

<hr />

<h2 id="118-carservice的jar包">1.18. carservice的jar包</h2>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">/</span><span class="n">out</span><span class="o">/</span><span class="n">target</span><span class="o">/</span><span class="n">common</span><span class="o">/</span><span class="n">obj</span><span class="o">/</span><span class="n">JAVA_LIBRARIES</span><span class="o">/</span><span class="n">android.car_intermediates</span><span class="o">$</span><span class="w"> </span><span class="n">ll</span><span class="w">
</span><span class="n">total</span><span class="w"> </span><span class="m">1988</span><span class="w">
</span><span class="n">drwxr</span><span class="o">-</span><span class="n">xr</span><span class="o">-</span><span class="n">x</span><span class="w">   </span><span class="m">2</span><span class="w"> </span><span class="n">user</span><span class="w"> </span><span class="n">domain</span><span class="w"> </span><span class="n">users</span><span class="w">   </span><span class="m">4096</span><span class="w"> </span><span class="n">Nov</span><span class="w"> </span><span class="m">26</span><span class="w"> </span><span class="m">16</span><span class="o">:</span><span class="m">34</span><span class="w"> </span><span class="n">.</span><span class="o">/</span><span class="w">
</span><span class="n">drwxr</span><span class="o">-</span><span class="n">xr</span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="m">161</span><span class="w"> </span><span class="n">user</span><span class="w"> </span><span class="n">domain</span><span class="w"> </span><span class="n">users</span><span class="w">  </span><span class="m">12288</span><span class="w"> </span><span class="n">Nov</span><span class="w"> </span><span class="m">23</span><span class="w"> </span><span class="m">10</span><span class="o">:</span><span class="m">13</span><span class="w"> </span><span class="n">..</span><span class="o">/</span><span class="w">
</span><span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="o">--</span><span class="n">r</span><span class="o">--</span><span class="w">   </span><span class="m">1</span><span class="w"> </span><span class="n">user</span><span class="w"> </span><span class="n">domain</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="m">578273</span><span class="w"> </span><span class="n">Nov</span><span class="w"> </span><span class="m">26</span><span class="w"> </span><span class="m">16</span><span class="o">:</span><span class="m">16</span><span class="w"> </span><span class="n">classes</span><span class="o">-</span><span class="n">header.jar</span><span class="w">
</span><span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="o">--</span><span class="n">r</span><span class="o">--</span><span class="w">   </span><span class="m">1</span><span class="w"> </span><span class="n">user</span><span class="w"> </span><span class="n">domain</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="m">782057</span><span class="w"> </span><span class="n">Nov</span><span class="w"> </span><span class="m">26</span><span class="w"> </span><span class="m">16</span><span class="o">:</span><span class="m">16</span><span class="w"> </span><span class="n">classes.jar</span><span class="w">   </span><span class="o">---------&gt;</span><span class="w"> </span><span class="n">该文件作为app导入的jar包</span><span class="w">
</span><span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="o">--</span><span class="n">r</span><span class="o">--</span><span class="w">   </span><span class="m">1</span><span class="w"> </span><span class="n">user</span><span class="w"> </span><span class="n">domain</span><span class="w"> </span><span class="n">users</span><span class="w">      </span><span class="m">0</span><span class="w"> </span><span class="n">Nov</span><span class="w"> </span><span class="m">12</span><span class="w"> </span><span class="m">11</span><span class="o">:</span><span class="m">24</span><span class="w"> </span><span class="n">exported</span><span class="o">-</span><span class="n">sdk</span><span class="o">-</span><span class="n">libs</span><span class="w">
</span><span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="o">--</span><span class="n">r</span><span class="o">--</span><span class="w">   </span><span class="m">1</span><span class="w"> </span><span class="n">user</span><span class="w"> </span><span class="n">domain</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="m">648154</span><span class="w"> </span><span class="n">Nov</span><span class="w"> </span><span class="m">26</span><span class="w"> </span><span class="m">16</span><span class="o">:</span><span class="m">34</span><span class="w"> </span><span class="n">javalib.jar</span><span class="w">
</span><span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="o">--</span><span class="n">r</span><span class="o">--</span><span class="w">   </span><span class="m">1</span><span class="w"> </span><span class="n">user</span><span class="w"> </span><span class="n">domain</span><span class="w"> </span><span class="n">users</span><span class="w">     </span><span class="m">14</span><span class="w"> </span><span class="n">Nov</span><span class="w"> </span><span class="m">12</span><span class="w"> </span><span class="m">11</span><span class="o">:</span><span class="m">24</span><span class="w"> </span><span class="n">link_type</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="119-updateengine升级模块学习博客">1.19. UpdateEngine升级模块学习博客</h2>

<p><strong>Upgrade OTA升级相关文档：</strong></p>

<p><strong>系列文章：</strong></p>
<ul>
  <li><a href="https://blog.csdn.net/guyongqiangx/article/details/71334889">系列-Android A/B System OTA分析和Update模块</a></li>
  <li><a href="https://blog.csdn.net/twk121109281/article/details/90715730">系列-Android OTA升级原理和流程分析</a></li>
  <li><a href="https://blog.csdn.net/u011913612/article/details/52503318">系列-android编译系统分析（五）system.img的生成过程</a></li>
  <li><a href="https://blog.csdn.net/liyuchong2537631/article/details/97516299">android P OTA 初探 —— 1、OTA简单介绍</a></li>
  <li><a href="https://blog.csdn.net/liyuchong2537631/article/details/97517850?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-13&amp;utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-13">android P OTA初探 —— 2、基于块（Block）的OTA：Target 包的制作流程</a></li>
  <li><a href="https://blog.csdn.net/liyuchong2537631/article/details/97528659?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3&amp;utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3">android P OTA初探 —— 3、基于块（Block）的OTA：升级包的制作流程</a></li>
</ul>

<p><strong>单篇文章：</strong></p>
<ul>
  <li><a href="https://source.android.google.cn/devices/tech/ota">Google原生OTA升级说明文档</a></li>
  <li><a href="https://blog.csdn.net/Android_2016/article/details/101554104">A/B 升级update_engine分析-整体模块</a></li>
  <li><a href="https://blog.csdn.net/mrcc_yy/article/details/51615540">Android recovery分析（一）—全量升级包的编译流程</a></li>
  <li><a href="https://blog.csdn.net/wzy_1988/article/details/47056035">Android Recovery OTA升级（一）—— make otapackage</a></li>
  <li><a href="https://blog.csdn.net/austindev/article/details/55213444">Android Recovery 源码解析和界面定制</a></li>
  <li><a href="https://www.cnblogs.com/MMLoveMeMM/articles/4086796.html">Android OTA 差分包升级</a></li>
  <li><a href="https://www.cnblogs.com/startkey/p/10553672.html">update_engine简介</a></li>
  <li><a href="https://www.csdn.net/tags/NtDagg5sMzE1ODAtYmxvZwO0O0OO0O0O.html">Android P update_engine分析（六）– PostinstallRunnerAction的工作</a></li>
  <li><a href="https://blog.csdn.net/Android_2016/article/details/102912469">A/B 升级update_engine分析-Action流程</a></li>
  <li><a href="https://blog.csdn.net/guyongqiangx/article/details/82805813">Android Update Engine分析（七） DownloadAction之FileWriter</a></li>
</ul>

<p><strong>独立文章：</strong></p>

<ul>
  <li><a href="https://zhuanlan.zhihu.com/p/86449761">汽车OTA介绍</a></li>
  <li><a href="https://zhuanlan.zhihu.com/p/63560871">什么是T-BOX？</a></li>
  <li><a href="https://zhuanlan.zhihu.com/p/69390321">android ota升级理论1</a></li>
</ul>

<hr />

<h2 id="120-update_engine升级包的hash值计算">1.20. update_engine升级包的hash值计算</h2>

<blockquote>
  <p>升级包解压后在payload_properties.txt文件可以看到payload.bin和metadata的文件大小和hash值</p>

  <p>该hash值是通过sha256sum计算后，再base64编码成字符串</p>

  <p>参考：<a href="https://blog.csdn.net/guyongqiangx/article/details/122393172">Android Update Engine分析（十） 生成 payload 和 metadata 的哈希</a></p>
</blockquote>

<p><strong>实际操作：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat</span> ../../../payload_properties.txt 
<span class="c">#payload.bin升级包数据</span>
<span class="nv">FILE_HASH</span><span class="o">=</span>Kw0egs8dY9Qm1sULkpkAPF+0PiKx14Uo0fHGZO3vNAk<span class="o">=</span>
<span class="nv">FILE_SIZE</span><span class="o">=</span>2515281637
<span class="c">#压缩后的 manifest 数据</span>
<span class="nv">METADATA_HASH</span><span class="o">=</span>z+5Qz8UIBrhCtMDgo+5IG4/rQqFUqmN/FMFyIWhaIEU<span class="o">=</span>
<span class="nv">METADATA_SIZE</span><span class="o">=</span>179557

<span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-l</span> payload.bin 
<span class="c">#文件大小对应</span>
<span class="nt">-rw-r--r--</span> 1 user domain <span class="nb">users </span>2515281637 Jan  1  2009 payload.bin
<span class="nv">$ </span><span class="nb">sha256sum </span>payload.bin | <span class="nb">awk</span> <span class="s1">'{print $1}'</span> | xxd <span class="nt">-r</span> <span class="nt">-ps</span> | <span class="nb">base64</span>
<span class="c">#计算后的文件hash值对应</span>
Kw0egs8dY9Qm1sULkpkAPF+0PiKx14Uo0fHGZO3vNAk<span class="o">=</span>

<span class="c">#计算后的metadata hash值对应</span>
<span class="nv">$ </span><span class="nb">dd </span><span class="k">if</span><span class="o">=</span>payload.bin <span class="nv">bs</span><span class="o">=</span>1 <span class="nv">count</span><span class="o">=</span>179557 2&gt;/dev/null | <span class="nb">sha256sum</span> | <span class="nb">awk</span> <span class="s1">'{print $1}'</span> | xxd <span class="nt">-r</span> <span class="nt">-ps</span> | <span class="nb">base64 
</span>z+5Qz8UIBrhCtMDgo+5IG4/rQqFUqmN/FMFyIWhaIEU<span class="o">=</span>
</code></pre></div></div>

<hr />

<h2 id="121-编译升级包出现报错lkrrpart-faileddevice-or-resource-busy">1.21. 编译升级包出现报错LKRRPART failed：Device or resource busy</h2>

<p><strong>输入以下命令：</strong></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>losetup <span class="nt">-d</span> /dev/loop0
<span class="nb">sudo </span>losetup <span class="nt">-d</span> /dev/loop1
<span class="nb">sudo </span>losetup <span class="nt">-d</span> /dev/loop2
<span class="nb">sudo </span>losetup <span class="nt">-d</span> /dev/loop3
</code></pre></div></div>

<hr />

<h2 id="122-原生升级系统的demog-apk">1.22. 原生升级系统的Demog APK</h2>

<p>adb shell am start com.android.car.systemupdater/.SystemUpdaterActivity</p>

<h2 id="123-原生carsystemui-apk">1.23. 原生carsystemUI apk</h2>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">/</span><span class="n">out</span><span class="o">/</span><span class="n">target</span><span class="o">/</span><span class="n">product</span><span class="o">/</span><span class="n">product</span><span class="o">/</span><span class="n">system</span><span class="o">$</span><span class="w"> </span><span class="n">find</span><span class="w"> </span><span class="n">.</span><span class="w"> </span><span class="o">-</span><span class="n">iname</span><span class="w"> </span><span class="s2">"*systemui*"</span><span class="w">
</span><span class="n">.</span><span class="o">/</span><span class="n">product</span><span class="o">/</span><span class="n">priv</span><span class="o">-</span><span class="n">app</span><span class="o">/</span><span class="n">CarSystemUI</span><span class="w">
</span><span class="n">.</span><span class="o">/</span><span class="n">product</span><span class="o">/</span><span class="n">priv</span><span class="o">-</span><span class="n">app</span><span class="o">/</span><span class="n">CarSystemUI</span><span class="o">/</span><span class="n">CarSystemUI.apk</span><span class="w">
</span><span class="n">.</span><span class="o">/</span><span class="n">product</span><span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">permissions</span><span class="o">/</span><span class="n">com.android.systemui.xml</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="124-update_engine升级信息获取">1.24. update_engine升级信息获取</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb root
adb pull /data/misc/update_engine_log
adb pull /data/misc/update_engine/
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell
/data/misc/update_engine/prefs <span class="c"># ls -thl</span>
total 38K
<span class="nt">-rw-------</span> 1 root root   1 2021-01-01 00:11 total-bytes-downloaded
<span class="nt">-rw-------</span> 1 root root  17 2021-01-01 00:11 system-updated-marker
<span class="nt">-rw-------</span> 1 root root   1 2021-01-01 00:11 delta-update-failures
<span class="nt">-rw-------</span> 1 root root  36 2021-01-01 00:11 update-completed-on-boot-id
<span class="nt">-rw-------</span> 1 root root   4 2021-01-01 00:11 post-install-succeeded
<span class="nt">-rw-------</span> 1 root root   4 2021-01-01 00:09 verity-written
<span class="nt">-rw-------</span> 1 root root   1 2021-01-01 00:08 update-state-next-data-length
<span class="nt">-rw-------</span> 1 root root   9 2021-01-01 00:08 update-state-next-data-offset
<span class="nt">-rw-------</span> 1 root root   4 2021-01-01 00:08 update-state-next-operation
<span class="nt">-rw-------</span> 1 root root 112 2021-01-01 00:08 update-state-sha-256-context
<span class="nt">-rw-------</span> 1 root root 264 2021-01-01 00:08 update-state-signature-blob
<span class="nt">-rw-------</span> 1 root root 112 2021-01-01 00:08 update-state-signed-sha-256-context
<span class="nt">-rw-------</span> 1 root root   6 2021-01-01 00:02 manifest-metadata-size
<span class="nt">-rw-------</span> 1 root root   3 2021-01-01 00:02 manifest-signature-size
<span class="nt">-rw-------</span> 1 root root   4 2021-01-01 00:02 dynamic-partition-metadata-updated
<span class="nt">-rw-------</span> 1 root root   1 2021-01-01 00:02 resumed-update-failures
<span class="nt">-rw-------</span> 1 root root  88 2021-01-01 00:02 update-check-response-hash
<span class="nt">-rw-------</span> 1 root root  36 1970-01-01 08:00 boot-id
<span class="nt">-rw-------</span> 1 root root  24 1970-01-01 08:00 previous-version
:/data/misc/update_engine/prefs <span class="c"># cat boot-id</span>
e2ecfeb4-d097-449c-8c7b-e8aee5067cc6

:/data/misc/update_engine/prefs <span class="c"># cat previous-version</span>
eng.user.20211201.031118

:/data/misc/update_engine/prefs <span class="c"># cat update-check-response-hash</span>
LBhFc9jN99sOu9/VKy7COfkBXtXzlDt5fHdx1kQyU3c<span class="o">=</span>vsNI/ZuKzDY0uOuXZIOjr42UHrzJ86isJSEtAwlFf9k<span class="o">=</span>
</code></pre></div></div>

<hr />

<h2 id="125-update_engine错误码errorcode">1.25. update_engine错误码errorcode</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//android/system/update_engine/common/error_code.h</span>
<span class="c1">// Action exit codes.</span>
<span class="k">enum</span> <span class="k">class</span> <span class="nc">ErrorCode</span> <span class="o">:</span> <span class="kt">int</span> <span class="p">{</span>
  <span class="n">kSuccess</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>   <span class="c1">//升级成功</span>
  <span class="n">kError</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>  <span class="c1">//升级失败</span>
  <span class="n">kOmahaRequestError</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="c1">//请求action错误（action机制用于控制升级每个步骤）</span>
  <span class="n">kOmahaResponseHandlerError</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>   <span class="c1">//返回handler action错误</span>
  <span class="n">kFilesystemCopierError</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>  <span class="c1">//文件系统拷贝错误</span>
  <span class="n">kPostinstallRunnerError</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>   <span class="c1">//预编译运行步骤错误（PostinstallRunner是一个升级步骤）</span>
  <span class="n">kPayloadMismatchedType</span> <span class="o">=</span> <span class="mi">6</span><span class="p">,</span>    <span class="c1">//NOT NEED</span>
  <span class="n">kInstallDeviceOpenError</span> <span class="o">=</span> <span class="mi">7</span><span class="p">,</span> <span class="c1">//安装设备打开错误</span>
  <span class="n">kKernelDeviceOpenError</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span> <span class="c1">//内核设备打开错误</span>
  <span class="n">kDownloadTransferError</span> <span class="o">=</span> <span class="mi">9</span><span class="p">,</span> <span class="c1">//下载传输错误</span>
  <span class="n">kPayloadHashMismatchError</span> <span class="o">=</span> <span class="mi">10</span><span class="p">,</span> <span class="c1">//升级包hash未匹配错误</span>
  <span class="n">kPayloadSizeMismatchError</span> <span class="o">=</span> <span class="mi">11</span><span class="p">,</span>   <span class="c1">//升级包size未匹配错误</span>
  <span class="n">kDownloadPayloadVerificationError</span> <span class="o">=</span> <span class="mi">12</span><span class="p">,</span> <span class="c1">//下载过程升级包校验错误</span>
  <span class="n">kDownloadNewPartitionInfoError</span> <span class="o">=</span> <span class="mi">13</span><span class="p">,</span> <span class="c1">//下载过程新分区信息错误</span>
  <span class="n">kDownloadWriteError</span> <span class="o">=</span> <span class="mi">14</span><span class="p">,</span>   <span class="c1">//下载过程数据写入错误</span>
  <span class="n">kNewRootfsVerificationError</span> <span class="o">=</span> <span class="mi">15</span><span class="p">,</span> <span class="c1">//升级分区hash校验失败</span>
  <span class="n">kNewKernelVerificationError</span> <span class="o">=</span> <span class="mi">16</span><span class="p">,</span> <span class="c1">//升级kernel校验失败</span>
  <span class="n">kSignedDeltaPayloadExpectedError</span> <span class="o">=</span> <span class="mi">17</span><span class="p">,</span>  <span class="c1">//NOT NEED</span>
  <span class="n">kDownloadPayloadPubKeyVerificationError</span> <span class="o">=</span> <span class="mi">18</span><span class="p">,</span> <span class="c1">//下载过程升级包public key公钥校验错误</span>
  <span class="n">kPostinstallBootedFromFirmwareB</span> <span class="o">=</span> <span class="mi">19</span><span class="p">,</span>   <span class="c1">//NOT NEED</span>
  <span class="n">kDownloadStateInitializationError</span> <span class="o">=</span> <span class="mi">20</span><span class="p">,</span> <span class="c1">//下载状态初始化错误</span>
  <span class="n">kDownloadInvalidMetadataMagicString</span> <span class="o">=</span> <span class="mi">21</span><span class="p">,</span> <span class="c1">//NOT NEED</span>
  <span class="n">kDownloadSignatureMissingInManifest</span> <span class="o">=</span> <span class="mi">22</span><span class="p">,</span>  <span class="c1">//下载过程manifest缺少签名错误</span>
  <span class="n">kDownloadManifestParseError</span> <span class="o">=</span> <span class="mi">23</span><span class="p">,</span> <span class="c1">//下载过程manifest分析错误</span>
  <span class="n">kDownloadMetadataSignatureError</span> <span class="o">=</span> <span class="mi">24</span><span class="p">,</span>   <span class="c1">//下载过程元数据签名错误</span>
  <span class="n">kDownloadMetadataSignatureVerificationError</span> <span class="o">=</span> <span class="mi">25</span><span class="p">,</span>   <span class="c1">//下载过程元数据签名校验错误</span>
  <span class="n">kDownloadMetadataSignatureMismatch</span> <span class="o">=</span> <span class="mi">26</span><span class="p">,</span>   <span class="c1">//下载过程元数据签名不匹配错误</span>
  <span class="n">kDownloadOperationHashVerificationError</span> <span class="o">=</span> <span class="mi">27</span><span class="p">,</span> <span class="c1">//下载过程操作hash校验错误</span>
  <span class="n">kDownloadOperationExecutionError</span> <span class="o">=</span> <span class="mi">28</span><span class="p">,</span>  <span class="c1">//下载过程操作执行错误</span>
  <span class="n">kDownloadOperationHashMismatch</span> <span class="o">=</span> <span class="mi">29</span><span class="p">,</span> <span class="c1">//下载过程操作hash不匹配错误</span>
  <span class="n">kOmahaRequestEmptyResponseError</span> <span class="o">=</span> <span class="mi">30</span><span class="p">,</span>   <span class="c1">//请求action无返回错误</span>
  <span class="n">kOmahaRequestXMLParseError</span> <span class="o">=</span> <span class="mi">31</span><span class="p">,</span>  <span class="c1">//请求action分析xml错误</span>
  <span class="n">kDownloadInvalidMetadataSize</span> <span class="o">=</span> <span class="mi">32</span><span class="p">,</span>   <span class="c1">//下载过程非法元数据大小</span>
  <span class="n">kDownloadInvalidMetadataSignature</span> <span class="o">=</span> <span class="mi">33</span><span class="p">,</span> <span class="c1">//下载过程非法元数据签名</span>
  <span class="n">kOmahaResponseInvalid</span> <span class="o">=</span> <span class="mi">34</span><span class="p">,</span> <span class="c1">//返回action非法错误</span>
  <span class="n">kOmahaUpdateIgnoredPerPolicy</span> <span class="o">=</span> <span class="mi">35</span><span class="p">,</span>   <span class="c1">//NOT NEED（含义是接收已回滚版本，忽略此次升级）</span>
  <span class="n">kOmahaUpdateDeferredPerPolicy</span> <span class="o">=</span> <span class="mi">36</span><span class="p">,</span>  <span class="c1">//NOT NEED（含义是因更新策略延迟，忽略此次升级）</span>
  <span class="n">kOmahaErrorInHTTPResponse</span> <span class="o">=</span> <span class="mi">37</span><span class="p">,</span>   <span class="c1">//HTTP返回错误</span>
  <span class="n">kDownloadOperationHashMissingError</span> <span class="o">=</span> <span class="mi">38</span><span class="p">,</span>   <span class="c1">//下载过程操作时缺失hash错误</span>
  <span class="n">kDownloadMetadataSignatureMissingError</span> <span class="o">=</span> <span class="mi">39</span><span class="p">,</span>  <span class="c1">//下载过程元数据签名缺失错误</span>
  <span class="n">kOmahaUpdateDeferredForBackoff</span> <span class="o">=</span> <span class="mi">40</span><span class="p">,</span>    <span class="c1">//NOT NEED（含义是忽略本次升级）</span>
  <span class="n">kPostinstallPowerwashError</span> <span class="o">=</span> <span class="mi">41</span><span class="p">,</span>  <span class="c1">//NOT NEED（回滚报错，该版本已去除版本回滚限制）</span>
  <span class="n">kUpdateCanceledByChannelChange</span> <span class="o">=</span> <span class="mi">42</span><span class="p">,</span> <span class="c1">//通道变化升级取消</span>
  <span class="n">kPostinstallFirmwareRONotUpdatable</span> <span class="o">=</span> <span class="mi">43</span><span class="p">,</span> <span class="c1">//NOT NEED(需要升级固件firmware时才会取消，因为无法从FW B分区启动到FW A分区)</span>
  <span class="n">kUnsupportedMajorPayloadVersion</span> <span class="o">=</span> <span class="mi">44</span><span class="p">,</span> <span class="c1">//获取manifest偏移量错误</span>
  <span class="n">kUnsupportedMinorPayloadVersion</span> <span class="o">=</span> <span class="mi">45</span><span class="p">,</span>   <span class="c1">//未manifest可支持更小版本错误</span>
  <span class="n">kOmahaRequestXMLHasEntityDecl</span> <span class="o">=</span> <span class="mi">46</span><span class="p">,</span> <span class="c1">//请求action xml hash非法错误</span>
  <span class="n">kFilesystemVerifierError</span> <span class="o">=</span> <span class="mi">47</span><span class="p">,</span> <span class="c1">//文件系统校验错误（FilesystemVerifier是一个升级步骤）</span>
  <span class="n">kUserCanceled</span> <span class="o">=</span> <span class="mi">48</span><span class="p">,</span> <span class="c1">//用户取消</span>
  <span class="n">kNonCriticalUpdateInOOBE</span> <span class="o">=</span> <span class="mi">49</span><span class="p">,</span> <span class="c1">//NOT NEED（Ignoring a non-critical Omaha update before OOBE completion.）</span>
  <span class="n">kOmahaUpdateIgnoredOverCellular</span> <span class="o">=</span> <span class="mi">50</span><span class="p">,</span>   <span class="c1">//NOT NEED（未设置设备策略，因此用户首选项需要覆盖是否允许通过蜂窝网络进行更新）</span>
  <span class="n">kPayloadTimestampError</span> <span class="o">=</span> <span class="mi">51</span><span class="p">,</span> <span class="c1">//升级包时间戳错误 （payload.bin是OTA镜像打包文件）</span>
  <span class="n">kUpdatedButNotActive</span> <span class="o">=</span> <span class="mi">52</span><span class="p">,</span>  <span class="c1">//升级分区非action状态错误</span>
  <span class="n">kNoUpdate</span> <span class="o">=</span> <span class="mi">53</span><span class="p">,</span> <span class="c1">//无升级（There are no updates. Aborting.）</span>
  <span class="n">kRollbackNotPossible</span> <span class="o">=</span> <span class="mi">54</span><span class="p">,</span>  <span class="c1">//NOT NEED</span>
  <span class="n">kFirstActiveOmahaPingSentPersistenceError</span> <span class="o">=</span> <span class="mi">55</span><span class="p">,</span>  <span class="c1">//NOT NEED（用于旧设备的Omaha校验）</span>
  <span class="n">kVerityCalculationError</span> <span class="o">=</span> <span class="mi">56</span><span class="p">,</span>  <span class="c1">//校验计算错误（在FilesystemVerifier步骤中进行分区校验时）</span>
</code></pre></div></div>

<hr />

<h2 id="126-android代码架构释义">1.26. Android代码架构释义</h2>

<blockquote>
  <p><a href="https://www.jianshu.com/p/d92ad2e4b529">Android系统源代码目录与系统目录</a></p>

  <p><a href="https://blog.csdn.net/zxc024000/article/details/110825669">Android源码及系统目录结构分析</a></p>

  <p><a href="https://blog.csdn.net/u012195899/article/details/82078384">Ubuntu16.04编译Android源码系列</a></p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- art/  <span class="c"># Android runtime运行环境, java虚拟机art源码</span>
- bionic/ <span class="c"># C库</span>
- bootable/	<span class="c"># 启动引导和恢复系统相关代码（bootloader和recovery）</span>
   - bootloader
      - dramk_2712/	
   	- lk/	 <span class="c"># liitle kernel，负责引导Linux的⼀个迷你⼩系统，启动顺序位于fastboot/uboot之后，Linux kernel之前，系统开机之后是进Android还是进recovery，即是这⾥负责</span>
	   - lk_ext_mod/
   - recovery <span class="c"># 负责升级、恢复出⼚等⼀些⽐较重要⼜不是很复杂的⼯作，一般属于升级update子模块</span>

- bootstrap.bash	
- build/	<span class="c"># 存放系统编译规则等基础开发包配置</span>
   - blueprint <span class="c"># .bp的全程，由Go语⾔编写，是⽣成、解析Android.bp的⼯具，是Soong的⼀部分。Soong则是专为Android编译⽽设计的⼯具，Blueprint只是解析⽂件的形式，⽽Soong则解释内容的含义</span>
   - kati <span class="c"># 基于Makefile来⽣成ninja.build的⼩项⽬。主要⽤于把Makefile转成成ninja file，⾃⾝没有编译能⼒，转换后使⽤Ninja编译</span>
   - make
   - soong <span class="c"># Soong（go语言写的项目）构建系统是在Android 7中引⼊的，旨在取代Make。它利⽤Kati GNU Make克隆⼯具和Ninja构建系统组件来加速 Android 的构建；Soong在编译时使⽤，解析Android.bp，将之转化为Ninja⽂件</span>

- compatibility/	<span class="c"># 兼容性计划文档目录，一些markdown文档</span>
- cts/	<span class="c"># cts测试用例源码，但是一般cts跑测是从官网下载官方更新的cts测试包，如果跑测出现case faile，可以根据log在这边阅读源码分析</span>
- dalvik/	<span class="c"># java dalvik虚拟机</span>
- developers/	<span class="c"># demo源码和开发者参考文档</span>
   - demo
   - samples
- development/	<span class="c"># 应用程序开发相关的实例/模板/工具</span>
   - apps  <span class="c"># 开发者选项等app</span>
   - tools

- device/	<span class="c"># 芯片厂商定制代码，包含AOSP自带的多家芯片厂以及芯片平台的代码，android O引入treble后，该目录下大部分关于厂商定制的源码移到vendor目录下</span>
   - mediatek_projects <span class="c"># 此目录存放芯片平台平台下，几种不同的变种芯片项目编译定制化内容</span>

- external/	<span class="c"># 开源第三方组件模块，存放着⼤量Google在创造、更新Android过程中，因实现某些功能⽽引⼊的开源第三⽅库，有些库是做了多系统适配（例如适配Android、Win、Linux），有些是only for Android</span>
   - aac/ <span class="c"># aac解码库，AAC是高级音频编码（Advanced Audio Coding）</span>
   - bsdiff/ <span class="c"># diff工具</span>

- frameworks/	<span class="c"># framework层，应用程序框架层</span>
   - av
      - camera <span class="c"># cameram模块</span>
      - cmds 
         - screenrecord/	<span class="c"># 录屏工具源码</span>
	      - stagefright/	   <span class="c"># 一个多媒体框架</span>
      - media <span class="c"># audio、media源码</span>
      - packages <span class="c"># 只有一个MediaComponents apk源码</span>
      - services <span class="c"># audioflinger、cameraservice、mediacodec等核心源码</span>
      - soundtrigger <span class="c"># audio模块的语音识别唤醒相关</span>
   - base
      - api <span class="c">#Android对外暴露的API整体存放位置，make update-api命令更新的就是这里的文件</span>
      - cmds <span class="c"># 命令/工具源码路径，例如am、pm、screencap等</span>
         - bootanimation/	<span class="c"># 开机动画</span>
      - core <span class="c"># android java和JNI核心库源码，例如framework.jar,hwbinder.jar,libandroid_runtime等</span>
         - jni <span class="c"># libandroid_runtime</span>
      - data <span class="c"># Android系统中字体、效果音等资源文件存放目录</span>
      - graphics <span class="c"># graphic基础补充库，属于framework.jar</span>
      - libs <span class="c"># display模块的hwui库位于这个目录下，以及其他的几个模块</span>
      - media <span class="c"># media部分的java实现和jni实现源码，java部分属于framework.jar</span>
      - opengl <span class="c"># opengl java api，隶属于framework.jar</span>
      - packages <span class="c"># aosp core app源码路径，有DocumentsUI、Keyguard、MtpDocumentsProvider、SettingsProvide、SystemUI、Wallpaper等</span>
      - services <span class="c"># service.jar源码，systemServer中启动的多数java service服务代码所在地</span>
      - telecomm/	<span class="c"># tele模块，属于framework.jar</span>
      - telephony/
      - wifi <span class="c"># wifi java api源码目录，属于framework.jar</span>
   - native
      - cmds <span class="c"># bugreport、dumpsys等Android debug工具的c实现源码，以及serviceManager</span>
      - libs <span class="c"># binder的Android中间件层实现源码，及libgui、libui、libsensor等中间件库源码</span>
      - opengl <span class="c"># OpenGL的c/c++实现源码</span>
      - services <span class="c"># 一些Android核心C进程的C/C++源码，例如surfaceFlinger、inputflinger、sensorservice等</span>

- gerrit_config.sh
- hardware/	 <span class="c"># 硬件适配层hal层</span>
   - interfaces/  <span class="c"># Android aosp定义的hidl interface实现，hal service实现源码</span>
   - libhardware
      - moudles
         - audio
         - camera
         - input
   - mediatek  <span class="c"># 芯片平台 HAL实现代码，上层对接Google HIDL interface</span>

- kernel/ <span class="c"># aosp的kernel适配目录</span>
- kernel-4.9/	<span class="c"># kernel层源码，Android P之前使用，Q和R是4.14</span>
- libcore/	<span class="c"># Java api核心库实现源码，dalvik、art部分java实现代码也在这里</span>
   - dalvik
- libnativehelper/	<span class="c"># native层helper，即JNJ相关cpp源码</span>
- Makefile	<span class="c"># 全局makefile</span>
- packages/	<span class="c"># 大部分android apk源码</span>
   - apps
      - Calendar <span class="c">#日历应用</span>
      - Camera2/ <span class="c"># 官方camera</span>
      - car
      - Email/
      - Gallery <span class="c"># 图库</span>
      - SoundRecorder   <span class="c"># 录音机</span>
   - providers <span class="c"># 存放着一堆provider的源码，media模块使用的MediaProvider，ContactsProvider等</span>
   - services  <span class="c"># java service源码位置</span>
      - car  <span class="c"># carservice源码</span>

- pdk/	<span class="c"># platform development kit，平台开发工具</span>
- platform_testing/	<span class="c"># 平台一些测试相关的源码</span>
- prebuilts/	<span class="c"># 预编译资源</span>
   - go <span class="c"># go语言环境，比如用于支持soong</span>
- sdk/	<span class="c"># sdk工具源码</span>
- system/	<span class="c"># 底层文件系统库、应用、组件，特殊功能模块</span>
   - bt <span class="c"># 蓝牙相关</span>
   - connectivity <span class="c"># wifi相关库</span>
   - core <span class="c"># Android C/C++部分核心组件源码，有相当一部分是common类的组件</span>
      - adb <span class="c"># adb进程源码</span>
      - fastboot  <span class="c"># fastboot命令工具源码</span>
      - healthd   <span class="c"># 电池服务守护进程源码</span>
      - init      <span class="c"># init进程实现源码</span>
      - libcutils <span class="c"># 工具库源码，例如property_set</span>
      - ibion <span class="c"># ion内存管理工具类源码</span>
      - liblog <span class="c"># ALOGX等相关函数实现源码</span>
      - libusbhost <span class="c"># usbhost实现库</span>
      - logcat <span class="c"># logcat实现源码</span>
      - logd <span class="c"># 负责输出log的logd守护进程源码</span>
      - sdcard <span class="c"># 外置存储卡挂载时会使用到的工具类</span>
   - hwservicemanager   <span class="c"># hwservicemanager的实现源码，负责管理hidl service的，类似serviceManager</span>
   - libhidl <span class="c"># hidl功能的c/c++层实现源码</span>
   - libhwbinder <span class="c"># hwbinder功能的实现源码</span>
   - sepolicy <span class="c"># aosp selinux配置文件所在目录</span>
   - vold <span class="c"># 用于磁盘管理的守护进程</span>

- <span class="nb">test</span>/	<span class="c"># vts测试源码</span>
   - vts
- toolchain/ <span class="c"># Android工具链</span>
- tools/	<span class="c"># android一些工具的源码，例如apk签名</span>
   - apksig
- vendor/	<span class="c"># 厂商定制模块（芯片厂商以及我司定制化功能的实现代码）</span>
   - mediatek  <span class="c"># </span>
      - external/	
	   - hardware/	
	   - kernel_modules/	
	   - packages/	<span class="c"># 芯片平台方案的私有app源码</span>
      - proprietary/
</code></pre></div></div>

<hr />

<h2 id="127-android编译三步命令解析">1.27. Android编译三步命令解析</h2>

<h3 id="1271-source-buildenvsetupsh">1.27.1. source build/envsetup.sh</h3>

<p>1.source执行device和vendor⽬录下⾯⼀堆vendorsetup.sh⽂件，该shell脚本实就是调⽤add_lunch_combo函数，注册了对应的product</p>

<p><strong>PS：</strong>如果我们需要⾃定义新增⼀个product，那么第⼀步，我们应该：在vendor⽬录或device⽬录下（这⾥推荐在vendor⽬录下），新增⼀个vendorsetup.sh⽂件，并且调⽤add_lunch_combo函数将我们期望增加的product name传递进去。</p>

<p>2.在执⾏完<code class="language-plaintext highlighter-rouge">source build/envsetup.sh</code>命令后，定义⼀堆shell到当前的⽤⼾环境中，此时可以输⼊<code class="language-plaintext highlighter-rouge">hmm</code>命令看到这些扩展shell命令，或者执行<code class="language-plaintext highlighter-rouge">m help</code></p>

<p>可以在<code class="language-plaintext highlighter-rouge">build/envsetup.sh</code>文件头看到以下的注释：</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center">命令</th>
      <th style="text-align: center">解释</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">lunch</td>
      <td style="text-align: center">lunch <product_name>-<build_variant>选择<product_name>作为要构建的产品，<build_variant>作为要构建的变体，并将这些选择存储在环境中，以便后续调⽤“m”等读取</build_variant></product_name></build_variant></product_name></td>
    </tr>
    <tr>
      <td style="text-align: center">tapas</td>
      <td style="text-align: center">交互⽅式：tapas [..] (arm/x86/mips/arm64/x86_64/mips64)(eng/userdebug/user)</td>
    </tr>
    <tr>
      <td style="text-align: center">croot</td>
      <td style="text-align: center">将⽬录更改到树的顶部或其⼦⽬录</td>
    </tr>
    <tr>
      <td style="text-align: center">m</td>
      <td style="text-align: center">编译整个源码，可以不⽤切换到根⽬录</td>
    </tr>
    <tr>
      <td style="text-align: center">mm</td>
      <td style="text-align: center">编译当前⽬录下的源码，不包含他们的依赖模块</td>
    </tr>
    <tr>
      <td style="text-align: center">mma</td>
      <td style="text-align: center">编译当前⽬录下的源码，包含他们的依赖模块</td>
    </tr>
    <tr>
      <td style="text-align: center">mmm</td>
      <td style="text-align: center">编译指定⽬录下的所有模块，不包含他们的依赖模块 例如：mmm dir/:target1,target2</td>
    </tr>
    <tr>
      <td style="text-align: center">mmma</td>
      <td style="text-align: center">编译指定⽬录下的所模块，包含他们的依赖模块</td>
    </tr>
    <tr>
      <td style="text-align: center">provision</td>
      <td style="text-align: center">具有所有必需分区的闪存设备。选项将传递给fastboot</td>
    </tr>
    <tr>
      <td style="text-align: center">cgrep</td>
      <td style="text-align: center">对系统本地所有的C/C++ ⽂件执⾏grep命令</td>
    </tr>
    <tr>
      <td style="text-align: center">ggrep</td>
      <td style="text-align: center">对系统本地所有的Gradle⽂件执⾏grep命令</td>
    </tr>
    <tr>
      <td style="text-align: center">jgrep</td>
      <td style="text-align: center">对系统本地所有的Java⽂件执⾏grep命令</td>
    </tr>
    <tr>
      <td style="text-align: center">resgrep</td>
      <td style="text-align: center">对系统本地所有的res⽬录下的xml⽂件执⾏grep命令</td>
    </tr>
    <tr>
      <td style="text-align: center">mangrep</td>
      <td style="text-align: center">对系统本地所有的AndroidManifest.xml⽂件执⾏grep命令</td>
    </tr>
    <tr>
      <td style="text-align: center">mgrep</td>
      <td style="text-align: center">对系统本地所有的Makefiles⽂件执⾏grep命令</td>
    </tr>
    <tr>
      <td style="text-align: center">sepgrep</td>
      <td style="text-align: center">对系统本地所有的sepolicy⽂件执⾏grep命令</td>
    </tr>
    <tr>
      <td style="text-align: center">sgrep</td>
      <td style="text-align: center">对系统本地所有的source⽂件执⾏grep命令</td>
    </tr>
    <tr>
      <td style="text-align: center">godir</td>
      <td style="text-align: center">根据godir后的参数⽂件名在整个⽬录下查找，并且切换⽬录</td>
    </tr>
  </tbody>
</table>

<hr />

<h4 id="12711-useruserdebugeng区别">1.27.1.1. user/userdebug/eng区别</h4>

<p><code class="language-plaintext highlighter-rouge">user/userdebug/eng</code>标签指的是Android.mk编译脚本⾥对应的属性<code class="language-plaintext highlighter-rouge">LOCAL_MODULE_TAGS</code>，LOCAL_MODULE_TAGS的取值有<code class="language-plaintext highlighter-rouge">user、debug、eng、tests、optional</code>五个。</p>

<p>optional的意思是该模块不主动参与编译，需要开发者⾃⾏在对应的mk⽂件中include这个模块，就是在device.mk中的<code class="language-plaintext highlighter-rouge">PRODUCT_PACKAGES</code>变量中引⼊该模块</p>

<ul>
  <li>user版</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. root权限关闭，默认情况下⽆法adb root，⽆法adb remount，发货版本均为user版；
2. 仅标签为user的模块会参与编译；
3. app及jar包，及java源码的代码混淆被开启（除⾮显式指定关闭的模块会关闭）；
4. 不包含D级别log输出
</code></pre></div></div>

<ul>
  <li>userdebug版</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. root权限开启，可remount，开发常⽤版本；
2. 标签为user、userdebug的模块会参与编译
</code></pre></div></div>
<ul>
  <li>eng版</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. root权限开启；
2. 标签为user、userdebug、eng的模块参与编译
</code></pre></div></div>

<h4 id="12712-adb-remount">1.27.1.2. adb remount</h4>

<p><code class="language-plaintext highlighter-rouge">adb remount</code>将<code class="language-plaintext highlighter-rouge">/system</code>部分置于可写入的模式，默认情况下<code class="language-plaintext highlighter-rouge">/system</code>部分是只读模式的。这个命令只适用于已被root的设备。</p>

<p>在将文件push到<code class="language-plaintext highlighter-rouge">/system</code>文件夹之前，必须先输入命令<code class="language-plaintext highlighter-rouge">adb remount</code>。</p>

<p><code class="language-plaintext highlighter-rouge">adb remount</code>的作用相当于<code class="language-plaintext highlighter-rouge">adb shell mount -o rw,remount,rw /system</code></p>

<p><code class="language-plaintext highlighter-rouge">adb remount</code>重新挂载system分区，实现对system分区重新挂载，重新挂载的时候将修改分区的属性，常见的修改参数为分区的读写。</p>

<p>使用该命令主要是因为android系统的system分区在启动之后是只读分区，但在开发过程中需要对system分区进行修改，则需重新挂载成读写模式</p>

<hr />

<h2 id="128-内存泄露">1.28. 内存泄露</h2>

<blockquote>
  <p>参考：<a href="https://zhuanlan.zhihu.com/p/358838529">内存泄漏问题初探</a></p>

  <p>参考：<a href="https://blog.csdn.net/yun_hen/article/details/116604034">性能问题分析方法（1） — RAM</a></p>

  <p>参考：<a href="https://sunwengang.top/Blog/2021/06/07/2021/210607_android_debug3/">Android Display/Graphics调试技巧（六月份更新）</a></p>
</blockquote>

<p>1.物理内存泄漏</p>

<p>一般物理内存泄漏会导致<code class="language-plaintext highlighter-rouge">kernel oom-killer</code> 杀进程内存</p>

<p>日志中会打印具体哪个进程<code class="language-plaintext highlighter-rouge">invoked oom-killer</code>，以及申请内存的堆栈，可以据此分析。</p>

<p>2.native 进程内存泄漏</p>

<p>从<code class="language-plaintext highlighter-rouge">event log</code>中可以看到进程的内存使用信息。</p>

<p>一般通过<code class="language-plaintext highlighter-rouge">am_pss</code>中能看个各个进程内存使用情况，如果某个native进程的am_pss中uss过高，大概率是它引起内存泄漏</p>

<pre><code class="language-log">am_pss: Pid, UID, ProcessName, Pss, Uss
am_pss (Pid|1|5),(UID|1|5),(Process Name|3),(Pss|2|2),(Uss|2|2),(SwapPss|2|2),(Rss|2|2),(StatType|1|5),(ProcState|1|5),(TimeToCollect|2|2)

am_meminfo: Cached,Free,Zram,Kernel,Native
am_low_memory: NumProcesses

//内存含义
vss (virtual set size) 虚拟内存，从进程地址空间统计，包括未实际申请到物理内存的部分。
rss (resident set size) 实际物理内存+共享库。共享库如果映射多个进程，不均摊。
pss ( proportional set size) 物理内存+均摊的共享内存。
uss (unique set size) 独占物理内存，不包含共享库部分（进程所占用的私有内存（内存泄漏的最佳观察数据）不包含so库占用内存的）

//例如
Line 82266: 01-01 08:24:27.599  2697  2798 I am_pss  : [3541,1000,vendor.projection.androidauto,51497984,47623168,0,133905408,0,5,7]
</code></pre>

<p>3.虚拟地址内存泄漏</p>

<p><code class="language-plaintext highlighter-rouge">vss oom</code>，多发生在32位应用。虚拟地址空间不足，无法申请到 vma，所以申请内存失败。
一般只有发生泄漏的应用会崩溃，物理内存情况可能使用并不多，虚拟内存可能接近 4G(32位)。
一般需要 smaps/maps 信息做进一步分析，确认哪种类型的 vma 占比较多，那么大概率是它泄漏（比如 libc, egl，ion，等等）</p>

<p>4.ion内存泄漏</p>

<p>ion是android中特有的内存分配器，一般在camera，图像中使用较多。</p>

<p>通过 ion 的 ioctl 接口，可以申请不同类型的内存，可以为指定进程预留，比如 物理地址空间连续（cma），为相机预留。</p>

<p>ion 申请的内存需要主动释放，不释放会存在泄漏。每一个 heap 中申请的都对应一个 ion_buffer，我们可以统计 ion buffer 中某个进程占比多少（高通支持）。</p>

<p>如果 ion 的总量特别大（比如 4/8G），那么大概率是 ion 泄漏。再通过每个进程的信息，确定到是哪个进程申请导致的泄漏。</p>

<hr />

<h3 id="1281-查看进程占用内存情况">1.28.1. 查看进程占用内存情况</h3>

<p><code class="language-plaintext highlighter-rouge">adb shell procrank</code></p>

<h3 id="1282-dump信息">1.28.2. dump信息</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">adb shell dumpsys cpuinfo &gt; cpuinfo.txt</code></li>
  <li><code class="language-plaintext highlighter-rouge">adb shell dumpsys meminfo &gt; meminfo.txt</code></li>
  <li><code class="language-plaintext highlighter-rouge">top -d 3</code></li>
</ul>

<p><strong>结果显示：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Tasks: 227 total,   1 running, 226 sleeping,   0 stopped,   0 zombie
  Mem:      2.8G total,      2.1G used,      733M free,      4.0M buffers
 Swap:      1.4G total,       38M used,      1.3G free,      323M cached
400%cpu  42%user   0%nice 111%sys 233%idle   0%iow   8%irq   6%sirq   0%host
  PID USER         PR  NI VIRT  RES  SHR S[%CPU] %MEM     TIME+ ARGS
 4279 shell        20   0  35M 9.0M 2.4M R 55.5   0.3   0:00.15 top <span class="nt">-d</span> 3
 2316 system       <span class="nt">-3</span> <span class="nt">-20</span> 165M  16M 5.0M S 27.7   0.5  17:25.76 android.hardware.graphics.composer@2.3-service
 2314 system       <span class="nt">-2</span>  <span class="nt">-8</span> 340M  30M  15M S 25.0   1.0  16:40.84 surfaceflinger
</code></pre></div></div>

<hr />

<h2 id="129-查看进程优先级">1.29. 查看进程优先级</h2>

<p>1.<code class="language-plaintext highlighter-rouge">ps -A|grep 进程名</code>
2.查看进程优先级:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell
<span class="c"># 如果该进程其值为0。0表示该进程是正在前台运行</span>
<span class="nb">cat </span>proc/917/oom_adj
</code></pre></div></div>

<hr />

<h2 id="130-查看系统服务">1.30. 查看系统服务</h2>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">$</span><span class="w"> </span><span class="n">service</span><span class="w"> </span><span class="n">list</span><span class="o">|</span><span class="n">grep</span><span class="w"> </span><span class="n">Update</span><span class="w">
</span><span class="m">60</span><span class="w">      </span><span class="n">updatelock</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="n">android.os.IUpdateLock</span><span class="p">]</span><span class="w">
</span><span class="m">61</span><span class="w">      </span><span class="n">system_update</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="n">android.os.ISystemUpdateManager</span><span class="p">]</span><span class="w">
</span><span class="m">105</span><span class="w">     </span><span class="n">webviewupdate</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="n">android.webkit.IWebViewUpdateService</span><span class="p">]</span><span class="w">
</span><span class="m">147</span><span class="w">     </span><span class="n">android.os.UpdateEngineService</span><span class="o">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="131-解析crash地址">1.31. 解析crash地址</h2>

<p>在对应包的symbols下查找库文件，使用addr2line命令：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>symbols/vendor<span class="nv">$ </span>addr2line <span class="nt">-f</span> <span class="nt">-e</span> bin/test_service 000000000000ac40
_ZN12UpdateSocket9handleMsgEPv
vendor/..../test.cpp:87
</code></pre></div></div>

<hr />

<h2 id="132-编码过程中单词常用的缩写方式转载">1.32. 编码过程中单词常用的缩写方式（转载）</h2>

<ul>
  <li>参考：<a href="https://blog.csdn.net/weixin_41178745/article/details/109765561">编码过程中单词常用的缩写方式（转载）</a></li>
</ul>

<hr />

<h2 id="133-清除应用数据及查看版本">1.33. 清除应用数据及查看版本</h2>

<ul>
  <li>清除应用数据:<code class="language-plaintext highlighter-rouge">adb shell pm clear com.test.upgrade</code></li>
  <li>查看应用版本号：<code class="language-plaintext highlighter-rouge">dumpsys package com.test.upgrade |grep version</code></li>
</ul>

<hr />

<h2 id="134-android110r-添加新分区">1.34. Android11.0(R) 添加新分区</h2>

<blockquote>
  <p>参考：<a href="https://cpu52.com/archives/373.html">Android11.0(R) 添加新分区</a></p>
</blockquote>

<h2 id="135-系统编译中local_cflags宏用法">1.35. 系统编译中LOCAL_CFLAGS宏用法</h2>

<blockquote>
  <p>参考：<a href="https://blog.csdn.net/lqktz/article/details/84959151">Android.bp 添加宏开关</a></p>

  <p>参考源码：<code class="language-plaintext highlighter-rouge">hardware/interfaces/configstore/1.1/default/surfaceflinger.mk</code></p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">LOCAL_CFLAGS += -DXXX</code>:相当于在所有源文件中增加一个宏定义<code class="language-plaintext highlighter-rouge">#define XXX</code></p>

<p><strong>例如：</strong></p>

<p>在Android.mk中增加：</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ifeq <span class="o">(</span><span class="si">$(</span>PRODUCT_MODEL<span class="si">)</span>,XXX_A<span class="o">)</span>
LOCAL_CFLAGS +<span class="o">=</span> <span class="nt">-DBUILD_MODEL</span>
endif
</code></pre></div></div>

<p>即能在所编译的cpp文件中使用：</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#ifdef BUILD_MODEL</span>
....
<span class="c">#endif</span>
</code></pre></div></div>

<hr />

<h2 id="136-android只编译system或者vendor">1.36. android只编译system或者vendor</h2>

<ul>
  <li>编译system：<code class="language-plaintext highlighter-rouge">make snod</code></li>
  <li>编译vendor：<code class="language-plaintext highlighter-rouge">make vendorimage</code></li>
</ul>

<hr />

<h2 id="137-repo切换分支">1.37. repo切换分支</h2>

<p><code class="language-plaintext highlighter-rouge">repo forall -c git checkout Branch</code>:切换分支</p>

<hr />

<h2 id="138-android-编译不生成odex文件编译时不优化">1.38. Android 编译不生成odex文件（编译时不优化）</h2>

<ul>
  <li>参考：<a href="https://www.showdoc.com.cn/2701?page_id=735330408300734">ODEX优化和配置</a></li>
  <li><a href="https://blog.csdn.net/FeiPeng_/article/details/122061946">Android 编译不生成odex文件（编译时不优化）</a></li>
</ul>

<p>在Android.mk配置：
<code class="language-plaintext highlighter-rouge">LOCAL_DEX_PREOPT := false</code></p>

<hr />

<h2 id="139-emmc分区检查">1.39. emmc分区检查</h2>

<ul>
  <li><a href="https://www.csdn.net/tags/NtjaAgxsNTk5OTItYmxvZwO0O0OO0O0O.html">Linux e2fsck修复下受损的硬盘文件命令详解</a></li>
  <li><a href="https://blog.csdn.net/yexiangCSDN/article/details/83181885">Linux 磁盘维护 : e2fsck 命令详解</a></li>
</ul>

<h3 id="1391-使用e2fsck命令">1.39.1. 使用e2fsck命令</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>8|console:/dev/block/by-name <span class="c"># e2fsck -d vendor_a</span>
e2fsck 1.44.4 <span class="o">(</span>18-Aug-2018<span class="o">)</span>
vendor: clean, 748/32768 files, 120659/131072 blocks
console:/dev/block/by-name <span class="c">#</span>

console:/dev/block/by-name <span class="c"># e2fsck -a vendor_a</span>
vendor: clean, 748/32768 files, 120659/131072 blocks
console:/dev/block/by-name <span class="c">#</span>

console:/dev/block/by-name <span class="c"># e2fsck -c vendor_a</span>
e2fsck 1.44.4 <span class="o">(</span>18-Aug-2018<span class="o">)</span>
sh: badblocks: inaccessible or not found
vendor: Updating bad block inode.
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information

vendor: <span class="k">*****</span> FILE SYSTEM WAS MODIFIED <span class="k">*****</span>
vendor: 748/32768 files <span class="o">(</span>0.9% non-contiguous<span class="o">)</span>, 120659/131072 blocks
</code></pre></div></div>

<hr />

<h3 id="1392-使用android自带工具badblocks">1.39.2. 使用Android自带工具badblocks</h3>

<ul>
  <li><a href="https://blog.csdn.net/kris_fei/article/details/77316961?utm_source=app&amp;app_version=5.3.1">调试笔记 — eMMC坏块测试</a></li>
  <li><a href="https://blog.csdn.net/Ian22l/article/details/117085760?utm_source=app&amp;app_version=5.3.1">Android性能分析之emmc坏块测试</a></li>
</ul>

<blockquote>
  <p>android支持emmc坏块测试工具badblocks</p>
</blockquote>

<p><strong>代码路径:</strong><code class="language-plaintext highlighter-rouge">./external/e2fsprogs/misc/badblocks.c</code></p>

<p>默认不编译，需配置：<code class="language-plaintext highlighter-rouge">PRODUCT_PACKAGES += badblocks</code></p>

<p>当然你也可以为了开始验证可以直接使用如下命令进行单独编译，前提是你之前已经全部编译通过了。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source</span> ./build/envsetup.sh
lunch platform_name        <span class="c">###(platform_name对应的sdk）</span>
mmm external/e2fsprogs/misc/
</code></pre></div></div>

<p>然后将其push到<code class="language-plaintext highlighter-rouge">system/bin</code>目录下</p>

<p><strong>使用方法：</strong></p>
<ul>
  <li><a href="http://t.zoukankan.com/52why-p-12363039.html">badblocks坏道检测</a></li>
</ul>

<p>1.对硬盘进行只读扫描，自动获取硬盘块数目并扫描全部块，将扫描日志输出到屏幕同时记录在sdbbadblocks.log文件中。</p>

<p>由于扫描速度比较低，一次不一定能扫完，可以分多次进行。</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">sudo badblocks -s -v -o sdbbadblocks.log /dev/sdb END START</code></li>
  <li><code class="language-plaintext highlighter-rouge">badblocks -sv /dev/block/mmcblk0p10</code></li>
</ul>

<p>2.如果找到了坏道，可以进行写入扫描进行修复。写入扫描遇到坏道的时候会自动重映射。写入扫描会覆盖原有数据，所以请先备份。写入扫描速度很低，所以应该只扫描只读扫描时候发现错误的部分。</p>

<p><code class="language-plaintext highlighter-rouge">sudo badblocks -w -s /dev/sdb END START</code></p>

<hr />

<hr />

<h2 id="140-android-property属性">1.40. Android property属性</h2>

<ul>
  <li>参考：<a href="https://blog.csdn.net/u014674293/article/details/120670723">Android 添加系统属性</a></li>
</ul>

<p>在 Android 系统中有一个Property Service服务，这个服务对外提供了两个接口：</p>

<ul>
  <li>SystemProperties.get(String key, String def) 读取系统属性</li>
  <li>SystemProperties.set(String key, String val) 设置系统属性</li>
</ul>

<p><strong>特殊前缀属性含义：</strong></p>
<ul>
  <li>ro：只读属性，不能修改。</li>
  <li>persist：修改属性后，重启依然有效。数据会保存到<code class="language-plaintext highlighter-rouge">/data/property</code>目录。其他前缀的属性被设置后，只是保存在内存中而已，并没有保存到磁盘，所有重启后就恢复默认值了</li>
  <li>ctrl：用来启动和停止服务。每一项服务必须在<code class="language-plaintext highlighter-rouge">init.rc</code>中定义。init一旦收到设置<code class="language-plaintext highlighter-rouge">ctrl.start</code>属性的请求，属性服务将使用该属性值作为服务名找到该服务，启动该服务。这项服务的启动结果将会放入<code class="language-plaintext highlighter-rouge">init.svc.&lt;服务名&gt;</code>属性中</li>
</ul>

<hr />

<h3 id="1401-property的相关生成文件和设置">1.40.1. property的相关生成文件和设置</h3>

<blockquote>
  <p>android通过SystemProperties的set和get方法来控制很多东西，一般上层添加一个控制开关可以使用这个方法，在系统里面存在很多个prop文件。它们分别是<code class="language-plaintext highlighter-rouge">system/build.prop</code>,<code class="language-plaintext highlighter-rouge">system/etc/prop.default</code>,<code class="language-plaintext highlighter-rouge">vendor/build.prop</code>,<code class="language-plaintext highlighter-rouge">vendor/default.prop</code>。下面分别来说下这几个文件的构成。</p>
</blockquote>

<ul>
  <li>system/build.prop：这个主要是由device***(platform)sample\product/system.prop,还有在build目录下添加的ADDITIONAL_BUILD_PROPERTIES</li>
  <li>system/etc/prop.default：主要是系统添加的PRODUCT_SYSTEM_DEFAULT_PROPERTIES</li>
  <li>vendor/build.prop（比较重要）：主要是系统添加的PRODUCT_PROPERTY_OVERRIDES，添加在device.mk的这个属性会被编译到这里，但是在9.0的系统，加到这里会无效，获取不到值</li>
  <li>vendor/default.prop（会被同目录的build.prop相同property覆盖）：主要是系统添加的PRODUCT_DEFAULT_PROPERTY_OVERRIDES</li>
</ul>

<hr />

<h2 id="141-anr日志提取">1.41. ANR日志提取</h2>

<p>xxx_app_anr这个⽂件，⼀般记录的进程堆栈信息较之traces⽂件少⼀些。</p>

<p>所以，如果有traces，那我们就基于该⽂件分析定位bug。</p>

<ul>
  <li>traces⽂件位于<code class="language-plaintext highlighter-rouge">/data/anr/</code>⽬录下，默认情况下，每发⽣⼀次anr，都会⽣成traces.txt⽂件，但是后发⽣的会将之前的覆盖掉</li>
  <li>xxx_app_anr⽂件位于<code class="language-plaintext highlighter-rouge">/data/system/dropbox/</code>⽬录下，按不同压缩包存放。由Dropbox生成</li>
</ul>

<p>出现ANR可以使⽤<code class="language-plaintext highlighter-rouge">adb pull</code>命令将⻋机⾥以上两个⽬录提取出来</p>

<hr />

<h2 id="142-soc串口禁止恢复打印内核日志">1.42. SOC串口禁止/恢复打印内核日志</h2>

<p>通过调整printk的打印级别：</p>

<ul>
  <li>禁止打印：dmesg -n1</li>
  <li>
    <p>恢复打印：dmesg -n8</p>
  </li>
  <li>恢复禁止: echo 0 &gt; /proc/sys/kernel/printk</li>
  <li>恢复打印: echo 7 &gt; /proc/sys/kernel/printk</li>
</ul>

<hr />

<h2 id="143-dump分区镜像内容dd命令">1.43. dump分区镜像内容（dd命令）</h2>

<ul>
  <li>参考：<a href="https://blog.csdn.net/coraline1991/article/details/120202643">shell dd命令在bs参数太大的时候出现异常的解决方法</a></li>
</ul>

<p>通过使用dd命令dump出来实际的分区内容，可以用来同原始image比较</p>

<p><code class="language-plaintext highlighter-rouge">dd if=/dev/block/by-name/mmcblkp*** of=/sdacar/a.img bs=512</code></p>

<p><strong>dump出来的是raw文件，如果像system/vendor等都是sparse文件，需要先使用<code class="language-plaintext highlighter-rouge">simg2img</code>转换成raw文件（Android编译环境直接使用）</strong></p>

<p>块大小bs=2x80x18b表示2 × 80 × 18 × 512 = 1474560字节，也就是一张1440 KiB软盘的确切大小</p>

<p><strong>另一种方式：</strong>
在当前目录生成一个大小为1G的大文件，内容是0</p>

<p><code class="language-plaintext highlighter-rouge">dd if=/dev/zero of=/test count=2 bs=512M</code></p>

<p><code class="language-plaintext highlighter-rouge">dd if=/dev/zero of=/test count=1 bs=1G</code></p>

<p><strong>注解：</strong></p>
<ul>
  <li>if 输入文件</li>
  <li>of 输出文件</li>
  <li>bs 字节为单位的块大小</li>
  <li>count 被复制的块数</li>
  <li>/dev/zero 是一个字符设备，不断的返回0值字节</li>
</ul>

<p>PS：simg2img是一个能把Android sparse 镜像转为row 镜像的工具，进而可以挂载解压</p>

<h3 id="1431-将vendor_b分区写入到vendor_a分区">1.43.1. 将vendor_b分区写入到vendor_a分区</h3>

<p><code class="language-plaintext highlighter-rouge">console:/dev/block/by-name # dd if=vendor_b of=vendor_a</code></p>

<h3 id="1432-simg2img">1.43.2. simg2img</h3>

<p>对比img文件类型：</p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">//</span><span class="n">车机dd出来的img</span><span class="w">
</span><span class="n">Debug_OTA_0401_img</span><span class="o">$</span><span class="w"> </span><span class="n">file</span><span class="w"> </span><span class="n">system_old.img</span><span class="w"> 
</span><span class="n">system_old.img</span><span class="o">:</span><span class="w"> </span><span class="n">Linux</span><span class="w"> </span><span class="n">rev</span><span class="w"> </span><span class="m">1.0</span><span class="w"> </span><span class="n">ext4</span><span class="w"> </span><span class="n">filesystem</span><span class="w"> </span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="n">UUID</span><span class="o">=</span><span class="m">4729639</span><span class="n">d</span><span class="o">-</span><span class="n">b5f2</span><span class="m">-5</span><span class="n">cc1</span><span class="o">-</span><span class="n">a120</span><span class="m">-9</span><span class="n">ac5f788683c</span><span class="w"> </span><span class="p">(</span><span class="n">extents</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">large</span><span class="w"> </span><span class="n">files</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">huge</span><span class="w"> </span><span class="n">files</span><span class="p">)</span><span class="w">

</span><span class="o">//</span><span class="n">系统编译的img</span><span class="w">
</span><span class="o">$</span><span class="w"> </span><span class="n">file</span><span class="w"> </span><span class="n">system.img</span><span class="w"> 
</span><span class="n">system.img</span><span class="o">:</span><span class="w"> </span><span class="n">Android</span><span class="w"> </span><span class="n">sparse</span><span class="w"> </span><span class="n">image</span><span class="p">,</span><span class="w"> </span><span class="n">version</span><span class="o">:</span><span class="w"> </span><span class="m">1.0</span><span class="p">,</span><span class="w"> </span><span class="n">Total</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="m">1310720</span><span class="w"> </span><span class="m">4096</span><span class="o">-</span><span class="n">byte</span><span class="w"> </span><span class="n">output</span><span class="w"> </span><span class="n">blocks</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">86</span><span class="w"> </span><span class="n">input</span><span class="w"> </span><span class="n">chunks.</span><span class="w">
</span></code></pre></div></div>

<h4 id="14321-解压打开image文件">1.43.2.1. 解压打开image文件</h4>

<p>采用挂载分区的方式来打开system.img文件</p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Debug_OTA_0401_img</span><span class="o">$</span><span class="w"> </span><span class="n">file</span><span class="w"> </span><span class="n">system_old.img</span><span class="w"> 
</span><span class="n">system_old.img</span><span class="o">:</span><span class="w"> </span><span class="n">Linux</span><span class="w"> </span><span class="n">rev</span><span class="w"> </span><span class="m">1.0</span><span class="w"> </span><span class="n">ext4</span><span class="w"> </span><span class="n">filesystem</span><span class="w"> </span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="n">UUID</span><span class="o">=</span><span class="m">4729639</span><span class="n">d</span><span class="o">-</span><span class="n">b5f2</span><span class="m">-5</span><span class="n">cc1</span><span class="o">-</span><span class="n">a120</span><span class="m">-9</span><span class="n">ac5f788683c</span><span class="w"> </span><span class="p">(</span><span class="n">needs</span><span class="w"> </span><span class="n">journal</span><span class="w"> </span><span class="n">recovery</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">extents</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">large</span><span class="w"> </span><span class="n">files</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">huge</span><span class="w"> </span><span class="n">files</span><span class="p">)</span><span class="w">
</span><span class="n">Debug_OTA_0401_img</span><span class="o">$</span><span class="w"> </span><span class="n">mkdir</span><span class="w"> </span><span class="m">1</span><span class="w">
</span><span class="n">Debug_OTA_0401_img</span><span class="o">$</span><span class="w"> </span><span class="n">sudo</span><span class="w"> </span><span class="n">mount</span><span class="w"> </span><span class="n">system_old.img</span><span class="w"> </span><span class="m">1</span><span class="o">/</span><span class="w"> </span><span class="o">-</span><span class="n">o</span><span class="w"> </span><span class="n">loop</span><span class="w">
</span></code></pre></div></div>

<h4 id="14322-tips使用方法">1.43.2.2. Tips使用方法</h4>

<p><strong>android本身提供了源代码工具在两者之间转换，源代码位于：</strong></p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">libsparse</span><span class="o">/</span><span class="n">simg2img.c</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">将sparse</span><span class="w"> </span><span class="n">image转换为raw</span><span class="w"> </span><span class="n">image</span><span class="p">;</span><span class="w">
</span><span class="n">system</span><span class="o">/</span><span class="n">core</span><span class="o">/</span><span class="n">libsparse</span><span class="o">/</span><span class="n">img2simg.c</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">将raw</span><span class="w"> </span><span class="n">image转换为sparse</span><span class="w"> </span><span class="n">image</span><span class="p">;</span><span class="w">
</span></code></pre></div></div>

<p>如果完整的进行过一次Android的编译，默认会将simg2img当作主机工具编译出来，放在<code class="language-plaintext highlighter-rouge">out/host/linux-x86/bin/simg2img</code>处</p>

<p>但默认是不会编译img2simg的，我们可以手工进行编译：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>.build/envsetup.sh
<span class="nv">$ </span>lunch aosp_hammerhead-userdebug
<span class="nv">$ </span>make img2simg_host
</code></pre></div></div>

<p>这样就会编译出<code class="language-plaintext highlighter-rouge">out/host/linux-x86/bin/img2simg</code></p>

<p>如果要将system.raw.img转换为system.simg，执行：<code class="language-plaintext highlighter-rouge">img2simg system.raw.img system.simg</code></p>

<hr />

<h2 id="144-android解析image镜像">1.44. Android解析image镜像</h2>

<h3 id="1441-ota升级包">1.44.1. ota升级包</h3>

<p>1.解压ota包获取到payload.bin
2.使用工具<code class="language-plaintext highlighter-rouge">payload_dumper-win64</code>，将其放入payload_input（windows工具）
3.运行payload_dumper.exe
4.image生成在payload_output</p>

<h3 id="1442-dump获取车机的image文件">1.44.2. dump获取车机的image文件</h3>

<p>1.进入adb shell
2.在<code class="language-plaintext highlighter-rouge">/dev/block/by-name/</code>查看分区对应的<code class="language-plaintext highlighter-rouge">/dev/block/mmcblk0p7</code>
3.使用<code class="language-plaintext highlighter-rouge">dd if=/dev/block/by-name/mmcblkp*** of=/sdacar/a.img bs=512</code>命令获取到</p>

<h3 id="1443-解析image文件">1.44.3. 解析image文件</h3>

<p>1.确认文件属性</p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">//</span><span class="n">车机dd出来的img</span><span class="w">
</span><span class="n">Debug_OTA_0401_img</span><span class="o">$</span><span class="w"> </span><span class="n">file</span><span class="w"> </span><span class="n">system_old.img</span><span class="w"> 
</span><span class="n">system_old.img</span><span class="o">:</span><span class="w"> </span><span class="n">Linux</span><span class="w"> </span><span class="n">rev</span><span class="w"> </span><span class="m">1.0</span><span class="w"> </span><span class="n">ext4</span><span class="w"> </span><span class="n">filesystem</span><span class="w"> </span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="n">UUID</span><span class="o">=</span><span class="m">4729639</span><span class="n">d</span><span class="o">-</span><span class="n">b5f2</span><span class="m">-5</span><span class="n">cc1</span><span class="o">-</span><span class="n">a120</span><span class="m">-9</span><span class="n">ac5f788683c</span><span class="w"> </span><span class="p">(</span><span class="n">extents</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">large</span><span class="w"> </span><span class="n">files</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">huge</span><span class="w"> </span><span class="n">files</span><span class="p">)</span><span class="w">

</span><span class="o">//</span><span class="n">系统编译的img</span><span class="w">
</span><span class="o">$</span><span class="w"> </span><span class="n">file</span><span class="w"> </span><span class="n">system.img</span><span class="w"> 
</span><span class="n">system.img</span><span class="o">:</span><span class="w"> </span><span class="n">Android</span><span class="w"> </span><span class="n">sparse</span><span class="w"> </span><span class="n">image</span><span class="p">,</span><span class="w"> </span><span class="n">version</span><span class="o">:</span><span class="w"> </span><span class="m">1.0</span><span class="p">,</span><span class="w"> </span><span class="n">Total</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="m">1310720</span><span class="w"> </span><span class="m">4096</span><span class="o">-</span><span class="n">byte</span><span class="w"> </span><span class="n">output</span><span class="w"> </span><span class="n">blocks</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="m">86</span><span class="w"> </span><span class="n">input</span><span class="w"> </span><span class="n">chunks.</span><span class="w">
</span></code></pre></div></div>

<p>2.如果是sparse文件属性，则进行转化：(android编译环境)</p>

<p><code class="language-plaintext highlighter-rouge">simg2img get.img result.img</code></p>

<p>3.采用挂载分区的方式来打开img文件</p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Debug_OTA_0401_img</span><span class="o">$</span><span class="w"> </span><span class="n">file</span><span class="w"> </span><span class="n">system_old.img</span><span class="w"> 
</span><span class="n">system_old.img</span><span class="o">:</span><span class="w"> </span><span class="n">Linux</span><span class="w"> </span><span class="n">rev</span><span class="w"> </span><span class="m">1.0</span><span class="w"> </span><span class="n">ext4</span><span class="w"> </span><span class="n">filesystem</span><span class="w"> </span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="n">UUID</span><span class="o">=</span><span class="m">4729639</span><span class="n">d</span><span class="o">-</span><span class="n">b5f2</span><span class="m">-5</span><span class="n">cc1</span><span class="o">-</span><span class="n">a120</span><span class="m">-9</span><span class="n">ac5f788683c</span><span class="w"> </span><span class="p">(</span><span class="n">needs</span><span class="w"> </span><span class="n">journal</span><span class="w"> </span><span class="n">recovery</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">extents</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">large</span><span class="w"> </span><span class="n">files</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="n">huge</span><span class="w"> </span><span class="n">files</span><span class="p">)</span><span class="w">
</span><span class="n">Debug_OTA_0401_img</span><span class="o">$</span><span class="w"> </span><span class="n">mkdir</span><span class="w"> </span><span class="m">1</span><span class="w">
</span><span class="n">Debug_OTA_0401_img</span><span class="o">$</span><span class="w"> </span><span class="n">sudo</span><span class="w"> </span><span class="n">mount</span><span class="w"> </span><span class="n">system_old.img</span><span class="w"> </span><span class="m">1</span><span class="o">/</span><span class="w"> </span><span class="o">-</span><span class="n">o</span><span class="w"> </span><span class="n">loop</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="145-lpunpack工具">1.45. lpunpack工具</h2>

<blockquote>
  <p>参考：<a href="https://blog.csdn.net/u012045061/article/details/119383397">Android super.img镜像解包</a></p>
</blockquote>

<p>1.编译</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source </span>build/envsetup.sh
make lpunpack
</code></pre></div></div>

<p>2.可以用 lpdump 查看镜像的一些信息，信息里就包括了分区名称</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./lpdump super.img_raw<span class="o">(</span>这里是用 simg2img 转换后的文件<span class="o">)</span>
</code></pre></div></div>

<hr />

<h2 id="146-升级包制作打包方法">1.46. 升级包制作打包方法</h2>

<blockquote>
  <p>参考: <a href="http://www.codes51.com/article/detail_153362.html">Android Recovery OTA升级（一）—— make otapackage</a></p>

  <p>参考：<a href="https://blog.csdn.net/guyongqiangx/article/details/82871409">Android Update Engine分析（八）升级包制作脚本分析</a></p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">make otapackage</code>是Android Build系统支持的命令，用来生成Recovery系统能够进行升级的zip包</p>

<p><strong>或者使用target包制作：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 制作全量升级包</span>
<span class="nv">$ </span>./build/tools/releasetools/ota_from_target_files <span class="se">\</span>
    dist-output/bcm7252ssffdr4-target_files-eng.ygu.zip <span class="se">\</span>
    full-ota.zip

<span class="c"># 制作增量升级包</span>
<span class="nv">$.</span>/build/tools/releasetools/ota_from_target_files <span class="se">\</span>
    <span class="nt">-i</span> dist-output/bcm7252ssffdr4-target_files-eng.ygu.zip <span class="se">\</span>
    dist-output-new/bcm7252ssffdr4-target_files-eng.ygu.zip <span class="se">\</span>
    incremental-ota.zip
</code></pre></div></div>

<h3 id="1461-生成target-fileszip">1.46.1. 生成target-files.zip</h3>

<p><code class="language-plaintext highlighter-rouge">make target-files-package</code></p>

<p>编译出来的默认路径在:</p>

<p><code class="language-plaintext highlighter-rouge">out/target/product/{机型名}/obj/PACKAGING/target_files_intermediates/***-target_files-eng.***.zip</code></p>

<hr />

<h2 id="147-如何禁用ota更新包生成">1.47. 如何禁用OTA更新包生成</h2>

<p>在所选用的device中BoardConfig.mk文件，修改或者增加一行<code class="language-plaintext highlighter-rouge">TARGET_SKIP_OTA_PACKAGE := true</code> 即可在构建时不生成ota更新包</p>

<h2 id="148-解析升级包payloadbin工具">1.48. 解析升级包payload.bin工具</h2>

<ul>
  <li>参考<a href="https://gist.github.com/ius/42bd02a5df2226633a342ab7a9c60f15">payload dumper</a></li>
</ul>

<p>使用payload dumper工具对升级包patload.bin文件进行解析，可以生成对应升级的image镜像文件</p>

<hr />

<h2 id="149-android命令打开触摸坐标显示">1.49. android命令打开触摸坐标显示</h2>

<ul>
  <li>打开：<code class="language-plaintext highlighter-rouge">settings put system pointer_location 1</code></li>
  <li>关闭：<code class="language-plaintext highlighter-rouge">settings put system pointer_location 0</code></li>
</ul>

<hr />

<hr />

<h2 id="150-解决adb-devices报错no-permissionsuser-mi-is-not-in-the-plugdev-group">1.50. 解决adb devices报错no permissions(user mi is not in the plugdev group)</h2>

<p><code class="language-plaintext highlighter-rouge">sudo vim /etc/udev/rules.d/android.rules</code></p>

<p><code class="language-plaintext highlighter-rouge">SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0666"</code></p>

<p>重新拔插一下手机即可</p>

<hr />

<h2 id="151-adb打开某应用">1.51. adb打开某应用</h2>

<ul>
  <li>查看应用包名<code class="language-plaintext highlighter-rouge">dumpsys package</code></li>
  <li>打开具体包查看activity:<code class="language-plaintext highlighter-rouge">dumpsys package com.***.***</code></li>
  <li>查看包位置:<code class="language-plaintext highlighter-rouge">adb shell pm path com.***.***</code></li>
  <li>打开应用：<code class="language-plaintext highlighter-rouge">am start ***/***.actvity</code></li>
  <li>查看当前打开应用的activity：<code class="language-plaintext highlighter-rouge">dumpsys activity top|grep ACTIVITY</code></li>
</ul>

<p><strong>例如打开CarSettings应用：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/system/priv-app/CarSettings <span class="c"># dumpsys package|grep "car.settings"</span>
        d1e0997 com.android.car.settings/.common.CarSettingActivity <span class="o">(</span>2 filters<span class="o">)</span>
        .....
/system/priv-app/CarSettings <span class="c"># dumpsys activity top|grep ACTIVITY</span>
  ACTIVITY com.android.car.settings/.common.CarSettingActivity 154d0d8 <span class="nv">pid</span><span class="o">=</span>6296

/system/priv-app/CarSettings <span class="c"># am start com.android.car.settings/.common.CarSettingActivity</span>
Starting: Intent <span class="o">{</span> <span class="nv">act</span><span class="o">=</span>android.intent.action.MAIN <span class="nb">cat</span><span class="o">=[</span>android.intent.category.LAUNCHER] <span class="nv">cmp</span><span class="o">=</span>com.android.car.settings/.common.CarSettingActivity <span class="o">}</span>
Warning: Activity not started, intent has been delivered to currently running top-most instance.
</code></pre></div></div>

<hr />

<h3 id="1511-adb命令获取当前打开应用包名">1.51.1. adb命令获取当前打开应用包名</h3>

<ul>
  <li>windows:<code class="language-plaintext highlighter-rouge">adb shell dumpsys window | findstr mCurrentFocus</code></li>
  <li>linux:<code class="language-plaintext highlighter-rouge">adb shell dumpsys window | grep mCurrentFocus</code></li>
</ul>

<hr />

<h2 id="152-initrc文件创建空zip文件">1.52. init.rc文件创建空zip文件</h2>

<p>android的init rc目前不支持touch:</p>
<ul>
  <li>touch /data/misc/logd/kmsg.log</li>
</ul>

<p><strong>log中会报错:init: /init.rc: 83: invalid keyword ‘touch’</strong></p>

<p><strong>可以用copy和write命令创建文件:</strong></p>
<ul>
  <li>write/data/non-empty-file</li>
  <li>copy /dev/null    xxx   //创建xxx文件</li>
</ul>

<hr />

<h2 id="153-rc文件添加服务启动">1.53. .rc文件添加服务启动</h2>

<ul>
  <li>参考<a href="https://blog.csdn.net/tq501501/article/details/103556837">Android init.rc 添加自定义服务</a></li>
  <li>参考<a href="https://deepinout.com/android-mk-explanation/android-mk-module-description-variable/android-mk-local_proprietary_module.html">LOCAL_PROPRIETARY_MODULE属性</a></li>
  <li>参考<a href="https://lishiwen4.github.io/android/selinux-security-context">SELinux 安全上下文</a></li>
</ul>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">.rc文件</span><span class="err">：</span><span class="w">
</span><span class="o">//</span><span class="n">服务定义</span><span class="err">：</span><span class="w">
</span><span class="n">service</span><span class="w"> </span><span class="n">screencnap</span><span class="o">/</span><span class="n">system</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">test_service</span><span class="w">
</span><span class="c1"># class 包括core main late_start等，可以不设置。默认default</span><span class="w">
</span><span class="n">class</span><span class="w"> </span><span class="n">main</span><span class="w">
</span><span class="c1"># 用户属性，默认root，一般给root即可，权限最大</span><span class="w">
</span><span class="n">user</span><span class="w"> </span><span class="n">root</span><span class="w">
</span><span class="c1"># 所属组，默认root</span><span class="w">
</span><span class="n">group</span><span class="w"> </span><span class="n">root</span><span class="w">
</span><span class="c1"># disabled 即通过class不能启动，只能start screencap 这种name的方式启动</span><span class="w">
</span><span class="n">disabled</span><span class="w">
</span><span class="c1"># 只运行一次</span><span class="w">
</span><span class="n">oneshot</span><span class="w">

</span><span class="o">//</span><span class="n">服务启动条件</span><span class="o">:</span><span class="w">
</span><span class="n">on</span><span class="w"> </span><span class="n">property</span><span class="o">:</span><span class="n">sys.start_service</span><span class="o">=</span><span class="m">1</span><span class="w">
</span><span class="n">start</span><span class="w"> </span><span class="n">test_service</span><span class="w">
</span></code></pre></div></div>

<h3 id="1531-模块目录将服务名添加到mkbp文件">1.53.1. 模块目录将服务名添加到mk/bp文件</h3>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">//</span><span class="n">Android.mk</span><span class="w">
</span><span class="n">LOCAL_INIT_RC</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="n">test.rc</span><span class="w">

</span><span class="o">//</span><span class="n">因为该属性设成true表示vendor专属模块</span><span class="w">
</span><span class="o">//</span><span class="n">如果要移到vendor</span><span class="o">/</span><span class="n">bin</span><span class="err">，</span><span class="n">则注释掉proprietary</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">true</span><span class="w">

</span><span class="o">//</span><span class="n">Android.bp</span><span class="w">
</span><span class="n">init_rc</span><span class="o">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"test.rc"</span><span class="p">],</span><span class="w">
</span></code></pre></div></div>

<h3 id="1532-devicemk添加该服务">1.53.2. device.mk添加该服务</h3>

<p>添加目录：device/project.mk</p>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># test service</span><span class="w">
</span><span class="n">PRODUCT_PACKAGES</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="err">\</span><span class="w">
    </span><span class="n">test_service</span><span class="w">
</span></code></pre></div></div>

<h3 id="1533-权限初始添加">1.53.3. 权限初始添加</h3>

<p>添加目录：device/project/sepolicy/vendor/common/</p>

<ol>
  <li>修改device/project/sepolicy/vendor/common/file_contexts
    <div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># test service</span><span class="w">
</span><span class="o">/</span><span class="n">vendor</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">test_service</span><span class="w"> </span><span class="n">u</span><span class="o">:</span><span class="n">object_r</span><span class="o">:</span><span class="n">test_service_exec</span><span class="o">:</span><span class="n">s0</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>新增device/project/sepolicy/vendor/commo/test_service.te</li>
</ol>

<hr />

<h2 id="154-系统分区rwx权限">1.54. 系统分区RWX权限</h2>

<blockquote>
  <p><a href="https://blog.csdn.net/jaczen/article/details/72999945">Android启动过程深入解析</a>
<a href="https://blog.csdn.net/weixin_42135087/article/details/104824248">android10.0的init执行顺序</a>
<a href="https://blog.csdn.net/innost/article/details/19299937">深入理解SELinux SEAndroid（第一部分）</a>
<a href="https://blog.csdn.net/weixin_35649059/article/details/106406696">Android 8.1RK平台增加自定义脚本，修改文件权限</a></p>
</blockquote>

<h3 id="1541-权限查看修改方法">1.54.1. 权限查看修改方法</h3>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ls -al</span><span class="w">
</span><span class="n">total</span><span class="w"> </span><span class="m">36</span><span class="w">
</span><span class="n">drwxrwxrwx</span><span class="w">  </span><span class="m">4</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w">  </span><span class="m">4096</span><span class="w"> </span><span class="m">2021-12-12</span><span class="w"> </span><span class="m">08</span><span class="o">:</span><span class="m">00</span><span class="w"> </span><span class="n">.</span><span class="w">
</span><span class="n">drwxr</span><span class="o">-</span><span class="n">xr</span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="m">25</span><span class="w"> </span><span class="n">root</span><span class="w">   </span><span class="n">root</span><span class="w">    </span><span class="m">4096</span><span class="w"> </span><span class="m">2022-03-15</span><span class="w"> </span><span class="m">18</span><span class="o">:</span><span class="m">48</span><span class="w"> </span><span class="n">..</span><span class="w">
</span><span class="n">drwx</span><span class="o">------</span><span class="w">  </span><span class="m">2</span><span class="w"> </span><span class="n">root</span><span class="w">   </span><span class="n">root</span><span class="w">   </span><span class="m">16384</span><span class="w"> </span><span class="m">1970-01-01</span><span class="w"> </span><span class="m">08</span><span class="o">:</span><span class="m">00</span><span class="w"> </span><span class="n">lost</span><span class="o">+</span><span class="n">found</span><span class="w">
</span><span class="n">drwx</span><span class="o">------</span><span class="w">  </span><span class="m">3</span><span class="w"> </span><span class="n">system</span><span class="w"> </span><span class="n">system</span><span class="w">  </span><span class="m">4096</span><span class="w"> </span><span class="m">2021-12-12</span><span class="w"> </span><span class="m">08</span><span class="o">:</span><span class="m">00</span><span class="w"> </span><span class="n">usb</span><span class="w">
</span></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">r读 数字4，w写数字2，x执行数字1，3个一组</code></p>

<p><code class="language-plaintext highlighter-rouge">第一组是自己的权限，第二组是自己所在组的权限，第三组是所有人的权限</code></p>

<ul>
  <li>修改权限方法:<code class="language-plaintext highlighter-rouge">chmod 764 文件名</code>，表示<code class="language-plaintext highlighter-rouge">7=4+2+1，6=4+2,4=4</code></li>
</ul>

<hr />

<h3 id="1542-文件夹权限无法打开读写">1.54.2. 文件夹权限（无法打开读写）</h3>

<p><code class="language-plaintext highlighter-rouge">sudo chmod -R 777 folderName</code></p>

<hr />

<h2 id="155-查看文件权限和selinux域">1.55. 查看文件权限和SELinux域</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell
/sdcard/Android/data <span class="c"># ls -lAZ</span>
total 4
<span class="nt">-rw-rw----</span> 1 root   sdcard_rw u:object_r:sdcardfs:s0    0 2021-01-01 08:00 .nomedia
drwxrwx--x 3 system sdcard_rw u:object_r:sdcardfs:s0 4096 2021-01-01 08:00 com.android.media
</code></pre></div></div>

<hr />

<h2 id="156-selinux对某个文件的权限有neverallow处理方法">1.56. selinux对某个文件的权限（有neverAllow）处理方法</h2>

<p><code class="language-plaintext highlighter-rouge">cat log.txt | grep avc | audit2allow</code></p>

<p>方法一：（出现naverAllow错误）</p>

<blockquote>
  <p>参考：<a href="https://blog.csdn.net/mafei852213034/article/details/109817800">Android O selinux违反Neverallow解决办法</a></p>
</blockquote>

<pre><code class="language-log">type=1400 audit(0.0:124): avc: denied { write } for name="com.engineeringmode-Ok7vxJdZR3gumPFFem-2fg==" dev="mmcblk0p43" ino=8777 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=dir permissive=0
</code></pre>

<p>根据这个mmcblk0p43去在file_context添加：<code class="language-plaintext highlighter-rouge">/dev/block/mmcblk0p43      u:object_r:mmcblk_device:s0</code></p>

<p>然后对mmcblk_device赋权限</p>

<hr />

<p>方法二：</p>

<blockquote>
  <p>参考：<a href="https://blog.csdn.net/ly890700/article/details/54645212">Android6.0 selinux没有对某个文件的权限neverAllow处理方法</a></p>
</blockquote>

<pre><code class="language-log">01-01 08:03:22.410000   217   217 W applypatch: type=1400 audit(0.0:16): avc: denied { read } for name="mmcblk0p15" dev="tmpfs" ino=3364 scontext=u:r:install_recovery:s0 tcontext=u:object_r:block_device:s0 tclass=blk_file permissive=0
</code></pre>

<p>意思是说明install_revovery没有block_device的权限</p>

<p>只要在<code class="language-plaintext highlighter-rouge">install_recovery.te</code>中加入下面权限就可以了。</p>

<p><code class="language-plaintext highlighter-rouge">allow install_recovery recover_block_device:blk_file { open read write };</code></p>

<hr />

<h2 id="157-android-selinux-之-platform_appsystem_apppriv_appuntrusted_app">1.57. android Selinux 之 platform_app，system_app，priv_app，untrusted_app</h2>

<blockquote>
  <p>概念：平台签名：Android.mk中，定义<code class="language-plaintext highlighter-rouge">LOCAL_CERTIFICATE := platform</code></p>
</blockquote>

<blockquote>
  <p>system权限：AndroidManifest.xml中声明<code class="language-plaintext highlighter-rouge">android:sharedUserId="android.uid.system"</code>，同时是平台签名</p>
</blockquote>

<p><strong>分类：</strong></p>
<ul>
  <li>untrusted_app: 第三方app，没有Android平台签名，没有system权限</li>
  <li>platform_app: 有android平台签名，没有system权限</li>
  <li>system_app: 有android平台签名和system权限</li>
  <li>priv_app 没有platform签名的app(肯定没有system权限)， 但Android.mk 中<code class="language-plaintext highlighter-rouge">LOCAL_PRIVILEGED_MODULE := true</code>， 在priv-app目录下的app查看</li>
</ul>

<p>备注：ps只能查看正在运行的进程，如果需要查看指定的app，需要先运行该app</p>

<ul>
  <li>查看全部app类型：<code class="language-plaintext highlighter-rouge">adb shell ps -Z -e</code></li>
  <li>过滤查看：<code class="language-plaintext highlighter-rouge">adb shell ps -Z -e |grep xxx  或者  adb shell ps -Z -e |findstr xxx</code></li>
</ul>

<p>输出的结果第一列是SContext，第二列是UID，只要UID是system的基本都是system_app</p>

<hr />

<h2 id="158-selinux报错unlabel">1.58. selinux报错unlabel</h2>

<pre><code class="language-log">Line 43826: 01-01 09:44:32.220  5076  5076 W engineeringmode: type=1400 audit(0.0:540): avc: denied { search } for name="/" dev="mmcblk0p41" ino=2 scontext=u:r:system_app:s0 tcontext=u:object_r:unlabeled:s0 tclass=dir permissive=0
    Line 43826: 01-01 09:44:32.220  5076  5076 W engineeringmode: type=1400 audit(0.0:540): avc: denied { search } for name="/" dev="mmcblk0p41" ino=2 scontext=u:r:system_app:s0 tcontext=u:object_r:unlabeled:s0 tclass=dir permissive=0
</code></pre>

<p>假设mmcblk0p41节点映射的是system语音分区，该分区是unlabeled，即系统没有指定的目录。
但是在sepolicy已经对该目录打了标签，所以对该分区路径重新打签名</p>

<p><strong>重新relable指定的文件(夹)：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/dev/block/by-name <span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-al</span>
...
lrwxrwxrwx 1 root root   21 1970-01-01 08:00 system -&gt; /dev/block/mmcblk0p41
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 修改device/project/init.project.rc</span>
 on fs
    restorecon_recursive /system
</code></pre></div></div>

<p>编译权限生成在系统目录：<code class="language-plaintext highlighter-rouge">/vendor/etc/selinux</code></p>

<ul>
  <li>可以使用命令调试：<code class="language-plaintext highlighter-rouge">restorecon -R /（对应权限目录）</code></li>
</ul>

<hr />

<h2 id="159-代码关闭selinux权限">1.59. 代码关闭selinux权限</h2>

<blockquote>
  <p>参考：<a href="https://blog.csdn.net/lwz622/article/details/122014647">android11 关闭 Selinux 的方法</a></p>
</blockquote>

<p>修改代码<code class="language-plaintext highlighter-rouge">android/system/core/init/selinux.cpp</code>：<code class="language-plaintext highlighter-rouge">IsEnforcing() 添加：return false</code></p>

<hr />

<h2 id="160-selinux适配android-11android-12变更">1.60. SElinux适配（Android 11&amp;Android 12变更）</h2>

<blockquote>
  <p>参考Google官网解释（繁体）https://source.android.google.cn/docs/security/features/selinux/build</p>
</blockquote>

<p>Android 12源码说明，BOARD_PLAT_PUBLIC_SEPOLICY_DIR和BOARD_PLAT_PRIVATE_SEPOLICY_DIR被启用，使用SYSTEM_EXT_*替代</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//system/sepolicy/README 
SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS +<span class="o">=</span> device/acme/roadrunner-sepolicy/systemext/public
SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS +<span class="o">=</span> device/acme/roadrunner-sepolicy/systemext/private
PRODUCT_PUBLIC_SEPOLICY_DIRS +<span class="o">=</span> device/acme/roadrunner-sepolicy/product/public
PRODUCT_PRIVATE_SEPOLICY_DIRS +<span class="o">=</span> device/acme/roadrunner-sepolicy/product/private

The old BOARD_PLAT_PUBLIC_SEPOLICY_DIR and BOARD_PLAT_PRIVATE_SEPOLICY_DIR
variables have been deprecated <span class="k">in </span>favour of SYSTEM_EXT_<span class="k">*</span><span class="nb">.</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th style="text-align: center">地点</th>
      <th style="text-align: center">包含</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">system/sepolicy/public</td>
      <td style="text-align: center">平台的 sepolicy API</td>
    </tr>
    <tr>
      <td style="text-align: center">system/sepolicy/private</td>
      <td style="text-align: center">平台实现细节（产商可以忽略）</td>
    </tr>
    <tr>
      <td style="text-align: center">system/sepolicy/vendor</td>
      <td style="text-align: center">供应商可以使用的策略和上下文文件（如果需要，供应商可以忽略）</td>
    </tr>
    <tr>
      <td style="text-align: center">BOARD_SEPOLICY_DIRS</td>
      <td style="text-align: center">供应商政策</td>
    </tr>
    <tr>
      <td style="text-align: center">BOARD_ODM_SEPOLICY_DIRS</td>
      <td style="text-align: center">（Android 9 及更高版本）	odm sepolicy</td>
    </tr>
    <tr>
      <td style="text-align: center">SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS （Android 11 及更高版本）</td>
      <td style="text-align: center">System_ext 的 sepolicy API</td>
    </tr>
    <tr>
      <td style="text-align: center">SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS （Android 11 及更高版本）</td>
      <td style="text-align: center">System_ext 实现细节（产商可以忽略）</td>
    </tr>
    <tr>
      <td style="text-align: center">PRODUCT_PUBLIC_SEPOLICY_DIRS</td>
      <td style="text-align: center">（Android 11 及更高版本）	产品的 sepolicy API</td>
    </tr>
    <tr>
      <td style="text-align: center">PRODUCT_PRIVATE_SEPOLICY_DIRS</td>
      <td style="text-align: center">（Android 11 及更高版本）</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="161-git去掉文本末尾的m符号">1.61. git去掉文本末尾的^M符号</h2>

<p>执行<code class="language-plaintext highlighter-rouge">git config --global core.whitespace cr-at-eol</code></p>

<hr />

<h2 id="162-android分区太大编译失败">1.62. Android分区太大编译失败</h2>

<p>尝试下面操作：编译失败后
1） 切换到Android根目录，先source lunch，然后执行: <code class="language-plaintext highlighter-rouge">make api-stubs-docs-update-current-api</code>
2） 再整体编译下</p>

<hr />

<h1 id="2-windows技巧">2. windows技巧</h1>

<h2 id="21-windows获取文件hash哈希值">2.1. windows获取文件hash哈希值</h2>

<ol>
  <li><code class="language-plaintext highlighter-rouge">win+x</code>键选择PowerShell（增强型命令提示符），或者<code class="language-plaintext highlighter-rouge">win+r</code>输入<code class="language-plaintext highlighter-rouge">PowerShell</code>进入终端</li>
  <li>执行<code class="language-plaintext highlighter-rouge">get-filehash usb_ota_update.zip -algorithm md5</code></li>
</ol>

<p><strong>输出：</strong></p>

<pre><code class="language-log">get-filehash usb_ota_update.zip -algorithm md5
Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
MD5             5FD6FA7C9C9BCCE6A18102D8A1C4250D                                       D:\...
</code></pre>

<p><strong>或者使用certutil命令获取md5值：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>certutil <span class="nt">-hashfile</span> usb_ota_update.zip MD5
结果：
MD5 的 usb_ota_update.zip 哈希:
5fd6fa7c9c9bcce6a18102d8a1c4250d
CertUtil: <span class="nt">-hashfile</span> 命令成功完成。
</code></pre></div></div>

<p><strong>还可以使用：</strong></p>
<ul>
  <li>certutil -hashfile D:\1.exe MD5</li>
  <li>certutil -hashfile D:\1.exe SHA1</li>
  <li>certutil -hashfile D:\1.exe SHA256</li>
</ul>

<p><strong>对应Ubuntu上就是:</strong></p>

<ul>
  <li>md5sum file</li>
  <li>sha1sum file</li>
  <li>sha256sum file</li>
</ul>

<p><strong>Tips：</strong>如果出现两个系统对比的文件校验码不同，是因为ftp上传得过程中采用了文本模式，会把文件中换行回车替换为换行。可以用二进制模式上传。（文件打开读取要用二进制方式，文件传输也要用二进制方式）</p>

<hr />

<h3 id="211-md5sum整个文件夹下的文件">2.1.1. md5sum整个文件夹下的文件</h3>

<p><code class="language-plaintext highlighter-rouge">find ./ -type f -print0 | xargs -0 md5sum</code></p>

<hr />

<h2 id="22-windows强制删除文件及文件夹命令">2.2. Windows强制删除文件及文件夹命令</h2>

<ul>
  <li>删除文件或目录CMD命令：</li>
  <li>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">rd/s/q D:\app</code>：强制删除文件文件夹和文件夹内所有文件</li>
    </ul>
  </li>
  <li>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">del/f/s/q D:\app.txt</code>：强制删除文件，文件名必须加文件后缀名</li>
    </ul>
  </li>
  <li>删除文件或目录BAT命令：</li>
  <li>
    <ul>
      <li>新建.BAT批处理文件输入如下命令，然后将要删除的文件拖放到批处理文件图标上即可删除：<code class="language-plaintext highlighter-rouge">DEL /F /A /Q RD /S /Q</code></li>
    </ul>
  </li>
</ul>

<hr />

<hr />

<h1 id="3-linux技巧">3. linux技巧</h1>

<h2 id="31-守护进程daemon">3.1. 守护进程Daemon</h2>

<blockquote>
  <p>Linux Daemon（守护进程）是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务，不是对整个系统就是对某个用户程序提供服务。</p>

  <p>守护进程一般在系统启动时开始运行，除非强行终止，否则直到系统关机都保持运行。守护进程经常以超级用户（root）权限运行，因为它们要使用特殊的端口（1-1024）或访问某些特殊的资源。</p>

  <p>一个守护进程的父进程是init进程，因为它真正的父进程在fork出子进程后就先于子进程exit退出了，所以它是一个由init继承的孤儿进程。守护进程是非交互式程序，没有控制终端，所以任何输出，无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。</p>

  <p>守护进程的名称通常以d结尾，比如sshd、xinetd、crond等</p>
</blockquote>

<hr />

<h2 id="32-tree命令使用">3.2. tree命令使用</h2>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tree</span><span class="w"> </span><span class="p">[</span><span class="o">-</span><span class="n">aACdDfFgilnNpqstux</span><span class="p">][</span><span class="o">-</span><span class="n">I</span><span class="w"> </span><span class="o">&lt;</span><span class="n">范本样式</span><span class="o">&gt;</span><span class="p">][</span><span class="o">-</span><span class="n">P</span><span class="w"> </span><span class="o">&lt;</span><span class="n">范本样式</span><span class="o">&gt;</span><span class="p">][</span><span class="n">目录...</span><span class="p">]</span><span class="w">

</span><span class="o">-</span><span class="n">a</span><span class="w"> </span><span class="n">显示所有文件和目录</span><span class="w">
</span><span class="o">-</span><span class="n">d</span><span class="w"> </span><span class="n">显示目录名称而非内容</span><span class="w">
</span><span class="o">-</span><span class="n">D</span><span class="w"> </span><span class="n">列出文件或目录的更改时间</span><span class="err">。</span><span class="w">
</span><span class="o">-</span><span class="n">f</span><span class="w"> </span><span class="n">在每个文件或目录之前</span><span class="err">，</span><span class="n">显示完整的相对路径名称</span><span class="w">
</span><span class="o">-</span><span class="n">L</span><span class="w"> </span><span class="n">level</span><span class="w"> </span><span class="n">限制目录显示层级</span><span class="err">，</span><span class="n">例如</span><span class="w"> </span><span class="o">-</span><span class="n">L</span><span class="w"> </span><span class="m">1</span><span class="w">

</span><span class="o">-</span><span class="n">s</span><span class="w"> </span><span class="n">列出文件或目录大小</span><span class="w">
</span><span class="o">-</span><span class="n">t</span><span class="w"> </span><span class="n">用文件和目录的更改时间排序</span><span class="w">
</span></code></pre></div></div>

<hr />

<h2 id="33-重启ubuntu虚拟机后重新挂载">3.3. 重启ubuntu虚拟机后重新挂载</h2>

<ul>
  <li>重启命令：<code class="language-plaintext highlighter-rouge">sudo shutdown -r now</code></li>
  <li>重新挂载：<code class="language-plaintext highlighter-rouge">sudo mount -t ext4 /dev/xvdc /home/user/C</code></li>
</ul>

<hr />

<h2 id="34-清理系统">3.4. 清理系统</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">sudo apt-get autoclean</code>:将已经删除了的软件包的.deb安装文件从硬盘中删除掉</li>
  <li><code class="language-plaintext highlighter-rouge">sudo apt-get clean</code>:删除包缓存中的所有包，也会把你已安装的软件包的安装包也删除掉</li>
  <li><code class="language-plaintext highlighter-rouge">sudo apt-get autoremove</code>:删除为了满足其他软件包的依赖而安装的，但现在不再需要的软件包（<a href="https://www.cnblogs.com/boyzgw/p/6610510.html">或使用ubuntu-tweak工具清理</a>）</li>
  <li><code class="language-plaintext highlighter-rouge">apt-get remove PackageName</code>: 删除已安装的软件包（保留配置文件）</li>
  <li><code class="language-plaintext highlighter-rouge">apt-get --purge remove PackageName</code>: 删除已安装包（不保留配置文件)</li>
</ul>

<hr />

<h2 id="35-文件权限从root改成system">3.5. 文件权限从root改成system</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">chown system 文件名</code></li>
  <li><code class="language-plaintext highlighter-rouge">chgrp system 文件名</code></li>
</ul>

<hr />

<h2 id="36-beyond-compare文件diff工具">3.6. beyond compare文件diff工具</h2>

<blockquote>
  <p>参考<a href="https://www.jianshu.com/p/93303b9fb21a">ubuntu 安装 Beyond Compare 安装，永久破解方法</a></p>
</blockquote>

<p>单个文件可以直接使用linux的diff命令</p>

<hr />

<h2 id="37-前10个列表">3.7. 前10个列表</h2>

<p><code class="language-plaintext highlighter-rouge">ls -tl| head -n 10</code></p>

<h2 id="38-压缩解压命令">3.8. 压缩解压命令</h2>

<ul>
  <li>tar：</li>
</ul>

<p>同样适用于windows系统，速度快</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 例子：把/test文件夹打包后生成一个/home/test.tar.gz的文件</span>
<span class="nb">tar</span> <span class="nt">-zcvf</span> /home/test.tar.gz /test
<span class="nb">tar</span> <span class="nt">-zcvf</span> 打包后生成的文件名全路径 要打包的目录

<span class="c"># 解压：</span>
<span class="nb">tar</span> <span class="nt">-xvf</span> test.tar.gz
</code></pre></div></div>

<ul>
  <li>zip 压缩方法：</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>压缩当前的文件夹 zip <span class="nt">-r</span> ./test.zip ./<span class="k">*</span> <span class="nt">-r</span>表示递归
zip <span class="o">[</span>参数] <span class="o">[</span>打包后的文件名] <span class="o">[</span>打包的目录路径]
</code></pre></div></div>

<ul>
  <li>解压<code class="language-plaintext highlighter-rouge">unzip test.zip</code>：如果要把文件解压到指定的目录下,需要用到-d参数，<code class="language-plaintext highlighter-rouge">unzip -d /temp test.zip</code></li>
</ul>

<hr />

<h2 id="39-gnu-nano使用保存退出的说明">3.9. GNU nano使用保存退出的说明</h2>

<p>文件编辑中常用快捷键：ctrl+X 离开nano软件，若有修改过的文件会提示是否保存；选择 ：yes</p>

<p>又提示：file name to write ：***.launch ，选择：<code class="language-plaintext highlighter-rouge">Ctrl+T</code></p>

<p>在下一个界面用 “上下左右” 按键 选择要保存的文件名，然后直接点击 “Enter” 按键即可保存</p>

<ul>
  <li>ctrl+O 保存文件； ctrl+W 查询字符串</li>
  <li>ctrl +C 说明目前光标所在处的行数和列数等信息</li>
  <li>ctrl+ _ 可以直接输入行号，让光标快速移到该行</li>
</ul>

<hr />

<h2 id="310-ubuntu无法使用命令ll">3.10. ubuntu无法使用命令ll</h2>

<p><code class="language-plaintext highlighter-rouge">source .bashrc</code></p>

<h2 id="311-linux查看文件夹大小">3.11. linux查看文件夹大小</h2>

<div class="language-s highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">system</span><span class="o">$</span><span class="w"> </span><span class="n">du</span><span class="w"> </span><span class="o">-</span><span class="n">sh</span><span class="w">
</span><span class="m">1.7</span><span class="n">G</span><span class="w">	</span><span class="n">.</span><span class="w">
</span><span class="n">system</span><span class="o">$</span><span class="w"> </span><span class="n">du</span><span class="w"> </span><span class="o">-</span><span class="n">sh</span><span class="w"> </span><span class="o">-</span><span class="n">k</span><span class="w">
</span><span class="m">1746700</span><span class="w">	</span><span class="n">.</span><span class="w">
</span><span class="n">system</span><span class="o">$</span><span class="w"> </span><span class="n">du</span><span class="w"> </span><span class="o">-</span><span class="n">sh</span><span class="w"> </span><span class="o">-</span><span class="n">m</span><span class="w">
</span><span class="m">1706</span><span class="w">	
</span></code></pre></div></div>

<hr />

<h2 id="312-ubuntu磁盘挂载">3.12. ubuntu磁盘挂载</h2>

<p>1.查看新增的磁盘：<code class="language-plaintext highlighter-rouge">sudo fdisk -l</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.....
Disk /dev/xvdc: 500 GiB, 536870912000 bytes, 1048576000 sectors
Units: sectors of 1 <span class="k">*</span> 512 <span class="o">=</span> 512 bytes
Sector size <span class="o">(</span>logical/physical<span class="o">)</span>: 512 bytes / 512 bytes
I/O size <span class="o">(</span>minimum/optimal<span class="o">)</span>: 512 bytes / 512 bytes
</code></pre></div></div>

<p>2.创建分区</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~<span class="nv">$ </span><span class="nb">sudo </span>fdisk /dev/xvdc 

Welcome to fdisk <span class="o">(</span>util-linux 2.27.1<span class="o">)</span><span class="nb">.</span>
Changes will remain <span class="k">in </span>memory only, <span class="k">until </span>you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0x990e1f81.

Command <span class="o">(</span>m <span class="k">for </span><span class="nb">help</span><span class="o">)</span>: n   //输入n，创建新逻辑磁盘
Partition <span class="nb">type
   </span>p   primary <span class="o">(</span>0 primary, 0 extended, 4 free<span class="o">)</span>
   e   extended <span class="o">(</span>container <span class="k">for </span>logical partitions<span class="o">)</span>
Select <span class="o">(</span>default p<span class="o">)</span>:     //回车

Using default response p.
Partition number <span class="o">(</span>1-4, default 1<span class="o">)</span>: 1   //输入1
First sector <span class="o">(</span>2048-1048575999, default 2048<span class="o">)</span>:   //回车
Last sector, +sectors or +size<span class="o">{</span>K,M,G,T,P<span class="o">}</span> <span class="o">(</span>2048-1048575999, default 1048575999<span class="o">)</span>:    //回车

Created a new partition 1 of <span class="nb">type</span> <span class="s1">'Linux'</span> and of size 500 GiB.

Command <span class="o">(</span>m <span class="k">for </span><span class="nb">help</span><span class="o">)</span>: ^C   //ctrl+c
</code></pre></div></div>

<p>3.磁盘格式化：<code class="language-plaintext highlighter-rouge">sudo mkfs -t ext4 /dev/xvdc</code></p>

<p>4.挂载</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~<span class="nv">$ </span><span class="nb">mkdir </span>workspace
~<span class="nv">$ </span><span class="nb">sudo </span>mount <span class="nt">-t</span> ext4 /dev/xvdc /home//workspace
~<span class="nv">$ </span><span class="nb">df</span> <span class="nt">-h</span>
Filesystem      Size  Used Avail Use% Mounted on
udev            7.9G     0  7.9G   0% /dev
tmpfs           1.6G  9.7M  1.6G   1% /run
/dev/xvda1       83G  5.1G   74G   7% /
tmpfs           7.9G   20M  7.9G   1% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           7.9G     0  7.9G   0% /sys/fs/cgroup
/dev/xvdb1       13M  5.8M  7.3M  45% /mnt/iddisk
tmpfs           1.6G   32K  1.6G   1% /run/user/108
127.0.0.1:/     2.0G 1000M 1000M  50% /ctxmnt
tmpfs           1.6G  120K  1.6G   1% /run/user/16777216
/dev/xvdc       493G   70M  467G   1% /home//workspace
</code></pre></div></div>

<p><strong>PS：重启虚拟机后，如果硬盘没有自动挂载，重新执行 挂载 这一步</strong></p>

<p>5.更改用户组</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~<span class="nv">$ </span><span class="nb">sudo chown</span> <span class="nt">-R</span> : workspace/    //用户冒号后面一个空格然后按tab选择挂载的文件夹
~<span class="nv">$ </span>ll
...
drwxr-xr-x  3  domain <span class="nb">users </span>4096 Aug 10 14:35 workspace/  //原来是root
</code></pre></div></div>

<p>6.自动挂载硬盘</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~<span class="nv">$ </span><span class="nb">cat</span> /etc/fstab 
.....
<span class="c"># swap was on /dev/xvda5 during installation</span>
<span class="nv">UUID</span><span class="o">=</span>8e68541c-22a5-4156-8bae-c4479adf10d4 none            swap    sw              0       0
/dev/xvdc /home/USER/workspace ext4 defaults 0 1   //新增行在最后
</code></pre></div></div>

<hr />

<h2 id="313-ubuntu当在终端执行sudo命令时系统提示lizh-is-not-in-the-sudoers-file">3.13. ubuntu当在终端执行sudo命令时，系统提示“lizh is not in the sudoers file”</h2>

<p><strong>其实就是没有权限进行sudo，解决方法如下：</strong></p>

<ol>
  <li>切换到超级用户：<code class="language-plaintext highlighter-rouge">su root</code></li>
  <li>打开<code class="language-plaintext highlighter-rouge">/etc/sudoers</code>文件：<code class="language-plaintext highlighter-rouge">vim /etc/sudoers</code></li>
  <li>修改文件内容：找到<code class="language-plaintext highlighter-rouge">root ALL=(ALL) ALL</code>一行，在下面插入新的一行，内容是<code class="language-plaintext highlighter-rouge">lizh ALL=(ALL) ALL</code>，然后保存并退出</li>
  <li>退出超级用户</li>
</ol>

<hr />

<h2 id="314-deb安装包安装命令">3.14. deb安装包安装命令</h2>

<p>安装一个Debian软件包，如你手动下载的文件: <code class="language-plaintext highlighter-rouge">dpkg -i &lt;package.deb&gt;</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>删除软件包 dpkg <span class="nt">-r</span> xxx.deb
连同配置文件一起删除 dpkg <span class="nt">-r</span> <span class="nt">--purge</span> xxx.deb
查看软件包信息 dpkg <span class="nt">-info</span> xxx.deb
查看文件拷贝详情 dpkg <span class="nt">-L</span> xxx.deb
查看系统中已安装软件包信息 dpkg <span class="nt">-l</span>
重新配置软件包 dpkg-reconfigure xxx
</code></pre></div></div>

<hr />

<h2 id="315-vim设置80字符对齐线">3.15. vim设置80字符对齐线</h2>

<blockquote>
  <p>参考：<a href="https://www.jianshu.com/p/45c1675bec69">代码规范参考</a></p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">set cc=100</code> (gerrit上行限制字符数是100)</p>

<hr />

<h2 id="316-通过which-命令获取该命令的指向路径">3.16. 通过<code class="language-plaintext highlighter-rouge">which +命令</code>获取该命令的指向路径</h2>

<h2 id="317-linux-read函数和write函数">3.17. linux read()函数和write()函数</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ssize_t read(int fildes, void *buf, size_t nbyte);</code></li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>返回值:
　　&gt; 0: 实际读到的字节数
　　= 0: 读完数据(读文件, 管道, socket末尾--&gt;对端关闭, 对端未关闭会一直等待)
　　-1: 异常:
　　　　errno == EINTR被信号中断, 重启或者退出
　　　　errno == EAGAIN或者EWOULDBLOCK以非阻塞方式读, 并且没有数据
　　　　其他值: 出现错误perror eixt
</code></pre></div></div>
<ul>
  <li><code class="language-plaintext highlighter-rouge">ssize_t write(int fildes, const void *buf, size_t nbyte);</code></li>
</ul>

<p><code class="language-plaintext highlighter-rouge">返回值: 返回实际写出的字节数, 0表示什么也没有写</code></p>

<hr />

<h2 id="318-ubuntu新建用户">3.18. ubuntu新建用户</h2>

<p><strong>ubuntu初始环境只有root用户，为了安全，新建一般权限用户。</strong></p>

<ol>
  <li>添加用户名为test:<code class="language-plaintext highlighter-rouge">root@iZbp10p2g1civut:/# useradd test</code></li>
  <li>为test用户创建密码，输入该命令后会输入密码，和密码确认：<code class="language-plaintext highlighter-rouge">root@iZbp10p2g1civut:/# passwd test</code></li>
  <li>为test用户指定命令解释程序（通常为/bin/bash）：<code class="language-plaintext highlighter-rouge">root@iZbp10p2g1civut:/# usermod -s /bin/bash test</code></li>
  <li>为test用户指定用户主目录：<code class="language-plaintext highlighter-rouge">root@iZbp10p2g1civut:/# usermod -d /home/csdn test</code></li>
  <li>创建test用户主目录文件夹:<code class="language-plaintext highlighter-rouge">root@iZbp10p2g1civut:/#  mkdir /home/test</code></li>
  <li>把test文件夹所有权赋给test用户:<code class="language-plaintext highlighter-rouge">root@iZbp10p2g1civut:/#  chown -R test:test /home/test</code></li>
</ol>

<p>此时创建用户已经成功，可以用test用户进行登录。但此时的test用户不能使用sudo命令，因为没有在<code class="language-plaintext highlighter-rouge">/etc/sudoers</code>文件里给test用户添加权限。</p>

<p>此时切换到root用户下，在<code class="language-plaintext highlighter-rouge">/etc/sudoers</code>文件中添加一行命令：<code class="language-plaintext highlighter-rouge">test ALL=(ALL) ALL</code></p>

<p><strong>注意：</strong></p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">test ALL=(ALL) ALL</code>：允许用户youuser执行sudo命令(需要输入密码)</li>
  <li><code class="language-plaintext highlighter-rouge">%test ALL=(ALL) ALL</code>：允许用户组youuser里面的用户执行sudo命令(需要输入密码)</li>
  <li><code class="language-plaintext highlighter-rouge">test ALL=(ALL) NOPASSWD: ALL</code>：允许用户youuser执行sudo命令,并且在执行的时候不输入密码</li>
  <li><code class="language-plaintext highlighter-rouge">%test ALL=(ALL) NOPASSWD: ALL</code>：允许用户组youuser里面的用户执行sudo命令,并且在执行的时候不输入密码</li>
</ol>

<h3 id="3181-ubuntu查看所有用户">3.18.1. ubuntu查看所有用户</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc# <span class="nb">grep </span>bash /etc/passwd
root:x:0:0:root:/root:/bin/bash
<span class="nb">test</span>:x:1000:1000:test,,,:/home/test:/bin/bash
</code></pre></div></div>

<h3 id="3182-ubuntu切换用户">3.18.2. ubuntu切换用户</h3>

<ol>
  <li>root命令：<code class="language-plaintext highlighter-rouge">sudo su</code></li>
  <li>切换其他账户：<code class="language-plaintext highlighter-rouge">su 账户名</code></li>
</ol>

<h3 id="3183-为ubuntu新创建用户创建默认bashrc并自动加载">3.18.3. 为Ubuntu新创建用户创建默认.bashrc并自动加载</h3>

<ol>
  <li>拷贝.bashrc:<code class="language-plaintext highlighter-rouge">cp /etc/skel/.bashrc ~/</code></li>
  <li>拷贝.profile:<code class="language-plaintext highlighter-rouge">cp /etc/skel/.profile ~/</code></li>
  <li><code class="language-plaintext highlighter-rouge">source ~/.profile</code></li>
</ol>

<hr />

<h2 id="319-cpu核数">3.19. cpu核数</h2>

<p><code class="language-plaintext highlighter-rouge">cat /proc/cpuinfo</code></p>

<p>然后查看一共有几核</p>

<p>⼀般⾄少留⼀个核给其他软件使⽤，剩余的拿来编Android。⽽不是上去直接<code class="language-plaintext highlighter-rouge">-j16</code>，这样很⼤概率不会起到编译加速的作⽤，反⽽会减慢编译速度。</p>

<p>例如9核CPU，⼀般情况下<code class="language-plaintext highlighter-rouge">make -j8</code>；挂机编译时候直接给满:make -j9`</p>

<hr />

<h2 id="320-ubuntulinux查看内存大小">3.20. ubuntu/linux查看内存大小</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dmidecode|grep <span class="nt">-P</span> <span class="nt">-A5</span> <span class="s2">"Memory</span><span class="se">\s</span><span class="s2">+Device"</span>|grep Size|grep <span class="nt">-v</span> Range
<span class="o">[</span><span class="nb">sudo</span><span class="o">]</span> password <span class="k">for </span>user: 
	Size: 16376 MB

<span class="c"># 或者使用</span>
free <span class="nt">-m</span>
</code></pre></div></div>

<hr />

<h1 id="4-cc编程技巧">4. C/C++编程技巧</h1>

<h2 id="41-new和getinstance区别">4.1. new和getInstance()区别</h2>

<ul>
  <li><strong>new的使用</strong>:如Object _object = new Object()，这时候，就必须要知道可能会有第二个Object的存在，而第二个Object也常常是在当前的域中的，可以被直接调用的</li>
  <li><strong>GetInstance的使用</strong>:在主函数开始时调用，返回一个实例化对象。此对象是static的，在内存中保留着它的引用，即内存中有一块区域专门用来存放静态方法和变量，可以直接使用，调用多次返回同一个对象。</li>
</ul>

<p><strong>两者区别对照:</strong></p>
<ol>
  <li>大部分类(非抽象类/非接口/不屏蔽constructor的类)都可以用new，new就是通过生产一个新的实例对象，或者在栈上声明一个对象 ，每部分的调用用的都是一个新的对象。</li>
  <li>getInstance是少部分类才有的一个方法，各自的实现也不同。getInstance在单例模式(保证一个类仅有一个实例，并提供一个访问它的全局访问点)的类中常见，用来生成唯一的实例，getInstance往往是static的。</li>
</ol>

<hr />

<p><strong>单例模式：一个类有且只有一个实例</strong></p>
<ol>
  <li>一个私有的构造器</li>
  <li>一个私有的该类类型的变量</li>
  <li>必须有一个共有的返回类型为该类类型的方法，用来返回这个唯一的变量</li>
</ol>

<hr />

<h2 id="42-snprintf函数用于将格式化的数据写入字符串">4.2. snprintf()函数用于将格式化的数据写入字符串</h2>

<p>其原型为：<code class="language-plaintext highlighter-rouge">int snprintf(char *str, int n, char * format [, argument, ...]);</code></p>

<ul>
  <li>【参数】str为要写入的字符串；n为要写入的字符的最大数目，超过n会被截断；format为格式化字符串，与printf()函数相同；argument为变量</li>
  <li>【返回值】成功则返回参数str字符串长度，失败则返回-1，错误原因存于errno中</li>
</ul>

<hr />

<h2 id="43-文件操作函数">4.3. 文件操作函数</h2>

<h3 id="431-fseek">4.3.1. fseek</h3>

<p><code class="language-plaintext highlighter-rouge">int fseek(FILE *stream, long offset, int fromwhere);</code>：函数设置文件指针stream的位置。</p>

<p>如果执行成功，stream将指向以fromwhere为基准，偏移offset（指针偏移量）个字节的位置，函数返回0。如果执行失败(比如offset取值大于等于2<em>1024</em>1024*1024，即long的正数范围2G)，则不改变stream指向的位置，函数返回一个非0值。</p>

<p>fseek函数和lseek函数类似，但lseek返回的是一个off_t数值，而fseek返回的是一个整型。</p>

<p><strong>具体说明：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int fseek<span class="o">(</span> FILE <span class="k">*</span>stream, long offset, int origin <span class="o">)</span><span class="p">;</span>
第一个参数stream为文件指针
第二个参数offset为偏移量，正数表示正向偏移，负数表示负向偏移
第三个参数origin设定从文件的哪里开始偏移,可能取值为：SEEK_CUR、 SEEK_END 或 SEEK_SET
SEEK_SET： 文件开头
SEEK_CUR： 当前位置
SEEK_END： 文件结尾
其中SEEK_SET,SEEK_CUR和SEEK_END依次为0，1和2.
简言之：
fseek<span class="o">(</span>fp,100L,0<span class="o">)</span><span class="p">;</span>把stream指针移动到离文件开头100字节处；
fseek<span class="o">(</span>fp,100L,1<span class="o">)</span><span class="p">;</span>把stream指针移动到离文件当前位置100字节处；
fseek<span class="o">(</span>fp,-100L,2<span class="o">)</span><span class="p">;</span>把stream指针退回到离文件结尾100字节处。
</code></pre></div></div>

<h3 id="432-ftell">4.3.2. ftell</h3>

<p><code class="language-plaintext highlighter-rouge">long int ftell(FILE *stream)</code>：函数 ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时，由于文件位置频繁的前后移动，程序不容易确定文件的当前位置。</p>

<p><code class="language-plaintext highlighter-rouge">ftell(fp);</code>:利用函数 ftell() 也能方便地知道一个文件的长。</p>

<p>如以下语句序列： <code class="language-plaintext highlighter-rouge">fseek(fp, 0L,SEEK_END); len =ftell(fp);</code></p>

<p>首先将文件的当前位置移到文件的末尾，然后调用函数ftell()获得当前位置相对于文件首的位移，该位移值等于文件所含字节数。</p>

<h3 id="433-feof">4.3.3. feof</h3>

<p>feof是C语言标准库函数，其原型在stdio.h中，其功能是检测流上的文件结束符，如果文件结束，则返回非0值，否则返回0（即，文件结束：返回非0值；文件未结束：返回0值）</p>

<h3 id="434-fgets">4.3.4. fgets</h3>

<p>fgets函数功能为从指定的流中读取数据，每次读取一行。其原型为：<code class="language-plaintext highlighter-rouge">char *fgets(char *str, int n, FILE *stream);</code></p>

<ul>
  <li>函数原型:<code class="language-plaintext highlighter-rouge">char *fgets(char *str, int n, FILE *stream);</code></li>
  <li>参数:</li>
  <li>
    <ul>
      <li>str– 这是指向一个字符数组的指针，该数组存储了要读取的字符串。</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>n– 这是要读取的最大字符数（包括最后的空字符）。通常是使用以 str 传递的数组长度。</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>stream– 这是指向 FILE 对象的指针，该 FILE 对象标识了要从中读取字符的流</li>
    </ul>
  </li>
</ul>

<p>从指定的流 stream 读取一行，并把它存储在str所指向的字符串内。当读取(n-1)个字符时，或者读取到换行符时，或者到达文件末尾时，它会停止，具体视情况而定</p>

<hr />

<h2 id="44-ascii码映射表">4.4. ASCII码映射表</h2>

<p><img src="../../assets/post/2022/2022-12-01-android_debug/ascii码映射表.webp" alt="" /></p>

<h3 id="441-ascii转十六进制实现">4.4.1. ASCII转十六进制实现</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint8_t</span> <span class="nf">char_2_hex</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="o">*</span><span class="n">src</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint8_t</span> <span class="n">desc</span><span class="p">;</span>

    <span class="k">if</span><span class="p">((</span><span class="o">*</span><span class="n">src</span> <span class="o">&gt;=</span> <span class="sc">'0'</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="o">*</span><span class="n">src</span> <span class="o">&lt;=</span> <span class="sc">'9'</span><span class="p">))</span>
        <span class="n">desc</span> <span class="o">=</span> <span class="o">*</span><span class="n">src</span> <span class="o">-</span> <span class="mh">0x30</span><span class="p">;</span>
    <span class="k">else</span> <span class="k">if</span><span class="p">((</span><span class="o">*</span><span class="n">src</span> <span class="o">&gt;=</span> <span class="sc">'a'</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="o">*</span><span class="n">src</span> <span class="o">&lt;=</span> <span class="sc">'f'</span><span class="p">))</span>
        <span class="n">desc</span> <span class="o">=</span> <span class="o">*</span><span class="n">src</span> <span class="o">-</span> <span class="mh">0x57</span><span class="p">;</span>
    <span class="k">else</span> <span class="k">if</span><span class="p">((</span><span class="o">*</span><span class="n">src</span> <span class="o">&gt;=</span> <span class="sc">'A'</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="o">*</span><span class="n">src</span> <span class="o">&lt;=</span> <span class="sc">'F'</span><span class="p">))</span>
        <span class="n">desc</span> <span class="o">=</span> <span class="o">*</span><span class="n">src</span> <span class="o">-</span> <span class="mh">0x37</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">desc</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>其中char字符<code class="language-plaintext highlighter-rouge">buf[1] - 0x30</code>:表示将16进制数转换成10进制数<code class="language-plaintext highlighter-rouge">0x30在ascii里面是字符0</code></p>

<h3 id="442-十六进制转ascii实现">4.4.2. 十六进制转ASCII实现</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">uint8_t</span> <span class="nf">hex_2_char</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="o">*</span><span class="n">src</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">uint8_t</span> <span class="n">desc</span><span class="p">;</span>

    <span class="k">if</span><span class="p">((</span><span class="o">*</span><span class="n">src</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="o">*</span><span class="n">src</span> <span class="o">&lt;=</span> <span class="mi">9</span><span class="p">))</span>
        <span class="n">desc</span> <span class="o">=</span> <span class="o">*</span><span class="n">src</span> <span class="o">+</span> <span class="mh">0x30</span><span class="p">;</span>
    <span class="k">else</span> <span class="k">if</span><span class="p">((</span><span class="o">*</span><span class="n">src</span> <span class="o">&gt;=</span> <span class="mh">0xA</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="o">*</span><span class="n">src</span> <span class="o">&lt;=</span> <span class="mh">0xF</span><span class="p">))</span>
        <span class="n">desc</span> <span class="o">=</span> <span class="o">*</span><span class="n">src</span> <span class="o">+</span> <span class="mh">0x37</span><span class="p">;</span>
    
    <span class="k">return</span> <span class="n">desc</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="45-c函数readdir文件夹路径读取">4.5. C函数readdir文件夹路径读取</h2>

<p>readdir()返回参数dir 目录流的下个目录进入点</p>

<p>d_type表示档案类型：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enum
<span class="o">{</span>
    DT_UNKNOWN <span class="o">=</span> 0,
 <span class="c"># define DT_UNKNOWN DT_UNKNOWN</span>
     DT_FIFO <span class="o">=</span> 1,
 <span class="c"># define DT_FIFO DT_FIFO</span>
     DT_CHR <span class="o">=</span> 2,
 <span class="c"># define DT_CHR DT_CHR</span>
     DT_DIR <span class="o">=</span> 4,
 <span class="c"># define DT_DIR DT_DIR</span>
     DT_BLK <span class="o">=</span> 6,
 <span class="c"># define DT_BLK DT_BLK</span>
     DT_REG <span class="o">=</span> 8,
 <span class="c"># define DT_REG DT_REG</span>
     DT_LNK <span class="o">=</span> 10,
 <span class="c"># define DT_LNK DT_LNK</span>
     DT_SOCK <span class="o">=</span> 12,
 <span class="c"># define DT_SOCK DT_SOCK</span>
     DT_WHT <span class="o">=</span> 14
 <span class="c"># define DT_WHT DT_WHT</span>
<span class="o">}</span><span class="p">;</span>
</code></pre></div></div>

<hr />

<h2 id="46-c函数toupper函数或者tolower函数将字符串统一转换为大写或小写然后比较">4.6. C++函数toupper函数或者tolower函数将字符串统一转换为大写或小写然后比较</h2>

<p>这种方法不用考虑跨平台的问题，因为使用的是C++标准库中的函数实现的。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
* 文件名    ：    main.cpp
* 功能        ： 将字符串转换为大写，使用transform函数
*/</span>
<span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;algorithm&gt;</span><span class="cp">
</span><span class="k">using</span> <span class="k">namespace</span> <span class="n">std</span><span class="p">;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">string</span> <span class="n">strTest</span> <span class="o">=</span> <span class="s">"use test."</span><span class="p">;</span>
    <span class="n">transform</span><span class="p">(</span><span class="n">strTest</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">strTest</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> <span class="n">strTest</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">toupper</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">strTest</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">strTest</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
    <span class="p">}</span>
    <span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">endl</span><span class="p">;</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h1 id="5-工具使用技巧">5. 工具使用技巧</h1>

<h2 id="51-android-studio环境配置及常见问题">5.1. Android Studio环境配置及常见问题</h2>

<h3 id="511-apk缺少签名system-app性质">5.1.1. APK缺少签名（system app性质）</h3>

<p><strong>例如：</strong>
1.AndroidManifest.xml中添加：</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;manifest</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
    <span class="na">android:sharedUserId=</span><span class="s">"android.uid.system"</span>
    <span class="na">package=</span><span class="s">"com.example.debugapp"</span><span class="nt">&gt;</span>
    ....
</code></pre></div></div>

<p>2.build.gradle中添加签名文件</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    signingConfigs <span class="o">{</span>
        debug <span class="o">{</span>
            storeFile file<span class="o">(</span><span class="s2">"../signApk/platform.jks"</span><span class="o">)</span>  //导入对应的keystore文件
            storePassword <span class="s1">'android'</span>
            keyAlias <span class="s1">'androiddebugkey'</span>
            keyPassword <span class="s1">'android'</span>
        <span class="o">}</span>
    <span class="o">}</span>
</code></pre></div></div>

<p><strong>或者这样加：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>release <span class="o">{</span>
    storeFile file<span class="o">(</span><span class="s2">"platform.jks"</span><span class="o">)</span>
    storePassword <span class="s1">'android'</span>
    keyAlias <span class="s1">'platform'</span>
    keyPassword <span class="s1">'android'</span>
    v1SigningEnabled <span class="nb">true
    </span>v2SigningEnabled <span class="nb">true</span>
<span class="o">}</span>
debug <span class="o">{</span>
    storeFile file<span class="o">(</span><span class="s2">"platform.jks"</span><span class="o">)</span>
    storePassword <span class="s1">'android'</span>
    keyAlias <span class="s1">'platform'</span>
    keyPassword <span class="s1">'android'</span>
    v1SigningEnabled <span class="nb">true
    </span>v2SigningEnabled <span class="nb">true</span>
<span class="o">}</span>
</code></pre></div></div>

<ol>
  <li>rebuild</li>
</ol>

<hr />

<h4 id="5111-app安装报错install_failed_update_incompatible">5.1.1.1. APP安装报错INSTALL_FAILED_UPDATE_INCOMPATIBLE</h4>

<pre><code class="language-log">adb install -r -t -d Debug.apk
Performing Streamed Install
adb: failed to install Debug.apk: Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.example.debugapp signatures do not match previously installed version; ignoring!]
</code></pre>

<ol>
  <li>将设备<code class="language-plaintext highlighter-rouge">/data/system/packages.xml</code>pull到本地</li>
  <li>删除该包package中的数据，然后push到设备，重启</li>
</ol>

<hr />

<h3 id="512-报错ssl-peer-shut-down-incorrectly问题">5.1.2. 报错SSL peer shut down incorrectly问题</h3>

<ul>
  <li>参考<a href="https://www.jianshu.com/p/194b57cf7162">AndroidStudio SSL peer shut down incorrectly 问题</a></li>
</ul>

<p><strong>根build.gradle文件中添加镜像仓库:</strong></p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">buildscript</span> <span class="o">{</span>
    <span class="n">repositories</span> <span class="o">{</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//jitpack.io' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/public' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/central' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/google' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/public' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/gradle-plugin' }</span>
        <span class="n">google</span><span class="o">()</span>
        <span class="n">jcenter</span><span class="o">()</span>
    <span class="o">}</span>
    <span class="n">dependencies</span> <span class="o">{</span>
       <span class="o">.....</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="n">allprojects</span> <span class="o">{</span>
    <span class="n">repositories</span> <span class="o">{</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//jitpack.io' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/public' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/central' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/google' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/public' }</span>
        <span class="n">maven</span> <span class="o">{</span> <span class="n">url</span> <span class="err">'</span><span class="nl">https:</span><span class="c1">//maven.aliyun.com/repository/gradle-plugin' }</span>
        <span class="n">google</span><span class="o">()</span>
        <span class="n">jcenter</span><span class="o">()</span>
    <span class="o">}</span>
</code></pre></div></div>

<hr />

<h3 id="513-报错caused-by-orggradleapiinternalpluginspluginapplicationexception-failed-to-app">5.1.3. 报错Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Failed to app</h3>

<ul>
  <li>参考<a href="https://blog.csdn.net/weixin_48437363/article/details/109569449">Android Studio出现Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Failed to app</a></li>
</ul>

<p>在项目中的gradle.properties文件中添加语句：</p>

<p><code class="language-plaintext highlighter-rouge">android.overridePathCheck=true</code></p>

<hr />

<h3 id="514-报错error-could-not-install-gradle-distribution-from-httpsservicesgradleorgdistributionsgradle">5.1.4. 报错ERROR: Could not install Gradle distribution from ‘https://services.gradle.org/distributions/gradle</h3>

<p>网络问题，切换可用网络</p>

<hr />

<h2 id="52-vscode技巧">5.2. VSCODE技巧</h2>

<h3 id="521-vscode离线插件下载网址">5.2.1. vscode离线插件下载网址</h3>

<ul>
  <li>进入[vscode插件官网]（https://marketplace.visualstudio.com/）</li>
</ul>

<h3 id="522-思维导图工具">5.2.2. 思维导图工具</h3>

<p><strong>工具推荐：</strong></p>
<ol>
  <li><a href="https://markmap.js.org/repl/">Try markmap在线制作使用markdown</a></li>
  <li><a href="https://naotu.baidu.com/">百度脑图在线制作</a>：可以导入markdown导出png图片</li>
</ol>

<p><strong>vscode插件：</strong></p>
<ol>
  <li>mindmap（文件以km结尾）（参考https://blog.csdn.net/liuxiao723846/article/details/107414365）</li>
  <li>Markmap（同Try markmap使用markdown，文件以mm.md结尾）</li>
</ol>

<hr />

<h3 id="523-代码注释插件">5.2.3. 代码注释插件</h3>

<ul>
  <li>参考：<a href="https://blog.csdn.net/a314753967/article/details/125422370">VScode插件自动添加注释</a></li>
</ul>

<p>安装koroFileHeader插件</p>

<p><strong>设置选项搜索cursorMode修改如下：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // 函数注释
    <span class="s2">"fileheader.cursorMode"</span>: <span class="o">{</span>
        // 默认字段
        <span class="s2">"description"</span>:<span class="s2">""</span>,
        <span class="s2">"param"</span>:<span class="s2">""</span>,
        <span class="s2">"return"</span>:<span class="s2">""</span>
</code></pre></div></div>

<hr />

<h3 id="524-vscode添加分隔线">5.2.4. vscode添加分隔线</h3>

<p>在<code class="language-plaintext highlighter-rouge">file-&gt;preferences-&gt;settings</code>的settings.json中添加：<code class="language-plaintext highlighter-rouge">"editor.rulers": [4, 100]</code></p>

<h3 id="525-vscode删除缩进多行tab">5.2.5. vscode删除缩进多行tab</h3>

<p>按：<code class="language-plaintext highlighter-rouge">shift + tab</code></p>

<hr />

<h3 id="526-vscode阅读android内核源码方法">5.2.6. VSCode阅读Android内核源码方法</h3>

<blockquote>
  <p>参考：<a href="https://www.jianshu.com/p/af723ff252e6">使用Visual Studio Code阅读Android内核源码</a></p>
</blockquote>

<h3 id="527-visual-studio-codevscode关闭右侧预览功能">5.2.7. Visual Studio Code（VSCode）关闭右侧预览功能</h3>

<p>关闭方法：点击文件-首选项-设置,搜索<code class="language-plaintext highlighter-rouge">editor.minimap.enabled</code>，默认值为打钩，只需要把钩去掉即可</p>

<h3 id="528-vscode始终打开新标签">5.2.8. vscode始终打开新标签</h3>

<p><code class="language-plaintext highlighter-rouge">workbench.editor.enablePreview</code>改为false</p>

<h3 id="529-vscode标签多行显示">5.2.9. vscode标签多行显示</h3>

<p><code class="language-plaintext highlighter-rouge">wrap tabs</code></p>

<h3 id="5210-vscode自动换行">5.2.10. vscode自动换行</h3>

<p><code class="language-plaintext highlighter-rouge">ALT+Z</code></p>

<h3 id="5211-markdown-pdf转换失败">5.2.11. markdown pdf转换失败</h3>

<p>chromeium安装失败</p>

<p>在首选项-设置-扩展-markdown pdf的executablePath添加路径：<code class="language-plaintext highlighter-rouge">C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe</code></p>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="debug" /><summary type="html"><![CDATA[Android系统调试技巧积累笔记，主要包含Android Framework，以及日常接触的git、adb、linux系统等调试技巧。]]></summary></entry><entry><title type="html">Android SELinux权限概念和配置说明</title><link href="https://alonealive.github.io/android/android-selinux/" rel="alternate" type="text/html" title="Android SELinux权限概念和配置说明" /><published>2022-11-14T10:11:02+00:00</published><updated>2022-11-14T10:11:02+00:00</updated><id>https://alonealive.github.io/android/android-selinux</id><content type="html" xml:base="https://alonealive.github.io/android/android-selinux/"><![CDATA[<blockquote>
  <p>本篇主要讲述Android SELinux的基本概念，包含类型、属性、规则，Sepolicy的核心模块、关键文件，SELinux配置的步骤、调试验证方法等。</p>
</blockquote>

<h1 id="1-概述">1. 概述</h1>

<h2 id="11-概念">1.1. 概念</h2>

<blockquote>
  <p>作为Android安全模型的一部分，Android使用安全增强型Linux(SELinux)对所有进程强制执行强制访问控制(MAC)，甚至包括以Root/超级用户权限运行的进程（Linux功能）。借助SELinux，Android可以更好地保护和限制系统服务、控制对应用数据和系统日志的访问、降低恶意软件的影响，并保护用户免遭移动设备上的代码可能存在的缺陷的影响。</p>
</blockquote>

<p>SELinux按照默认拒绝的原则运行：任何未经明确允许的行为都会被拒绝。<strong>SELinux可按两种全局模式运行：</strong></p>

<ul>
  <li>宽容模式：权限拒绝事件会被记录下来，但不会被强制执行（仅记录但不强制执行SELinux安全政策）</li>
  <li>强制模式：权限拒绝事件会被记录下来并强制执行。（强制执行并记录安全政策。如果失败，则显示为EPERM错误）</li>
</ul>

<p>在选择强制执行级别时只能二择其一，您的选择将决定您的政策是采取操作，还是仅允许您收集潜在的失败事件。宽容模式在实现过程中尤其有用。</p>

<hr />

<h2 id="12-mac和dac">1.2. MAC和DAC</h2>

<p><strong>安全增强型Linux(SELinux)</strong>是适用于Linux操作系统的<code class="language-plaintext highlighter-rouge">强制访问控制(MAC)系统</code>。</p>

<p>作为MAC系统，它与Linux中用户非常熟悉的<code class="language-plaintext highlighter-rouge">自主访问控制(DAC)系统</code>不同。在DAC系统中，存在所有权的概念，即特定资源的所有者可以控制与该资源关联的访问权限。这种系统通常比较粗放，并且容易出现无意中提权的问题。MAC系统则会在每次收到访问请求时都先咨询核心机构，再做出决定。</p>

<hr />

<h2 id="13-类型属性和规则te">1.3. 类型、属性和规则(te)</h2>

<blockquote>
  <p>Android依靠SELinux的类型强制执行(TE)组件来实施其政策。这表示所有对象（例如文件、进程或套接字）都具有相关联的类型。例如，<strong>默认情况下，应用的类型为untrusted_app</strong>。<strong>对于进程而言，其类型也称为域</strong>。<strong>可以使用一个或多个属性为类型添加注解。属性可用于同时指代多种类型</strong>。</p>
</blockquote>

<p>对象会映射到类（例如文件、目录、符号链接、socket套接字），并且每个类的不同访问权限类型由权限表示。</p>

<p>例如，file类存在权限open。虽然类型和属性作为Android SELinux政策的一部分会进行定期更新，但权限和类是静态定义的，并且作为新Linux版本的一部分也很少进行更新。</p>

<p>政策规则采用以下格式：<code class="language-plaintext highlighter-rouge">allow source target:class permissions;</code>，其中：</p>

<ul>
  <li>source - 规则主题的类型（或属性）。谁正在请求访问权限？</li>
  <li>目标 - 对象的类型（或属性）。对哪些内容提出了访问权限请求？</li>
  <li>类 - 要访问的对象（例如，文件、套接字）的类型</li>
  <li>权限 - 要执行的操作（或一组操作，例如读取、写入）</li>
</ul>

<hr />

<p><strong>规则的一个示例如下：</strong>这表示应用可以读取和写入带有<code class="language-plaintext highlighter-rouge">app_data_file</code>标签的文件。还有其他应用类型。例如，isolated_app用于清单中含有<code class="language-plaintext highlighter-rouge">isolatedProcess=true</code>的应用服务。</p>

<p><code class="language-plaintext highlighter-rouge">allow untrusted_app app_data_file:file { read write };</code></p>

<p><strong>Android对涵盖应用的所有类型使用名为appdomain的属性，而不是对这两种类型重复同一规则：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Associate the attribute appdomain with the type untrusted_app.</span>
typeattribute untrusted_app, appdomain<span class="p">;</span>

<span class="c"># Associate the attribute appdomain with the type isolated_app.</span>
typeattribute isolated_app, appdomain<span class="p">;</span>

allow appdomain app_data_file:file <span class="o">{</span> <span class="nb">read </span>write <span class="o">}</span><span class="p">;</span>
</code></pre></div></div>

<p>当编写的规则指定了某个属性名称时，该名称会自动扩展为列出与该属性关联的所有域或类型。一些重要属性包括：</p>

<ul>
  <li>domain - 与所有进程类型相关联的属性</li>
  <li>file_type - 与所有文件类型相关联的属性</li>
</ul>

<hr />

<h3 id="131-宏的使用">1.3.1. 宏的使用</h3>

<p>特别是对于文件访问权限，有很多种权限需要考虑。例如，read权限不足以打开相应文件或对其调用stat。<strong>为了简化规则定义，Android提供了一组宏来处理最常见的情况</strong></p>

<p>例如，若要添加open等缺少的权限，可以将上述规则改写为：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>allow appdomain app_data_file:file rw_file_perms<span class="p">;</span>
</code></pre></div></div>

<p><strong>查看实用宏的更多示例：</strong></p>
<ul>
  <li>system/sepolicy/public/global_macros</li>
  <li>system/sepolicy/public/te_macros</li>
</ul>

<p>尽可能使用宏，以降低因相关权限被拒而导致失败的可能性。定义类型后，需要将其与所代表的文件或进程相关联。</p>

<hr />

<h2 id="14-安全上下文和类别file_contexts">1.4. 安全上下文和类别（file_contexts）</h2>

<p>调试SELinux政策或为文件添加标签时（通过<code class="language-plaintext highlighter-rouge">file_contexts</code>或运行<code class="language-plaintext highlighter-rouge">ls -Z</code>），可能会遇到安全上下文（也称为标签）。</p>

<p>例如<code class="language-plaintext highlighter-rouge">u:r:untrusted_app:s0:c15,c256,c513,c768</code>。安全上下文的格式为：<code class="language-plaintext highlighter-rouge">user:role:type:sensitivity[:categories]</code>。</p>

<p>通常可以忽略上下文的<code class="language-plaintext highlighter-rouge">user、role和sensitivity</code>字段。</p>

<p><strong>从Android S开始，类别被用于：</strong></p>

<ul>
  <li>分隔应用数据，使其不被其他应用访问。</li>
  <li>分隔不同实际用户的应用数据。</li>
</ul>

<h3 id="141-file_contexts解释说明">1.4.1. file_contexts解释说明</h3>

<p><code class="language-plaintext highlighter-rouge">oem_lock                                  u:object_r:oem_lock_service:s0</code></p>

<p><strong>说明如下：</strong></p>

<ul>
  <li>oem_lock：系统中具体资源，如服务名、设备名、文件目录等</li>
  <li>u: selinux中唯一的用户</li>
  <li>object_r:描述资源类型。r为进程资源，object_r非进程资源</li>
  <li>oem_lock_service：资源在权限规则中所属代表</li>
  <li>s0：selinux中权限级别，一般使用s0</li>
</ul>

<hr />

<h2 id="15-te文件内容的语法规则">1.5. te文件内容的语法规则</h2>

<p><code class="language-plaintext highlighter-rouge">rule_name  source_type  target_type : class perm_set</code></p>

<ul>
  <li>rule_name：赋予权限的规则，包含allow、dontaudit、auditallow、neverallow，命令不可以随意添加。</li>
  <li>source_type：访问target_type的主体或主体集合（域），可自定义</li>
  <li>target_type：接受主体访问的客体或客体集合（域），可自定义</li>
  <li>class：客体资源类型，不同的资源类型具有不同访问权限，可自定义、可继承</li>
  <li>perm_set：客体予以主体的权限说明。是class中具有的权限的子集</li>
</ul>

<p><strong>source_type、target_type使用type、typeattribute、attribute定义</strong></p>

<ul>
  <li>attribute定义一个代表具有某种相中属性的集合（即域）：<code class="language-plaintext highlighter-rouge">attribute dev_type;</code></li>
  <li>type定义代表一个或一类资源类型，并分配至不同属性（域）中：</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 定义一个类型，属于dev_type属性</span>
<span class="nb">type </span>ttyMT_device, dev_type<span class="p">;</span> 

<span class="c">###############以上定义可以拆分为两部分</span>
<span class="c"># 仅定义一个类型</span>
<span class="nb">type </span>ttyMT_device；
<span class="c"># 仅把ttyMT_device类型关联到dev_type属性</span>
typeattribute ttyMT_device dev_type；
</code></pre></div></div>

<ul>
  <li>属性间使用逗号，一个类型可以关联至多个属性：<code class="language-plaintext highlighter-rouge">type oem_lock_service, system_api_service, system_server_service, service_manager_type;</code></li>
  <li>class字段使用comm和class定义，comm定义的class可以被class定义的对象继承</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>common file <span class="o">{</span>
	ioctl <span class="nb">read </span>write create getattr setattr lock relabelfrom relabelto
	append <span class="nb">unlink link </span>rename execute swapon quotaon mounton
<span class="o">}</span>
</code></pre></div></div>

<p><strong>class类型继承comm类型：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class <span class="nb">dir
</span>inherits file
<span class="o">{</span>
	add_name
	remove_name
	reparent
	search
	<span class="nb">rmdir
	</span>open
	audit_access
	execmod
<span class="o">}</span>
</code></pre></div></div>

<hr />

<h2 id="16-注意点">1.6. 注意点</h2>

<p>Android并不会使用SELinux提供的所有功能。注意以下几点：</p>

<ul>
  <li>AOSP中的大部分政策都是使用内核政策语言定义的。在使用通用中间语言(CIL)时，会存在一些例外情况</li>
  <li>不使用SELinux用户。唯一定义的用户是<code class="language-plaintext highlighter-rouge">u</code>。必要时，系统会使用安全上下文的类别字段表示实际用户</li>
  <li>不使用SELinux角色和基于角色的访问权限控制 (RBAC)。定义并使用了两个默认角色：<code class="language-plaintext highlighter-rouge">r（适用于主题）</code>和<code class="language-plaintext highlighter-rouge">object_r（适用于对象）</code></li>
  <li>不使用SELinux敏感度。已始终设置好默认的<code class="language-plaintext highlighter-rouge">s0</code>敏感度</li>
  <li>不使用SELinux布尔值。一旦设备政策构建完成，该政策不再取决于设备状态。这简化了政策的审核和调试过程</li>
</ul>

<hr />

<h1 id="2-selinux实现">2. SElinux实现</h1>

<h2 id="21-selinux核心模块">2.1. SElinux核心模块</h2>

<blockquote>
  <p>Android的SElinux可以阅读<code class="language-plaintext highlighter-rouge">system/sepolicy</code>目录中的文件。这些文件在编译后会包含SELinux内核安全政策，并涵盖上游Android操作系统</p>
</blockquote>

<p>通常情况下，不能直接修改<code class="language-plaintext highlighter-rouge">system/sepolicy</code>文件，但可以添加或修改自己的设备专用政策文件（位于<code class="language-plaintext highlighter-rouge">/device/manufacturer/device-name/sepolicy</code>目录中）。在Android 8.0及更高版本中，对这些文件所做的更改只会影响供应商目录中的政策。</p>

<p><strong>无论是哪个Android版本，都仍需要修改以下文件：</strong></p>

<h3 id="211-selinux相关模块">2.1.1. SElinux相关模块</h3>

<p><strong>SELinux 的构建逻辑位于以下文件中：</strong></p>
<ul>
  <li>external/selinux：外部SELinux项目，用于构建HOST命令行实用工具以编译SELinux政策和标签</li>
  <li>
    <ul>
      <li>external/selinux/libselinux：Android仅使用外部libselinux项目的一个子集，以及一些Android专用自定义内容（参阅external/selinux/README.android了解详情）</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>external/selinux/libsepol：</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>
        <ul>
          <li>chkcon：确定安全环境对指定的二进制政策（主机可执行文件）是否有效</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <ul>
      <li>
        <ul>
          <li>libsepol：用于操控二进制安全政策（主机静态/共享库、目标静态库）的 SELinux 库</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <ul>
      <li>external/selinux/checkpolicy：SELinux 政策编译器（主机可执行文件：checkpolicy、checkmodule和dispol）。依赖于libsepol</li>
    </ul>
  </li>
  <li>system/sepolicy：核心Android SELinux政策配置，包括上下文和政策文件。主要sepolicy构建逻辑也位于此处 (system/sepolicy/Android.mk)</li>
</ul>

<hr />

<h2 id="22-selinux关键文件">2.2. SElinux关键文件</h2>

<h3 id="221-上下文的描述文件_contexts">2.2.1. 上下文的描述文件（*_contexts）</h3>

<blockquote>
  <p>可以在上下文的描述文件中为需要的对象指定标签</p>
</blockquote>

<ul>
  <li>file_contexts：用于为文件分配标签，并且可供多种用户空间组件使用。在创建新政策时，需创建或更新该文件，以便为文件分配新标签。如需应用新的file_contexts，需重新构建文件系统映像，或对要重新添加标签的文件运行<code class="language-plaintext highlighter-rouge">restorecon</code>（比如重新加载权限<code class="language-plaintext highlighter-rouge">restorecon -R /（对应权限目录）</code>）。在升级时，对<code class="language-plaintext highlighter-rouge">file_contexts</code>所做的更改会在升级过程中自动应用于系统和用户数据分区。此外，还可以通过以下方式使这些更改在升级过程中自动应用于其他分区：在以允许读写的方式装载相应分区后，将<code class="language-plaintext highlighter-rouge">restorecon_recursive</code>（重载权限）调用添加到<code class="language-plaintext highlighter-rouge">init.board.rc</code>文件中</li>
  <li>property_contexts：用于为Android系统属性分配标签，以便控制哪些进程可以设置这些属性。在启动期间，init进程会读取此配置</li>
  <li>service_contexts：用于为<code class="language-plaintext highlighter-rouge">Android Binder服务</code>分配标签，以便控制哪些进程可以为相应服务添加（注册）和查找（查询）Binder引用。在启动期间，<code class="language-plaintext highlighter-rouge">servicemanager</code>进程会读取此配置</li>
  <li>genfs_contexts：用于为不支持扩展属性的文件系统（例如，<code class="language-plaintext highlighter-rouge">proc</code>或<code class="language-plaintext highlighter-rouge">vfat</code>）分配标签。此配置会作为内核政策的一部分进行加载，但更改可能对<code class="language-plaintext highlighter-rouge">内核inode</code>无效。要全面应用更改，需要重新启动设备，或卸载并重新装载文件系统。此外，通过使用<code class="language-plaintext highlighter-rouge">context=mount</code>选项，还可以为装载的特定系统文件（例如<code class="language-plaintext highlighter-rouge">vfat</code>）分配特定标签</li>
  <li>seapp_contexts：用于为<strong>应用进程和/data/data目录</strong>分配标签。在每次应用启动时，zygote进程都会读取此配置；在启动期间，installd会读取此配置</li>
  <li>mac_permissions.xml：用于根据应用签名和应用软件包名称（后者可选）为应用分配seinfo标记。随后，分配的seinfo标记可在<code class="language-plaintext highlighter-rouge">seapp_contexts</code>文件中用作密钥，以便为带有该seinfo标记的所有应用分配特定标签。在启动期间，system_server会读取此配置</li>
</ul>

<p><strong>简单来说：</strong></p>
<ul>
  <li>file_contexts //系统中所有file_contexts安全上下文</li>
  <li>seapp_contexts //app安全上下文</li>
  <li>property_contexts //属性的安全上下文</li>
  <li>service_contexts    //service文件安全上下文</li>
  <li>genfs_contexts //虚拟文件系统安全上下文</li>
</ul>

<hr />

<h4 id="2211-文件上下文file_contexts">2.2.1.1. 文件上下文file_contexts</h4>

<p><strong>Android8.0针对file_contexts引入了以下更改：</strong></p>

<ul>
  <li>为了避免启动期间在设备上产生额外的编译开销，file_contexts不再以二进制文件形式存在。而是可读的正则表达式文本文件，例如{property,service}_contexts（和7.0之前的版本一样）</li>
  <li>file_contexts拆分成了两个文件：</li>
  <li>
    <ul>
      <li>plat_file_contexts</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>
        <ul>
          <li>Android平台file_context，没有设备专用标签，例外情况是，必须准确标记/vendor分区的某些部分，以确保sepolicy文件正常运行</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <ul>
      <li>
        <ul>
          <li>必须位于设备上system分区中的<code class="language-plaintext highlighter-rouge">/system/etc/selinux/plat_file_contexts</code>下，并由init在启动时加载（与供应商file_context一起加载）</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <ul>
      <li>vendor_file_contexts</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>
        <ul>
          <li>设备专用file_context，通过合并file_contexts（位于设备的Boardconfig.mk文件中由BOARD_SEPOLICY_DIRS指向的目录下）进行构建</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <ul>
      <li>
        <ul>
          <li>必须安装到vendor分区中的<code class="language-plaintext highlighter-rouge">/vendor/etc/selinux/vendor_file_contexts</code>下，并由init在启动时加载（与平台file_context一起加载）</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<hr />

<h4 id="2212-属性上下文property_contexts">2.2.1.2. 属性上下文property_contexts</h4>

<p><strong>在Android8.0中，property_contexts拆分成了两个文件：</strong></p>

<ul>
  <li>plat_property_contexts</li>
  <li>
    <ul>
      <li>没有设备专用标签的Android平台property_context</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于system分区中的<code class="language-plaintext highlighter-rouge">/system/etc/selinux/plat_property_contexts</code>下，并由init在启动时加载（与供应商property_contexts一起加载）</li>
    </ul>
  </li>
  <li>vendor_property_contexts</li>
  <li>
    <ul>
      <li>设备专用property_context，通过合并property_contexts（位于设备的Boardconfig.mk文件中由BOARD_SEPOLICY_DIRS指向的目录下）进行构建</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于vendor分区中的<code class="language-plaintext highlighter-rouge">/vendor/etc/selinux/vendor_property_contexts</code>下，并由init在启动时加载（与平台property_context一起加载）</li>
    </ul>
  </li>
</ul>

<hr />

<h4 id="2213-服务上下文service_contexts">2.2.1.3. 服务上下文service_contexts</h4>

<p><strong>在Android8.0中，service_contexts拆分成了以下文件：</strong></p>

<ul>
  <li>plat_service_contexts</li>
  <li>
    <ul>
      <li>servicemanager的Android平台专用service_context。service_context没有设备专用标签</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于system分区中的<code class="language-plaintext highlighter-rouge">/system/etc/selinux/plat_service_contexts</code>下，并由servicemanager在启动时加载（与供应商service_contexts一起加载）</li>
    </ul>
  </li>
  <li>vendor_service_contexts</li>
  <li>
    <ul>
      <li>设备专用service_context，通过合并service_contexts（位于设备的Boardconfig.mk文件中由BOARD_SEPOLICY_DIRS指向的目录下）进行构建</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于vendor分区中的<code class="language-plaintext highlighter-rouge">/vendor/etc/selinux/vendor_service_contexts</code>下，并由servicemanager在启动时加载（与平台service_contexts一起加载）</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>虽然servicemanager会在启动时查找此文件，但对于完全兼容的TREBLE设备，vendor_service_contexts绝不能存在。这是因为，vendor和system进程之间的所有交互都必须通过hwservicemanager/hwbinder发生</li>
    </ul>
  </li>
  <li>plat_hwservice_contexts</li>
  <li>
    <ul>
      <li>hwservicemanager的Android平台hwservice_context（没有设备专用标签）</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于system分区中的<code class="language-plaintext highlighter-rouge">/system/etc/selinux/plat_hwservice_contexts</code>下，并由hwservicemanager在启动时加载（与vendor_hwservice_contexts一起加载）</li>
    </ul>
  </li>
  <li>vendor_hwservice_contexts</li>
  <li>
    <ul>
      <li>设备专用hwservice_context，通过合并hwservice_contexts（位于设备的Boardconfig.mk文件中由BOARD_SEPOLICY_DIRS指向的目录下）进行构建</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于vendor分区中的<code class="language-plaintext highlighter-rouge">/vendor/etc/selinux/vendor_hwservice_contexts</code>下，并由hwservicemanager在启动时加载（与plat_service_contexts一起加载）</li>
    </ul>
  </li>
  <li>vndservice_contexts</li>
  <li>
    <ul>
      <li>vndservicemanager的设备专用service_context，通过合并vndservice_contexts（位于设备的Boardconfig.mk中由BOARD_SEPOLICY_DIRS指向的目录下）进行构建</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>此文件必须位于vendor分区中的<code class="language-plaintext highlighter-rouge">/vendor/etc/selinux/vndservice_contexts</code>下，并由vndservicemanager在启动时加载</li>
    </ul>
  </li>
</ul>

<hr />

<h4 id="2214-seapp-上下文seapp_contexts">2.2.1.4. Seapp 上下文seapp_contexts</h4>

<p><strong>在Android8.0中，seapp_contexts拆分成了两个文件：</strong></p>

<ul>
  <li>plat_seapp_contexts</li>
  <li>
    <ul>
      <li>没有设备专用更改的Android平台seapp_context</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于system分区中的<code class="language-plaintext highlighter-rouge">/system/etc/selinux/plat_seapp_contexts.</code>下</li>
    </ul>
  </li>
  <li>vendor_seapp_contexts</li>
  <li>
    <ul>
      <li>平台seapp_context的设备专用扩展，通过合并seapp_contexts（位于设备的Boardconfig.mk文件中由<code class="language-plaintext highlighter-rouge">BOARD_SEPOLICY_DIRS</code>指向的目录下）进行构建</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于vendor分区中的<code class="language-plaintext highlighter-rouge">/vendor/etc/selinux/vendor_seapp_contexts</code>下</li>
    </ul>
  </li>
</ul>

<hr />

<h4 id="2215-mac权限mac_permissions">2.2.1.5. MAC权限mac_permissions</h4>

<p><strong>在Android8.0中，mac_permissions.xml拆分成了两个文件：</strong></p>

<ul>
  <li>平台mac_permissions.xml</li>
  <li>
    <ul>
      <li>没有设备专用更改的Android平台mac_permissions.xml。</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于system分区中的<code class="language-plaintext highlighter-rouge">/system/etc/selinux/.</code>下。</li>
    </ul>
  </li>
  <li>非平台mac_permissions.xml</li>
  <li>
    <ul>
      <li>平台mac_permissions.xml的设备专用扩展，通过mac_permissions.xml（位于设备的Boardconfig.mk文件中由BOARD_SEPOLICY_DIRS指向的目录下）进行构建</li>
    </ul>
  </li>
  <li>
    <ul>
      <li>必须位于vendor分区中的<code class="language-plaintext highlighter-rouge">/vendor/etc/selinux/.</code>下</li>
    </ul>
  </li>
</ul>

<hr />

<h3 id="222-boardconfigmk-makefile引用">2.2.2. BoardConfig.mk makefile引用</h3>

<blockquote>
  <p>修改或添加政策文件和上下文的描述文件后，需要更新<code class="language-plaintext highlighter-rouge">/device/manufacturer/device-name/BoardConfig.mk</code>makefile以引用sepolicy子目录和每个新的政策文件</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 引用目录</span>
BOARD_SEPOLICY_DIRS +<span class="o">=</span> <span class="se">\</span>
        &lt;root&gt;/device/manufacturer/device-name/sepolicy

<span class="c"># 单独引用</span>
BOARD_SEPOLICY_UNION +<span class="o">=</span> <span class="se">\</span>
        genfs_contexts <span class="se">\</span>
        file_contexts <span class="se">\</span>
        sepolicy.te
</code></pre></div></div>

<hr />

<h2 id="23-实现步骤">2.3. 实现步骤</h2>

<h3 id="231-内核启用selinux">2.3.1. 内核启用SElinux</h3>

<p>配置<code class="language-plaintext highlighter-rouge">CONFIG_SECURITY_SELINUX=y</code>（例如kernel/msm-5.4/kernel/configs/android-base.config）</p>

<h3 id="232-更改kernel_cmdline参数配置修改为permissive">2.3.2. 更改kernel_cmdline参数（配置修改为permissive）</h3>

<p>配置<code class="language-plaintext highlighter-rouge">BOARD_KERNEL_CMDLINE := androidboot.selinux=permissive</code></p>

<p><strong>仅适用于初始制定设备政策的情况。在拥有初始引导程序政策后，请移除此参数，以便将设备恢复强制模式，否则设备将无法通过CTS验证</strong></p>

<h3 id="233-宽容模式启动系统查看所需权限">2.3.3. 宽容模式启动系统查看所需权限</h3>

<ul>
  <li>在Ubuntu 14.04或更高版本中，请运行以下命令：</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">adb shell su -c dmesg | grep denied | audit2allow -p out/target/product/BOARD/root/sepolicy</code></p>

<ul>
  <li>在 Ubuntu 12.04 中，请运行以下命令：</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb pull /sys/fs/selinux/policy
adb logcat <span class="nt">-b</span> all | audit2allow <span class="nt">-p</span> policy
</code></pre></div></div>

<hr />

<h3 id="234-评估警告的输出">2.3.4. 评估警告的输出</h3>

<p>评估与以下内容类似的警告的输出：<code class="language-plaintext highlighter-rouge">init: Warning! Service name needs a SELinux domain defined; please fix!</code></p>

<h3 id="235-标识设备以及需要添加标签的其他新文件">2.3.5. 标识设备以及需要添加标签的其他新文件</h3>

<h3 id="236-配置对象使用现有标签或新标签">2.3.6. 配置对象使用现有标签或新标签</h3>

<p>查看<code class="language-plaintext highlighter-rouge">*_contexts</code>文件，了解之前是如何为内容添加标签的，然后根据对标签含义的了解分配一个新标签。</p>

<p>这个标签最好是能够融入到政策中的现有标签，但有时也需要使用新标签，而且还需要提供关于访问该标签的规则。将标签添加到相应的上下文的描述文件中</p>

<h3 id="237-标识应该拥有自己的安全域的域进程">2.3.7. 标识应该拥有自己的安全域的域/进程</h3>

<p>可能需要为每一项分别编写一个全新的政策。例如，从<code class="language-plaintext highlighter-rouge">init</code>衍生的所有服务都应该有自己的安全域。以下命令有助于查看保持运行的服务（不过所有服务都需要如此处理）：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell su <span class="nt">-c</span> ps <span class="nt">-Z</span> | <span class="nb">grep </span>init
adb shell su <span class="nt">-c</span> dmesg | <span class="nb">grep</span> <span class="s1">'avc: '</span>
</code></pre></div></div>

<hr />

<h3 id="238-查看initdevicerc发现没有域类型的域">2.3.8. 查看init.device.rc发现没有域类型的域</h3>

<p>在开发过程早期为其提供相应的域，以避免向init添加规则或将init访问权限与其自身政策中的访问权限混淆</p>

<p><strong>通常会为指定的项目新建定义rc文件作一些操作，比如chmod、chown权限赋予、创建文件夹、启动service、重载selinux权限等等</strong></p>

<hr />

<h3 id="239-设置board_configmk">2.3.9. 设置BOARD_CONFIG.mk</h3>

<p>设置BOARD_CONFIG.mk以使用<code class="language-plaintext highlighter-rouge">BOARD_SEPOLICY_*</code>变量（通常不需要修改）</p>

<h3 id="2310-检查initdevicerc和fstabdevice文件">2.3.10. 检查init.device.rc和fstab.device文件</h3>

<p>确保每一次使用mount都对应一个添加了适当标签的文件系统，或者指定了<code class="language-plaintext highlighter-rouge">context= mount</code>选项</p>

<hr />

<h3 id="2311-查看每个拒绝事件">2.3.11. 查看每个拒绝事件</h3>

<p>查看每个拒绝事件，并创建SELinux政策来妥善处理每个拒绝事件</p>

<p>实际进行权限修复修改</p>

<hr />

<h1 id="3-自定义selinux">3. 自定义SELinux</h1>

<blockquote>
  <p>集成基本级别的SELinux功能并全面分析结果后，可以添加自己的政策设置，以便涵盖对Android操作系统所做的自定义。这些政策必须仍然满足Android兼容性计划的要求，并且不得移除默认的 SELinux设置</p>
</blockquote>

<blockquote>
  <p>制造商不得移除现有的SELinux政策，否则可能会破坏Android SELinux的实施方式及其管控的应用。这包括可能需要改进以遵守政策并正常运行的第三方应用。应用必须无需任何修改即可继续在启用了SELinux的设备上正常运行</p>
</blockquote>

<h2 id="31-注意点">3.1. 注意点</h2>

<p><strong>当开始自定义SELinux时，需注意：</strong></p>

<ul>
  <li>为所有新的守护进程编写SELinux政策</li>
  <li>尽可能使用预定义的域</li>
  <li>为作为init服务衍生的所有进程分配域</li>
  <li>在编写政策之前先熟悉相关的宏</li>
  <li>
    <p>向AOSP提交对核心政策进行的更改</p>
  </li>
  <li>不得创建不兼容的政策</li>
  <li>不得允许对最终用户政策进行自定义</li>
  <li>不得允许对移动设备管理 (MDM) 政策进行自定义</li>
  <li>不得恐吓违反政策的用户</li>
  <li>不得添加后门程序</li>
</ul>

<h2 id="32-操作步骤">3.2. 操作步骤</h2>

<p>如果要自定义SELinux设置，则应格外谨慎，以免破坏现有应用。要开始使用，请按下列步骤操作：</p>

<ol>
  <li>使用最新的Android内核</li>
  <li>采用最小权限原则</li>
  <li>仅针对Android需要添加的内容调整SELinux政策。默认政策能够自动适用于Android开源项目代码库</li>
  <li><strong>将各个软件组件拆分成多个负责执行单项任务的模块</strong>（按模块、架构合理有效的划分sepolicy的配置）</li>
  <li>创建将这些任务与无关功能隔离开来的 SELinux 政策</li>
  <li>将这些政策放在<code class="language-plaintext highlighter-rouge">/device/manufacturer/device-name/sepolicy</code>目录中的<code class="language-plaintext highlighter-rouge">*.te</code>文件内（<code class="language-plaintext highlighter-rouge">te</code>是SELinux政策源代码文件使用的扩展名），然后使用<code class="language-plaintext highlighter-rouge">BOARD_SEPOLICY</code>变量将它们纳入到的build编译中</li>
  <li>先将新域设为宽容域。为此，可以在该域的<code class="language-plaintext highlighter-rouge">.te</code>文件中使用宽容声明（调试手法）</li>
  <li>分析结果并优化域定义</li>
  <li>当userdebug版本中不再出现拒绝事件时，移除宽容声明（<strong>将模式从宽容模式切换成强制模式</strong>）</li>
</ol>

<hr />

<h2 id="33-声明宏编写示例">3.3. 声明宏编写示例</h2>

<blockquote>
  <p>SELinux基于M4计算机语言，因此支持多种有助于节省时间的宏。</p>
</blockquote>

<p>在以下示例中，所有域都被授予向<code class="language-plaintext highlighter-rouge">/dev/null</code>读写数据(write)的权限以及从<code class="language-plaintext highlighter-rouge">/dev/zero</code>读取数据(read)的权限</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Allow read / write access to /dev/null</span>
allow domain null_device:chr_file <span class="o">{</span> getattr open <span class="nb">read </span>ioctl lock append write<span class="o">}</span><span class="p">;</span>

<span class="c"># Allow read-only access to /dev/zero</span>
allow domain zero_device:chr_file <span class="o">{</span> getattr open <span class="nb">read </span>ioctl lock <span class="o">}</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>从<code class="language-plaintext highlighter-rouge">system/sepolicy/public/global_macros</code>看到宏定义：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>define<span class="o">(</span><span class="sb">`</span>x_file_perms<span class="s1">', `{ getattr execute execute_no_trans map }'</span><span class="o">)</span>
define<span class="o">(</span><span class="sb">`</span>r_file_perms<span class="s1">', `{ getattr open read ioctl lock map watch watch_reads }'</span><span class="o">)</span>
define<span class="o">(</span><span class="sb">`</span>w_file_perms<span class="s1">', `{ open append write lock map }'</span><span class="o">)</span>
define<span class="o">(</span><span class="sb">`</span>rx_file_perms<span class="s1">', `{ r_file_perms x_file_perms }'</span><span class="o">)</span>
define<span class="o">(</span><span class="sb">`</span>ra_file_perms<span class="s1">', `{ r_file_perms append }'</span><span class="o">)</span>
define<span class="o">(</span><span class="sb">`</span>rw_file_perms<span class="s1">', `{ r_file_perms w_file_perms }'</span><span class="o">)</span>
define<span class="o">(</span><span class="sb">`</span>rwx_file_perms<span class="s1">', `{ rw_file_perms x_file_perms }'</span><span class="o">)</span>
</code></pre></div></div>

<p><strong>可以使用宏编写替代，简短的编写权限：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Allow read / write access to /dev/null</span>
allow domain null_device:chr_file rw_file_perms<span class="p">;</span>

<span class="c"># Allow read-only access to /dev/zero</span>
allow domain zero_device:chr_file r_file_perms<span class="p">;</span>
</code></pre></div></div>

<hr />

<h2 id="34-自定义权限示例及分析">3.4. 自定义权限示例及分析</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 类型声明，声明DHCP守护进程将沿用基本的安全政策 (domain)</span>
<span class="nb">type </span>dhcp, domain<span class="p">;</span>
<span class="c"># DHCP 被声明为宽容域（后面调试成功后需去除）</span>
permissive dhcp<span class="p">;</span>
<span class="nb">type </span>dhcp_exec, exec_type, file_type<span class="p">;</span>
<span class="nb">type </span>dhcp_data_file, file_type, data_file_type<span class="p">;</span>
<span class="c"># 该政策声明DHCP是从init衍生而来的，并且可以与其通信</span>
init_daemon_domain<span class="o">(</span>dhcp<span class="o">)</span>
<span class="c"># 该政策允许DHCP使用net域中的常用网络功能，例如读取和写入TCP数据包、通过套接字进行通信，以及执行DNS请求</span>
net_domain<span class="o">(</span>dhcp<span class="o">)</span>

......
<span class="c"># For /proc/sys/net/ipv4/conf/*/promote_secondaries</span>
<span class="c"># 该政策声明DHCP可以向/proc中的特定文件写入数据。这一行显示了SELinux的详细文件标签</span>
<span class="c"># 它使用proc_net标签来限定DHCP仅对/proc/sys/net中的文件具有写入权限</span>
allow dhcp proc_net:file write<span class="p">;</span>
......
<span class="c"># 描述了允许应用之间如何交互。</span>
<span class="c"># 该政策声明DHCP和netd之间可通过文件描述符、FIFO 文件、数据报套接字以及UNIX信息流套接字进行通信。</span>
<span class="c"># DHCP只能向数据报套接字和UNIX信息流套接字中读写数据，但不能创建或打开此类套接字</span>
allow dhcp netd:fd use<span class="p">;</span>
allow dhcp netd:fifo_file rw_file_perms<span class="p">;</span>
allow dhcp netd:<span class="o">{</span> dgram_socket_class_set unix_stream_socket <span class="o">}</span> <span class="o">{</span> <span class="nb">read </span>write <span class="o">}</span><span class="p">;</span>
allow dhcp netd:<span class="o">{</span> netlink_kobject_uevent_socket netlink_route_socket
netlink_nflog_socket <span class="o">}</span> <span class="o">{</span> <span class="nb">read </span>write <span class="o">}</span><span class="p">;</span>
</code></pre></div></div>

<hr />

<h2 id="35-常用可用权限列表">3.5. 常用可用权限列表</h2>

<p><img src="../../assets/post/2022/2022-11-14-android-selinux/selinux-1.png" alt="" /></p>

<hr />

<h2 id="36-android-80及更高版本selinux存放位置变更">3.6. Android 8.0及更高版本SElinux存放位置变更</h2>

<ul>
  <li>在Android 7.0及更低版本中，设备制造商可以将政策添加到<code class="language-plaintext highlighter-rouge">BOARD_SEPOLICY_DIRS</code>，包括用来在不同设备类型之间增强AOSP政策的政策</li>
  <li>在Android 8.0及更高版本中，将政策添加到<code class="language-plaintext highlighter-rouge">BOARD_SEPOLICY_DIRS</code>会将该政策仅存放在供应商映像中</li>
</ul>

<p><strong>在Android 8.0及更高版本中，政策位于AOSP中的以下位置：</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">system/sepolicy/public</code>:其中包括所导出的用于供应商特定政策的政策。所有内容都会纳入Android 8.0兼容性基础架构。公共政策会保留在不同版本上，因此可以在自定义政策的<code class="language-plaintext highlighter-rouge">/public</code>中添加任何内容。正因如此，可存放在<code class="language-plaintext highlighter-rouge">/public</code>中的政策类型的限制性更强。将此目录视为相应平台的已导出政策API：<code class="language-plaintext highlighter-rouge">处理/system与/vendor之间的接口的所有内容都位于这里</code></li>
  <li><code class="language-plaintext highlighter-rouge">system/sepolicy/private</code>:包括系统映像正常运行所必需（但供应商映像政策应该不知道）的政策</li>
  <li><code class="language-plaintext highlighter-rouge">system/sepolicy/vendor</code>:包括位于<code class="language-plaintext highlighter-rouge">/vendor</code>但存在于核心平台树（非设备特定目录）中的组件的相关政策。这是构建系统区分设备和全局组件的软件工件；从概念上讲，这是下述设备专用政策的一部分</li>
  <li><code class="language-plaintext highlighter-rouge">device/manufacturer/device-name/sepolicy</code>:包含设备专用政策，以及对政策进行的设备自定义（在Android 8.0及更高版本中，该政策对应于供应商映像组件的相关政策）</li>
</ul>

<p>在Android 11及更高版本中，<code class="language-plaintext highlighter-rouge">system_ext</code>和<code class="language-plaintext highlighter-rouge">product</code>分区还可以包含特定于分区的政策。<code class="language-plaintext highlighter-rouge">system_ext</code>和<code class="language-plaintext highlighter-rouge">product</code>政策也分为公共政策和私有政策，且供应商可以使用<code class="language-plaintext highlighter-rouge">system_ext</code>和<code class="language-plaintext highlighter-rouge">product</code>的公共政策（例如系统政策）</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS</code>:包括所导出的用于供应商特定政策的政策。已安装到system_ext分区（<strong>编译结果会存在system_ext/etc/selinux</strong>）</li>
  <li><code class="language-plaintext highlighter-rouge">SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS</code>:包括<code class="language-plaintext highlighter-rouge">system_ext</code>映像正常运行所必需（但供应商映像政策应该不知道）的政策。已安装到 system_ext分区（<strong>编译结果会存在system_ext/etc/selinux</strong>）</li>
  <li><code class="language-plaintext highlighter-rouge">PRODUCT_PUBLIC_SEPOLICY_DIRS</code>:包括所导出的用于供应商特定政策的政策。已安装到product分区（<strong>主要针对packages/services的权限，编译结果在system/product/etc/selinux</strong>）</li>
  <li><code class="language-plaintext highlighter-rouge">PRODUCT_PRIVATE_SEPOLICY_DIRS</code>:包括product映像正常运行所必需（但供应商映像政策应该不知道）的政策。已安装到product分区</li>
</ul>

<p><strong>注意：</strong>在使用system_ext和product公共政策时要格外小心。公共政策充当system_ext/product和vendor之间的导出API。合作伙伴应自行管理兼容性问题</p>

<hr />

<h1 id="4-selinux存放位置">4. SElinux存放位置</h1>

<p><img src="../../assets/post/2022/2022-11-14-android-selinux/selinux-2.png" alt="" /></p>

<p>构建系统采用此策略并在相应分区上生成system、system_ext、product、vendor和odm策略组件。步骤包括：</p>
<ol>
  <li>将策略转换成SELinux通用中间语言（CIL）格式，具体包含：
    <ul>
      <li>公共平台册策略（system+system_ext+product）</li>
      <li>私人+公共政策相结合</li>
      <li>公共+供应商和BOARD_SEPOLICY_DIRS政策</li>
    </ul>
  </li>
  <li>将公开提供的政策作为供应商政策的一部分进行版本控制。为此，使用生成的公共CIL政策向<code class="language-plaintext highlighter-rouge">公共+供应商+BOARD_SEPOLICY_DIRS</code>组合政策指明必须将哪些部分转换为将与平台政策相关联的属性</li>
  <li>创建将平台和供应商部分关联在一起的映射文件。最初，该文件只是将公共政策中的类型与供应商政策中对应的属性相关联；之后，该文件还为未来的平台版本中维护的文件提供依据，从而兼容以此平台版本作为目标版本的供应商政策</li>
  <li>合并政策文件（描述设备解决方案和预编译解决方案）
    <ul>
      <li>合并映射政策、平台政策和供应商政策</li>
      <li>编译输出二进制政策文件</li>
    </ul>
  </li>
</ol>

<hr />

<h1 id="5-特殊分区概念和权限">5. 特殊分区概念和权限</h1>

<blockquote>
  <p>在Android 11及更高版本中，<code class="language-plaintext highlighter-rouge">system_ext</code>和<code class="language-plaintext highlighter-rouge">product</code>分区还可以包含特定于分区的政策。宏也会变更，替换原来的<code class="language-plaintext highlighter-rouge">BOARD_PLAT_PUBLIC_SEPOLICY_DIR</code>和<code class="language-plaintext highlighter-rouge">BOARD_PLAT_PRIVATE_SEPOLICY_DIR</code></p>
</blockquote>

<p>查看Android 12源码<code class="language-plaintext highlighter-rouge">/system/sepolicy/README</code>的说明：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SYSTEM_EXT_PUBLIC_SEPOLICY_DIRS +<span class="o">=</span> device/acme/roadrunner-sepolicy/systemext/public
SYSTEM_EXT_PRIVATE_SEPOLICY_DIRS +<span class="o">=</span> device/acme/roadrunner-sepolicy/systemext/private
PRODUCT_PUBLIC_SEPOLICY_DIRS +<span class="o">=</span> device/acme/roadrunner-sepolicy/product/public
PRODUCT_PRIVATE_SEPOLICY_DIRS +<span class="o">=</span> device/acme/roadrunner-sepolicy/product/private

The old BOARD_PLAT_PUBLIC_SEPOLICY_DIR and BOARD_PLAT_PRIVATE_SEPOLICY_DIR
variables have been deprecated <span class="k">in </span>favour of SYSTEM_EXT_<span class="k">*</span><span class="nb">.</span>
</code></pre></div></div>

<blockquote>
  <p>从Android 11开始，system_ext和product分区可以将其指定的公共类型导出到vendor分区。</p>
</blockquote>

<p>当system_ext和product分区基于同一平台版本N时，构建系统会生成到<code class="language-plaintext highlighter-rouge">system_ext/etc/selinux/mapping/N.cil</code>和<code class="language-plaintext highlighter-rouge">product/etc/selinux/mapping/N.cil</code>的基本映射文件，其中包含从type到type_N的身份映射。vendor可以通过版本化属性type_N访问type</p>

<hr />

<h2 id="51-product分区概念">5.1. product分区概念</h2>

<ul>
  <li>Android 11上，编译结果目录根目录有个product，但是里面除了etc没有其他文件，不会生成镜像；在system也有product，里面存在实际结果文件（含build.prop）</li>
  <li>Android 12上，编译结果目录根目录没有product；在system也有product，里面存在实际结果文件（不包含build.prop）</li>
</ul>

<p><strong>使用以下编译标记向 product 分区中安装模块：</strong></p>
<ul>
  <li>Android.bp 中的 product_specific: true</li>
  <li>Android.mk 中的 LOCAL_PRODUCT_MODULE := true</li>
</ul>

<h2 id="52-system_ext分区">5.2. system_ext分区</h2>

<ul>
  <li>Android 11上（未开启动态分区），编译结果目录根目录有个system_ext，但是里面除了etc没有其他文件，不会生成镜像；在system也有system_ext，里面存在实际结果文件（含build.prop）</li>
  <li>Android 12上（开启动态分区），编译结果目录根目录有system_ext，并且会生成system_ext.img镜像，但是分区大小配置是同system一起；在system也有system_ext，但是是软链接链接到根目录的system_ext</li>
</ul>

<p><strong>使用以下编译标记向system_ext分区中安装模块：</strong>（宏的源码参阅<code class="language-plaintext highlighter-rouge">build\soong\androidmk\androidmk\android.go</code>）</p>
<ul>
  <li>Android.bp中配置 system_ext_specific: true，就将编译到 system\system_ext\ 中</li>
  <li>Android.mk 中配置 LOCAL_SYSTEM_EXT_MODULE := true，就将编译到 system\system_ext\ 中；<code class="language-plaintext highlighter-rouge">LOCAL_PRIVILEGED_MODULE := true</code> 决定在 priv-app 文件中，不加<code class="language-plaintext highlighter-rouge"> LOCAL_PRIVILEGED_MODULE</code> 则编译到 system\system_ext\app\</li>
</ul>

<hr />

<h1 id="6-selinux验证调试方法">6. SElinux验证调试方法</h1>

<h2 id="61-读取avc-denied拒绝事件">6.1. 读取avc denied拒绝事件</h2>

<blockquote>
  <p>检查是否有错误，错误会以事件日志的形式传给dmesg和logcat，并可在设备上从本地查看。</p>

  <p>制造商应先检查这些设备上传给dmesg的SELinux输出并优化设置，然后再在宽容模式下公开发布，最后切换到强制模式。</p>

  <p>SELinux日志消息中包含“avc:”字样，因此可使用grep找到。可以通过运行<code class="language-plaintext highlighter-rouge">cat/proc/kmsg</code>来获取当前的拒绝事件日志，也可以通过运行<code class="language-plaintext highlighter-rouge">cat/sys/fs/pstore/console-ramoops</code>来获取上次启动时的拒绝事件日志。</p>
</blockquote>

<p>根据这些输出内容，制造商可以轻松发现系统用户或组件违反SELinux政策的行为。然后，制造商便可更改相应软件和/或SELinux政策，以防范此类恶意行为。</p>

<h3 id="611-avc示例一">6.1.1. avc示例一</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>avc: denied  <span class="o">{</span> connectto <span class="o">}</span> <span class="k">for  </span><span class="nv">pid</span><span class="o">=</span>2671 <span class="nb">comm</span><span class="o">=</span><span class="s2">"ping"</span> <span class="nv">path</span><span class="o">=</span><span class="s2">"/dev/socket/dnsproxyd"</span>
<span class="nv">scontext</span><span class="o">=</span>u:r:shell:s0 <span class="nv">tcontext</span><span class="o">=</span>u:r:netd:s0 <span class="nv">tclass</span><span class="o">=</span>unix_stream_socket
</code></pre></div></div>

<p><strong>解读如下：</strong></p>

<ul>
  <li>上方的<code class="language-plaintext highlighter-rouge">{connectto}</code>表示执行的操作。根据它和末尾的<code class="language-plaintext highlighter-rouge">tclass(unix_stream_socket)</code>，可以大致了解是对什么对象执行什么操作。在此例中，是操作方正在试图连接到UNIX信息流套接字</li>
  <li><code class="language-plaintext highlighter-rouge">scontext(u:r:shell:s0)</code>表示发起相应操作的环境，在此例中是shell中运行的某个程序</li>
  <li><code class="language-plaintext highlighter-rouge">tcontext(u:r:netd:s0)</code>表示操作目标的环境，在此例中是归netd所有的某个<code class="language-plaintext highlighter-rouge">unix_stream_socket</code></li>
  <li>顶部的<code class="language-plaintext highlighter-rouge">comm="ping"</code>可了解拒绝事件发生时正在运行的程序。在此示例中，给出的信息非常清晰明了</li>
</ul>

<hr />

<h3 id="612-avc示例二">6.1.2. avc示例二</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;5&gt; <span class="nb">type</span><span class="o">=</span>1400 audit: avc:  denied  <span class="o">{</span> <span class="nb">read </span>write <span class="o">}</span> <span class="k">for  </span><span class="nv">pid</span><span class="o">=</span>177
<span class="nb">comm</span><span class="o">=</span><span class="s2">"rmt_storage"</span> <span class="nv">name</span><span class="o">=</span><span class="s2">"mem"</span> <span class="nv">dev</span><span class="o">=</span><span class="s2">"tmpfs"</span> <span class="nv">ino</span><span class="o">=</span>6004 <span class="nv">scontext</span><span class="o">=</span>u:r:rmt:s0
<span class="nv">tcontext</span><span class="o">=</span>u:object_r:kmem_device:s0 <span class="nv">tclass</span><span class="o">=</span>chr_file
</code></pre></div></div>

<p><strong>以下是此拒绝事件的关键元素：</strong></p>

<ul>
  <li>操作 - 试图进行的操作会使用括号突出显示：read write 或 setenforce</li>
  <li>操作方 - scontext（来源环境）条目表示操作方；在此例中为 rmt_storage 守护程序</li>
  <li>对象 - tcontext（目标环境）条目表示对哪个对象执行操作；在此例中为 kmem</li>
  <li>结果 - tclass（目标类别）条目表示操作对象的类型；在此例中为 chr_file（字符设备）</li>
</ul>

<hr />

<h2 id="62-转储用户和内核堆栈">6.2. 转储用户和内核堆栈</h2>

<blockquote>
  <p>在某些情况下，事件日志中包含的信息不足以查明拒绝事件的来源。通常，获取调用链（包括内核和用户空间）有助于更好地了解发生拒绝事件的原因。</p>

  <p>最新的内核定义了一个名为 avc:selinux_audited 的跟踪点。使用<code class="language-plaintext highlighter-rouge">Android simpleperf</code>可启用此跟踪点并获取调用链。</p>
</blockquote>

<p><strong>支持的配置：</strong></p>
<ul>
  <li>支持5.10及更高版本的Linux内核（尤其是Android通用内核分支<code class="language-plaintext highlighter-rouge">mainline</code>和<code class="language-plaintext highlighter-rouge">android12-5.10</code>），也支持android12-5.4分支。可以使用simpleperf来确定您的设备上是否定义了跟踪点：<code class="language-plaintext highlighter-rouge">adb root &amp;&amp; adb shell simpleperflist|grep avc:selinux_audited</code></li>
  <li>应该可以重现正在调试的事件。使用Simpleperf时不支持启动时间事件；不过，您仍然可以重启服务以触发事件</li>
</ul>

<hr />

<h3 id="621-simpleperf抓取调用堆栈链">6.2.1. simpleperf抓取调用堆栈链</h3>

<blockquote>
  <p>调用链是一个统一的内核和用户空间调用链，可发起跟踪从用户空间直到内核中发生拒绝事件的位置，更好地查看代码流</p>
</blockquote>

<p><strong>获取调用链：</strong></p>

<p>第一步是使用<code class="language-plaintext highlighter-rouge">simpleperf record</code>录制事件：<code class="language-plaintext highlighter-rouge">adb shell -t "cd /data/local/tmp &amp;&amp; su root simpleperf record -a -g -e avc:selinux_audited"</code></p>

<p>第二步应触发导致拒绝事件的事件。之后，应停止录制。在此例中应使用<code class="language-plaintext highlighter-rouge">Ctrl-c</code>获取样本：<code class="language-plaintext highlighter-rouge">^Csimpleperf I cmd_record.cpp:751] Samples recorded: 1. Samples lost: 0.</code></p>

<p>最后，可使用<code class="language-plaintext highlighter-rouge">simpleperf report</code>检查获取的堆栈轨迹。 例如：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell <span class="nt">-t</span> <span class="s2">"cd /data/local/tmp &amp;&amp; su root simpleperf report -g --full-callgraph"</span>
<span class="o">[</span>...]
Children  Self     Command  Pid   Tid   Shared Object                                   Symbol
100.00%   0.00%    dmesg    3318  3318  /apex/com.android.runtime/lib64/bionic/libc.so  __libc_init
       |
       <span class="nt">--</span> __libc_init
          |
           <span class="nt">--</span> main
              toybox_main
              toy_exec_which
              dmesg_main
              klogctl
              entry_SYSCALL_64_after_hwframe
              do_syscall_64
              __x64_sys_syslog
              do_syslog
              selinux_syslog
              slow_avc_audit
              common_lsm_audit
              avc_audit_post_callback
              avc_audit_post_callback
</code></pre></div></div>

<hr />

<h2 id="63-切换宽容模式permissive">6.3. 切换宽容模式（permissive）</h2>

<blockquote>
  <p>生产设备不支持宽容模式。CTS 测试会确认是否已启用强制模式</p>

  <p>SELinux强制模式可以在<code class="language-plaintext highlighter-rouge">userdebug或eng build</code>中通过ADB停用。运行以下命令：</p>
</blockquote>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb root
<span class="c"># 获取模式</span>
adb shell getenforce
<span class="c"># 设置模式</span>
<span class="c"># 0--代表Permissive</span>
<span class="c"># 1--代表Enforcing</span>
adb shell setenforce 0

<span class="c"># 查看进程的sContext</span>
ps <span class="nt">-Z</span>

<span class="c"># 查看文件权限</span>
<span class="nb">ls</span> <span class="nt">-Z</span>
</code></pre></div></div>

<p>或在内核命令行中输入以下命令（适用于设备开发初期）：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>androidboot.selinux<span class="o">=</span>permissive
androidboot.selinux<span class="o">=</span>enforcing
</code></pre></div></div>

<p><strong>或使用Android 12中的bootconfig功能：</strong></p>

<p>···shell
androidboot.selinux=permissive
androidboot.selinux=enforcing
···</p>

<hr />

<h2 id="64-audit2allow自动生成权限工具">6.4. audit2allow自动生成权限工具</h2>

<blockquote>
  <p>audit2allow工具可以获取dmesg拒绝事件并将其转换成相应的SELinux政策声明。因此，该工具有助于大幅加快SELinux开发速度</p>

  <p>AOSP不再提供audit2allow。需要使用Linux发行版提供的软件包（Debian和Ubuntu上为<code class="language-plaintext highlighter-rouge">policycoreutils-python-utils</code>软件包）</p>
</blockquote>

<p>可以使用linux原生工具，将log输入到log.txt文件，然后执行：</p>

<p><code class="language-plaintext highlighter-rouge">cat log.txt | grep avc | audit2allow</code></p>

<p>或者</p>

<p><code class="language-plaintext highlighter-rouge">audit2allow -i log.txt</code></p>

<p><strong>例如：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat </span>log.txt 
avc: denied  <span class="o">{</span> connectto <span class="o">}</span> <span class="k">for  </span><span class="nv">pid</span><span class="o">=</span>2671 <span class="nb">comm</span><span class="o">=</span><span class="s2">"ping"</span> <span class="nv">path</span><span class="o">=</span><span class="s2">"/dev/socket/dnsproxyd"</span> <span class="nv">scontext</span><span class="o">=</span>u:r:shell:s0 <span class="nv">tcontext</span><span class="o">=</span>u:r:netd:s0 <span class="nv">tclass</span><span class="o">=</span>unix_stream_socket

<span class="nv">$ </span>audit2allow <span class="nt">-i</span> log.txt 
<span class="c">#============= shell ==============</span>
allow shell netd:unix_stream_socket connectto<span class="p">;</span>
</code></pre></div></div>

<hr />

<h3 id="641-实时日志生成权限">6.4.1. 实时日志生成权限</h3>

<p><strong>在设备运行时，执行以下命令：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb pull /sys/fs/selinux/policy
adb logcat <span class="nt">-b</span> events <span class="nt">-d</span> | audit2allow <span class="nt">-p</span> policy
</code></pre></div></div>

<p><strong>注意：</strong>运行这些命令不会更改bugreport.txt，因为所有日志都已经存在，包括上次重新启动之前存在的日志。在设备进行OTA更新或向设备刷入开发版系统时，新旧违规行为会混杂在一起，直到下一次重新启动为止。如需解决此问题，需重新启动设备，或者从错误报告中滤除<code class="language-plaintext highlighter-rouge">console-ramoops</code>和<code class="language-plaintext highlighter-rouge">LAST_LOGCAT</code></p>

<p><strong>例如本地测试执行结果：</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~/Documents/temp<span class="nv">$ </span>adb pull sys/fs/selinux/policy
sys/fs/selinux/policy: 1 file pulled, 0 skipped. 3.7 MB/s <span class="o">(</span>502496 bytes <span class="k">in </span>0.128s<span class="o">)</span>

~/Documents/temp<span class="nv">$ </span>adb logcat <span class="nt">-b</span> events <span class="nt">-d</span>|audit2allow <span class="nt">-p</span> policy 
<span class="c">#============= hal_vehicle_default ==============</span>
allow hal_vehicle_default sysfs:file getattr<span class="p">;</span>

<span class="c">#============= init-insmod-sh ==============</span>
allow init-insmod-sh debugfs:file setattr<span class="p">;</span>

<span class="c">#============= start_natp ==============</span>
allow start_natp unlabeled:dir search<span class="p">;</span>

<span class="c">#============= system_server ==============</span>
allow system_server idmap_exec:file execute<span class="p">;</span>

<span class="c">#============= webview_zygote ==============</span>
allow webview_zygote app_data_file:dir getattr<span class="p">;</span>
</code></pre></div></div>

<p><strong>不过，必须仔细审核要添加到政策中的条目，以免出现权限过宽的情况。</strong>
例如，如果将上面示例的<code class="language-plaintext highlighter-rouge">rmt_storage</code>拒绝事件输入到audit2allow中，会生成以下SELinux政策声明建议：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#============= shell ==============</span>
allow shell kernel:security setenforce<span class="p">;</span>
<span class="c">#============= rmt ==============</span>
allow rmt kmem_device:chr_file <span class="o">{</span> <span class="nb">read </span>write <span class="o">}</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>注意点：这会授予rmt向内核内存写入内容的权限，从而形成明显的安全漏洞。</strong>通常情况下，audit2allow给出的声明建议只是一个大致的基础。
在添加这些声明后，可能需要更改来源域和目标标签，并纳入适当的宏，才能实现良好的政策配置。有时，应对拒绝事件的合理方式不是更改政策，而是更改违规的应用。</p>

<hr />

<h2 id="65-新服务模块添加权限">6.5. 新服务模块添加权限</h2>

<blockquote>
  <p>通过init启动的服务需要在各自的SELinux域中运行。以下示例会将服务“foo”放入它自己的SELinux网域中并为其授予权限。</p>
</blockquote>

<p>该服务是在设备的<code class="language-plaintext highlighter-rouge">init.device.rc</code>文件中启动的，如下所示：</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service foo /system/bin/foo
    class core
</code></pre></div></div>

<p>1.创建一个新网域“foo”：创建包含以下内容的文件<code class="language-plaintext highlighter-rouge">device/manufacturer/device-name/sepolicy/foo.te</code>：</p>

<p><strong>这是foo SELinux网域的初始模板，可以根据该可执行文件执行的具体操作为该模板添加规则</strong></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># foo service</span>
<span class="nb">type </span>foo, domain<span class="p">;</span>
<span class="nb">type </span>foo_exec, exec_type, file_type<span class="p">;</span>

init_daemon_domain<span class="o">(</span>foo<span class="o">)</span>
</code></pre></div></div>

<p>2.为<code class="language-plaintext highlighter-rouge">/system/bin/foo</code>添加标签:将以下内容添加到<code class="language-plaintext highlighter-rouge">device/manufacturer/device-name/sepolicy/file_contexts：</code></p>

<p><strong>这可确保为该可执行文件添加适当的标签，以便SELinux在适当的网域中运行相应服务</strong></p>

<p><code class="language-plaintext highlighter-rouge">/system/bin/foo   u:object_r:foo_exec:s0</code></p>

<p>3.构建并刷写启动映像和系统映像。
4.优化相应域的SELinux规则：根据拒绝事件确定所需的权限。audit2allow工具提供了一些实用的指南，但该工具仅适用于提供编写政策时所需的信息。切勿只是复制输出内容。</p>

<hr />

<h2 id="66-授予dac_override权能">6.6. 授予dac_override权能</h2>

<blockquote>
  <p>dac_override拒绝事件意味着违规进程正在尝试使用错误的<code class="language-plaintext highlighter-rouge">unix user/group/world</code>权限访问某个文件。
正确的解决方案几乎从不授予dac_override权限，而是更改相应文件或进程的unix权限。有些网域（例如 init、vold 和 installd）确实需要能够替换 unix文件权限才能访问其他进程的文件。</p>
</blockquote>

<hr />

<h1 id="7-参考">7. 参考</h1>

<ul>
  <li><a href="https://source.android.google.cn/docs/security/features/selinux">Google官方文档SElinux-繁体</a></li>
  <li><a href="https://www.pianshen.com/article/6549296922/">程序员大本营Selinux</a></li>
  <li><a href="https://www.cnblogs.com/onelikeone/p/11556427.html">Android编译product分区</a></li>
  <li><a href="https://blog.csdn.net/u012932409/article/details/111322248">Android11(R)system_ext分区system_ext_specific属性</a></li>
  <li><a href="https://blog.csdn.net/jgw2008/article/details/123150350">Android SELinux 权限问题处理</a></li>
</ul>]]></content><author><name>sunwengang</name><email>1332963488@qq.com</email></author><category term="android" /><category term="selinux" /><summary type="html"><![CDATA[本篇主要讲述Android SELinux的基本概念，包含类型、属性、规则，Sepolicy的核心模块、关键文件，SELinux配置的步骤、调试验证方法等。]]></summary></entry></feed>