<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Ssk-wh&#39;s Blog</title>
  
  
  <link href="https://ssk-wh.github.io/atom.xml" rel="self"/>
  
  <link href="https://ssk-wh.github.io/"/>
  <updated>2025-08-05T09:31:31.000Z</updated>
  <id>https://ssk-wh.github.io/</id>
  
  <author>
    <name>ssk-wh</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Conan 的配置与使用</title>
    <link href="https://ssk-wh.github.io/2025/08297bee2f.html"/>
    <id>https://ssk-wh.github.io/2025/08297bee2f.html</id>
    <published>2025-08-01T00:00:00.000Z</published>
    <updated>2025-08-05T09:31:31.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>本文面向已经“听说过 Conan”的同学，帮你把“能跑起来”到“能上线”的每一步都踩实。官方文档很香，但太长；这篇博客很干，但够短。</p></blockquote><hr><blockquote><p><strong>官网指路</strong><br>Conan 官网：<a class="link"   href="https://conan.io/" >https://conan.io/<i class="fas fa-external-link-alt"></i></a><br>可用包速查：<a class="link"   href="https://conan.io/center" >https://conan.io/center<i class="fas fa-external-link-alt"></i></a><br>官方完整文档（建议完整阅读一遍）：<a class="link"   href="https://docs.conan.io/2/index.html" >https://docs.conan.io/2/index.html<i class="fas fa-external-link-alt"></i></a></p></blockquote><hr><h2 id="1-30-秒完成环境初始化"><a href="#1-30-秒完成环境初始化" class="headerlink" title="1. 30 秒完成环境初始化"></a>1. 30 秒完成环境初始化</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 装 Conan（Python3 自备）</span></span><br><span class="line">pip3 install <span class="string">&quot;conan&gt;=2.17.0&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 生成默认 profile（只跑一次）</span></span><br><span class="line">conan profile detect --force</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 拉依赖、编缺失</span></span><br><span class="line">conan install . --build=missing -s build_type=Release</span><br><span class="line"></span><br><span class="line"><span class="comment"># 4. 本地打包（可跳过）</span></span><br><span class="line">conan create . --user=myname</span><br><span class="line"></span><br><span class="line"><span class="comment"># 5. 推到私有仓库</span></span><br><span class="line">conan upload ffmpeg/5.0 -r myrepo</span><br></pre></td></tr></table></figure><hr><h2 id="2-先把项目摆成“Conan-喜欢的样子”"><a href="#2-先把项目摆成“Conan-喜欢的样子”" class="headerlink" title="2. 先把项目摆成“Conan 喜欢的样子”"></a>2. 先把项目摆成“Conan 喜欢的样子”</h2><p>以 CMake 工程为例，推荐目录：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">conan-fmt-demo/</span><br><span class="line">├── conanfile.txt       # 或 conanfile.py，二选一</span><br><span class="line">├── CMakeLists.txt</span><br><span class="line">└── main.cpp</span><br></pre></td></tr></table></figure><h3 id="最小可用-conanfile-txt"><a href="#最小可用-conanfile-txt" class="headerlink" title="最小可用 conanfile.txt"></a>最小可用 conanfile.txt</h3><figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">[requires]</span></span><br><span class="line"><span class="attr">fmt/10.1.1</span></span><br><span class="line"></span><br><span class="line"><span class="attr">[generators]</span></span><br><span class="line"><span class="attr">CMakeDeps</span></span><br><span class="line"><span class="attr">CMakeToolchain</span></span><br></pre></td></tr></table></figure><blockquote><p>说明  </p><ul><li><code>fmt/10.1.1</code>：告诉 Conan“我要这个版本”。  </li><li><code>CMakeDeps + CMakeToolchain</code>：自动生成 <code>fmt-config.cmake</code> 和 toolchain 文件，CMake 一句 <code>find_package(fmt)</code> 就能用。</li></ul></blockquote><p>想知道还有哪些版本？两条路：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">conan search <span class="built_in">fmt</span>                  <span class="comment"># 本地+远端一起搜</span></span><br><span class="line"><span class="comment"># 或者打开浏览器：https://conan.io/center</span></span><br></pre></td></tr></table></figure><hr><h2 id="3-一条命令安装全部依赖"><a href="#3-一条命令安装全部依赖" class="headerlink" title="3. 一条命令安装全部依赖"></a>3. 一条命令安装全部依赖</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conan install . --build=missing -s build_type=Release</span><br></pre></td></tr></table></figure><ul><li><code>--build=missing</code>：远端没有现成二进制就本地现编。  </li><li>成功后会在 <code>build/Release/generators/</code> 里吐出 <code>conan_toolchain.cmake</code>。</li></ul><hr><h2 id="4-编译项目：两种姿势，任选"><a href="#4-编译项目：两种姿势，任选" class="headerlink" title="4. 编译项目：两种姿势，任选"></a>4. 编译项目：两种姿势，任选</h2><h3 id="姿势-A：经典手写"><a href="#姿势-A：经典手写" class="headerlink" title="姿势 A：经典手写"></a>姿势 A：经典手写</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cmake -G <span class="string">&quot;Unix Makefiles&quot;</span> \</span><br><span class="line">      -DCMAKE_TOOLCHAIN_FILE=build/Release/generators/conan_toolchain.cmake \</span><br><span class="line">      -DCMAKE_BUILD_TYPE=Release \</span><br><span class="line">      -S . -B build/Release</span><br><span class="line">cmake --build build/Release -j$(<span class="built_in">nproc</span>)</span><br></pre></td></tr></table></figure><h3 id="姿势-B：CMake-≥3-19-的-preset（真香）"><a href="#姿势-B：CMake-≥3-19-的-preset（真香）" class="headerlink" title="姿势 B：CMake ≥3.19 的 preset（真香）"></a>姿势 B：CMake ≥3.19 的 preset（真香）</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cmake --preset conan-default</span><br><span class="line">cmake --build --preset conan-release</span><br></pre></td></tr></table></figure><blockquote><p>老版本 CMake 想用 preset？官方教你曲线救国：<a class="link"   href="https://docs.conan.io/2/tutorial/consuming_packages/use_tools_as_conan_packages.html" >https://docs.conan.io/2/tutorial/consuming_packages&#x2F;use_tools_as_conan_packages.html<i class="fas fa-external-link-alt"></i></a></p></blockquote><hr><h2 id="5-踩坑现场：缺失系统库怎么办？"><a href="#5-踩坑现场：缺失系统库怎么办？" class="headerlink" title="5. 踩坑现场：缺失系统库怎么办？"></a>5. 踩坑现场：缺失系统库怎么办？</h2><p>典型报错：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ERROR: vaapi/system … No package &#x27;libva&#x27; found</span><br></pre></td></tr></table></figure><p>原因：<code>vaapi/system</code> 这类 <code>system</code> 包<strong>不会</strong>帮你编 libva，它假设系统里已经装好。<br>解决（Ubuntu&#x2F;Debian 演示）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 装 apt-file 并更新索引</span></span><br><span class="line"><span class="built_in">sudo</span> apt update &amp;&amp; <span class="built_in">sudo</span> apt install apt-file</span><br><span class="line"><span class="built_in">sudo</span> apt-file update</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 找到提供 libva.pc 的包</span></span><br><span class="line">apt-file search libva.pc</span><br><span class="line"><span class="comment"># 输出：libva-dev: /usr/lib/.../libva.pc</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 装它</span></span><br><span class="line"><span class="built_in">sudo</span> apt install libva-dev</span><br></pre></td></tr></table></figure><p>如果缺失的不止一个，可以偷懒用脚本：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/env bash</span></span><br><span class="line"><span class="comment"># 暴力补齐任意 system 包缺失的 .pc 系统依赖</span></span><br><span class="line"><span class="comment"># Ubuntu/Debian 专用</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> -euo pipefail</span><br><span class="line"></span><br><span class="line"><span class="comment"># ======== 用户可编辑区域 ========</span></span><br><span class="line">SYSTEM_PKGS=(</span><br><span class="line">  xorg/system</span><br><span class="line">  vaapi/system</span><br><span class="line">  vdpau/system</span><br><span class="line">  </span><br><span class="line">  <span class="comment"># 继续往这里加其他 system 包</span></span><br><span class="line">)</span><br><span class="line"><span class="comment"># ================================</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== 开始暴力补齐 system 包缺失依赖 ===&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 确保 apt-file 存在且索引最新</span></span><br><span class="line"><span class="keyword">if</span> ! <span class="built_in">command</span> -v apt-file &amp;&gt;/dev/null; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">sudo</span> apt-get update &amp;&amp; <span class="built_in">sudo</span> apt-get install -y apt-file</span><br><span class="line">  <span class="built_in">sudo</span> apt-file update</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">declare</span> -A SEEN  <span class="comment"># 记录已处理过的 .pc，避免死循环</span></span><br><span class="line"></span><br><span class="line">pip3 install conan&gt;=2.17.0</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;检测本地编译工具&quot;</span></span><br><span class="line">conan profile detect --force</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> sys_pkg <span class="keyword">in</span> <span class="string">&quot;<span class="variable">$&#123;SYSTEM_PKGS[@]&#125;</span>&quot;</span>; <span class="keyword">do</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;🔍 检查 <span class="variable">$sys_pkg</span> ...&quot;</span></span><br><span class="line">  <span class="keyword">while</span> :; <span class="keyword">do</span></span><br><span class="line">    OUTPUT=$(<span class="built_in">mktemp</span>)</span><br><span class="line">    <span class="built_in">trap</span> <span class="string">&#x27;rm -f &quot;$OUTPUT&quot;&#x27;</span> EXIT</span><br><span class="line"></span><br><span class="line">    <span class="built_in">set</span> +e</span><br><span class="line">    conan install --requires=<span class="string">&quot;<span class="variable">$sys_pkg</span>&quot;</span> \</span><br><span class="line">        -c tools.system.package_manager:mode=check \</span><br><span class="line">        -c tools.system.package_manager:<span class="built_in">sudo</span>=False \</span><br><span class="line">        &gt;<span class="string">&quot;<span class="variable">$OUTPUT</span>&quot;</span> 2&gt;&amp;1</span><br><span class="line">    RET=$?</span><br><span class="line">    <span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 提取所有缺失的 .pc 名</span></span><br><span class="line">    <span class="built_in">mapfile</span> -t MISSING &lt; &lt;(grep -oP <span class="string">&quot;No package &#x27;\K[^&#x27;]+&quot;</span> <span class="string">&quot;<span class="variable">$OUTPUT</span>&quot;</span> | <span class="built_in">sort</span> -u)</span><br><span class="line"></span><br><span class="line">    [[ <span class="variable">$&#123;#MISSING[@]&#125;</span> -eq 0 ]] &amp;&amp; &#123; <span class="built_in">echo</span> <span class="string">&quot;✅ <span class="variable">$sys_pkg</span> 已满足&quot;</span>; <span class="built_in">break</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> pc <span class="keyword">in</span> <span class="string">&quot;<span class="variable">$&#123;MISSING[@]&#125;</span>&quot;</span>; <span class="keyword">do</span></span><br><span class="line">      [[ -n <span class="string">&quot;<span class="variable">$&#123;SEEN[$pc]:-&#125;</span>&quot;</span> ]] &amp;&amp; <span class="built_in">continue</span></span><br><span class="line">      SEEN[<span class="variable">$pc</span>]=1</span><br><span class="line"></span><br><span class="line">      <span class="built_in">echo</span> <span class="string">&quot;🔍 查找缺失 .pc：<span class="variable">$pc</span>.pc&quot;</span></span><br><span class="line">      PKG=$(apt-file search <span class="string">&quot;<span class="variable">$pc</span>.pc&quot;</span> | awk -F: <span class="string">&#x27;&#123;print $1&#125;&#x27;</span> | <span class="built_in">sort</span> -u)</span><br><span class="line"></span><br><span class="line">      <span class="keyword">case</span> $(<span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$PKG</span>&quot;</span> | <span class="built_in">wc</span> -l) <span class="keyword">in</span></span><br><span class="line">        0)</span><br><span class="line">          <span class="built_in">echo</span> <span class="string">&quot;⚠️  未找到包含 <span class="variable">$pc</span>.pc 的包，跳过&quot;</span></span><br><span class="line">          <span class="built_in">continue</span></span><br><span class="line">          ;;</span><br><span class="line">        1)</span><br><span class="line">          <span class="built_in">echo</span> <span class="string">&quot;📦 安装 <span class="variable">$PKG</span>&quot;</span></span><br><span class="line">          <span class="built_in">sudo</span> apt-get install -y <span class="string">&quot;<span class="variable">$PKG</span>&quot;</span></span><br><span class="line">          ;;</span><br><span class="line">        *)</span><br><span class="line">          <span class="built_in">echo</span> <span class="string">&quot;❓ 多个候选包：<span class="variable">$PKG</span>&quot;</span></span><br><span class="line">          <span class="built_in">echo</span> <span class="string">&quot;   请手动处理：sudo apt install &lt;包名&gt;&quot;</span></span><br><span class="line">          ;;</span><br><span class="line">      <span class="keyword">esac</span></span><br><span class="line">    <span class="keyword">done</span></span><br><span class="line">  <span class="keyword">done</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== 所有 system 包依赖已补齐 ===&quot;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>脚本会自动扫描 <code>xorg/system</code>、<code>vaapi/system</code>、<code>vdpau/system</code> 等常见 system 包，缺啥补啥，直到全部绿灯。</p><hr><h2 id="6-私有仓库：又快又稳"><a href="#6-私有仓库：又快又稳" class="headerlink" title="6. 私有仓库：又快又稳"></a>6. 私有仓库：又快又稳</h2><h3 id="6-1-添加仓库"><a href="#6-1-添加仓库" class="headerlink" title="6.1 添加仓库"></a>6.1 添加仓库</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conan remote add myrepo https://your-private-repo.com --index=0</span><br></pre></td></tr></table></figure><blockquote><p><code>--index=0</code> 把私有仓库置顶，优先级最高。<br>登录（仅上传时需要）：</p></blockquote><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conan remote login myrepo &lt;user&gt; -p &lt;pass&gt;</span><br></pre></td></tr></table></figure><h3 id="6-2-查看-调整顺序"><a href="#6-2-查看-调整顺序" class="headerlink" title="6.2 查看 &#x2F; 调整顺序"></a>6.2 查看 &#x2F; 调整顺序</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">conan remote list</span><br><span class="line">conan remote remove myrepo          <span class="comment"># 先删</span></span><br><span class="line">conan remote add myrepo &lt;url&gt; --index=0</span><br></pre></td></tr></table></figure><hr><h2 id="7-把包推上去"><a href="#7-把包推上去" class="headerlink" title="7. 把包推上去"></a>7. 把包推上去</h2><h3 id="场景-A：官方包直接迁到私有"><a href="#场景-A：官方包直接迁到私有" class="headerlink" title="场景 A：官方包直接迁到私有"></a>场景 A：官方包直接迁到私有</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">conan install --requires=ffmpeg/5.0@ --build=missing</span><br><span class="line">conan upload ffmpeg/5.0 -r myrepo --confirm</span><br></pre></td></tr></table></figure><h3 id="场景-B：自研项目"><a href="#场景-B：自研项目" class="headerlink" title="场景 B：自研项目"></a>场景 B：自研项目</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">conan create . --user=mychannel</span><br><span class="line">conan upload mylib/1.0@mychannel -r myrepo --confirm</span><br></pre></td></tr></table></figure><hr><h2 id="8-命令小抄（建议收藏）"><a href="#8-命令小抄（建议收藏）" class="headerlink" title="8. 命令小抄（建议收藏）"></a>8. 命令小抄（建议收藏）</h2><table><thead><tr><th>目的</th><th>命令</th></tr></thead><tbody><tr><td>查看本地缓存</td><td><code>conan list &#39;*&#39;</code></td></tr><tr><td>删除本地包</td><td><code>conan remove &lt;ref&gt;</code></td></tr><tr><td>搜索远端包</td><td><code>conan search &lt;pkg&gt; -r myrepo</code></td></tr><tr><td>查看 profile 路径</td><td><code>conan profile path default</code></td></tr><tr><td>编辑 profile</td><td><code>conan profile edit default</code></td></tr></tbody></table><hr><h2 id="9-结语"><a href="#9-结语" class="headerlink" title="9. 结语"></a>9. 结语</h2><ul><li>装好 Conan → <code>profile detect</code> → <code>conan install</code>，项目就跑起来了。  </li><li>缺系统库 → <code>apt-file</code> 搜 <code>-dev</code> 包，或者一键脚本。  </li><li>私有仓库 → <code>remote add --index=0</code> 置顶，上传一条命令搞定。</li></ul><p>祝你编译愉快，一路无坑！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;本文面向已经“听说过 Conan”的同学，帮你把“能跑起来”到“能上线”的每一步都踩实。官方文档很香，但太长；这篇博客很干，但够短。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;官网指路&lt;/stro</summary>
      
    
    
    
    <category term="Conan" scheme="https://ssk-wh.github.io/categories/Conan/"/>
    
    
    <category term="conan" scheme="https://ssk-wh.github.io/tags/conan/"/>
    
    <category term="cmake" scheme="https://ssk-wh.github.io/tags/cmake/"/>
    
    <category term="python" scheme="https://ssk-wh.github.io/tags/python/"/>
    
    <category term="python3" scheme="https://ssk-wh.github.io/tags/python3/"/>
    
  </entry>
  
  <entry>
    <title>FFmpeg开发指南：从核心概念到实战录制</title>
    <link href="https://ssk-wh.github.io/2025/0772985.html"/>
    <id>https://ssk-wh.github.io/2025/0772985.html</id>
    <published>2025-07-22T00:00:00.000Z</published>
    <updated>2025-07-23T01:49:52.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><strong>注意</strong>：本指南默认您已掌握C语言基础和Linux命令行操作。若不会使用<code>gcc</code>，建议先学习《C语言：从入门到内存泄漏》。</p></blockquote><hr><span id="more"></span><h2 id="一、FFmpeg简介：音视频界的瑞士军刀"><a href="#一、FFmpeg简介：音视频界的瑞士军刀" class="headerlink" title="一、FFmpeg简介：音视频界的瑞士军刀"></a>一、FFmpeg简介：音视频界的瑞士军刀</h2><p>FFmpeg集成了<strong>600+编解码器</strong>和<strong>100+封装格式</strong>的超级工具链：</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0772985/0.svg"                                        ></p><p><strong>核心组件功能</strong>：</p><ul><li><strong>libavcodec</strong>：编解码器大本营（H.264&#x2F;HEVC&#x2F;AAC&#x2F;MP3）</li><li><strong>libavformat</strong>：封装&#x2F;解封装指挥官（MP4&#x2F;FLV&#x2F;TS等）</li><li><strong>libavdevice</strong>：硬件设备特工（摄像头&#x2F;麦克风&#x2F;屏幕捕获）</li><li><strong>libswscale</strong>：像素魔术师（YUV↔RGB转换）</li><li><strong>libavutil</strong>：工具百宝箱（哈希&#x2F;数学&#x2F;日志）</li></ul><p>官网金句：<code>FFmpeg is the future, but you can type ffmpeg today</code></p><p>下面用「一句话 + 命令&#x2F;思路」的形式，把 FFmpeg 能做的「日常必备」到「高阶黑科技」一次性列给你。直接复制命令即可跑；带 <code>‼</code> 的表示进阶&#x2F;易踩坑。</p><hr><h3 id="1-常用操作（90-场景覆盖）"><a href="#1-常用操作（90-场景覆盖）" class="headerlink" title="1.常用操作（90% 场景覆盖）"></a>1.常用操作（90% 场景覆盖）</h3><table><thead><tr><th>目的</th><th>一句话示例</th></tr></thead><tbody><tr><td>查看媒体信息</td><td><code>ffprobe -hide_banner -i input.mp4</code></td></tr><tr><td>无损合并 ts 分段</td><td>&#96;ffmpeg -i “concat:1.ts</td></tr><tr><td>提取音频</td><td><code>ffmpeg -i in.mp4 -vn -c:a copy a.aac</code></td></tr><tr><td>提取视频</td><td><code>ffmpeg -i in.mp4 -an -c:v copy v.mp4</code></td></tr><tr><td>裁剪片段</td><td><code>ffmpeg -ss 00:01:00 -to 00:01:30 -i in.mp4 -c copy clip.mp4</code></td></tr><tr><td>转码 H.264 → H.265</td><td><code>ffmpeg -i in.mp4 -c:v libx265 -crf 28 -c:a copy out.mp4</code></td></tr><tr><td>调整分辨率</td><td><code>ffmpeg -i in.mp4 -vf scale=1280:-2 -c:a copy out.mp4</code></td></tr><tr><td>加水印</td><td><code>ffmpeg -i in.mp4 -i logo.png -filter_complex &quot;overlay=W-w-10:H-h-10&quot; out.mp4</code></td></tr><tr><td>GIF 转 MP4</td><td><code>ffmpeg -f gif -i anim.gif -pix_fmt yuv420p out.mp4</code></td></tr><tr><td>合并音视频</td><td><code>ffmpeg -i v.mp4 -i a.m4a -c copy va.mp4</code></td></tr></tbody></table><hr><h3 id="2-高级玩法（性能-画质-实时向）"><a href="#2-高级玩法（性能-画质-实时向）" class="headerlink" title="2. 高级玩法（性能&#x2F;画质&#x2F;实时向）"></a>2. 高级玩法（性能&#x2F;画质&#x2F;实时向）</h3><table><thead><tr><th>目的</th><th>一句话示例 &#x2F; 思路</th></tr></thead><tbody><tr><td>4K60 实时 NVENC 硬编‼</td><td><code>ffmpeg -hwaccel cuda -i in.mp4 -c:v h264_nvenc -preset p7 -b:v 50M out.mp4</code></td></tr><tr><td>录屏+摄像头画中画‼</td><td><code>ffmpeg -f x11grab -s 1920x1080 -i :0.0 -f v4l2 -video_size 320x240 -i /dev/video0 -filter_complex &quot;[0:v][1:v]overlay=W-w-10:H-h-10&quot; -c:v libx264 out.mkv</code></td></tr><tr><td>直播推流 RTMP‼</td><td><code>ffmpeg -re -i in.mp4 -c:v libx264 -preset veryfast -f flv rtmp://server/app/stream</code></td></tr><tr><td>10-bit HDR → SDR tonemap‼</td><td><code>ffmpeg -i hdr.mp4 -vf zscale=transfer=linear,tonemap=clip,zscale=transfer=bt709,format=yuv420p -c:v libx265 sdr.mp4</code></td></tr><tr><td>多线程极速转码‼</td><td><code>ffmpeg -i in.mp4 -c:v libx264 -preset ultrafast -threads 0 -c:a aac out.mp4</code></td></tr><tr><td>抽帧做缩略图网格‼</td><td><code>ffmpeg -i in.mp4 -vf &quot;select=not(mod(n\,30)),scale=320:-1,tile=4x4&quot; -frames 1 grid.jpg</code></td></tr><tr><td>实时音频重采样‼</td><td><code>ffmpeg -f pulse -i default -af aresample=44100,aformat=fltp -c:a aac out.m4a</code></td></tr><tr><td>无重新编码快速倒放‼</td><td><code>ffmpeg -i in.mp4 -vf reverse -af areverse -c copy reverse.mp4</code></td></tr><tr><td>生成测试信号（彩条+噪声）</td><td><code>ffmpeg -f lavfi -i testsrc=duration=10:size=1920x1080:rate=30 -f lavfi -i sine=f=1000 -shortest test.mp4</code></td></tr><tr><td>提取 RAW YUV</td><td><code>ffmpeg -i in.mp4 -c:v rawvideo -pix_fmt yuv420p out.yuv</code></td></tr></tbody></table><hr><h3 id="3-黑科技天花板（需要二次开发-脚本）"><a href="#3-黑科技天花板（需要二次开发-脚本）" class="headerlink" title="3.黑科技天花板（需要二次开发&#x2F;脚本）"></a>3.黑科技天花板（需要二次开发&#x2F;脚本）</h3><table><thead><tr><th>目的</th><th>一句话思路</th></tr></thead><tbody><tr><td>帧级精确剪辑‼</td><td><code>ffprobe -select_streams v -show_frames -print_format csv</code> 解析 pts → <code>ss</code>&#x2F;<code>to</code> 毫秒级切割</td></tr><tr><td>AI 超分前处理‼</td><td><code>ffmpeg -i in.mp4 -vf zscale=1920:1080:filter=lanczos -c:v png frame%06d.png</code> → 超分 → 再编码</td></tr><tr><td>低延迟云游戏采集‼</td><td><code>ffmpeg -f kmsgrab -i - -vf hwdownload,format=nv12 -c:v h264_nvenc -tune zerolatency -f rtsp rtsp://host/live</code></td></tr><tr><td>音视频自动对齐‼</td><td>先用 <code>ffprobe</code> 读 <code>start_time</code> → 计算偏移 → <code>itsoffset</code> 参数</td></tr><tr><td>DRM 直接抓帧‼</td><td><code>ffmpeg -f kmsgrab -crtc_id 63 -framerate 60 -i - ...</code> （需 root 权限）</td></tr></tbody></table><hr><h3 id="4-速查表"><a href="#4-速查表" class="headerlink" title="4.速查表"></a>4.速查表</h3><table><thead><tr><th>场景</th><th>关键参数</th></tr></thead><tbody><tr><td>最高压缩</td><td><code>-c:v libx265 -crf 28 -preset slow</code></td></tr><tr><td>实时&#x2F;直播</td><td><code>-preset ultrafast -tune zerolatency</code></td></tr><tr><td>无损备份</td><td><code>-c:v ffv1 -level 3 -c:a flac</code></td></tr><tr><td>仅复制流</td><td><code>-c copy</code></td></tr><tr><td>精确毫秒</td><td><code>-ss 00:01:23.456 -to 00:01:25.000</code></td></tr></tbody></table><hr><p>一句话总结：<br>从「剪个 10 秒小视频」到「4K60 帧低延迟直播」，FFmpeg 一条命令行就能搞定；再往上，用脚本&#x2F;代码把 FFmpeg 当「底层 GPU 音视频引擎」用，天花板几乎没有。</p><hr><h2 id="二、核心数据结构：四大天王"><a href="#二、核心数据结构：四大天王" class="headerlink" title="二、核心数据结构：四大天王"></a>二、核心数据结构：四大天王</h2><h3 id="1-AVFormatContext-容器指挥官"><a href="#1-AVFormatContext-容器指挥官" class="headerlink" title="1. AVFormatContext - 容器指挥官"></a>1. <code>AVFormatContext</code> - 容器指挥官</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">AVFormatContext* fmt_ctx = <span class="literal">NULL</span>;</span><br><span class="line">avformat_alloc_output_context2(&amp;fmt_ctx, <span class="literal">NULL</span>, <span class="string">&quot;mp4&quot;</span>, <span class="string">&quot;output.mp4&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>职责</strong>：</p><ul><li>管理文件&#x2F;流的封装信息</li><li>关键字段： <ul><li><code>nb_streams</code>：流数量</li><li><code>pb</code>：I&#x2F;O上下文指针</li></ul></li></ul><p>类比：快递分拣中心，管理所有包裹（音视频流）</p><h3 id="2-AVCodecContext-编解码大脑"><a href="#2-AVCodecContext-编解码大脑" class="headerlink" title="2. AVCodecContext - 编解码大脑"></a>2. <code>AVCodecContext</code> - 编解码大脑</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);</span><br><span class="line">AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);</span><br><span class="line">codec_ctx-&gt;bit_rate = <span class="number">4000000</span>;  <span class="comment">// 4Mbps</span></span><br><span class="line">codec_ctx-&gt;framerate = (AVRational)&#123;<span class="number">30</span>, <span class="number">1</span>&#125;; <span class="comment">// 30FPS</span></span><br></pre></td></tr></table></figure><table><thead><tr><th>参数</th><th>视频示例</th><th>音频示例</th></tr></thead><tbody><tr><td>分辨率</td><td>1920x1080</td><td>-</td></tr><tr><td>采样率</td><td>-</td><td>44100 Hz</td></tr><tr><td>像素格式</td><td>YUV420P</td><td>-</td></tr><tr><td>声道布局</td><td>-</td><td>AV_CH_LAYOUT_STEREO</td></tr></tbody></table><p><strong>避坑指南</strong>：未设置帧率会导致视频加速播放</p><h3 id="3-AVPacket-压缩数据包"><a href="#3-AVPacket-压缩数据包" class="headerlink" title="3. AVPacket - 压缩数据包"></a>3. <code>AVPacket</code> - 压缩数据包</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">AVPacket* pkt = av_packet_alloc();</span><br><span class="line"><span class="keyword">while</span> (av_read_frame(fmt_ctx, pkt) &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// 处理H.264 NAL单元</span></span><br><span class="line">    av_packet_unref(pkt); <span class="comment">// 关键！防内存泄漏</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>特点</strong>：</p><ul><li>携带编码后的音视频数据</li><li>使用引用计数管理内存</li></ul><h3 id="4-AVFrame-原始数据帧"><a href="#4-AVFrame-原始数据帧" class="headerlink" title="4. AVFrame - 原始数据帧"></a>4. <code>AVFrame</code> - 原始数据帧</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">AVFrame* frame = av_frame_alloc();</span><br><span class="line">frame-&gt;format = AV_PIX_FMT_YUV420P;</span><br><span class="line">av_frame_get_buffer(frame, <span class="number">0</span>); <span class="comment">// 显式分配内存</span></span><br></pre></td></tr></table></figure><p><strong>数据存储</strong>：</p><ul><li>视频：<code>data[0]</code>(Y), <code>data[1]</code>(U), <code>data[2]</code>(V)</li><li>音频：<code>data[0]</code>(左声道), <code>data[1]</code>(右声道)</li></ul><p><strong>冷知识</strong>：人眼对亮度敏感度是色度的6倍，故YUV420P更省带宽！</p><hr><h2 id="三、核心API详解"><a href="#三、核心API详解" class="headerlink" title="三、核心API详解"></a>三、核心API详解</h2><h3 id="1-设备操作三剑客"><a href="#1-设备操作三剑客" class="headerlink" title="1.设备操作三剑客"></a>1.设备操作三剑客</h3><table><thead><tr><th>函数</th><th>作用</th><th>返回值</th><th>经典错误</th></tr></thead><tbody><tr><td>avdevice_register_all()</td><td>注册设备</td><td>void</td><td>未调用导致设备找不到</td></tr><tr><td>av_find_input_format()</td><td>查找设备格式</td><td>AVInputFormat*</td><td>返回NULL表示格式不支持</td></tr><tr><td>avformat_open_input()</td><td>打开设备</td><td>int</td><td>负数需用av_strerror解析</td></tr></tbody></table><h3 id="2-编解码关键流程"><a href="#2-编解码关键流程" class="headerlink" title="2.编解码关键流程"></a>2.编解码关键流程</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 查找编解码器</span></span><br><span class="line">AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建编解码上下文</span></span><br><span class="line">AVCodecContext *ctx = avcodec_alloc_context3(codec);</span><br><span class="line">ctx-&gt;width = <span class="number">1920</span>; </span><br><span class="line">ctx-&gt;height = <span class="number">1080</span>;</span><br><span class="line">ctx-&gt;time_base = (AVRational)&#123;<span class="number">1</span>, <span class="number">30</span>&#125;; <span class="comment">// 必须设置！</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 打开编解码器</span></span><br><span class="line"><span class="keyword">if</span> (avcodec_open2(ctx, codec, <span class="literal">NULL</span>) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="type">char</span> err_buf[<span class="number">256</span>];</span><br><span class="line">    av_strerror(ret, err_buf, <span class="keyword">sizeof</span>(err_buf)); <span class="comment">// 错误解析</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 编码循环</span></span><br><span class="line">avcodec_send_frame(ctx, frame); <span class="comment">// 送入原始帧</span></span><br><span class="line"><span class="keyword">while</span> (avcodec_receive_packet(ctx, pkt) == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="comment">// 处理编码包</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-数据转换核心"><a href="#3-数据转换核心" class="headerlink" title="3.数据转换核心"></a>3.数据转换核心</h3><table><thead><tr><th>函数</th><th>作用</th><th>适用场景</th></tr></thead><tbody><tr><td>sws_getContext()</td><td>图像缩放&#x2F;格式转换</td><td>摄像头YUV→编码器YUV</td></tr><tr><td>swr_alloc_set_opts()</td><td>音频重采样</td><td>麦克风PCM→AAC编码</td></tr><tr><td>av_frame_make_writable()</td><td>避免内存拷贝</td><td>高性能处理</td></tr></tbody></table><hr><h2 id="四、深入PulseAudio"><a href="#四、深入PulseAudio" class="headerlink" title="四、深入PulseAudio"></a>四、深入PulseAudio</h2><h3 id="1-核心架构"><a href="#1-核心架构" class="headerlink" title="1.核心架构"></a>1.核心架构</h3><p><img                         lazyload                       alt="image"                       data-src="/2025/0772985/1.svg"                                        ></p><h3 id="2-设备类型详解"><a href="#2-设备类型详解" class="headerlink" title="2.设备类型详解"></a>2.设备类型详解</h3><table><thead><tr><th>类型</th><th>功能</th><th>FFmpeg标识</th><th>查询命令</th></tr></thead><tbody><tr><td>Source</td><td>音频输入</td><td>-f pulse -i default</td><td>pactl list sources</td></tr><tr><td>Sink</td><td>音频输出</td><td>不支持直接访问</td><td>pactl list sinks</td></tr><tr><td>Monitor</td><td>监听输出</td><td>.monitor<br/>后缀</td><td>pactl list sources</td></tr></tbody></table><h3 id="3-实战场景"><a href="#3-实战场景" class="headerlink" title="3.实战场景"></a>3.实战场景</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 录制麦克风</span></span><br><span class="line">avformat_open_input(&amp;ctx, <span class="string">&quot;default&quot;</span>, av_find_input_format(<span class="string">&quot;pulse&quot;</span>), <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 录制系统声音（关键！）</span></span><br><span class="line">avformat_open_input(&amp;ctx, <span class="string">&quot;alsa_output.pci-0000_00_1f.3.analog-stereo.monitor&quot;</span>, </span><br><span class="line">                    av_find_input_format(<span class="string">&quot;pulse&quot;</span>), <span class="literal">NULL</span>);</span><br></pre></td></tr></table></figure><blockquote><p><strong>系统救星</strong>：<code>pulseaudio -k &amp;&amp; pulseaudio --start</code> 解决90%音频问题</p></blockquote><hr><h2 id="五、音视频录制实战"><a href="#五、音视频录制实战" class="headerlink" title="五、音视频录制实战"></a>五、音视频录制实战</h2><h3 id="1-摄像头录制（Linux-V4L2）"><a href="#1-摄像头录制（Linux-V4L2）" class="headerlink" title="1. 摄像头录制（Linux V4L2）"></a>1. 摄像头录制（Linux V4L2）</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">avdevice_register_all();</span><br><span class="line">AVInputFormat* input_fmt = av_find_input_format(<span class="string">&quot;v4l2&quot;</span>);</span><br><span class="line">avformat_open_input(&amp;cam_ctx, <span class="string">&quot;/dev/video0&quot;</span>, input_fmt, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 转码决策树</span></span><br><span class="line"><span class="keyword">if</span> (codecpar-&gt;codec_id == AV_CODEC_ID_MJPEG) &#123;</span><br><span class="line">    <span class="comment">// 直接流复制</span></span><br><span class="line">    avcodec_parameters_copy(out_stream-&gt;codecpar, codecpar);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 完整转码流水线</span></span><br><span class="line">    AVCodec *decoder = avcodec_find_decoder(codecpar-&gt;codec_id);</span><br><span class="line">    AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_H264);</span><br><span class="line">    <span class="comment">// ...构建解码-&gt;处理-&gt;编码流水线</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-麦克风录制（PulseAudio）"><a href="#2-麦克风录制（PulseAudio）" class="headerlink" title="2. 麦克风录制（PulseAudio）"></a>2. 麦克风录制（PulseAudio）</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">SwrContext* swr = swr_alloc_set_opts(<span class="literal">NULL</span>, </span><br><span class="line">                                     AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLTP, <span class="number">44100</span>, <span class="comment">// 输出格式</span></span><br><span class="line">                                     src_ch_layout, src_sample_fmt, src_sample_rate, <span class="comment">// 输入格式</span></span><br><span class="line">                                     <span class="number">0</span>, <span class="literal">NULL</span>);</span><br><span class="line">swr_init(swr); <span class="comment">// 必须初始化！</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 音频冷知识：AAC编码器强制要求浮点平面格式(AV_SAMPLE_FMT_FLTP)</span></span><br></pre></td></tr></table></figure><h3 id="3-音视频同步（核心！）"><a href="#3-音视频同步（核心！）" class="headerlink" title="3. 音视频同步（核心！）"></a>3. 音视频同步（核心！）</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 统一时间基准（微秒级精度）</span></span><br><span class="line"><span class="type">int64_t</span> video_pts = av_rescale_q(frame-&gt;pts, video_time_base, AV_TIME_BASE_Q);</span><br><span class="line"><span class="type">int64_t</span> audio_pts = av_rescale_q(samples-&gt;pts, audio_time_base, AV_TIME_BASE_Q);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同步写入决策</span></span><br><span class="line"><span class="keyword">if</span> (video_pts &lt;= audio_pts) &#123;</span><br><span class="line">    av_interleaved_write_frame(fmt_ctx, video_pkt); <span class="comment">// 优先写视频</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    av_interleaved_write_frame(fmt_ctx, audio_pkt); <span class="comment">// 优先写音频</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>同步要诀</strong>：使用<code>av_rescale_q</code>而非手动计算，避免精度丢失</p><h3 id="4-音频录制-重采样实战"><a href="#4-音频录制-重采样实战" class="headerlink" title="4.音频录制+重采样实战"></a>4.音频录制+重采样实战</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 打开麦克风</span></span><br><span class="line">avformat_open_input(&amp;audio_fmt_ctx, <span class="string">&quot;default&quot;</span>, av_find_input_format(<span class="string">&quot;pulse&quot;</span>), <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 创建音频编码器</span></span><br><span class="line">AVCodec *audio_codec = avcodec_find_encoder(AV_CODEC_ID_AAC);</span><br><span class="line">AVCodecContext *a_codec_ctx = avcodec_alloc_context3(audio_codec);</span><br><span class="line">a_codec_ctx-&gt;sample_rate = <span class="number">44100</span>;</span><br><span class="line">a_codec_ctx-&gt;channel_layout = AV_CH_LAYOUT_STEREO;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 重采样设置（解决格式不匹配）</span></span><br><span class="line">SwrContext *swr = swr_alloc();</span><br><span class="line">swr_alloc_set_opts2(&amp;swr,</span><br><span class="line">                    &amp;a_codec_ctx-&gt;ch_layout, a_codec_ctx-&gt;sample_fmt, a_codec_ctx-&gt;sample_rate,</span><br><span class="line">                    &amp;in_ch_layout, in_sample_fmt, in_sample_rate,</span><br><span class="line">                    <span class="number">0</span>, <span class="literal">NULL</span>);</span><br><span class="line">swr_init(swr);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. 处理循环</span></span><br><span class="line"><span class="keyword">while</span> (<span class="number">1</span>) &#123;</span><br><span class="line">    av_read_frame(audio_fmt_ctx, pkt);</span><br><span class="line">    swr_convert(swr, out_samples, out_sample_count, </span><br><span class="line">                (<span class="type">const</span> <span class="type">uint8_t</span>**)pkt-&gt;data, pkt-&gt;size/<span class="number">4</span>);</span><br><span class="line">    <span class="comment">// 编码处理...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="六、高级开发技巧"><a href="#六、高级开发技巧" class="headerlink" title="六、高级开发技巧"></a>六、高级开发技巧</h2><h3 id="1-性能优化三连击"><a href="#1-性能优化三连击" class="headerlink" title="1. 性能优化三连击"></a>1. 性能优化三连击</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CUDA硬件加速（5x+速度提升）</span></span><br><span class="line">ctx-&gt;hw_device_ctx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 多线程编码（榨干CPU）</span></span><br><span class="line">ctx-&gt;thread_count = <span class="number">16</span>; </span><br><span class="line">ctx-&gt;thread_type = FF_THREAD_FRAME;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 零拷贝优化</span></span><br><span class="line">av_frame_make_writable(frame);  <span class="comment">// 避免隐式拷贝</span></span><br></pre></td></tr></table></figure><h3 id="2-调试神器"><a href="#2-调试神器" class="headerlink" title="2. 调试神器"></a>2. 调试神器</h3><table><thead><tr><th>工具</th><th>命令示例</th><th>功能</th></tr></thead><tbody><tr><td><strong>ffprobe</strong></td><td>ffprobe -show_streams input.mp4</td><td>分析文件结构</td></tr><tr><td><strong>ffplay</strong></td><td>ffplay -f v4l2 &#x2F;dev&#x2F;video0</td><td>实时预览设备流</td></tr></tbody></table><h3 id="3-必备诊断命令"><a href="#3-必备诊断命令" class="headerlink" title="3.必备诊断命令"></a>3.必备诊断命令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 检查设备支持</span></span><br><span class="line">ffmpeg -f pulse -list_devices <span class="literal">true</span> -i dummy</span><br><span class="line"></span><br><span class="line"><span class="comment"># 测试摄像头</span></span><br><span class="line">ffplay -f v4l2 -video_size 1280x720 /dev/video0</span><br><span class="line"></span><br><span class="line"><span class="comment"># 分析输出文件</span></span><br><span class="line">ffprobe -show_streams output.mp4</span><br></pre></td></tr></table></figure><h3 id="4-经典错误表"><a href="#4-经典错误表" class="headerlink" title="4.经典错误表"></a>4.经典错误表</h3><table><thead><tr><th align="left"><strong>错误码</strong></th><th align="left"><strong>含义</strong></th><th align="left"><strong>解决方案</strong></th></tr></thead><tbody><tr><td align="left">AVERROR(EAGAIN)</td><td align="left">资源暂时不可用</td><td align="left">重试或等待</td></tr><tr><td align="left">AVERROR(ENOMEM)</td><td align="left">内存不足</td><td align="left">检查资源泄露</td></tr><tr><td align="left">AVERROR(EINVAL)</td><td align="left">无效参数</td><td align="left">检查参数合法性</td></tr><tr><td align="left">AVERROR_PATCHWELCOME</td><td align="left">功能未实现</td><td align="left">升级FFmpeg版本</td></tr></tbody></table><h3 id="5-常见问题急救包"><a href="#5-常见问题急救包" class="headerlink" title="5. 常见问题急救包"></a>5. 常见问题急救包</h3><p><strong>问题</strong>：视频花屏<br><strong>原因</strong>：</p><ul><li>关键帧丢失（I帧被狗吃了）</li><li>PTS时间戳不连续<br><strong>解决方案</strong>：</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 强制插入关键帧</span></span><br><span class="line">frame-&gt;key_frame = <span class="number">1</span>;</span><br><span class="line">frame-&gt;pict_type = AV_PICTURE_TYPE_I;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修复时间戳（避免跳跃）</span></span><br><span class="line">frame-&gt;pts = last_pts + av_rescale_q(<span class="number">1</span>, time_base, stream_time_base);</span><br></pre></td></tr></table></figure><h3 id="6-跨平台兼容方案"><a href="#6-跨平台兼容方案" class="headerlink" title="6. 跨平台兼容方案"></a>6. 跨平台兼容方案</h3><table><thead><tr><th>功能</th><th>Linux</th><th>Windows</th><th>macOS</th></tr></thead><tbody><tr><td>摄像头访问</td><td>v4l2</td><td>dshow</td><td>avfoundation</td></tr><tr><td>音频录制</td><td>pulse&#x2F;alsa</td><td>dshow</td><td>avfoundation</td></tr><tr><td>屏幕录制</td><td>x11grab</td><td>gdigrab</td><td>avfoundation</td></tr><tr><td>硬件加速</td><td>VAAPI&#x2F;NVDEC</td><td>DXVA2&#x2F;NVDEC</td><td>VideoToolbox</td></tr></tbody></table><h4 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h4><ol><li><strong>Linux (PulseAudio)</strong>：<ul><li>需要安装<code>libpulse-dev</code></li><li>设备名称为<code>default</code>或特定设备名称</li></ul></li><li><strong>Windows (DirectShow)</strong>：<ul><li>修改设备名称为<code>audio=麦克风名称</code></li><li>可能需要先列出可用设备</li></ul></li><li><strong>macOS (AVFoundation)</strong>：<ul><li>修改设备名称为<code>:0</code>或特定设备索引</li><li>输入格式应为<code>avfoundation</code></li></ul></li></ol><hr><h2 id="七、终极Q-A"><a href="#七、终极Q-A" class="headerlink" title="七、终极Q&amp;A"></a>七、终极Q&amp;A</h2><h3 id="1-为什么摄像头录制中同时需要解码器和编码器"><a href="#1-为什么摄像头录制中同时需要解码器和编码器" class="headerlink" title="1. 为什么摄像头录制中同时需要解码器和编码器"></a>1. 为什么摄像头录制中同时需要解码器和编码器</h3><ul><li>摄像头输出是压缩数据（如MJPEG）</li><li>目标格式需要重新编码（如H.264）</li><li>转码流水线：<code>MJPEG -&gt; YUV -&gt; H.264</code></li></ul><h3 id="2-AVPacket-和-AVFrame-之间的关系"><a href="#2-AVPacket-和-AVFrame-之间的关系" class="headerlink" title="2.AVPacket 和 AVFrame 之间的关系"></a>2.AVPacket 和 AVFrame 之间的关系</h3><ol><li>解码过程：<ul><li>从多媒体文件中读取 AVPacket。</li><li>使用解码器将 AVPacket 中的压缩数据解码到 AVFrame 中。</li><li>处理（例如，显示或进一步处理）AVFrame 中的未压缩数据。</li></ul></li><li>编码过程：<ul><li>从原始数据（例如，摄像头捕获的视频帧或麦克风捕获的音频样本）创建 AVFrame。</li><li>使用编码器将 AVFrame 中的未压缩数据编码到 AVPacket 中。</li><li>将 AVPacket 写入到多媒体文件中。</li></ul></li><li>转换过程：<ul><li>读取 AVPacket，解码到 AVFrame。</li><li>对 AVFrame 进行格式转换或处理。</li><li>将处理后的 AVFrame 重新编码到 AVPacket。</li><li>写入新的 AVPacket 到输出文件。</li></ul></li></ol><p>通过这种方式，AVPacket 和 AVFrame 在 FFmpeg 的多媒体处理流程中起到了桥梁的作用，确保数据能够在不同的处理阶段之间正确传递和转换。</p><h3 id="3-一些性能优化思路-待验证"><a href="#3-一些性能优化思路-待验证" class="headerlink" title="3.一些性能优化思路(待验证)"></a>3.一些性能优化思路(待验证)</h3><p>1.从DRM（Direct Rendering Manager）直接获取帧数据可以显著提高屏幕捕获效率，避免通过 X11 的额外开销—缺点是通过 drm 需要 root 权限，仅支持 linux，实现起来较为复杂。</p><p>2.优化转码算法,录屏耗时一般都在捕获和转码阶段，参考开源录屏应用 simplescreenrecorder(ssr) 的算法实现，可有效较少转码耗时—转码本质上是数学运算，一些 cpu 指令集对特定数学运算也有较好的加速效果。</p><p>3.RGB 到 YUV 的转换可以使用更高效的 SIMD 指令优化</p><h3 id="4-录制音频时怎么查找系统的默认音频设备"><a href="#4-录制音频时怎么查找系统的默认音频设备" class="headerlink" title="4.录制音频时怎么查找系统的默认音频设备"></a>4.录制音频时怎么查找系统的默认音频设备</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ pactl list sources short</span><br><span class="line">0alsa_output.pci-0000_00_1f.3.analog-stereo.monitormodule-alsa-card.cs16le 2ch 44100HzSUSPENDED</span><br><span class="line">1alsa_input.pci-0000_00_1f.3.analog-stereomodule-alsa-card.cs16le 2ch 44100HzSUSPENDED</span><br></pre></td></tr></table></figure><p>选 **带 **<code>**monitor**</code> 的那一行即可。</p><p>也可以到操作系统的设置中查看输出设备是否一致，命令可能会查询到多个输出设备</p><h3 id="5-官方示例"><a href="#5-官方示例" class="headerlink" title="5.官方示例"></a>5.官方示例</h3><p>以 linux 操作系统为例，如果是基于 Debian 的发行版，运行以下命令安装示例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install ffmpeg-doc</span><br></pre></td></tr></table></figure><p><img                         lazyload                       alt="image"                       data-src="/0.png"                                        ></p><h3 id="6-录制的视频有时候会花屏"><a href="#6-录制的视频有时候会花屏" class="headerlink" title="6.录制的视频有时候会花屏"></a>6.录制的视频有时候会花屏</h3><p>1.帧间预测依赖链断裂-视频编码基于 <strong>I帧(关键帧)</strong>、<strong>P帧(前向预测)</strong> 和 <strong>B帧(双向预测)(见附2,先了解下 I P B 帧的概念)</strong></p><p>2.时间戳不连续，也就是帧的 pts</p><h3 id="7-视频播放速度是怎么控制的"><a href="#7-视频播放速度是怎么控制的" class="headerlink" title="7.视频播放速度是怎么控制的"></a>7.视频播放速度是怎么控制的</h3><p>播放行为本质上是：</p><ol><li><strong>以PTS为绝对时间基准</strong></li><li><strong>以预设帧率为初始化参考</strong></li><li><strong>通过动态同步算法实时调整</strong></li></ol><h2 id="八、结语：通往音视频大师之路"><a href="#八、结语：通往音视频大师之路" class="headerlink" title="八、结语：通往音视频大师之路"></a>八、结语：通往音视频大师之路</h2><p>当你：</p><ol><li>能30分钟写出录制程序 → <strong>青铜</strong></li><li>搞定音视频同步 → <strong>白银</strong></li><li>实现4K60帧编码 → <strong>黄金</strong></li><li>修复花屏&#x2F;卡顿问题 → <strong>王者</strong></li><li>给FFmpeg提交补丁 → <strong>封神</strong></li></ol><p>FFmpeg不是一天学会的，每个崩溃的程序都让你离大师更近一步。</p><h2 id="附0：ffmpeg重点接口"><a href="#附0：ffmpeg重点接口" class="headerlink" title="附0：ffmpeg重点接口"></a>附0：ffmpeg重点接口</h2><h3 id="1-编解码相关接口"><a href="#1-编解码相关接口" class="headerlink" title="1.编解码相关接口"></a>1.编解码相关接口</h3><table><thead><tr><th>avcodec_find_encoder&#x2F;avcodec_find_decoder</th><th>查找指定的编解码器。</th><th>在编码或解码之前，需要通过这些函数获取正确的编解码器。</th></tr></thead><tbody><tr><td>avcodec_open2</td><td>打开一个编解码器实例，初始化编解码器上下文。</td><td>在开始编解码操作之前，必须调用此函数以确保编解码器准备好。</td></tr><tr><td>avcodec_send_frame&#x2F;avcodec_receive_packet</td><td>分别用于将解码后的帧发送给编码器，以及从编码器接收编码后的数据包。</td><td>在编码过程中，用于将帧数据传递给编码器并获取编码结果。</td></tr><tr><td>avcodec_send_packet&#x2F;avcodec_receive_frame</td><td>分别用于将编码后的数据包发送给解码器，以及从解码器接收解码后的帧。</td><td>在解码过程中，用于处理编码数据并获取解码后的帧数据。</td></tr></tbody></table><h3 id="2-格式上下文相关接口"><a href="#2-格式上下文相关接口" class="headerlink" title="2.格式上下文相关接口"></a>2.格式上下文相关接口</h3><table><thead><tr><th>avformat_open_input</th><th>打开一个媒体文件输入流。</th><th>读取媒体文件时使用，用于初始化 AVFormatContext。</th></tr></thead><tbody><tr><td>avformat_write_header</td><td>写入媒体文件的文件头信息。</td><td>在创建媒体文件时，用于写入必要的格式和流信息。</td></tr><tr><td>av_interleaved_write_frame</td><td>将编码后的数据包写入文件，支持交错写入。</td><td>在录制或转码过程中，用于将编码后的数据写入目标文件。</td></tr><tr><td>avformat_close_input&#x2F;avio_closep</td><td>关闭媒体文件输入流和输出流。</td><td>在完成读写操作后，清理资源。</td></tr></tbody></table><h3 id="3-图像转换相关接口"><a href="#3-图像转换相关接口" class="headerlink" title="3.图像转换相关接口"></a>3.图像转换相关接口</h3><table><thead><tr><th>sws_getContext</th><th>创建一个图像转换上下文，用于在不同像素格式之间进行转换。</th><th>在处理屏幕捕获或视频帧格式转换时使用。</th></tr></thead><tbody><tr><td>sws_scale</td><td>执行图像转换，将一个图像从一种像素格式转换为另一种格式。</td><td>在需要调整图像大小或格式时使用，例如将捕获的屏幕图像从 RGB 转换为 YUV 格式。</td></tr></tbody></table><h3 id="4-其他重要接口"><a href="#4-其他重要接口" class="headerlink" title="4.其他重要接口"></a>4.其他重要接口</h3><table><thead><tr><th>avformat_alloc_output_context2</th><th>分配一个输出上下文，用于创建输出文件。</th><th>在录制或转码时初始化输出文件的格式和流。</th></tr></thead><tbody><tr><td>avcodec_parameters_from_context</td><td>将编解码器上下文的参数复制到流参数中。</td><td>在创建输出流时，用于设置流的编解码器参数。</td></tr><tr><td>av_packet_alloc&#x2F;av_packet_unref</td><td>分配和释放 AVPacket 的内存。</td><td>在处理编码数据包时，用于管理数据包的生命周期。</td></tr></tbody></table><h3 id="5-高级功能"><a href="#5-高级功能" class="headerlink" title="5.高级功能"></a>5.高级功能</h3><table><thead><tr><th>AVFilterGraph</th><th>用于构建和处理复杂的音视频滤镜链。</th><th>在需要对音视频数据进行复杂的实时处理（如裁剪、旋转、添加水印等）时使用。</th></tr></thead><tbody><tr><td>avfilter_graph_create_filter</td><td>在滤镜图中创建一个新的滤镜实例。</td><td>用于初始化和配置滤镜链中的各个滤镜</td></tr></tbody></table><h2 id="附1：FFmpeg编解码器与封装格式大全"><a href="#附1：FFmpeg编解码器与封装格式大全" class="headerlink" title="附1：FFmpeg编解码器与封装格式大全"></a>附1：FFmpeg编解码器与封装格式大全</h2><p><strong>温馨提示</strong>：本清单仅展示FFmpeg支持的<strong>部分代表性格式</strong>（完整列表超600+），建议保存备用。当有人质疑FFmpeg能力时，请优雅地甩出此表😎</p><hr><h3 id="1-视频编解码器"><a href="#1-视频编解码器" class="headerlink" title="1.视频编解码器"></a>1.视频编解码器</h3><table><thead><tr><th><strong>主流编解码器</strong></th><th><strong>专有格式</strong></th><th><strong>创新编码</strong></th><th><strong>遗留格式</strong></th></tr></thead><tbody><tr><td>H.264&#x2F;AVC</td><td>Apple ProRes</td><td>AV1</td><td>MJPEG</td></tr><tr><td>H.265&#x2F;HEVC</td><td>DNxHD</td><td>VP9</td><td>Motion JPEG</td></tr><tr><td>AV1</td><td>CineForm</td><td>Daala</td><td>DV (Digital Video)</td></tr><tr><td>VP8</td><td>GoPro CineForm</td><td>Thor</td><td>HuffYUV</td></tr><tr><td>MPEG-2</td><td>Windows Media Video</td><td>Dirac</td><td>Sorenson Video</td></tr><tr><td>MPEG-4 Part 2</td><td>RealVideo</td><td>Snow</td><td>Indeo</td></tr><tr><td>Theora</td><td>Flash Screen Video</td><td>FFV1 (无损)</td><td>MSU Screen Capture</td></tr></tbody></table><h3 id="2-音频编解码器"><a href="#2-音频编解码器" class="headerlink" title="2.音频编解码器"></a>2.音频编解码器</h3><table><thead><tr><th><strong>通用音频</strong></th><th><strong>语音编码</strong></th><th><strong>专业音频</strong></th><th><strong>创新格式</strong></th></tr></thead><tbody><tr><td>AAC</td><td>Opus</td><td>FLAC (无损)</td><td>Dolby Atmos</td></tr><tr><td>MP3</td><td>Speex</td><td>ALAC (Apple无损)</td><td>DTS:X</td></tr><tr><td>AC-3</td><td>AMR-NB&#x2F;WB</td><td>DSD (Direct Stream)</td><td>CELT</td></tr><tr><td>Vorbis</td><td>G.711 (A-law&#x2F;μ-law)</td><td>PCM (各种位深)</td><td>Siren</td></tr><tr><td>WMA</td><td>GSM</td><td>Dolby TrueHD</td><td>AptX</td></tr></tbody></table><hr><h3 id="3-视频容器格式"><a href="#3-视频容器格式" class="headerlink" title="3.视频容器格式"></a>3.视频容器格式</h3><table><thead><tr><th><strong>主流容器</strong></th><th><strong>流媒体专用</strong></th><th><strong>专业制作</strong></th><th><strong>设备专用</strong></th></tr></thead><tbody><tr><td>MP4</td><td>HLS (m3u8)</td><td>MXF</td><td>3GP</td></tr><tr><td>MKV</td><td>DASH</td><td>GXF</td><td>3G2</td></tr><tr><td>AVI</td><td>RTMP</td><td>MOV (ProRes版)</td><td>F4V</td></tr><tr><td>WebM</td><td>RTSP</td><td>XDCAM</td><td>VOB (DVD)</td></tr><tr><td>FLV</td><td>SRT</td><td>AVC-Intra</td><td>MOD (摄像机)</td></tr></tbody></table><h3 id="4-音频容器格式"><a href="#4-音频容器格式" class="headerlink" title="4.音频容器格式"></a>4.音频容器格式</h3><table><thead><tr><th><strong>通用音频</strong></th><th><strong>无损音频</strong></th><th><strong>广播专用</strong></th><th><strong>元数据载体</strong></th></tr></thead><tbody><tr><td>MP3</td><td>FLAC</td><td>ADTS</td><td>ID3v2</td></tr><tr><td>AAC</td><td>WAV (PCM)</td><td>LATM</td><td>APE Tag</td></tr><tr><td>Ogg</td><td>AIFF</td><td>RTP</td><td>Vorbis Comment</td></tr><tr><td>WMA</td><td>DSF (DSD)</td><td>AU</td><td>Xiph Comment</td></tr></tbody></table><hr><h3 id="5-特殊格式"><a href="#5-特殊格式" class="headerlink" title="5.特殊格式"></a>5.特殊格式</h3><table><thead><tr><th><strong>类别</strong></th><th><strong>代表成员</strong></th><th><strong>特殊技能</strong></th></tr></thead><tbody><tr><td><strong>图像序列</strong></td><td>PNG, JPEG, TIFF, DPX</td><td>支持帧序列导出</td></tr><tr><td><strong>游戏专用</strong></td><td>Bink Video, Smacker</td><td>游戏过场动画专用</td></tr><tr><td><strong>闭路电视</strong></td><td>CCTV H.264+</td><td>监控设备专用格式</td></tr><tr><td><strong>光碟格式</strong></td><td>Blu-ray, HD-DVD</td><td>蓝光碟片支持</td></tr><tr><td><strong>字幕格式</strong></td><td>SRT, ASS, WebVTT, DVB-SUB</td><td>支持硬字幕+软字幕</td></tr></tbody></table><p><strong>冷知识</strong>：FFmpeg甚至支持解析 <strong>.gif动图</strong> 和 <strong>.ico图标文件</strong>，堪称格式界的”瑞士军刀”</p><hr><h3 id="6-结语："><a href="#6-结语：" class="headerlink" title="6.结语："></a>6.结语：</h3><p>当看到如此庞大的格式支持列表时，请记住FFmpeg作者的名言：</p><p><em>“我们不是选择支持什么格式，而是选择不支持什么格式——因为实在太少了”</em></p><p>（注：完整列表可通过 <code>ffmpeg -codecs</code> 和 <code>ffmpeg -formats</code> 查看）</p><h2 id="附2：视频编码中的-I-P-B帧"><a href="#附2：视频编码中的-I-P-B帧" class="headerlink" title="附2：视频编码中的 I  P  B帧"></a>附2：视频编码中的 I  P  B帧</h2><p>庆幸的是，在 ffmpeg 的音视频编程中，我们只需要知道 IPB 帧 的概念即可，ffmpeg会自动处理这些帧。</p><h3 id="1-I帧（Intra-coded-Frame，关键帧）"><a href="#1-I帧（Intra-coded-Frame，关键帧）" class="headerlink" title="1.I帧（Intra-coded Frame，关键帧）"></a>1.I帧（Intra-coded Frame，关键帧）</h3><ul><li><strong>定义</strong>：完全独立编码的帧，不依赖其他帧即可解码。类似静态JPEG图像，仅使用<strong>帧内压缩</strong>（空间冗余压缩）。</li><li><strong>特点</strong>：<ul><li>包含完整图像数据，解码时无需参考其他帧。</li><li>压缩率最低（因无时间冗余利用），但可作为随机访问点（如快进、seek操作）。</li><li>通常定期插入（如每秒1次），用于错误恢复（若P&#x2F;B帧丢失，可从下一个I帧重新开始解码）。</li></ul></li><li><strong>应用场景</strong>：<ul><li>视频开头（首个帧必须是I帧）。</li><li>场景切换时（新旧场景无关联，需重新生成完整帧）。</li><li>广播流媒体（支持随机换台）。</li></ul></li></ul><hr><h3 id="2-P帧（Predicted-Frame，前向预测帧）"><a href="#2-P帧（Predicted-Frame，前向预测帧）" class="headerlink" title="2.P帧（Predicted Frame，前向预测帧）"></a>2.P帧（Predicted Frame，前向预测帧）</h3><ul><li><strong>定义</strong>：通过<strong>前向预测</strong>编码的帧，仅参考<strong>过去的I帧或P帧</strong>。</li><li><strong>工作原理</strong>：<ul><li>基于运动估计（Motion Estimation），在参考帧中查找相似块（宏块），记录<strong>运动矢量</strong>（位移数据）和<strong>残差</strong>（预测误差）。</li><li>例如：若背景静止，P帧只需编码移动物体的变化，大幅节省数据量。</li></ul></li><li><strong>特点</strong>：<ul><li>压缩率高于I帧（利用时间冗余），但解码依赖参考帧（若参考帧丢失会出错）。</li><li>无法处理未来帧的遮挡关系（如物体从后方出现）。</li></ul></li><li><strong>应用场景</strong>：<ul><li>大多数连续运动的视频片段（如人物行走）。</li></ul></li></ul><hr><h3 id="3-B帧（Bidirectional-Frame，双向预测帧）"><a href="#3-B帧（Bidirectional-Frame，双向预测帧）" class="headerlink" title="3.B帧（Bidirectional Frame，双向预测帧）"></a>3.B帧（Bidirectional Frame，双向预测帧）</h3><ul><li><strong>定义</strong>：通过<strong>双向预测</strong>编码的帧，可同时参考<strong>过去和未来的I&#x2F;P帧</strong>。</li><li><strong>工作原理</strong>：<ul><li>综合前后参考帧进行插值预测。例如：若物体从左侧移动到右侧，B帧可结合前一帧（物体在左）和后一帧（物体在右）生成中间状态。</li><li>支持<strong>直接模式</strong>（Direct Mode）：复用运动矢量，进一步减少数据量。</li></ul></li><li><strong>特点</strong>：<ul><li>压缩率最高（因时间冗余利用最充分），但引入编码延迟（需等待未来帧到达）。</li><li>解码顺序与显示顺序可能不同（如先解码未来的P帧再解码B帧）。</li></ul></li><li><strong>应用场景</strong>：<ul><li>非实时场景（如点播视频、蓝光光盘）。</li><li>高速运动场景（如体育赛事），通过双向预测减少残差数据。</li></ul></li></ul><hr><h3 id="4-三者的协同工作流程"><a href="#4-三者的协同工作流程" class="headerlink" title="4.三者的协同工作流程"></a>4.三者的协同工作流程</h3><p>以典型的 IBBPBBP… GOP（Group of Pictures）结构为例：</p><ol><li><strong>解码顺序</strong>：I₀ → P₃ → B₁ → B₂ → P₆ → B₄ → B₅…</li><li><strong>显示顺序</strong>：I₀ → B₁ → B₂ → P₃ → B₄ → B₅ → P₆…<ul><li>解码器会缓存未来帧（如P₃），以便解码B₁&#x2F;B₂时使用。</li></ul></li></ol><hr><h3 id="5-关键对比表"><a href="#5-关键对比表" class="headerlink" title="5.关键对比表"></a>5.关键对比表</h3><table><thead><tr><th align="left"><strong>属性</strong></th><th align="left"><strong>I帧</strong></th><th align="left"><strong>P帧</strong></th><th align="left"><strong>B帧</strong></th></tr></thead><tbody><tr><td align="left"><strong>参考帧</strong></td><td align="left">无</td><td align="left">过去的I&#x2F;P帧</td><td align="left">过去+未来的I&#x2F;P帧</td></tr><tr><td align="left"><strong>压缩率</strong></td><td align="left">最低</td><td align="left">中等</td><td align="left">最高</td></tr><tr><td align="left"><strong>解码依赖</strong></td><td align="left">独立</td><td align="left">依赖前向帧</td><td align="left">依赖双向帧</td></tr><tr><td align="left"><strong>延迟</strong></td><td align="left">无</td><td align="left">低</td><td align="left">高（需未来帧）</td></tr><tr><td align="left"><strong>用途</strong></td><td align="left">随机访问、错误恢复</td><td align="left">主流预测</td><td align="left">高压缩优化</td></tr></tbody></table><hr><h3 id="6-实际应用中的权衡"><a href="#6-实际应用中的权衡" class="headerlink" title="6.实际应用中的权衡"></a>6.实际应用中的权衡</h3><ul><li><strong>直播场景</strong>：通常减少B帧（或不用）以降低延迟（如RTMP协议常用IPPP结构）。</li><li><strong>存储场景</strong>：大量使用B帧（如H.264的<code>IBBBP...</code> 结构），节省50%以上码率。</li><li><strong>错误恢复</strong>：若P&#x2F;B帧丢失，解码器会跳过到下一个I帧（可能出现短暂花屏）。</li></ul><p>理解这三类帧的机制，对优化编码参数（如GOP长度、B帧数量）和排查播放问题（如花屏、卡顿）至关重要。</p><blockquote><p>网络上也有比较好的文章可以参考：<a class="link"   href="https://zhuanlan.zhihu.com/p/614236013" >https://zhuanlan.zhihu.com/p/614236013<i class="fas fa-external-link-alt"></i></a></p></blockquote><h2 id="附3：-YUV-格式与-RGB-图像格式介绍"><a href="#附3：-YUV-格式与-RGB-图像格式介绍" class="headerlink" title="附3： YUV 格式与 RGB 图像格式介绍"></a>附3： YUV 格式与 RGB 图像格式介绍</h2><p>在YUV文件中，Y、U、V分别代表亮度（Luma）和两个色差（Chroma）分量。与一张真正的RGB图片相比，它们的对应关系如下：</p><hr><h3 id="1-Y-Luma-亮度分量"><a href="#1-Y-Luma-亮度分量" class="headerlink" title="1.Y (Luma) - 亮度分量"></a>1.Y (Luma) - 亮度分量</h3><ul><li><strong>作用</strong>：表示图像的黑白（灰度）信息，即每个像素的明暗程度。</li><li><strong>类比</strong>：相当于将彩色照片去色后的灰度图，单独看Y分量时，是一张完整的黑白图片。</li><li><strong>示例</strong>：一张彩色照片中的白色区域Y值高，黑色区域Y值低。</li></ul><hr><h3 id="2-U-V-Chroma-色差分量"><a href="#2-U-V-Chroma-色差分量" class="headerlink" title="2.U&#x2F;V (Chroma) - 色差分量"></a>2.U&#x2F;V (Chroma) - 色差分量</h3><ul><li><strong>作用</strong>：表示颜色信息（色调和饱和度），通过两个色差信号（U和V）描述颜色与亮度的差异。<ul><li><strong>U (Cb)</strong>：蓝色与亮度的差值（B - Y）。</li><li><strong>V (Cr)</strong>：红色与亮度的差值（R - Y）。</li></ul></li><li><strong>类比</strong>：相当于给灰度图“上色”的指令。单独看U或V分量时，是模糊的色度图（类似低分辨率的彩色蒙版）。</li><li><strong>示例</strong>：红色区域V值高，蓝色区域U值高，绿色通过U和V的组合间接表示。</li></ul><hr><h3 id="3-与RGB图片的对比"><a href="#3-与RGB图片的对比" class="headerlink" title="3.与RGB图片的对比"></a>3.与RGB图片的对比</h3><table><thead><tr><th><strong>RGB图片</strong></th><th><strong>YUV文件</strong></th></tr></thead><tbody><tr><td>每个像素由R、G、B三个值表示</td><td>Y、U、V可能以不同采样率存储（如YUV420）</td></tr><tr><td>直接描述颜色</td><td>分离亮度和色度，便于压缩</td></tr><tr><td>文件较大</td><td>色度可下采样（如U&#x2F;V分辨率减半），节省空间</td></tr></tbody></table><hr><h3 id="4-实际存储示例（YUV420格式）"><a href="#4-实际存储示例（YUV420格式）" class="headerlink" title="4.实际存储示例（YUV420格式）"></a>4.实际存储示例（YUV420格式）</h3><ul><li><strong>Y</strong>：每像素一个值（分辨率&#x3D;原图）。</li><li><strong>U&#x2F;V</strong>：每2x2像素共享一个U和V值（分辨率&#x3D;原图的1&#x2F;4）。</li><li><strong>效果</strong>：人眼对亮度更敏感，这种压缩对视觉影响小。</li></ul><hr><h3 id="5-总结"><a href="#5-总结" class="headerlink" title="5.总结"></a>5.总结</h3><ul><li><strong>Y</strong>：黑白细节，决定清晰度。</li><li><strong>U&#x2F;V</strong>：颜色信息，决定色调。</li><li><strong>关系</strong>：YUV通过牺牲部分色度精度，用更小体积存储接近RGB的视觉内容。</li></ul><p>注：以下是一个将 rgb 的图像格式转换为 yuv420 图像格式的 demo：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * gcc make_yuv.c -o make_yuv</span></span><br><span class="line"><span class="comment"> * ./make_yuv</span></span><br><span class="line"><span class="comment"> * 生成文件: yellow_red_64x64.yuv</span></span><br><span class="line"><span class="comment"> * @note 用于了解 yuv420p 文件格式，以及和 rgb 图像元素的对应关系</span></span><br><span class="line"><span class="comment"> * @note: ffplay -f rawvideo -pixel_format yuv420p -video_size 64x64 yellow_red_64x64.yuv 可查看 yuv 图片内容</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdint.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> W 64</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> H 64</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> HALF_W (W/2)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> HALF_H (H/2)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 标准 BT.601 转换系数 */</span></span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">uint8_t</span> <span class="title function_">RGB_TO_Y</span><span class="params">(<span class="type">uint8_t</span> r, <span class="type">uint8_t</span> g, <span class="type">uint8_t</span> b)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> (<span class="type">uint8_t</span>)( ( <span class="number">66</span> * r + <span class="number">129</span> * g +  <span class="number">25</span> * b + <span class="number">128</span>) &gt;&gt; <span class="number">8</span>) + <span class="number">16</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">uint8_t</span> <span class="title function_">RGB_TO_U</span><span class="params">(<span class="type">uint8_t</span> r, <span class="type">uint8_t</span> g, <span class="type">uint8_t</span> b)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> (<span class="type">uint8_t</span>)( (<span class="number">-38</span> * r -  <span class="number">74</span> * g + <span class="number">112</span> * b + <span class="number">128</span>) &gt;&gt; <span class="number">8</span>) + <span class="number">128</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">uint8_t</span> <span class="title function_">RGB_TO_V</span><span class="params">(<span class="type">uint8_t</span> r, <span class="type">uint8_t</span> g, <span class="type">uint8_t</span> b)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> (<span class="type">uint8_t</span>)( (<span class="number">112</span> * r -  <span class="number">94</span> * g -  <span class="number">18</span> * b + <span class="number">128</span>) &gt;&gt; <span class="number">8</span>) + <span class="number">128</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">/* 申请 Y,U,V 三个平面 */</span></span><br><span class="line">    <span class="type">uint8_t</span> *y = <span class="built_in">malloc</span>(W * H);</span><br><span class="line">    <span class="type">uint8_t</span> *u = <span class="built_in">malloc</span>(HALF_W * HALF_H);</span><br><span class="line">    <span class="type">uint8_t</span> *v = <span class="built_in">malloc</span>(HALF_W * HALF_H);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 1. 填充 Y */</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> row = <span class="number">0</span>; row &lt; H; ++row) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> col = <span class="number">0</span>; col &lt; W; ++col) &#123;</span><br><span class="line">            <span class="keyword">if</span> (col &lt; W/<span class="number">2</span>) &#123;               <span class="comment">/* 左侧黄色 */</span></span><br><span class="line">                y[row * W + col] = RGB_TO_Y(<span class="number">255</span>, <span class="number">255</span>, <span class="number">0</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;                       <span class="comment">/* 右侧红色 */</span></span><br><span class="line">                y[row * W + col] = RGB_TO_Y(<span class="number">255</span>, <span class="number">0</span>,   <span class="number">0</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 2. 填充 U/V（2×2 平均后下采样） */</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> row = <span class="number">0</span>; row &lt; HALF_H; ++row) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> col = <span class="number">0</span>; col &lt; HALF_W; ++col) &#123;</span><br><span class="line">            <span class="type">int</span> even_row = row * <span class="number">2</span>;</span><br><span class="line">            <span class="type">int</span> even_col = col * <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">/* 取 2×2 块左上角像素的颜色做近似 */</span></span><br><span class="line">            <span class="type">uint8_t</span> r, g, b;</span><br><span class="line">            <span class="keyword">if</span> (even_col &lt; W/<span class="number">2</span>) &#123;          <span class="comment">/* 黄色 */</span></span><br><span class="line">                r = <span class="number">255</span>; g = <span class="number">255</span>; b = <span class="number">0</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;                       <span class="comment">/* 红色 */</span></span><br><span class="line">                r = <span class="number">255</span>; g = <span class="number">0</span>;   b = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            u[row * HALF_W + col] = RGB_TO_U(r, g, b);</span><br><span class="line">            v[row * HALF_W + col] = RGB_TO_V(r, g, b);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 3. 写入文件 */</span></span><br><span class="line">    FILE *fp = fopen(<span class="string">&quot;yellow_red_64x64.yuv&quot;</span>, <span class="string">&quot;wb&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (!fp) &#123; perror(<span class="string">&quot;fopen&quot;</span>); <span class="keyword">return</span> <span class="number">1</span>; &#125;</span><br><span class="line"></span><br><span class="line">    fwrite(y, <span class="number">1</span>, W * H, fp);</span><br><span class="line">    fwrite(u, <span class="number">1</span>, HALF_W * HALF_H, fp);</span><br><span class="line">    fwrite(v, <span class="number">1</span>, HALF_W * HALF_H, fp);</span><br><span class="line">    fclose(fp);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">free</span>(y); <span class="built_in">free</span>(u); <span class="built_in">free</span>(v);</span><br><span class="line">    <span class="built_in">puts</span>(<span class="string">&quot;yellow_red_64x64.yuv 已生成&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="附4：PulseAudio-中的-Sink-和-Source-详解"><a href="#附4：PulseAudio-中的-Sink-和-Source-详解" class="headerlink" title="附4：PulseAudio 中的 Sink 和 Source 详解"></a>附4：PulseAudio 中的 Sink 和 Source 详解</h2><p>在 PulseAudio 音频系统中，<strong>sink</strong> 和 <strong>source</strong> 是两个核心概念，它们代表了音频流的两个不同方向：</p><h3 id="1-Sink（接收器）"><a href="#1-Sink（接收器）" class="headerlink" title="1.Sink（接收器）"></a>1.Sink（接收器）</h3><ul><li><strong>定义</strong>：音频输出设备（播放设备）</li><li><strong>功能</strong>：接收音频流并将其播放出来</li><li><strong>对应设备</strong>：<ul><li>扬声器（内置或外接）</li><li>耳机</li><li>HDMI&#x2F;DisplayPort 音频输出</li><li>蓝牙音频设备</li></ul></li><li><strong>命令</strong>：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacmd list-sinks  # 列出所有可用的音频输出设备</span><br></pre></td></tr></table></figure><ul><li><strong>示例输出</strong>：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">name: &lt;alsa_output.pci-0000_00_1f.3.analog-stereo&gt;</span><br></pre></td></tr></table></figure><h3 id="2-Source（源）"><a href="#2-Source（源）" class="headerlink" title="2.Source（源）"></a>2.Source（源）</h3><ul><li><strong>定义</strong>：音频输入设备（录制设备）</li><li><strong>功能</strong>：捕获音频流并将其发送到系统</li><li><strong>对应设备</strong>：<ul><li>麦克风（内置或外接）</li><li>线路输入</li><li>蓝牙麦克风</li><li>系统音频捕获（监视器）</li></ul></li><li><strong>命令</strong>：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacmd list-sources  # 列出所有可用的音频输入设备</span><br></pre></td></tr></table></figure><ul><li><strong>示例输出</strong>：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">name: &lt;alsa_input.pci-0000_00_1f.3.analog-stereo&gt;</span><br></pre></td></tr></table></figure><h3 id="3-特殊类型：监视器（Monitor）"><a href="#3-特殊类型：监视器（Monitor）" class="headerlink" title="3.特殊类型：监视器（Monitor）"></a>3.特殊类型：监视器（Monitor）</h3><p>在 PulseAudio 中，还有一个重要的概念是<strong>监视器（Monitor）</strong>：</p><ul><li><strong>定义</strong>：一种特殊的 source（输入设备）</li><li><strong>功能</strong>：捕获从 sink（输出设备）播放的音频</li><li><strong>命名规则</strong>：<code>&lt;sink名称&gt;.monitor</code></li><li><strong>用途</strong>：<ul><li>录制系统声音（如音乐、通知音）</li><li>创建虚拟音频设备</li></ul></li><li><strong>示例</strong>：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">name: &lt;alsa_output.pci-0000_00_1f.3.analog-stereo.monitor&gt;</span><br></pre></td></tr></table></figure><h3 id="4-实际应用场景"><a href="#4-实际应用场景" class="headerlink" title="4.实际应用场景"></a>4.实际应用场景</h3><h4 id="1-设置默认输出设备（sink）"><a href="#1-设置默认输出设备（sink）" class="headerlink" title="1. 设置默认输出设备（sink）"></a>1. 设置默认输出设备（sink）</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacmd set-default-sink alsa_output.pci-0000_00_1f.3.analog-stereo</span><br></pre></td></tr></table></figure><h4 id="2-设置默认输入设备（source）"><a href="#2-设置默认输入设备（source）" class="headerlink" title="2. 设置默认输入设备（source）"></a>2. 设置默认输入设备（source）</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacmd set-default-source alsa_input.pci-0000_00_1f.3.analog-stereo</span><br></pre></td></tr></table></figure><h4 id="3-录制系统声音（使用监视器）"><a href="#3-录制系统声音（使用监视器）" class="headerlink" title="3. 录制系统声音（使用监视器）"></a>3. 录制系统声音（使用监视器）</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ffmpeg -f pulse -i alsa_output.pci-0000_00_1f.3.analog-stereo.monitor output.wav</span><br></pre></td></tr></table></figure><h4 id="4-查看所有设备状态"><a href="#4-查看所有设备状态" class="headerlink" title="4. 查看所有设备状态"></a>4. 查看所有设备状态</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># 完整输出设备列表</span><br><span class="line">pacmd list-sinks</span><br><span class="line"></span><br><span class="line"># 完整输入设备列表</span><br><span class="line">pacmd list-sources</span><br></pre></td></tr></table></figure><h3 id="5-常见问题解决"><a href="#5-常见问题解决" class="headerlink" title="5.常见问题解决"></a>5.常见问题解决</h3><p>如果您遇到 <code>未指定有效的命令</code> 错误，可能是以下原因：</p><ol><li><strong>PulseAudio 未运行</strong>：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pulseaudio --start</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>命令语法错误</strong>：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># 正确命令</span><br><span class="line">pactl get-default-sink</span><br><span class="line">pactl get-default-source</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>权限问题</strong>：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo usermod -aG audio $USER</span><br></pre></td></tr></table></figure><ol start="4"><li><strong>PulseAudio 配置问题</strong>：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rm ~/.config/pulse/*</span><br><span class="line">pulseaudio -k</span><br><span class="line">pulseaudio --start</span><br></pre></td></tr></table></figure><p>理解 sink 和 source 的概念对于在 Linux 系统上管理音频设备至关重要，特别是在开发音频应用程序或进行多媒体录制时。</p><h2 id="附5：简单-demo"><a href="#附5：简单-demo" class="headerlink" title="附5：简单 demo"></a>附5：简单 demo</h2><p>在学习 ffmpeg 的过程中，我尽可能将一些简单的功能抽取了单独的 demo。See：<a class="link"   href="https://github.com/ssk-wh/ffmpeg-demo" >https://github.com/ssk-wh/ffmpeg-demo<i class="fas fa-external-link-alt"></i></a></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：本指南默认您已掌握C语言基础和Linux命令行操作。若不会使用&lt;code&gt;gcc&lt;/code&gt;，建议先学习《C语言：从入门到内存泄漏》。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;</summary>
    
    
    
    <category term="FFmpeg" scheme="https://ssk-wh.github.io/categories/FFmpeg/"/>
    
    
    <category term="ffmpeg" scheme="https://ssk-wh.github.io/tags/ffmpeg/"/>
    
    <category term="audio" scheme="https://ssk-wh.github.io/tags/audio/"/>
    
    <category term="video" scheme="https://ssk-wh.github.io/tags/video/"/>
    
    <category term="aac" scheme="https://ssk-wh.github.io/tags/aac/"/>
    
    <category term="mp4" scheme="https://ssk-wh.github.io/tags/mp4/"/>
    
    <category term="yuv" scheme="https://ssk-wh.github.io/tags/yuv/"/>
    
    <category term="yuv420" scheme="https://ssk-wh.github.io/tags/yuv420/"/>
    
    <category term="pulse" scheme="https://ssk-wh.github.io/tags/pulse/"/>
    
  </entry>
  
  <entry>
    <title>如何定位linux无法启动</title>
    <link href="https://ssk-wh.github.io/2025/0361178.html"/>
    <id>https://ssk-wh.github.io/2025/0361178.html</id>
    <published>2025-03-20T14:22:54.000Z</published>
    <updated>2025-04-17T09:50:09.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>注：主要以UOS举例，其他发行版步骤类似，只是细节可能有些不同。</p></blockquote><h2 id="引导文件丢失"><a href="#引导文件丢失" class="headerlink" title="引导文件丢失"></a>引导文件丢失</h2><p>如果用户的引导文件（如<code>/boot/grub/grub.cfg</code>文件）被损坏或删除，系统将无法正常启动。启动系统时可能出现图中的场景：</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/0.1.png"                                        ></p><p>接下来，我将针对这一场景介绍相应的解决办法：</p><p>在 GRUB 的终端交互界面中，使用 <code>ls</code> 命令查看当前块设备信息，并找到用户存放 boot 数据的分区位置（由于内核和 initrd 均位于 boot 目录下，后续步骤中需要用到该分区信息）。例如，若 boot 数据位于 <code>(hd0,gpt3)</code>，则通过输入 <code>set root=(hd0,gpt3)</code> 命令来指定该设备。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/0.2.png"                                        ></p><p>接下来，你需要获取一份与当前系统版本相同的<code>grub.cfg</code>配置文件，并参考其内容，依次执行其中的GRUB命令。通常情况下，这些命令主要包括加载内核和<code>initrd</code>。以下是其他机器的<code>/boot/grub/grub.cfg</code>文件内容，供你参考。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/0.4.png"                                        ></p><p>重点关注<code>linux</code>和<code>initrd</code>这两行内容。<code>linux</code>命令用于指定引导的内核及其参数，不同机器的内核参数会有所不同。其中，<code>root</code>和<code>ostree</code>参数尤为重要（这里针对的是UOS磐石镜像，其他发行版一般只需指定<code>root</code>参数即可）。</p><p>在UOS磐石镜像中，<code>ostree</code>参数指向系统部署的一个特定目录，通常位于<code>/persistent/ostree/data/[commit]/checkout</code>。你可以先通过<code>ls</code>命令查看当前系统中实际的目录路径（幸好可以补全，否则手动输入<code>ostree</code>参数可能会让人感到头疼……-_ -）。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/0.3.png"                                        ></p><p>接下来，根据示例<code>grub.cfg</code>的内容，使用<code>linux</code>命令指定<code>root=/dev/sda3</code>和<code>ostree=...</code>，注意在<code>ostree</code>参数中需去掉实际目录前的<code>/persistent</code>路径前缀。然后，通过<code>initrd</code>命令加载实际的<code>initrd</code>文件。完成后，使用<code>boot</code>命令开始加载当前设置的引导信息。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/0.5.png"                                        ></p><p>如果输入无误，此时即可进入系统。但当前系统的<code>/boot/grub/grub.cfg</code>仍处于损坏状态。进入系统后，你可以通过运行<code>grub-mkconfig -o /boot/grub/grub.cfg</code>或<code>update-grub</code>命令来重建引导信息。之后，系统再次启动时，便无需再手动执行上述命令了。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/0.6.png"                                        ></p><p>至此，引导修复结束。</p><h2 id="启动后光标一直闪烁"><a href="#启动后光标一直闪烁" class="headerlink" title="启动后光标一直闪烁"></a>启动后光标一直闪烁</h2><p>在grub引导项点击确认后,之后系统一直处于光标闪动的状态.</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/1.png"                                        ></p><p>这种情况，根据经验推测，可能是 <code>/boot</code> 目录下的 <code>initrd</code> 文件未被正确找到或加载。首先应检查 GRUB 引导参数中 <code>initrd</code> 相关配置是否存在错误。在 GRUB 菜单中，按 <code>e</code> 键进入引导参数编辑页面，仔细查看以 <code>initrd</code> 开头的那一行内容，确认其路径和文件名是否准确无误。<code>initrd</code> 是一个临时的根文件系统，内核在启动后会将其挂载，它在内核启动的早期阶段提供必要的驱动程序和工具，以便内核能够顺利访问实际的根文件系统。</p><p>针对此类问abbrlink题，可以在 GRUB 参数编辑页面中，在以 <code>linux</code> 开头的那一行末尾添加 <code>&quot;debug&quot;</code> 参数。若内核因找不到 <code>initrd</code> 而触发 panic，其报错内容大致如下：&#96;</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/2.png"                                        ></p><p>上图可以明显看到rootfs 无法挂载,这种情况,只需分析为何找不到 initrd 的原因即可.</p><h2 id="启动后卡住"><a href="#启动后卡住" class="headerlink" title="启动后卡住"></a>启动后卡住</h2><p>有时候系统启动后会卡在某个服务上，屏幕上只显示系统 logo 闪烁，而看不到相关日志。这种情况下，可以删除 GRUB 参数中的 <code>quiet splash</code>，以便查看详细的启动日志.</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/3.png"                                        ></p><p>如果希望进一步查看内核日志，可以将 <code>loglevel</code> 参数的值设置为最高级别（7，0 是最低级别）。在 GRUB 菜单中，按 <code>e</code> 键进入引导参数编辑页面，然后在 <code>linux</code> 开头的行中添加 <code>loglevel=7</code> 参数，最后按 <code>F10</code> 键加载引导信息，即可看到更详细的日志信息。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/4.png"                                        ></p><h2 id="启动后系统进入emergency-mode"><a href="#启动后系统进入emergency-mode" class="headerlink" title="启动后系统进入emergency mode"></a>启动后系统进入emergency mode</h2><p>系统启动后进入了紧急模式怎么办</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/4.1.png"                                        ></p><p>按下 Ctrl + Alt + Del 组合键重启系统。在 <code>GRUB</code> 引导菜单页面中，选择当前的引导项，然后按下 <code>e</code> 键进入 <code>GRUB</code> 编辑模式。在以 <code>linux</code> 开头的行的末尾，添加 <code>init=/bin/bash</code> 参数。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/4.2.png"                                        ></p><p>接下来，按下 <code>F10</code> 键以加载引导程序，使系统启动后直接进入 <code>bash</code> 环境，而不是执行默认的 <code>/sbin/init</code> 进程。这样可以为我们后续的调试工作提供便利。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/4.3.png"                                        ></p><p>进入此阶段后，我们需要深入分析导致系统进入紧急模式（Emergency Mode）的原因。实际上，紧急模式是由 <code>systemd</code> 提供的一个特殊服务。</p><p>在 <code>systemd</code> 的启动流程中，如果某些关键服务启动失败，系统会触发 <code>emergency.target</code>，进而执行 <code>emergency.service</code>，最终进入紧急模式。以下是可能导致系统进入紧急模式的服务故障情况：</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/4.4.png"                                        ></p><p>接下来，我们将深入分析系统的运行日志，以查找是否有相关服务的报错信息。通过执行 <code>**journalctl**</code> 命令，可以查询 <code>systemd</code> 服务的运行历史日志。在日志中，我们能够定位到问题发生的具体时间点。从日志信息中可以发现，<code>local-fs.target</code> 的依赖条件未能满足，导致该目标未能成功完成初始化。显然，这会触发系统的紧急模式（Emergency Mode）。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/4.5.png"                                        ></p><p>在进一步分析系统运行日志时，我们注意到日志中显示的红色超时信息，特别是 <code>dev-disk-by***.service</code> 服务的超时问题。通过查询相关资料，我们了解到 <code>dev-disk-by***.service</code> 是由 <code>udev</code> 规则触发并由 <code>systemd</code> 动态生成的服务，主要用于管理设备挂载。</p><p>系统中的设备挂载配置大概分为两部分：一部分在 <code>initrd</code> 中，另一部分在 <code>/etc/fstab</code> 文件中。从日志中可以看到，由于某个块设备的 UUID 挂载失败，导致 <code>local-fs.target</code> 未能完成初始化，进而触发了紧急模式（Emergency Mode）。</p><p>为了进一步排查问题，可以查询系统中所有块设备的 UUID。以下是常用的命令：</p><ol><li><code>lsblk -f</code>：列出所有块设备及其文件系统信息，包括 UUID。</li><li><code>blkid</code>：直接显示所有块设备的 UUID，包括文件系统类型和分区信息。</li></ol><p>执行以下命令来查询所有块设备的 UUID：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">blkid -f</span><br></pre></td></tr></table></figure><p>该命令将列出系统中所有块设备的 UUID，帮助我们确定挂载失败的具体设备</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/4.6.png"                                        ></p><p>在排查过程中，我们发现 <code>/dev/vda1</code> 的 UUID 与报错信息中显示的 UUID 仅相差一位。这引起了我们的怀疑，于是进一步检查 <code>/etc/fstab</code> 文件。果不其然，问题出在这里——<code>/etc/fstab</code> 中的 UUID 写错了。</p><p>实际上，这是为了展示分析步骤而手动修改的（故意引入了一个错误）。将 <code>/etc/fstab</code> 中多余的字符“a”删除后，重新启动系统，一切便恢复正常了。</p><p>(注：实际是我手动改的，为了展示 &#x2F;etc&#x2F;fstab 内容错误导致系统无法正常启动时的分析步骤)</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/4.7.png"                                        ></p><p>删除 UUID 最前面的多余字符“a”，保存文件后重启系统，随后系统便能够正常启动了。</p><h2 id="找不到-init进程"><a href="#找不到-init进程" class="headerlink" title="找不到 init进程"></a>找不到 init进程</h2><p>如果开机后能看到 GRUB 引导项，但系统无法正常启动，通常问题不大。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/5.png"                                        ></p><p>选中对应的引导项，按 <code>e</code> 键进入引导参数编辑页面。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/6.png"                                        ></p><p>如果系统在确认引导项后卡死，或者进入 <code>initramfs</code> 的 shell 页面，需要具体分析问题。例如，当系统实际的 <code>/</code> 无法找到时，通常会在 <code>initramfs</code> 阶段进入 <code>busybox</code> 内置的 shell 环境。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/7.png"                                        ></p><p>在这种情况下，可以查看内核参数，确认是否存在参数错误。例如，在 GRUB 编辑页面中，<code>ostree=</code> 参数的值被我故意在最后添加一个字母,从而让系统启动出错,方面展开接下来的排错过程。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/8.png"                                        ></p><p>此时，需要确认对应的目录是否存在。注意，<code>ostree</code> 参数的值通常需要以 <code>/root/persistent</code> 为前缀，之所以如此,当然是因为我提前看过initrd中对 ostree 参数的处理啦。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/9.png"                                        ></p><p>该问题源于系统启动过程中检查的<code>checkouta</code>目录不存在，而实际正确的目录名称应为<code>checkout</code>，这导致系统启动失败。<br>在排查类似问题时，建议在内核参数中添加<code>debug</code>选项，以获取 initramfs 初始化阶段的详细调试信息。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/10.png"                                        ></p><p>之后按下 <code>F10</code>,加载引导信息,当前这里的<code>checkout</code>我仍然修改为<code>checkouta</code>,方便我们复现问题并分析,不出意外,我们又来到了熟悉的页面</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/11.png"                                        ></p><p>在 initramfs 的 shell 中，可以通过查看 <code>/run/initramfs/initramfs.debug</code> 文件来分析日志。日志文件中包含了详细的启动过程信息，通过查看日志可以找到报错的地方。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/12.png"                                        ></p><p>这里都是熟悉的 bash 脚本,相信阅读对大家来说不会产生障碍,接下来就是找到报错的地方</p><p>(initramfs 中存在许多天然断点，这些断点可以通过在 GRUB 参数中添加 <code>break=[break_point]</code> 来触发。<code>break_point</code> 的取值包括以下几种：</p><ul><li>top：initramfs 启动过程的最开始阶段。</li><li>modules：加载必要的内核模块阶段。</li><li>premount：挂载根文件系统之前的准备阶段。</li><li>mount：挂载根文件系统的阶段。</li><li>mountroot：挂载根文件系统的具体实现阶段。</li><li>bottom：initramfs 启动过程的最后阶段。</li></ul><p>通过在特定阶段暂停启动过程，可以方便地检查和干预启动过程)。</p><p>我一般习惯先开最后,再看开头,翻到最后面,可以明显看到是 initramfs 阶段找不到对应的<code>init</code>进程(其实就是找不到系统的<code>/</code>),所以就单独启动了一个<code>sh</code>终端,方便使用者自行分析。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/13.png"                                        ></p><p>系统无法启动的原因已经找到,接下来就是查看为何找不到<code>init</code>进程,这需要我们找到最初开始报错的地方,查找起来实际还是很轻松的,如下图,果然是这里异常了,知道了问题我们在下次启动时,手动调整对应的<code>grub</code>参数即可解决.</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/14.png"                                        ></p><h2 id="分阶段调试"><a href="#分阶段调试" class="headerlink" title="分阶段调试"></a>分阶段调试</h2><p>前面说了,initramfs 中存在很多<code>maybe_break</code>的天然断点,按照先后执行顺序如下所示:</p><ul><li><ol><li>top</li></ol></li></ul><p>作用：这是 initramfs 启动过程的最开始阶段。</p><p>调试用途：如果在启动参数中设置 <code>break=top</code>，系统会在 initramfs 的初始化脚本开始执行时暂停，方便用户检查早期启动环境。</p><ul><li><ol start="2"><li>modules</li></ol></li></ul><p>作用：在这个阶段，系统会加载必要的内核模块。这些模块通常是根据 <code>/conf/modules</code> 文件的配置加载的，尽管在现代系统中，<code>/conf/modules</code> 文件可能不存在，因为很多模块已经通过 udev 动态加载。</p><p>调试用途：如果设置 <code>break=modules</code>，系统会在加载模块之前暂停，用户可以检查模块加载情况或干预模块加载过程。</p><ul><li><ol start="3"><li>premount</li></ol></li></ul><p>作用：这个阶段是为了挂载根文件系统做准备。它会运行 <code>/scripts/init-premount</code> 目录下的脚本，这些脚本通常用于初始化设备和文件系统，例如启动 udev 守护进程、处理 USB 或 FireWire 设备等。</p><p>调试用途：如果设置 <code>break=premount</code>，系统会在挂载根文件系统之前暂停，用户可以检查设备初始化情况。</p><ul><li><ol start="4"><li>mount</li></ol></li></ul><p>作用：这个阶段的主要任务是挂载根文件系统。它会执行 <code>/scripts/local</code> 中的 <code>mountroot</code> 函数，检测根文件系统的类型并将其挂载到 <code>$&#123;rootmnt&#125;</code>。</p><p>调试用途：如果设置 <code>break=mount</code>，系统会在挂载根文件系统之前暂停，用户可以检查挂载参数或干预挂载过程。</p><ul><li><ol start="5"><li>mountroot</li></ol></li></ul><p>作用：这个阶段是挂载根文件系统的具体实现阶段。它会根据启动参数（如 <code>root=</code>）解析根文件系统的设备路径，并尝试将其挂载到指定的挂载点。</p><p>调试用途：如果设置 <code>break=mountroot</code>，系统会在挂载根文件系统的过程中暂停，用户可以检查挂载失败的原因。</p><ul><li><ol start="6"><li>bottom</li></ol></li></ul><p>作用：这是 initramfs 启动过程的最后阶段。在这个阶段，系统会执行 <code>/scripts/init-bottom</code> 目录下的脚本，这些脚本通常用于清理和完成启动过程中的最后一步操作。</p><p>调试用途：如果设置 <code>break=bottom</code>，系统会在启动过程的最后阶段暂停，用户可以检查启动过程中的最后一步操作。</p><p>通过在 <code>grub</code> 中添加<code>break=[break_point]</code>可以让启动过程分别停止在不同的阶段并启动一个 <code>sh</code>环境,更方便我们确认是哪个环节出现了问题。</p><p>在这个<code>sh</code>环境中，当我们检查完系统信息后，可以执行 <code>exit</code>命令，此时系统会继续启动。</p><h2 id="分析-initrd内容"><a href="#分析-initrd内容" class="headerlink" title="分析 initrd内容"></a>分析 initrd内容</h2><p>当我们安装内核获驱动时,我们一般会执行 <code>update-initramfs</code> 命令,生成新的 <code>initrd</code>,偶尔也会出现生成的<code>initrd</code>内容异常的问题.我们可以对比 update-initramfs 前后的 initrd 内容.</p><p>initrd 存放于 <code>/boot/initrd.img-* </code>,大小一般在200MB以内, 通过<code>lsinitramfs</code>命令查看其内容,通过对比更新前后的 initrd 可以发现绝大部分问题,</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/15.png"                                        ></p><p>如果确实是<code>update-initramfs</code>命令执行后 initrd 内容出现了问题,也不要恐慌,update-initramfs 也只是一个普通的 bash 脚本而已,分析其源码实现即可,这也是我们了解系统启动过程的一个绝好机会.</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361178/16.png"                                        ></p><h2 id="卡死无输出-无日志-键鼠无反应"><a href="#卡死无输出-无日志-键鼠无反应" class="headerlink" title="卡死无输出,无日志,键鼠无反应"></a>卡死无输出,无日志,键鼠无反应</h2><p>实话说,很少见,一般都出现在未适配的硬件上面,这种情况需要电脑有串口(电脑没有串口怎么办?凉拌),通过串口查询内核输出的日志,串口的使用网络上有很多资料,因为问题不太常见所以在碰到后您可以直接百度搜索如何配置串口参数,并联系专业的内核研发人员陪同定位.</p><h2 id="启动阶段"><a href="#启动阶段" class="headerlink" title="启动阶段"></a>启动阶段</h2><p>在 grub 编辑阶段，添加参数 <code>init=/bin/bash</code>，此时处于刚挂载根文件系统，系统中的其他进程都没还启动。<br>添加参数 <code>systemd.debug-shell=1</code>，在启动后，systemd 会占用 tty9，可以按 Ctrl + Alt + F9 进入此终端，此时用户默认具有 root 权限。<br>添加参数 <code>break=bottom </code>,然后在接下来的 sh 环境中,先找到根文件系统，之后设置 <code>/etc/systemd/system/default.target</code> 指向的不同 target,可以让后续 systemd 在启动时运行到不同的级别。<br>或者添加参数  <code>systemd.unit=multi-user.target</code>，效果和上面的操作等同，您也可以设置为其他的 target。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>想到哪里就记到哪里了,后续再随时补充…</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;注：主要以UOS举例，其他发行版步骤类似，只是细节可能有些不同。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;引导文件丢失&quot;&gt;&lt;a href=&quot;#引导文件丢失&quot; class=&quot;headerlink&quot; title=&quot;引导文件丢失&quot;&gt;&lt;/a&gt;引</summary>
      
    
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/categories/Linux/"/>
    
    
    <category term="grub" scheme="https://ssk-wh.github.io/tags/grub/"/>
    
    <category term="ostree" scheme="https://ssk-wh.github.io/tags/ostree/"/>
    
    <category term="initrd" scheme="https://ssk-wh.github.io/tags/initrd/"/>
    
    <category term="initramfs" scheme="https://ssk-wh.github.io/tags/initramfs/"/>
    
    <category term="kernel" scheme="https://ssk-wh.github.io/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>Linux 无障碍支持与自动化测试实战：AT-SPI + D-Bus 的实践指南</title>
    <link href="https://ssk-wh.github.io/2025/0361177.html"/>
    <id>https://ssk-wh.github.io/2025/0361177.html</id>
    <published>2025-03-19T15:43:48.000Z</published>
    <updated>2025-04-02T05:37:13.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Linux 桌面环境中，无障碍（Accessibility）支持是提升用户体验的重要组成部分。本文将深入探讨 Linux 系统中无障碍支持的实现原理，并结合实际案例，展示如何利用这些技术进行自动化测试和开发。</p><span id="more"></span><h3 id="一、AT-SPI-与-D-Bus-的核心作用"><a href="#一、AT-SPI-与-D-Bus-的核心作用" class="headerlink" title="一、AT-SPI 与 D-Bus 的核心作用"></a>一、AT-SPI 与 D-Bus 的核心作用</h3><p>Linux 系统中的无障碍功能主要通过 AT-SPI（Assistive Technology Service Provider Interface）实现，而 AT-SPI 服务与应用程序之间的通信则依赖于 D-Bus 这一强大的 IPC（进程间通信）机制。</p><ul><li>AT-SPI 提供了统一的接口，允许辅助技术（如屏幕阅读器、语音控制工具）访问应用程序的用户界面元素。</li><li>D-Bus 作为系统级的消息总线，为 AT-SPI 服务与应用程序之间提供了高效可靠的通信渠道。</li></ul><h3 id="二、安装与启动无障碍服务"><a href="#二、安装与启动无障碍服务" class="headerlink" title="二、安装与启动无障碍服务"></a>二、安装与启动无障碍服务</h3><h4 id="1-安装必要的软件包"><a href="#1-安装必要的软件包" class="headerlink" title="1. 安装必要的软件包"></a>1. 安装必要的软件包</h4><p>在 Debian&#x2F;Ubuntu 系统中，我们可以使用以下命令安装 at-spi2-core：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">sudo</span> apt install at-spi2-core</span><br></pre></td></tr></table></figure><h4 id="2-启动-AT-SPI-服务"><a href="#2-启动-AT-SPI-服务" class="headerlink" title="2. 启动 AT-SPI 服务"></a>2. 启动 AT-SPI 服务</h4><p>安装完成后，我们需要确保 AT-SPI 服务（at-spi2-registryd）已经启动。你可以手动启动服务：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ /usr/libexec/at-spi2-registryd</span><br></pre></td></tr></table></figure><h3 id="三、检查无障碍服务状态"><a href="#三、检查无障碍服务状态" class="headerlink" title="三、检查无障碍服务状态"></a>三、检查无障碍服务状态</h3><ol><li>使用 d-feet 工具连接到 Session Bus，查找 <code>org.a11y.Bus</code> 服务。</li></ol><p><img                         lazyload                       alt="image"                       data-src="/2025/0361177/1.png"                                        ></p><ol start="2"><li>如果服务状态为 <code>Enabled: false</code>，我们需要手动启用：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ busctl --user set-property org.a11y.Bus /org/a11y/bus org.a11y.Status IsEnabled b <span class="literal">true</span></span><br></pre></td></tr></table></figure><h3 id="四、获取应用的-Bus-地址"><a href="#四、获取应用的-Bus-地址" class="headerlink" title="四、获取应用的 Bus 地址"></a>四、获取应用的 Bus 地址</h3><ol><li>通过 d-feet 获取 a11y 服务的 bus address</li></ol><p><img                         lazyload                       alt="image"                       data-src="/2025/0361177/2.png"                                        ></p><ol start="2"><li>使用 d-feet 的 “连接到其他总线” 功能，输入之前获取的 Bus 地址。注意：地址前后的单引号需要去掉。</li></ol><p><img                         lazyload                       alt="image"                       data-src="/2025/0361177/3.png"                                        ></p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361177/4.png"                                        ></p><ol start="3"><li>成功连接后，切换到当前 Bus,并通过 Registry 服务的 GetChildren 方法查看已注册的应用程序。</li></ol><p><img                         lazyload                       alt="image"                       data-src="/2025/0361177/5.png"                                        ></p><ol start="4"><li>这些应用可以通过无障碍服务进行页面元素的访问，包括但不限于：</li></ol><ul><li>模拟点击</li><li>文本读取</li><li>获取屏幕坐标</li><li>监听控件状态变化</li></ul><p><img                         lazyload                       alt="image"                       data-src="/2025/0361177/6.png"                                        ></p><h3 id="五、高级调试与事件监听"><a href="#五、高级调试与事件监听" class="headerlink" title="五、高级调试与事件监听"></a>五、高级调试与事件监听</h3><h4 id="1-使用-dbus-monitor-监听事件"><a href="#1-使用-dbus-monitor-监听事件" class="headerlink" title="1. 使用 dbus-monitor 监听事件"></a>1. 使用 dbus-monitor 监听事件</h4><p>通过 dbus-monitor 工具，我们可以实时监听特定应用的状态变化。请根据你的系统环境调整 Bus 地址：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ dbus-monitor --address unix:path=/run/user/1000/at-spi/bus_0,guid=eb7bf1d52d6058ef7a2efe7a67da5f88</span><br></pre></td></tr></table></figure><p>在对应应用进行操作时，终端会显示对应事件，这对自动化测试的开发与调试非常有帮助。</p><p><img                         lazyload                       alt="image"                       data-src="/2025/0361177/7.png"                                        ></p><h3 id="六、实际应用案例"><a href="#六、实际应用案例" class="headerlink" title="六、实际应用案例"></a>六、实际应用案例</h3><h4 id="1-应用截图与自动化测试"><a href="#1-应用截图与自动化测试" class="headerlink" title="1. 应用截图与自动化测试"></a>1. 应用截图与自动化测试</h4><p>结合无障碍功能，我们可以实现以下高级功能：</p><ul><li>精确获取应用窗口的位置和大小信息</li><li>模拟用户操作（如点击、输入）</li><li>截取特定应用区域的屏幕截图（类似 Windows 的 Snipaste 功能）</li></ul><h4 id="2-坐标定位与自动化脚本开发"><a href="#2-坐标定位与自动化脚本开发" class="headerlink" title="2. 坐标定位与自动化脚本开发"></a>2. 坐标定位与自动化脚本开发</h4><p>通过获取应用内部控件的坐标信息，我们可以实现：</p><ul><li>高效的区域截图</li><li>自动化测试脚本</li><li>桌面应用的自动化操作</li></ul><h3 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h3><ol><li>所有需要修改系统服务的操作都需要管理员权限。</li><li>不同 Linux 发行版可能需要安装不同的软件包。</li><li>如果遇到服务无法启动的问题，可以检查系统日志以获取更多信息。<br>通过本文的介绍，相信你已经对 Linux 系统中的无障碍支持有了更深入的理解，并能够将其应用到实际开发场景中。</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 Linux 桌面环境中，无障碍（Accessibility）支持是提升用户体验的重要组成部分。本文将深入探讨 Linux 系统中无障碍支持的实现原理，并结合实际案例，展示如何利用这些技术进行自动化测试和开发。&lt;/p&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/categories/Linux/"/>
    
    
    <category term="Accessibility" scheme="https://ssk-wh.github.io/tags/Accessibility/"/>
    
  </entry>
  
  <entry>
    <title>记录一次系统启动失败的修复过程</title>
    <link href="https://ssk-wh.github.io/2025/0298d22fbe.html"/>
    <id>https://ssk-wh.github.io/2025/0298d22fbe.html</id>
    <published>2025-02-18T20:42:12.000Z</published>
    <updated>2025-02-18T12:48:44.000Z</updated>
    
    <content type="html"><![CDATA[<p>做了一次升级后,正常使用,但是在重启后,系统直接进入了 initramfs 的 bash 中,且在开机时一直按方向键也看不到系统在 grub 引导界面停留</p><span id="more"></span><p>在 initramfs 的 bash 中,使用 blkid 查看块设备信息,找到 Roota 设备,将其挂载到创建的 &#x2F;roota 目录上(因为 boot 目录的数据一般都在 roota 上)<br>进入 roota 目录,可以看到其中存在 boot 目录,编辑 boot&#x2F;grub&#x2F;grub.cfg,找到 menuentry 引导项菜单的内容,首先去掉 quiet splash选项,为了方便调试,额外加了一条 systemd.debug-shell&#x3D;1 的参数<br>此时,忽然发现 grub 参数中还存在 fusec 之类的安全特性参数,这些参数是需要配置安全的定制软件包使用的,可能会导致挂载失败,删除掉它<br>Ctrl + Alt + Del 重启电脑,之后顺利进入系统</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;做了一次升级后,正常使用,但是在重启后,系统直接进入了 initramfs 的 bash 中,且在开机时一直按方向键也看不到系统在 grub 引导界面停留&lt;/p&gt;</summary>
    
    
    
    
    <category term="linux" scheme="https://ssk-wh.github.io/tags/linux/"/>
    
  </entry>
  
  <entry>
    <title>ostree中GPG签名的使用方式</title>
    <link href="https://ssk-wh.github.io/2024/0754520fd3.html"/>
    <id>https://ssk-wh.github.io/2024/0754520fd3.html</id>
    <published>2024-07-11T00:00:00.000Z</published>
    <updated>2024-07-12T03:05:34.000Z</updated>
    
    <content type="html"><![CDATA[<p>OSTree 是一个用于版本化文件系统树的工具，可以将其视为 Git 的文件系统版本。OSTree 不仅支持文件系统的版本管理，还支持使用 GPG 对其提交进行签名，以确保数据的完整性和来源的可信性。下面介绍 GPG 签名的原理和如何在 OSTree 中使用它。</p><span id="more"></span><h2 id="GPG签名简介"><a href="#GPG签名简介" class="headerlink" title="GPG签名简介"></a>GPG签名简介</h2><p>GPG（GNU Privacy Guard）签名是一种用于验证数字内容完整性和真实性的方法。GPG是一个开源的加密软件，广泛用于数据加密和数字签名。</p><h3 id="GPG签名的工作原理"><a href="#GPG签名的工作原理" class="headerlink" title="GPG签名的工作原理"></a>GPG签名的工作原理</h3><h4 id="生成密钥对"><a href="#生成密钥对" class="headerlink" title="生成密钥对"></a>生成密钥对</h4><ul><li>用户生成一对密钥：一个私钥（private key）和一个公钥（public key）。</li><li>私钥用于创建签名，公钥用于验证签名。</li></ul><h4 id="签名过程"><a href="#签名过程" class="headerlink" title="签名过程"></a>签名过程</h4><ul><li>当用户想要对一段数据（如文件、电子邮件等）进行签名时，GPG使用用户的私钥对数据进行哈希运算，生成一个摘要（hash）。</li><li>这个摘要通过私钥加密后，形成签名附加在原始数据上。</li></ul><h4 id="验证签名"><a href="#验证签名" class="headerlink" title="验证签名"></a>验证签名</h4><ul><li>接收者使用签名者的公钥对签名进行解密，得到哈希值。</li><li>接收者对接收到的数据重新计算哈希值，并与解密得到的哈希值进行比较。</li><li>如果两个哈希值一致，说明数据没有被篡改，并且签名确实是由持有私钥的人生成的。</li></ul><h3 id="GPG签名的应用"><a href="#GPG签名的应用" class="headerlink" title="GPG签名的应用"></a>GPG签名的应用</h3><ul><li><strong>软件分发</strong>：开发者使用GPG签名他们发布的软件，用户可以通过验证签名确保下载的软件是原版且未被篡改。</li><li><strong>电子邮件</strong>：使用GPG对电子邮件进行签名，接收者可以确认邮件内容的完整性和发送者的身份。</li><li><strong>文档和合同</strong>：对重要的文件和合同进行数字签名，确保文件在传输过程中的完整性。</li></ul><h3 id="使用GPG签名的步骤"><a href="#使用GPG签名的步骤" class="headerlink" title="使用GPG签名的步骤"></a>使用GPG签名的步骤</h3><h4 id="安装-GPG"><a href="#安装-GPG" class="headerlink" title="安装 GPG"></a>安装 GPG</h4><p> 如果尚未安装 GPG，可以通过以下命令安装：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install gnupg</span><br></pre></td></tr></table></figure><h4 id="生成密钥对-1"><a href="#生成密钥对-1" class="headerlink" title="生成密钥对"></a>生成密钥对</h4><p>如果尚未生成 GPG 密钥对，可以使用以下命令生成：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --full-generate-key</span><br></pre></td></tr></table></figure><p>选择合适的选项并按照提示生成密钥对。</p><h4 id="签名文件"><a href="#签名文件" class="headerlink" title="签名文件"></a>签名文件</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --sign &lt;file&gt;</span><br></pre></td></tr></table></figure><h4 id="验证签名-1"><a href="#验证签名-1" class="headerlink" title="验证签名"></a>验证签名</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --verify &lt;file&gt;.gpg</span><br></pre></td></tr></table></figure><p>通过GPG签名，可以有效地保护数字内容的完整性和真实性，广泛应用于软件开发、安全通信和数据保护等领域。</p><h2 id="OSTree-GPG-签名的原理"><a href="#OSTree-GPG-签名的原理" class="headerlink" title="OSTree GPG 签名的原理"></a>OSTree GPG 签名的原理</h2><p>GPG 签名用于验证数据的完整性和来源。通过对 OSTree 提交进行 GPG 签名，可以确保文件系统树在传输和部署过程中没有被篡改，并且确实来自预期的签名者。</p><h2 id="配置和使用-OSTree-GPG-签名"><a href="#配置和使用-OSTree-GPG-签名" class="headerlink" title="配置和使用 OSTree GPG 签名"></a>配置和使用 OSTree GPG 签名</h2><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><h4 id="列出所有-GPG-密钥"><a href="#列出所有-GPG-密钥" class="headerlink" title="列出所有 GPG 密钥"></a>列出所有 GPG 密钥</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --list-keys</span><br></pre></td></tr></table></figure><p>这将显示你密钥环中的所有公钥。输出类似于以下格式：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">pub   rsa2048 2021-01-01 [SC] [expires: 2023-01-01]</span><br><span class="line">      3AA5C34371567BD2</span><br><span class="line">uid           [ultimate] Your Name &lt;youremail@example.com&gt;</span><br><span class="line">sub   rsa2048 2021-01-01 [E] [expires: 2023-01-01]</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="识别密钥-ID"><a href="#识别密钥-ID" class="headerlink" title="识别密钥 ID"></a>识别密钥 ID</h4><p>在输出中，pub 行包含公钥信息。下面是如何识别密钥 ID：</p><ul><li>公钥行的第二行显示密钥 ID。例如，上面的输出中，3AA5C34371567BD2 是密钥 ID。</li><li>uid 行显示用户名和电子邮件地址。你可以通过用户名或电子邮件地址识别你感兴趣的密钥。</li></ul><h4 id="导出-GPG-公钥"><a href="#导出-GPG-公钥" class="headerlink" title="导出 GPG 公钥"></a>导出 GPG 公钥</h4><p> 通过<strong>密钥</strong> ID 导出公钥以供其他人验证签名：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --armor --export &lt;your-key-id&gt; &gt; pubkey.asc</span><br></pre></td></tr></table></figure><h3 id="签名和验证-OSTree-提交"><a href="#签名和验证-OSTree-提交" class="headerlink" title="签名和验证 OSTree 提交"></a>签名和验证 OSTree 提交</h3><h4 id="初始化-OSTree-仓库"><a href="#初始化-OSTree-仓库" class="headerlink" title="初始化 OSTree 仓库"></a>初始化 OSTree 仓库</h4><p> 如果尚未初始化 OSTree 仓库，可以使用以下命令：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ostree init --repo=repo --mode=archive-z2</span><br></pre></td></tr></table></figure><blockquote><p>注：在使用 OSTree 时，可以通过环境变量 OSTREE_REPO 指定仓库的地址。这个环境变量允许你在命令行中不必每次都指定 –repo 参数，而是通过设置环境变量来定义仓库的位置。<br>export OSTREE_REPO&#x3D;&#x2F;path&#x2F;to&#x2F;your&#x2F;repo</p></blockquote><h4 id="签名-OSTree-提交"><a href="#签名-OSTree-提交" class="headerlink" title="签名 OSTree 提交"></a>签名 OSTree 提交</h4><p>创建一个提交并进行签名：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ostree commit --repo=repo --branch=example-branch --gpg-sign=&lt;your-key-id&gt; --subject=&quot;Example Commit&quot; --body=&quot;This is an example commit&quot;</span><br></pre></td></tr></table></figure><p>其中 <code>&lt;your-key-id&gt;</code> 是你的 GPG 密钥 ID。</p><h4 id="验证-OSTree-提交"><a href="#验证-OSTree-提交" class="headerlink" title="验证 OSTree 提交"></a>验证 OSTree 提交</h4><p>在另一个系统上验证签名，需要首先导入公钥：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --import pubkey.asc</span><br></pre></td></tr></table></figure><p>然后验证签名：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ostree pull --repo=repo --gpg-verify=true &lt;remote-repo&gt; &lt;branch&gt;</span><br></pre></td></tr></table></figure><h3 id="发布和部署"><a href="#发布和部署" class="headerlink" title="发布和部署"></a>发布和部署</h3><ol><li><strong>发布签名的 OSTree 提交</strong></li></ol><p>将签名的提交推送到远程仓库：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ostree --repo=repo remote add --gpg-import=pubkey.asc origin &lt;remote-url&gt;</span><br><span class="line">ostree --repo=repo push --remote=origin &lt;branch&gt;</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>部署签名的 OSTree 提交</strong></li></ol><p>在客户端系统上，拉取并部署签名的提交：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ostree remote add --gpg-import=pubkey.asc origin &lt;remote-url&gt;</span><br><span class="line">ostree pull origin &lt;branch&gt;</span><br><span class="line">ostree admin deploy &lt;branch&gt;</span><br></pre></td></tr></table></figure><h3 id="实践示例"><a href="#实践示例" class="headerlink" title="实践示例"></a>实践示例</h3><p>以下是一个完整的示例，从签名到验证和部署：</p><h4 id="服务器端（签名和发布）"><a href="#服务器端（签名和发布）" class="headerlink" title="服务器端（签名和发布）"></a>服务器端（签名和发布）</h4><h5 id="初始化仓库"><a href="#初始化仓库" class="headerlink" title="初始化仓库"></a>初始化仓库</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ostree init --repo=repo --mode=archive-z2</span><br></pre></td></tr></table></figure><h5 id="创建并签名提交"><a href="#创建并签名提交" class="headerlink" title="创建并签名提交"></a>创建并签名提交</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ostree commit --repo=repo --branch=example-branch --gpg-sign=&lt;your-key-id&gt; --subject=&quot;Initial Commit&quot; --body=&quot;Initial commit with GPG signature&quot;</span><br></pre></td></tr></table></figure><h5 id="推送提交"><a href="#推送提交" class="headerlink" title="推送提交"></a>推送提交</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ostree remote add --repo=repo --gpg-import=pubkey.asc origin &lt;remote-url&gt;</span><br><span class="line">ostree --repo=repo push --remote=origin example-branch</span><br></pre></td></tr></table></figure><h4 id="客户端（验证和部署）"><a href="#客户端（验证和部署）" class="headerlink" title="客户端（验证和部署）"></a>客户端（验证和部署）</h4><h5 id="导入公钥"><a href="#导入公钥" class="headerlink" title="导入公钥"></a>导入公钥</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gpg --import pubkey.asc</span><br></pre></td></tr></table></figure><h5 id="添加远程仓库并拉取提交"><a href="#添加远程仓库并拉取提交" class="headerlink" title="添加远程仓库并拉取提交"></a>添加远程仓库并拉取提交</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ostree remote add --gpg-import=pubkey.asc origin &lt;remote-url&gt;</span><br><span class="line">ostree pull origin example-branch</span><br></pre></td></tr></table></figure><h5 id="部署提交"><a href="#部署提交" class="headerlink" title="部署提交"></a>部署提交</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ostree admin deploy example-branch</span><br></pre></td></tr></table></figure><p>通过以上步骤，你可以实现 OSTree 提交的 GPG 签名和验证，从而确保文件系统树的安全性和完整性。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;OSTree 是一个用于版本化文件系统树的工具，可以将其视为 Git 的文件系统版本。OSTree 不仅支持文件系统的版本管理，还支持使用 GPG 对其提交进行签名，以确保数据的完整性和来源的可信性。下面介绍 GPG 签名的原理和如何在 OSTree 中使用它。&lt;/p&gt;</summary>
    
    
    
    <category term="ostree" scheme="https://ssk-wh.github.io/categories/ostree/"/>
    
    
    <category term="ostree" scheme="https://ssk-wh.github.io/tags/ostree/"/>
    
    <category term="GPG" scheme="https://ssk-wh.github.io/tags/GPG/"/>
    
    <category term="签名" scheme="https://ssk-wh.github.io/tags/%E7%AD%BE%E5%90%8D/"/>
    
  </entry>
  
  <entry>
    <title>理解CPU的主频、倍频和外频</title>
    <link href="https://ssk-wh.github.io/2024/07e7dec690.html"/>
    <id>https://ssk-wh.github.io/2024/07e7dec690.html</id>
    <published>2024-07-04T00:00:00.000Z</published>
    <updated>2024-07-04T02:20:07.000Z</updated>
    
    <content type="html"><![CDATA[<p>在计算机的世界里，CPU（中央处理器）作为计算机的大脑，其性能决定了整机的速度和效率。而CPU的主频、倍频和外频则是衡量其性能的重要参数。本文将为您深入浅出地介绍这三个概念，帮助您更好地理解它们之间的关系及其对CPU性能的影响。</p><span id="more"></span><h2 id="CPU的主频"><a href="#CPU的主频" class="headerlink" title="CPU的主频"></a>CPU的主频</h2><p>主频（Clock Speed）是指CPU的工作频率，通常以GHz（千兆赫兹）为单位。它表示CPU每秒钟能够执行的指令周期数。例如，一个主频为3.5GHz的CPU，每秒钟可以进行35亿个指令周期。</p><h3 id="为什么主频重要？"><a href="#为什么主频重要？" class="headerlink" title="为什么主频重要？"></a>为什么主频重要？</h3><ol><li><strong>执行速度</strong>：主频越高，CPU在单位时间内能够执行的指令就越多，处理速度也就越快。</li><li><strong>响应时间</strong>：高主频的CPU可以更快速地响应系统和应用程序的需求，提高计算机的整体性能。</li></ol><h2 id="CPU的外频"><a href="#CPU的外频" class="headerlink" title="CPU的外频"></a>CPU的外频</h2><p>外频（External Clock）是指主板与CPU之间的基准时钟频率。它通常由主板上的时钟发生器提供。外频的单位同样是Hz（赫兹）。</p><h3 id="外频的作用"><a href="#外频的作用" class="headerlink" title="外频的作用"></a>外频的作用</h3><p>外频是决定CPU最终工作频率（即主频）的基础参数。外频与倍频相结合，确定了CPU的主频。例如，如果外频为100MHz，而倍频为35，那么CPU的主频就是100MHz × 35 &#x3D; 3500MHz（即3.5GHz）。</p><h2 id="CPU的倍频"><a href="#CPU的倍频" class="headerlink" title="CPU的倍频"></a>CPU的倍频</h2><p>倍频（Multiplier）是一个无量纲的系数，它将外频放大，从而得到CPU的主频。倍频由CPU内部的倍频器决定，一般由CPU的设计厂商设定。</p><h3 id="倍频的特点"><a href="#倍频的特点" class="headerlink" title="倍频的特点"></a>倍频的特点</h3><ol><li><strong>可调节性</strong>：许多高端CPU支持倍频调节，允许用户在一定范围内调整倍频，从而超频或降频，以满足不同的性能需求和节能要求。</li><li><strong>灵活性</strong>：通过调整倍频，用户可以在不改变外频的情况下，灵活调整CPU的工作频率，提供更好的系统稳定性和性能优化。</li></ol><h2 id="主频、倍频与外频的关系"><a href="#主频、倍频与外频的关系" class="headerlink" title="主频、倍频与外频的关系"></a>主频、倍频与外频的关系</h2><p>简单来说，CPU的主频由外频和倍频共同决定，公式为： 主频&#x3D;外频×倍频\text{主频} &#x3D; \text{外频} \times \text{倍频}</p><h3 id="举例说明"><a href="#举例说明" class="headerlink" title="举例说明"></a>举例说明</h3><p>假设一款CPU的外频为100MHz，倍频为35，那么它的主频就是：<br><code>主频=100MHz×35=3500MHz=3.5GHz</code></p><p>通过调整外频和倍频的组合，可以实现对CPU主频的精确控制。例如，通过将外频提升到105MHz，倍频保持35不变，新的主频就是：<br><code>主频=105MHz×35=3675MHz=3.675GHz</code></p><h2 id="如何调节外频和倍频"><a href="#如何调节外频和倍频" class="headerlink" title="如何调节外频和倍频"></a>如何调节外频和倍频</h2><p>现代计算机主板和CPU通常允许用户在BIOS&#x2F;UEFI设置中调整外频和倍频。这一过程称为“超频”（Overclocking）或“降频”（Underclocking）。不过，超频可能会带来系统不稳定和过热的问题，需要谨慎操作并确保良好的散热条件。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>理解CPU的主频、倍频和外频是提升计算机性能的重要一步。主频是CPU性能的直接表现，而外频和倍频则共同决定了这一表现。通过合理调整这两个参数，用户可以在一定范围内优化CPU的工作频率，从而满足不同的计算需求。<br>希望通过本文，您对CPU的主频、倍频和外频有了更清晰的认识。如果有任何疑问或需要进一步的探讨，欢迎在评论区留言，我会尽力解答您的问题。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在计算机的世界里，CPU（中央处理器）作为计算机的大脑，其性能决定了整机的速度和效率。而CPU的主频、倍频和外频则是衡量其性能的重要参数。本文将为您深入浅出地介绍这三个概念，帮助您更好地理解它们之间的关系及其对CPU性能的影响。&lt;/p&gt;</summary>
    
    
    
    <category term="计算机" scheme="https://ssk-wh.github.io/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA/"/>
    
    
    <category term="CPU" scheme="https://ssk-wh.github.io/tags/CPU/"/>
    
  </entry>
  
  <entry>
    <title>makeself 的使用</title>
    <link href="https://ssk-wh.github.io/2024/0717fd6bfe.html"/>
    <id>https://ssk-wh.github.io/2024/0717fd6bfe.html</id>
    <published>2024-07-02T00:00:00.000Z</published>
    <updated>2024-07-02T05:24:23.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 Linux 中，创建自解压安装脚本是一种常见的方法，可以将多个文件打包成一个可执行文件，以便于分发和安装。这种方法通常使用 <code>shar</code>（Shell Archive）或者 <code>makeself</code> 工具来实现。</p><span id="more"></span><p>以下是使用 <code>makeself</code> 创建自解压安装脚本的步骤。</p><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>在大多数 Linux 发行版中，<code>makeself</code> 工具可以通过包管理器安装。以下是一些常见的安装方法：</p><ul><li><strong>Ubuntu&#x2F;Debian:</strong></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install makeself</span><br></pre></td></tr></table></figure><ul><li><strong>Fedora:</strong></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo dnf install makeself</span><br></pre></td></tr></table></figure><ul><li><strong>Arch Linux:</strong></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo pacman -S makeself</span><br></pre></td></tr></table></figure><h3 id="准备文件"><a href="#准备文件" class="headerlink" title="准备文件"></a>准备文件</h3><p>假设你有一组文件或一个目录需要打包。在这个例子中，我们将使用一个目录 <code>my_app</code>。</p><h3 id="编写安装脚本"><a href="#编写安装脚本" class="headerlink" title="编写安装脚本"></a>编写安装脚本</h3><p>编写一个名为 <code>install.sh</code> 的脚本，定义如何安装和配置你的应用程序。以下是一个简单的例子：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/bash</span><br><span class="line">echo &quot;正在安装 My App...&quot;</span><br><span class="line"># 执行安装步骤，例如复制文件、设置权限等</span><br><span class="line">cp -r * /opt/my_app/</span><br><span class="line">echo &quot;安装完成！&quot;</span><br></pre></td></tr></table></figure><p>确保你的安装脚本是可执行的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chmod +x install.sh</span><br></pre></td></tr></table></figure><h3 id="使用makeself创建自解压安装包"><a href="#使用makeself创建自解压安装包" class="headerlink" title="使用makeself创建自解压安装包"></a>使用makeself创建自解压安装包</h3><p>使用 <code>makeself</code> 工具创建自解压安装包。以下是一个示例命令：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">makeself my_app my_app_installer.run &quot;My App Installer&quot; ./install.sh</span><br></pre></td></tr></table></figure><ul><li><code>my_app</code> 是包含你所有文件的目录。</li><li><code>my_app_installer.run</code> 是生成的自解压安装包的名称。</li><li><code>&quot;My App Installer&quot;</code> 是安装包的描述。</li><li><code>./install.sh</code> 是安装脚本的路径。</li></ul><h3 id="完整示例"><a href="#完整示例" class="headerlink" title="完整示例"></a>完整示例</h3><p>假设你有以下目录结构：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">my_app/</span><br><span class="line">├── install.sh</span><br><span class="line">├── bin/</span><br><span class="line">│   └── my_app_executable</span><br><span class="line">└── lib/</span><br><span class="line">    └── my_app_library.so</span><br></pre></td></tr></table></figure><ol><li>创建 <code>install.sh</code>：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/bash</span><br><span class="line">echo &quot;正在安装 My App...&quot;</span><br><span class="line"># 创建目标目录</span><br><span class="line">mkdir -p /opt/my_app</span><br><span class="line"># 复制文件</span><br><span class="line">cp -r * /opt/my_app/</span><br><span class="line">echo &quot;安装完成！&quot;</span><br></pre></td></tr></table></figure><ol start="2"><li>确保 <code>install.sh</code> 是可执行的：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chmod +x my_app/install.sh</span><br></pre></td></tr></table></figure><ol start="3"><li>使用 <code>makeself</code> 创建自解压安装包：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">makeself my_app my_app_installer.run &quot;My App Installer&quot; ./install.sh</span><br></pre></td></tr></table></figure><h3 id="运行自解压安装包"><a href="#运行自解压安装包" class="headerlink" title="运行自解压安装包"></a>运行自解压安装包</h3><p>用户可以通过执行生成的 <code>.run</code> 文件来安装应用程序：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./my_app_installer.run</span><br></pre></td></tr></table></figure><p>这会解压文件并运行 <code>install.sh</code> 脚本，完成应用程序的安装。<br />通过这些步骤，你可以轻松创建一个自解压安装脚本，方便分发和安装你的 Linux 应用程序。</p><h3 id="解压为原始文件"><a href="#解压为原始文件" class="headerlink" title="解压为原始文件"></a>解压为原始文件</h3><p>通过 makeself 制作的自解压安装包同样支持解压为原始文件，您可以在修改后重新打包为 .run 格式的自解压安装包。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./my_app_installer.run --noexec --targegt my_app_dir</span><br></pre></td></tr></table></figure><h3 id="查看压缩包内容"><a href="#查看压缩包内容" class="headerlink" title="查看压缩包内容"></a>查看压缩包内容</h3><p>压缩后的可执行文件可以通过 –list 参数查看文件内容</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./my_app_installer.run --list</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 Linux 中，创建自解压安装脚本是一种常见的方法，可以将多个文件打包成一个可执行文件，以便于分发和安装。这种方法通常使用 &lt;code&gt;shar&lt;/code&gt;（Shell Archive）或者 &lt;code&gt;makeself&lt;/code&gt; 工具来实现。&lt;/p&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/categories/Linux/"/>
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/tags/Linux/"/>
    
    <category term="makeself" scheme="https://ssk-wh.github.io/tags/makeself/"/>
    
  </entry>
  
  <entry>
    <title>宏内核与微内核：深入理解计算机操作系统的核心</title>
    <link href="https://ssk-wh.github.io/2024/072650bdc9.html"/>
    <id>https://ssk-wh.github.io/2024/072650bdc9.html</id>
    <published>2024-07-02T00:00:00.000Z</published>
    <updated>2024-07-02T05:24:23.000Z</updated>
    
    <content type="html"><![CDATA[<p>在计算机操作系统的设计中，内核（Kernel）是一个至关重要的组成部分。内核作为系统软件的核心，负责管理系统资源，协调硬件和软件之间的交互。根据设计思想的不同，内核可以分为两种主要类型：宏内核（Monolithic Kernel）和微内核（Microkernel）。本文将深入探讨这两种内核的区别、各自的优缺点以及一些实际的例子。</p><span id="more"></span><h4 id="什么是宏内核？"><a href="#什么是宏内核？" class="headerlink" title="什么是宏内核？"></a>什么是宏内核？</h4><p>宏内核是一种传统的操作系统内核设计，其特点是将操作系统的大部分功能都集成在一个大内核中。这个大内核直接运行在硬件之上，并且具有完整的系统功能，包括进程管理、内存管理、文件系统、设备驱动等。<br /><strong>优点：</strong></p><ol><li><strong>性能高：</strong> 由于大部分服务都在内核空间内运行，系统调用和进程间通信的开销较低。</li><li><strong>实现简单：</strong> 开发者可以直接在内核空间内实现大部分功能，减少了用户空间与内核空间之间的切换。</li></ol><p><strong>缺点：</strong></p><ol><li><strong>稳定性差：</strong> 由于所有功能都在内核空间运行，一旦某个部分出现错误，可能导致整个系统崩溃。</li><li><strong>维护复杂：</strong> 宏内核代码量大，任何修改都需要对整个内核进行重新编译，测试和调试难度较大。</li></ol><p><strong>实际例子：</strong></p><ul><li><strong>Linux内核：</strong> Linux是最典型的宏内核操作系统，它将大部分功能集成在内核中。</li><li><strong>Windows NT内核：</strong> 尽管微软声称Windows NT内核是微内核架构，但实际上它更接近于宏内核设计，因为许多服务都在内核模式下运行。</li></ul><h4 id="什么是微内核？"><a href="#什么是微内核？" class="headerlink" title="什么是微内核？"></a>什么是微内核？</h4><p>微内核则采用了截然不同的设计思路。它将操作系统的核心功能精简到最小，仅保留最基础的部分，如进程间通信、基本的内存管理和简单的硬件驱动。其他系统服务则运行在用户空间，以减少内核的复杂度。<br /><strong>优点：</strong></p><ol><li><strong>稳定性高：</strong> 由于内核中只包含最基本的功能，即使某个用户空间服务崩溃，系统整体仍能保持稳定。</li><li><strong>安全性好：</strong> 用户空间服务与内核之间的隔离，减少了内核受到攻击的可能性。</li></ol><p><strong>缺点：</strong></p><ol><li><strong>性能低：</strong> 由于系统服务运行在用户空间，频繁的系统调用和进程间通信会带来较大的性能开销。</li><li><strong>实现复杂：</strong> 需要设计高效的通信机制，以保证用户空间服务之间的交互顺畅。</li></ol><p><strong>实际例子：</strong></p><ul><li><strong>MINIX：</strong> MINIX是一个典型的微内核操作系统，因其简洁的设计而被广泛用于教学。</li><li><strong>QNX：</strong> QNX是一种商用实时操作系统，采用微内核设计，广泛应用于嵌入式系统中。</li></ul><h4 id="宏内核与微内核的比较"><a href="#宏内核与微内核的比较" class="headerlink" title="宏内核与微内核的比较"></a>宏内核与微内核的比较</h4><table><thead><tr><th>特点</th><th>宏内核</th><th>微内核</th></tr></thead><tbody><tr><td>结构</td><td>所有功能集成在内核中</td><td>只保留核心功能，其他服务运行在用户空间</td></tr><tr><td>性能</td><td>高</td><td>较低</td></tr><tr><td>稳定性</td><td>稳定性差</td><td>稳定性高</td></tr><tr><td>安全性</td><td>安全性一般</td><td>安全性好</td></tr><tr><td>维护和扩展</td><td>维护复杂，扩展困难</td><td>维护较简单，扩展性好</td></tr></tbody></table><h4 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h4><p>宏内核和微内核各有优缺点，选择哪种内核架构取决于具体的应用场景和需求。对于需要高性能和快速响应的系统，如桌面操作系统和服务器操作系统，宏内核通常是首选。而对于需要高稳定性和安全性的系统，如嵌入式系统和实时操作系统，微内核则更具优势。通过理解这两种内核架构的特点和差异，开发者可以更好地设计和优化操作系统，以满足不同的应用需求。<br />希望这篇文章能帮助你更好地理解宏内核和微内核。如果你有任何疑问或需要进一步探讨，欢迎在评论区留言。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在计算机操作系统的设计中，内核（Kernel）是一个至关重要的组成部分。内核作为系统软件的核心，负责管理系统资源，协调硬件和软件之间的交互。根据设计思想的不同，内核可以分为两种主要类型：宏内核（Monolithic Kernel）和微内核（Microkernel）。本文将深入探讨这两种内核的区别、各自的优缺点以及一些实际的例子。&lt;/p&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/categories/Linux/"/>
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/tags/Linux/"/>
    
    <category term="内核" scheme="https://ssk-wh.github.io/tags/%E5%86%85%E6%A0%B8/"/>
    
  </entry>
  
  <entry>
    <title>段页式内存管理</title>
    <link href="https://ssk-wh.github.io/2024/07eadf9fd1.html"/>
    <id>https://ssk-wh.github.io/2024/07eadf9fd1.html</id>
    <published>2024-07-02T00:00:00.000Z</published>
    <updated>2024-07-02T05:24:23.000Z</updated>
    
    <content type="html"><![CDATA[<p>段页式内存管理是一种结合了分段和分页两种内存管理技术的机制，它旨在充分利用这两者的优点，减少它们各自的缺点。这种混合策略广泛应用于现代操作系统中，以实现灵活高效的内存管理。</p><span id="more"></span><h2 id="段页式内存管理的基本概念"><a href="#段页式内存管理的基本概念" class="headerlink" title="段页式内存管理的基本概念"></a>段页式内存管理的基本概念</h2><h3 id="段（Segment）"><a href="#段（Segment）" class="headerlink" title="段（Segment）"></a>段（Segment）</h3><ul><li><strong>分段</strong>：内存被划分为不同的段，每个段代表一个逻辑单位，比如代码段、数据段、堆栈段等。每个段有一个段基址和段长度。</li><li><strong>段表（Segment Table）</strong>：操作系统为每个进程维护一个段表，段表条目包含段基址和段长度，用于将逻辑地址转换为段内的偏移地址。</li></ul><h3 id="页（Page）"><a href="#页（Page）" class="headerlink" title="页（Page）"></a>页（Page）</h3><ul><li><strong>分页</strong>：每个段进一步划分为固定大小的页，内存被划分成大小相等的页框（Page Frame）。</li><li><strong>页表（Page Table）</strong>：每个段对应一个页表，页表条目包含页框号，用于将段内的偏移地址转换为物理地址。</li></ul><h2 id="地址转换过程"><a href="#地址转换过程" class="headerlink" title="地址转换过程"></a>地址转换过程</h2><p>段页式内存管理将逻辑地址转换为物理地址的过程涉及两个步骤：</p><ol><li><strong>段选择</strong>：首先根据段号查找段表，获取段基址和段长度。如果逻辑地址中的偏移量超过了段长度，触发段错误（Segment Fault）。</li><li><strong>页选择</strong>：其次在段内进行分页，根据段内的页号查找页表，获取对应的页框号，将页内偏移量加到页框基址上，形成最终的物理地址。</li></ol><p>具体步骤如下：</p><ul><li><strong>逻辑地址（段号：段内偏移）</strong>：逻辑地址由段号（Segment Number）和段内偏移（Offset within Segment）组成。</li><li><strong>段表查找</strong>：使用段号在段表中查找，获取段基址和段长度。</li><li><strong>页表查找</strong>：将段内偏移分为页号（Page Number）和页内偏移（Offset within Page），使用页号在页表中查找，获取页框号。</li><li><strong>物理地址计算</strong>：将页框号和页内偏移组合，形成最终的物理地址。</li></ul><h2 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h2><ul><li><strong>灵活性</strong>：结合了分段和分页的优点，既支持逻辑分段，又能够有效利用内存碎片。</li><li><strong>保护和共享</strong>：通过段表和页表的多级映射，可以实现内存保护和进程间的内存共享。</li><li><strong>减少外部碎片</strong>：分页的引入减少了分段带来的外部碎片问题。</li></ul><h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>假设一个逻辑地址由段号、页号和页内偏移组成，如下所示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">逻辑地址格式： 段号（S） 页号（P） 页内偏移（D）</span><br></pre></td></tr></table></figure><p>具体转换过程如下：</p><ol><li>使用段号S在段表中查找，得到段基址和段长度。</li><li>将逻辑地址中的页号P和页内偏移D分离，计算段内偏移。</li><li>使用页号P在对应段的页表中查找，得到页框号。</li><li>将页框号和页内偏移D组合，形成物理地址。</li></ol><h3 id="示例数据"><a href="#示例数据" class="headerlink" title="示例数据"></a>示例数据</h3><p>假设段表和页表如下：<br />段表：</p><table><thead><tr><th>段号</th><th>段基址</th><th>段长度</th></tr></thead><tbody><tr><td>0</td><td>1000</td><td>400</td></tr><tr><td>1</td><td>2000</td><td>800</td></tr></tbody></table><p>页表（段0）：</p><table><thead><tr><th>页号</th><th>页框号</th></tr></thead><tbody><tr><td>0</td><td>5</td></tr><tr><td>1</td><td>8</td></tr></tbody></table><p>页表（段1）：</p><table><thead><tr><th>页号</th><th>页框号</th></tr></thead><tbody><tr><td>0</td><td>7</td></tr><tr><td>1</td><td>10</td></tr><tr><td>2</td><td>3</td></tr></tbody></table><p>假设逻辑地址为：(1, 1, 50) ，表示段1，页1，页内偏移50：</p><ol><li>在段表中查找段1，得到段基址2000。</li><li>使用页号1在段1的页表中查找，得到页框号10。</li><li>将页框号10转换为物理地址，假设每页大小为100：<ul><li>物理地址 &#x3D; 页框号 * 页大小 + 页内偏移</li><li>物理地址 &#x3D; 10 * 100 + 50 &#x3D; 1050</li></ul></li></ol><p>最终物理地址为1050。</p><h2 id="段页式内存管理的应用"><a href="#段页式内存管理的应用" class="headerlink" title="段页式内存管理的应用"></a>段页式内存管理的应用</h2><p>段页式内存管理广泛应用于现代操作系统，如Windows和Unix&#x2F;Linux，这些系统通过段页式内存管理实现内存保护、多任务处理和虚拟内存管理。通过段页式管理，操作系统可以灵活地分配和管理内存，提高系统的可靠性和效率。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;段页式内存管理是一种结合了分段和分页两种内存管理技术的机制，它旨在充分利用这两者的优点，减少它们各自的缺点。这种混合策略广泛应用于现代操作系统中，以实现灵活高效的内存管理。&lt;/p&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/categories/Linux/"/>
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/tags/Linux/"/>
    
    <category term="内存管理" scheme="https://ssk-wh.github.io/tags/%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/"/>
    
  </entry>
  
  <entry>
    <title>Greenboot服务介绍</title>
    <link href="https://ssk-wh.github.io/2024/06697cec4e.html"/>
    <id>https://ssk-wh.github.io/2024/06697cec4e.html</id>
    <published>2024-06-18T20:19:08.000Z</published>
    <updated>2024-06-22T04:11:24.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="概况"><a href="#概况" class="headerlink" title="概况"></a>概况</h2><p>Fedora 中提供了 <a class="link"   href="https://github.com/fedora-iot/greenboot/tree/main" >greenboot<i class="fas fa-external-link-alt"></i></a> 服务，这是一款基于 rpm-ostree 的系统上 systemd 的通用运行状况检查框架。</p><span id="more"></span><p>Greenboot 由两部分组成：</p><ul><li>greenboot ：检查提供的脚本，如果这些检查未通过则重新启动，如果重新启动未能解决问题则回滚到之前的部署。</li><li>greenboot-default-health-checks ，由 Greenboot 维护者提供的一系列可选和策划的健康检查。</li></ul><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>为了在 Fedora Silverblue、Fedora IoT 或 Fedora CoreOS 上获得完整的 Greenboot ，请使用如下命令安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rpm-ostree install greenboot greenboot-default-health-checks</span><br><span class="line">systemctl reboot</span><br></pre></td></tr></table></figure><h2 id="名词介绍"><a href="#名词介绍" class="headerlink" title="名词介绍"></a>名词介绍</h2><ul><li>MOTD：指&#x2F;run&#x2F;motd.d&#x2F;boot-status，存放 greenboot 运行阶段的一些日志信息。</li></ul><h2 id="脚本目录"><a href="#脚本目录" class="headerlink" title="脚本目录"></a>脚本目录</h2><p>目录结构</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">/etc</span><br><span class="line">└── greenboot</span><br><span class="line">    ├── check</span><br><span class="line">    │   ├── required.d</span><br><span class="line">    │   └── wanted.d</span><br><span class="line">    ├── green.d</span><br><span class="line">    └── red.d</span><br></pre></td></tr></table></figure><ul><li>&#x2F;etc&#x2F;greenboot&#x2F;check&#x2F;required.d：此目录中的运行状态检查脚本不得失败。如果此文件夹中的任何脚本退出时出现错误代码，则启动将被声明为失败。错误消息将出现在 MOTD 和 journalctl -u greenboot-healthcheck.service 中。</li><li>&#x2F;etc&#x2F;greenboot&#x2F;check&#x2F;wanted.d：此目录中的运行状态检查脚本可能失败，。此文件夹中的脚本可以退出并显示错误代码，并且启动不会被声明为失败。错误消息将出现在 MOTD 和 journalctl -u greenboot-healthcheck.service -b 中。</li><li>&#x2F;etc&#x2F;greenboot&#x2F;green.d：此目录中的交叉脚本将在启动成功（绿色）后运行。</li><li>&#x2F;etc&#x2F;greenboot&#x2F;red.d：此目录中的交叉脚本将在启动失败（红色）后运行。</li></ul><p>除非您的发行版中默认启用 greenboot，否则请通过运行 <code>systemctl enable greenboot-task-runner greenboot-healthcheck greenboot-status greenboot-loading-message greenboot-grub2-set-counter greenboot-grub2-set-success greenboot-rpm-ostree-grub2-check-fallback redboot-auto-reboot redboot-task-runner</code> 来启用它。它将在下次启动过程中自动启动并运行检查。</p><p>之后当您 ssh 进入计算机时，将显示启动状态消息：<br><code>Boot Status is GREEN - Health Check SUCCESS</code><br><code>Boot Status is RED - Health Check FAILURE!</code></p><h2 id="greenboot-default-health-checks"><a href="#greenboot-default-health-checks" class="headerlink" title="greenboot-default-health-checks"></a>greenboot-default-health-checks</h2><p>这些运行状况检查可在 rpm-ostree 系统中的只读目录 &#x2F;usr&#x2F;lib&#x2F;greenboot&#x2F;check 中找到。</p><ul><li>检查存储库 URL 是否仍可通过 DNS 解析：此脚本位于 &#x2F;usr&#x2F;lib&#x2F;greenboot&#x2F;check&#x2F;required.d&#x2F;01_repository_dns_check.sh 下，并确保对存储库 URL 的 DNS 查询仍然可用。</li><li>检查更新平台是否仍然可访问：此脚本位于 &#x2F;usr&#x2F;lib&#x2F;greenboot&#x2F;check&#x2F;wanted.d&#x2F;01_update_platform_check.sh 下，并尝试连接并从 &#x2F;etc&#x2F;ostree&#x2F;remotes.d 中定义的更新平台获取 2XX 或 3XX HTTP 代码。</li><li>检查当前启动是否由硬件看门狗触发：此脚本位于 &#x2F;usr&#x2F;lib&#x2F;greenboot&#x2F;check&#x2F;required.d&#x2F;02_watchdog.sh 下，用于检查当前启动是否由看门狗触发。如果是，但在一定的宽限期（默认为 24 小时，可通过 &#x2F;etc&#x2F;greenboot&#x2F;greenboot.conf 中的 GREENBOOT_WATCHDOG_GRACE_PERIOD&#x3D;number_of_hours 配置）后重新启动，Greenboot 不会将当前启动标记为红色，并且不会回滚到之前的部署。如果在宽限期内发生，此时当前启动将被标记为红色，但 Greenboot 不会回滚到之前的部署。默认情况下启用，但可以通过将 &#x2F;etc&#x2F;greenboot&#x2F;greenboot.conf 中的 GREENBOOT_WATCHDOG_CHECK_ENABLED 修改为 false 来禁用它。</li></ul><h2 id="使用-systemd-服务进行健康检查"><a href="#使用-systemd-服务进行健康检查" class="headerlink" title="使用 systemd 服务进行健康检查"></a>使用 systemd 服务进行健康检查</h2><p>总体启动成功是根据 boot-complete.target 来衡量的。</p><h3 id="Required-Checks"><a href="#Required-Checks" class="headerlink" title="Required Checks"></a>Required Checks</h3><p>创建一个不能失败的一次性健康检查服务单元，例如 &#x2F;etc&#x2F;systemd&#x2F;system&#x2F;required-check.service 。确保它在失败时调用 redboot.target ( OnFailure&#x3D;redboot.target )。运行 <code>systemctl enable required-check </code>来启用它。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=Custom Required Health Check</span><br><span class="line">Before=boot-complete.target</span><br><span class="line">OnFailure=redboot.target</span><br><span class="line">OnFailureJobMode=fail</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=oneshot</span><br><span class="line">ExecStart=/usr/libexec/mytestsuite/required-check</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">RequiredBy=boot-complete.target</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><h3 id="Wanted-Checks"><a href="#Wanted-Checks" class="headerlink" title="Wanted Checks"></a>Wanted Checks</h3><p>创建一个可能会失败的一次性健康检查服务单元，例如 &#x2F;etc&#x2F;systemd&#x2F;system&#x2F;wanted-check.service 。运行 <code>systemctl enable wanted-check</code> 来启用它。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=Custom Wanted Health Check</span><br><span class="line">Before=boot-complete.target</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">Type=oneshot</span><br><span class="line">ExecStart=/usr/libexec/mytestsuite/wanted-check</span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=boot-complete.target</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><h2 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h2><p>目前，可以通过环境变量自定义以下参数。这些环境变量也可以在配置文件 &#x2F;etc&#x2F;greenboot&#x2F;greenboot.conf 中描述：</p><ul><li>GREENBOOT_MAX_BOOT_ATTEMPTS：在声明部署有问题并回滚到上一个部署之前尝试启动的最大次数,默认为3。</li><li>GREENBOOT_WATCHDOG_CHECK_ENABLED：启用&#x2F;禁用检查当前启动是否已由硬件看门狗健康检查触发。有关健康检查的更多信息包含在子包 greenboot-default-health-checks 部分中。</li><li>GREENBOOT_WATCHDOG_GRACE_PERIOD：升级后我们认为新部署导致重新启动的小时数。</li></ul><h2 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h2><ul><li>greenboot-rpm-ostree-grub2-check-fallback.service 在 greenboot-healthcheck.service 之前运行，并检查 GRUB2 环境变量 boot_counter 是否为 -1。<ul><li>如果为 -1，则意味着系统处于回退部署中，并将执行 <code>rpm-ostree rollback </code>返回到之前的工作部署。</li><li>如果 boot_counter 不为-1，则此步骤不执行任何操作。</li></ul></li><li>greenboot-healthcheck.service 在 systemd 的 boot-complete.target 之前运行。它启动 &#x2F;usr&#x2F;libexec&#x2F;greenboot&#x2F;greenboot check ，它运行 required.d 和 wanted.d 脚本。<ul><li>如果 required.d 文件夹中的任何脚本失败，则调用 redboot.target 。<ul><li>它触发 redboot-task-runner.service ，从而启动 &#x2F;usr&#x2F;libexec&#x2F;greenboot&#x2F;greenboot red 。这将运行 red.d 文件夹中的脚本。</li><li>经过上述操作后：<ul><li>greenboot-status.service 运行，创建 MOTD 指定哪些脚本失败。</li><li>redboot-auto-reboot.service 已运行。它执行一系列检查以确定是否需要手动干预。如果没有，它将重新启动系统。</li></ul></li></ul></li><li>如果 required.d 文件夹中的所有脚本都成功：<ul><li>已达到 boot-complete.target </li><li>greenboot-grub2-set-success.service 已运行。它取消设置 boot_counter GRUB 环境变量并将 boot_success GRUB 环境变量设置为 1。</li><li>greenboot-task-runner.service 启动<code> /usr/libexec/greenboot/greenboot green</code> ，它运行 green.d 文件夹中的脚本，这些脚本将在成功更新后运行。</li><li>greenboot-status.service 运行，创建 MOTD 并显示成功消息。</li></ul></li></ul></li></ul><h2 id="Services-一览"><a href="#Services-一览" class="headerlink" title="Services 一览"></a>Services 一览</h2><table><thead><tr><th><strong>服务</strong></th><th><strong>作用</strong></th><th><strong>其他</strong></th></tr></thead><tbody><tr><td>greenboot-grub2-set-counter.service</td><td>greenboot-grub2-set-counter：<br>grub2-editenv - set boot_counter&#x3D;”$max_boot_attempts” <br>grub2-editenv - set boot_success&#x3D;0<br>通过命令参数或配置文件中获取最大重启次数，未指定则默认为3次</td><td>配置文件：<br>&#x2F;etc&#x2F;greenboot&#x2F;greenboot.conf<br>配置项：<br>GREENBOOT_MAX_BOOT_ATTEMPTS<br>配置文件中允许通过DISABLED_HEALTHCHECKS禁用某些检查项</td></tr><tr><td>greenboot-grub2-set-success.service</td><td>grub2-editenv - set boot_success&#x3D;1<br>grub2-editenv - unset boot_counter<br></td><td>After&#x3D;boot-complete.target<br>在系统启动完成后运行</td></tr><tr><td>greenboot-healthcheck.service</td><td>&#x2F;usr&#x2F;libexec&#x2F;greenboot&#x2F;greenboot check<br>执行&#x2F;usr&#x2F;lib&#x2F;greenboot&#x2F;check和&#x2F;etc&#x2F;greenboot&#x2F;check目录中required.d和wanted.d的检查脚本，如果required.d中的脚本运行失败，将导致greenboot进程退出值为1(异常退出)。</td><td>wanted.d中脚本失败无影响OnFailure&#x3D;redboot.target<br>失败后到达redboot.target，从而启动redboot-task-runner.service</td></tr><tr><td>greenboot-loading-message.service</td><td>&#x2F;usr&#x2F;libexec&#x2F;greenboot&#x2F;greenboot-loading-message<br>只是向&#x2F;run&#x2F;motd.d&#x2F;boot-status中输出一些信息</td><td>-</td></tr><tr><td>greenboot-rpm-ostree-grub2-check-fallback.service</td><td>&#x2F;usr&#x2F;libexec&#x2F;greenboot&#x2F;greenboot-rpm-ostree-grub2-check-fallback</td><td>rpm-ostree rollback<br>修改grub引导，第一项变为上一版本</td></tr><tr><td>greenboot-status.service</td><td>&#x2F;usr&#x2F;libexec&#x2F;greenboot&#x2F;greenboot-status<br>综合其他的日志信息，统一导入到&#x2F;run&#x2F;motd.d&#x2F;boot-status</td><td>服务包括：<br>greenboot-healthcheck.service<br>greenboot-task-runner.service<br>redboot-task-runner.service <br>redboot-auto-reboot.service<br>greenboot-rpm-ostree-grub2-check-fallback.service</td></tr><tr><td>greenboot-task-runner.service</td><td>&#x2F;usr&#x2F;libexec&#x2F;greenboot&#x2F;greenboot green<br>执行&#x2F;usr&#x2F;lib&#x2F;greenboot&#x2F;green.d和&#x2F;etc&#x2F;greenboot&#x2F;green.d中的脚本</td><td>After&#x3D;boot-complete.target在系统启动完成后运行</td></tr><tr><td>redboot-auto-reboot.service</td><td>&#x2F;usr&#x2F;libexec&#x2F;greenboot&#x2F;redboot-auto-reboot<br>检查是否需要重启</td><td>根据grub2-editenv list中的信息以及&#x2F;boot&#x2F;loader&#x2F;entries&#x2F;中文件数量决定当前是否重启</td></tr><tr><td>redboot-task-runner.service</td><td>&#x2F;usr&#x2F;libexec&#x2F;greenboot&#x2F;greenboot red<br>执行&#x2F;usr&#x2F;lib&#x2F;greenboot&#x2F;red.d和&#x2F;etc&#x2F;greenboot&#x2F;red.d中的脚本</td><td>RequiredBy&#x3D;redboot.target<br>此服务在系统启动失败后运行</td></tr><tr><td>redboot.target</td><td>-</td><td>-</td></tr></tbody></table><h2 id="grub2-editenv"><a href="#grub2-editenv" class="headerlink" title="grub2-editenv"></a>grub2-editenv</h2><p>在设置 GRUB 环境的时候，用到了 grub2-editenv 命令，由 grub2-tools-minimal 提供。<br>查阅其 <a class="link"   href="https://git.savannah.gnu.org/git/grub.git" >源码<i class="fas fa-external-link-alt"></i></a> 可以得知，这里只是把 set 的内容写入 &#x2F;boot&#x2F;grub2&#x2F;grubenv 文件中进行保存。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;概况&quot;&gt;&lt;a href=&quot;#概况&quot; class=&quot;headerlink&quot; title=&quot;概况&quot;&gt;&lt;/a&gt;概况&lt;/h2&gt;&lt;p&gt;Fedora 中提供了 &lt;a class=&quot;link&quot;   href=&quot;https://github.com/fedora-iot/greenboot/tree/main&quot; &gt;greenboot&lt;i class=&quot;fas fa-external-link-alt&quot;&gt;&lt;/i&gt;&lt;/a&gt; 服务，这是一款基于 rpm-ostree 的系统上 systemd 的通用运行状况检查框架。&lt;/p&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/categories/Linux/"/>
    
    
    <category term="greenboot" scheme="https://ssk-wh.github.io/tags/greenboot/"/>
    
    <category term="fedora" scheme="https://ssk-wh.github.io/tags/fedora/"/>
    
    <category term="grub" scheme="https://ssk-wh.github.io/tags/grub/"/>
    
  </entry>
  
  <entry>
    <title>systemd 目录重定向</title>
    <link href="https://ssk-wh.github.io/2024/0650acf1b8.html"/>
    <id>https://ssk-wh.github.io/2024/0650acf1b8.html</id>
    <published>2024-06-17T17:13:08.000Z</published>
    <updated>2024-06-18T02:12:44.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 <code>systemd</code> 中，可以使用 <code>BindPaths</code> 或 <code>BindReadOnlyPaths</code> 来重定向应用访问的目录。这些选项可以在 <code>.service</code> 单元文件中设置，用于将特定的目录绑定到不同的位置，从而实现目录的重定向。<code>systemd</code> 的这种机制，可以让我们更好的控制应用的行为，增强系统的安全性。</p><span id="more"></span><p><code>BindPaths</code> 是通过使用 Linux 内核的挂载命名空间（mount namespaces）和绑定挂载（bind mounts）来实现的。这些功能允许在特定进程的命名空间中修改文件系统的视图，而不会影响到其他进程或系统的全局视图。</p><p>挂载命名空间（mount namespaces）允许在特定进程的视图中隔离和重新配置挂载点。创建一个新的挂载命名空间后，进程可以在不影响其他进程的情况下修改其挂载点。<br>绑定挂载（bind mounts）是 Linux 文件系统功能的一部分，允许将一个目录或文件挂载到另一个目录。这使得同一目录或文件可以在多个位置同时出现。</p><p>以下是一个示例，展示如何配置一个 <code>systemd</code> 服务单元文件以重定向目录访问：</p><h2 id="服务单元"><a href="#服务单元" class="headerlink" title="服务单元"></a>服务单元</h2><p>假设你有一个名为 <code>example.service</code> 的服务单元文件，路径可能在 <code>/etc/systemd/system/example.service</code> 或 <code>/lib/systemd/system/example.service</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[Unit]</span><br><span class="line">Description=Example Service</span><br><span class="line"></span><br><span class="line">[Service]</span><br><span class="line">ExecStart=/usr/bin/example-binary</span><br><span class="line">BindPaths=/path/to/redirected:/path/to/original</span><br><span class="line"><span class="comment"># 或者使用只读绑定</span></span><br><span class="line"><span class="comment"># BindReadOnlyPaths=/path/to/redirected:/path/to/original</span></span><br><span class="line"></span><br><span class="line">[Install]</span><br><span class="line">WantedBy=multi-user.target</span><br></pre></td></tr></table></figure><p>在这个示例中，<code>/path/to/original</code> 是应用程序预期访问的目录，<code>/path/to/redirected</code> 是你希望应用程序实际访问的目录。</p><p>修改单元文件后，重新加载 <code>systemd</code> 配置以使更改生效，在 <code>example-binary</code> 中访问 <code>/path/to/original</code> 目录的内容时，此时均会重定向至 <code>/path/to/redirected</code> 目录。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># 重新加载 systemd 配置</span><br><span class="line">sudo systemctl daemon-reload</span><br><span class="line"></span><br><span class="line"># 启动或重启服务</span><br><span class="line">sudo systemctl restart example.service</span><br></pre></td></tr></table></figure><h2 id="其他配置"><a href="#其他配置" class="headerlink" title="其他配置"></a>其他配置</h2><p>除了 BindPaths ，systemd还提供了其他的选项，使用者可以根据自己的需求选择合适的配置。</p><h3 id="BindPaths"><a href="#BindPaths" class="headerlink" title="BindPaths"></a>BindPaths</h3><ul><li><code>BindPaths=/target:/source</code></li><li>将 <code>/source</code> 目录绑定到 <code>/target</code> 目录。服务访问 <code>/source</code> 目录时实际上会访问 <code>/target</code> 目录。</li></ul><h3 id="BindReadOnlyPaths"><a href="#BindReadOnlyPaths" class="headerlink" title="BindReadOnlyPaths"></a>BindReadOnlyPaths</h3><ul><li><code>BindReadOnlyPaths=/target:/source</code></li><li>与 <code>BindPaths</code> 类似，但绑定的目录为只读模式。服务只能读取 <code>/target</code> 目录，无法进行写操作。</li></ul><h3 id="ReadWritePaths"><a href="#ReadWritePaths" class="headerlink" title="ReadWritePaths"></a>ReadWritePaths</h3><ul><li><code>ReadWritePaths=/path/to/dir</code></li><li>指定服务可以读写访问的目录。这些目录将被临时挂载为读写，即使根文件系统是只读的。</li></ul><h3 id="ReadOnlyPaths"><a href="#ReadOnlyPaths" class="headerlink" title="ReadOnlyPaths"></a>ReadOnlyPaths</h3><ul><li><code>ReadOnlyPaths=/path/to/dir</code></li><li>指定服务可以只读访问的目录。这些目录将被临时挂载为只读。</li></ul><h3 id="InaccessiblePaths"><a href="#InaccessiblePaths" class="headerlink" title="InaccessiblePaths"></a>InaccessiblePaths</h3><ul><li><code>InaccessiblePaths=/path/to/dir</code></li><li>指定服务无法访问的目录。访问这些目录将导致权限错误。</li></ul><h3 id="TemporaryFileSystem"><a href="#TemporaryFileSystem" class="headerlink" title="TemporaryFileSystem"></a>TemporaryFileSystem</h3><ul><li><code>TemporaryFileSystem=/path/to/dir:mode=755,size=10M</code></li><li>将指定目录挂载为临时文件系统（tmpfs），类似于 mount -t tmpfs。可以用来提供服务的临时存储空间。</li></ul><h3 id="PrivateTmp"><a href="#PrivateTmp" class="headerlink" title="PrivateTmp"></a>PrivateTmp</h3><ul><li><code>PrivateTmp=yes</code></li><li>启用服务的私有 &#x2F;tmp 和 &#x2F;var&#x2F;tmp 目录，防止不同服务之间的临时文件相互影响。</li></ul><h3 id="ProtectSystem"><a href="#ProtectSystem" class="headerlink" title="ProtectSystem"></a>ProtectSystem</h3><ul><li><code>ProtectSystem=full</code></li><li>限制服务对系统文件和目录的写入访问。<ul><li>ProtectSystem&#x3D;yes：将 &#x2F;usr 和其他系统目录设置为只读。</li><li>ProtectSystem&#x3D;full：将 &#x2F;etc 也设置为只读。</li><li>ProtectSystem&#x3D;strict：将整个系统设置为只读，除了通过 ReadWritePaths 显式允许的目录。</li></ul></li></ul><h3 id="ProtectHome"><a href="#ProtectHome" class="headerlink" title="ProtectHome"></a>ProtectHome</h3><ul><li><code>ProtectHome=yes</code></li><li>限制服务对用户主目录的访问。<ul><li>ProtectHome&#x3D;yes：将用户主目录挂载为不可访问。</li><li>ProtectHome&#x3D;read-only：将用户主目录挂载为只读。</li><li>ProtectHome&#x3D;no：不限制用户主目录的访问。</li></ul></li></ul><h3 id="MountFlags"><a href="#MountFlags" class="headerlink" title="MountFlags"></a>MountFlags</h3><ul><li><code>MountFlags=slave</code></li><li>设置挂载点的标志。通常用来隔离服务的挂载命名空间。</li></ul><h2 id="备注"><a href="#备注" class="headerlink" title="备注"></a>备注</h2><p>部分环境中设置后并不能生效，报错内容如下：<br><img                         lazyload                       alt="image"                       data-src="/2024/0650acf1b8/1.png"                                        ><br>你需要升级systemd和内核。</p><p>systemd在 233 版本之后对这一特性添加了支持。见<a class="link"   href="https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#BindPaths=" >systemd-man-BindPaths<i class="fas fa-external-link-alt"></i></a>。<br>内核版本不确定，笔者在4.19的内核上测试失败但在6.1.32的内核验证成功。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 &lt;code&gt;systemd&lt;/code&gt; 中，可以使用 &lt;code&gt;BindPaths&lt;/code&gt; 或 &lt;code&gt;BindReadOnlyPaths&lt;/code&gt; 来重定向应用访问的目录。这些选项可以在 &lt;code&gt;.service&lt;/code&gt; 单元文件中设置，用于将特定的目录绑定到不同的位置，从而实现目录的重定向。&lt;code&gt;systemd&lt;/code&gt; 的这种机制，可以让我们更好的控制应用的行为，增强系统的安全性。&lt;/p&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/categories/Linux/"/>
    
    
    <category term="systemd" scheme="https://ssk-wh.github.io/tags/systemd/"/>
    
  </entry>
  
  <entry>
    <title>Fedora 中 ostree 更新 grub 引导的流程</title>
    <link href="https://ssk-wh.github.io/2024/0699dea904.html"/>
    <id>https://ssk-wh.github.io/2024/0699dea904.html</id>
    <published>2024-06-12T17:17:17.000Z</published>
    <updated>2024-06-13T03:10:33.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="grub-d-下的脚本"><a href="#grub-d-下的脚本" class="headerlink" title="grub.d 下的脚本"></a>grub.d 下的脚本</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">root@fedora:/etc/grub.d# <span class="built_in">ls</span> -al</span><br><span class="line">总计 108</span><br><span class="line">drwx------. 1 root root   392  6月11日 13:16 .</span><br><span class="line">drwxr-xr-x. 1 root root  4048  6月11日 13:22 ..</span><br><span class="line">-rwxr-xr-x. 1 root root  9346  6月11日 13:16 00_header</span><br><span class="line">-rwxr-xr-x. 1 root root   236  6月11日 13:16 01_users</span><br><span class="line">-rwxr-xr-x. 1 root root   835  6月11日 13:16 08_fallback_counting</span><br><span class="line">-rwxr-xr-x. 1 root root 19332  6月11日 13:16 10_linux</span><br><span class="line">-rwxr-xr-x. 1 root root   833  6月11日 13:16 10_reset_boot_success</span><br><span class="line">-rwxr-xr-x. 1 root root   892  6月11日 13:16 12_menu_auto_hide</span><br><span class="line">-rwxr-xr-x. 1 root root   410  6月11日 13:16 14_menu_show_once</span><br><span class="line">lrwxrwxrwx. 1 root root    38  6月11日 13:16 15_ostree -&gt; /usr/libexec/libostree/grub2-15_ostree</span><br><span class="line">-rwxr-xr-x. 1 root root 13613  6月11日 13:16 20_linux_xen</span><br><span class="line">-rwxr-xr-x. 1 root root  2562  6月11日 13:16 20_ppc_terminfo</span><br><span class="line">-rwxr-xr-x. 1 root root 10869  6月11日 13:16 30_os-prober</span><br><span class="line">-rwxr-xr-x. 1 root root  1122  6月11日 13:16 30_uefi-firmware</span><br><span class="line">-rwxr-xr-x. 1 root root   725  6月11日 13:16 35_fwupd</span><br><span class="line">-rwxr-xr-x. 1 root root   218  6月11日 13:16 40_custom</span><br><span class="line">-rwxr-xr-x. 1 root root   219  6月11日 13:16 41_custom</span><br><span class="line">-rw-r--r--. 1 root root   483  6月11日 13:16 README</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="ostree-修改-grub-引导项"><a href="#ostree-修改-grub-引导项" class="headerlink" title="ostree 修改 grub 引导项"></a>ostree 修改 grub 引导项</h2><p>核心逻辑见最后一行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/usr/bin/sh</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Copyright (C) 2014 Colin Walters &lt;walters@verbum.org&gt;</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># This program is free software: you can redistribute it and/or modify</span></span><br><span class="line"><span class="comment"># it under the terms of the GNU Lesser General Public License as published</span></span><br><span class="line"><span class="comment"># by the Free Software Foundation; either version 2 of the licence or (at</span></span><br><span class="line"><span class="comment"># your option) any later version.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># This library is distributed in the hope that it will be useful,</span></span><br><span class="line"><span class="comment"># but WITHOUT ANY WARRANTY; without even the implied warranty of</span></span><br><span class="line"><span class="comment"># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU</span></span><br><span class="line"><span class="comment"># Lesser General Public License for more details.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># You should have received a copy of the GNU Lesser General</span></span><br><span class="line"><span class="comment"># Public License along with this library. If not, see &lt;https://www.gnu.org/licenses/&gt;.</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Gracefully exit if ostree is not installed, or there&#x27;s</span></span><br><span class="line"><span class="comment"># no system repository initialized.</span></span><br><span class="line"><span class="keyword">if</span> ! <span class="built_in">which</span> ostree &gt;/dev/null 2&gt;/dev/null; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">exit</span> 0</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"><span class="keyword">if</span> ! <span class="built_in">test</span> -d /ostree/repo; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">exit</span> 0</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Gracefully exit if we can not find the grub2 &#x27;default&#x27; configuration as it is</span></span><br><span class="line"><span class="comment"># the case on new installations with bootupd where it is not needed.</span></span><br><span class="line"><span class="keyword">if</span> ! <span class="built_in">test</span> -f /etc/default/grub; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">exit</span> 0</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Gracefully exit if the grub2 configuration has BLS enabled,</span></span><br><span class="line"><span class="comment"># and the installed version has support for the blscfg module.</span></span><br><span class="line"><span class="comment"># Since there is no need to create menu entries for that case.</span></span><br><span class="line"><span class="comment"># See: https://src.fedoraproject.org/rpms/grub2/c/7c2bab5e98d</span></span><br><span class="line">. /etc/default/grub</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">test</span> -f /boot/grub2/.grub2-blscfg-supported &amp;&amp; \</span><br><span class="line">   <span class="built_in">test</span> <span class="string">&quot;<span class="variable">$&#123;GRUB_ENABLE_BLSCFG&#125;</span>&quot;</span> = <span class="string">&quot;true&quot;</span>; <span class="keyword">then</span></span><br><span class="line">   <span class="built_in">exit</span> 0</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Make sure we&#x27;re in the right environment</span></span><br><span class="line"><span class="keyword">if</span> ! <span class="built_in">test</span> -n <span class="string">&quot;<span class="variable">$&#123;GRUB_DEVICE&#125;</span>&quot;</span>; <span class="keyword">then</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;This script must be run as a child of grub2-mkconfig&quot;</span> 1&gt;&amp;2</span><br><span class="line">    <span class="built_in">exit</span> 1</span><br><span class="line"><span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="comment"># Pick up stuff from grub&#x27;s helper that we want to inject into our</span></span><br><span class="line"><span class="comment"># generated bootloader configuration.  Yes, this is pretty awful, but</span></span><br><span class="line"><span class="comment"># it&#x27;s a lot better than reimplementing the config-generating bits of</span></span><br><span class="line"><span class="comment"># OSTree in shell script.</span></span><br><span class="line"></span><br><span class="line">. /usr/share/grub/grub-mkconfig_lib</span><br><span class="line"></span><br><span class="line">DEVICE=<span class="variable">$&#123;GRUB_DEVICE_BOOT:-<span class="variable">$&#123;GRUB_DEVICE&#125;</span>&#125;</span></span><br><span class="line"></span><br><span class="line">GRUB2_BOOT_DEVICE_ID=<span class="string">&quot;<span class="subst">$(grub_get_device_id $&#123;DEVICE&#125;)</span>&quot;</span></span><br><span class="line"><span class="built_in">export</span> GRUB2_BOOT_DEVICE_ID</span><br><span class="line">GRUB2_PREPARE_ROOT_CACHE=<span class="string">&quot;<span class="subst">$(prepare_grub_to_access_device $&#123;DEVICE&#125;)</span>&quot;</span></span><br><span class="line"><span class="built_in">export</span> GRUB2_PREPARE_ROOT_CACHE</span><br><span class="line"></span><br><span class="line"><span class="built_in">exec</span> ostree admin instutil grub2-generate</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h2><p>阅读 <a class="link"   href="https://github.com/ostreedev/ostree" >ostree 源码<i class="fas fa-external-link-alt"></i></a>，函数调用顺序如下：<br>ot_admin_instutil_builtin_grub2_generate<br>ostree_generate_grub2_config<br>impl_ostree_generate_grub2_config<br>_ostree_bootloader_grub2_generate_config   &#x2F;&#x2F; 向target_fd(实际是向标准输出)中写入引导数据</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* This implementation is quite complex; see this issue for</span></span><br><span class="line"><span class="comment"> * a starting point:</span></span><br><span class="line"><span class="comment"> * https://github.com/ostreedev/ostree/issues/717</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">gboolean</span><br><span class="line">_ostree_bootloader_grub2_generate_config (OstreeSysroot                 *sysroot,</span><br><span class="line">                                          <span class="type">int</span>                            bootversion,</span><br><span class="line">                                          <span class="type">int</span>                            target_fd,</span><br><span class="line">                                          GCancellable                  *cancellable,</span><br><span class="line">                                          GError                       **error)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">/* So... yeah.  Just going to hardcode these. */</span></span><br><span class="line">    <span class="type">static</span> <span class="type">const</span> <span class="type">char</span> hardcoded_video[] = <span class="string">&quot;load_video\n&quot;</span></span><br><span class="line">        <span class="string">&quot;set gfxpayload=keep\n&quot;</span>;</span><br><span class="line">    <span class="type">static</span> <span class="type">const</span> <span class="type">char</span> hardcoded_insmods[] = <span class="string">&quot;insmod gzio\n&quot;</span>;</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *grub2_boot_device_id =</span><br><span class="line">        g_getenv (<span class="string">&quot;GRUB2_BOOT_DEVICE_ID&quot;</span>);</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span> *grub2_prepare_root_cache =</span><br><span class="line">        g_getenv (<span class="string">&quot;GRUB2_PREPARE_ROOT_CACHE&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* We must have been called via the wrapper script */</span></span><br><span class="line">    g_assert (grub2_boot_device_id != <span class="literal">NULL</span>);</span><br><span class="line">    g_assert (grub2_prepare_root_cache != <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Passed from the parent */</span></span><br><span class="line">    gboolean is_efi = g_getenv (<span class="string">&quot;_OSTREE_GRUB2_IS_EFI&quot;</span>) != <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    g_autoptr(GOutputStream) out_stream = g_unix_output_stream_new (target_fd, FALSE);</span><br><span class="line"></span><br><span class="line">    g_autoptr(GPtrArray) loader_configs = <span class="literal">NULL</span>;</span><br><span class="line">    <span class="keyword">if</span> (!_ostree_sysroot_read_boot_loader_configs (sysroot, bootversion,</span><br><span class="line">                                                 &amp;loader_configs,</span><br><span class="line">                                                 cancellable, error))</span><br><span class="line">        <span class="keyword">return</span> FALSE;</span><br><span class="line"></span><br><span class="line">    g_autoptr(GString) output = g_string_new (<span class="string">&quot;&quot;</span>);</span><br><span class="line">    <span class="keyword">for</span> (guint i = <span class="number">0</span>; i &lt; loader_configs-&gt;len; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        OstreeBootconfigParser *config = loader_configs-&gt;pdata[i];</span><br><span class="line">        <span class="type">const</span> <span class="type">char</span> *title;</span><br><span class="line">        <span class="type">const</span> <span class="type">char</span> *options;</span><br><span class="line">        <span class="type">const</span> <span class="type">char</span> *kernel;</span><br><span class="line">        <span class="type">const</span> <span class="type">char</span> *initrd;</span><br><span class="line">        <span class="type">const</span> <span class="type">char</span> *devicetree;</span><br><span class="line">        <span class="type">char</span> *quoted_title = <span class="literal">NULL</span>;</span><br><span class="line">        <span class="type">char</span> *uuid = <span class="literal">NULL</span>;</span><br><span class="line">        <span class="type">char</span> *quoted_uuid = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">        title = ostree_bootconfig_parser_get (config, <span class="string">&quot;title&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (!title)</span><br><span class="line">            title = <span class="string">&quot;(Untitled)&quot;</span>;</span><br><span class="line"></span><br><span class="line">        kernel = ostree_bootconfig_parser_get (config, <span class="string">&quot;linux&quot;</span>);</span><br><span class="line"></span><br><span class="line">        quoted_title = g_shell_quote (title);</span><br><span class="line">        uuid = g_strdup_printf (<span class="string">&quot;ostree-%u-%s&quot;</span>, (guint)i, grub2_boot_device_id);</span><br><span class="line">        quoted_uuid = g_shell_quote (uuid);</span><br><span class="line">        g_string_append_printf (output, <span class="string">&quot;menuentry %s --class gnu-linux --class gnu --class os --unrestricted %s &#123;\n&quot;</span>, quoted_title, quoted_uuid);</span><br><span class="line">        g_free (uuid);</span><br><span class="line">        g_free (quoted_title);</span><br><span class="line">        g_free (quoted_uuid);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* Hardcoded sections */</span></span><br><span class="line">        g_string_append (output, hardcoded_video);</span><br><span class="line">        g_string_append (output, hardcoded_insmods);</span><br><span class="line">        g_string_append (output, grub2_prepare_root_cache);</span><br><span class="line">        g_string_append_c (output, <span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!kernel)</span><br><span class="line">            <span class="keyword">return</span> glnx_throw (error, <span class="string">&quot;No \&quot;linux\&quot; key in bootloader config&quot;</span>);</span><br><span class="line">        g_string_append (output, <span class="string">&quot;linux&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (is_efi)</span><br><span class="line">            g_string_append (output, GRUB2_EFI_SUFFIX);</span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            g_string_append (output, GRUB2_SUFFIX);</span><br><span class="line">        g_string_append_c (output, <span class="string">&#x27; &#x27;</span>);</span><br><span class="line">        g_string_append (output, kernel);</span><br><span class="line"></span><br><span class="line">        options = ostree_bootconfig_parser_get (config, <span class="string">&quot;options&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (options)</span><br><span class="line">        &#123;</span><br><span class="line">            g_string_append_c (output, <span class="string">&#x27; &#x27;</span>);</span><br><span class="line">            g_string_append (output, options);</span><br><span class="line">        &#125;</span><br><span class="line">        g_string_append_c (output, <span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line"></span><br><span class="line">        initrd = ostree_bootconfig_parser_get (config, <span class="string">&quot;initrd&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (initrd)</span><br><span class="line">        &#123;</span><br><span class="line">            g_string_append (output, <span class="string">&quot;initrd&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span> (is_efi)</span><br><span class="line">                g_string_append (output, GRUB2_EFI_SUFFIX);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                g_string_append (output, GRUB2_SUFFIX);</span><br><span class="line">            g_string_append_c (output, <span class="string">&#x27; &#x27;</span>);</span><br><span class="line">            g_string_append (output, initrd);</span><br><span class="line">            g_string_append_c (output, <span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        devicetree = ostree_bootconfig_parser_get (config, <span class="string">&quot;devicetree&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (devicetree)</span><br><span class="line">        &#123;</span><br><span class="line">            g_string_append (output, <span class="string">&quot;devicetree&quot;</span>);</span><br><span class="line">            g_string_append_c (output, <span class="string">&#x27; &#x27;</span>);</span><br><span class="line">            g_string_append (output, devicetree);</span><br><span class="line">            g_string_append_c (output, <span class="string">&#x27;\n&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        g_string_append (output, <span class="string">&quot;&#125;\n&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    gsize bytes_written;</span><br><span class="line">    <span class="keyword">if</span> (!g_output_stream_write_all (out_stream, output-&gt;str, output-&gt;len,</span><br><span class="line">                                  &amp;bytes_written, cancellable, error))</span><br><span class="line">        <span class="keyword">return</span> FALSE;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> TRUE;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="查看所属软件包"><a href="#查看所属软件包" class="headerlink" title="查看所属软件包"></a>查看所属软件包</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@fedora:/etc/grub.d# rpm -qf /usr/libexec/libostree/grub2-15_ostree</span><br><span class="line">ostree-grub2-2024.5-1.fc40.x86_64</span><br></pre></td></tr></table></figure><h2 id="查询软件包提供者"><a href="#查询软件包提供者" class="headerlink" title="查询软件包提供者"></a>查询软件包提供者</h2><p>源码也是由 ostree 提供</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">root@fedora:/etc/grub.d# rpm -qi ostree-grub2</span><br><span class="line">Name        : ostree-grub2</span><br><span class="line">Version     : 2024.5</span><br><span class="line">Release     : 1.fc40</span><br><span class="line">Architecture: x86_64</span><br><span class="line">Install Date: 2024年04月15日 星期一 02时09分59秒</span><br><span class="line">Group       : Unspecified</span><br><span class="line">Size        : 2266</span><br><span class="line">License     : LGPL-2.0-or-later</span><br><span class="line">Signature   : RSA/SHA256, 2024年03月15日 星期五 07时19分33秒, Key ID 0727707ea15b79cc</span><br><span class="line">Source RPM  : ostree-2024.5-1.fc40.src.rpm</span><br><span class="line">Build Date  : 2024年03月15日 星期五 06时09分26秒</span><br><span class="line">Build Host  : buildhw-x86-08.iad2.fedoraproject.org</span><br><span class="line">Packager    : Fedora Project</span><br><span class="line">Vendor      : Fedora Project</span><br><span class="line">URL         : https://ostree.readthedocs.io/en/latest/</span><br><span class="line">Bug URL     : https://bugz.fedoraproject.org/ostree</span><br><span class="line">Summary     : GRUB2 integration <span class="keyword">for</span> OSTree</span><br><span class="line">Description :</span><br><span class="line">GRUB2 integration <span class="keyword">for</span> OSTree</span><br></pre></td></tr></table></figure><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>ostree-grub2 安装时提供了 &#x2F;usr&#x2F;libexec&#x2F;libostree&#x2F;grub2-15_ostree , 并链接至 &#x2F;etc&#x2F;grub.d&#x2F;15_ostree，在重启前，执行 grub-mkconfig 操作，更新 grub 引导数据。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;grub-d-下的脚本&quot;&gt;&lt;a href=&quot;#grub-d-下的脚本&quot; class=&quot;headerlink&quot; title=&quot;grub.d 下的脚本&quot;&gt;&lt;/a&gt;grub.d 下的脚本&lt;/h2&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table</summary>
      
    
    
    
    <category term="Fedora" scheme="https://ssk-wh.github.io/categories/Fedora/"/>
    
    
    <category term="fedora" scheme="https://ssk-wh.github.io/tags/fedora/"/>
    
    <category term="grub" scheme="https://ssk-wh.github.io/tags/grub/"/>
    
    <category term="ostree" scheme="https://ssk-wh.github.io/tags/ostree/"/>
    
  </entry>
  
  <entry>
    <title>ostree命令的基础用法</title>
    <link href="https://ssk-wh.github.io/2024/0633106365.html"/>
    <id>https://ssk-wh.github.io/2024/0633106365.html</id>
    <published>2024-06-02T10:15:29.000Z</published>
    <updated>2024-06-03T03:54:13.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>sudo apt install ostree</p><h2 id="init"><a href="#init" class="headerlink" title="init"></a>init</h2><p>cd ostree&#x2F;<br>ostree init –repo&#x3D;. init<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/1.png"                                        ></p><p>Just make your own changes and continueostree…</p><h2 id="commit"><a href="#commit" class="headerlink" title="commit"></a>commit</h2><p>将 tree&#x2F; 目录下的内容导入，提交信息未填写默认为空<br>ostree –repo&#x3D;. commit –branch&#x3D;foo tree&#x2F;<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/2.png"                                        ></p><p>指定提交内容<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/3.png"                                        ></p><h2 id="refs"><a href="#refs" class="headerlink" title="refs"></a>refs</h2><p>列出仓库分支<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/4.png"                                        ></p><h2 id="ls"><a href="#ls" class="headerlink" title="ls"></a>ls</h2><p>查看文件系统树的内容<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/5.png"                                        ></p><h2 id="cat"><a href="#cat" class="headerlink" title="cat"></a>cat</h2><p>查看文件内容<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/6.png"                                        ></p><h2 id="checkout"><a href="#checkout" class="headerlink" title="checkout"></a>checkout</h2><p>检出<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/7.png"                                        ></p><h2 id="reset"><a href="#reset" class="headerlink" title="reset"></a>reset</h2><p>重置到某个提交，在此之后的提交全部丢弃<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/8.png"                                        ></p><h2 id="log"><a href="#log" class="headerlink" title="log"></a>log</h2><p>查看分支提交信息<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/9.png"                                        ></p><h2 id="show"><a href="#show" class="headerlink" title="show"></a>show</h2><p>查看最新一笔提交<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/10.png"                                        ></p><h2 id="diff"><a href="#diff" class="headerlink" title="diff"></a>diff</h2><p>比较提交差异<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/11.png"                                        ></p><h2 id="OSTREE-REPO"><a href="#OSTREE-REPO" class="headerlink" title="OSTREE_REPO"></a>OSTREE_REPO</h2><p>指定仓库地址，从而无需在命令中指定–repo<br><img                         lazyload                       alt="image"                       data-src="/2024/0633106365/12.png"                                        ></p><h2 id="Manual"><a href="#Manual" class="headerlink" title="Manual"></a>Manual</h2><p><a class="link"   href="https://ostreedev.github.io/ostree/man/" >https://ostreedev.github.io/ostree/man/<i class="fas fa-external-link-alt"></i></a></p><h2 id="开发"><a href="#开发" class="headerlink" title="开发"></a>开发</h2><p><a class="link"   href="https://github.com/qt/qtotaupdate" >https://github.com/qt/qtotaupdate<i class="fas fa-external-link-alt"></i></a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;安装&quot;&gt;&lt;a href=&quot;#安装&quot; class=&quot;headerlink&quot; title=&quot;安装&quot;&gt;&lt;/a&gt;安装&lt;/h2&gt;&lt;p&gt;sudo apt install ostree&lt;/p&gt;
&lt;h2 id=&quot;init&quot;&gt;&lt;a href=&quot;#init&quot; class=&quot;heade</summary>
      
    
    
    
    
    <category term="ostree" scheme="https://ssk-wh.github.io/tags/ostree/"/>
    
  </entry>
  
  <entry>
    <title>Linux的启动</title>
    <link href="https://ssk-wh.github.io/2024/0530982cbe.html"/>
    <id>https://ssk-wh.github.io/2024/0530982cbe.html</id>
    <published>2024-05-31T13:48:54.000Z</published>
    <updated>2024-07-02T05:24:23.000Z</updated>
    
    <content type="html"><![CDATA[<p><img                         lazyload                       alt="image"                       data-src="/2024/0530982cbe/Linux%E7%9A%84%E5%90%AF%E5%8A%A8.jpg"                                        ></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;img  
                       lazyload
                       alt=&quot;image&quot;
                       data-src=&quot;/2024/0530982cbe/Linux%E7%9A%8</summary>
      
    
    
    
    
    <category term="grub" scheme="https://ssk-wh.github.io/tags/grub/"/>
    
    <category term="Linux" scheme="https://ssk-wh.github.io/tags/Linux/"/>
    
    <category term="boot" scheme="https://ssk-wh.github.io/tags/boot/"/>
    
    <category term="initrd" scheme="https://ssk-wh.github.io/tags/initrd/"/>
    
    <category term="initramfs" scheme="https://ssk-wh.github.io/tags/initramfs/"/>
    
    <category term="内核" scheme="https://ssk-wh.github.io/tags/%E5%86%85%E6%A0%B8/"/>
    
  </entry>
  
  <entry>
    <title>OverlayFS</title>
    <link href="https://ssk-wh.github.io/2024/05c2403edf.html"/>
    <id>https://ssk-wh.github.io/2024/05c2403edf.html</id>
    <published>2024-05-31T13:19:27.000Z</published>
    <updated>2024-05-31T05:56:27.000Z</updated>
    
    <content type="html"><![CDATA[<p>OverlayFS（Overlay Filesystem）是一种联合文件系统，允许将一个或多个文件系统层叠合并为一个单一的文件系统视图。它最常用于容器技术（如Docker）和其他需要高效文件系统管理的场景。以下是OverlayFS的基本概念、工作原理、以及常见的使用场景。</p><span id="more"></span><h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p>OverlayFS有三个主要的目录概念：</p><ul><li><p><strong>Lower Directory（下层目录）</strong>：这是底层的只读文件系统。</p></li><li><p><strong>Upper Directory（上层目录）</strong>：这是上层的可写文件系统。</p></li><li><p><strong>Work Directory（工作目录）</strong>：这是一个用于支持上层目录操作的工作目录。</p></li></ul><p>OverlayFS通过将上层目录和下层目录合并，提供一个统一的视图给用户。在这个视图中，上层目录的文件会覆盖下层目录中的文件。</p><h2 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h2><p>当你挂载OverlayFS时，你指定一个下层目录（lowerdir）、一个上层目录（upperdir）和一个工作目录（workdir）。合并后的结果会展示在一个挂载点（merged）上。<br>以下是一些操作如何在OverlayFS中处理：</p><ul><li><strong>读取文件</strong>：如果文件存在于上层目录，则读取上层目录的文件；如果不存在，则读取下层目录的文件。</li><li><strong>写入文件</strong>：写入操作始终发生在上层目录。如果文件在下层目录中存在，写入操作会在上层目录中创建一个文件副本并进行修改。</li><li><strong>删除文件</strong>：删除操作在上层目录中创建一个白化文件（whiteout file），从而在合并视图中隐藏下层目录的文件。</li></ul><h2 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><p>假设你有以下目录结构：</p><ul><li><strong>lowerdir</strong>：下层只读目录。</li><li><strong>upperdir</strong>：上层可写目录。</li><li><strong>workdir</strong>：工作目录。</li><li><strong>merged</strong>：合并后的挂载点。</li></ul><h2 id="挂载OverlayFS"><a href="#挂载OverlayFS" class="headerlink" title="挂载OverlayFS"></a>挂载OverlayFS</h2><p><strong>创建目录</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p lowerdir upperdir workdir merged</span><br></pre></td></tr></table></figure><p><strong>挂载OverlayFS</strong>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="built_in">sudo</span> mount -t overlay overlay -o lowerdir=lowerdir,upperdir=upperdir,workdir=workdir merged</span><br></pre></td></tr></table></figure><p><strong>验证挂载</strong>：<br>你可以通过以下命令查看挂载的结果：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">ls</span> merged</span><br></pre></td></tr></table></figure><h2 id="操作示例"><a href="#操作示例" class="headerlink" title="操作示例"></a>操作示例</h2><h3 id="读取文件"><a href="#读取文件" class="headerlink" title="读取文件"></a>读取文件</h3><p>如果 <strong>lowerdir</strong> 包含 <strong>file1</strong>，但 <strong>upperdir</strong> 不包含，那么 <strong>file1</strong> 可以在 <strong>merged</strong> 中读取。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;This is a file in lowerdir&quot;</span> &gt; lowerdir/file1</span><br><span class="line"><span class="built_in">cat</span> merged/file1</span><br><span class="line"><span class="comment"># 输出: This is a file in lowerdir</span></span><br></pre></td></tr></table></figure><h3 id="写入文件"><a href="#写入文件" class="headerlink" title="写入文件"></a>写入文件</h3><p>如果你在 <strong>merged</strong> 中写入文件，文件会出现在 <strong>upperdir</strong> 中。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;This is a new file in merged&quot;</span> &gt; merged/file2</span><br><span class="line"><span class="built_in">cat</span> upperdir/file2</span><br><span class="line"><span class="comment"># 输出: This is a new file in merged</span></span><br></pre></td></tr></table></figure><h3 id="修改文件"><a href="#修改文件" class="headerlink" title="修改文件"></a>修改文件</h3><p>如果你修改 <strong>merged</strong> 中存在于 <strong>lowerdir</strong> 的文件，修改后的副本会保存在 <strong>upperdir</strong> 中。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">&quot;This is a modified file in merged&quot;</span> &gt; merged/file1</span><br><span class="line"><span class="built_in">cat</span> upperdir/file1</span><br><span class="line"><span class="comment"># 输出: This is a modified file in merged</span></span><br><span class="line"><span class="built_in">cat</span> lowerdir/file1</span><br><span class="line"><span class="comment"># 输出: This is a file in lowerdir</span></span><br></pre></td></tr></table></figure><h3 id="删除文件"><a href="#删除文件" class="headerlink" title="删除文件"></a>删除文件</h3><p>删除操作在上层目录中创建一个白化文件，从而在合并视图中隐藏下层目录的文件。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">rm</span> merged/file1</span><br><span class="line"><span class="built_in">ls</span> merged</span><br><span class="line"><span class="comment"># `file1` 不再存在</span></span><br><span class="line"><span class="built_in">ls</span> upperdir</span><br><span class="line"><span class="comment"># 输出: file1 (白化文件)</span></span><br></pre></td></tr></table></figure><h3 id="取消挂载"><a href="#取消挂载" class="headerlink" title="取消挂载"></a>取消挂载</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看挂载信息（确认挂载点）</span></span><br><span class="line"><span class="built_in">sudo</span> mount | grep merged</span><br><span class="line"></span><br><span class="line"><span class="comment"># 取消挂载</span></span><br><span class="line"><span class="built_in">sudo</span> umount merged</span><br></pre></td></tr></table></figure><h2 id="常见使用场景"><a href="#常见使用场景" class="headerlink" title="常见使用场景"></a>常见使用场景</h2><ul><li><strong>容器技术</strong>：Docker等容器技术广泛使用OverlayFS来管理镜像层和容器层，使得镜像可以共享底层文件而不重复存储。</li><li><strong>开发和测试</strong>：开发人员可以使用OverlayFS创建临时的文件系统视图进行测试，而不影响原有文件系统。</li><li><strong>系统升级和恢复</strong>：系统管理员可以使用OverlayFS进行系统升级测试，确保系统稳定后再进行实际的升级操作。</li></ul><p>OverlayFS提供了一种高效且灵活的文件系统管理方式，适用于多种需要层叠文件系统视图的应用场景。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;OverlayFS（Overlay Filesystem）是一种联合文件系统，允许将一个或多个文件系统层叠合并为一个单一的文件系统视图。它最常用于容器技术（如Docker）和其他需要高效文件系统管理的场景。以下是OverlayFS的基本概念、工作原理、以及常见的使用场景。&lt;/p&gt;</summary>
    
    
    
    
    <category term="fs" scheme="https://ssk-wh.github.io/tags/fs/"/>
    
    <category term="overlay" scheme="https://ssk-wh.github.io/tags/overlay/"/>
    
  </entry>
  
  <entry>
    <title>Wayland Coding 速记-Staging</title>
    <link href="https://ssk-wh.github.io/2024/05df3c7382.html"/>
    <id>https://ssk-wh.github.io/2024/05df3c7382.html</id>
    <published>2024-05-28T10:53:07.000Z</published>
    <updated>2024-05-28T06:11:11.000Z</updated>
    
    <content type="html"><![CDATA[<p>因工作需要加之个人比较感兴趣的原因，在实现 Wayland 合成器相关协议之途中，随笔记录记录一些相关的基础知识。</p><span id="more"></span><p><a name="Tmrp0"></a></p><h2 id="Wayland"><a href="#Wayland" class="headerlink" title="Wayland"></a>Wayland</h2><p>关于 wayland 的总结，个人觉得下面一段话挺好：</p><blockquote><p>WaylandWayland is a replacement for the X11 window system protocol and architecture with the aim to be easier to develop, extend, and maintain.<br /></p><p>WaylandWayland 是 X11 窗口系统协议和架构的替代品，旨在更易于开发、扩展和维护。<br /></p><p>Wayland is the language (protocol) that applications can use to talk to a display server in order to make themselves visible and get input from the user (a person). A Wayland server is called a “compositor”. Applications are Wayland clients.<br /></p><p>Wayland 是应用程序可用来与显示服务器对话的语言（协议），以便使自己可见并获取用户（人）的输入。 Wayland 服务器被称为“合成器”。应用程序是 Wayland 客户端。<br /></p><p>Wayland also refers to a system architecture. It is not just a server-client relationship between a compositor and applications. There is no single common Wayland server like Xorg is for X11, but every graphical environment brings with it one of many compositor implementations. Window management and the end user experience are often tied to the compositor rather than swappable components.<br /></p><p>Wayland 也指一种系统架构。它不仅仅是合成器和应用程序之间的服务器-客户端关系。没有像 Xorg 那样适用于 X11 的单一通用 Wayland 服务器，但每个图形环境都带来了许多合成器实现之一。窗口管理和最终用户体验通常与合成器而不是可交换组件相关。<br /></p><p>A core part of Wayland architecture is libwayland: an inter-process communication library that translates a protocol definition in XML to a C language API. This library does not implement Wayland, it merely encodes and decodes Wayland messages. The actual implementations are in the various compositor and application toolkit projects.<br /></p><p>Wayland 架构的核心部分是 libwayland：一个进程间通信库，它将 XML 中的协议定义转换为 C 语言 API。该库没有实现 Wayland，它只是对 Wayland 消息进行编码和解码。实际的实现是在各种合成器和应用程序工具包项目中。<br /></p><p>Wayland does not restrict where and how it is used. A Wayland compositor could be a standalone display server running on Linux kernel modesetting and evdev input devices or on many other operating systems, or a nested compositor that itself is an X11 or Wayland application (client). Wayland can even be used in application-internal communication as is done in some web browsers.<br /></p><p>Wayland 不限制其使用地点和方式。 Wayland 合成器可以是在 Linux 内核模式设置和 evdev 输入设备或许多其他操作系统上运行的独立显示服务器，也可以是本身就是 X11 或 Wayland 应用程序（客户端）的嵌套合成器。 Wayland 甚至可以用于应用程序内部通信，就像某些 Web 浏览器中所做的那样。<br /></p><p>Part of the Wayland project is also the Weston reference implementation of a Wayland compositor. Weston can run as an X client or under Linux KMS and ships with a few demo clients. The Weston compositor is a minimal and fast compositor and is suitable for many embedded and mobile use cases.</p><p>Wayland 项目的一部分也是 Wayland 合成器的 Weston 参考实现。 Weston 可以作为 X 客户端或在 Linux KMS 下运行，并附带一些演示客户端。 Weston 合成器是一个最小且快速的合成器，适用于许多嵌入式和移动用例。</p></blockquote><p><a name="LJoxs"></a></p><h2 id="开发库"><a href="#开发库" class="headerlink" title="开发库"></a>开发库</h2><p>后续开发内容均基于 libwayland-dev 进行。</p><p><a name="yTd8M"></a></p><h3 id="主要结构体"><a href="#主要结构体" class="headerlink" title="主要结构体"></a>主要结构体</h3><table><thead><tr><th><strong>Struct</strong></th><th><strong>Description</strong></th><th><strong>Example</strong></th></tr></thead><tbody><tr><td>wl_display</td><td>代表一个 Wayland 显示服务器的连接。它用于管理客户端和服务器之间的通信和事件处理</td><td>struct wl_display *wl_display_create(void);<br />void wl_display_run(struct wl_display *display);<br />struct wl_list *wl_display_get_client_list(struct wl_display *display);</td></tr><tr><td>wl_global</td><td>用于描述全局对象。这些全局对象在 Wayland 显示服务器中注册，可以被客户端发现和使用。<strong>wl_global</strong> 对象通常表示服务器中提供的某些功能或接口，例如 compositor、shell、seat 等</td><td>struct wl_global *wl_global_create(struct wl_display *display, const struct wl_interface *interface, int version, void *data, wl_global_bind_func_t bind);</td></tr><tr><td>wl_event_loop</td><td>用于管理事件循环。在 Wayland 服务器中，事件循环用于处理来自客户端的请求、内部超时事件以及文件描述符上的事件。</td><td>struct wl_event_loop *wl_event_loop_create(void);<br />void wl_event_loop_destroy(struct wl_event_loop *loop);</td></tr><tr><td>wl_event_source</td><td>用于描述一个事件源。在 Wayland 服务器的事件循环中，事件源可以是文件描述符事件、定时器事件或信号事件。</td><td>struct wl_event_source *wl_event_loop_add_fd(struct wl_event_loop *loop, int fd, uint32_t mask, wl_event_loop_fd_func_t func, void *data);</td></tr><tr><td>wl_interface</td><td>用于描述 Wayland 协议中的接口。接口是 Wayland 协议的基本构建块，定义了客户端和服务器之间可以进行的交互。每个接口包括一组方法（requests）和事件（events）。</td><td>struct wl_interface {<br />    const char *name;<br />    int version;<br />    int method_count;<br />    const struct wl_message *methods;<br />    int event_count;<br />    const struct wl_message *events;<br />};</td></tr><tr><td>wl_message</td><td>用于描述接口中的每个方法和事件。</td><td>static const struct wl_message my_interface_events[] &#x3D; { { “something_done”, “s”, NULL }  &#x2F;&#x2F; “s” 表示事件发送一个字符串参数 };</td></tr><tr><td>wl_resource</td><td>用于表示 Wayland 客户端与服务器之间的一个协议对象。它在客户端和服务器之间传递方法调用和事件通知。每个 <strong>wl_resource</strong> 都与一个特定的 <strong>wl_interface</strong>（接口）相关联，表示该接口的一个实例。</td><td>struct wl_resource *wl_resource_create(struct wl_client *client, const struct wl_interface *interface, int version, uint32_t id);<br />void wl_resource_set_implementation(struct wl_resource *resource, const void *implementation, void *data, wl_resource_destroy_func_t destroy);</td></tr><tr><td>wl_surface</td><td>代表了一个可供客户端绘制的表面（Surface）。它是构建用户界面的基本单元，可以是窗口、按钮、文本框等可见的元素。<strong>wl_surface</strong> 通过 Wayland 协议与客户端和服务器进行通信，客户端可以向 <strong>wl_surface</strong> 发送绘图指令，服务器则负责将这些指令转换为屏幕上的图像。</td><td></td></tr><tr><td>wl_output</td><td>Wayland 中用于表示显示器（output）的接口。每个 <strong>wl_output</strong> 对象代表了系统中的一个物理显示设备，比如显示器或投影仪。通过 <strong>wl_output</strong> 接口，客户端程序可以获取有关显示器的信息，如分辨率、缩放因子、物理尺寸、制造商信息等，并接收显示器的事件，如模式更改、连接状态变化等。</td><td>wl_output_add_geometry_listener</td></tr><tr><td>wl_client</td><td>用于表示一个连接到 Wayland 服务器的客户端。它负责管理客户端连接、处理客户端的请求，并向客户端发送事件</td><td>struct wl_client *wl_resource_get_client(struct wl_resource *resource);<br />void wl_client_post_no_memory(struct wl_client *client);<br />void wl_client_post_implementation_error(struct wl_client *client, const char *msg);<br />void wl_client_post_event(struct wl_client *client, uint32_t opcode, …);</td></tr><tr><td>wl_signal</td><td>用于实现发布-订阅模式的信号机制。它允许对象在状态变化时通知感兴趣的侦听器（监听器）。<strong>wl_signal</strong> 是一个简单但功能强大的机制，可以在 Wayland 服务端内部或者在客户端与服务端之间传递事件通知。</td><td>struct wl_signal my_signal;<br />wl_signal_init(&amp;my_signal);<br />void my_signal_handler(struct wl_listener *listener, void *data) {<br />    printf(“Signal received with data: %s\n”, (char *)data);<br />}<br />struct wl_listener my_listener;<br />my_listener.notify &#x3D; my_signal_handler;<br />wl_signal_add(&amp;my_signal, &amp;my_listener);<br />&#x2F;&#x2F; send signal to notify all listener<br />const char *signal_data &#x3D; “Hello, World!”;<br />wl_signal_emit(&amp;my_signal, (void *)signal_data);</td></tr><tr><td>wl_listener</td><td>用于监听 <strong>wl_signal</strong> 发出的信号。每个 <strong>wl_listener</strong> 都包含一个回调函数，当监听的信号发出时，该回调函数会被调用。这种机制使得对象之间的通信变得更加灵活和解耦。</td><td>struct wl_listener {<br />    struct wl_list link;<br />    wl_notify_func_t notify;<br />};<br />struct wl_listener my_listener;<br />my_listener.notify &#x3D; my_signal_handler; &#x2F;&#x2F; 设置回调函数<br />wl_signal_add(&amp;my_signal, &amp;my_listener); &#x2F;&#x2F; 将监听器添加到信号中</td></tr><tr><td>wl_list</td><td>Wayland 核心库中的一个双向链表实现，用于在 Wayland 内部和相关组件中进行列表管理</td><td>struct wl_list {<br />    struct wl_list *prev;<br />    struct wl_list *next;<br />};<br />void wl_list_init(struct wl_list *list);<br />void wl_list_insert(struct wl_list *list, struct wl_list *elm);<br />void wl_list_remove(struct wl_list *elm);<br />wl_list_for_each &amp; wl_list_for_each_safe</td></tr><tr><td>wl_shm_buffer</td><td>Wayland 的共享内存（Shared Memory）缓冲区，用于在客户端和服务器之间共享图像数据。它允许客户端将图像数据写入共享内存，然后将该内存区域作为缓冲区发送到服务器。服务器可以直接访问这个共享内存，从而避免了数据的拷贝，提高了效率</td><td></td></tr><tr><td>wl_shm_pool</td><td>Wayland 提供的一个共享内存池，用于在客户端和服务器之间共享图像数据。<strong>wl_shm_pool</strong> 是通过 Wayland 的 <strong>wl_shm</strong> 接口创建的，它允许客户端从共享内存中分配多个缓冲区。这些缓冲区可以被客户端用来绘制图像，并将其传递给服务器显示。</td><td></td></tr><tr><td>wl_protocol_logger</td><td>用于记录 Wayland 协议的消息。它允许开发者记录客户端和服务器之间交换的协议消息，方便调试和分析 Wayland 协议的使用情况。</td><td>struct wl_protocol_logger {<br />    void (*log)(void *user_data, struct wl_resource *resource,<br />                uint32_t opcode, const struct wl_message *message,<br />                union wl_argument *args);<br />    void *user_data;<br />};<br /><br />struct wl_protocol_logger logger &#x3D; { <br />.log &#x3D; protocol_log, <br />.user_data &#x3D; “Wayland”  &#x2F;&#x2F; 这里可以传递任何用户数据<br /> }; <br />wl_display_add_protocol_logger(display, &amp;logger);</td></tr><tr><td>wl_display_add_destroy_listener</td><td>绑定到一个 wl_listener 结构，通过指定 wl_listener 的.notify成员实现对 display 销毁时的监听<br />用于在 wl_display 对象销毁时注册一个回调函数。这个回调函数会在 wl_display 对象销毁时被调用，以便进行清理或其他必要的操作。</td><td>manager-&gt;display_destroy.notify &#x3D; handle_display_destroy;<br />    wl_display_add_destroy_listener(display, &amp;manager-&gt;display_destroy);</td></tr></tbody></table><p><a name="jKOh5"></a></p><h3 id="主要函数"><a href="#主要函数" class="headerlink" title="主要函数"></a>主要函数</h3><table><thead><tr><th><strong>Function</strong></th><th><strong>Description</strong></th><th><strong>Example</strong></th></tr></thead><tbody><tr><td>wl_display_add_destroy_listener</td><td>绑定到一个 wl_listener 结构，通过指定 wl_listener 的.notify成员实现对 display 销毁时的监听<br />用于在 wl_display 对象销毁时注册一个回调函数。这个回调函数会在 wl_display 对象销毁时被调用，以便进行清理或其他必要的操作。</td><td>manager-&gt;display_destroy.notify &#x3D; handle_display_destroy;<br />    wl_display_add_destroy_listener(display, &amp;manager-&gt;display_destroy);</td></tr><tr><td>wl_event_loop_add_destroy_listener</td><td>用于向事件循环添加一个监听器，以便在事件循环被销毁时触发回调函数。</td><td>struct wl_listener *wl_event_loop_add_destroy_listener(struct wl_event_loop *loop, wl_listener *listener);</td></tr><tr><td>wl_global_create</td><td>用于创建一个全局对象，并将其注册到 Wayland 显示服务器上。全局对象可以被客户端程序获取并使用，从而实现客户端和服务器之间的通信。</td><td>struct wl_global *wl_global_create(struct wl_display *display,<br />                                   const struct wl_interface *interface,<br />                                   int version,<br />                                   void *data,<br />                                   wl_global_bind_func_t bind);</td></tr><tr><td>wl_global_remove</td><td>类似wl_global_destroy，但并不销毁。通常使用 wl_global_destroy 即可</td><td></td></tr><tr><td>wl_global_destroy</td><td>销毁一个全局对象。</td><td></td></tr><tr><td>wl_resource_set_implementation</td><td>用于将一组回调函数（即接口实现）和用户数据关联到一个 <strong>wl_resource</strong> 对象。每当与该资源相关的客户端请求到达时，Wayland 会调用相应的回调函数，从而实现具体的行为。</td><td>void wl_resource_set_implementation(struct wl_resource *resource,<br /> const void *implementation,<br />void *data,<br /> wl_resource_destroy_func_t destroy);</td></tr><tr><td>wl_resource_set_user_data</td><td>用于将用户自定义的数据与特定的 <strong>wl_resource</strong> 资源关联起来。这使得在处理资源相关的回调时，可以访问和使用这些用户数据。</td><td>void wl_resource_set_user_data(struct wl_resource *resource, void *user_data);</td></tr><tr><td>wl_display_roundtrip</td><td>Wayland 客户端程序中常用的函数之一，用于同步地发送请求并等待服务器对请求的响应。它会阻塞当前线程，直到服务器返回响应或者发生错误。</td><td>int wl_display_roundtrip(struct wl_display *display);</td></tr><tr><td>wl_resource_get_user_data</td><td>用于获取与特定 <strong>wl_resource</strong> 资源关联的用户数据。这个函数通常与 <strong>wl_resource_set_user_data</strong> 一起使用，后者用于将用户数据与资源关联。</td><td>void *wl_resource_get_user_data(struct wl_resource *resource);</td></tr></tbody></table><p><a name="Vw3Mb"></a></p><h3 id="核心文件"><a href="#核心文件" class="headerlink" title="核心文件"></a>核心文件</h3><p>wayland-server-core.h</p><p><a name="ICOD6"></a></p><h2 id="协议分类"><a href="#协议分类" class="headerlink" title="协议分类"></a>协议分类</h2><p>详见: <a class="link"   href="https://wayland.app/protocols/" >https://wayland.app/protocols/<i class="fas fa-external-link-alt"></i></a><br>:::success<br><em>实际上，wayland 合成器就是一组协议的实现者。</em><br>:::<br>wayland 协议大致分为 core(核心)、stable(稳定)、staging(考虑中，可能会变为稳定)、unstable(不稳定)等，表明其当前状态，出于兼容考虑，合成器的开发应且务必实现 core 和 stable 协议，视情况需要实现部分 unstable 或其它甚至是自定义协议。</p><p><a name="AgOPi"></a></p><h3 id="Core"><a href="#Core" class="headerlink" title="Core"></a>Core</h3><table><thead><tr><th><strong>Name</strong></th><th><strong>Description</strong></th></tr></thead><tbody><tr><td>Wayland</td><td>Wayland 核心协议，这个文件定义了客户端和服务器之间通信的标准接口，包括各种对象、请求和事件。是使用其他协议的前提。</td></tr></tbody></table><p><a name="mcV5i"></a></p><h3 id="Stable"><a href="#Stable" class="headerlink" title="Stable"></a>Stable</h3><table><thead><tr><th><strong>Name</strong></th><th><strong>Description</strong></th></tr></thead><tbody><tr><td>Presentation time</td><td>Presentation-Time 协议是为了解决音频与视频同步播放时出现的问题而设计的。它允许客户端在显示器上的特定时间点提交图像，以确保图像在预期时间显示，从而实现音频和视频的同步播放。</td></tr><tr><td>Viewporter</td><td>它旨在支持客户端动态调整输出显示区域（viewport）的大小和位置。这个协议特别适用于需要对输出进行缩放、平移或裁剪的应用场景，比如 VR（虚拟现实）和多显示器环境下的窗口管理器等。</td></tr><tr><td>XDG shell</td><td>定义了一种标准的方式来管理窗口和窗口管理器之间的通信，使得窗口的创建、调整和销毁等操作能够在 Wayland 环境下进行。</td></tr><tr><td>Linux DMA-BUF</td><td>Linux 内核中用于在设备之间共享内存的机制。DMA-BUF 全称是 Direct Memory Access Buffer，它允许不同的设备（如图形处理器、显示器、摄像头等）直接访问内核中的一块共享内存区域，而无需复制数据到每个设备的私有内存中。</td></tr><tr><td>Tablet</td><td>旨在提供对触摸板和手写板等输入设备的更丰富支持。它定义了一组标准接口，使得客户端能够更好地与这些特殊输入设备进行交互，并实现更丰富的用户体验。</td></tr></tbody></table><p><a name="sOLys"></a></p><h3 id="Staging"><a href="#Staging" class="headerlink" title="Staging"></a>Staging</h3><table><thead><tr><th><strong>Name</strong></th><th><strong>Description</strong></th></tr></thead><tbody><tr><td>XDG activation</td><td>旨在提供一种标准化的方式来启动和激活桌面应用程序。它定义了一组接口，允许应用程序和桌面环境之间进行通信，以便启动应用程序、切换窗口焦点、和处理用户交互。</td></tr><tr><td>DRM lease</td><td>允许应用程序临时租用图形硬件资源，例如显示器和显卡，从而绕过窗口系统，直接控制这些资源。这在虚拟现实（VR）和增强现实（AR）等需要低延迟和高性能图形渲染的应用中尤为重要。</td></tr><tr><td>DRM synchronization object</td><td>用于在图形渲染过程中协调和同步不同操作和资源的使用。它们在确保图形管线中的各个阶段按正确顺序执行、避免资源竞争和数据不一致方面发挥着关键作用。</td></tr><tr><td>Session lock</td><td>用于保护用户会话安全，防止未授权的访问。当用户暂时离开工作站时，可以锁定会话以确保其正在运行的应用程序和数据不会被其他人查看或篡改。</td></tr><tr><td>Single-pixel buffer</td><td>专门用于优化小图形元素的传输和渲染。它的主要目标是提供一种高效的方式来处理单个像素的图形操作，这对于某些类型的图形应用程序（如光标、点状图形、单色图形元素等）非常重要。</td></tr><tr><td>Content type hint</td><td>允许客户端向合成器（compositor）提供关于表面（surface）内容类型的提示。这些提示有助于合成器优化渲染和处理不同类型的内容，提高显示性能和视觉效果。</td></tr><tr><td>Idle notify</td><td>使客户端能够接收用户空闲状态的通知，从而在用户不活动时执行特定任务。这种机制有助于优化系统资源使用，提高用户体验，特别是在屏幕保护和节能模式等应用场景中。通过合理使用该协议，开发者可以显著提升应用程序的智能化和响应能力。</td></tr><tr><td>Tearing control</td><td>旨在解决屏幕撕裂问题。屏幕撕裂是在显示器刷新过程中，显示的图像部分来自于不同的帧，导致图像出现不连续的现象。这种现象在快速运动的场景中特别明显。Tearing Control 协议通过提供机制让客户端和合成器协作，来减少或消除屏幕撕裂，提高显示效果和用户体验。</td></tr><tr><td>Xwayland shell</td><td>Xwayland 是 Wayland 的一个兼容层，使得 X11 应用程序可以在 Wayland 合成器上运行。Xwayland Shell 协议是一个特定的 Wayland 扩展协议，旨在为运行在 Xwayland 上的 X11 客户端提供更好的窗口管理和集成支持。这个协议使得 Wayland 合成器能够更好地控制和管理这些 X11 窗口，从而提高整体用户体验。</td></tr><tr><td>Fractional scale</td><td>用于支持显示器的分数缩放（Fractional Scaling）。传统的显示器缩放通常只支持整数比例，例如 1x、2x、3x 等，这可能无法完全满足高分辨率显示器上的 UI 缩放需求。Fractional Scale 协议允许用户以分数形式（如 1.25x、1.5x、1.75x 等）调整 UI 的缩放级别，以更好地适应高分辨率显示器和不同的视觉需求。</td></tr><tr><td>Cursor shape</td><td>旨在允许客户端动态地改变鼠标指针的形状。这个协议使得应用程序可以根据需要更改鼠标指针的外观，以提供更好的用户体验和交互反馈。</td></tr><tr><td>Foreign toplevel list</td><td>旨在提供一种机制，让 Wayland 合成器（compositor）可以跟踪和管理来自外部系统的顶层窗口（例如 X11 窗口）。通过这个协议，Wayland 合成器可以更好地集成和管理来自不同窗口系统的窗口，提供更统一的用户体验。</td></tr><tr><td>Security context</td><td>Security Context 协议为 Wayland 提供了一种机制，允许客户端和服务端在通信中传递安全上下文信息，增强通信的安全性。尽管该协议需要客户端和服务端的正确实现，并可能带来一定的性能开销，但它能够有效地保护通信内容的机密性和完整性，并支持基于权限的访问控制，从而提高了 Wayland 通信的安全性。</td></tr><tr><td>Transient seat</td><td>它允许在同一系统上的不同输入设备之间建立父子关系。这种关系对于多屏幕系统或者具有多个输入设备的系统非常有用，它能够确保特定的输入设备（例如触摸板或者鼠标）仅控制特定的屏幕或应用程序。</td></tr><tr><td>XDG toplevel drag</td><td>用于实现在 Wayland 上对窗口进行拖动操作。这个协议允许用户在屏幕上拖动应用程序的顶层窗口，以便更改其位置或将其拖入其他工作区等。XDG Toplevel Drag 协议的实现使得用户能够通过简单的拖动操作来管理和组织窗口，提高了桌面环境的交互性和可用性。</td></tr><tr><td>XDG dialog windows</td><td>旨在为桌面环境提供一种标准化的方式来管理对话框窗口。该协议定义了一组接口和事件，用于创建、显示和管理对话框窗口，并规定了对话框窗口的行为和外观，以提供更一致的用户体验。</td></tr><tr><td>Alpha modifier protocol</td><td>它允许客户端与服务器协商窗口表面（surface）的 Alpha 值，以实现窗口的透明度调整。</td></tr></tbody></table><p>其他协议请查阅：<a class="link"   href="https://wayland.app/protocols/" >https://wayland.app/protocols/<i class="fas fa-external-link-alt"></i></a></p><p><a name="Z39oa"></a></p><h2 id="自定义协议"><a href="#自定义协议" class="headerlink" title="自定义协议"></a>自定义协议</h2><p>使用 wayland-scanner 命令将协议文件(XML)生成胶水代码，如果是服务端，需设置新协议的实现，客户端直接调用胶水代码的接口即可。<br><a name="RzRTz"></a></p><h3 id="服务端实现"><a href="#服务端实现" class="headerlink" title="服务端实现"></a>服务端实现</h3><p>参考 <a class="link"   href="https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/0.17?ref_type=heads" >wlroots<i class="fas fa-external-link-alt"></i></a> 项目，不再赘述。<br />附一些之前手写的合成器对 <a class="link"   href="https://wayland.app/protocols/ext-session-lock-v1" >ext_session_lock_v1<i class="fas fa-external-link-alt"></i></a> 协议支持的代码：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">pragma</span> once</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;ext-session-lock-server-protocol.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_event_loop</span> *<span class="title">event_loop</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_global</span> *<span class="title">global</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_list</span> <span class="title">contexts</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_resource</span> *<span class="title">client</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_listener</span> <span class="title">display_destroy</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">    &#123;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">wl_signal</span> <span class="title">lock</span>;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">wl_signal</span> <span class="title">destroy</span>;</span></span><br><span class="line">    &#125; events;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_v1</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_resource</span> *<span class="title">resource</span>;</span></span><br><span class="line">    <span class="type">uint32_t</span> id;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_list</span> <span class="title">contexts</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">    &#123;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">wl_signal</span> <span class="title">get_lock_surface</span>;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">wl_signal</span> <span class="title">unlock_and_destroy</span>;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">wl_signal</span> <span class="title">destroy</span>;</span></span><br><span class="line">    &#125; events;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_surface_v1</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_resource</span> *<span class="title">resource</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_resource</span> *<span class="title">surface</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_resource</span> *<span class="title">output</span>;</span></span><br><span class="line">    <span class="type">uint32_t</span> id;</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span></span></span><br><span class="line"><span class="class">    &#123;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">wl_signal</span> <span class="title">ack_configure</span>;</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">wl_signal</span> <span class="title">destroy</span>;</span></span><br><span class="line">    &#125; events;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">ext_session_lock_v1_destroy</span><span class="params">(<span class="keyword">struct</span> ext_session_lock_v1 *context)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">ext_session_lock_surface_v1_destroy</span><span class="params">(<span class="keyword">struct</span> ext_session_lock_surface_v1 *context)</span>;</span><br><span class="line"><span class="keyword">struct</span> ext_session_lock_manager_v1 *<span class="title function_">ext_session_lock_manager_v1_create</span><span class="params">(<span class="keyword">struct</span> wl_display *display)</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;ext_session_lock_manager_impl.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">lock_surface_handle_destroy</span><span class="params">(<span class="keyword">struct</span> wl_client *client, <span class="keyword">struct</span> wl_resource *resource)</span></span><br><span class="line">&#123;</span><br><span class="line">    wl_resource_destroy(resource);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">lock_surface_handle_ack_configure</span><span class="params">([[maybe_unused]] <span class="keyword">struct</span> wl_client *client,</span></span><br><span class="line"><span class="params">                                              <span class="keyword">struct</span> wl_resource *resource,</span></span><br><span class="line"><span class="params">                                              <span class="type">uint32_t</span> serial)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_surface_v1</span> *<span class="title">context</span> =</span></span><br><span class="line">        static_cast&lt;ext_session_lock_surface_v1 *&gt;(wl_resource_get_user_data(resource));</span><br><span class="line">    <span class="keyword">if</span> (!context) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    wl_signal_emit_mutable(&amp;context-&gt;events.ack_configure, context);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_surface_v1_interface</span> <span class="title">lock_surface_implementation</span> =</span> &#123;</span><br><span class="line">    .destroy = lock_surface_handle_destroy,</span><br><span class="line">    .ack_configure = lock_surface_handle_ack_configure,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">ext_session_lock_surface_v1_destroy</span><span class="params">(<span class="keyword">struct</span> ext_session_lock_surface_v1 *context)</span></span><br><span class="line">&#123;</span><br><span class="line">    wl_signal_emit_mutable(&amp;context-&gt;events.destroy, context);</span><br><span class="line">    <span class="built_in">free</span>(context);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">ext_session_lock_surface_v1_destroy_func</span><span class="params">(wl_resource *resource)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_surface_v1</span> *<span class="title">context</span> =</span></span><br><span class="line">        static_cast&lt;ext_session_lock_surface_v1 *&gt;(wl_resource_get_user_data(resource));</span><br><span class="line">    <span class="keyword">if</span> (!context) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ext_session_lock_surface_v1_destroy(context);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">lock_handle_get_lock_surface</span><span class="params">(<span class="keyword">struct</span> wl_client *client,</span></span><br><span class="line"><span class="params">                                         <span class="keyword">struct</span> wl_resource *lock_resource,</span></span><br><span class="line"><span class="params">                                         <span class="type">uint32_t</span> id,</span></span><br><span class="line"><span class="params">                                         <span class="keyword">struct</span> wl_resource *surface,</span></span><br><span class="line"><span class="params">                                         <span class="keyword">struct</span> wl_resource *output)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_v1</span> *<span class="title">context</span> =</span></span><br><span class="line">        static_cast&lt;ext_session_lock_v1 *&gt;(wl_resource_get_user_data(lock_resource));</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_resource</span> *<span class="title">resource</span> =</span> wl_resource_create(client,</span><br><span class="line">                                                      &amp;ext_session_lock_surface_v1_interface,</span><br><span class="line">                                                      EXT_SESSION_LOCK_V1_DESTROY_SINCE_VERSION,</span><br><span class="line">                                                      id);</span><br><span class="line">    <span class="keyword">if</span> (resource == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        wl_resource_post_no_memory(lock_resource);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_surface_v1</span> *<span class="title">lock_surface</span> =</span></span><br><span class="line">        static_cast&lt;ext_session_lock_surface_v1 *&gt;(<span class="built_in">calloc</span>(<span class="number">1</span>, <span class="keyword">sizeof</span>(*lock_surface)));</span><br><span class="line">    <span class="keyword">if</span> (lock_surface == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        wl_resource_post_no_memory(lock_resource);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    wl_resource_set_implementation(resource,</span><br><span class="line">                                   &amp;lock_surface_implementation,</span><br><span class="line">                                   lock_surface,</span><br><span class="line">                                   ext_session_lock_surface_v1_destroy_func);</span><br><span class="line"></span><br><span class="line">    wl_resource_set_user_data(resource, lock_surface);</span><br><span class="line"></span><br><span class="line">    wl_signal_init(&amp;lock_surface-&gt;events.ack_configure);</span><br><span class="line">    wl_signal_init(&amp;lock_surface-&gt;events.destroy);</span><br><span class="line"></span><br><span class="line">    lock_surface-&gt;resource = resource;</span><br><span class="line">    lock_surface-&gt;surface = surface;</span><br><span class="line">    lock_surface-&gt;output = output;</span><br><span class="line">    lock_surface-&gt;id = id;</span><br><span class="line"></span><br><span class="line">    wl_signal_emit_mutable(&amp;context-&gt;events.get_lock_surface, lock_surface);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">lock_handle_destroy</span><span class="params">([[maybe_unused]] <span class="keyword">struct</span> wl_client *client,</span></span><br><span class="line"><span class="params">                                <span class="keyword">struct</span> wl_resource *resource)</span></span><br><span class="line">&#123;</span><br><span class="line">    wl_resource_destroy(resource);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">lock_handle_unlock_and_destroy</span><span class="params">(<span class="keyword">struct</span> wl_client *client, <span class="keyword">struct</span> wl_resource *resource)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_v1</span> *<span class="title">context</span> =</span></span><br><span class="line">        static_cast&lt;ext_session_lock_v1 *&gt;(wl_resource_get_user_data(resource));</span><br><span class="line">    wl_signal_emit_mutable(&amp;context-&gt;events.unlock_and_destroy, context);</span><br><span class="line">    lock_handle_destroy(client, resource);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_v1_interface</span> <span class="title">lock_implementation</span> =</span> &#123;</span><br><span class="line">    .destroy = lock_handle_destroy,</span><br><span class="line">    .get_lock_surface = lock_handle_get_lock_surface,</span><br><span class="line">    .unlock_and_destroy = lock_handle_unlock_and_destroy,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">manager_handle_destroy</span><span class="params">(<span class="keyword">struct</span> wl_client *client, <span class="keyword">struct</span> wl_resource *resource)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1</span> *<span class="title">context</span> =</span></span><br><span class="line">        static_cast&lt;ext_session_lock_manager_v1 *&gt;(wl_resource_get_user_data(resource));</span><br><span class="line">    <span class="keyword">if</span> (!context) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    wl_signal_emit_mutable(&amp;context-&gt;events.destroy, context);</span><br><span class="line">    wl_list_remove(wl_resource_get_link(resource));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">ext_session_lock_v1_destroy</span><span class="params">(<span class="keyword">struct</span> ext_session_lock_v1 *context)</span></span><br><span class="line">&#123;</span><br><span class="line">    wl_signal_emit_mutable(&amp;context-&gt;events.destroy, context);</span><br><span class="line">    <span class="built_in">free</span>(context);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">ext_session_lock_v1_destroy_func</span><span class="params">(wl_resource *resource)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_v1</span> *<span class="title">context</span> =</span></span><br><span class="line">        static_cast&lt;ext_session_lock_v1 *&gt;(wl_resource_get_user_data(resource));</span><br><span class="line">    <span class="keyword">if</span> (!context) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ext_session_lock_v1_destroy(context);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">manager_handle_lock</span><span class="params">(<span class="keyword">struct</span> wl_client *client,</span></span><br><span class="line"><span class="params">                                <span class="keyword">struct</span> wl_resource *manager_resource,</span></span><br><span class="line"><span class="params">                                <span class="type">uint32_t</span> id)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1</span> *<span class="title">manager</span> =</span></span><br><span class="line">        static_cast&lt;ext_session_lock_manager_v1 *&gt;(wl_resource_get_user_data(manager_resource));</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_resource</span> *<span class="title">resource</span> =</span> wl_resource_create(client,</span><br><span class="line">                                                      &amp;ext_session_lock_v1_interface,</span><br><span class="line">                                                      EXT_SESSION_LOCK_V1_DESTROY_SINCE_VERSION,</span><br><span class="line">                                                      id);</span><br><span class="line">    <span class="keyword">if</span> (resource == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        wl_resource_post_no_memory(manager_resource);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_v1</span> *<span class="title">context</span> =</span></span><br><span class="line">        static_cast&lt;ext_session_lock_v1 *&gt;(<span class="built_in">calloc</span>(<span class="number">1</span>, <span class="keyword">sizeof</span>(*context)));</span><br><span class="line">    <span class="keyword">if</span> (context == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        wl_resource_post_no_memory(manager_resource);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    wl_resource_set_implementation(resource,</span><br><span class="line">                                   &amp;lock_implementation,</span><br><span class="line">                                   context,</span><br><span class="line">                                   ext_session_lock_v1_destroy_func);</span><br><span class="line">    wl_resource_set_user_data(resource, context);</span><br><span class="line"></span><br><span class="line">    wl_signal_init(&amp;context-&gt;events.get_lock_surface);</span><br><span class="line">    wl_signal_init(&amp;context-&gt;events.unlock_and_destroy);</span><br><span class="line">    wl_signal_init(&amp;context-&gt;events.destroy);</span><br><span class="line"></span><br><span class="line">    context-&gt;resource = resource;</span><br><span class="line">    context-&gt;id = id;</span><br><span class="line">    wl_list_init(&amp;context-&gt;contexts);</span><br><span class="line">    wl_list_insert(&amp;manager-&gt;contexts, wl_resource_get_link(resource));</span><br><span class="line"></span><br><span class="line">    wl_signal_emit_mutable(&amp;manager-&gt;events.lock, context);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1_interface</span> <span class="title">lock_manager_implementation</span> =</span> &#123;</span><br><span class="line">    .destroy = manager_handle_destroy,</span><br><span class="line">    .lock = manager_handle_lock,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">bind_ext_session_lock_manager_v1</span><span class="params">(<span class="keyword">struct</span> wl_client *client,</span></span><br><span class="line"><span class="params">                                             <span class="type">void</span> *data,</span></span><br><span class="line"><span class="params">                                             <span class="type">uint32_t</span> version,</span></span><br><span class="line"><span class="params">                                             <span class="type">uint32_t</span> id)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1</span> *<span class="title">manager</span> =</span></span><br><span class="line">        static_cast&lt;<span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1</span> *&gt;</span>(data);</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_resource</span> *<span class="title">resource</span> =</span></span><br><span class="line">        wl_resource_create(client, &amp;ext_session_lock_manager_v1_interface, version, id);</span><br><span class="line">    <span class="keyword">if</span> (!resource) &#123;</span><br><span class="line">        wl_client_post_no_memory(client);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    wl_resource_set_implementation(resource, &amp;lock_manager_implementation, manager, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line">    wl_list_insert(&amp;manager-&gt;contexts, wl_resource_get_link(resource));</span><br><span class="line"></span><br><span class="line">    manager-&gt;client = resource;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">handle_display_destroy</span><span class="params">(<span class="keyword">struct</span> wl_listener *listener, [[maybe_unused]] <span class="type">void</span> *data)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1</span> *<span class="title">manager</span> =</span></span><br><span class="line">        wl_container_of(listener, manager, display_destroy);</span><br><span class="line">    wl_signal_emit_mutable(&amp;manager-&gt;events.destroy, manager);</span><br><span class="line">    wl_list_remove(&amp;manager-&gt;display_destroy.link);</span><br><span class="line">    wl_global_destroy(manager-&gt;global);</span><br><span class="line">    <span class="built_in">free</span>(manager);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SESSION_LOCK_MANAGEMENT_V1_VERSION 1</span></span><br><span class="line"></span><br><span class="line">ext_session_lock_manager_v1 *<span class="title function_">ext_session_lock_manager_v1_create</span><span class="params">(wl_display *display)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1</span> *<span class="title">manager</span> =</span></span><br><span class="line">        static_cast&lt;<span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1</span> *&gt;</span>(<span class="built_in">calloc</span>(<span class="number">1</span>, <span class="keyword">sizeof</span>(*manager)));</span><br><span class="line">    <span class="keyword">if</span> (!manager) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    manager-&gt;event_loop = wl_display_get_event_loop(display);</span><br><span class="line">    manager-&gt;global = wl_global_create(display,</span><br><span class="line">                                       &amp;ext_session_lock_manager_v1_interface,</span><br><span class="line">                                       SESSION_LOCK_MANAGEMENT_V1_VERSION,</span><br><span class="line">                                       manager,</span><br><span class="line">                                       bind_ext_session_lock_manager_v1);</span><br><span class="line">    <span class="keyword">if</span> (!manager-&gt;global) &#123;</span><br><span class="line">        <span class="built_in">free</span>(manager);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    wl_signal_init(&amp;manager-&gt;events.lock);</span><br><span class="line">    wl_signal_init(&amp;manager-&gt;events.destroy);</span><br><span class="line">    wl_list_init(&amp;manager-&gt;contexts);</span><br><span class="line"></span><br><span class="line">    manager-&gt;display_destroy.notify = handle_display_destroy;</span><br><span class="line">    wl_display_add_destroy_listener(display, &amp;manager-&gt;display_destroy);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> manager;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>协议的书写上面大同小异，其他协议均按照类似的代码方式进行了支持，如感兴趣，强烈建议参阅  <a class="link"   href="https://gitlab.freedesktop.org/wlroots/wlroots/-/tree/0.17?ref_type=heads" >wlroots<i class="fas fa-external-link-alt"></i></a> 、weston、sway、mutter、kwin等项目的源码。<br><a name="Q6MUv"></a></p><h3 id="客户端调用"><a href="#客户端调用" class="headerlink" title="客户端调用"></a>客户端调用</h3><p>以 <a class="link"   href="https://wayland.app/protocols/ext-session-lock-v1" >ext_session_lock_v1<i class="fas fa-external-link-alt"></i></a> 协议为例，客户端(例如锁屏程序)的实现如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 生成协议头文件</span></span><br><span class="line">wayland-scanner client-header ext-session-lock-v1.xml client.h</span><br><span class="line"><span class="comment"># 生成协议源文件(通过命令生成的代码，全是胶水代码)</span></span><br><span class="line">wayland-scanner code ext-session-lock-v1.xml client.c</span><br></pre></td></tr></table></figure><p>main文件内容如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;wayland-client.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;client.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">example_data</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_display</span> *<span class="title">display</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_registry</span> *<span class="title">registry</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_compositor</span> *<span class="title">compositor</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_surface</span> *<span class="title">surface</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wl_output</span> *<span class="title">output</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_manager_v1</span> *<span class="title">lock_manager</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_v1</span> *<span class="title">lock</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_surface_v1</span> *<span class="title">lock_surface</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">on_locked</span><span class="params">(<span class="type">void</span> *data, <span class="keyword">struct</span> ext_session_lock_v1 *lock)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Session successfully locked.\n&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">on_configure</span><span class="params">(<span class="type">void</span> *data,</span></span><br><span class="line"><span class="params">  <span class="keyword">struct</span> ext_session_lock_surface_v1 *ext_session_lock_surface_v1,</span></span><br><span class="line"><span class="params">  <span class="type">uint32_t</span> serial,</span></span><br><span class="line"><span class="params">  <span class="type">uint32_t</span> width,</span></span><br><span class="line"><span class="params">  <span class="type">uint32_t</span> height)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Received configure event, send ack_configure back\n&quot;</span>);</span><br><span class="line">    ext_session_lock_surface_v1_ack_configure(ext_session_lock_surface_v1, serial);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_v1_listener</span> <span class="title">lock_listener</span> =</span> &#123;</span><br><span class="line">    on_locked</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">ext_session_lock_surface_v1_listener</span> <span class="title">lock_surface_listener</span> =</span> &#123;</span><br><span class="line">    on_configure</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">registry_handle_global</span><span class="params">(<span class="type">void</span> *data, <span class="keyword">struct</span> wl_registry *registry, <span class="type">uint32_t</span> id,</span></span><br><span class="line"><span class="params">                                   <span class="type">const</span> <span class="type">char</span> *interface, <span class="type">uint32_t</span> version)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">example_data</span> *<span class="title">example_data</span> =</span> data;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">strcmp</span>(interface, <span class="string">&quot;ext_session_lock_manager_v1&quot;</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">        example_data-&gt;lock_manager = wl_registry_bind(registry, id, &amp;ext_session_lock_manager_v1_interface, <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">strcmp</span>(interface, <span class="string">&quot;wl_compositor&quot;</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">        example_data-&gt;compositor = wl_registry_bind(registry, id, &amp;wl_compositor_interface, <span class="number">1</span>);</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Got wl_compositor\n&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">strcmp</span>(interface, <span class="string">&quot;wl_output&quot;</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">        example_data-&gt;output = wl_registry_bind(registry, id, &amp;wl_output_interface, version);</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Got wl_output\n&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">wl_registry_listener</span> <span class="title">registry_listener</span> =</span> &#123;</span><br><span class="line">    registry_handle_global,</span><br><span class="line">    <span class="literal">NULL</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">example_data</span> <span class="title">data</span>;</span></span><br><span class="line"></span><br><span class="line">    data.lock = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    data.display = wl_display_connect(<span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">if</span> (!data.display) &#123;</span><br><span class="line">        <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Failed to connect to Wayland display.\n&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Get registry</span></span><br><span class="line">    data.registry = wl_display_get_registry(data.display);</span><br><span class="line">    wl_registry_add_listener(data.registry, &amp;registry_listener, &amp;data);</span><br><span class="line">    wl_display_roundtrip(data.display);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!data.lock_manager || !data.compositor || !data.output) &#123;</span><br><span class="line">        <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Required interfaces not available\n&quot;</span>);</span><br><span class="line">        wl_registry_destroy(data.registry);</span><br><span class="line">        wl_display_disconnect(data.display);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Request lock</span></span><br><span class="line">    data.lock = ext_session_lock_manager_v1_lock(data.lock_manager);</span><br><span class="line">    ext_session_lock_v1_add_listener(data.lock, &amp;lock_listener, &amp;data);</span><br><span class="line">    wl_display_roundtrip(data.display);</span><br><span class="line">    <span class="keyword">if</span> (!data.lock) &#123;</span><br><span class="line">        <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Required interfaces: lock not available\n&quot;</span>);</span><br><span class="line">        wl_registry_destroy(data.registry);</span><br><span class="line">        wl_display_disconnect(data.display);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Request destroy</span></span><br><span class="line">    <span class="comment">// ext_session_lock_manager_v1_destroy(data.lock);</span></span><br><span class="line">    <span class="comment">// wl_display_roundtrip(data.display);</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">//     // Wait for events</span></span><br><span class="line">    <span class="comment">// while (wl_display_dispatch(data.display) != -1) &#123;</span></span><br><span class="line">    <span class="comment">//     // Handle events</span></span><br><span class="line">    <span class="comment">// &#125;</span></span><br><span class="line">    <span class="comment">// return 0;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Create surface</span></span><br><span class="line">    data.surface = wl_compositor_create_surface(data.compositor);</span><br><span class="line">    <span class="keyword">if</span> (!data.surface) &#123;</span><br><span class="line">        <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Failed to create wl_surface\n&quot;</span>);</span><br><span class="line">        wl_registry_destroy(data.registry);</span><br><span class="line">        wl_display_disconnect(data.display);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Request get_lock_surface</span></span><br><span class="line">    data.lock_surface = ext_session_lock_v1_get_lock_surface(data.lock, data.surface, data.output);</span><br><span class="line">    ext_session_lock_surface_v1_add_listener(data.lock_surface, &amp;lock_surface_listener, &amp;data);</span><br><span class="line">    wl_display_roundtrip(data.display);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Request unlock_and_destroy</span></span><br><span class="line">    <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Start Unlock\n&quot;</span>);</span><br><span class="line">    ext_session_lock_v1_unlock_and_destroy(data.lock);</span><br><span class="line">    wl_display_roundtrip(data.display);</span><br><span class="line">    <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Unlock success\n&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Wait for events</span></span><br><span class="line">    <span class="keyword">while</span> (wl_display_dispatch(data.display) != <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="comment">// Handle events</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Cleanup</span></span><br><span class="line">    ext_session_lock_manager_v1_destroy(data.lock_manager);</span><br><span class="line">    wl_registry_destroy(data.registry);</span><br><span class="line">    wl_display_disconnect(data.display);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过 <code>gcc -o client client.c main.c -lwayland-client</code>编译生成 client 二进制。<br />运行：注意在运行此二进制之前，需要指定其 WAYLAND_DISPLAY环境变量，这是由 Wayland 合成器决定的。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> WAYLAND_DISPLAY=wayland-0</span><br><span class="line">./client</span><br></pre></td></tr></table></figure><p>至此，我们已经完成了一个简单的客户端调用 Wayland 协议的 demo。在实际开发中，这些调用通常由各种开发库进行了封装。例如，libqtwayland 就是由 Qt 对部分 Wayland 协议的调用进行了封装，从而使得开发者在开发桌面应用时无需直接处理协议调用，只需使用 Qt 中已有的类和接口即可。</p><blockquote><p>&#x2F;&#x2F; By A Way<br>A week 的合成器之旅达到 Ending,以后有时间再继续丰富.</p></blockquote><p><a name="Xir9t"></a></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a class="link"   href="https://wayland.freedesktop.org/" >https://wayland.freedesktop.org/<i class="fas fa-external-link-alt"></i></a><br /><a class="link"   href="https://wayland.arktoria.org/index.html" >The Wayland Protocol 中文版<i class="fas fa-external-link-alt"></i></a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;因工作需要加之个人比较感兴趣的原因，在实现 Wayland 合成器相关协议之途中，随笔记录记录一些相关的基础知识。&lt;/p&gt;</summary>
    
    
    
    
    <category term="wayland" scheme="https://ssk-wh.github.io/tags/wayland/"/>
    
  </entry>
  
  <entry>
    <title>Hexo博客本地部署</title>
    <link href="https://ssk-wh.github.io/2024/05b59ef6f5.html"/>
    <id>https://ssk-wh.github.io/2024/05b59ef6f5.html</id>
    <published>2024-05-22T17:40:15.000Z</published>
    <updated>2024-05-23T01:33:44.000Z</updated>
    
    <content type="html"><![CDATA[<p>在本地快速预览博客部署效果，可按照此步骤操作。否则直接向源文件仓库中提交 md 文档即可。</p><span id="more"></span><h2 id="安装软件包"><a href="#安装软件包" class="headerlink" title="安装软件包"></a>安装软件包</h2><p><code>sudo apt install npm</code><br>uos系统中的默认版本有点问题，无法通过npm安装其他包(如果您的版本没问题，直接跳到下一下节)，可按照 <a class="link"   href="https://github.com/nodesource/distributions" >nodesource<i class="fas fa-external-link-alt"></i></a> 的 <a class="link"   href="https://github.com/nodesource/distributions/blob/master/README.md" >README<i class="fas fa-external-link-alt"></i></a> 更新 nodejs.<br>或按照以下步骤(适合 Debian 系的 Linux 发行版):<br><code>sudo apt-get install -y curl</code><br><code>curl -fsSL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh</code><br><code>sudo -E bash nodesource_setup.sh</code><br><code>sudo apt-get install -y nodejs</code></p><p>安装成功后，通过 <code>node -v</code> 查看安装后的版本</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; node -v</span><br><span class="line">v20.13.1</span><br></pre></td></tr></table></figure><h2 id="预览"><a href="#预览" class="headerlink" title="预览"></a>预览</h2><p><code>cd your_hexo_dir</code><br><code>npm install hexo</code><br>阿拉的电脑已经安装过了，所以安装结果如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&gt; npm install hexo</span><br><span class="line">npm notice Beginning October 4, 2021, all connections to the npm registry - including <span class="keyword">for</span> package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog <span class="keyword">for</span> more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/</span><br><span class="line">npm notice Beginning October 4, 2021, all connections to the npm registry - including <span class="keyword">for</span> package installation - must use TLS 1.2 or higher. You are currently using plaintext http to connect. Please visit the GitHub blog <span class="keyword">for</span> more information: https://github.blog/2021-08-23-npm-registry-deprecating-tls-1-0-tls-1-1/</span><br><span class="line"></span><br><span class="line">up to <span class="built_in">date</span> <span class="keyword">in</span> 2s</span><br><span class="line"></span><br><span class="line">22 packages are looking <span class="keyword">for</span> funding</span><br><span class="line">  run `npm fund` <span class="keyword">for</span> details</span><br></pre></td></tr></table></figure><p>此时正常情况下可以使用 <code>npm hexo server</code> 或 <code>npm run server</code> 查看预览效果</p><p>如果碰到 npm 不识别 hexo 命令的情况，请自行配置 npm 的环境变量，或者找到当前安装的 hexo 命令所在，一般在当前安装目录的 <code>node_modules/hexo/bin/</code> 下，之后执行即可，例如 <code>./node_modules/hexo/bin/hexo s</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">uos@uos-PC ~ ./node_modules/hexo/bin/hexo s</span><br><span class="line">INFO  Validating config</span><br><span class="line">INFO  </span><br><span class="line">------------------------------------------</span><br><span class="line">     __  ___  _______  _______ .______</span><br><span class="line">    |  |/  / |   ____||   ____||   _  \</span><br><span class="line">    |  <span class="string">&#x27;  /  |  |__   |  |__   |  |_)  |</span></span><br><span class="line"><span class="string">    |    &lt;   |   __|  |   __|  |   ___/</span></span><br><span class="line"><span class="string">    |  .  \  |  |____ |  |____ |  |</span></span><br><span class="line"><span class="string">    |__|\__\ |_______||_______|| _|</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">------------------------------------------</span></span><br><span class="line"><span class="string">Keep version 4.1.2</span></span><br><span class="line"><span class="string">Documentation: https://keep-docs.xpoet.cn</span></span><br><span class="line"><span class="string">------------------------------------------</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">INFO  Start processing</span></span><br><span class="line"><span class="string">INFO  url_submission: Start generating url list...</span></span><br><span class="line"><span class="string">INFO  url_submission: Page urls will generate into file named submit_url.txt</span></span><br><span class="line"><span class="string">INFO  Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop.</span></span><br></pre></td></tr></table></figure><p>之后在浏览器中访问 <code>[http://localhost:4000/]</code> 就可以看到预览效果了.</p><p>后期如果只是为了提交新文章，只需要 clone 对应的项目到本地，在 source&#x2F;_posts 目录下新建文档即可。提交后由经由 github actions 自动部署并发布。</p><h2 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h2><p>npm 执行时提示不存在 package.json 文件，执行 <code>npm init</code> 进行初始化。再执行你要执行的操作，如 <code>npm install hexo</code><br>详情可参考<a class="link"   href="https://blog.csdn.net/weixin_40161974/article/details/99441501" >https://blog.csdn.net/weixin_40161974&#x2F;article&#x2F;details&#x2F;99441501<i class="fas fa-external-link-alt"></i></a></p><h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><p><a class="link"   href="https://hexo.io/zh-cn/docs/" >hexo官方说明<i class="fas fa-external-link-alt"></i></a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在本地快速预览博客部署效果，可按照此步骤操作。否则直接向源文件仓库中提交 md 文档即可。&lt;/p&gt;</summary>
    
    
    
    <category term="Writing" scheme="https://ssk-wh.github.io/categories/Writing/"/>
    
    
    <category term="hexo" scheme="https://ssk-wh.github.io/tags/hexo/"/>
    
    <category term="npm" scheme="https://ssk-wh.github.io/tags/npm/"/>
    
    <category term="nodejs" scheme="https://ssk-wh.github.io/tags/nodejs/"/>
    
  </entry>
  
  <entry>
    <title>僵尸进程</title>
    <link href="https://ssk-wh.github.io/2024/022a2a7853.html"/>
    <id>https://ssk-wh.github.io/2024/022a2a7853.html</id>
    <published>2024-02-22T22:30:31.000Z</published>
    <updated>2024-07-02T05:24:23.000Z</updated>
    
    <content type="html"><![CDATA[<p>僵尸进程是指一个子进程已经终止但其父进程尚未读取其终止状态的进程，导致其进程表项仍然保留在系统中。</p><span id="more"></span><h1 id="产生"><a href="#产生" class="headerlink" title="产生"></a>产生</h1><p>如果子进程先退出了，父进程还未结束并且没有调用 wait 或者 waitpid 函数获取子进程的状态信息，则子进程残留的状态信息（ task_struct 结构和少量资源信息）会变成僵尸进程。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// gcc -o zombie zombie.c</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">pid_t</span> pid;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建子进程</span></span><br><span class="line">    pid = fork();</span><br><span class="line">    <span class="keyword">if</span> (pid &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// fork失败</span></span><br><span class="line">        perror(<span class="string">&quot;fork&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 子进程</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Child process with PID: %d\n&quot;</span>, getpid());</span><br><span class="line">        <span class="comment">// 子进程立即退出</span></span><br><span class="line">        <span class="built_in">exit</span>(EXIT_SUCCESS);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 父进程</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Parent process with PID: %d, Child PID: %d\n&quot;</span>, getpid(), pid);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 父进程继续执行其他任务，例如睡眠一段时间</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Parent process continue do something\n&quot;</span>);</span><br><span class="line">    sleep(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在终端中输入 <code>gcc -o zombie zombie.c</code> 进行编译，之后运行 <code>./zombie</code> 进程。此时通过 <code>ps -ef | grep zombie</code> 可以看到 fork 后的子进程变为僵尸进程。</p><p><img                         lazyload                       alt="image"                       data-src="/2024/022a2a7853/1.png"                                        ></p><h1 id="预防"><a href="#预防" class="headerlink" title="预防"></a>预防</h1><p>结合其产生的原因，在父进程中使用 wait 回收子进程残留的进程资源。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// gcc -o zombie zombie.c</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">pid_t</span> pid;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建子进程</span></span><br><span class="line">    pid = fork();</span><br><span class="line">    <span class="keyword">if</span> (pid &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// fork失败</span></span><br><span class="line">        perror(<span class="string">&quot;fork&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 子进程</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Child process with PID: %d\n&quot;</span>, getpid());</span><br><span class="line">        <span class="comment">// 子进程立即退出</span></span><br><span class="line">        <span class="built_in">exit</span>(EXIT_SUCCESS);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 父进程</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Parent process with PID: %d, Child PID: %d\n&quot;</span>, getpid(), pid);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 父进程等待子进程结束</span></span><br><span class="line">        <span class="type">int</span> status;</span><br><span class="line">        <span class="type">pid_t</span> wpid = wait(&amp;status);</span><br><span class="line">        <span class="keyword">if</span> (wpid == <span class="number">-1</span>) &#123;</span><br><span class="line">            perror(<span class="string">&quot;wait&quot;</span>);</span><br><span class="line">            <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 检查子进程的退出状态</span></span><br><span class="line">        <span class="keyword">if</span> (WIFEXITED(status)) &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;Child process exited with status %d\n&quot;</span>, WEXITSTATUS(status));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;Child process terminated by signal %d\n&quot;</span>, WTERMSIG(status));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 父进程继续执行其他任务，例如睡眠一段时间</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Parent process continue do something\n&quot;</span>);</span><br><span class="line">    sleep(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img                         lazyload                       alt="image"                       data-src="/2024/022a2a7853/2.png"                                        ></p><p>当一个子进程结束时，内核会向其父进程发送SIGCHLD信号。父进程可以通过捕获这个信号并在信号处理函数中调用wait()或waitpid()来回收子进程的资源，从而避免僵尸进程的产生。<br>这种方式也优雅一些。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// gcc -o zombie zombie.c</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/wait.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 信号处理函数</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">sigchld_handler</span><span class="params">(<span class="type">int</span> signum)</span> &#123;</span><br><span class="line">    <span class="comment">// 这里可以添加一些日志记录或其他处理</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;SIGCHLD signal received, cleaning up child processes.\n&quot;</span>);</span><br><span class="line">    <span class="comment">// 等待所有子进程结束</span></span><br><span class="line">    <span class="keyword">while</span> (waitpid(<span class="number">-1</span>, <span class="literal">NULL</span>, WNOHANG) &gt; <span class="number">0</span>); <span class="comment">// WNOHANG选项使得waitpid不会阻塞</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 设置信号处理函数</span></span><br><span class="line">    signal(SIGCHLD, sigchld_handler);</span><br><span class="line"></span><br><span class="line">    <span class="type">pid_t</span> pid;</span><br><span class="line">    <span class="comment">// 创建子进程</span></span><br><span class="line">    pid = fork();</span><br><span class="line">    <span class="keyword">if</span> (pid &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        perror(<span class="string">&quot;fork&quot;</span>);</span><br><span class="line">        <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 子进程</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Child process with PID: %d\n&quot;</span>, getpid());</span><br><span class="line">        sleep(<span class="number">5</span>); <span class="comment">// 模拟子进程执行操作</span></span><br><span class="line">        <span class="built_in">exit</span>(EXIT_SUCCESS);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 父进程</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Parent process with PID: %d, Child PID: %d\n&quot;</span>, getpid(), pid);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 父进程继续执行其他任务</span></span><br><span class="line">        sleep(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 父进程结束前，子进程可能已经结束，信号处理函数会处理SIGCHLD信号</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;僵尸进程是指一个子进程已经终止但其父进程尚未读取其终止状态的进程，导致其进程表项仍然保留在系统中。&lt;/p&gt;</summary>
    
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/categories/Linux/"/>
    
    
    <category term="C" scheme="https://ssk-wh.github.io/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>性能分析-火焰图</title>
    <link href="https://ssk-wh.github.io/2024/029209f875.html"/>
    <id>https://ssk-wh.github.io/2024/029209f875.html</id>
    <published>2024-02-19T22:13:46.000Z</published>
    <updated>2024-05-22T10:12:22.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>火焰图是一种可视化工具，用于直观地展示程序的性能瓶颈和函数调用层次。它通过图形化展示函数调用栈，使得用户可以轻松地识别性能瓶颈和分析程序的性能特征。<br>在Linux系统中，使用火焰图通常需要使用工具如 FlameGraph 和 <strong>perf</strong> 来生成和分析火焰图。</p><blockquote><p>使用比较简单，按照下面的步骤固定操作即可</p></blockquote><h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h1><p>安装 perf 和 FlameGraph 工具</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get install linux-base</span><br><span class="line"><span class="comment"># 因为我的内核版本是4.19，所以安装的是linux-perf-4.19</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get install linux-perf-4.19</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 FlameGraph，用于将 perf 采集的数据生成火焰图</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/brendangregg/FlameGraph.git</span><br></pre></td></tr></table></figure><h1 id="采集"><a href="#采集" class="headerlink" title="采集"></a>采集</h1><p><code>perf record -F 99 -g -p &lt;pid_or_command&gt;</code><br><code>perf record -F 99 -g &lt;command&gt;</code></p><p><code>perf script | ./FlameGraph/stackcollapse-perf.pl &gt; out.folded</code></p><p><code>./FlameGraph/flamegraph.pl out.folded &gt; output.svg</code></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;介绍&quot;&gt;&lt;a href=&quot;#介绍&quot; class=&quot;headerlink&quot; title=&quot;介绍&quot;&gt;&lt;/a&gt;介绍&lt;/h1&gt;&lt;p&gt;火焰图是一种可视化工具，用于直观地展示程序的性能瓶颈和函数调用层次。它通过图形化展示函数调用栈，使得用户可以轻松地识别性能瓶颈和分析程序的性</summary>
      
    
    
    
    <category term="Linux" scheme="https://ssk-wh.github.io/categories/Linux/"/>
    
    
    <category term="性能分析" scheme="https://ssk-wh.github.io/tags/%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90/"/>
    
    <category term="火焰图" scheme="https://ssk-wh.github.io/tags/%E7%81%AB%E7%84%B0%E5%9B%BE/"/>
    
  </entry>
  
</feed>
