<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; 内核</title>
	<atom:link href="http://tonybai.com/tag/%e5%86%85%e6%a0%b8/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Mon, 08 Jun 2026 23:32:23 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Go 2025 密码学年度报告：后量子时代的防御与 FIPS 的“纯 Go”革命</title>
		<link>https://tonybai.com/2025/11/22/the-2025-go-cryptography-state-of-the-union/</link>
		<comments>https://tonybai.com/2025/11/22/the-2025-go-cryptography-state-of-the-union/#comments</comments>
		<pubDate>Sat, 22 Nov 2025 09:47:05 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AccumulatedTestVectors]]></category>
		<category><![CDATA[AES-CTR]]></category>
		<category><![CDATA[AI原生开发工作流实战]]></category>
		<category><![CDATA[AmpereAltra]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[arm64]]></category>
		<category><![CDATA[AssemblyMutationTesting]]></category>
		<category><![CDATA[BoringSSL]]></category>
		<category><![CDATA[BorisNagaev]]></category>
		<category><![CDATA[Cgo]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[Constanttime]]></category>
		<category><![CDATA[crypto/internal/fips140]]></category>
		<category><![CDATA[crypto/mlkem]]></category>
		<category><![CDATA[crypto/rand.Read]]></category>
		<category><![CDATA[crypto/tls]]></category>
		<category><![CDATA[CryptographyStateoftheUnion]]></category>
		<category><![CDATA[EdgeRouter]]></category>
		<category><![CDATA[fallback]]></category>
		<category><![CDATA[FilippoValsorda]]></category>
		<category><![CDATA[FIPS]]></category>
		<category><![CDATA[FIPS合规]]></category>
		<category><![CDATA[FIPS模块]]></category>
		<category><![CDATA[FIPS模式]]></category>
		<category><![CDATA[getrandom]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.24]]></category>
		<category><![CDATA[GoBoringCrypto]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GopherCon2025]]></category>
		<category><![CDATA[GopherConUS]]></category>
		<category><![CDATA[Go团队]]></category>
		<category><![CDATA[Go语言进阶课]]></category>
		<category><![CDATA[kyber]]></category>
		<category><![CDATA[math/big]]></category>
		<category><![CDATA[MIPS]]></category>
		<category><![CDATA[ML-KEM]]></category>
		<category><![CDATA[ML-KEM768]]></category>
		<category><![CDATA[NeverFails]]></category>
		<category><![CDATA[NIST]]></category>
		<category><![CDATA[Oof]]></category>
		<category><![CDATA[Ouch]]></category>
		<category><![CDATA[RecordNowDecryptLater]]></category>
		<category><![CDATA[RollingHash]]></category>
		<category><![CDATA[RSA]]></category>
		<category><![CDATA[RSA密钥]]></category>
		<category><![CDATA[Seccomp]]></category>
		<category><![CDATA[TrailofBits]]></category>
		<category><![CDATA[vDSO]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[X25519]]></category>
		<category><![CDATA[交叉编译]]></category>
		<category><![CDATA[侧信道攻击]]></category>
		<category><![CDATA[内存安全]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[协议设计缺陷]]></category>
		<category><![CDATA[双保险]]></category>
		<category><![CDATA[合规认证]]></category>
		<category><![CDATA[后量子保护]]></category>
		<category><![CDATA[后量子时代]]></category>
		<category><![CDATA[后量子签名]]></category>
		<category><![CDATA[回退逻辑]]></category>
		<category><![CDATA[复杂性]]></category>
		<category><![CDATA[安全性]]></category>
		<category><![CDATA[审计]]></category>
		<category><![CDATA[密码学]]></category>
		<category><![CDATA[密码学库]]></category>
		<category><![CDATA[工具箱]]></category>
		<category><![CDATA[工程哲学]]></category>
		<category><![CDATA[常数时间]]></category>
		<category><![CDATA[年度报告]]></category>
		<category><![CDATA[抗量子计算]]></category>
		<category><![CDATA[持续测试]]></category>
		<category><![CDATA[攻击模式]]></category>
		<category><![CDATA[敏感信息]]></category>
		<category><![CDATA[数字签名]]></category>
		<category><![CDATA[架构演进]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[格密码学]]></category>
		<category><![CDATA[椭圆曲线算法]]></category>
		<category><![CDATA[汇编代码]]></category>
		<category><![CDATA[汇编变异测试]]></category>
		<category><![CDATA[测试覆盖]]></category>
		<category><![CDATA[混合加密]]></category>
		<category><![CDATA[滚动哈希]]></category>
		<category><![CDATA[漏洞]]></category>
		<category><![CDATA[生态系统]]></category>
		<category><![CDATA[用户]]></category>
		<category><![CDATA[窃听]]></category>
		<category><![CDATA[简单]]></category>
		<category><![CDATA[累积测试向量]]></category>
		<category><![CDATA[解密]]></category>
		<category><![CDATA[计时攻击]]></category>
		<category><![CDATA[跨平台]]></category>
		<category><![CDATA[重构]]></category>
		<category><![CDATA[量子计算]]></category>
		<category><![CDATA[防御]]></category>
		<category><![CDATA[防线]]></category>
		<category><![CDATA[随机数]]></category>
		<category><![CDATA[零Go专属漏洞]]></category>
		<category><![CDATA[零漏洞审计记录]]></category>
		<category><![CDATA[零高危漏洞]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5421</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/11/22/the-2025-go-cryptography-state-of-the-union 大家好，我是Tony Bai。 2025 年 8 月，Go 官方密码学库核心维护者、Geomys 创始人 Filippo Valsorda 在 GopherCon US 上发表了备受瞩目的年度主题演讲 —— “The Go Cryptography State of the Union“。 这是一次年度技术汇报，也是一份关于 Go 语言如何应对未来十年安全挑战的战略蓝图。从抗量子计算的未雨绸缪，到 FIPS 合规的架构性重构，再到令人惊叹的“零漏洞”审计记录，Go 团队用行动证明了：最好的安全性，是让开发者无需感知、却时刻被守护的安全性。 在本文中，我们将深入解读这次演讲的核心内容，从后量子加密的技术细节到纯 Go FIPS 的实现突破，带你一窥 Go 语言构建未来安全防线的全景图。 后量子时代的第一道防线：ML-KEM 如果说量子计算是悬在现代密码学头顶的达摩克利斯之剑，那么 Go 团队已经提前为我们铸造了盾牌。 来自https://words.filippo.io/2025-state 为什么是现在？”Record Now, Decrypt Later” Filippo 开场便澄清了一个常见的误区：量子计算机可能还需要 5 到 50 年才能破解现有的非对称加密（如 RSA、ECDH），为什么我们现在就要着急？ 答案在于 “Record [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/the-2025-go-cryptography-state-of-the-union-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/11/22/the-2025-go-cryptography-state-of-the-union">本文永久链接</a> &#8211; https://tonybai.com/2025/11/22/the-2025-go-cryptography-state-of-the-union</p>
<p>大家好，我是Tony Bai。</p>
<p>2025 年 8 月，Go 官方密码学库核心维护者、Geomys 创始人 <strong>Filippo Valsorda</strong> 在 GopherCon US 上发表了备受瞩目的年度主题演讲 —— <a href="https://www.youtube.com/watch?v=YnyeAQblUyA">“The Go Cryptography State of the Union</a>“。</p>
<p>这是一次年度技术汇报，也是一份关于 Go 语言如何应对未来十年安全挑战的战略蓝图。从<a href="https://tonybai.com/2025/05/20/post-quantum-cryptography-in-go">抗量子计算的未雨绸缪</a>，到 <a href="https://tonybai.com/2024/11/16/go-crypto-and-fips-140">FIPS 合规的架构性重构</a>，再到<a href="https://tonybai.com/2025/05/21/go-crypto-audit">令人惊叹的“零漏洞”审计记录</a>，Go 团队用行动证明了：<strong>最好的安全性，是让开发者无需感知、却时刻被守护的安全性。</strong></p>
<p>在本文中，我们将深入解读这次演讲的核心内容，从后量子加密的技术细节到纯 Go FIPS 的实现突破，带你一窥 Go 语言构建未来安全防线的全景图。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-crypto-101-qr.png" alt="img{512x368}" /></p>
<hr />
<h2>后量子时代的第一道防线：ML-KEM</h2>
<p>如果说<a href="https://tonybai.com/2024/12/11/simulate-quantum-computing-in-go">量子计算</a>是悬在现代密码学头顶的达摩克利斯之剑，那么 Go 团队已经提前为我们铸造了盾牌。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/the-2025-go-cryptography-state-of-the-union-2.jpeg" alt="" /><br />
<center>来自https://words.filippo.io/2025-state</center></p>
<h3>为什么是现在？”Record Now, Decrypt Later”</h3>
<p>Filippo 开场便澄清了一个常见的误区：量子计算机可能还需要 5 到 50 年才能破解现有的非对称加密（如 RSA、ECDH），为什么我们现在就要着急？</p>
<p>答案在于 <strong>“Record Now, Decrypt Later”（现在窃听，以后解密）</strong> 的攻击模式。攻击者（或是某些国家级力量）可以现在捕获并存储加密流量，耐心等待数十年后量子计算机问世，再解密这些数据。对于长期敏感的信息（如外交电文、个人健康数据、商业机密），<strong>现在的连接已经不再安全了</strong>。</p>
<h3>Go 的应对：ML-KEM 与混合加密</h3>
<ul>
<li><strong>标准落地</strong>：Go 1.24 正式在标准库中引入了 crypto/mlkem 包，实现了 NIST 最终选定的后量子密钥交换标准 <strong>ML-KEM</strong>（即 Kyber）。</li>
<li><strong>默认开启的混合保护</strong>：最令人兴奋的是，普通开发者无需修改一行代码。在 crypto/tls 中，Go 1.24+ 默认启用了 <strong>X25519 + ML-KEM-768</strong> 的混合密钥交换模式。
<ul>
<li><strong>混合的智慧</strong>：密码学界对新算法总是保持谨慎。ML-KEM 虽然基于格密码学（Lattices），但仍可能隐藏着未知的数学缺陷。Go 团队采用了“双保险”策略：将经典的 X25519 椭圆曲线算法与 ML-KEM 结合，将两者的结果进行哈希组合。</li>
<li><strong>安全性</strong>：除非攻击者<strong>同时</strong>拥有量子计算机（破解 X25519）<strong>和</strong>破解 ML-KEM 数学结构的天才数学家，否则你的连接坚不可摧。</li>
</ul>
</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2025/the-2025-go-cryptography-state-of-the-union-3.jpeg" alt="" /><br />
<center>来自https://words.filippo.io/2025-state</center></p>
<h3>为什么不急于“后量子签名”？</h3>
<p>与密钥交换不同，Filippo 解释了为什么后量子<strong>数字签名</strong>的推进更加缓慢。因为伪造签名需要实时进行，无法通过“现在记录，以后攻击”来实现，因此紧迫性较低。更重要的是，后量子签名的大小通常高达数 KB（相比现在的几百字节），这对网络协议设计带来了巨大的挑战，需要更多时间来演进。</p>
<hr />
<h2>FIPS 140-3：一场“纯 Go”的合规革命</h2>
<p>对于服务政府、金融或受监管行业的企业来说，<strong>FIPS 140</strong> 合规认证往往是强制性的。长期以来，Go 社区只能依赖 Go+BoringCrypto —— 一个基于 CGO 调用 Google 内部 C 语言库 BoringSSL 的方案。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/the-2025-go-cryptography-state-of-the-union-4.jpeg" alt="" /><br />
<center>来自https://words.filippo.io/2025-state</center></p>
<p>这不仅破坏了 Go 引以为傲的“静态编译、无依赖”特性，还引入了 C 代码的内存安全风险。Filippo 甚至透露，Trail of Bits 审计中发现的唯一一个真正漏洞，正是出在 Go+BoringCrypto 中。</p>
<h3>Go 1.24+ 的破局：原生 Go 模块</h3>
<p>Go 团队做出了一个大胆的决定：<strong>用纯 Go 重新实现 FIPS 模块</strong>。</p>
<ul>
<li><strong>原生与透明</strong>：新的 FIPS 模块位于 crypto/internal/fips140/&#8230;。对于用户来说，它只是标准库的一部分。当开启 FIPS 模式时，标准库会自动路由到这些经过认证的代码路径，而 API 保持完全一致。</li>
<li><strong>全平台制霸</strong>：得益于纯 Go 的跨平台特性，FIPS 支持不再局限于特定的 Linux 发行版。Filippo 自豪地展示了他在<strong>自家客厅</strong>搭建的测试实验室——从高端的 Ampere Altra ARM64 服务器，到女友的 Windows 笔记本，甚至是作为路由器的 EdgeRouter (MIPS/ARM)，全部通过了 FIPS 测试。</li>
<li><strong>无需 CGO</strong>：这是最大的胜利。开发者终于可以既拥有 FIPS 合规性，又享受 Go 原生的交叉编译和内存安全。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2025/the-2025-go-cryptography-state-of-the-union-5.jpeg" alt="" /><br />
<center>来自https://words.filippo.io/2025-state</center></p>
<hr />
<h2>安全记录：用测试堆出来的“零漏洞”</h2>
<p>Go 密码学库最令人骄傲的或许不是新特性，而是其惊人的安全记录。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/the-2025-go-cryptography-state-of-the-union-6.jpeg" alt="" /><br />
<center>来自https://words.filippo.io/2025-state</center></p>
<h3>惊人的成绩单</h3>
<ul>
<li><strong>零高危漏洞</strong>：自 2019 年以来，Go 密码学库未发生过任何严重（Ouch 级别）的安全漏洞。</li>
<li><strong>零 Go 专属漏洞</strong>：自 2021 年以来，甚至没有出现过 Go 实现特有的中等严重漏洞（Oof 级别）。所有出现的漏洞几乎都是协议本身的设计缺陷。</li>
<li><strong>审计背书</strong>：2025 年初，著名安全公司 <strong>Trail of Bits</strong> 对 Go 密码学库的基础设施进行了全面审计。结果令人欣慰：<strong>他们没有发现任何安全漏洞</strong>。</li>
</ul>
<h3>幕后功臣：疯狂的测试</h3>
<p>这种安全记录不是运气，而是工程化的结果：</p>
<ul>
<li><a href="https://words.filippo.io/accumulated/">累积测试向量 (Accumulated Test Vectors)</a>：如何测试一个算法在 0 到 200 字节长度的所有组合？这会产生数百万个测试用例。Go 团队使用了一种名为 <strong>“Accumulated”</strong> 的技巧：将算法在所有输入下的输出进行<strong>滚动哈希 (Rolling Hash)</strong>，最后只比对这一个哈希值。这使得在 CI 中运行海量测试成为可能。</li>
<li><a href="https://words.filippo.io/assembly-mutation/">汇编变异测试 (Assembly Mutation Testing)</a>：密码学底层大量使用汇编。为了测试难以覆盖的分支（例如进位标志的处理），团队开发了一套工具，自动<strong>“变异”</strong>汇编代码。例如，将一个“带进位加法”指令强制替换为“普通加法”。如果测试套件在汇编代码被故意破坏后依然通过，说明<strong>测试覆盖不足</strong>。这种反向验证直接消灭了潜在的盲区。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2025/the-2025-go-cryptography-state-of-the-union-7.jpeg" alt="" /><br />
<center>来自https://words.filippo.io/2025-state</center></p>
<hr />
<h2>细节中的魔鬼：更安全、更快的底层</h2>
<p>除了大方向的演进，无数细节的优化构成了 Go 安全的基石。Filippo 分享了几个令人印象深刻的案例：</p>
<ul>
<li><strong>RSA 的重生</strong>：crypto/rsa 包经历了彻底的重构。它不再使用通用的、性能较慢且难以防御侧信道攻击的 math/big 库，而是采用了全新的、<strong>常数时间 (Constant-time)</strong> 的底层实现。这不仅提升了性能，更从数学层面杜绝了计时攻击。同时，Go 果断<strong>移除了对小于 1024 位 RSA 密钥的支持</strong>，强制推动行业向更安全的标准迁移。</li>
<li><strong>AES-CTR 性能飞跃</strong>：通过一位社区成员 (Boris Nagaev) 的贡献，AES-CTR 模式的性能提升了 <strong>2 到 9 倍</strong>。</li>
<li><strong>永不失败的随机数</strong>：crypto/rand.Read 现在的承诺是 <strong>“Never Fails”</strong>。
<ul>
<li>在 Linux 上，它利用 vDSO 技术直接调用内核，大幅提升了获取随机数的性能。</li>
<li>为了确保承诺，团队甚至重新编写了 seccomp 库，专门用来在测试中模拟 getrandom 系统调用失败的极端场景，确保回退逻辑（fallback）绝对可靠。</li>
</ul>
</li>
</ul>
<hr />
<h2>小结：不仅要做得好，还要让开发者用得轻松</h2>
<p>Filippo Valsorda 的演讲向我们展示了 Go 语言在安全领域的宏大愿景：<strong>安全不应是开发者的负担，而应是语言赋予的基础设施。</strong></p>
<p>无论是默认开启的后量子保护，还是透明、无感的 FIPS 合规，Go 团队都在践行一种极致的工程哲学——<strong>把复杂性留给自己，把简单留给用户。</strong> 他们不满足于仅仅提供“能用”的加密算法，而是致力于通过持续的测试、审计和架构演进，为整个生态系统构筑一道坚不可摧、且能抵御未来威胁的防线。</p>
<p>随着 Go 1.24 及后续版本的发布，每一位 Gopher 手中的工具箱，都已在不知不觉中完成了升级。当我们轻松地编写代码时，Go 的密码学库正在底层默默地为我们抵挡着来自现在和未来的风暴。</p>
<hr />
<h2>参考资料</h2>
<ul>
<li><a href="https://words.filippo.io/2025-state/">Filippo Valsorda: The 2025 Go Cryptography State of the Union</a></li>
<li><a href="https://www.youtube.com/watch?v=YnyeAQblUyA">Youtube Video: GopherCon 2025 &#8211; The Go Cryptography State of the Union</a></li>
<li><a href="https://go.dev/doc/go1.24">Go 1.24 Release Notes</a></li>
<li><a href="https://words.filippo.io/accumulated/">Accumulated Test Vectors</a></li>
<li><a href="https://words.filippo.io/assembly-mutation/">Assembly Mutation Testing</a></li>
</ul>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/11/22/the-2025-go-cryptography-state-of-the-union/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Azure CTO 深度解读：微软为何要用 Rust “替换” C/C++，又将如何用 AI 加速代码迁移？</title>
		<link>https://tonybai.com/2025/09/11/microsoft-is-getting-rusty/</link>
		<comments>https://tonybai.com/2025/09/11/microsoft-is-getting-rusty/#comments</comments>
		<pubDate>Thu, 11 Sep 2025 13:05:49 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[AzureDataExplorer]]></category>
		<category><![CDATA[C/C++]]></category>
		<category><![CDATA[Crate生态系统]]></category>
		<category><![CDATA[CTO]]></category>
		<category><![CDATA[DirectWrite]]></category>
		<category><![CDATA[DISKANN]]></category>
		<category><![CDATA[GenAI]]></category>
		<category><![CDATA[GraphRAG]]></category>
		<category><![CDATA[MarkRussinovich]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Office]]></category>
		<category><![CDATA[OS]]></category>
		<category><![CDATA[ProjectMu]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[SDK]]></category>
		<category><![CDATA[Transpiler]]></category>
		<category><![CDATA[UEFI]]></category>
		<category><![CDATA[Win32k.sys]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[互操作性]]></category>
		<category><![CDATA[代码迁移]]></category>
		<category><![CDATA[内存安全]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[动态链接]]></category>
		<category><![CDATA[安全未来倡议]]></category>
		<category><![CDATA[安全漏洞]]></category>
		<category><![CDATA[工具链]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[性能提升]]></category>
		<category><![CDATA[自动化代码翻译]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5148</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/09/11/microsoft-is-getting-rusty 大家好，我是Tony Bai。 近日，微软 Azure CTO、技术巨擘 Mark Russinovich 在一场 Rust 技术会议上发表了闭幕演讲，以前所未有的坦诚和力度，揭示了微软内部正在进行的一场深刻的技术变革：全面拥抱 Rust，并战略性地替代 C/C++。 他不仅分享了 Rust 在 Windows 内核、Office、Azure 云等核心产品中的惊人实践案例，还首次披露了微软正在研发的、利用 AI 大模型自动将 C/C++ 代码转换为安全 Rust 的前沿工具。这既是一次技术分享，也是一份来自行业顶层的宣言。 在这篇文章中，我们就来看看微软在走向Rust的路上究竟做了哪些工作和改变，用户和社区的反馈又是如何。 战略驱动：为何微软必须转向 Rust？ 演讲开篇，Mark Russinovich 就抛出了一个触目惊心的数据，这也是驱动微软进行这场变革的根本原因： 在过去十几年中，微软所有产品中 70% 的安全漏洞，均由 C/C++ 中的内存安全问题导致。 他直言，这种趋势仍在继续，这已不仅仅是技术债，更是持续不断的安全事件和威胁。正是基于此，他个人早已成为 Rust 的坚定拥护者，并分享了一段有趣的往事：2022年，他在看到编程语言排行榜后，有感而发地发布了一条推文——“是时候停止在任何新项目中使用 C/C++ 了，业界应该转向 Rust”。 这条推文成为了他有史以来互动量最高的内容，甚至引来了微软 CEO Satya Nadella 的电话询问。而他的回答坚定不移：“是的，我坚信如此。” 这并非一时冲动，而是一场席卷微软的、自下而上与自上而下相结合的运动。从美国国家安全局 (NSA) 呼吁业界放弃内存不安全的语言，到微软自身因不安全代码被攻击后发起的“安全未来倡议 (Secure Future Initiative)”，微软上下已经形成共识：必须摆脱不安全的语言。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/microsoft-is-getting-rusty-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/09/11/microsoft-is-getting-rusty">本文永久链接</a> &#8211; https://tonybai.com/2025/09/11/microsoft-is-getting-rusty</p>
<p>大家好，我是Tony Bai。</p>
<p>近日，微软 Azure CTO、技术巨擘 Mark Russinovich 在一场 Rust 技术会议上发表了<a href="https://www.youtube.com/watch?v=1VgptLwP588">闭幕演讲</a>，以前所未有的坦诚和力度，揭示了微软内部正在进行的一场深刻的技术变革：全面拥抱 Rust，并战略性地替代 C/C++。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/microsoft-is-getting-rusty-2.png" alt="" /></p>
<p>他不仅分享了 Rust 在 Windows 内核、Office、Azure 云等核心产品中的惊人实践案例，还首次披露了微软正在研发的、利用 AI 大模型自动将 C/C++ 代码转换为安全 Rust 的前沿工具。这既是一次技术分享，也是一份来自行业顶层的宣言。</p>
<p>在这篇文章中，我们就来看看微软在走向Rust的路上究竟做了哪些工作和改变，用户和社区的反馈又是如何。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-micro-column-2025-pr.png" alt="" /></p>
<h2>战略驱动：为何微软必须转向 Rust？</h2>
<p>演讲开篇，Mark Russinovich 就抛出了一个触目惊心的数据，这也是驱动微软进行这场变革的根本原因：</p>
<blockquote>
<p><strong>在过去十几年中，微软所有产品中 70% 的安全漏洞，均由 C/C++ 中的内存安全问题导致。</strong></p>
</blockquote>
<p>他直言，这种趋势仍在继续，这已不仅仅是技术债，更是持续不断的安全事件和威胁。正是基于此，他个人早已成为 Rust 的坚定拥护者，并分享了一段有趣的往事：2022年，他在看到编程语言排行榜后，有感而发地发布了一条推文——<strong>“是时候停止在任何新项目中使用 C/C++ 了，业界应该转向 Rust”</strong>。</p>
<p>这条推文成为了他有史以来互动量最高的内容，甚至引来了微软 CEO Satya Nadella 的电话询问。而他的回答坚定不移：“是的，我坚信如此。”</p>
<p>这并非一时冲动，而是一场席卷微软的、自下而上与自上而下相结合的运动。从美国国家安全局 (NSA) 呼吁业界放弃内存不安全的语言，到微软自身因不安全代码被攻击后发起的“安全未来倡议 (Secure Future Initiative)”，微软上下已经形成共识：<strong>必须摆脱不安全的语言</strong>。</p>
<h2>实践版图：Rust 在微软核心产品中的落地生根</h2>
<p>Mark Russinovich 随后详细介绍了 Rust 在微软内部的实践版图，其广度和深度令人瞩目。</p>
<h3>Windows：从内核“阿喀琉斯之踵”开始</h3>
<ul>
<li><strong>Project Mu (UEFI 固件):</strong> 微软选择从安全性要求极高的系统引导固件入手，用 Rust 重写了 UEFI 实现（<a href="https://github.com/microsoft/mu">Project Mu</a>）。该项目已应用于 Azure 数据中心和 Surface 笔记本，并已开源，旨在推动整个硬件生态采用 Rust。</li>
<li><strong>DirectWrite (核心图形组件):</strong> 团队选择了一个漏洞频发的独立组件——负责字体解析的 DirectWrite 进行移植。两名开发者耗时六个月，将 <strong>15.4 万行</strong> C/C++ 代码移植为 Rust。结果不仅消除了安全隐患，还意外获得了 <strong>5% 到 15% 的性能提升</strong>。</li>
<li><strong>Win32k.sys (GDI 模块):</strong> 这是 Windows 安全的“阿喀琉斯之踵”，过去20年间漏洞不断。微软选择用 Rust 重写了其中的 GDI Regions 子系统。两名开发者耗时三个月，移植了 <strong>6.3 万行</strong> 代码进入内核态。尽管 C++/Rust 的互操作边界带来了巨大挑战，但项目最终成功，且没有性能衰退。如今，在 Windows 系统目录中，你甚至能找到带有 &#95;rs 后缀的内核模块文件。</li>
</ul>
<h3>Office 与 Azure 云：性能与安全的双重胜利</h3>
<ul>
<li><strong>Office (DISKANN 向量搜索):</strong> Office 团队将一个前沿的向量搜索算法（DISKANN）从 C++ 移植到 Rust，用于 Office 365 和 Azure Cosmos DB。结果是惊人的：在实现同等 QPS 的情况下，<strong>召回率显著提升，内存占用反而下降</strong>。</li>
<li><strong>Azure (CTO 的铁腕):</strong> Mark Russinovich 透露，早在发布那条著名推文的两三年前，他就已在 Azure 内部颁布指令：<strong>“Azure 中不再有新的 C++ 系统代码”</strong>。这一指令推动了 Rust 在 Azure 基础架构中的全面应用：
<ul>
<li><strong>硬件层面:</strong> 云服务器的开源可信根项目 <strong>Caliptra</strong>、深入每台服务器的 <strong>Azure Integrated HSM</strong> 硬件安全模块，其固件均由 Rust 编写。</li>
<li><strong>硬件卸载卡:</strong> 负责网络和存储处理的智能网卡（DPU）上的新组件，已全部使用 Rust 开发，部分已有 C++ 组件也被迁移到了 Rust。</li>
<li><strong>虚拟化:</strong> Hyper-V 的 Arm64 模拟代码已用 Rust 重写；最近开源的 <strong>Open VMM</strong>（一个准虚拟化监视器）完全由 Rust 构建；而革命性的 <a href="https://github.com/hyperlight-dev/hyperlight">Hyper-V Lite 项目</a>，能以微秒级速度启动一个超轻量级虚拟机来运行 WASM 负载，其原型虽为 C#，但最终的开源实现完全是 Rust。</li>
</ul>
</li>
<li><strong>Azure 服务:</strong>
<ul>
<li><strong>Azure Data Explorer (ADX):</strong> 这个每天处理 PB 级数据的日志分析平台，其 V2 版本后完全用 <strong>35 万行 Rust 代码</strong> 重写，性能超越 C++ 版本，成为微软内部 Rust 实践的标杆案例。</li>
<li><strong>Azure SDK for Rust:</strong> 顺应客户需求，Azure 官方已发布了 Rust SDK 的 Beta 版本，标志着 Rust 正式成为 Azure 的一等公民语言。</li>
</ul>
</li>
</ul>
<h2>真实反馈：来自一线开发者的收获与挑战</h2>
<p>这场变革并非一帆风顺。Mark Russinovich 坦诚地分享了一线开发者的真实反馈：</p>
<p>** 收获 (The Positives):**</p>
<ul>
<li><strong>“如果它能编译，它就能工作”</strong>: 这是开发者们提到最多的一点，与 C++ 编译通过后仍充满不确定性的体验形成鲜明对比。</li>
<li><strong>减少摩擦，专注创新</strong>: 消除了内存安全和数据竞争等底层心智负担。</li>
<li><strong>“两个月的转变”</strong>: 一个常见的模式是，C++ 开发者最初会对所有权和借用检查器感到痛苦，但大约两个月后，他们会转变为 Rust 的忠实拥护者。</li>
</ul>
<p>** 挑战 (The Negatives):**</p>
<ul>
<li><strong>C++ 互操作性是第一大难题</strong>: 在逐步替换大型 C++ 项目时，处理两种语言的边界问题耗费了大量精力。</li>
<li><strong>工具链仍有待成熟</strong>。</li>
<li><strong>Crate 生态系统</strong>: 开发者不确定应该使用和信任哪些第三方库。</li>
<li><strong>部分依赖的特性尚未稳定</strong>。</li>
<li><strong>动态链接</strong>: 在 Windows 生态中常见的动态链接，与 Rust 的结合存在问题。</li>
</ul>
<p>尽管存在这些挑战，但 Mark Russinovich 强调，<strong>优点已经足够让微软“全身心投入 (all in)”</strong>。</p>
<h2>展望未来：用 AI 加速 “去 C++” 进程</h2>
<p>演讲的最后，Mark Russinovich 揭示了微软正在探索的、旨在加速 Rust 迁移的“终极武器”——<strong>自动化代码翻译</strong>。</p>
<p>微软正在从两个方向推进这项工作：</p>
<ol>
<li><strong>专用转译器 (Transpiler):</strong> 针对特定领域，如经过形式化验证的加密库。微软研究团队已开发出一个工具，能将严格遵循特定规范的 C 代码<strong>自动、安全地转译为 100% safe 的 Rust 代码</strong>，并确保其数学验证在转译后依然有效。</li>
<li><strong>通用 AI 翻译器 (GenAI + GraphRAG):</strong> 这是更宏伟的目标。传统的 LLM 在处理多文件、复杂的 C++ 项目时效果不佳。微软正在利用一种名为 <strong>GraphRAG</strong> (图检索增强生成) 的先进技术。该技术能将代码解析为抽象语法树，并构建一个多层次的、包含代码摘要和依赖关系的图谱。当进行翻译时，AI 可以基于这个图谱进行更精准、更具上下文感知的代码生成。</li>
</ol>
<p>他现场演示了一个将多文件 Python 小游戏翻译为 Rust 的例子。普通的 GPT-4o 生成的代码无法编译，而 <strong>GraphRAG 驱动的翻译器则一次性生成了可完美运行的、100% safe 的 Rust 代码</strong>。</p>
<h2>总结：一场自上而下的语言革命</h2>
<p>Mark Russinovich 的演讲，标志着 Rust 在主流工业界的应用进入了一个全新的阶段。微软的实践雄辩地证明，用 Rust 替代 C/C++ 不仅是为了安全，更能带来意想不到的性能收益和开发体验提升。</p>
<p>更重要的是，微软的承诺是全方位的：从内部产品的深度重构，到对社区的资金支持，再到投入研发力量攻克 C++ 互操作和自动化迁移这两大核心难题。</p>
<p>正如 Mark 所言，一门语言的成熟需要超过十年的时间。Rust 已经走到了这个节点，其生态和工具链的成熟度已经达到了一个临界点，使得像微软这样的巨头可以放心下注。对于任何想要挑战 Rust 地位的新语言来说，都将面临一座极难逾越的高山。</p>
<p>微软的“All in”，不仅是对 Rust 过去的肯定，更是对未来的巨大投资。这无疑为整个软件行业指明了一个更安全、更高效的方向。</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/09/11/microsoft-is-getting-rusty/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Rust 2025 深度解读：在十周年里程碑上，Niko Matsakis 如何擘画下一个时代的灵魂与蓝图？</title>
		<link>https://tonybai.com/2025/08/19/rust-in-2025/</link>
		<comments>https://tonybai.com/2025/08/19/rust-in-2025/#comments</comments>
		<pubDate>Tue, 19 Aug 2025 02:50:25 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ACM]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[cargo]]></category>
		<category><![CDATA[Discord]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GraydonHoare]]></category>
		<category><![CDATA[GUI]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[JS]]></category>
		<category><![CDATA[Leptos]]></category>
		<category><![CDATA[NikoMatsakis]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[Tauri]]></category>
		<category><![CDATA[TS]]></category>
		<category><![CDATA[TypeScript]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[云原生]]></category>
		<category><![CDATA[互操作]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[编译器]]></category>
		<category><![CDATA[设计哲学]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5054</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/08/18/rust-in-2025 大家好，我是Tony Bai。 2025 年 5 月 15 日，Rust 语言迎来了其 1.0 版本发布的十周年纪念日。这是一个充满里程碑意义的时刻，不仅是对Rust过去十年辉煌成就的回顾，更是展望未来的关键节点。值此之际，Rust 语言团队负责人、核心开发者 Niko Matsakis 发表了一系列题为“Rust in 2025”的纲领性博客文章，系统性地阐述了他个人对 Rust 未来发展的深邃思考。本文将融合 Niko 在十周年庆典上的感言与“Rust 2025”系列的技术蓝图，和大家一起解读一下Niko对下一个时代Rust演进路径的擘画。 回望十年 —— 指引 Rust 航程的两大“北极星” 任何对未来的展望，都必须植根于对过去的深刻理解。在十周年庆典的感言中，Niko Matsakis 将 Rust 的非凡成功，归功于其传奇创始人 Graydon Hoare 从一开始就为这门语言设定的两个坚定不移的“北极星”。它们不仅塑造了 Rust 的技术内核，更铸就了其独特的社区文化。 技术北极星：拒绝妥协，“我们可以拥有好东西” Graydon Hoare 最初为 Rust 设定的目标是“创建一种‘不会吃掉你衣物’的系统编程语言”。这个看似风趣的目标背后，是一种对行业“常识”的根本性挑战。Niko 将其精炼为一句充满信念的口号：“是的，我们可以拥有好东西 (Yes, we can have nice things)”。 这句话的深层含义在于，Rust 拒绝接受在软件开发中长期存在的、看似不可避免的“魔鬼交易”： [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/rust-in-2025-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/08/18/rust-in-2025">本文永久链接</a> &#8211; https://tonybai.com/2025/08/18/rust-in-2025</p>
<p>大家好，我是Tony Bai。</p>
<p>2025 年 5 月 15 日，Rust 语言迎来了<a href="https://blog.rust-lang.org/2025/05/15/Rust-1.87.0/">其 1.0 版本发布的十周年纪念日</a>。这是一个充满里程碑意义的时刻，不仅是对Rust过去十年辉煌成就的回顾，更是展望未来的关键节点。值此之际，Rust 语言团队负责人、核心开发者 Niko Matsakis 发表了一系列题为“Rust in 2025”的纲领性博客文章，系统性地阐述了他个人对 Rust 未来发展的深邃思考。本文将融合 Niko 在十周年庆典上的感言与<a href="https://smallcultfollowing.com/babysteps/series/rust-in-2025/">“Rust 2025”系列的技术蓝图</a>，和大家一起解读一下Niko对下一个时代Rust演进路径的擘画。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/go-micro-column-2025-pr.png" alt="" /></p>
<h2>回望十年 —— 指引 Rust 航程的两大“北极星”</h2>
<p>任何对未来的展望，都必须植根于对过去的深刻理解。在十周年庆典的感言中，Niko Matsakis 将 Rust 的非凡成功，归功于其传奇创始人 Graydon Hoare 从一开始就为这门语言设定的两个坚定不移的“北极星”。它们不仅塑造了 Rust 的技术内核，更铸就了其独特的社区文化。</p>
<h3>技术北极星：拒绝妥协，“我们可以拥有好东西”</h3>
<p>Graydon Hoare 最初为 Rust 设定的目标是“创建一种‘不会吃掉你衣物’的系统编程语言”。这个看似风趣的目标背后，是一种对行业“常识”的根本性挑战。Niko 将其精炼为一句充满信念的口号：<strong>“是的，我们可以拥有好东西 (Yes, we can have nice things)”</strong>。</p>
<p>这句话的深层含义在于，Rust 拒绝接受在软件开发中长期存在的、看似不可避免的“魔鬼交易”：</p>
<ul>
<li><strong>性能 vs. 安全：</strong> 传统观念认为，要获得 C/C++ 般的极致性能和底层控制力，就必须放弃内存安全，开发者需要像走钢丝一样，为每一个内存操作的正确性负全责。</li>
<li><strong>抽象 vs. 效率：</strong> 高级语言如 Java 或 Go 提供了垃圾回收和丰富的抽象，带来了更高的生产力，但在性能敏感的“基础软件”领域，开发者又必须小心翼翼地规避其抽象带来的性能开销，比如 GC 停顿(STW)。</li>
</ul>
<p>Rust 的技术北极星，就是要在这一点上实现突破。它通过借鉴 C++ 的“零成本抽象”理念，并独创性地引入所有权、借用和生命周期等概念构成的类型系统，实现了<strong>编译期的内存安全保证</strong>。这使得开发者能够像使用 OCaml 等高级语言一样，编写富有表现力、高度抽象的代码，同时又能获得媲美 C/C++ 的运行性能。这一定位，精准地命中了“基础软件”开发的核心痛点，也成为了 Rust 在过去十年中攻城略地的最强武器。</p>
<h3>文化北极星：社区的力量与谦逊的协作</h3>
<p>如果说技术北极星定义了 Rust 的“硬实力”，那么文化北极星则塑造了其无与伦比的“软实力”。Niko 强调，Graydon 从项目伊始就认识到构建正确文化的重要性。这份远见卓识，集中体现在由他亲自撰写的《行为准则 (Code of Conduct)》中。</p>
<blockquote>
<p>“提供一个友好、安全和欢迎的环境，无论经验水平、性别认同和表达、残疾、国籍或其他类似特征如何……友善和礼貌应被优先考虑……并认识到‘很少有唯一的正确答案’，‘人们有不同意见’，‘每个设计或实现选择都带有权衡’。”</p>
</blockquote>
<p>这些条款不仅仅是空洞的口号，它们已经内化为 Rust 社区的行事准则。Niko 坦言，如果没有这种真正开放、尊重的协作氛围，Rust 绝不会是今天的样子。无数伟大的想法——从 Brian Anderson 创造的、沿用至今的 #[test] 语言基础设施，到 Sophia Turner 和 Esteban Kuber 对编译器错误信息的革命性改进——都源于社区成员的自发贡献。</p>
<p>Niko 分享了一个极具代表性的故事，来诠释这种“集体所有”的文化。2024 年，当计算机科学顶级学术组织 <a href="https://sigplan.org/Awards/Software/">ACM 将其 SIGPLAN 软件奖授予 Rust</a> 时，一个难题出现了：获奖名单上应该写谁的名字？核心贡献者们无法达成一致，提出的名单从数千人到“空无一人”。最终，这份荣誉归于一个由领导力委员会决定的名单，并以 <strong>“所有过去与现在的 Rust 贡献者”</strong> 结尾。</p>
<p>这个故事完美地诠释了 Rust 的成功之道：它是一场由全球成千上万开发者共同参与的、去中心化的伟大协作。这种文化，是 Rust 能够持续进化、不断吸纳新思想的根本保障。</p>
<h2>2025 使命 —— 聚焦基础软件，深化语言哲学</h2>
<p>在“两大北极星”的持续指引下，Niko Matsakis 在其“Rust in 2025”系列中，为 Rust 的下一个发展阶段确立了更加聚焦的核心使命：<strong>显著降低编写和维护“基础软件 (Foundational Software)”的门槛。</strong></p>
<p>所谓基础软件，即“构成其他一切软件基石的部分”。Rust 如今已在这一领域遍地开花：</p>
<ul>
<li><strong>云原生基础设施：</strong> AWS 的几乎所有服务背后都有 Rust 的身影，其 Firecracker 微型虚拟机更是完全由 Rust 构建。</li>
<li><strong>开发者工具链：</strong> 从命令行工具到大型构建系统，Rust 正在重塑开发者的工作流。</li>
<li><strong>终端应用与嵌入式：</strong> 亚马逊 PrimeVideo 在 Web 端使用 Rust 编译的 WebAssembly 播放视频；在嵌入式领域，Rust 的应用也已“上天入海”。</li>
<li><strong>操作系统内核：</strong> Windows 和 Linux 两大主流操作系统内核，都已开始集成 Rust 代码。</li>
</ul>
<p>为了让 Rust 在这条道路上走得更远，Niko 提出了几个关键的指导原则，它们可以被看作是 Rust 核心设计哲学的深化与具体化。</p>
<h3>原则一：人体工程学飞轮 —— 用“拉伸目标”驱动普适性改进</h3>
<p>一个有趣的观点是，Niko 认为尽管 GUI（如 Dioxus, Tauri）或 Web 前端（如 Leptos）可能永远不会是 Rust 的“最佳应用场景”，但这些高层应用的探索对 Rust 而言至关重要。</p>
<p>他将此称为“拉伸目标 (Stretch Goals)”。这些项目试图将 Rust 推向其舒适区之外，必然会对其<strong>人体工程学 (ergonomics)</strong> 提出更高的要求。为了在这些领域与 JavaScript/TypeScript 等语言竞争，Rust 必须变得更简洁、更方便。而这些为了满足高层应用而进行的改进——无论是更强大的宏系统、更灵活的类型系统，还是更智能的编译器——最终会“涓滴”下来，惠及所有 Rust 开发者，包括那些专注于编写内核模块或网络服务的底层系统工程师。这是一个正向的“人体工程学飞轮”。</p>
<h3>原则二：全栈覆盖 —— 单一技术栈的生产力红利</h3>
<p>Niko 观察到一个趋势：许多团队最初只打算在某个对延迟敏感的特定服务（如 Discord 的数据平面）中使用 Rust，但最终却将其扩展到整个技术栈。原因在于，一旦团队跨过了最初的学习曲线，Rust 的生产力相当可观。使用单一语言可以共享库、工具和知识，从而极大地降低了维护成本和认知负荷。正如 Niko 所说：“简单的代码，无论用何种语言编写，都是简单的。” 确保 Rust 在高层应用中也“足够好用”，是在为用户提供构建全栈应用的能力，这本身就是一个巨大的价值主张。</p>
<h3>原则三：“平滑的迭代式深化 (Smooth, iterative deepening)”</h3>
<p>这是 Niko 提出的一个核心设计哲学，也是对 Rust 学习曲线问题的直接回应。他理想中的用户体验应该是：</p>
<ol>
<li><strong>上手简单：</strong> 用户可以快速启动并运行一个简单的项目。</li>
<li><strong>渐进深入：</strong> 当项目变得复杂，用户需要更多控制权时，他们应该能够以一种<strong>局部化</strong>的方式进行优化或重构，而无需一次性学习大量复杂的背景知识。</li>
</ol>
<p>这个过程应该是“平滑”的，像走在一个缓坡上，而不是面对一面“悬崖”。许多技术要么上手极难，要么从“简单模式”切换到“专家模式”时需要彻底重写或学习一套全新的概念。Rust 并非总是能完美做到这一点，但这是其持续努力的方向。</p>
<h2>技术蓝图 —— 以“可扩展编译器”实现“丝滑互操作”</h2>
<p>如果说“赋能基础软件”是战略目标，那么 Niko 提出的技术蓝图就是实现这一目标的具体战术。其核心可以概括为一句话：<strong>通过构建一个“可扩展的编译器”，实现“丝滑流畅的语言互操作 (silky smooth language interop)”。</strong></p>
<h3>核心问题：基础软件生于一个多语言世界</h3>
<p>Niko 清醒地认识到，基础软件的世界是异构的。C 语言长期以来是计算世界的“通用语 (lingua franca)”，而 C++ 则构建了庞大的软件帝国。Rust 若想在这些领域取得成功，就不能成为一个孤岛，而必须成为一个优秀的“连接者”。</p>
<blockquote>
<p>注：在成为一个优秀“连接者”的道路上，Go恰恰是做的不够好的那一个！</p>
</blockquote>
<p>他将语言互操作的需求分为两大场景：</p>
<ul>
<li>
<p><strong>场景一：最小公分母 (Least Common Denominator, LCD)</strong></p>
<ul>
<li><strong>目标：</strong> “一次编写，多处使用”。比如，用 Rust 编写一个核心业务逻辑库，然后将其打包成 SDK，供 Android (Kotlin)、iOS (Swift)、Web (WASM) 和桌面端调用。</li>
<li><strong>特点：</strong> 调用方向主要是单向的（从其他语言到 Rust），暴露的 API 相对简单，易于在不同语言中惯用地表达。</li>
<li><strong>愿景：“语言互操作领域的 serde”。</strong> Niko 提出了一个极具启发性的构想。正如 serde 库定义了一套通用的序列化/反序列化 Trait (Serialize, Deserialize)，而具体的数据格式（JSON, YAML 等）则由社区以独立的 crate 实现一样。他也期望能有一个核心的互操作框架，定义通用的 API 规范，然后由社区为不同的目标语言（Python, Java, Swift 等）开发具体的“后端”实现。</li>
</ul>
</li>
<li>
<p><strong>场景二：深度互操作 (Deep Interop)</strong></p>
<ul>
<li><strong>目标：</strong> 与某一特定语言进行深度、双向的集成。</li>
<li><strong>特点：</strong> 通常发生在用 Rust 逐步替换大型 C++ 或 Java 应用的模块时，或者在像 Linux 内核这样的 C 项目中嵌入 Rust 代码。这需要处理复杂的类型、内存模型和调用约定。</li>
<li><strong>重点：C 和 C++ 是重中之重。</strong> 由于历史原因，这两个语言构成了现有基础软件的最大存量。Niko 对 cxx、crubit 等项目以及 Rust 基金会的“Rust-C++ 互操作性倡议”给予了高度评价。</li>
</ul>
</li>
</ul>
<h3>核心解决方案：“可扩展编译器 (The Extensible Compiler)”</h3>
<p>如何实现上述宏大的互操作目标？其他语言（如 Swift/Zig 对 C/C++）的做法是，将对特定语言的支持“烘焙 (bake it in)”进编译器。Niko 认为 Rust 应该走一条更具自身特色的道路——<strong>构建一个可扩展的编译器</strong>。</p>
<p>这个构想的本质，是对现有的过程宏（procedural macros）机制进行一次彻底的“超级充电”。目前的过程宏非常强大，但其接口极其简单：“输入一堆 Token，输出一堆 Token”。它对编译器的内部状态一无所知。Niko 设想的未来过程宏（或者说编译器插件）将拥有前所未有的能力：</p>
<ol>
<li><strong>检查类型信息：</strong> 这是最大的突破。宏将能够查询编译器已经推断出的类型信息，从而做出更智能的代码生成决策。这将彻底改变 ORM、RPC 框架和 FFI 绑定的编写方式。</li>
<li><strong>按需生成代码：</strong> 宏将能够在编译的更后期阶段（如单态化 monomorphization）被调用，根据具体的类型实例化请求来生成代码。这意味着可以避免编译大量永远不会被使用的模板代码，同时能与编译器的优化过程更紧密地集成。</li>
<li><strong>影响诊断信息和 Lint：</strong> 宏将能向编译器提供信息，以生成更贴近用户原始代码的、高质量的错误和警告信息，而不是目前常常出现的、令人困惑的宏展开后代码的错误。</li>
<li><strong>定制语言规则：</strong> 在更遥远的未来，甚至可能允许宏在一定程度上定制方法分发等语言核心行为，为领域特定语言（DSL）的嵌入提供无限可能。</li>
</ol>
<p>这个“可扩展编译器”的愿景，其影响远不止于语言互操作。它将赋能社区，以 crate 的形式创造出今天难以想象的各种工具和库。Niko 以 F# 的类型提供者 (Type Providers) 为例，展示了这种能力可以如何彻底改变开发者与外部数据源（如数据库、Web API）的交互方式。</p>
<blockquote>
<p>注：感叹一下！过程宏如今已经足够复杂了！按这个思路下去，未来将可能更复杂:(，心疼一下过程宏的开发者！不过，对于过程宏的最终用户，也许这能够提供更强大、更智能、更用户友好的功能。</p>
</blockquote>
<h2>结论 —— 稳定性与进化，无畏地创造未来</h2>
<blockquote>
<p>“没有停滞的稳定性 (Stability without stagnation)”是 Rust 最重要的价值观。在我看来，一种语言一旦停止进化，它就开始死亡。</p>
</blockquote>
<p>Niko Matsakis 的这句话，为整个“Rust 2025”愿景提供了最终的注脚。这份蓝图，正是 Rust 践行“稳定性与进化”并存理念的生动体现。</p>
<p>它同样展现了一种成熟和自信的姿态。Niko 明确表示，我们不需要“Rust 福音派特别行动队 (Rust Evangelism Task Force)”。Rust 的目标不是说服全世界放弃其他语言，而是<strong>让 Rust 与其他语言更好地协同工作</strong>。当向现有项目添加 Rust 变得异常简单时，它的价值自然会吸引开发者。这是一种基于实力的吸引，而非基于宣传的推广。</p>
<p>在十周年的感言结尾，Niko 也分享了他的个人感悟。作为 Rust 的核心开发者，他们每天面对的是无尽的 Bug、不符合人体工程学的设计和永无休止的 RFC 讨论。有时，这会让人感到沮丧。但他发现，唯一的“解药”，就是走出去和真实的用户交流，去看看大家正在用 Rust 构建的那些令人惊叹的东西。</p>
<p>那一刻，他们会再次记起，这一切的最终目的，是<strong>赋能人们去构建和重构我们赖以生存的基础软件</strong>。或者，用 Felix Klock 的经典名言来说，就是去<strong>“无畏地创造 (hack without fear)”</strong>。</p>
<p>Rust 的第一个十年，已经证明了其“北极星”的正确性。而“Rust 2025”愿景，则为第二个十年的航程，设定了清晰、务实且激动人心的航向。这场关于 Rust 未来的对话，不仅关乎一门编程语言，更关乎我们如何构建一个更可靠、更高效、更安全的数字世界。</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/08/19/rust-in-2025/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>2025年最佳机器人Linux操作系统——顶级发行版与最新进展！</title>
		<link>https://tonybai.com/2025/08/17/best-linux-os-for-robotics-in-2025/</link>
		<comments>https://tonybai.com/2025/08/17/best-linux-os-for-robotics-in-2025/#comments</comments>
		<pubDate>Sun, 17 Aug 2025 14:49:28 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[ARM]]></category>
		<category><![CDATA[DDS]]></category>
		<category><![CDATA[Debian]]></category>
		<category><![CDATA[Fedora]]></category>
		<category><![CDATA[Gazebo]]></category>
		<category><![CDATA[Humble]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[LIDAR]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[LTS]]></category>
		<category><![CDATA[MoveIt!]]></category>
		<category><![CDATA[NASA]]></category>
		<category><![CDATA[NVIDIA]]></category>
		<category><![CDATA[OpenCV]]></category>
		<category><![CDATA[OpenEmbedded]]></category>
		<category><![CDATA[OS]]></category>
		<category><![CDATA[PyTorch]]></category>
		<category><![CDATA[reddit]]></category>
		<category><![CDATA[ROS]]></category>
		<category><![CDATA[ROS2]]></category>
		<category><![CDATA[Stackoverflow]]></category>
		<category><![CDATA[TensorFlow]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[UbuntuCore]]></category>
		<category><![CDATA[Yocto]]></category>
		<category><![CDATA[人工智能]]></category>
		<category><![CDATA[仿真]]></category>
		<category><![CDATA[具身智能]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[嵌入式]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[机器人]]></category>
		<category><![CDATA[机器人操作系统]]></category>
		<category><![CDATA[树莓派]]></category>
		<category><![CDATA[波士顿动力]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5048</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/08/17/best-linux-os-for-robotics-in-2025 大家好，我是Tony Bai。 如果你正投身于机器人技术领域，选择正确的操作系统至关重要。随着人工智能、自动化和机器学习的进步，机器人正变得前所未有的复杂。在为这些智能机器提供动力方面，Linux凭借其开源的灵活性、稳定性以及对机器人框架的广泛支持，仍然是首选。 在本文中，我们将探讨2025年最佳的机器人Linux操作系统，帮助你为你的项目找到完美的发行版——无论你是从事工业自动化、人工智能驱动的机器人技术，还是业余爱好者的创作。我们还将介绍专注于机器人的Linux发行版的最新发展，让你保持领先。 1. Ubuntu机器人操作系统 机器人技术正以前所未有的速度发展，改变着医疗、自动化、制造乃至太空探索等行业。任何机器人系统的基础都是其操作系统，它决定了系统的效率、安全性和性能。 Ubuntu机器人操作系统 截至2025年，Ubuntu已成为机器人领域的最佳Linux操作系统。凭借其与机器人操作系统（ROS）的无缝集成、优化的实时性能以及对AI驱动机器人技术的扩展支持，Ubuntu成为开发者、研究人员和行业的首选。 为什么Ubuntu是机器人领域的最佳Linux操作系统 Ubuntu在机器人领域的主导地位并非偶然——它建立在多年的持续发展和强大的社区支持之上。以下是Ubuntu脱颖而出的一些关键原因： 1. 与ROS（机器人操作系统）的无缝集成 ROS已成为使用最广泛的机器人中间件，提供了一系列工具和库，帮助开发者构建复杂的机器人应用程序。由于ROS最初就是为Ubuntu设计的，因此集成非常无缝。 ROS 2与Ubuntu：到2025年，Ubuntu为ROS 2提供了内置支持，ROS 2提供了实时功能、安全增强和对多机器人系统更好的支持。 预装ROS软件包：Ubuntu通过预配置的软件包简化了ROS的安装，为开发者节省了大量时间。 强大的开发者社区：由于Ubuntu是机器人领域使用最多的操作系统，因此有庞大的支持网络可用于故障排除、教程和协作。 2. 针对嵌入式和边缘设备进行优化 并非所有机器人系统都是大型工业机器——许多现代机器人是需要轻量级和高效软件的小型嵌入式设备。Ubuntu Core是Ubuntu的最小化版本，专为边缘计算和嵌入式机器人技术而优化。 基于事务的更新：Ubuntu Core提供自动、故障安全的更新，确保机器人系统保持最新状态，而不会有破坏功能的风险。 注重安全的设计：Ubuntu Core包含内置的安全功能，如应用程序沙箱和验证启动机制，这对于在敏感环境中运行的机器人至关重要。 低系统资源占用：凭借其轻量级的特性，Ubuntu Core能在小型机器人硬件上高效运行，包括树莓派（Raspberry Pi）、NVIDIA Jetson和定制AI板卡。 3. 安全性与长期维护 安全性是机器人技术中的一个主要问题，尤其是在医疗和国防等行业。Ubuntu背后的公司Canonical提供扩展安全维护（ESM），确保基于Ubuntu的机器人系统获得长期的安全更新。 定期安全补丁：这可以防止可能被黑客利用的漏洞，使Ubuntu成为机器人项目最安全的选择之一。 行业采用：许多航空航天、汽车和工业自动化公司因其安全优先的方法而信任Ubuntu。 4. 硬件兼容性与行业采用 Ubuntu支持广泛的硬件，从AI驱动的机械臂到自动驾驶无人机。无论你是在开发工业机器人还是个人助理机器人，Ubuntu都为大量的传感器、执行器和计算单元提供驱动程序、库和支持。 可与流行的硬件平台配合使用，例如： NVIDIA Jetson AI驱动的机器人套件 树莓派（用于小型机器人项目） Intel RealSense（用于3D深度感应机器人） 定制的基于ARM的机器人系统 因为Ubuntu是一个开源操作系统，制造商也可以为其特定的机器人应用定制内核并进行优化。 Ubuntu机器人技术的最新发展（2025年） 过去一年，Ubuntu的机器人技术生态系统取得了显著进步。以下是2025年一些最激动人心的更新： 1. [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/best-linux-os-for-robotics-in-2025-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/08/17/best-linux-os-for-robotics-in-2025">本文永久链接</a> &#8211; https://tonybai.com/2025/08/17/best-linux-os-for-robotics-in-2025</p>
<p>大家好，我是Tony Bai。</p>
<p>如果你正投身于机器人技术领域，选择正确的操作系统至关重要。随着人工智能、自动化和机器学习的进步，机器人正变得前所未有的复杂。在为这些智能机器提供动力方面，Linux凭借其开源的灵活性、稳定性以及对机器人框架的广泛支持，仍然是首选。</p>
<p>在本文中，我们将探讨2025年最佳的机器人Linux操作系统，帮助你为你的项目找到完美的发行版——无论你是从事工业自动化、人工智能驱动的机器人技术，还是业余爱好者的创作。我们还将介绍专注于机器人的Linux发行版的最新发展，让你保持领先。</p>
<h2>1. Ubuntu机器人操作系统</h2>
<p>机器人技术正以前所未有的速度发展，改变着医疗、自动化、制造乃至太空探索等行业。任何机器人系统的基础都是其操作系统，它决定了系统的效率、安全性和性能。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/best-linux-os-for-robotics-in-2025-2.png" alt="" /><br />
<center>Ubuntu机器人操作系统</center></p>
<p>截至2025年，Ubuntu已成为机器人领域的最佳Linux操作系统。凭借其与机器人操作系统（ROS）的无缝集成、优化的实时性能以及对AI驱动机器人技术的扩展支持，Ubuntu成为开发者、研究人员和行业的首选。</p>
<h3>为什么Ubuntu是机器人领域的最佳Linux操作系统</h3>
<p>Ubuntu在机器人领域的主导地位并非偶然——它建立在多年的持续发展和强大的社区支持之上。以下是Ubuntu脱颖而出的一些关键原因：</p>
<p><strong>1. 与ROS（机器人操作系统）的无缝集成</strong></p>
<p>ROS已成为使用最广泛的机器人中间件，提供了一系列工具和库，帮助开发者构建复杂的机器人应用程序。由于ROS最初就是为Ubuntu设计的，因此集成非常无缝。</p>
<ul>
<li>ROS 2与Ubuntu：到2025年，Ubuntu为ROS 2提供了内置支持，ROS 2提供了实时功能、安全增强和对多机器人系统更好的支持。</li>
<li>预装ROS软件包：Ubuntu通过预配置的软件包简化了ROS的安装，为开发者节省了大量时间。</li>
<li>强大的开发者社区：由于Ubuntu是机器人领域使用最多的操作系统，因此有庞大的支持网络可用于故障排除、教程和协作。</li>
</ul>
<p><strong>2. 针对嵌入式和边缘设备进行优化</strong></p>
<p>并非所有机器人系统都是大型工业机器——许多现代机器人是需要轻量级和高效软件的小型嵌入式设备。Ubuntu Core是Ubuntu的最小化版本，专为边缘计算和嵌入式机器人技术而优化。</p>
<ul>
<li>基于事务的更新：Ubuntu Core提供自动、故障安全的更新，确保机器人系统保持最新状态，而不会有破坏功能的风险。</li>
<li>注重安全的设计：Ubuntu Core包含内置的安全功能，如应用程序沙箱和验证启动机制，这对于在敏感环境中运行的机器人至关重要。</li>
<li>低系统资源占用：凭借其轻量级的特性，Ubuntu Core能在小型机器人硬件上高效运行，包括树莓派（Raspberry Pi）、NVIDIA Jetson和定制AI板卡。</li>
</ul>
<p><strong>3. 安全性与长期维护</strong></p>
<p>安全性是机器人技术中的一个主要问题，尤其是在医疗和国防等行业。Ubuntu背后的公司Canonical提供扩展安全维护（ESM），确保基于Ubuntu的机器人系统获得长期的安全更新。</p>
<ul>
<li>定期安全补丁：这可以防止可能被黑客利用的漏洞，使Ubuntu成为机器人项目最安全的选择之一。</li>
<li>行业采用：许多航空航天、汽车和工业自动化公司因其安全优先的方法而信任Ubuntu。</li>
</ul>
<p><strong>4. 硬件兼容性与行业采用</strong></p>
<p>Ubuntu支持广泛的硬件，从AI驱动的机械臂到自动驾驶无人机。无论你是在开发工业机器人还是个人助理机器人，Ubuntu都为大量的传感器、执行器和计算单元提供驱动程序、库和支持。</p>
<p>可与流行的硬件平台配合使用，例如：</p>
<ul>
<li>NVIDIA Jetson AI驱动的机器人套件</li>
<li>树莓派（用于小型机器人项目）</li>
<li>Intel RealSense（用于3D深度感应机器人）</li>
<li>定制的基于ARM的机器人系统</li>
</ul>
<p>因为Ubuntu是一个开源操作系统，制造商也可以为其特定的机器人应用定制内核并进行优化。</p>
<h3>Ubuntu机器人技术的最新发展（2025年）</h3>
<p>过去一年，Ubuntu的机器人技术生态系统取得了显著进步。以下是2025年一些最激动人心的更新：</p>
<p><strong>1. 针对机器人技术的实时内核增强</strong></p>
<p>实时性能在机器人技术中至关重要，微秒之差可能决定机器人是平稳运行还是彻底失败。2025年，Ubuntu引入了改进的实时内核支持，确保机器人应用满足低延迟处理要求。</p>
<ul>
<li>更快的响应时间：改进后的内核确保机器人的运动和决策能够无延迟地发生。</li>
<li>为多任务机器人提供更好的调度：对于同时执行多项操作的工业机器人非常有用。</li>
<li>增强的稳定性：减少机器人功能中的意外崩溃和延迟。</li>
</ul>
<p><strong>2. AI与机器学习集成</strong></p>
<p>现代机器人依赖于AI驱动的决策，Ubuntu已采取重要措施来优化机器人在机器学习方面的能力。</p>
<ul>
<li>内置的AI库，如TensorFlow、PyTorch和OpenCV，都为Ubuntu进行了预配置。</li>
<li>ROS 2现在包含了基于AI的运动规划和计算机视觉改进。</li>
<li>边缘AI支持：机器人可以在本地处理AI任务，而不是依赖云计算，从而减少延迟并改善实时决策。</li>
</ul>
<p><strong>3. 扩展对机器人硬件的支持</strong></p>
<p>Ubuntu已扩大其硬件支持范围，包括更多的工业机械臂、自动驾驶车辆和人形机器人。开发者现在可以将Ubuntu用于更广泛的机器人组件，包括：</p>
<ul>
<li>用于自动驾驶机器人的LIDAR传感器</li>
<li>用于云连接机器人的5G连接支持</li>
<li>用于基于感知的机器人的高级摄像头和深度感应模块</li>
</ul>
<p>通过这种扩展的兼容性，Ubuntu可以加快机器人应用程序的原型设计和部署。</p>
<p><strong>机器人社区对Ubuntu的评价</strong></p>
<p>机器人社区因其可靠性、灵活性和强大的开发者生态系统而广泛接受<a href="https://ubuntu.com/robotics">Ubuntu</a>。</p>
<ul>
<li>许多机器人专家认为精通Linux是必备技能，因为大多数机器人工具都是为Ubuntu构建的。</li>
<li>在Reddit和Stack Overflow等论坛的讨论中，经常强调Ubuntu相比其他操作系统选项提供了更好的支持、库和长期稳定性。</li>
<li>NASA、特斯拉和波士顿动力等公司都使用Ubuntu进行机器人研究和开发。</li>
</ul>
<p><strong>Ubuntu是机器人技术的未来</strong></p>
<p>凭借以下优势，Ubuntu已在2025年牢固确立了其作为最佳机器人Linux操作系统的地位：</p>
<ul>
<li>无缝的ROS 2集成</li>
<li>支持实时计算</li>
<li>AI和机器学习优化</li>
<li>增强的安全性和长期维护</li>
<li>广泛的行业采用</li>
</ul>
<p>无论你是在构建自动驾驶无人机、工业机器人，还是以研究为中心的AI驱动机器人系统，Ubuntu都为成功提供了最佳基础。</p>
<p>如果你计划进入机器人领域，学习Ubuntu、ROS和AI驱动的机器人开发是你能做出的最明智的决定。</p>
<h2>2. Debian机器人操作系统</h2>
<p>在快速发展的机器人世界中，选择正确的操作系统可以决定一个项目的成败。机器人工程师、研究人员和爱好者需要一个不仅稳定可靠，而且配备最新工具和库以支持开发的操作系统。在2025年，Debian机器人操作系统已成为机器人领域最佳的基于Linux的操作系统，提供了无与伦比的稳定性、灵活性和尖端软件支持的组合。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/best-linux-os-for-robotics-in-2025-3.png" alt="" /><br />
<center>Debian机器人操作系统</center></p>
<h3>为什么选择Debian用于机器人技术？</h3>
<p>Debian长期以来以其对自由和开源软件的承诺而闻名，这使其成为机器人开发者的一个有吸引力的选择。与专有系统不同，Debian确保了对庞大工具库的无限制访问，允许开发者在没有许可限制的情况下进行实验、创新和协作。</p>
<p>以下是Debian在2025年成为机器人领域首选Linux发行版的原因：</p>
<ul>
<li>稳定性和可靠性：Debian以其严格的测试过程而闻名。每个稳定版本都经过广泛审查，确保机器人应用程序平稳、一致地运行。</li>
<li>全面的软件包仓库：Debian维护着最大的软件仓库之一，其中包括数千个专门为机器人应用设计的软件包。</li>
<li>社区支持：一个强大而活跃的Debian社区为持续的改进、错误修复和功能增强做出贡献，使机器人开发者更容易解决问题和改进他们的项目。</li>
<li>安全性和性能：Debian增强的安全功能确保机器人系统免受潜在威胁，这在工业自动化和自主系统等关键应用中尤为重要。</li>
</ul>
<p><strong>与ROS的无缝集成</strong></p>
<p>机器人操作系统（ROS）是现代机器人开发的支柱。它提供了必要的工具、库和驱动程序，帮助开发者高效地创建复杂的机器人应用程序。Debian与ROS的深度集成确保了无缝的开发体验，允许用户在没有兼容性问题的情况下利用ROS的功能。</p>
<p>Debian的包管理系统使安装ROS变得简单直接。Debian科学团队积极维护一个专门用于机器人相关软件包的仓库，确保用户始终能访问到最新版本的基本工具。</p>
<p>对于那些从事高级机器人系统开发的开发者来说，Debian对ROS 2（ROS的下一代版本）的支持确保了与更新框架的兼容性、增强的实时性能和改进的安全功能。</p>
<h3>Debian机器人技术的最新发展</h3>
<p>Debian机器人技术在2025年持续发展，取得了显著进步。以下是一些最新的更新：</p>
<p><strong>1. 扩展的机器人软件包仓库</strong></p>
<p>Debian科学团队一直在积极扩展机器人软件包仓库。此次更新包括了流行工具的新的和改进的版本，例如：</p>
<ul>
<li>Gazebo – 一款强大的仿真工具，用于在虚拟环境中测试机器人应用。</li>
<li>MoveIt! – 一个广泛用于机械臂和操纵器的运动规划框架。</li>
<li>OpenCV – 这个计算机视觉库的最新版本现已针对机器人应用中的更佳性能进行了优化。</li>
<li>Navigation Stack – 升级的模块，用于改进自主机器人的路径规划和避障功能。</li>
</ul>
<p>通过这些更新，开发者无需安装第三方仓库即可访问最前沿的工具。</p>
<p><strong>2. 实时内核支持</strong></p>
<p>实时处理对于机器人技术至关重要，精确的计时和快速的响应率是必不可少的。Debian现在正式支持实时Linux内核（RT-PREEMPT），允许开发者以最小的延迟运行对时间敏感的机器人应用程序。</p>
<p>这项更新对于工业机器人、机器人手术和自主无人机尤其有益，因为在这些领域，即使是毫秒级的延迟也可能导致严重问题。</p>
<p><strong>3. 增强的安全功能</strong></p>
<p>随着机器人更多地融入工业和智能环境，安全风险也随之增加。作为回应，Debian为机器人系统引入了先进的安全功能，包括：</p>
<ul>
<li>强制访问控制（MAC） – 强制执行严格的安全策略，以防止对机器人系统的未授权访问。</li>
<li>安全启动支持 – 确保只有经过验证和信任的软件才能在机器人硬件上运行。</li>
<li>自动安全更新 – 实时保护机器人应用免受漏洞和新兴威胁的侵害。</li>
</ul>
<p>凭借这些增强功能，Debian机器人操作系统现在成为依赖机器人进行自动化、医疗和国防的行业的一个更安全的选择。</p>
<p><strong>社区与支持</strong></p>
<p>Debian最大的优势之一是其社区驱动的开发模式。与专有机器人软件不同，Debian受益于全球数千名开发者和研究人员对其改进的贡献。Debian科学邮件列表、论坛和Git仓库是宝贵的资源，用户可以在这些地方讨论问题、分享解决方案和协作项目。</p>
<p>Debian科学团队还确保Debian机器人操作系统与最新的技术进步保持同步，使初学者和专家都能更容易地开始机器人开发。</p>
<h3>为什么在2025年选择Debian机器人操作系统？</h3>
<p><a href="https://wiki.debian.org/DebianScience/Robotics/ROS">Debian机器人操作系统</a>不仅仅是一个操作系统；它是一个生态系统，使开发者、研究人员和企业能够充满信心地构建先进的机器人系统。从其无缝的ROS集成和实时内核支持，到其强大的安全功能和广泛的软件包仓库，Debian为2025年的机器人开发提供了一切所需。</p>
<p>无论你是从事自主机器人、工业自动化还是AI驱动的机器人应用，Debian机器人操作系统都提供了一个稳定、安全和强大的基础，以构建机器人技术的未来。</p>
<p>你在项目中使用Debian机器人操作系统吗？在下面的评论中分享你的想法和经验吧！</p>
<h2>3. 基于ROS的发行版 (ROS 2)</h2>
<p>机器人操作系统（ROS）一直是机器人行业的变革者，为开发机器人应用程序提供了一个强大而灵活的框架。多年来，ROS 2已发展成为致力于尖端机器人解决方案的开发者、研究人员和公司的首选。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/best-linux-os-for-robotics-in-2025-4.png" alt="" /><br />
<center>基于ROS的发行版 (ROS 2)</center></p>
<p>当我们进入2025年，ROS 2发行版已经成熟，提供了改进的实时能力、增强的安全性和更广泛的兼容性。如果你正在寻找最佳的机器人Linux操作系统，本指南将带你了解最新的ROS 2发行版、它们的特性以及运行它们的理想Linux发行版。</p>
<h3>理解ROS 2发行版</h3>
<p>ROS 2发行版是ROS 2框架的定期发布版本，包含了最新的改进、安全补丁和功能升级。</p>
<p>每个发行版都有一个定义的生命周期，通常每两年提供一次长期支持（LTS），而非LTS版本则作为实验性功能的测试平台。选择正确的ROS 2发行版取决于项目稳定性要求、硬件兼容性和功能需求等因素。</p>
<p><strong>2025年的关键ROS 2发行版</strong></p>
<p><strong>1. Jazzy Jalisco (LTS) – 2024年5月23日发布</strong></p>
<p>最新的LTS版本<a href="https://docs.ros.org/en/rolling/Releases/Release-Jazzy-Jalisco.html">Jazzy Jalisco</a>，将获得五年的支持，使其成为工业应用和长期项目的最佳选择。它引入了：</p>
<ul>
<li>针对时间敏感机器人操作的先进实时能力</li>
<li>通过加密通信和认证功能增强的安全性</li>
<li>扩展了对不同硬件平台的兼容性</li>
<li>更好的中间件支持，以提高性能和可伸缩性</li>
</ul>
<p><strong>2. Iron Irwini (非LTS) – 2023年5月23日发布</strong></p>
<p>虽然<a href="https://docs.ros.org/en/iron/Releases/Release-Iron-Irwini.html">Iron Irwini</a>不是LTS版本（支持期1.5年），但它充当了新创新的试验场。希望尝试尖端机器人功能的开发者可以从中受益：</p>
<ul>
<li>更快的开发周期和频繁的更新</li>
<li>实验性的中间件改进</li>
<li>提前接触可能包含在未来LTS版本中的功能</li>
</ul>
<p><strong>3. Humble Hawksbill (LTS) – 2022年5月23日发布</strong></p>
<p><a href="https://docs.ros.org/en/foxy/Releases/Release-Humble-Hawksbill.html">Humble Hawksbill</a>在2025年仍然是一个受欢迎的选择，因为它将获得支持直到2027年。它在以下方面发挥了关键作用：</p>
<ul>
<li>改进中间件通信协议</li>
<li>改进工具和调试能力</li>
<li>在基于ARM的平台上有更好的性能</li>
</ul>
<p>对于在Humble上启动的项目，迁移到Jazzy Jalisco可以确保长期稳定性。</p>
<h3>ROS 2的最新发展（2025年）</h3>
<p>ROS 2持续发展，为机器人技术生态系统带来了几项关键改进：</p>
<p><strong>1. 实时支持</strong></p>
<p>凭借改进的实时调度，ROS 2现在可以处理更复杂的机器人任务，并具有确定性的性能。</p>
<p><strong>2. 安全性增强</strong></p>
<p>通过安全的通信协议和更好的认证机制，ROS 2现在比以往任何时候都更安全，解决了工业机器人和自动驾驶汽车中的安全问题。</p>
<p><strong>3. 跨平台兼容性</strong></p>
<p>虽然Ubuntu仍然是主要的操作系统，但ROS 2已将其支持扩展到Debian、Fedora、Windows甚至macOS。</p>
<p><strong>4. 更好的中间件性能</strong></p>
<p>DDS（数据分发服务）中的中间件改进增强了大型机器人系统中的延迟、可靠性和可伸缩性。</p>
<p>随着机器人技术的不断进步，ROS 2仍然是行业领先的框架，其中Jazzy Jalisco（LTS）是2025年的首选。</p>
<p>对于ROS 2的最佳Linux操作系统，Ubuntu 22.04 LTS作为最稳定和得到最广泛支持的选项脱颖而出。然而，开发者也可以灵活选择Debian、Fedora和Arch Linux。</p>
<p>随着在实时性能、安全性和跨平台支持方面的持续改进，ROS 2正在塑造2025年及以后机器人技术的未来。保持对最新发展的了解，可以确保你的机器人项目保持未来竞争力。</p>
<h2>4. Fedora机器人操作系统</h2>
<p>在不断发展的机器人世界中，选择正确的操作系统对于无缝开发和部署至关重要。截至2025年，Fedora机器人操作系统凭借其专门的工具、强大的社区支持和对开源原则的承诺，已成为机器人领域最强大的Linux发行版之一。无论你是尝试自主机器人的业余爱好者，还是开发工业自动化解决方案的专业人士，Fedora机器人操作系统都提供了一个量身定制的综合平台，以满足你的需求。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/best-linux-os-for-robotics-in-2025-5.png" alt="" /><br />
<center>Fedora机器人操作系统</center></p>
<h3>为什么Fedora机器人操作系统脱颖而出</h3>
<p>Fedora机器人操作系统是Fedora项目的一个专门分支，专为机器人专家设计。它提供了一套精心策划的软件包，涵盖了机器人技术的各个方面，从仿真到硬件接口。以下是Fedora机器人操作系统在2025年广受欢迎的关键原因：</p>
<p><strong>1. 全面的软件套件</strong></p>
<p>Fedora机器人操作系统包含一套广泛的预装软件包，使开发者可以轻松上手，而无需花费数小时设置环境。Fedora机器人操作系统中的一些核心工具包括：</p>
<ul>
<li>Gazebo – 一款强大的3D机器人模拟器，使开发者能够在虚拟环境中测试机器人应用程序。</li>
<li>OpenCV – 广泛用于图像处理和机器学习任务的计算机视觉库。</li>
<li>Arduino IDE – 用于编程微控制器的流行开发环境。</li>
<li>Player/Stage – 在学术界和研究中广泛使用的仿真工具。</li>
<li>Gazebo, V-REP, and Webots – 先进的机器人仿真软件，用于训练AI模型和在虚拟环境中测试算法。</li>
</ul>
<p><strong>2. 与ROS（机器人操作系统）的无缝集成</strong></p>
<p>Fedora机器人操作系统的最大优势之一是其与ROS的无缝集成，ROS是使用最广泛的机器人软件框架。ROS提供了一些基本服务，例如：</p>
<ul>
<li>硬件抽象 – 使控制传感器、电机和执行器变得更加容易。</li>
<li>底层设备控制 – 提供对机器人硬件组件的直接访问。</li>
<li>进程间通信 – 促进不同机器人模块和进程之间的无缝通信。</li>
</ul>
<p>Fedora机器人操作系统预配置了最新版本的ROS 2，确保与尖端的机器人应用兼容。这种集成使开发者能够利用广泛的ROS生态系统，包括库、驱动程序和可视化工具。</p>
<p><strong>3. 强大的社区支持</strong></p>
<p>Fedora机器人操作系统得益于一个由开发者、研究人员和机器人爱好者组成的活跃社区。Fedora机器人特别兴趣小组（SIG）致力于确保Fedora用户能够获得最新的机器人软件和更新。该小组积极维护Fedora的机器人软件包，提供教程，并帮助用户解决问题。</p>
<h3>Fedora机器人技术的最新发展</h3>
<p>Fedora机器人团队一直积极地将该领域的最新进展融入其中。2025年一些最显著的更新包括：</p>
<p><strong>1. 增强的仿真工具</strong></p>
<p>仿真在机器人开发中起着至关重要的作用，它允许开发者在物理机器人上部署算法之前进行测试。Fedora机器人操作系统通过集成以下内容显著改善了其仿真能力：</p>
<ul>
<li>Ignition Gazebo – 一款提供高保真物理和传感器仿真的高级模拟器。</li>
<li>AI驱动的仿真环境 – 支持基于机器学习的仿真，机器人可以在其中学习并适应环境。</li>
</ul>
<p><strong>2. 改进的硬件支持</strong></p>
<p>随着机器人硬件的迅速扩展，Fedora机器人操作系统已包括对以下内容的支持：</p>
<ul>
<li>新的机器人传感器和执行器 – 确保软件和硬件组件之间的无缝通信。</li>
<li>树莓派和Jetson Nano优化 – Fedora机器人操作系统现在在低功耗硬件上运行更高效，非常适合DIY机器人项目。</li>
<li>扩展的驱动程序支持 – Fedora机器人操作系统现在包括用于机械臂、激光雷达传感器和人形机器人的额外驱动程序。</li>
</ul>
<p><strong>3. 教育资源和教程</strong></p>
<p>了解到机器人技术对初学者可能具有挑战性，Fedora机器人操作系统在教育资源上投入了大量资金。这些资源包括：</p>
<ul>
<li>分步教程 – 涵盖从设置开发环境到编程机器人运动的所有内容。</li>
<li>交互式学习模块 – 用户可以在虚拟训练环境中练习为不同的机器人任务编写代码。</li>
<li>在线社区论坛和黑客马拉松 – 为开发者提供协作、学习和分享见解的空间。</li>
</ul>
<p><strong>为什么开发者更喜欢Fedora机器人操作系统而非其他Linux发行版</strong></p>
<p>机器人社区经常争论用于开发的最佳操作系统。虽然像Ubuntu和Debian这样的其他Linux发行版被广泛使用，但Fedora机器人操作系统具有明显的优势：</p>
<ul>
<li>最新的内核和软件包 – Fedora以跟上最新技术而闻名，确保开发者能够访问尖端功能。</li>
<li>为性能和安全优化 – Fedora的安全特性使其成为工业和研究应用的首选。</li>
<li>使用DNF实现无缝包管理 – Fedora的包管理系统效率高，减少了在其他发行版中经常遇到的依赖问题。</li>
</ul>
<p>此外，基于Linux的操作系统通常比Windows更受机器人开发者的青睐，因为它们提供：</p>
<ul>
<li>更好地控制操作系统功能 – 直接访问系统资源。</li>
<li>更简便的依赖管理 – 简化了机器人库的安装。</li>
<li>开源的灵活性 – 可根据项目需求进行完全定制。</li>
</ul>
<p><a href="https://fedoraproject.org/wiki/Robotics">Fedora机器人操作系统</a>无疑是2025年最佳的机器人Linux发行版之一。凭借其广泛的软件套件、强大的ROS集成、改进的硬件支持和活跃的社区，它为机器人专家开发、测试和部署他们的项目提供了一个理想的环境。</p>
<p>随着机器人技术的不断发展，Fedora机器人操作系统仍然致力于走在创新的前沿，使其成为有抱负的和专业的机器人专家的首选。如果你正在寻找一个强大、可靠且面向未来的机器人Linux操作系统，Fedora机器人操作系统是完美的选择。</p>
<h2>5. 用于机器人的OpenEmbedded Linux (Yocto)</h2>
<p>机器人领域正以前所未有的速度发展，人工智能、自动化和边缘计算的进步推动了对强大且可定制的操作系统的需求。在2025年，由Yocto项目驱动的OpenEmbedded Linux，作为机器人领域最佳的基于Linux的操作系统之一脱颖而出。它提供灵活性、可扩展性和优化性能的能力，使其成为从事机器人应用的开发者的首选。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/best-linux-os-for-robotics-in-2025-6.png" alt="" /><br />
<center>用于机器人的OpenEmbedded Linux (Yocto)</center></p>
<p>如果你参与机器人开发——无论是在工业自动化、自动驾驶汽车、无人机还是AI驱动的机器人系统中——了解OpenEmbedded Linux和Yocto的能力将至关重要。在这篇博文中，我们将深入探讨OpenEmbedded Linux（Yocto）如何成为机器人的理想操作系统，探索其最新发展，并讨论其对机器人行业的影响。</p>
<h3>什么是OpenEmbedded Linux和Yocto项目？</h3>
<p><strong>OpenEmbedded Linux</strong></p>
<p>OpenEmbedded是一个开源的构建框架和交叉编译环境，专为创建针对嵌入式设备的Linux发行版而设计。与Ubuntu或Fedora等通用Linux发行版不同，OpenEmbedded允许开发者专门为他们的硬件和应用需求定制和优化他们的Linux构建。</p>
<p><strong>Yocto项目</strong></p>
<p>Yocto项目由Linux基金会于2010年发起，是一个与OpenEmbedded协同工作的合作项目，旨在简化和标准化为嵌入式和物联网设备定制Linux发行版的开发。以BitBake为核心构建系统，Yocto项目为开发者提供工具、模板和最佳实践，以创建最小化、高效且针对硬件优化的基于Linux的操作系统。</p>
<p>对于机器人开发者来说，OpenEmbedded和Yocto项目的结合使他们能够创建为机器人应用量身定制的轻量、快速且针对特定硬件的Linux发行版。</p>
<h3>为什么OpenEmbedded Linux (Yocto)是2025年机器人的理想选择</h3>
<p><strong>高度定制化与模块化</strong></p>
<ul>
<li>与传统的Linux发行版（预装了软件和功能）不同，OpenEmbedded Linux让开发者可以构建一个只包含其机器人系统所需内容的发行版。</li>
<li>这种模块化的方法确保了一个优化且轻量级的操作系统，从而提升性能。</li>
</ul>
<p><strong>硬件抽象与兼容性</strong></p>
<ul>
<li>机器人项目通常涉及各种各样的硬件组件，从传感器和执行器到专用处理器和AI加速器。</li>
<li>OpenEmbedded的基于层的结构使开发者能够创建板级支持包（BSP），从而可以轻松地与不同的硬件架构集成。</li>
</ul>
<p><strong>长期支持与安全性</strong></p>
<ul>
<li>Yocto项目定期发布带有安全补丁的LTS（长期支持）版本，使其成为机器人应用的一个安全稳定的选择。</li>
<li>安全性是机器人技术中的一个主要问题，尤其是在自主系统和工业自动化中，而OpenEmbedded Linux提供了安全启动、内核加固和访问控制策略等功能。</li>
</ul>
<p><strong>更好的资源效率</strong></p>
<ul>
<li>机器人应用通常在低功耗和资源受限的硬件上运行。</li>
<li>OpenEmbedded Linux允许开发者创建极简的Linux构建，减少系统开销并最大化效率。</li>
</ul>
<p><strong>强大的社区与行业采用</strong></p>
<ul>
<li>Yocto项目得到了嵌入式Linux社区和英特尔、高通、恩智浦和德州仪器等主要行业参与者的强力支持。</li>
<li>这意味着为机器人开发者提供了持续的改进、广泛的文档和长期的可靠性。</li>
</ul>
<h3>OpenEmbedded Linux在机器人技术领域的最新发展（2025年）</h3>
<p><strong>1. 上游Linux对机器人硬件的支持</strong></p>
<p>在2025年，像Linaro和高通这样的公司通过将对高通机器人RB5等机器人平台的支持上游化，为OpenEmbedded Linux做出了重大贡献。这一发展确保了下一代机器人系统更好的兼容性、实时处理和AI集成。</p>
<p><strong>2. 改进的培训与学习资源</strong></p>
<p>随着基于Yocto的Linux系统的日益普及，Bootlin和Yocto项目社区等组织推出了新的培训项目、研讨会和在线课程。这些资源使开发者更容易为机器人项目学习、实施和优化OpenEmbedded Linux。</p>
<p><strong>3. 扩展的AI与机器学习能力</strong></p>
<p>OpenEmbedded Linux在集成AI和机器学习框架（如TensorFlow Lite和ROS 2（机器人操作系统））方面取得了重大改进。这使得机器人系统能够执行边缘AI推理、实时决策和高级自动化。</p>
<p><strong>4. 全行业采用与标准化</strong></p>
<p>许多机器人公司和研究机构已转向使用基于Yocto的Linux发行版作为其嵌入式机器人平台。这一转变正在帮助创建一个更加标准化的软件生态系统，减少碎片化并改善机器人设备间的兼容性。</p>
<h3>OpenEmbedded Linux在机器人技术中的应用</h3>
<p><strong>工业自动化</strong></p>
<p>OpenEmbedded Linux正在为需要高性能计算、实时处理和强大安全功能的新一代自动化制造机器人提供动力。</p>
<p><strong>自动驾驶汽车与无人机</strong></p>
<p>机器人公司正在使用基于Yocto的Linux来开发自主无人机和自动驾驶汽车，确保低延迟通信和AI驱动的导航。</p>
<p><strong>医疗机器人</strong></p>
<p>医疗机器人，如手术机器人和康复设备，受益于OpenEmbedded Linux提供安全、实时和稳定操作系统环境的能力。</p>
<p><strong>AI驱动的家庭与服务机器人</strong></p>
<p>智能助手、配送机器人和其他AI驱动的机器人解决方案利用OpenEmbedded Linux进行定制化的AI模型和实时的语音/图像处理。</p>
<h3>OpenEmbedded Linux是2025年最佳的机器人操作系统吗？</h3>
<p>随着机器人行业的不断扩大，对可定制、轻量级和高性能操作系统的需求比以往任何时候都更加关键。由Yocto项目驱动的OpenEmbedded Linux无疑是2025年机器人领域最佳的Linux操作系统。</p>
<p>其提供针对特定硬件的优化、实时处理、安全性和AI集成的能力，使其成为全球机器人专家、工程师和开发者的首选。随着持续的进步和行业采用，OpenEmbedded Linux必将在未来几年塑造机器人技术的未来。</p>
<p>如果你正在开发一个机器人项目，并且需要一个可扩展且高效的Linux操作系统，那么OpenEmbedded Linux (Yocto)是2025年的最佳选择。</p>
<p><strong>下一步是什么？</strong></p>
<p>探索OpenEmbedded Linux：<a href="https://www.openembedded.org/wiki/Main_Page">https://www.openembedded.org/wiki/Main&#95;Page</a></p>
<p>了解更多关于Yocto项目的信息：<a href="https://techrefreshing.com/best-linux-os-for-robotics-in-2025/www.yoctoproject.org">www.yoctoproject.org</a></p>
<p>开始开发：<a href="https://docs.yoctoproject.org/">Yocto文档</a></p>
<p>你在机器人项目中使用OpenEmbedded Linux吗？在下面的评论中分享你的想法和经验吧！</p>
<h2>结论</h2>
<p>在2025年选择最佳的机器人Linux操作系统取决于你的具体需求。如果你需要一个支持良好、对初学者友好的选项，Ubuntu机器人操作系统是你的不二之选。对于稳定性和长期项目，Debian机器人操作系统是一个绝佳的选择。那些从事AI驱动或实验性机器人技术的人应该考虑Fedora机器人操作系统，而嵌入式系统开发者可以依赖基于Yocto的Linux发行版。随着ROS 2、AI和实时内核优化的不断进步，Linux仍然是塑造机器人技术未来的首选操作系统。</p>
<h2><strong>免责声明</strong></h2>
<p>本文中的信息基于截至2025年的最新可用更新。2025年最佳的机器人Linux操作系统可能因特定的硬件、软件更新和项目要求而异。在选择操作系统之前，请务必验证其与你的机器人框架的兼容性。本文仅供参考，不构成专业建议。</p>
<p>本文翻译自文章《<a href="https://techrefreshing.com/best-linux-os-for-robotics-in-2025/">Best Linux OS for Robotics in 2025</a>》- https://techrefreshing.com/best-linux-os-for-robotics-in-2025/</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/08/17/best-linux-os-for-robotics-in-2025/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>内核之外的冰山：为什么说从零写一个操作系统已几乎不可能？</title>
		<link>https://tonybai.com/2025/08/16/brand-new-os-impossible/</link>
		<comments>https://tonybai.com/2025/08/16/brand-new-os-impossible/#comments</comments>
		<pubDate>Sat, 16 Aug 2025 00:03:53 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BSD]]></category>
		<category><![CDATA[cargo]]></category>
		<category><![CDATA[cgroups]]></category>
		<category><![CDATA[GCC]]></category>
		<category><![CDATA[Glibc]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[JS]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[libc]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[node.js]]></category>
		<category><![CDATA[open]]></category>
		<category><![CDATA[OS]]></category>
		<category><![CDATA[POSIX]]></category>
		<category><![CDATA[Printf]]></category>
		<category><![CDATA[proc]]></category>
		<category><![CDATA[Read]]></category>
		<category><![CDATA[Redox]]></category>
		<category><![CDATA[relibc]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[syscall]]></category>
		<category><![CDATA[write]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[补丁]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5040</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/08/16/brand-new-os-impossible 大家好，我是Tony Bai。 对于许多心怀浪漫主义的开发者来说，“从零开始编写一个属于自己的操作系统”，或许是技术生涯中最终极、最性感的梦想。这几乎是现代编程世界的“创世纪”，是掌控计算机每一个比特的至高权力。 然而，最近一位名为 Wildan M 的工程师，在他的一篇博文中，用一次亲身参与 Redox OS 项目的经历，给我们所有人泼了一盆冷水。他的结论简单而又颠覆： 现在，从零开始编写一个全新的、能被广泛采用的操作系统，已几乎是一项不可能完成的任务。 而其真正的难点，并非我们想象中那个神秘而复杂的内核，而在于内核之外，那座看不见的、庞大到令人绝望的“冰山”。 冰山一角：内核，那个“最简单”的部分 故事的主角是 Redox OS，一个雄心勃勃的项目。它旨在用内存安全的 Rust 语言，构建一个现代的、基于微内核架构的、可以替代 Linux 和 BSD 的完整操作系统。 当我们谈论“写一个 OS”时，我们通常指的是编写内核。那么 Redox OS 的内核有多复杂呢？文章给出了惊人的数据： * 代码量： 约 3 万行 (30k LoC)。 * 启动速度： 大多数情况下，不到 1 秒。 在短短十年间，Redox 团队已经完成了动态链接、Unix 套接字等核心功能。这无疑是令人敬佩的工程壮举。但 Wildan 指出，这仅仅是浮出水面的冰山一角。一个能启动的内核，距离一个“能用”的操作系统，还有着遥远的距离。 冰山之下：生态移植的“五层地狱” 当作者兴致勃勃地想为 Redox OS 贡献力量，尝试将一些现代程序（如 Go, Node.js, Rust [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/brand-new-os-impossible-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/08/16/brand-new-os-impossible">本文永久链接</a> &#8211; https://tonybai.com/2025/08/16/brand-new-os-impossible</p>
<p>大家好，我是Tony Bai。</p>
<p>对于许多心怀浪漫主义的开发者来说，“从零开始编写一个属于自己的操作系统”，或许是技术生涯中最终极、最性感的梦想。这几乎是现代编程世界的“创世纪”，是掌控计算机每一个比特的至高权力。</p>
<p>然而，最近一位名为 Wildan M 的工程师，在<a href="https://blog.wellosoft.net/writing-a-brand-new-os-is-almost-impossible-by-now">他的一篇博文</a>中，用一次亲身参与 Redox OS 项目的经历，给我们所有人泼了一盆冷水。他的结论简单而又颠覆：</p>
<p><strong>现在，从零开始编写一个全新的、能被广泛采用的操作系统，已几乎是一项不可能完成的任务。</strong></p>
<p>而其真正的难点，并非我们想象中那个神秘而复杂的内核，而在于内核之外，那座看不见的、庞大到令人绝望的“冰山”。</p>
<h2>冰山一角：内核，那个“最简单”的部分</h2>
<p>故事的主角是 Redox OS，一个雄心勃勃的项目。它旨在用内存安全的 Rust 语言，构建一个现代的、基于微内核架构的、可以替代 Linux 和 BSD 的完整操作系统。</p>
<p>当我们谈论“写一个 OS”时，我们通常指的是编写内核。那么 Redox OS 的内核有多复杂呢？文章给出了惊人的数据：<br />
*   <strong>代码量：</strong> 约 3 万行 (30k LoC)。<br />
*   <strong>启动速度：</strong> 大多数情况下，不到 1 秒。</p>
<p>在短短十年间，Redox 团队已经完成了动态链接、Unix 套接字等核心功能。这无疑是令人敬佩的工程壮举。但 Wildan 指出，这仅仅是浮出水面的冰山一角。一个能启动的内核，距离一个“能用”的操作系统，还有着遥远的距离。</p>
<h2>冰山之下：生态移植的“五层地狱”</h2>
<p>当作者兴致勃勃地想为 Redox OS 贡献力量，尝试将一些现代程序（如 Go, Node.js, Rust 编译器）移植上去时，他才真正撞上了那座隐藏在水面之下的巨大冰山。</p>
<p>一个现代操作系统之所以“能用”，是因为它能运行我们日常使用的所有软件。而将这些软件“搬”到一个全新的操作系统上，需要闯过一重又一重难关。</p>
<p><strong>第一层：系统调用 (Syscall) 的鸿沟</strong></p>
<p>这是最底层的障碍。每个操作系统都有自己的一套与硬件和内核交互的“语言”，即系统调用。Redox OS 的 syscall 与我们熟知的 Linux 完全不同。这意味着，任何需要与内核打交道的程序（几乎是所有程序），都必须重写这部分逻辑，告诉它如何在新世界里“说话”。</p>
<p><strong>第二层：libc 的重担</strong></p>
<p>为了不让每个程序都去痛苦地学习 syscall 这门“方言”，操作系统通常会提供一个标准的“翻译官”——C 标准库 (libc)。它将复杂的 syscall 封装成开发者熟悉的函数（如 printf, open, read）。因此，一个新 OS 的核心任务之一，就是自己实现一个兼容的 libc。Redox 为此用 Rust 实现了一个名为 relibc 的项目，其工程量之浩大可想而知。</p>
<p><strong>第三层：POSIX 的“几乎兼容”陷阱</strong></p>
<p>即便新 OS 像 Redox 一样，努力兼容 POSIX 这个通用标准，噩梦也远未结束。因为无数现有的软件，早已深度依赖于 Linux 特有的、非 POSIX 的功能，比如解析 /proc 文件系统、操作 cgroups 等。结果就是，即使有了 relibc，你依然需要为这些软件挨个打上无数的“补丁”。文章提到，仅 Redox OS 的官方“软件食谱 (Cookbook)”中，就包含了<strong>约 70 个</strong>这样的补丁。</p>
<p><strong>第四层：编译器的“先有鸡还是先有蛋”</strong></p>
<p>你想在新 OS 上原生编译软件吗？那你首先需要一个能在这个 OS 上运行的编译器，比如 GCC、Rustc 或 Go 编译器。但问题是，移植编译器本身，就是所有软件移植任务中最复杂、最艰巨的一种。它需要处理极其底层的二进制格式、链接方式和系统调用。这形成了一个经典的“鸡生蛋还是蛋生鸡”的困局。</p>
<p><strong>第五层：语言生态的“次元壁”</strong></p>
<p>如果说移植 C 语言程序还只是“困难模式”，那么移植那些拥有自己庞大生态的现代语言程序（如 Rust, Go, Node.js），则是“地狱模式”。这些语言的包管理器（如 Cargo, Go Modules）会从中央仓库下载海量依赖，你很难像修改 C 代码一样，通过一个简单的 .patch 文件来修复所有问题。唯一的办法，往往是去 fork 无数个核心依赖库，然后逐一修改，这几乎是一项不可能完成的任务。</p>
<h2>小结：生态，才是那座无法逾越的山</h2>
<p>当 Wildan 经历过这一切后，他得出了文章开头的那个结论。</p>
<p>一个操作系统的成功，或许 <strong>20% 在于内核的精巧，而 80% 在于其上能否运行用户想要的所有软件。</strong> 后者，那个由编译器、标准库、第三方包、应用软件共同构成的庞大生态，才是真正的、几乎无法被复制的“护城河”。</p>
<p>这就像建造一座城市。你可以设计出最宏伟、最先进的市政厅（内核），但如果没有配套的道路、水电、学校、医院、商店（软件生态），这座城市就永远只是一座无法住人的“鬼城”。</p>
<p>这篇文章并非是要劝退所有对底层技术抱有热情的开发者。正如作者所说，如果你想<strong>学习</strong>，从零开始或加入 Redox 这样的项目，会是一段极其宝贵的经历。但如果你想构建一个<strong>被广泛采用</strong>的新 OS，你面对的将不仅仅是技术挑战，更是一个需要说服全球成千上万开发者为你“投票”的社会学难题。</p>
<p>这或许就是对那些仍在坚持构建新 OS 的探索者们，我们应该报以最高敬意的原因。因为他们挑战的，不仅仅是代码，更是一整个时代建立起来的软件文明。</p>
<p>资料链接：https://blog.wellosoft.net/writing-a-brand-new-os-is-almost-impossible-by-now</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/08/16/brand-new-os-impossible/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Rob Pike的“抱怨”与Go的“解药”：直面软件膨胀的四大根源</title>
		<link>https://tonybai.com/2025/04/27/rob-pike-on-bloat/</link>
		<comments>https://tonybai.com/2025/04/27/rob-pike-on-bloat/#comments</comments>
		<pubDate>Sat, 26 Apr 2025 22:57:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Bazaar]]></category>
		<category><![CDATA[Bloat]]></category>
		<category><![CDATA[Cathedral]]></category>
		<category><![CDATA[Community]]></category>
		<category><![CDATA[deps.dev]]></category>
		<category><![CDATA[FIPS]]></category>
		<category><![CDATA[FIPS-140]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[GDPR]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go.mod]]></category>
		<category><![CDATA[go.sum]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Gopher]]></category>
		<category><![CDATA[HackerNews]]></category>
		<category><![CDATA[HN]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[NIH]]></category>
		<category><![CDATA[npm]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[RobPike]]></category>
		<category><![CDATA[runtime]]></category>
		<category><![CDATA[utf-8]]></category>
		<category><![CDATA[依赖]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[分层]]></category>
		<category><![CDATA[加解密]]></category>
		<category><![CDATA[包装]]></category>
		<category><![CDATA[可预测性]]></category>
		<category><![CDATA[复杂性]]></category>
		<category><![CDATA[工具链]]></category>
		<category><![CDATA[工程效率]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[应用]]></category>
		<category><![CDATA[开发文化]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[抽象]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[框架]]></category>
		<category><![CDATA[硬件]]></category>
		<category><![CDATA[社区]]></category>
		<category><![CDATA[纪律]]></category>
		<category><![CDATA[组合]]></category>
		<category><![CDATA[继承]]></category>
		<category><![CDATA[维护成本]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[脆弱性]]></category>
		<category><![CDATA[设计哲学]]></category>
		<category><![CDATA[软件工程]]></category>
		<category><![CDATA[过度抽象]]></category>
		<category><![CDATA[运行时]]></category>
		<category><![CDATA[错误处理]]></category>
		<category><![CDATA[集市与大教堂]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4626</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/04/27/rob-pike-on-bloat 大家好，我是Tony Bai。 今年年初，Go语言之父、UTF-8编码的发明者Rob Pike的一篇题为”On Bloat”（关于膨胀）的演讲幻灯片(在2024年下旬做的)在技术圈，尤其是在Hacker News(以下简称HN)上，引发了相当热烈的讨论。Pike作为业界泰斗，其对当前软件开发中普遍存在的“膨胀”现象的犀利批评，以及对依赖管理、软件分层等问题的深刻担忧，无疑戳中了许多开发者的痛点。 HN上的讨论更是五花八门，开发者们纷纷从自身经历出发，探讨“膨胀”的定义、成因和后果。有人认为膨胀是“层层叠加的间接性”导致简单修改寸步难行；有人认为是“不必要的功能堆砌”；还有人归咎于“失控的依赖树”和“缺乏纪律的开发文化”。 那么，Rob Pike究竟在“抱怨”什么？他指出的软件膨胀根源有哪些？而作为我们Gopher，Go语言的设计哲学和工具链，能否为我们从纯技术层面提供对抗膨胀的“解药”呢？今天，我们就结合Pike的演讲精髓和HN的热议，深入聊聊软件膨胀的四大根源，并从Go的视角尝试寻找一下应对之道。 “膨胀”的真相：远不止代码大小和运行速度 在深入探讨根源之前，我们需要认识到，“膨胀”并不止是字面意义上我们理解的最终编译产物的大小或者应用的运行速度慢，Pike的观点和HN讨论中的“软件膨胀”体现在多个维度： 复杂性失控： 过度的抽象层次、复杂的依赖关系、难以理解的代码路径，使得维护和迭代变得异常困难。 维护成本剧增： 添加新功能的长期维护成本（包括理解、测试、修复Bug、处理兼容性）远超初次实现的成本，但往往被低估。 不可预测性与脆弱性： 庞大且快速变化的依赖树使得我们几乎无法完全理解和掌控软件的实际构成和行为，任何更新都可能引入未知风险。 下面我们具体看看Pike指出的“膨胀”几个核心根源： 根源一：特性 (Features) —— “有用”不等于“值得” Pike 指出，我们不断地为产品添加特性，以使其“更好”。但所有特性都会增加复杂性和成本，而维护成本是最大的那部分，远超初次实现。他警示我们要注意“有用谬论” —— 并非所有“有用”的功能都值得我们付出长期的维护代价。 HN讨论也印证了这一点：功能冗余、为了匹配竞品或满足某个高层“拍脑袋”的想法而添加功能、甚至开发者为了个人晋升而开发复杂功能的现象屡见不鲜。 技术层面：Go的“解药”在哪？ 简洁哲学： Go从设计之初就强调“少即是多”，鼓励用简单的原语组合解决问题，天然地抵制不必要的复杂性。 强大的标准库： Go 提供了功能丰富且高质量的标准库，覆盖了网络、并发、加解密、I/O 等众多领域，减少了对外部特性库的依赖。很多时候，“自己动手，丰衣足食”（使用标准库）比引入一个庞大的外部框架更符合Go的风格。 关注工程效率： Go的设计目标之一是提高软件开发（尤其是大型项目）的工程效率和可维护性，这促使Go社区更关注代码的清晰度和长期成本。 注：技术层面包括语言、工具以及设计思路和方法。 根源二：分层 (Layering) —— 在错误的层级“打补丁” Pike 认为，现代软件层层叠加（硬件 -> 内核 -> 运行时 -> 框架 -> 应用代码），当出现问题时，我们太容易在更高的层级通过包装（wrap）来“修复”问题，而不是深入底层真正解决它。这导致了层层叠叠的“创可贴”，增加了复杂性和维护难度。他列举了ChromeOS文件App的例子，并强调要在正确的层级实现功能和修复。 在HN的讨论中，有开发者描述的修改按钮颜色需要穿透17个文件和多个抽象层的例子，正是这种“错误分层”或“过度抽象”的生动体现。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/rob-pike-on-bloat-1.jpg" alt="" /></p>
<p><a href="https://tonybai.com/2025/04/27/rob-pike-on-bloat">本文永久链接</a> &#8211; https://tonybai.com/2025/04/27/rob-pike-on-bloat</p>
<p>大家好，我是Tony Bai。</p>
<p>今年年初，Go语言之父、UTF-8编码的发明者Rob Pike的一篇题为”On Bloat”（关于膨胀）的演讲幻灯片(在2024年下旬做的)在技术圈，尤其是在Hacker News(以下简称HN)上，引发了相当热烈的讨论。Pike作为业界泰斗，其对当前软件开发中普遍存在的“膨胀”现象的犀利批评，以及对依赖管理、软件分层等问题的深刻担忧，无疑戳中了许多开发者的痛点。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/rob-pike-on-bloat-2.png" alt="" /></p>
<p>HN上的讨论更是五花八门，开发者们纷纷从自身经历出发，探讨“膨胀”的定义、成因和后果。有人认为膨胀是“层层叠加的间接性”导致简单修改寸步难行；有人认为是“不必要的功能堆砌”；还有人归咎于“失控的依赖树”和“缺乏纪律的开发文化”。</p>
<p>那么，Rob Pike究竟在“抱怨”什么？他指出的软件膨胀根源有哪些？而作为我们Gopher，Go语言的设计哲学和工具链，能否为我们从纯技术层面提供对抗膨胀的“解药”呢？今天，我们就结合Pike的演讲精髓和HN的热议，深入聊聊软件膨胀的四大根源，并从Go的视角尝试寻找一下应对之道。</p>
<h2>“膨胀”的真相：远不止代码大小和运行速度</h2>
<p>在深入探讨根源之前，我们需要认识到，“膨胀”并不止是字面意义上我们理解的最终编译产物的大小或者应用的运行速度慢，Pike的观点和HN讨论中的“软件膨胀”体现在多个维度：</p>
<ul>
<li><strong>复杂性失控：</strong> 过度的抽象层次、复杂的依赖关系、难以理解的代码路径，使得维护和迭代变得异常困难。</li>
<li><strong>维护成本剧增：</strong> 添加新功能的长期维护成本（包括理解、测试、修复Bug、处理兼容性）远超初次实现的成本，但往往被低估。</li>
<li><strong>不可预测性与脆弱性：</strong> 庞大且快速变化的依赖树使得我们几乎无法完全理解和掌控软件的实际构成和行为，任何更新都可能引入未知风险。</li>
</ul>
<p>下面我们具体看看Pike指出的“膨胀”几个核心根源：</p>
<h2>根源一：特性 (Features) —— “有用”不等于“值得”</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/rob-pike-on-bloat-3.png" alt="" /></p>
<p>Pike 指出，我们不断地为产品添加特性，以使其“更好”。但<strong>所有特性都会增加复杂性和成本</strong>，而<strong>维护成本是最大的那部分</strong>，远超初次实现。他警示我们要注意“有用谬论” —— 并非所有“有用”的功能都值得我们付出长期的维护代价。</p>
<p>HN讨论也印证了这一点：功能冗余、为了匹配竞品或满足某个高层“拍脑袋”的想法而添加功能、甚至开发者为了个人晋升而开发复杂功能的现象屡见不鲜。</p>
<p><strong>技术层面：Go的“解药”在哪？</strong></p>
<ul>
<li><strong>简洁哲学：</strong> Go从设计之初就强调“少即是多”，鼓励用简单的原语组合解决问题，天然地抵制不必要的复杂性。</li>
<li><strong>强大的标准库：</strong> Go 提供了功能丰富且高质量的标准库，覆盖了网络、并发、加解密、I/O 等众多领域，<strong>减少了对外部特性库的依赖</strong>。很多时候，“自己动手，丰衣足食”（使用标准库）比引入一个庞大的外部框架更符合Go的风格。</li>
<li><strong>关注工程效率：</strong> Go的设计目标之一是提高软件开发（尤其是大型项目）的工程效率和可维护性，这促使Go社区更关注代码的清晰度和长期成本。</li>
</ul>
<blockquote>
<p>注：技术层面包括语言、工具以及设计思路和方法。</p>
</blockquote>
<h2>根源二：分层 (Layering) —— 在错误的层级“打补丁”</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/rob-pike-on-bloat-4.png" alt="" /></p>
<p>Pike 认为，现代软件层层叠加（硬件 -> 内核 -> 运行时 -> 框架 -> 应用代码），当出现问题时，我们<strong>太容易在更高的层级通过包装（wrap）来“修复”问题，而不是深入底层真正解决它</strong>。这导致了层层叠叠的“创可贴”，增加了复杂性和维护难度。他列举了ChromeOS文件App的例子，并强调要<strong>在正确的层级实现功能和修复</strong>。</p>
<p>在HN的讨论中，有开发者描述的修改按钮颜色需要穿透17个文件和多个抽象层的例子，正是这种“错误分层”或“过度抽象”的生动体现。</p>
<p><strong>技术层面：Go的“解药”在哪？</strong></p>
<ul>
<li><strong>小接口哲学：</strong> Go 鼓励定义小而专注的接口，这使得组件之间的依赖更清晰、更松耦合。当问题出现时，更容易定位到具体的接口实现层去修复，而不是在外部层层包装。</li>
<li><strong>组合优于继承：</strong> Go 通过组合（struct embedding）而非继承来实现代码复用，避免了深度继承带来的复杂性和脆弱性，使得在“正确层级”修改代码更易操作。</li>
<li><strong>显式错误处理：</strong> if err != nil 的模式强制开发者在调用点处理错误，使得问题更难被“隐藏”到上层去统一“包装”处理，鼓励在错误发生的源头附近解决或添加上下文。</li>
</ul>
<h2>根源三：依赖 (Dependencies) —— 看不见的“冰山”</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/rob-pike-on-bloat-5.png" alt="" /></p>
<p>这是Pike演讲中着墨最多、也最为忧虑的一点。他用数据（NPM 包平均依赖 115 个其他包，每天 1/4 的依赖解析发生变化）和实例（Kubernetes 的复杂依赖图）强调：</p>
<ul>
<li>现代软件依赖数量惊人且变化极快。</li>
<li>我们几乎<strong>不可能完全理解</strong>自己项目的所有直接和间接依赖。</li>
<li>依赖中隐藏着巨大的<strong>维护成本、Bug 和安全风险</strong>。</li>
<li>简单的 npm update 或 audit <strong>无法解决根本问题</strong>。</li>
</ul>
<p>他强烈建议要<strong>理解依赖的成本</strong>，<strong>严格、定期地审视依赖树</strong>，并推荐了 deps.dev 这样的工具。</p>
<p>HN 社区对此深有同感，纷纷吐槽“为了一个函数引入整个库”、“脆弱的传递性依赖”、“供应链安全”等问题，并呼唤更好的依赖分析工具。</p>
<p><strong>技术层面：Go的“解药”在哪？</strong></p>
<ul>
<li><strong>Go Modules：</strong> 相比 NPM 等包管理器，Go Modules 提供了<strong>相对更好</strong>的依赖管理机制，包括语义化版本控制、go.sum 校验和、最小版本选择 (MVS) 等，提高了依赖的可预测性和安全性，但也要注意<strong>Go module并非完美</strong>。</li>
<li><strong>强大的标准库：</strong> 这是 Go 对抗依赖泛滥的最有力武器。很多功能可以直接使用标准库，避免引入外部依赖。</li>
<li><strong>社区文化：</strong> Go 社区相对而言更推崇<strong>稳定性和较少的依赖</strong>。引入一个大型框架或过多的外部库在 Go 社区通常需要更充分的理由。</li>
<li><strong>工具支持：</strong> Go 提供了 go mod graph, go mod why 等命令，可以帮助开发者理解依赖关系。结合 deps.dev，可以在一定程度上实践 Pike 的建议。</li>
</ul>
<h2>根源四：开源模式 (Open Source Development) —— “大门敞开” vs “严格把关”</h2>
<p><img src="https://tonybai.com/wp-content/uploads/2025/rob-pike-on-bloat-6.png" alt="" /></p>
<p>Pike 对比了两种开源开发模式：</p>
<ul>
<li><strong>“真正的开源方式” (The true open source way):</strong> 接受一切贡献 (Accept everything that comes)。他认为这是<strong>膨胀和 Bug 的巨大来源</strong>。</li>
<li><strong>更好的方式：</strong> 设立严格的代码质量、标准、评审、测试、贡献者审查等“门槛”，对允许合入的内容有标准。这种方式维护成本低得多。</li>
</ul>
<p>他暗示 Go 项目本身更倾向于后者，强调“先做好再提交”（make it good before checking it in）。可能很多Gopher也感受到了这一点，Go项目本身对代码质量的review非常严格，这一定程度上也“延缓”了一些新特性进入Go的时间点。</p>
<p>HN 的讨论中也涉及了类似 “Bazaar vs Cathedral” 的模式对比，但观点更加复杂，认为现实中的项目往往处于两者之间的某个位置，并且“完全不接受外部贡献”也并非良策。</p>
<p><strong>技术层面：Go的“解药”在哪？</strong></p>
<ul>
<li><strong>Go 自身的开发模式：</strong> Go 语言本身（由 Google 主导）的开发流程相对严谨，对代码质量和向后兼容性有较高要求，可以看作是“严格把关”模式的体现。</li>
<li><strong>标准库的设计：</strong> Go 标准库的设计精良、接口稳定，为开发者提供了一个高质量的基础平台，减少了对外部“随意贡献”的依赖。</li>
<li><strong>社区项目实践：</strong> 观察 Go 社区一些知名的开源项目，其贡献流程和代码标准通常也比较严格。</li>
</ul>
<h2>反思与现实：Go 也非万能，“警惕与纪律”仍是关键</h2>
<p>虽然 Go 的设计哲学和工具链在对抗软件膨胀方面提供了许多“天然优势”和“解药”，但我们必须清醒地认识到，<strong>Go 语言本身并不能完全免疫膨胀</strong>。</p>
<p>正如 Pike 在其“建议”(Advice) 中反复强调的，以及 HN 讨论中部分开发者指出的，最终软件的质量很大程度上取决于<strong>开发者和团队的“警惕与纪律” (vigilance and discipline)</strong>：</p>
<ul>
<li>我们是否真正理解并<strong>避免了增加不相称成本的特性</strong>？</li>
<li>我们是否努力<strong>在正确的层级解决问题</strong>？</li>
<li>我们是否<strong>审慎地评估和管理了每一个依赖</strong>？</li>
<li>我们是否坚持了<strong>高标准的开发和评审流程</strong>？</li>
</ul>
<p>如果缺乏这些，即使使用 Go，项目同样可能变得臃肿、复杂和难以维护。同时，HN 讨论也提醒我们，软件膨胀背后还有更深层次的<strong>组织、文化和经济因素</strong>，这些往往超出了单纯的技术和开发者纪律所能解决的范畴。</p>
<h2>小结：拥抱 Go 的简洁，但需务实前行</h2>
<p>Rob Pike 的“抱怨”为我们敲响了警钟，Hacker News 的热议则展现了软件膨胀问题的复杂性和普遍性。它确实是我们在工程实践中需要持续对抗的“熵增”现象。</p>
<p>Go 语言以其<strong>简洁、显式、组合</strong>的设计哲学，以及<strong>强大的标准库和相对稳健的依赖管理</strong>，在技术层面上，为我们提供了对抗膨胀的有力武器。理解并拥抱这些 Go 的“基因”，无疑能在一定程度上帮助我们构建更健康、更可持续的软件系统。</p>
<p>当然，Pike 的观点也并非金科玉律。有批评者指出，他的视角可能带有一定的“NIH（非我发明）倾向”，并且存在两个关键的“盲点”：</p>
<ol>
<li><strong>忽视了“不使用依赖”同样是巨大的技术债。</strong> 每一行自写的代码都需要永远维护。</li>
<li><strong>现实中的选择往往不是“使用依赖 vs 自己实现”，而是“使用依赖 vs 根本不做这个功能”。</strong> 面对复杂的合规要求（如 ADA、GDPR）、第三方集成或 FIPS 认证等，从零开始构建的成本（可能需要数百人年）往往让“自己实现”变得不切实际。为了让产品能够及时上线并满足用户（哪怕是 Pike 本人可能也在使用的“缓慢”网站）的需求，引入依赖和一定的“膨胀”有时是<strong>必要且务实</strong>的选择。</li>
</ol>
<blockquote>
<p>注：“NIH（非我发明）倾向”是一种心理现象，指的是人们对他人提出的想法或创新持有偏见，通常因为这些想法不是自己发明的。这种倾向使得人们倾向于低估或拒绝其他人的创意，尽管这些创意可能是有价值的。</p>
</blockquote>
<p>这种批评也提醒了我们，虽然 Pike 对简洁和纪律的呼吁值得我们高度重视，但在真实的商业环境和复杂的工程约束下，我们必须做出<strong>务实的权衡</strong>。纯粹的技术理想有时需要向现实妥协。</p>
<p><strong>最终，我们每一位 Gopher 都需要在理解 Go 简洁之道的同时，保持批判性思维和务实态度。</strong> 在日常的每一个决策中，审慎地权衡简单与复杂、理想与现实、引入依赖与自主掌控，才能在这场与“膨胀”的持久战中，找到最适合我们项目和团队的平衡点，交付真正有价值且可持续的软件。</p>
<p><strong>你如何看待 Rob Pike 对软件膨胀的观点？你认为他的批评切中要害，还是忽视了现实的复杂性？欢迎在评论区分享你的思考与实践！</strong></p>
<h2>参考资料</h2>
<ul>
<li><a href="https://docs.google.com/presentation/d/e/2PACX-1vSmIbSwh1_DXKEMU5YKgYpt5_b4yfOfpfEOKS5_cvtLdiHsX6zt-gNeisamRuCtDtCb2SbTafTI8V47/pub?slide=id.p">Rob Pike &#8211; On Bloat</a> &#8211; https://docs.google.com/presentation/d/e/2PACX-1vSmIbSwh1_DXKEMU5YKgYpt5_b4yfOfpfEOKS5_cvtLdiHsX6zt-gNeisamRuCtDtCb2SbTafTI8V47/pub?slide=id.p</li>
<li><a href="https://news.ycombinator.com/item?id=43045713">HN：On Bloat</a> &#8211; https://news.ycombinator.com/item?id=43045713 </li>
<li><a href="https://blog.habets.se/2025/02/Pike-is-wrong-on-bloat.html">Pike is wrong on bloat</a></li>
<li><a href="https://commandcenter.blogspot.com/2025/02/on-bloat-these-are-slides-from-talk-i.html">On Bloat</a> &#8211; https://commandcenter.blogspot.com/2025/02/on-bloat-these-are-slides-from-talk-i.html</li>
</ul>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/04/27/rob-pike-on-bloat/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>如何像gitlab-runner那样将Go应用安装为系统服务</title>
		<link>https://tonybai.com/2022/09/12/how-to-install-a-go-app-as-a-system-service-like-gitlab-runner/</link>
		<comments>https://tonybai.com/2022/09/12/how-to-install-a-go-app-as-a-system-service-like-gitlab-runner/#comments</comments>
		<pubDate>Mon, 12 Sep 2022 06:11:13 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[APUE]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[C语言]]></category>
		<category><![CDATA[Daemon]]></category>
		<category><![CDATA[DanielTheophanes]]></category>
		<category><![CDATA[flag]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[gitlab]]></category>
		<category><![CDATA[gitlab-runner]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go-daemon]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[init]]></category>
		<category><![CDATA[journalctl]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[OS]]></category>
		<category><![CDATA[restart]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[subcommand]]></category>
		<category><![CDATA[system-service]]></category>
		<category><![CDATA[systemctl]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[systemd-unit]]></category>
		<category><![CDATA[sysvinit]]></category>
		<category><![CDATA[UNIX环境高级编程]]></category>
		<category><![CDATA[upstart]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[后台]]></category>
		<category><![CDATA[命令行参数]]></category>
		<category><![CDATA[守护程序]]></category>
		<category><![CDATA[守护进程]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[系统服务]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3663</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2022/09/12/how-to-install-a-go-app-as-a-system-service-like-gitlab-runner 在《让reviewdog支持gitlab-push-commit，守住代码质量下限》一文中，gitlab-runner(一个Go语言开发的应用)通过自身提供的install命令将自己安装为了一个系统服务(如下面步骤)： # Create a GitLab CI user sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash # Install and run as service sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner sudo gitlab-runner start 在主流新版linux上(其他os或linux上的旧版守护服务管理器如sysvinit、upstart等，我们暂不care)，系统服务就是由systemd管理的daemon process(守护进程)。 systemd是什么？linux主机上电后，os内核被加载并启动，os内核完成初始化以后，由内核第一个启动的程序是init程序，其PID(进程ID)为1，它为系统里所有进程的“祖先”，systemd便是主流新版linux中的那个init程序，它负责在主机启动后拉起所有安装为系统服务的程序。 这些被systemd拉起的服务程序以守护进程(daemon process)的形式运行，那什么又是守护进程呢？《UNIX环境高级编程3rd(Advanced Programming in the UNIX Environment)》一书中是这样定义的： Daemons are processes that live for a long time. They are often [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/how-to-install-a-go-app-as-a-system-service-like-gitlab-runner-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2022/09/12/how-to-install-a-go-app-as-a-system-service-like-gitlab-runner">本文永久链接</a> &#8211; https://tonybai.com/2022/09/12/how-to-install-a-go-app-as-a-system-service-like-gitlab-runner</p>
<p>在<a href="https://tonybai.com/2022/09/08/make-reviewdog-support-gitlab-push-commit-to-preserve-the-code-quality-floor">《让reviewdog支持gitlab-push-commit，守住代码质量下限》</a>一文中，<a href="https://gitlab.com/gitlab-org/gitlab-runner">gitlab-runner</a>(一个Go语言开发的应用)通过自身提供的install命令将自己安装为了一个系统服务(如下面步骤)：</p>
<pre><code># Create a GitLab CI user
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash

# Install and run as service
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner start
</code></pre>
<p>在主流新版linux上(其他os或linux上的旧版守护服务管理器如sysvinit、upstart等，我们暂不care)，系统服务就是由<a href="https://systemd.io">systemd</a>管理的daemon process(守护进程)。</p>
<p>systemd是什么？linux主机上电后，os内核被加载并启动，os内核完成初始化以后，由内核第一个启动的程序是init程序，其PID(进程ID)为1，它为系统里所有进程的“祖先”，<strong>systemd便是主流新版linux中的那个init程序，它负责在主机启动后拉起所有安装为系统服务的程序</strong>。</p>
<p>这些被systemd拉起的服务程序以<strong>守护进程(daemon process)</strong>的形式运行，那什么又是守护进程呢？<a href="https://book.douban.com/subject/25900403/">《UNIX环境高级编程3rd(Advanced Programming in the UNIX Environment)》</a>一书中是这样定义的：</p>
<pre><code>Daemons are processes that live for a long time. They are often started when the system is bootstrapped and terminate only when the system is shut down. Because they don’t have a controlling terminal, we say that they run in the background. UNIX systems have numerous daemons that perform day-to-day activities.

守护进程是长期存在的进程。它们通常在系统启动时被启动，并在系统关闭时才终止。因为它们没有控制终端，我们说它们是在后台运行的。UNIX系统有许多执行日常活动的守护进程。
</code></pre>
<p>该书还提供了一个用户层应用程序将自己变为守护进程的标准步骤(编码规则(coding rules))，并给出了一个<a href="https://tonybai.com/2022/05/16/the-short-guide-of-embracing-c-lang-for-gopher">C语言</a>示例：</p>
<pre><code>#include "apue.h"
#include &lt;syslog.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;sys/resource.h&gt;

void
daemonize(const char *cmd)
{
    int i, fd0, fd1, fd2;
    pid_t pid;
    struct rlimit rl;
    struct sigaction sa;

    /*
     * Clear file creation mask.
     */
    umask(0);
    /*
     * Get maximum number of file descriptors.
     */
    if (getrlimit(RLIMIT_NOFILE, &amp;rl) &lt; 0)
        err_quit("%s: can’t get file limit", cmd);
    /*
     * Become a session leader to lose controlling TTY.
     */
    if ((pid = fork()) &lt; 0)
        err_quit("%s: can’t fork", cmd);
    else if (pid != 0) /* parent */
        exit(0);
    setsid();

    /*
     * Ensure future opens won’t allocate controlling TTYs.
     */
    sa.sa_handler = SIG_IGN;
    sigemptyset(&amp;sa.sa_mask);

    sa.sa_flags = 0;
    if (sigaction(SIGHUP, &amp;sa, NULL) &lt; 0)
        err_quit("%s: can’t ignore SIGHUP", cmd);
    if ((pid = fork()) &lt; 0)
        err_quit("%s: can’t fork", cmd);
    else if (pid != 0) /* parent */
        exit(0);
    /*
     * Change the current working directory to the root so
     * we won’t prevent file systems from being unmounted.
     */
    if (chdir("/") &lt; 0)
        err_quit("%s: can’t change directory to /", cmd);
    /*
     * Close all open file descriptors.
     */
    if (rl.rlim_max == RLIM_INFINITY)
        rl.rlim_max = 1024;
    for (i = 0; i &lt; rl.rlim_max; i++)
        close(i);
    /*
     * Attach file descriptors 0, 1, and 2 to /dev/null.
     */
    fd0 = open("/dev/null", O_RDWR);
    fd1 = dup(0);
    fd2 = dup(0);
    /*
     * Initialize the log file.
     */
    openlog(cmd, LOG_CONS, LOG_DAEMON);
    if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
        syslog(LOG_ERR, "unexpected file descriptors %d %d %d",
          fd0, fd1, fd2);
        exit(1);
    }
}
</code></pre>
<p>那么，Go应用程序是否可以参考上面的转换步骤将自己转换为一个守护进程呢？很遗憾！<a href="https://github.com/golang/go/issues/227">Go团队说很难做到</a>。Go社区倒是有很多第三方的方案，比如像<a href="https://github.com/sevlyar/go-daemon">go-daemon</a>这样的第三方实现，不过我并没有验证过这些方案，不保证完全ok。</p>
<p><a href="https://github.com/golang/go/issues/227#issuecomment-254819193">Go团队推荐通过像systemd这样的init system来实现Go程序的守护进程转换</a>。gitlab-runner就是将自己安装为system服务，并由systemd对其进行管理的。</p>
<blockquote>
<p>题外话：其实，自从有了容器技术(比如：docker)后，daemon service(守护进程服务)的需求似乎减少了。因为使用-d选项运行容器，应用本身就运行于后台，使用&#8211;restart=always/on-failure选项，容器引擎(比如docker engine)会帮我们管理service，并在service宕掉后重启service。</p>
</blockquote>
<p>那么，我们如何像gitlab-runner那样将自己安装为一个systemd service呢？我们继续向下看。</p>
<blockquote>
<p>注意：这里只是将Go应用安装成一个systemd service，并不是自己将自己转换为守护进程，安装为systemd service本身是可行的，也是安全的。</p>
</blockquote>
<p>翻看gitlab-runner源码，你会发现gitlab-runner将自己安装为系统服务全依仗于github.com/kardianos/service这个Go包，这个包是Go标准库database包维护者之一Daniel Theophanes开源的系统服务操作包，该包屏蔽了os层的差异，为开发人员提供了相对简单的Service操作接口，包括下面这些控制动作：</p>
<pre><code>// github.com/kardianos/service/blob/master/service.go
var ControlAction = [5]string{"start", "stop", "restart", "install", "uninstall"}
</code></pre>
<p>好了，下面我们就用一个例子<a href="https://github.com/bigwhite/experiments/tree/master/system-service">myapp</a>来介绍一下<strong>如何利用kardianos/service包让你的Go应用具备将自己安装为system service的能力</strong>。</p>
<p>myapp是一个http server，它在某个端口上提供服务，当收到请求时，返回”Welcome”字样的应答：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/system-service/main.go

func run(config string) error {
    ... ...

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Printf("[%s]: receive a request from: %s\n", c.Server.Addr, r.RemoteAddr)
        w.Write([]byte("Welcome"))
    })
    fmt.Printf("listen on %s\n", c.Server.Addr)
    return http.ListenAndServe(c.Server.Addr, nil)
}
</code></pre>
<p>现在我们要为myapp增加一些能力，让它支持将自己安装为systemd service，并可以通过subcommand启动(start)、停止(stop)和卸载(uninstall)systemd service。</p>
<p>我们首先通过os包和flag包为该程序增加subcommand和其参数的解析能力。我们不使用第三方命令行参数解析包，只是用标准库的flag包。由于myapp支持subcommand，我们需要为每个带命令行参数的subcommand单独申请一个FlagSet实例，如下面代码中的installCommand和runCommand。每个subcommand的命令行参数也要绑定到各自subcommand对应的FlagSet实例上，比如下面代码init函数体中的内容。</p>
<p>另外由于使用了subcommand，默认的flag.Usage不再能满足我们的要求了，我们需要自己实现一个usage函数并赋值给flag.Usage：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/system-service/main.go

var (
    installCommand = flag.NewFlagSet("install", flag.ExitOnError)
    runCommand     = flag.NewFlagSet("run", flag.ExitOnError)
    user           string
    workingdir     string
    config         string
)

const (
    defaultConfig = "/etc/myapp/config.ini"
)

func usage() {
    s := `
USAGE:
   myapp command [command options] 

COMMANDS:
     install               install service
     uninstall             uninstall service
     start                 start service
     stop                  stop service
     run                   run service

OPTIONS:
     -config string
        config file of the service (default "/etc/myapp/config.ini")
     -user string
        user account to run the service
     -workingdir string
        working directory of the service`

    fmt.Println(s)
}

func init() {
    installCommand.StringVar(&amp;user, "user", "", "user account to run the service")
    installCommand.StringVar(&amp;workingdir, "workingdir", "", "working directory of the service")
    installCommand.StringVar(&amp;config, "config", "/etc/myapp/config.ini", "config file of the service")
    runCommand.StringVar(&amp;config, "config", defaultConfig, "config file of the service")
    flag.Usage = usage
}

func main() {
    var err error
    n := len(os.Args)
    if n &lt;= 1 {
        fmt.Printf("invalid args\n")
        flag.Usage()
        return
    }

    subCmd := os.Args[1] // the second arg

    // get Config
    c, err := getServiceConfig(subCmd)
    if err != nil {
        fmt.Printf("get service config error: %s\n", err)
        return
    }
... ...
}
</code></pre>
<p>这些都完成后，我们在getServiceConfig函数中获取即将安装为systemd service的本服务的元配置信息：</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/system-service/config.go

func getServiceConfig(subCmd string) (*service.Config, error) {
    c := service.Config{
        Name:             "myApp",
        DisplayName:      "Go Daemon Service Demo",
        Description:      "This is a Go daemon service demo",
        Executable:       "/usr/local/bin/myapp",
        Dependencies:     []string{"After=network.target syslog.target"},
        WorkingDirectory: "",
        Option: service.KeyValue{
            "Restart": "always", // Restart=always
        },
    }   

    switch subCmd {
    case "install":
        installCommand.Parse(os.Args[2:])
        if user == "" {
            fmt.Printf("error: user should be provided when install service\n")
            return nil, errors.New("invalid user")
        }
        if workingdir == "" {
            fmt.Printf("error: workingdir should be provided when install service\n")
            return nil, errors.New("invalid workingdir")
        }
        c.UserName = user
        c.WorkingDirectory = workingdir

        // arguments
        // ExecStart=/usr/local/bin/myapp "run" "-config" "/etc/myapp/config.ini"
        c.Arguments = append(c.Arguments, "run", "-config", config)
    case "run":
        runCommand.Parse(os.Args[2:]) // parse config
    }   

    return &amp;c, nil
}
</code></pre>
<p>这里要注意的是service.Config中的Option和Arguments，前者用于在systemd service unit配置文件中放置任意的键值对（比如这里的Restart=always），而Arguments则会被组成为ExecStart键的值，该值会在start service时传入使用。</p>
<p>接下来，我们便利用service包基于加载的Config创建操作服务的实例(srv)，然后将它和subCommand一并传入runServiceControl实现对systemd service的控制(如下面代码)。</p>
<pre><code>// https://github.com/bigwhite/experiments/blob/master/system-service/main.go
func main() {

    // ... ...
    c, err := getServiceConfig(subCmd)
    if err != nil {
        fmt.Printf("get service config error: %s\n", err)
        return
    }

    prg := &amp;NullService{}
    srv, err := service.New(prg, c)
    if err != nil {
        fmt.Printf("new service error: %s\n", err)
        return
    }

    err = runServiceControl(srv, subCmd)
    if err != nil {
        fmt.Printf("%s operation error: %s\n", subCmd, err)
        return
    }

    fmt.Printf("%s operation ok\n", subCmd)
    return
}

func runServiceControl(srv service.Service, subCmd string) error {
    switch subCmd {
    case "run":
        return run(config)
    default:
        return service.Control(srv, subCmd)
    }
}
</code></pre>
<p>好了，代码已经完成！现在让我们来验证一下myapp的能力。</p>
<p>我们先来完成编译和二进制程序的安装：</p>
<pre><code>$make
go build -o myapp main.go config.go

$sudo make install
cp ./myapp /usr/local/bin
$sudo make install-cfg
mkdir -p /etc/myapp
cp ./config.ini /etc/myapp
</code></pre>
<p>接下来，我们就来将myapp安装为systemd的服务：</p>
<pre><code>$sudo ./myapp install -user tonybai -workingdir /home/tonybai
install operation ok

$sudo systemctl status myApp
● myApp.service - This is a Go daemon service demo
     Loaded: loaded (/etc/systemd/system/myApp.service; enabled; vendor preset: enabled)
     Active: inactive (dead)
</code></pre>
<p>我们看到安装后，myApp已经成为了myApp.service，并处于inactive状态，其systemd unit文件/etc/systemd/system/myApp.service内容如下：</p>
<pre><code>$sudo cat /etc/systemd/system/myApp.service
[Unit]
Description=This is a Go daemon service demo
ConditionFileIsExecutable=/usr/local/bin/myapp

After=network.target syslog.target 

[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/usr/local/bin/myapp "run" "-config" "/etc/myapp/config.ini"

WorkingDirectory=/home/tonybai
User=tonybai

Restart=always

RestartSec=120
EnvironmentFile=-/etc/sysconfig/myApp

[Install]
WantedBy=multi-user.target
</code></pre>
<p>接下来，我们来启动一下该服务：</p>
<pre><code>$sudo ./myapp start
start operation ok

$sudo systemctl status myApp
● myApp.service - This is a Go daemon service demo
     Loaded: loaded (/etc/systemd/system/myApp.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2022-09-09 23:30:01 CST; 5s ago
   Main PID: 623859 (myapp)
      Tasks: 6 (limit: 12651)
     Memory: 1.3M
     CGroup: /system.slice/myApp.service
             └─623859 /usr/local/bin/myapp run -config /etc/myapp/config.ini

Sep 09 23:30:01 tonybai systemd[1]: Started This is a Go daemon service demo.
Sep 09 23:30:01 tonybai myapp[623859]: listen on :65432
</code></pre>
<p>我们看到myApp服务成功启动，并在65432这个端口上监听！</p>
<p>我们利用curl向这个端口发送一个请求：</p>
<pre><code>$curl localhost:65432
Welcome                                                                         

$sudo systemctl status myApp
● myApp.service - This is a Go daemon service demo
     Loaded: loaded (/etc/systemd/system/myApp.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2022-09-09 23:30:01 CST; 1min 27s ago
   Main PID: 623859 (myapp)
      Tasks: 6 (limit: 12651)
     Memory: 1.4M
     CGroup: /system.slice/myApp.service
             └─623859 /usr/local/bin/myapp run -config /etc/myapp/config.ini

Sep 09 23:30:01 tonybai systemd[1]: Started This is a Go daemon service demo.
Sep 09 23:30:01 tonybai myapp[623859]: listen on :65432
Sep 09 23:31:24 tonybai myapp[623859]: [:65432]: receive a request from: 127.0.0.1:10348
</code></pre>
<p>我们看到myApp服务运行正常并返回预期应答结果。</p>
<p>现在我们利用stop subcommand停掉该服务：</p>
<pre><code>$sudo systemctl status myApp
● myApp.service - This is a Go daemon service demo
     Loaded: loaded (/etc/systemd/system/myApp.service; enabled; vendor preset: enabled)
     Active: inactive (dead) since Fri 2022-09-09 23:33:03 CST; 3s ago
    Process: 623859 ExecStart=/usr/local/bin/myapp run -config /etc/myapp/config.ini (code=killed, signal=TERM)
   Main PID: 623859 (code=killed, signal=TERM)

Sep 09 23:30:01 tonybai systemd[1]: Started This is a Go daemon service demo.
Sep 09 23:30:01 tonybai myapp[623859]: listen on :65432
Sep 09 23:31:24 tonybai myapp[623859]: [:65432]: receive a request from: 127.0.0.1:10348
Sep 09 23:33:03 tonybai systemd[1]: Stopping This is a Go daemon service demo...
Sep 09 23:33:03 tonybai systemd[1]: myApp.service: Succeeded.
Sep 09 23:33:03 tonybai systemd[1]: Stopped This is a Go daemon service demo.
</code></pre>
<p>修改配置/etc/myapp/config.ini（将监听端口从65432改为65431），然后再重启该服务：</p>
<pre><code>$sudo cat /etc/myapp/config.ini
[server]
addr=":65431"

$sudo ./myapp start
start operation ok

$sudo systemctl status myApp
● myApp.service - This is a Go daemon service demo
     Loaded: loaded (/etc/systemd/system/myApp.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2022-09-09 23:34:38 CST; 3s ago
   Main PID: 624046 (myapp)
      Tasks: 6 (limit: 12651)
     Memory: 1.4M
     CGroup: /system.slice/myApp.service
             └─624046 /usr/local/bin/myapp run -config /etc/myapp/config.ini

Sep 09 23:34:38 tonybai systemd[1]: Started This is a Go daemon service demo.
Sep 09 23:34:38 tonybai myapp[624046]: listen on :65431
</code></pre>
<p>从systemd的状态日志中我们看到myApp服务启动成功，并改为监听65431端口，我们访问一下该端口：</p>
<pre><code>$curl localhost:65431
Welcome                                                                                                                      

$curl localhost:65432
curl: (7) Failed to connect to localhost port 65432: Connection refused
</code></pre>
<p>从上述结果可以看出，我们的配置更新和重启都是成功的！</p>
<p>我们亦可以使用myapp的uninstall功能从systemd中卸载该服务：</p>
<pre><code>$sudo ./myapp uninstall
uninstall operation ok
$sudo systemctl status myApp
Unit myApp.service could not be found.
</code></pre>
<p>好了，到这里我们看到：在文章开始处提出的给Go应用增加将自己安装为systemd service的能力的目标已经顺利实现了。</p>
<p>最后小结一下：service包让我们的程序有了将自己安装为system service的能力。它也可以让你开发出将其他程序安装为一个system service的能力，不过这个作业就留给大家了:)。大家如有问题，欢迎在评论区留言。</p>
<p>本文涉及的代码可以在<a href="https://github.com/bigwhite/experiments/tree/master/system-service">这里</a>下载。</p>
<hr />
<p><a href="https://wx.zsxq.com/dweb2/index/group/51284458844544">“Gopher部落”知识星球</a>旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！2022年，Gopher部落全面改版，将持续分享Go语言与Go应用领域的知识、技巧与实践，并增加诸多互动形式。欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2022, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2022/09/12/how-to-install-a-go-app-as-a-system-service-like-gitlab-runner/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>BPF和Go：在Linux中内省的现代方式[译]</title>
		<link>https://tonybai.com/2020/12/25/bpf-and-go-modern-forms-of-introspection-in-linux/</link>
		<comments>https://tonybai.com/2020/12/25/bpf-and-go-modern-forms-of-introspection-in-linux/#comments</comments>
		<pubDate>Fri, 25 Dec 2020 09:31:55 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[bcc]]></category>
		<category><![CDATA[BPF]]></category>
		<category><![CDATA[bpftrace]]></category>
		<category><![CDATA[BrendanGregg]]></category>
		<category><![CDATA[Clang]]></category>
		<category><![CDATA[cloudflare]]></category>
		<category><![CDATA[FaceBook]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.16]]></category>
		<category><![CDATA[gobpf]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[GopherCon]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[handler]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[iovisor]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[kprobe]]></category>
		<category><![CDATA[libbcc]]></category>
		<category><![CDATA[libbpf]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Stack]]></category>
		<category><![CDATA[Tcpdump]]></category>
		<category><![CDATA[Trace]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[uprobe]]></category>
		<category><![CDATA[XDP]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[可观测性]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3051</guid>
		<description><![CDATA[本文翻译自马可·凯瓦克（Marko Kevac）的《BPF and Go: Modern forms of introspection in Linux》(https://medium.com/bumble-tech/bpf-and-go-modern-forms-of-introspection-in-linux-6b9802682223)。 每个人都有自己喜欢的关于魔法的书。对于一个人来说是托尔金，对于另一个人来说是普拉切特，对于第三个人来说，比如我，是马克斯-弗雷。今天我要给大家讲的是我最喜欢的IT魔法：BPF以及围绕它的现代基础设施。 BPF目前正处于普及的高峰期。这项技术正在飞速发展，深入到意想不到的地方，并且越来越容易被普通用户所接受。现在几乎每个流行的会议都有关于这个主题的演讲，早在8月份，我就应邀在俄罗斯GopherCon上(GopherCon Russia)做了这方面主题的演讲。 我在这方面有着很好的体验，所以我想和尽可能多的人分享一下。这篇文章将为你介绍为什么我们需要像BPF这样的东西，帮助你了解何时、如何使用它，以及它如何帮助作为工程师的你改善你正在进行的项目。我们还将看看它与Go的一些相关内容。 我真正希望的是，你看完这篇文章后，就像小孩子第一次读完《哈利波特》后的眼睛一样，开始发亮，并且希望你自己亲自去尝试一下这个新“玩具”。 一点点的背景 好吧，一个34岁的大胡子，眼神灼灼的告诉你这个魔法是什么？ 我们生活在2020年。打开Twitter，你可以读到愤怒的技术人士的推文，他们都在说，今天编写的软件质量太糟糕了，都需要扔掉，我们需要重新开始。有些人甚至威胁要彻底离开这个行业，因为他们实在无法忍受所有东西都坏了，不方便又慢。 他们可能是对的：如果不查阅千篇一律的评论，就无法确定原因。但有一点我绝对同意，那就是现代软件堆栈比以往任何时候都要复杂：我们有BIOS、EFI、操作系统、驱动程序、模块、库、网络交互、数据库、缓存、编排器（比如K8s）、Docker容器，最后还有我们自己的带有运行时和垃圾收集的软件。 一个真正的专业人士可能会花上几天时间来为你解释在浏览器中输入google.com之后会发生什么。 要了解你的系统里面发生了什么，是非常复杂的，尤其是在目前，事情出了问题，你正在损失金钱的情况下。正是因为这个问题，才出现了帮你搞清楚系统内部情况的企业。在大公司里，有整整一个部门的福尔摩斯式的侦探，他们只知道在哪里敲敲锤子，在哪里拧紧螺栓就能节省数百万美元。 我喜欢问人们如何在最短的时间内调试突发问题。大多数情况下，人们首先想到的方法是分析日志。但问题是，能获取的日志只局限于开发者放在系统中的日志，这是不灵活的。 第二种最流行的方法是研究度量数据。最流行的三个研究度量数据的系统都是用Go编写的。度量数据是非常有帮助的，然而，虽然它们确实可以让你看到症状，但它们并不总是能帮助你定义出问题的根本原因。 第三种是所谓的“可观察性”：你可以对系统的行为提出尽可能多的复杂问题，并获得这些问题的答案。由于问题可能非常复杂，所以答案可能需要最广泛的信息，而在问题被提出之前，我们并不知道这些信息是什么。而这意味着，可观察性绝对要求灵活性。 提供一个机会来改变”在飞行中”的日志级别呢？使用调试器，在程序运行时连接到程序，并在不中断程序工作的情况下做一些事情呢？了解哪些查询被发送到系统中，可视化慢速查询的来源，通过pprof看看什么在占用内存，并获得其随时间变化的曲线图？测量一个函数的延迟以及延迟对参数的依赖性呢？我想把所有这些方法都归入可观察性这个总称之下。这是一组实用工具、方法、知识和经验，它们结合在一起，给了我们机会，如果不能做到我们想做的所有事情，但至少可以在系统工作时，在系统中“现场”做很多事情。它相当于现代IT界的一把瑞士军刀。 但我们如何才能实现这一点呢？市场上已经存在很多类似的工具：有简单的，有复杂的，有危险的并且也有缓慢的。但今天的文章是关于BPF的。 Linux内核是一个事件驱动的系统。实际上，在内核和系统中发生的所有事情，都可以被认为是一组事件。中断是一个事件；通过网络接收一个数据包是一个事件；将处理器的控制权转移到另一个进程是一个事件；运行一个函数是一个事件。 对，所以BPF是Linux内核的一个子系统，它让你有机会编写小程序，这些小程序将在内核响应事件时被运行。这些程序既可以让你知道系统中发生了什么，也可以用于控制系统。 现在让我们来了解一下具体的内容。 什么是eBPF？ BPF的第一个版本在1994年问世。你们中的一些人可能会在为tcpdump工具编写简单的规则时遇到过它，该工具用于查看或”嗅探”网络数据包。你可以为tcpdump设置过滤器，所以你不必查看所有的数据包&#8211;只查看你感兴趣的数据包。例如，”只查看tcp协议和80端口”。对于每一个经过的数据包，都会运行一个函数来决定你是否需要保存这个特定的数据包。可以有非常多的数据包，所以我们的函数必须要快。事实上，我们的tcpdump过滤器被转化成了BPF函数。下面是一个例子。 最初的BPF代表了一个非常简单的虚拟机，有几个寄存器。但尽管如此，BPF还是大大加快了网络数据包的过滤速度。在当时，这是一个重大的进步。 2014年，一位非常著名的内核黑客Alexei Starovoitov对BPF的功能进行了扩展。他增加了寄存器的数量和程序允许的大小，增加了JIT编译，并创建了一个用于检查程序是否安全的程序。然而，最令人印象深刻的是，新的BPF程序不仅能够在处理数据包时运行，而且能够响应其他内核事件，并在内核和用户空间之间来回传递信息。 这些变化为使用BPF的新方法提供了机会。一些过去需要通过编写复杂而危险的内核模块来实现的事情，现在可以相对简单地通过BPF来完成。为什么这么好呢？因为在编写模块的时候，任何错误往往都会导致恐慌(panic)，这可不是Go语言中的恐慌(panic)，而是内核恐慌。一旦发生，我们唯一能做的就是重启(操作系统)。 普通的Linux用户突然拥有了一种新的超能力：能够查看”引擎盖下的情况”&#8211;这在以前只有核心内核开发者才有，或者说根本就没有人能够做到。这个选项可以和为iOS或Android编写程序的能力相提并论：在旧手机上，这要么是不可能的，要么就是太复杂。 Alexei Starovoitov的新版本的BPF被称为eBPF（e代表扩展：extended）。但现在，它已经取代了所有旧版的BPF用法，并且已经变得非常流行，为了简单起见，它仍然被称为BPF。 BPF用在哪里？ 好了，我们可以将BPF程序附加到哪些事件或触发器上呢，人们又是如何开始使用他们获得的新力量的呢？ 目前，触发器主要有两组。 第一组是用于处理网络数据包和管理网络流量的。这是XDP、流量控制事件和其他几个。 以下情况需要这些事件： 创建简单但非常有效的防火墙。Cloudflare和Facebook等公司使用BPF程序来过滤掉大量的寄生流量，并对抗最大规模的DDoS攻击。由于处理发生在数据包生命的最早阶段，直接在内核中进行（一个BPF程序有时甚至直接推送到网卡中进行处理），所以巨量的流量可以通过这种方式进行处理。这些事情过去都是在专门的网络硬件上完成的。 创建更智能、更有针对性、但性能更强的防火墙&#8211;这些防火墙可以检查通过的流量是否符合公司规则，是否存在漏洞模式等。例如，Facebook在内部进行这种审计，而一些项目则对外销售这类产品。 创建智能负载均衡器。最突出的例子是Cilium项目，它最常被用作K8s集群中的网格网络。Cilium对流量进行管理，平衡、重定向和分析。而所有这些都是在内核运行的小型BPF程序的帮助下完成的，以响应与网络数据包或套接字有关的这个或那个事件。 这是第一组与网络问题有关的触发器，并能够影响网络通信行为。第二组与更普遍的可观察性有关；这组中的程序大多时候无法影响任何事情，而只能”观察”。这是我比较感兴趣的。 在这组中，有如下触发器。 perf events &#8211; 与性能和perf Linux剖析器有关的事件：硬件处理器计数器，中断处理，拦截主要/次要内存异常等等。例如，我们可以设置一个处理程序，它将在每次内核需要从swap读取内存页时运行。例如，想象一下，一个显示当前使用swap的程序的工具。 tracepoints &#8211; [...]]]></description>
			<content:encoded><![CDATA[<p>本文翻译自马可·凯瓦克（Marko Kevac）的<a href="https://medium.com/bumble-tech/bpf-and-go-modern-forms-of-introspection-in-linux-6b9802682223">《BPF and Go: Modern forms of introspection in Linux》</a>(https://medium.com/bumble-tech/bpf-and-go-modern-forms-of-introspection-in-linux-6b9802682223)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-1.png" alt="img{512x368}" /></p>
<p>每个人都有自己喜欢的关于魔法的书。对于一个人来说是托尔金，对于另一个人来说是普拉切特，对于第三个人来说，比如我，是马克斯-弗雷。今天我要给大家讲的是我最喜欢的IT魔法：<a href="http://en.wikipedia.org/wiki/Berkeley_Packet_Filter">BPF</a>以及围绕它的现代基础设施。</p>
<p>BPF目前正处于普及的高峰期。这项技术正在飞速发展，深入到意想不到的地方，并且越来越容易被普通用户所接受。现在几乎每个流行的会议都有关于这个主题的演讲，早在8月份，我就应邀在俄罗斯GopherCon上(GopherCon Russia)做了这方面主题的演讲。</p>
<p>我在这方面有着很好的体验，所以我想和尽可能多的人分享一下。这篇文章将为你介绍为什么我们需要像BPF这样的东西，帮助你了解何时、如何使用它，以及它如何帮助作为工程师的你改善你正在进行的项目。我们还将看看它与Go的一些相关内容。</p>
<p>我真正希望的是，你看完这篇文章后，就像小孩子第一次读完《哈利波特》后的眼睛一样，开始发亮，并且希望你自己亲自去尝试一下这个新“玩具”。</p>
<h2>一点点的背景</h2>
<p>好吧，一个34岁的大胡子，眼神灼灼的告诉你这个魔法是什么？</p>
<p>我们生活在2020年。打开Twitter，你可以读到愤怒的技术人士的推文，他们都在说，今天编写的软件质量太糟糕了，都需要扔掉，我们需要重新开始。有些人甚至威胁要彻底离开这个行业，因为他们实在无法忍受所有东西都坏了，不方便又慢。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-2.png" alt="img{512x368}" /></p>
<p>他们可能是对的：如果不查阅千篇一律的评论，就无法确定原因。但有一点我绝对同意，那就是现代软件堆栈比以往任何时候都要复杂：我们有BIOS、EFI、操作系统、驱动程序、模块、库、网络交互、数据库、缓存、编排器（比如K8s）、Docker容器，最后还有我们自己的带有运行时和垃圾收集的软件。</p>
<p>一个真正的专业人士可能会花上几天时间来为你解释在浏览器中输入google.com之后会发生什么。</p>
<p>要了解你的系统里面发生了什么，是非常复杂的，尤其是在目前，事情出了问题，你正在损失金钱的情况下。正是因为这个问题，才出现了帮你搞清楚系统内部情况的企业。在大公司里，有整整一个部门的福尔摩斯式的侦探，他们只知道在哪里敲敲锤子，在哪里拧紧螺栓就能节省数百万美元。</p>
<p>我喜欢问人们如何在最短的时间内调试突发问题。大多数情况下，人们首先想到的方法是<strong>分析日志</strong>。但问题是，能获取的日志只局限于开发者放在系统中的日志，这是不灵活的。</p>
<p>第二种最流行的方法是<strong>研究度量数据</strong>。最流行的三个研究度量数据的系统都是用Go编写的。度量数据是非常有帮助的，然而，虽然它们确实可以让你看到症状，但它们并不总是能帮助你定义出问题的根本原因。</p>
<p>第三种是所谓的“可观察性”：你可以对系统的行为提出尽可能多的复杂问题，并获得这些问题的答案。由于问题可能非常复杂，所以答案可能需要最广泛的信息，而在问题被提出之前，我们并不知道这些信息是什么。而这意味着，可观察性绝对要求灵活性。</p>
<p>提供一个机会来改变”在飞行中”的日志级别呢？使用调试器，在程序运行时连接到程序，并在不中断程序工作的情况下做一些事情呢？了解哪些查询被发送到系统中，可视化慢速查询的来源，通过pprof看看什么在占用内存，并获得其随时间变化的曲线图？测量一个函数的延迟以及延迟对参数的依赖性呢？我想把所有这些方法都归入<strong>可观察性</strong>这个总称之下。这是一组实用工具、方法、知识和经验，它们结合在一起，给了我们机会，如果不能做到我们想做的所有事情，但至少可以在系统工作时，在系统中“现场”做很多事情。它相当于现代IT界的一把瑞士军刀。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-3.png" alt="img{512x368}" /></p>
<p>但我们如何才能实现这一点呢？市场上已经存在很多类似的工具：有简单的，有复杂的，有危险的并且也有缓慢的。但今天的文章是关于BPF的。</p>
<p>Linux内核是一个事件驱动的系统。实际上，在内核和系统中发生的所有事情，都可以被认为是一组事件。中断是一个事件；通过网络接收一个数据包是一个事件；将处理器的控制权转移到另一个进程是一个事件；运行一个函数是一个事件。</p>
<p>对，所以BPF是Linux内核的一个子系统，它让你有机会编写小程序，这些小程序将在内核响应事件时被运行。这些程序既可以让你知道系统中发生了什么，也可以用于控制系统。</p>
<p>现在让我们来了解一下具体的内容。</p>
<h2>什么是eBPF？</h2>
<p>BPF的第一个版本在1994年问世。你们中的一些人可能会在为tcpdump工具编写简单的规则时遇到过它，该工具用于查看或”嗅探”网络数据包。你可以为tcpdump设置过滤器，所以你不必查看所有的数据包&#8211;只查看你感兴趣的数据包。例如，”只查看tcp协议和80端口”。对于每一个经过的数据包，都会运行一个函数来决定你是否需要保存这个特定的数据包。可以有非常多的数据包，所以我们的函数必须要快。事实上，我们的tcpdump过滤器被转化成了BPF函数。下面是一个例子。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-4.png" alt="img{512x368}" /></p>
<p>最初的BPF代表了一个非常简单的虚拟机，有几个寄存器。但尽管如此，BPF还是大大加快了网络数据包的过滤速度。在当时，这是一个重大的进步。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-5.png" alt="img{512x368}" /></p>
<p>2014年，一位非常著名的内核黑客<a href="https://www.linkedin.com/in/alexey1/">Alexei Starovoitov</a>对BPF的功能进行了扩展。他增加了寄存器的数量和程序允许的大小，增加了JIT编译，并创建了一个用于检查程序是否安全的程序。然而，最令人印象深刻的是，新的BPF程序不仅能够在处理数据包时运行，而且能够响应其他内核事件，并在内核和用户空间之间来回传递信息。</p>
<p>这些变化为使用BPF的新方法提供了机会。一些过去需要通过编写复杂而危险的内核模块来实现的事情，现在可以相对简单地通过BPF来完成。为什么这么好呢？因为在编写模块的时候，任何错误往往都会导致恐慌(panic)，这可不是Go语言中的恐慌(panic)，而是内核恐慌。一旦发生，我们唯一能做的就是重启(操作系统)。</p>
<p>普通的Linux用户突然拥有了一种新的超能力：能够查看”引擎盖下的情况”&#8211;这在以前只有核心内核开发者才有，或者说根本就没有人能够做到。这个选项可以和为iOS或Android编写程序的能力相提并论：在旧手机上，这要么是不可能的，要么就是太复杂。</p>
<p>Alexei Starovoitov的新版本的BPF被称为eBPF（e代表扩展：extended）。但现在，它已经取代了所有旧版的BPF用法，并且已经变得非常流行，为了简单起见，它仍然被称为BPF。</p>
<h2>BPF用在哪里？</h2>
<p>好了，我们可以将BPF程序附加到哪些事件或触发器上呢，人们又是如何开始使用他们获得的新力量的呢？</p>
<p>目前，触发器主要有两组。</p>
<p>第一组是用于处理网络数据包和管理网络流量的。这是XDP、流量控制事件和其他几个。</p>
<p>以下情况需要这些事件：</p>
<ul>
<li>
<p>创建简单但非常有效的防火墙。Cloudflare和Facebook等公司使用BPF程序来过滤掉大量的寄生流量，并对抗最大规模的DDoS攻击。由于处理发生在数据包生命的最早阶段，直接在内核中进行（一个BPF程序有时甚至直接推送到网卡中进行处理），所以巨量的流量可以通过这种方式进行处理。这些事情过去都是在专门的网络硬件上完成的。</p>
</li>
<li>
<p>创建更智能、更有针对性、但性能更强的防火墙&#8211;这些防火墙可以检查通过的流量是否符合公司规则，是否存在漏洞模式等。例如，Facebook在内部进行这种审计，而一些项目则对外销售这类产品。</p>
</li>
<li>
<p>创建智能负载均衡器。最突出的例子是Cilium项目，它最常被用作K8s集群中的网格网络。Cilium对流量进行管理，平衡、重定向和分析。而所有这些都是在内核运行的小型BPF程序的帮助下完成的，以响应与网络数据包或套接字有关的这个或那个事件。</p>
</li>
</ul>
<p>这是第一组与网络问题有关的触发器，并能够影响网络通信行为。第二组与更普遍的可观察性有关；这组中的程序大多时候无法影响任何事情，而只能”观察”。这是我比较感兴趣的。</p>
<p>在这组中，有如下触发器。</p>
<ul>
<li>
<p>perf events &#8211; 与性能和perf Linux剖析器有关的事件：硬件处理器计数器，中断处理，拦截主要/次要内存异常等等。例如，我们可以设置一个处理程序，它将在每次内核需要从swap读取内存页时运行。例如，想象一下，一个显示当前使用swap的程序的工具。</p>
</li>
<li>
<p>tracepoints &#8211; 内核源代码中的静态（由开发者定义）位置，你可以通过附加到这些位置来提取静态信息（由开发者早先准备的信息）。在这种情况下，静态似乎是一件坏事，因为我说过，日志的缺点之一是它们只包含程序员最初放在那里的东西。从某种意义上说，这是对的，但tracepoints有三个重要的优点。</p>
<ul>
<li>有相当多的跟踪点散落在内核中最有趣的地方。</li>
<li>当它们不 “开启 “时，它们不使用任何资源。</li>
<li>它们是API的一部分，它们是稳定的，而且不会改变。这一点非常重要，因为我们将要提到的其他触发器缺乏稳定的API。</li>
</ul>
</li>
</ul>
<p>例如，想象一下，一个有关显示的工具程序(utility)，由于某种原因，内核没有给它执行的时间。你坐着想知道为什么它这么慢，而pprof却没有什么有趣的东西可以显示。</p>
<ul>
<li>USDT &#8211; 和tracepoints是一样的，但是是针对用户空间的程序。也就是说，作为一个程序员，你可以把这些位置添加到你的程序中。而且很多大规模的知名程序和编程语言已经采用了这些trace。比如：MySQL，或者PHP和Python等语言。通常它们的默认设置是”关闭”，如果要打开它们，你需要使用&#8211;enable-dtrace参数或类似的参数来重建解释器。是的，我们也可以在Go中注册这些类型的跟踪。你可能已经认出了参数名称中的单词<a href="https://en.wikipedia.org/wiki/DTrace"><strong>DTrace</strong></a>。重点是，这种静态跟踪是由<a href="https://en.wikipedia.org/wiki/Solaris_(operating_system)">Solaris操作系统</a>中诞生的同名系统所推广的。举个例子，想象一下，当一个新的线程被创建时，当一个GC或其他与特定语言或系统有关的东西被启动时，我们都能够觉察到。</li>
</ul>
<p>这就是另一个层次的魔法开始的地方。</p>
<ul>
<li>
<p>Ftrace触发器让我们可以选择在内核的任何功能开始时运行一个BPF程序。完全是动态的。这意味着内核会在你选择的任何内核函数开始执行之前，或者在所有内核函数开始执行之前，调用你的BPF函数&#8211;无论哪个，你都可以连接到所有的内核函数，并在输出时获得所有调用的可视化效果。</p>
</li>
<li>
<p>kprobes/uprobes给你提供的东西和ftrace几乎一样，但是你可以选择在内核和用户空间执行一个函数时附加到任何位置。如果在函数中间，有一个变量上的&#8217;if&#8217;，而你需要为这个变量建立一个值的直方图，那就不是问题了。</p>
</li>
<li>
<p>kretprobes/uretprobes&#8211;这里的一切类似于前面的触发器，但可以在内核函数或用户空间的函数返回时触发。这类触发器对于查看函数返回的内容，以及测量执行时间都很方便。例如，你可以查看&#8217;fork&#8217;系统调用返回的是哪个PID。</p>
</li>
</ul>
<p>关于这一切，我重复一遍，最美妙的事情是，当我们的BPF程序响应这些触发器而被调用后，我们的BPF程序可以好好的 “观察”一下：读取函数的参数，记录时间，读取变量，读取全局变量，进行堆栈跟踪，为以后保存一些东西，将数据发送到用户空间进行处理，和/或从用户空间获取数据或一些其他控制命令进行过滤。太棒了！</p>
<p>我不知道你是怎么想的，但对我来说，这个新的基础架构就像一个我一直想得到的玩具。</p>
<h2>API：如何使用它</h2>
<p>好了，马科，你已经说服了我们去看看BPF。现在我们怎么才能仔细看看呢？</p>
<p>让我们看看BPF程序由什么组成，以及如何与它交互。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-6.png" alt="img{512x368}" /></p>
<p>首先，我们有一个BPF程序，如果它通过验证，将被加载到内核中。在那里，它将被JIT编译器编译成机器代码，并在内核模式下运行，这时附加的触发器(trigger)将被激活。</p>
<p>BPF程序可以选择与第二部分，即与用户空间程序交互。有两种方式可以实现。我们可以向循环缓冲区写，用户空间部分可以从它那里读。我们也可以对键值图(key-value map)进行写和读，也就是所谓的BPF图(BPF map)，相应的，用户空间部分，也可以做同样的事情，这样，它们就可以互相传递信息了。</p>
<h2>基本用途</h2>
<p>最简单的BPF工作方式，但却是你在任何情况下都不应该采用的从头开始的方式，就是用C语言编写BPF程序，然后用Clang编译器，将相关代码编译成虚拟机的代码。然后，我们加载这些代码，直接使用BPF系统调用，与我们的BPF程序进行交互，也使用BPF系统调用。</p>
<p>第一个可用的简化方法是使用libbpf库。这是和内核的源代码一起提供的，可以让你直接使用BPF系统调用。基本上，它提供了方便的包装器来加载代码，以及使用BPF映射(BPF map)来从内核向用户空间发送数据并返回。</p>
<h2>bcc</h2>
<p>显然，这对人们来说是远远不够方便的。幸运的是，在iovizor这个品牌下，出现了BCC项目，这让我们的生活变得更加方便。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-7.png" alt="img{512x368}" /></p>
<p>基本上，它为我们准备了整个构建环境，让我们可以编写单个的BPF程序，其中С部分会自动构建并加载到内核中，而用户空间部分则可以用Python制作，简单明了。</p>
<h2><a href="https://github.com/iovisor/bpftrace">bpftrace</a></h2>
<p>但是，BCC似乎仍有很多事情很复杂。由于某些原因，人们特别不喜欢用С来写底层那部分。</p>
<p>那些来自iovizor的人也提供了一个工具&#8211;bpftrace，它可以让你用类似AWK的简单脚本语言（甚至是单行代码）来编写BPF脚本。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-8.png" alt="img{512x368}" /></p>
<p>Brendan Gregg是生产力和可观察性领域的著名专家，他为可用的BPF工作方式制作了以下的图片。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-9.png" alt="img{512x368}" /></p>
<p>纵轴显示的是某个工具的易用性，而横轴显示的是它的能力。你可以看到，BCC是一个非常强大的工具，但它并不是超级简单的工具。</p>
<h2>使用BPF的例子</h2>
<p>让我们来看看一些具体的例子，看看我们已经可以使用的这种神奇力量。</p>
<p>BCC和bpftrace都包含了一个”工具”目录，其中包含了大量有趣而有用的即用型脚本。它们也可以作为本地的Stack Overflow使用，你可以从中复制代码块用于自己的脚本。</p>
<p>例如，这里是显示DNS查询延迟的脚本。</p>
<pre><code>╭─marko@marko-home ~
╰─$ sudo gethostlatency-bpfcc
TIME  PID COMM        LATms HOST
16:27:32 21417 DNS Res~ver #93   3.97 live.github.com
16:27:33 22055 cupsd        7.28 NPI86DDEE.local
16:27:33 15580 DNS Res~ver #87   0.40 github.githubassets.com
16:27:33 15777 DNS Res~ver #89   0.54 github.githubassets.com
16:27:33 21417 DNS Res~ver #93   0.35 live.github.com
16:27:42 15580 DNS Res~ver #87   5.61 ac.duckduckgo.com
16:27:42 15777 DNS Res~ver #89   3.81 www.facebook.com
16:27:42 15777 DNS Res~ver #89   3.76 tech.badoo.com <img src='https://tonybai.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' />
16:27:43 21417 DNS Res~ver #93   3.89 static.xx.fbcdn.net
16:27:43 15580 DNS Res~ver #87   3.76 scontent-frt3-2.xx.fbcdn.net
16:27:43 15777 DNS Res~ver #89   3.50 scontent-frx5-1.xx.fbcdn.net
16:27:43 21417 DNS Res~ver #93   4.98 scontent-frt3-1.xx.fbcdn.net
16:27:44 15580 DNS Res~ver #87   5.53 edge-chat.facebook.com
16:27:44 15777 DNS Res~ver #89   0.24 edge-chat.facebook.com
16:27:44 22099 cupsd        7.28 NPI86DDEE.local
16:27:45 15580 DNS Res~ver #87   3.85 safebrowsing.googleapis.com
^C%
</code></pre>
<p>一个实时显示DNS查询完成时间的实用工具，例如，你可以抓住一些意想不到的异常值。</p>
<p>下面是一个可以”监视”别人在终端上输入的内容的脚本。</p>
<pre><code>╭─marko@marko-home ~
╰─$ sudo bashreadline-bpfcc
TIME  PID COMMAND
16:51:42 24309 uname -a
16:52:03 24309 rm -rf src/badoo
</code></pre>
<p>这种脚本可以用来捕捉”坏邻居”，或者对公司的服务器进行安全审计。</p>
<p>下面是一个输出高级语言函数调用链的脚本。</p>
<pre><code>╭─marko@marko-home ~/tmp
╰─$ sudo /usr/sbin/lib/uflow -l python 20590
Tracing method calls in python process 20590... Ctrl-C to quit.
CPU PID TID TIME(us) METHOD
5  20590 20590 0.173 -&gt; helloworld.py.hello
5  20590 20590 0.173  -&gt; helloworld.py.world
5  20590 20590 0.173  &lt;- helloworld.py.world
5  20590 20590 0.173 &lt;- helloworld.py.hello
5  20590 20590 1.174 -&gt; helloworld.py.hello
5  20590 20590 1.174  -&gt; helloworld.py.world
5  20590 20590 1.174  &lt;- helloworld.py.world
5  20590 20590 1.174 &lt;- helloworld.py.hello
5  20590 20590 2.175 -&gt; helloworld.py.hello
5  20590 20590 2.176  -&gt; helloworld.py.world
5  20590 20590 2.176  &lt;- helloworld.py.world
5  20590 20590 2.176 &lt;- helloworld.py.hello
6  20590 20590 3.176 -&gt; helloworld.py.hello
6  20590 20590 3.176  -&gt; helloworld.py.world
6  20590 20590 3.176  &lt;- helloworld.py.world
6  20590 20590 3.176 &lt;- helloworld.py.hello
6  20590 20590 4.177 -&gt; helloworld.py.hello
6  20590 20590 4.177  -&gt; helloworld.py.world
6  20590 20590 4.177  &lt;- helloworld.py.world
6  20590 20590 4.177 &lt;- helloworld.py.hello
^C%
</code></pre>
<p>下面这个例子显示了Python中程序的调用栈。(译注：原文似乎缺了这块的代码)。</p>
<p>Brendan Gregg 制作了一张图片，它汇集了所有相关的脚本，箭头指向每个实用程序允许你观察的子系统。正如你所看到的，我们已经有了大量的现成的实用程序供我们使用&#8211;几乎可以应对任何可能的情况。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-10.png" alt="img{512x368}" /></p>
<h2>那Go语言呢？</h2>
<p>现在我们来谈谈Go。我们有两个基本问题。</p>
<ul>
<li>你能用Go写BPF程序吗？</li>
<li>你能分析用Go写的程序吗？</li>
</ul>
<p>我们按顺序来做。</p>
<p>目前，唯一能够编译成BPF机器(BPF machine)能够理解的格式的编译器是Clang。另一个流行的编译器GСС，但gcc仍然没有BPF后端。而能够编译成BPF的编程语言，只有C语言的一个非常有限的版本(C的子集)。</p>
<p>然而，BPF程序还有第二部分，就是在用户空间。而这可以用Go来编写。</p>
<p>正如我在上面已经提到的，BCC允许你用Python来编写这部分，而Python是该工具的主要语言。同时，在主库中，BCC还支持Lua和C++，而且，在辅库中，它还支持<a href="https://github.com/iovisor/gobpf">Go</a>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-11.png" alt="img{512x368}" /></p>
<p>这个程序看起来和Python中的程序完全一样。一开始，它有一个字符串，其中的BPF程序是用C语言编写的，然后我们沟通在哪里附加一个给定的程序，我们用某种方式和它进行交互，比如从BPF图中提取数据。</p>
<p>基本上就是这样了。更详细的例子可以在<a href="https://github.com/iovisor/gobpf/tree/master/examples/bcc/bash_readline">Github上查看</a>。</p>
<p>主要的缺点可能是我们使用的是C库，libbcc或者libbpf，用C库构建一个Go程序远不是一件容易的”事”。</p>
<p>除了iovisor/gobpf之外，我还发现了另外三个最新的项目，可以让你在Go中写出用户层(userland)部分。</p>
<ul>
<li>https://github.com/dropbox/goebpf</li>
<li>https://github.com/cilium/ebpf</li>
<li>https://github.com/andrewkroh/go-ebpf</li>
</ul>
<p>Dropbox的版本不需要任何C库，但你需要自己用Clang构建BPF的内核部分，然后用Go程序将其加载到内核中。</p>
<p>Cilium的版本和Dropbox的版本有相同的具体内容。但值得一提的是，最主要的原因是它是由Cilium项目的人做的，这意味着它成功性更大。</p>
<p>第三个项目我出于完整性的考虑而列出了。和前面两个项目一样，它没有外部的C语言依赖，需要用C语言手动构建BPF程序，但看起来，未来的前景不是特别乐观。</p>
<p>其实，我们还应该问一个问题：到底为什么要用Go写BPF程序？因为如果你看BCC或者bpftrace，那么bPF程序占用的代码不到500行。但如果用bpftrace语言写一个小脚本，或者用一点Python，不是更简单吗？我看有两个理由要这么做。</p>
<p>第一个原因是这样的。你确实很喜欢Go，而且更愿意用Go来做所有事情(译注：拿着go这柄锤子，眼中到处都是钉子)。此外，把Go程序从机器迁移到机器上可能更简单：静态链接，简单的二进制，以及所有这些。但事情远没有这么简单，因为我们被绑在一个特定的内核上。我就不说了，否则，我的文章又要长50页了。</p>
<p>第二个原因是这样的。你写的不是一个简单的脚本，而是一个大规模的系统，这个系统内部也使用了BPF。我在Go中甚至有这样一个系统的例子。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-12.png" alt="img{512x368}" /></p>
<p>Scope项目看起来像一个二进制程序，当它在K8s或其他云的基础设施中运行时，会分析发生的一切，并显示有哪些容器和服务，它们是如何交互的等等。而很多这些都是用BPF完成的。一个有趣的项目。</p>
<h2>用Go分析程序</h2>
<p>如果你还记得，我们还有一个问题：我们能不能用BPF分析用Go编写的程序？我们的第一反应是：”可以，当然可以！” 程序用什么语言编写有什么区别呢？毕竟，它只是编译后的代码，和其他程序一样，在处理器中计算一些东西，疯狂地占用内存，并通过内核与硬件交互，通过系统调用与内核交互。原则上这是正确的，但也有一些细节&#8211;这些细节有不同程度的复杂性。</p>
<h3>传递参数</h3>
<p>其中一个细节是，Go不使用大多数其他语言所使用的ABI(application binary interface)。它的工作方式是，”创始人”决定从<a href="https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs">Plan 9系统</a>中提取ABI，这是一个他们非常熟悉的系统。</p>
<p>ABI和API一样，是一种接口约定&#8211;只是在比特、字节和机器代码的层面上。</p>
<p>我们对ABI的主要内容感兴趣的是它的参数是如何传递给函数的，以及响应是如何从函数中回来的。如果说在标准的ABI x86-64中，处理器的寄存器是用来传递参数和响应的，而在Plan 9 ABI中，堆栈是则是用来实现这个目的的。</p>
<p>Rob Pike和他的团队并没有打算做另一个标准；他们已经为Plan 9系统准备了一个几乎是现成的C编译器&#8211;就像2 x 2一样简单&#8211;在很短的准备时间内，他们将其改造成了Go的编译器。这就是一个工程师的方法。</p>
<p>然而，实际上这并不是一个如此关键的问题。首先，我们可能很快就会<a href="https://github.com/golang/go/issues/18597">在Go中看到通过寄存器传递参数</a>，其次，从BPF中获取堆栈参数并不复杂：<a href="https://github.com/iovisor/bpftrace/pull/828">sargX别名</a>已经被添加到bpftrace中，而<a href="https://github.com/iovisor/bcc/issues/934">另一个别名</a>很可能在不久的将来出现在BCC中。</p>
<p>更新：自从我做了演讲之后，Go官方甚至还出了一个<a href="https://go.googlesource.com/proposal/+/refs/changes/78/248178/1/design/40724-register-calling.md">关于在ABI中使用寄存器的详细技术草案</a>。</p>
<h3>唯一的线程标识符</h3>
<p>第二个则是与Go的一个被钟爱的功能有关，即goroutines。测量函数延迟的方法之一是保存函数被调用的时间，得到函数的退出时间，并计算其差值。我们需要保存函数的启动时间以及一个键，这这个键将包含函数的名称和TID（线程ID）。线程ID是需要的，因为同一个函数可以被不同的程序，或者一个程序的不同线程同时调用。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-13.png" alt="img{512x368}" /></p>
<p>但是，在Go中，goroutine在系统线程之间移动：前一分钟，一个goroutine在一个线程上执行，后一分钟，在另一个线程上执行。而且，在Go的情况下，我们最好不要将TID放入键中，而是放入GID，即goroutine的ID&#8211;但不幸的是，我们无法获得它。从纯技术的角度来看，这个ID确实存在。你甚至可以用肮脏的黑客手段来提取它，因为它可以在堆栈的某个地方被找到，但这样做是被Go核心团队建议严格禁止的。他们认为这是我们永远不会需要的信息。goroutine本地存储也是如此&#8211;但这有点跑题了。</p>
<h3>扩展栈</h3>
<p>第三个问题是最严重的问题。它是如此严重，以至于即使我们以某种方式解决了第二个问题，也无法帮助我们测量Go函数的延迟。</p>
<p>大多数读者可能对什么是栈有了很好的理解。这也就是栈，与堆不同，你可以为变量分配内存，而不必考虑释放它们。</p>
<p>但是对于C语言来说，在这种情况下，栈有一个固定的大小。如果我们超过了这个固定大小，就会出现众所周知的堆栈溢出现象。</p>
<p>但在Go中，栈是动态的。在旧版本中，它是通过链接的内存块列表来实现的(即分段栈)。现在，它是一个动态大小的连续块。这意味着，如果分配的内存块对我们来说不够用，我们就扩展当前的内存块。而如果我们不能扩展它，我们就分配一个更大的，并将所有数据从旧的位置移动到新的位置。这一点非常吸引人，并且涉及到安全保证、cgo和垃圾收集等问题，但这是另一篇文章的主题。</p>
<p>要知道，为了让Go能够移动堆栈，它必须处理调用栈，并且处理栈中的所有指针。</p>
<p>而这就是基本的问题所在：uretprobes，用于将bPF探针附加到函数返回中，动态地改变堆栈以整合对其处理程序的调用&#8211;这就是所谓的 “蹦床(trampoline)”。而且，在大多数情况下，这改变了栈，这是Go不期望发生的事情，它会导致程序崩溃。糟了!</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-14.png" alt="img{512x368}" /></p>
<p>顺便说一下，这个故事不是Go独有的。C++的堆栈拆分器在处理异常时也每每崩溃。</p>
<p>这个问题没有解决办法。在这种情况下，像往常一样，双方各自向对方抛出完全有理有据的论点进行指责。</p>
<p>但是，如果你真的需要设置uretprobe，有一个方法可以绕过这个问题。怎么解决？不要设置uretprobe探针。你可以在我们退出函数的所有位置设置一个uprobe。可能有一个这样的位置&#8211;或者50个。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-15.png" alt="img{512x368}" /></p>
<p>而这也是Go的独特性在我们手中发挥的地方。</p>
<p>通常情况下，这种诡计是行不通的。一个足够聪明的编译器知道如何执行所谓的尾部调用优化，这时，我们不是从函数中返回，而是简单地跳到下一个函数的开始处。这种优化对于Haskell这样的函数式语言来说是至关重要的。如果没有它，你就无法在不发生堆栈溢出的情况下寸步难行。但是，有了这种优化，根本不可能找到我们从函数返回的所有位置。</p>
<p>但具体来说，Go 1.14版本的编译器，还不能进行尾部调用优化。这就意味着，附加到函数的所有显式退出的技巧是可行的，即使它非常笨重。</p>
<h2>示例</h2>
<p>不要认为BPF对Go无用。远非如此。我们可以做所有不涉及上述问题的其他事情。而且我们会这样做的。</p>
<p>让我们来看一些例子。</p>
<p>首先，我们来看一个简单的程序。基本上，它是一个监听8080端口的web服务器，并且有一个HTTP查询的处理程序。处理程序从URL中获取一个名称参数和一个年份参数，进行检查，然后将这三个变量（名称、年份和检查状态）发送给prepareAnswer()函数，然后该函数以字符串的形式准备一个答案。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-16.png" alt="img{512x368}" /></p>
<p>Site check是一个HTTP查询，在通道和goroutines的帮助下，检查会议站点是否工作。prepareAnswer函数只是将所有这些转化为一个可读的字符串。</p>
<p>我们将通过curl的简单查询来触发我们的程序：</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-17.png" alt="img{512x368}" /></p>
<p>对于我们的第一个例子，我们将使用 bpftrace 打印所有程序的函数调用。在本例中，我们将对 “main “下的所有函数进行附加。在Go中，所有的函数都有一个符号，其形式如下：包名-点-函数名。我们的包是&#8217;main&#8217;，函数的运行时是&#8217;runtime&#8217;。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-18.png" alt="img{512x368}" /></p>
<p>当我使用curl时，处理程序(handler)、site检查函数和goroutine子函数都会被执行，然后是准备答案函数(prepareAnswer)。很好！</p>
<p>接下来，我不仅要导出那些正在执行的函数，还要导出它们的参数。让我们以函数prepareAnswer()为例，它有三个参数。让我们试着打印两个ints。</p>
<p>让我们拿bpftrace来说，只不过这次不是单行代码，而是一个脚本。让我们将其附在我们的函数上，让我们像我说的那样，为堆栈参数使用别名。</p>
<p>在输出中，我们看到，我们发送了2020，获得了状态200，还发送了一次2021。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-19.png" alt="img{512x368}" /></p>
<p>但这个函数有三个参数。第一个参数是一个字符串。那么这个参数呢？</p>
<p>我们简单的导出0到3的所有堆栈参数，我们看看会看到什么？一个大数字，一个稍小的数字，还有我们以前的数字2021和200。一开始这些奇怪的数字是什么？</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-20.png" alt="img{512x368}" /></p>
<p>这时，熟悉Go的内部结构是很有帮助的。如果说在C语言中，字符串只是一个以零结尾的字节数组，那么在Go语言中，字符串实际是一个结构体，由一个指向字节数组的指针（顺便说一下，这个指针不是以零结尾）和长度组成。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-21.png" alt="img{512x368}" /></p>
<p>但是Go编译器在以参数的形式发送一个字符串时，会将这个结构解开，作为两个参数发送。于是，第一个奇怪的数字确实是我们数组的指针，第二个是长度。</p>
<p>果然：预期的字符串长度是22。</p>
<p>相应地，我们修正一下我们的脚本，以便通过堆栈指针寄存器获得这两个值，以及正确的偏移量，并且，在集成的str()函数的帮助下，我们将其导出为一个字符串。这一切都成功了。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-22.png" alt="img{512x368}" /></p>
<p>我们也来看看运行时(runtime)的情况。例如，我想知道我们的程序启动了哪些goroutines。我知道goroutines是由函数newproc()和newproc1()启动的。我们来附着(attach)一下它们。funcval结构的指针是newproc1()函数的第一个参数。这个只有一个字段，就是函数的指针。</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-23.png" alt="img{512x368}" /></p>
<p>在这种情况下，我们将使用直接在脚本中定义结构的功能。这比使用偏移量要简单一些。我们已经导出了所有的goroutine，当我们的处理程序被调用时，这些goroutine就会启动。之后，如果我们想获取偏移量的符号名称，那么我们就可以在其中看到我们的checkSite函数。万岁!</p>
<p><img src="https://tonybai.com/wp-content/uploads/bpf-go-linux/bpf-and-go-modern-forms-of-introspection-in-linux-24.png" alt="img{512x368}" /></p>
<p>这些例子对于BPF、BCC和bpftrace的功能来说只是沧海一粟。只要对内部工作原理有足够的了解和经验，您就可以从工作程序中获得几乎任何信息，而无需停止或改变它。</p>
<h2>结论</h2>
<p>这就是我想告诉你的全部内容，希望对你有所启发。</p>
<p>BPF是Linux中最时髦、最有前途的领域之一。而且我相信，在未来的几年里，我们会看到更多有趣的东西&#8211;不仅是技术本身，还有工具和它的传播。</p>
<p>现在还不算太晚，也不是每个人都知道BPF，所以赶快去学习，成为魔术师，解决问题，帮助你的同事。都说魔术师的招数只有一次。</p>
<p>说到Go，照例，我们的结局很独特。我们总是有一些怪癖，无论是不同的编译器，还是ABI，需要GOPATH，有一个你无法谷歌的名字。但我认为，可以说我们（Go)已经成为一股不可忽视的力量，在我看来，情况只会越来越好。</p>
<h2>附录（译者添加，原文没有此节)</h2>
<h3>在ubuntu 18.04上安装bpftrace</h3>
<p>ubuntu 19.04及以后版本可以直接通过下面命令安装bpftrace：</p>
<pre><code>(sudo) apt-get install -y bpftrace
</code></pre>
<p>但18.04版本的apt官方源中并没有bpftrace。但snap中有：</p>
<pre><code># snap install --devmode bpftrace
2020-12-17T17:21:24+08:00 INFO Waiting for automatic snapd restart...
bpftrace 20201207-1718-v0.11.4 from Colin King (cking-kernel-tools) installed

# snap connect bpftrace:system-trace

# which bpftrace
/snap/bin/bpftrace

Build
  version: v0.11.4
  LLVM: 7
  foreach_sym: no
  unsafe uprobe: no
  bfd: yes
  bpf_attach_kfunc: no
  bcc_usdt_addsem: no
  bcc bpf_attach_uprobe refcount: no
  libbpf: no
  libbpf btf dump: no
  libbpf btf dump type decl: no

Kernel helpers
  probe_read: yes
  probe_read_str: yes
  probe_read_user: yes
  probe_read_user_str: yes
  probe_read_kernel: yes
  probe_read_kernel_str: yes
  get_current_cgroup_id: yes
  send_signal: yes
  override_return: yes

Kernel features
  Instruction limit: -1
  Loop support: no
  btf: no

Map types
  hash: yes
  percpu hash: yes
  array: yes
  percpu array: yes
  stack_trace: yes
  perf_event_array: yes

Probe types
  kprobe: no
  tracepoint: yes
  perf_event: yes
  kfunc: no
</code></pre>
<p>但通过snap安装的bpftrace有缺陷：</p>
<pre><code># bpftrace -e 'uprobe:/root/test/go/goebpf/testprogram:main.* { printf("%s - %s\n", comm, func); }'
sh: 1: objdump: not found
No probes to attach
</code></pre>
<p>这个问题在https://github.com/iovisor/bpftrace/issues/1430中有解决方法，那就是从bpftrace官方提供的docker镜像中将无缺陷的bpftrace拷贝出来：</p>
<pre><code># docker pull quay.io/iovisor/bpftrace:master-vanilla_llvm_clang_glibc2.27
master-vanilla_llvm_clang_glibc2.27: Pulling from iovisor/bpftrace
da7391352a9b: Pull complete
14428a6d4bcd: Pull complete
2c2d948710f2: Pull complete
8aeae4c5f345: Pull complete
e3b704c358bf: Pull complete
Digest: sha256:77ded0c887c91a431a1ebe508944eae0ed0fab9c51fc2867146c9b4b347becc7
Status: Downloaded newer image for quay.io/iovisor/bpftrace:master-vanilla_llvm_clang_glibc2.27
quay.io/iovisor/bpftrace:master-vanilla_llvm_clang_glibc2.27

# docker run -v $(pwd):/output quay.io/iovisor/bpftrace:master-vanilla_llvm_clang_glibc2.27 /bin/bash -c "cp /usr/bin/bpftrace /output"
# mv bpftrace /snap/bin  &lt;--- 覆盖掉原snap安装的bpftrace

# bpftrace -e 'uprobe:/root/test/go/goebpf/testprogram:main.* { printf("%s - %s\n", comm, func); }'
Attaching 5 probes...

</code></pre>
<h3>文中一些go文件的源码</h3>
<pre><code>// testprogram.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "strconv"
)

func main() {
    http.HandleFunc("/", handler)
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

func handler(writer http.ResponseWriter, request *http.Request) {
    query := request.URL.Query()
    name := query.Get("name")
    year_, _ := strconv.ParseUint(query.Get("year"), 10, 32)
    year := int(year_)
    status := checkSite()
    answer := prepareAnswer(name, year, status)
    writer.Write([]byte(answer + "\n"))
    return
}

//go:noinline
func checkSite() int {
    resultChan := make(chan int)
    go func() {
        resp, err := http.Get("https://www.gophercon-russia.ru")
        if err != nil {
            log.Fatalf("http get failed: %s\n", err)
        }
        resultChan &lt;- resp.StatusCode
    }()

    return &lt;-resultChan
}

//go:noinline
func prepareAnswer(name string, year int, status int) string {
    answer := fmt.Sprintf("Hello, %s %d! Website returned status %d.", name, year, status)
    return answer
}
</code></pre>
<p>myscript3.bt：</p>
<pre><code># cat myscript3.bt
uprobe:/root/test/go/goebpf/testprogram:main.prepareAnswer {
    $length = reg("sp")+16;
    $array = reg("sp")+8;
         printf("%s - %s %d %d\n", func, str(*($array), $length), sarg2, sarg3);
}
</code></pre>
<hr />
<p><strong>“Gopher部落”知识星球开球了！</strong>高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！星球首开，福利自然是少不了的！2020年年底之前，8.8折(很吉利吧^_^)加入星球，下方图片扫起来吧！</p>
<p><img src="http://image.tonybai.com/img/202011/gopher-tribe-zsxq.png" alt="" /></p>
<p>我的Go技术专栏：“<a href="https://www.imooc.com/read/87">改善Go语⾔编程质量的50个有效实践</a>”上线了，欢迎大家订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-column-pgo-with-qr-and-text.png" alt="img{512x368}" /></p>
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网热卖中，欢迎小伙伴们订阅学习！</p>
<p><img src="https://tonybai.com/wp-content/uploads/k8s-practice-with-qr-and-text.png" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/12/25/bpf-and-go-modern-forms-of-introspection-in-linux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>后端程序员一定要看的语言大比拼：Java vs. Go vs. Rust</title>
		<link>https://tonybai.com/2020/05/01/comparison-between-java-go-and-rust/</link>
		<comments>https://tonybai.com/2020/05/01/comparison-between-java-go-and-rust/#comments</comments>
		<pubDate>Thu, 30 Apr 2020 17:01:20 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[alpine]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[cargo]]></category>
		<category><![CDATA[Cpp]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[GC]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Glibc]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[image]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JVM]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Maven]]></category>
		<category><![CDATA[musl]]></category>
		<category><![CDATA[OpenJDK]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[STW]]></category>
		<category><![CDATA[wrk]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[垃圾回收]]></category>
		<category><![CDATA[性能基准]]></category>
		<category><![CDATA[递归]]></category>
		<category><![CDATA[镜像]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2901</guid>
		<description><![CDATA[这是Java，Go和Rust之间的比较。这不是基准测试，更多是对可执行文件大小、内存使用率、CPU使用率、运行时要求等的比较，当然还有一个小的基准测试，可以看到每秒处理的请求数量，我将尝试对这些数字进行有意义的解读。 为了尝试尽可能公平比较，我在此比较中使用每种语言编写了一个Web服务。Web服务非常简单，它提供了三个REST服务端点(endpoint)。 Web服务提供的服务端点 这三个Web服务的代码仓库托管在github上。 编译后的二进制文件尺寸 有关如何构建二进制文件的一些信息。对于Java，我使用maven-shade-plugin和mvn package命令将所有内容构建到一个大的jar中。对于Go，我使用go build。最后，我使用了cargo build &#8211;release构建Rust服务的二进制文件。 每个程序的大小（以兆字节为单位） 编译后的文件大小还取决于所选的库/依赖项，因此，如果依赖项的身躯臃肿，则编译后的程序也将难以幸免。在我的特定情况下，针对我选择的特定库，以上是程序编译后的大小。 在后续的一个单独小节中，我会把这三个程序都构建并打包为docker镜像，并列出它们的大小，以显示每种语言所需的运行时开销。下面有更多详细信息。 内存使用情况 空闲状态 每个应用程序在内存空闲时的内存使用情况 什么？Go和Rust版本显示空闲时内存占用量的条形图在哪里？好了，它们在那里，只有JVM启动的程序在空闲状态时消耗160 MB以上的内存，它什么也没做。Go应用程序仅使用0.86 MB，Rust应用也仅使用了0.36 MB。这是一个巨大的差异！在这里，Java使用的内存比Go和Rust应用使用的内存高出两个数量级，只是空占着内存却什么都不做。那是巨大的资源浪费。 服务REST请求 让我们使用wrk发起访问API的请求，并观察内存和CPU使用情况，以及在我的计算机上三个版本程序的每个端点每秒处理的请求数。 wrk -t2 -c400 -d30s http://127.0.0.1:8080/hello wrk -t2 -c400 -d30s http://127.0.0.1:8080/greeting/Jane wrk -t2 -c400 -d30s http://127.0.0.1:8080/fibonacci/35 上面的wrk命令使用两个线程并在连接池中保持400个打开的连接，并重复调用GET端点，持续30秒。这里我仅使用两个线程，因为wrk和被测程序都在同一台计算机上运行，所以我不希望它们在可用资源（尤其是CPU）上相互竞争（太多）。 每个Web服务都经过单独测试，并且在每次运行之间都重新启动了Web服务。 以下是该程序的每个版本的三个运行中的最佳结果。 /hello 该端点返回Hello，World！信息。它分配字符串“ Hello，World！” 并将其序列化并以JSON格式返回。 /hello端点的CPU使用率 /hello端点的内存使用情况 /hello端点处理的每秒请求数 /greeting/{name} 该端点接受一个段路径参数{name}，然后格式化字符串“Hello,{name}!”，序列化并以JSON格式的问候消息返回。 /greeting端点的CPU使用率 /greeting端点的内存使用情况 /greeting端点处理的每秒请求数 /fibonacci/{number} 该端点接受一个段路径参数{number}，并返回序列化为JSON格式的斐波纳契数和输入数。 对于这个特定的端点，我选择以递归形式实现它。我毫不怀疑，迭代实现会产生更好的性能结果，并且出于生产目的，应该选择一种迭代形式，但是在生产代码中，有些情况下必须使用递归（并非专门用于计算第n个斐波那契数 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-1.png" alt="" /></p>
<p>这是<a href="https://tonybai.com/tag/java">Java</a>，<a href="https://tonybai.com/tag/go">Go</a>和Rust之间的比较。这不是<a href="https://tonybai.com/2015/08/25/go-debugging-profiling-optimization/">基准测试</a>，更多是对可执行文件大小、内存使用率、CPU使用率、运行时要求等的比较，当然还有一个小的基准测试，可以看到每秒处理的请求数量，我将尝试对这些数字进行有意义的解读。</p>
<p>为了尝试尽可能公平比较，我在此比较中使用每种语言编写了一个Web服务。Web服务非常简单，它提供了三个REST服务端点(endpoint)。</p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-2.png" alt="" /><br />
<center>Web服务提供的服务端点</center></p>
<p>这三个Web服务的代码仓库托管在<a href="https://github.com/dexterdarwich/ws-compare">github上</a>。</p>
<h2>编译后的二进制文件尺寸</h2>
<p>有关如何构建二进制文件的一些信息。对于Java，我使用<a href="http://maven.apache.org/plugins/maven-shade-plugin/">maven-shade-plugin</a>和<code>mvn package</code>命令将所有内容构建到一个大的jar中。对于Go，我使用go build。最后，我使用了cargo build &#8211;release构建Rust服务的二进制文件。</p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-3.png" alt="" /><br />
<center> 每个程序的大小（以兆字节为单位）</center></p>
<p>编译后的文件大小还取决于所选的库/依赖项，因此，如果依赖项的身躯臃肿，则编译后的程序也将难以幸免。在我的特定情况下，针对我选择的特定库，以上是程序编译后的大小。</p>
<p>在后续的一个单独小节中，我会把这三个程序都构建并打包为<a href="https://tonybai.com/2017/12/21/the-concise-history-of-docker-image-building/">docker镜像</a>，并列出它们的大小，以显示每种语言所需的<a href="https://tonybai.com/2020/03/21/illustrated-tales-of-go-runtime-scheduler/">运行时</a>开销。下面有更多详细信息。</p>
<h2>内存使用情况</h2>
<h3>空闲状态</h3>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-4.png" alt="" /><br />
<center>每个应用程序在内存空闲时的内存使用情况</center></p>
<p>什么？Go和Rust版本显示空闲时内存占用量的条形图在哪里？好了，它们在那里，只有JVM启动的程序在空闲状态时消耗160 MB以上的内存，它什么也没做。Go应用程序仅使用0.86 MB，Rust应用也仅使用了0.36 MB。这是一个巨大的差异！在这里，Java使用的内存比Go和Rust应用使用的内存高出两个数量级，只是空占着内存却什么都不做。那是巨大的资源浪费。</p>
<h3>服务REST请求</h3>
<p>让我们使用<a href="https://github.com/wg/wrk">wrk</a>发起访问API的请求，并观察内存和CPU使用情况，以及在我的计算机上三个版本程序的每个端点每秒处理的请求数。</p>
<pre><code>wrk -t2 -c400 -d30s http://127.0.0.1:8080/hello
wrk -t2 -c400 -d30s http://127.0.0.1:8080/greeting/Jane
wrk -t2 -c400 -d30s http://127.0.0.1:8080/fibonacci/35
</code></pre>
<p>上面的wrk命令使用两个线程并在连接池中保持400个打开的连接，并重复调用GET端点，持续30秒。这里我仅使用两个线程，因为wrk和被测程序都在同一台计算机上运行，所以我不希望它们在可用资源（尤其是CPU）上相互竞争（太多）。</p>
<p>每个Web服务都经过单独测试，并且在每次运行之间都重新启动了Web服务。</p>
<p>以下是该程序的每个版本的三个运行中的最佳结果。</p>
<ul>
<li>/hello</li>
</ul>
<p>该端点返回Hello，World！信息。它分配字符串“ Hello，World！” 并将其序列化并以JSON格式返回。</p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-5.png" alt="" /><br />
<center>/hello端点的CPU使用率</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-6.png" alt="" /><br />
<center>/hello端点的内存使用情况</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-7.png" alt="" /><br />
<center>/hello端点处理的每秒请求数</center></p>
<ul>
<li>/greeting/{name}</li>
</ul>
<p>该端点接受一个段路径参数{name}，然后格式化字符串“Hello,{name}!”，序列化并以JSON格式的问候消息返回。</p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-8.png" alt="" /><br />
<center>/greeting端点的CPU使用率</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-9.png" alt="" /><br />
<center>/greeting端点的内存使用情况</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-10.png" alt="" /><br />
<center>/greeting端点处理的每秒请求数</center></p>
<ul>
<li>/fibonacci/{number}</li>
</ul>
<p>该端点接受一个段路径参数{number}，并返回序列化为JSON格式的斐波纳契数和输入数。</p>
<p>对于这个特定的端点，我选择以递归形式实现它。我毫不怀疑，迭代实现会产生更好的性能结果，并且出于生产目的，应该选择一种迭代形式，但是在生产代码中，有些情况下必须使用递归（并非专门用于计算第n个斐波那契数 ）。为此，我希望该实现涉及大量CPU栈分配。</p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-11.png" alt="" /><br />
<center>/fibonacci端点的CPU使用率</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-12.png" alt="" /><br />
<center>/fibonacci端点的内存使用情况</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-13.png" alt="" /><br />
<center>/fibonacci端点处理的每秒请求数</center></p>
<p>在Fibonacci端点测试期间，Java是唯一一个有150个请求超时的实现，如下面wrk的输出所示。</p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-14.png" alt="" /><br />
<center>超时时间</center></p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-15.png" alt="" /><br />
<center>/fibonacci端点的延迟</center></p>
<h2>运行时大小</h2>
<p>为了模拟现实世界中的云原生应用程序，并避免“它仅可以在我的机器上运行！”，我分别为这三个应用程序创建了一个docker镜像。</p>
<p>Docker文件的源代码包含在代码库相应程序文件夹下。</p>
<p>作为我使用过的Java应用程序的基础镜像，openjdk:8-jre-alpine是已知大小最小的镜像之一，但是，这附带了一些警告，这些警告可能适用于您的应用程序，也可能不适用于您的应用程序，主要是alpine镜像在处理环境变量名称方面不是posix兼容的，因此您不能在Dockerfile中使用ENV中的（点）字符（不过这没什么大不了的），另一个是alpine Linux镜像是使用musl libc而不是<a href="https://tonybai.com/tag/glibc">glibc</a>编译的，这意味着如果您的应用程序依赖于需要glibc，它可能无法正常工作。不过，在这里，alpine镜像工作是正常的。</p>
<p>至于应用程序的Go版本和Rust版本，我已经对其进行了静态编译，这意味着它们不希望在运行时镜像中存在libc（glibc，musl…等），这也意味着它们不需要运行OS的基本镜像。因此，我使用了scratch docker镜像，这是一个no-op镜像，以零开销托管已编译的可执行文件。</p>
<p>我使用的Docker镜像的命名约定为{lang}/webservice。该应用程序的Java，Go和Rust版本的镜像大小分别为113、8.68和4.24 MB。</p>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-16.png" alt="" /><br />
<center>最终Docker镜像大小</center></p>
<h2>结论</h2>
<p><img src="https://tonybai.com/wp-content/uploads/java-vs-go-vs-rust/comparison-between-java-go-and-rust-17.png" alt="" /><br />
<center>三种语言的比较</center></p>
<p>在得出任何结论之前，我想指出这三种语言之间的关系。Java和Go都是支持垃圾回收的语言，但是Java会提前编译为在JVM上运行的字节码。启动Java应用程序时，JIT编译器会被调用以通过将字节码编译为本地代码来优化字节码，以提高应用程序的性能。</p>
<p>Go和Rust都提前编译为本地代码，并且在运行时不会进行进一步的优化。</p>
<p>Java和Go都是支持垃圾收集的语言，具有<strong>STW(停止世界)</strong>的副作用。这意味着，每当垃圾收集器运行时，它将停止应用程序，进行垃圾收集，并在完成后从停止的地方恢复应用程序。大多数垃圾收集器需要停止运行，但是有些实现似乎不需要这样做。</p>
<p>当Java语言在90年代创建时，其最大的卖点之一是<strong>一次编写，可在任何地方运行</strong>。当时这非常好，因为市场上没有很多虚拟化解决方案。如今，大多数CPU支持虚拟化，这种虚拟化抵消了使用某种语言进行开发的诱惑(该语言承诺可以运行在任何平台上)。Docker和其他解决方案以更为低廉的代价提供虚拟化。</p>
<p>在整个测试中，应用程序的Java版本比Go或Rust对应版本消耗了更多的内存，在前两个测试中，Java使用的内存大约增加了8000％。这意味着对于实际应用程序，Java应用程序的运行成本会更高。</p>
<p>对于前两个测试，Go应用程序使用的CPU比Java少20％，同时处理比java版多出38％的请求。另一方面，Rust版本使用的CPU比Go减少了57％，而处理的请求却增加了13％。</p>
<p>第三次测试在设计上是占用大量CPU的资源，因此我想从中挤出CPU的每一分。Go和Rust都比Java多使用了1％的CPU。而且我认为，如果wrk不是在同一台计算机上运行，那么这三个版本都会使CPU达到100%的上限值。在内存方面，Java使用的内存比Go和Rust多2000％。Java可以处理的请求比Go多出20％，而Rust可以处理的请求比Java多出15％。</p>
<p>在撰写本文时，Java编程语言已经存在了将近30年，这使得在市场上寻找Java开发人员变得相对容易。另一方面，Go和Rust都是相对较新的语言，因此与Java相比，自然而然的开发人员的数量更少些。不过，Go和Rust都拥有很大的吸引力，许多开发人员正在将它们用于新项目，并且有许多使用Go和Rust的生产中正在运行的项目，因为简单地说，就资源而言，它们比Java更有效。</p>
<p>在编写本文的程序时，我同时学习了Go和Rust。就我而言，Go的学习曲线很短，因为它是一种相对容易掌握的语言，并且与其他语言相比语法很小。我只用了几天就用Go编写了程序。关于Go需要注意的一件事是编译速度，我不得不承认，与Java/C/C++/Rust等其他语言相比，它的速度非常快。该程序的Rust版本花了我大约一个星期的时间来完成，我不得不说，大部分时间都花在弄清borrow checker向我要什么上。Rust具有严格的所有权规则，但是一旦掌握了Rust的所有权和借用概念，编译器错误消息就会突然变得更加有意义。违反借阅检查规则时，Rust编译器对您大吼的原因是因为编译器希望在编译时证明已分配内存的寿命和所有权。这样做可以保证程序的安全性（例如：没有悬挂的指针，除非使用了不安全(unsafe)的代码逃离检查），并且在编译时确定了释放位置，从而消除了垃圾收集器的需求和运行时成本。当然，这是以学习Rust的所有权系统为代价的。</p>
<p>在竞争方面，我认为Go是Java（通常是JVM语言）的直接竞争对手，但不是Rust的竞争对手。另一方面，Rust是Java，Go，C和C ++的重要竞争对手。</p>
<p>由于他们的效率，我看到了自己将会在Go和Rust中编写更多的程序，但是很可能在Rust中编写更多的程序。两者都非常适合Web服务，CLI，系统程序（..etc）开发。但是，Rust比Go具有根本优势。它不是垃圾收集的语言，与C和C++相比，它可以安全地编写代码。例如，Go并不是特别适合用于编写OS内核，而这里又是Rust的亮点，并与C/C ++竞争，因为它们是使用OS编写的长期存在和事实上的语言。Rust与C/C++竞争的另一种方式在嵌入式世界中，我将继续进行讨论。</p>
<p>感谢您的阅读！</p>
<p>本文翻译自<a href="https://medium.com/@dexterdarwich/comparison-between-java-go-and-rust-fdb21bd5fb7c">《Comparison between Java, Go, and Rust》</a>。</p>
<hr />
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/05/01/comparison-between-java-go-and-rust/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>图解git原理的几个关键概念</title>
		<link>https://tonybai.com/2020/04/07/illustrated-tale-of-git-internal-key-concepts/</link>
		<comments>https://tonybai.com/2020/04/07/illustrated-tale-of-git-internal-key-concepts/#comments</comments>
		<pubDate>Tue, 07 Apr 2020 15:10:37 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[blob]]></category>
		<category><![CDATA[branch]]></category>
		<category><![CDATA[clone]]></category>
		<category><![CDATA[Commit]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[hash]]></category>
		<category><![CDATA[hub]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[MerkleTree]]></category>
		<category><![CDATA[progit]]></category>
		<category><![CDATA[pull]]></category>
		<category><![CDATA[push]]></category>
		<category><![CDATA[SHA-1]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[svn]]></category>
		<category><![CDATA[tag]]></category>
		<category><![CDATA[Tree]]></category>
		<category><![CDATA[trunk]]></category>
		<category><![CDATA[upstream]]></category>
		<category><![CDATA[vcs]]></category>
		<category><![CDATA[以太网]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[分支]]></category>
		<category><![CDATA[哈希值]]></category>
		<category><![CDATA[标签]]></category>
		<category><![CDATA[版本控制]]></category>
		<category><![CDATA[版本管理]]></category>
		<category><![CDATA[集线器]]></category>
		<category><![CDATA[默克尔树]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2884</guid>
		<description><![CDATA[git是那个“爱骂人”的Linux之父Linus Torvalds继Linux内核后奉献给全世界程序员的第二个礼物（不能确定已经逐渐老去的Torvalds能否迸发第三春，第三次给我们一个超大惊喜^_^）。这里再强调一下，git读作/git/，而不是/dʒit/。 在诞生十余载后(2005年发布第一版)，git毫无争议地成为了程序员版本管理工具的首选，它改变了全世界程序员的代码版本管理和生产协作的模式，极大促进了开源软件运动的发展。进化到今天的git已经成为了一个比较复杂的工具，多数程序员都将目光聚焦在如何记住这些命令并用好这些命令，对这些复杂命令行背后的原理却知之不多，虽然大多数程序员的确不太需要深刻了解git背后的原理^_^。 关于git原理的文章在互联网上也呈现出“汗牛充栋”之势，有些文章“蜻蜓点水”，有些文章“事无巨细”，看后似乎都无法让我满意。结合自己对git原理的学习，我觉得多数人把握住git运作机制的几个关键概念即可，于是就有了这篇文章，我努力尝试给大家讲清楚。 一. 我就是仓库，我拥有全部 我们首先要明确一个git与先前的版本管理工具（主要是subversion）的不同。下面是使用subversion版本管理工具时，程序员进行代码生产以及程序员间围绕代码仓库进行协作的模式： 图：subversion代码生产和协作模式 众所周知，subversion是基于中心版本仓库进行版本管理协作的版本管理工具。就像上图中那样，所有开发人员开始生产代码的前提是必须先从中心仓库checkout一份代码拷贝到自己本地的工作目录；而进行版本管理操作或者与他人进行协作的前提也是：中心版本仓库必须始终可用。这有点像以太网的“半双工的集线器(hub)模式”：svn中心仓库就像集线器本身，每个程序员节点就像连接到集线器上的主机；当一个程序员提交(commit)代码到中心仓库时，其他程序员不能提交，否则会出现冲突；如果中心仓库挂掉了，那么整个版本管理过程也将停止，程序员节点间无法进行协作，这就像集线器(hub)挂掉后，所有连接到hub上的主机节点间的网络也就断开无法相互通信一样。 如果我们使用git，我们是不需要“集线器”的： 图：git代码生产和协作模式 如上图所示，git号称分布式版本管理系统，本质上是没有像subversion中那个所谓的“中心仓库”的。每个程序员都拥有一个本地git仓库，而不仅仅是一份代码拷贝，这个仓库就是一个独立的版本管理节点，它拥有程序员进行代码生产、版本管理、与其他程序员协作的全部信息。即便在一台没有网络连接的机器上，程序员也能利用该仓库完成代码生产和版本管理工作。在网络ready的情况下，任意两个git仓库之间可以进行点对点的协作，这种协作无需中间协调者(中心仓库)参与。 二. github实现了基于git网络协作的控制平面 git实现了分布式版本管理系统，每个git仓库节点都是自治的。诸多git仓库节点一起形成了一个分布式git版本管理网络。这样的一个分布式网络存在着与普通分布式系统的类似的问题：如何发现对端节点的git仓库、如何管理和控制仓库间的访问权限等。如果说linus的git本身是这个分布式网络的数据平面工具(实现client/server间的双向数据通信)，那么这个分布式网络还缺少一个“控制平面”。 而github恰恰给出了一份git分布式网络控制平面的实现：托管、发现、控制&#8230;。其名称中含有的“hub”字样让我们想起了上面的“hub模式”： 图：github：git分布式网络控制平面的实现 我们看到在github的git协作模式实践中，引入了“中心仓库”的概念，各个程序员的节点git仓库源于(clone于)中心仓库。但是它和subversion的“中心仓库”有着本质的不同，这个仓库只是一个“upstream”库、是一个权威库。它并不是“集线器”，也没有按照“集线器”的那种工作模式进行协作。所有程序员节点的代码生产和版本管理操作完全可以脱离该所谓“中心库”而独立实施。 三. objects是个筐，什么都往里面装 上面都是从“宏观”谈git的一些与众不同的理念，而git原理，其实是从这一节才真正开始的^_^。 我们知道：每个git仓库的所有数据都存储在仓库顶层路径下的.git目录下： $tree -L 1 -F . ├── COMMIT_EDITMSG ├── HEAD ├── config ├── description ├── hooks/ ├── index ├── info/ ├── logs/ ├── objects/ └── refs/ 5 directories, 5 files 而在这些目录和文件中，又以objects路径下的数据内容最多，也最为重要。在git的设计中，objects目录就是一个“筐”，git的核心对象(object)都往里面“装”。 图：git核心数据对象类型与objects目录 从上图中，我们看到objects中存储的最主要的有三类对象：blob、commit和tree。这时你可能还不知道它们究竟是啥。不过没关系，我们通过一个例子来做一下“对号入座”。 我们在一个目录下建立git-internal-repo-demo目录，进入该目录，执行下面命令创建一个git仓库： [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-0.png" alt="img{512x368}" /></p>
<p><a href="https://git-scm.com/">git</a>是那个“爱骂人”的<a href="https://www.kernel.org/">Linux</a>之父<a href="https://github.com/torvalds">Linus Torvalds</a>继Linux内核后奉献给全世界程序员的第二个礼物（不能确定已经逐渐老去的Torvalds能否迸发第三春，第三次给我们一个超大惊喜^_^）。这里再强调一下，git读作<strong>/git/</strong>，而不是<strong>/dʒit/</strong>。</p>
<p>在诞生十余载后(2005年发布第一版)，<strong>git</strong>毫无争议地成为了程序员版本管理工具的首选，它改变了全世界程序员的<strong>代码版本管理和生产协作的模式</strong>，极大促进了开源软件运动的发展。进化到今天的<strong>git</strong>已经成为了一个比较复杂的工具，多数程序员都将目光聚焦在如何记住这些命令并用好这些命令，对这些复杂命令行背后的原理却知之不多，虽然大多数程序员的确不太需要深刻了解git背后的原理^_^。</p>
<p>关于git原理的文章在互联网上也呈现出“汗牛充栋”之势，有些文章“蜻蜓点水”，有些文章“事无巨细”，看后似乎都无法让我满意。结合自己对git原理的学习，我觉得多数人把握住<strong>git运作机制</strong>的几个关键概念即可，于是就有了这篇文章，我努力尝试给大家讲清楚。</p>
<h2>一. 我就是仓库，<strong>我拥有全部</strong></h2>
<p>我们首先要明确一个git与先前的版本管理工具（主要是<a href="https://subversion.apache.org/">subversion</a>）的不同。下面是使用<a href="https://tonybai.com/tag/svn">subversion</a>版本管理工具时，程序员进行代码生产以及程序员间围绕代码仓库进行协作的模式：</p>
<p><img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-1.png" alt="img{512x368}" /></p>
<p><center>图：subversion代码生产和协作模式</center></p>
<p>众所周知，subversion是<strong>基于中心版本仓库进行版本管理协作的版本管理工具</strong>。就像上图中那样，所有开发人员开始生产代码的前提是<strong>必须先从中心仓库checkout一份代码拷贝到自己本地的工作目录</strong>；而进行版本管理操作或者与他人进行协作的前提也是：<strong>中心版本仓库必须始终可用</strong>。这有点像以太网的“半双工的集线器(hub)模式”：svn中心仓库就像集线器本身，每个程序员节点就像连接到集线器上的主机；当一个程序员提交(commit)代码到中心仓库时，其他程序员不能提交，否则会出现冲突；如果中心仓库挂掉了，那么整个版本管理过程也将停止，程序员节点间无法进行协作，这就像集线器(hub)挂掉后，所有连接到hub上的主机节点间的网络也就断开无法相互通信一样。</p>
<p>如果我们使用git，我们是<strong>不需要“集线器”</strong>的：</p>
<p><img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-2.png" alt="img{512x368}" /></p>
<p><center>图：git代码生产和协作模式</center></p>
<p>如上图所示，git号称分布式版本管理系统，本质上是没有像subversion中那个所谓的“中心仓库”的。<strong>每个程序员都拥有一个本地git仓库</strong>，而不仅仅是一份代码拷贝，这个仓库就是一个<strong>独立的版本管理节点</strong>，它拥有程序员进行代码生产、版本管理、与其他程序员协作的<strong>全部信息</strong>。即便在一台没有网络连接的机器上，程序员也能利用该仓库完成代码生产和版本管理工作。在网络ready的情况下，任意两个git仓库之间可以进行点对点的协作，这种协作无需中间协调者(中心仓库)参与。</p>
<h2>二. github实现了基于git网络协作的<strong>控制平面</strong></h2>
<p>git实现了分布式版本管理系统，每个git仓库节点都是自治的。诸多git仓库节点一起形成了一个<strong>分布式git版本管理网络</strong>。这样的一个分布式网络存在着与普通分布式系统的类似的问题：如何发现对端节点的git仓库、如何管理和控制仓库间的访问权限等。如果说linus的git本身是这个分布式网络的数据平面工具(实现client/server间的双向数据通信)，那么这个分布式网络还缺少一个<strong>“控制平面”</strong>。</p>
<p>而<a href="https://github.com">github</a>恰恰给出了一份git分布式网络控制平面的实现：托管、发现、控制&#8230;。其名称中含有的“hub”字样让我们想起了上面的“hub模式”：</p>
<p><img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-3.png" alt="img{512x368}" /></p>
<p><center>图：github：git分布式网络控制平面的实现</center></p>
<p>我们看到在github的git协作模式实践中，引入了“中心仓库”的概念，各个程序员的节点git仓库源于(clone于)中心仓库。但是它和subversion的“中心仓库”有着本质的不同，这个仓库只是一个“upstream”库、是一个权威库。它并不是“集线器”，也没有按照“集线器”的那种工作模式进行协作。所有程序员节点的代码生产和版本管理操作完全可以脱离该所谓“中心库”而独立实施。</p>
<h2>三. <strong>objects</strong>是个筐，什么都往里面装</h2>
<p>上面都是从“宏观”谈git的一些与众不同的理念，而git原理，其实是从这一节才真正开始的^_^。</p>
<p>我们知道：每个git仓库的所有数据都存储在仓库顶层路径下的<strong>.git</strong>目录下：</p>
<pre><code>$tree -L 1 -F
.
├── COMMIT_EDITMSG
├── HEAD
├── config
├── description
├── hooks/
├── index
├── info/
├── logs/
├── objects/
└── refs/

5 directories, 5 files

</code></pre>
<p>而在这些目录和文件中，又以<strong>objects</strong>路径下的数据内容最多，也最为重要。在git的设计中，<strong>objects目录就是一个“筐”，git的核心对象(object)都往里面“装”</strong>。<br />
<img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-4.png" alt="img{512x368}" /></p>
<p><center>图：git核心数据对象类型与objects目录</center></p>
<p>从上图中，我们看到objects中存储的最主要的有三类对象：blob、commit和tree。这时你可能还不知道它们究竟是啥。不过没关系，我们通过一个例子来做一下“对号入座”。</p>
<p>我们在一个目录下建立git-internal-repo-demo目录，进入该目录，执行下面命令创建一个git仓库：</p>
<pre><code>➜  /Users/tonybai/test/git/git-internal-repo-demo git:(master) ✗ $git init .
Initialized empty Git repository in /Users/tonybai/Test/git/git-internal-repo-demo/.git/

</code></pre>
<p>这是一个处于初始状态的git仓库，我们看看存储git仓库数据的<strong>.git</strong>目录下的结构：</p>
<pre><code>➜  /Users/tonybai/test/git/git-internal-repo-demo git:(master) $tree .git
.git
├── HEAD
├── config
├── description
├── hooks
│   ├── applypatch-msg.sample
│   ├── commit-msg.sample
│   ├── fsmonitor-watchman.sample
│   ├── post-update.sample
│   ├── pre-applypatch.sample
│   ├── pre-commit.sample
│   ├── pre-push.sample
│   ├── pre-rebase.sample
│   ├── pre-receive.sample
│   ├── prepare-commit-msg.sample
│   └── update.sample
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs
    ├── heads
    └── tags

8 directories, 15 files

</code></pre>
<p>这个时候，<strong>objects这个筐还是空的</strong>！我们这就为仓库添点内容：</p>
<pre><code>$mkdir -p cmd/demo

在cmd/demo目录下添加main.go文件，内容如下:

// cmd/demo/main.go
package main

import "fmt"

func main() {
    fmt.Println("hello, git")
}

</code></pre>
<p>接下来我们使用git add将cmd/demo目录加入到stage区：</p>
<pre><code>$git add .

$git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached &lt;file&gt;..." to unstage)

    new file:   cmd/demo/main.go

</code></pre>
<p>这时我们来看一下objects这个筐是否有变化：</p>
<pre><code>├── objects
│   ├── 3e
│   │   └── 759ef88951df9b9b07077a7ec01f96b8e659b3
│   ├── info
│   └── pack

</code></pre>
<p>我们有一个object已经被装入到“筐”中了。我们看到objects目录下是一些以哈希值命名的文件和目录，其中目录由两个字符组成，是每个object hash值的前两个字符。hash值后续的字符串用于命名对应的object文件。在这里我们的object的hash值(实质是sha-1算法)为3e759ef88951df9b9b07077a7ec01f96b8e659b3，于是这个对象就被放入名为3e的目录下，对应的object文件为759ef88951df9b9b07077a7ec01f96b8e659b3。</p>
<p>我们使用git提供的<strong>低级命令</strong>查看一下这个object究竟是什么，其中git cat-file -t查看object的类型，git cat-file -p查看object的内容：</p>
<pre><code>$git cat-file -t 3e759ef889
blob

$git cat-file -p 3e759ef889
package main

import "fmt"

func main() {
    fmt.Println("hello, git")
}
</code></pre>
<p>我们看到objects这个筐中多了一个blob类型的对象，对象内容就是前面main.go文件中内容。</p>
<p>接下来，我们提交一下这次变更：</p>
<pre><code>$git commit -m"first commit" .
[master (root-commit) 3062e0e] first commit
 1 file changed, 7 insertions(+)
 create mode 100644 cmd/demo/main.go

</code></pre>
<p>再来看看<strong>.git/objects</strong>中的变化：</p>
<pre><code>├── objects
│   ├── 1f
│   │   └── 51fe448aacc69c0f799def9506e61ed3eb60fa
│   ├── 30
│   │   └── 62e0ebad9415b704e96e5cee1542187b7ed571
│   ├── 3d
│   │   └── 2045367ea40c098ec5c7688119d72d97fb09a5
│   ├── 3e
│   │   └── 759ef88951df9b9b07077a7ec01f96b8e659b3
│   ├── 40
│   │   └── 6d08e1159e03ae82bcdbe1ad9f076a04a41e2b
│   ├── info
│   └── pack

</code></pre>
<p>我们看到筐里被一下子新塞入4个object。我们分别看看新增的4个object类型和内容都是什么：</p>
<pre><code>$git cat-file -t 1f51fe448a
tree
$git cat-file -p 1f51fe448a
100644 blob 3e759ef88951df9b9b07077a7ec01f96b8e659b3    main.go

$git cat-file -t 3062e0ebad
commit
$git cat-file -p 3062e0ebad
tree 406d08e1159e03ae82bcdbe1ad9f076a04a41e2b
author Tony Bai &lt;bigwhite.cn@aliyun.com&gt; 1586243612 +0800
committer Tony Bai &lt;bigwhite.cn@aliyun.com&gt; 1586243612 +0800

first commit

$git cat-file -t 3d2045367e
tree
$git cat-file -p 3d2045367e
040000 tree 1f51fe448aacc69c0f799def9506e61ed3eb60fa    demo

$git cat-file -t 406d08e115
tree
$git cat-file -p 406d08e115
040000 tree 3d2045367ea40c098ec5c7688119d72d97fb09a5    cmd

</code></pre>
<p>这里我们看到了另外两种类型的object被加入“筐”中：commit和tree类型。objects这个筐里目前有了5个object，我们不考虑git是以何种格式存储这些object的，我们想知道的是这几个object的关系是什么样的。请看下一小节^_^。</p>
<h2>四. 每个<strong>commit</strong>都是一个git仓库的快照</h2>
<p>要理清objects“筐”中各object间的关系，就必须要把握住一个关键概念：“每个<strong>commit</strong>都是git仓库的一个快照” &#8211; 以一个commit为入口，我们能将当时objects下面的所有object联系在一起。因此，上面5个object中的那个commit对象就是我们分析各object关系的入口。我们根据上述5个object的内容将这5个object的关系组织为下面这幅示意图：</p>
<p><img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-5.png" alt="img{512x368}" /></p>
<p><center>图：commit、tree、blob对象之间的关系</center></p>
<p>通过上图我们看到：</p>
<ul>
<li>
<p>commit是对象关系图的入口；</p>
</li>
<li>
<p>tree对象用于描述目录结构，每个目录节点都会用一个tree对象表示。目录间、目录文件间的层次关系会在tree对象的内容中体现；</p>
</li>
<li>
<p>每个commit都会有一个root tree对象；</p>
</li>
<li>
<p>blob对象为tree的叶子节点，它的内容即为文件的内容。</p>
</li>
</ul>
<p>上面仅是一次commit后的关系图，为了更清晰的看到多个commit对象之间关系，我们再来对git repo进行一次变更提交:</p>
<pre><code>我们创建pkg/foo目录：

$mkdir -p pkg/foo

然后创建文件pkg/foo/foo.go，其内容如下：

// pkg/foo/foo.go
package foo

import "fmt"

func Foo() {
    fmt.Println("this is foo package")
}

</code></pre>
<p>提交这次变更：</p>
<pre><code>$git add pkg
$git commit -m"add package foo" .
[master 6f7f08b] add package foo
 1 file changed, 7 insertions(+)
 create mode 100644 pkg/foo/foo.go
</code></pre>
<p>下面是提交变更后的“筐”内的对象：</p>
<pre><code>$tree objects
objects
├── 1f
│   └── 51fe448aacc69c0f799def9506e61ed3eb60fa
├── 29
│   └── 3ae375dcef1952c88f35dd4d2a1d4576dea8ba
├── 30
│   └── 62e0ebad9415b704e96e5cee1542187b7ed571
├── 3d
│   └── 2045367ea40c098ec5c7688119d72d97fb09a5
├── 3e
│   └── 759ef88951df9b9b07077a7ec01f96b8e659b3
├── 40
│   └── 6d08e1159e03ae82bcdbe1ad9f076a04a41e2b
├── 65
│   └── 5dd3aae645813dc53834ebfa8d19608c4b3905
├── 6e
│   └── e873d9c7ca19c7fe609c9e1a963df8d000282b
├── 6f
│   └── 7f08b14168beb114c3cc099b8dc1c09ccd4739
├── cc
│   └── 9903a33cb99ae02a9cb648bcf4a71815be3474
├── info
└── pack

12 directories, 10 files

</code></pre>
<p>object已经多到不便逐一分析了。但我们把握住一点：<strong>commit是分析关系的入口</strong>。我们通过commit的输出或commit log(git log)可知，新增的commit对象的hash值为6f7f08b141。我们还是以它为入口分析新增object的关系以及它们与之前已存在的object的关系：</p>
<p><img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-6.png" alt="img{512x368}" /></p>
<p><center>图：commit、tree、blob对象之间的关系1</center></p>
<p>从上图我们看到：</p>
<ul>
<li>
<p>git新创建tree对象对应我们新建的pkg目录以及其子目录；</p>
</li>
<li>
<p>cmd目录下的子目录和文件内容并未改变，因此这次commit所对应的root tree对象(293ae375dc)直接使用了已存在的cmd目录对应的对象(3d2045367e);</p>
</li>
<li>
<p>新commit对象会将第一个commit对象作为parent，这样多个commit对象之间构成一个单向链表。</p>
</li>
</ul>
<p>上面的两个提交都是新增内容，我们再来提交一个commit，这次我们对已有文件内容做变更：</p>
<pre><code>将cmd/demo/main.go文件内容变更为如下内容：

// cmd/demo/main.go
package main

import (
    "fmt"

    "github.com/bigwhite/foo"
)

func main() {
    fmt.Println("hello, git")
    foo.Foo()
}

提交变更：

$git commit -m"call foo.Foo in main" .
[master 2f14635] call foo.Foo in main
 1 file changed, 6 insertions(+), 1 deletion(-)

</code></pre>
<p>和上面的分析方法一样，我们通过最新commit对应的hash值2f146359b4对新对象和现存对象的关系进行分析：</p>
<p><img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-7.png" alt="img{512x368}" /></p>
<p><center>图：commit、tree、blob对象之间的关系2</center></p>
<p>如上图，第三次变更提交后，我们看到：</p>
<ul>
<li>
<p>由于main.go文件变更，git重建了main.go blob对象、demo、cmd tree对象</p>
</li>
<li>
<p>由于pkg目录、其子目录布局、子目录下文件内容没有改变，于是新commit对象对应的root tree对象直接“复用”了上一次commit的pkg tree对象。</p>
</li>
<li>
<p>新commit对象加入commit对象单向链表，并将上一次的commit对象作为parent。</p>
</li>
</ul>
<p>我们看到沿着最新的commit对象(2f146359b4)，我们能获取当前仓库的最新结构布局以及各个blob对象的最新内容，即最新的一个快照！</p>
<h2>五. object是不可变的，默克尔树(Merkle Tree)判断变化</h2>
<p>从上面的三次变更，我们看到无论哪种对象object，<strong>一旦放入到objects这个“筐”就是不可变的(immutable)</strong>。即便是第三次commit对main.go进行了修改，git也只是根据main.go的最新内容<strong>创建一个新的blob对象</strong>，而不是修改或替换掉第一版main.go对应的blob对象。</p>
<p>对应目录的tree object亦是如此。如果某目录下的二级目录发生变化或目录下的文件内容发生改变，git会新生成一个对应该目录的tree对象，而不是去修改原先已存在的tree对象。</p>
<p>实际上，git tree对象的组织本身就是一棵<a href="http://en.wikipedia.org/wiki/Merkle_tree">默克尔树(Merkle Tree)</a>。</p>
<p>默克尔树是一类基于哈希值的二叉树或多叉树，其叶子节点上的值通常为数据块的哈希值，而非叶子节点上的值，是将该节点的所有孩子节点的组合结果的哈希值。默克尔树的特点是，底层数据的任何变动，都会传递到其父亲节点，一直到树根。</p>
<p><img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-merkle-tree.png" alt="img{512x368}" /></p>
<p><center>图：默克尔树(图片来自网络)</center></p>
<p>以上图为例：我们自下向上看，D0、D1、D2和D3是叶子节点包含的数据。N0、N1、N2和N3是叶子节点，它们是将数据（也就是D0、D1、D2和D3）进行hash运算后得到的hash值；继续往上看，N4和N5是中间节点，N4是N0和N1经过hash运算得到的哈希值，N5是N2和N3经过hash运算得到的哈希值。（注意，hash值计算方法：把相邻的两个叶子结点合并成一个字符串，然后运算这个字符串的哈希）。最后，Root节点是N4和N5经过hash运算后得到的哈希值，这就是这颗默克尔树的根哈希。当N0包含的数据发生变化时，根据默克尔树的节点hash值形成机制，我们可以快速判断出：<strong>N0、N4和root节点会发生变化</strong>。</p>
<p>对应git来说，叶子节点对应的就是每个文件的hash值，tree对象对应的是中间节点。因此，通过默克尔树(Merkle Tree)的特性，我们可以快速判断哪些对象对应的目录或文件发生了变化，应该重新创建对应的object。我们还以上面的第三次commit为例：</p>
<p><img src="https://tonybai.com/wp-content/uploads/illustrated-tale-of-git-internal-key-concepts/illustrated-tale-of-git-internal-key-concepts-8.png" alt="img{512x368}" /></p>
<p><center>图：通过默克尔树(Merkle Tree)的特性判断哪些对象发生变化需要重新创建</center><br />
如上图所示，第三次commit是因为cmd/demo/main.go内容发生了变化，根据merkle tree特性，我们可以快速判断红色的object会随之发生变化。于是git会自底向上逐一创建这些新对象：main.go文件对应的blob对象以及demo、cmd以及根节点对应的tree对象。</p>
<h2>六. branch和tag之所以轻量，因为它们都是“指针”</h2>
<p>使用subversion时，创建branch或打tag使用的是svn copy命令。svn copy执行的就是真实的文件拷贝，相当于将trunk下的目录和文件copy一份放到branch或tag下面，建立一个trunk的副本，这样的操作绝对是“超重量级”的。如果svn仓库中的文件数量庞大且size很大，那么svn copy执行起来不仅速度慢，而且还会在svn server上占用较大的磁盘存储空间，因此使用svn时，打tag和创建branch是要“谨慎”的。</p>
<p>而git的branch和tag则极为轻量，我们来给上面例子中的仓库创建一个dev分支：</p>
<pre><code>$git branch dev

</code></pre>
<p>我们看看.git下有啥变化：</p>
<pre><code>.

└── refs
    ├── heads
    │   ├── dev
    │   └── master
    └── tags

</code></pre>
<p>我们看到.git/refs/heads下面多出了一个dev文件，我们查看一下该文件的内容：</p>
<pre><code>$cat refs/heads/dev
2f146359b475909f2fdcdef046af3431c8077282

$git log --oneline

2f14635 (HEAD -&gt; master, dev) call foo.Foo in main
6f7f08b add package foo
3062e0e first commit

</code></pre>
<p>对比发现，dev文件中的内容恰是最新的commit对象：2f146359b475909f2fdcdef046af3431c8077282。</p>
<p>我们再来给repo打一个tag：</p>
<pre><code>$git tag v0.0.1

</code></pre>
<p>同样，我们来查看一下.git目录下的变化：</p>
<pre><code>└── refs
    ├── heads
    │   ├── dev
    │   └── master
    └── tags
        └── v0.0.1

</code></pre>
<p>我们看到在refs/tags下面增加一个名为v0.0.1的文件，查看其内容：</p>
<pre><code>$cat refs/tags/v0.0.1
2f146359b475909f2fdcdef046af3431c8077282

</code></pre>
<p>和dev分支文件一样，它的内容也是最新的commit对象：2f146359b475909f2fdcdef046af3431c8077282。</p>
<p>可见，使用git创建分支或tag仅仅是创建了一个指向某个commit对象的<strong>“指针”</strong>，这与subversion的副本操作相比，简直不能再轻量了。</p>
<p>前面说过，一个commit对象都是一个git仓库的快照，切换到(git checkout xxx)某个branch或tag，就是将本地工作拷贝切换到commit对象所代表的仓库快照的状态。当然也会将commit对象组成的单向链表的head指向该commit对象，这个head即.git/HEAD文件的内容。</p>
<h2>七. 小结</h2>
<p>到这里，git原理的几个关键概念就交代完了，再回顾一下：</p>
<ul>
<li>
<p>和subversion这样的集中式版本管理工具最大的不同就是每个程序员节点都是git仓库，拥有全部开发、协作所需的全部信息，完全可以脱离“中心节点”；</p>
</li>
<li>
<p>如果说git聚焦于数据平面的功能，那么github则是一个基于git网络协作的<strong>控制平面</strong>的实现；</p>
</li>
<li>
<p><strong>objects</strong>是个筐，什么都往里面装。git仓库的核心数据都存在.git/objects下面，主要类型包括：blob、tree和commit；</p>
</li>
<li>
<p>每个<strong>commit</strong>都是一个git仓库的快照，记住commit对象是分析对象关系的入口;</p>
</li>
<li>
<p>git是基于数据内容的hash值做等值判定的，object是不可变的，默克尔树(Merkle Tree)用来快速判断变化。</p>
</li>
<li>
<p>branch和tag因为是“指针”，因此创建、销毁和切换都非常轻量。</p>
</li>
</ul>
<h2>八. 参考资料</h2>
<ul>
<li>
<p><a href="https://git-scm.com/book/en/v2">Pro Git v2</a> &#8211; https://git-scm.com/book/en/v2</p>
</li>
<li>
<p><a href="https://www.cnblogs.com/kisun168/p/11408346.html">git介绍</a> &#8211; https://www.cnblogs.com/kisun168/p/11408346.html</p>
</li>
<li>
<p><a href="https://zhuanlan.zhihu.com/p/53750883">git内部原理</a> &#8211; https://zhuanlan.zhihu.com/p/53750883</p>
</li>
<li>
<p><a href="https://www.jianshu.com/p/72f9f8c9c47e">git仓库内部结构</a> &#8211; https://www.jianshu.com/p/72f9f8c9c47e</p>
</li>
</ul>
<hr />
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2020, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2020/04/07/illustrated-tale-of-git-internal-key-concepts/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
