<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>kirito の site</title>
  
  
  <link href="https://kiritosuki.top/atom.xml" rel="self"/>
  
  <link href="https://kiritosuki.top/"/>
  <updated>2026-03-18T13:34:06.142Z</updated>
  <id>https://kiritosuki.top/</id>
  
  <author>
    <name>kirito.</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>9.raft共识性算法-Part.1</title>
    <link href="https://kiritosuki.top/2026/9.raft%E5%85%B1%E8%AF%86%E6%80%A7%E7%AE%97%E6%B3%95-Part1/"/>
    <id>https://kiritosuki.top/2026/9.raft%E5%85%B1%E8%AF%86%E6%80%A7%E7%AE%97%E6%B3%95-Part1/</id>
    <published>2026-03-17T14:23:00.000Z</published>
    <updated>2026-03-18T13:34:06.142Z</updated>
    
    <content type="html"><![CDATA[<h1 id="raft共识性算法-Part-1"><a href="#raft共识性算法-Part-1" class="headerlink" title="raft共识性算法-Part.1"></a>raft共识性算法-Part.1</h1><p>最近在做 MIT 6.824 的 lab，其中 lab3 要求我们基于给出的代码框架，实现自己的 raft 算法。做该 lab 的时候遇到了不少困难，这是一个相当有趣又充满挑战的任务，有些地方必须要反复阅读论文并揣摩其中的意思才能理解。</p><p>如果你也在做这个 lab，不建议用 AI 逃课，论文中的 Part5 是必须要读的，否则实现代码的时候思路会很混乱。</p><p>论文连接：<a href="https://raft.github.io/raft.pdf">https://raft.github.io/raft.pdf</a></p><p>本篇文章主要聚焦于论文的第五部分，也就是 raft 算法的核心实现部分给出个人的理解。</p><h2 id="The-Raft-consensus-algorithm-核心总结"><a href="#The-Raft-consensus-algorithm-核心总结" class="headerlink" title="The Raft consensus algorithm 核心总结"></a>The Raft consensus algorithm 核心总结</h2><p><img src="https://files.seeusercontent.com/2026/03/17/C9qd/pasted-image-1773748135654.webp" alt="figer2"></p><p>这张图（原文 Figure2）简明地总结了该算法，也是全文最重要的部分。总结为下面的文字部分：</p><h3 id="State"><a href="#State" class="headerlink" title="State"></a>State</h3><p>所有服务器上的持久化状态（在响应 RPC 之前就更新到稳定存储）：</p><ul><li>currentTerm：服务器看到的最新 term（初始为0，之后单调递增）</li><li>voteFor：在当前term下，服务器把票投给了谁？candidateId</li><li>log[]：log entries；其中每个 log entry 包括 - command（要发给状态机的命令）、term（leader 收到 log entry 时的 term）</li></ul><p>所有服务器上的易变状态：</p><ul><li>commitIndex：提交过的 log entry 的最高索引 index（commit 标识着共识，committed log entry 最终会被执行）</li><li>lastApplied：已应用于状态机的 log entry 的最高索引 index（apply 表示真正执行了）</li></ul><p>leader 上的易变状态（选举后重置）：</p><ul><li>nextIndex[]：对每个服务器 i，nextIndex[i] 是下一个应该发送到该服务器的 log entry 的索引 index</li><li>matchIndex[]：对每个服务器 i，matchIndex[i] 是已在该服务器上复制过的 log entry 的最高索引 index（初始为0，之后单调递增）</li></ul><h3 id="RequestVote-RPC（candidate-用它来请求其他-server-投票）"><a href="#RequestVote-RPC（candidate-用它来请求其他-server-投票）" class="headerlink" title="RequestVote RPC（candidate 用它来请求其他 server 投票）"></a>RequestVote RPC（candidate 用它来请求其他 server 投票）</h3><p>Arguments：</p><ul><li>term：candidate 的 term</li><li>candidateId</li><li>lastLogIndex：candidate 的最新 log entry 的索引 index</li><li>lastLogTerm：candidate 的最新 log entry 的 term</li></ul><p>Results：</p><ul><li>term：currentTerm，用于 candidate 发现自己可能不是最新的，更新自己</li><li>voteGranted：bool，是否收到该服务器的票</li></ul><p>接收方需要实现：</p><ol><li>如果 term &lt; currentTerm，返回 false</li><li>如果 voteFor &#x3D;&#x3D; null or voteFor &#x3D;&#x3D; candidateId，并且 candidate 的 log 与接收方的 log <strong>一样新</strong>，就投票给它</li></ol><p>这里的<strong>一样新</strong>：谁的 log 更新，取决于谁的 term 更新，如果 term 相同，取决于 index 更大，一样新就是 log 的 term 和 index 都一样</p><h3 id="AppendEntries-RPC（leader-用它来追加日志和发送心跳）"><a href="#AppendEntries-RPC（leader-用它来追加日志和发送心跳）" class="headerlink" title="AppendEntries RPC（leader 用它来追加日志和发送心跳）"></a>AppendEntries RPC（leader 用它来追加日志和发送心跳）</h3><p>Arguments：</p><ul><li>term：leader 的 term</li><li>leaderId：便于将 client 打到 follower 的请求重定向给 leader</li><li>prevLogIndex：leader 要发送新的 log entry 给 follower，prevLogIndex 是紧接着这些要发送的日志的前一条 log entry 的索引</li><li>prevLogTerm：和上一条类似，prevLogIndex 索引处的 log entry 的 term</li><li>entries[]：要发送的 log entries（心跳时为空，为了提高效率可能一次性发送多个日志条目）</li><li>leaderCommit：leader 的 commitIndex</li></ul><p>Results：</p><ul><li>term：currentTerm，用于 leader 发现自己可能不是最新的，更新自己</li><li>success：bool，判断 follower 的 log entry 是否匹配 prevLogIndex 和 prevLogTerm</li></ul><p>接收方需要实现：</p><ol><li>如果 term &lt; currentTerm，返回 false</li><li>如果接收方在匹配的 prevLogTerm 下 log entry 的索引不与 prevLogIndex 匹配，返回 false</li><li>如果已经存在 log entry 和发送方发送的 log entry 冲突（索引相同但是 term 不同），删除已经存在的 log entry 及其后面的所有 log entries</li><li>添加 log 中不存在的任何新的 log entries</li><li>如果 leaderCommit &gt; commitIndex，更新接收方的 commitIndex &#x3D; min(leaderCommit, 最新 log entry 的 index)</li></ol><h3 id="Rules-for-Servers"><a href="#Rules-for-Servers" class="headerlink" title="Rules for Servers"></a>Rules for Servers</h3><p>All Servers:</p><ol><li>如果 commitIndex &gt; lastApplied：递增 lastApplied，状态机执行这些 log[lastApplied]</li><li>如果 RPC request or reply 中的 term &gt; currentTerm：更新当前服务器 currentTerm &#x3D; term，降级为 follower</li></ol><p>Followers：</p><ol><li>响应 RPC from candidate and leader</li><li>如果选举计时器超时：convert to candidate</li></ol><p>Candidates：</p><ol><li>升级为 candidates 时，发起选举：<ul><li>currentTerm++</li><li>给自己投一票</li><li>重置选举计时器</li><li>发送 RequestVote RPC 给其他所有的服务器</li></ul></li><li>如果收到了超过半数服务器的票，成为 leader</li><li>如果收到了来自另外一个新 leader 的 AppendEntries RPC，降级为 follower（新 leader：指 term 新）</li><li>如果选举计时器超时：发起新的选举</li></ol><p>Leader：</p><ol><li>选举时：不断发送心跳给每个服务器，以防选举计时器超时</li><li>如果从客户端收到了命令：添加 log entry 到本地 log，在 log entry 被实际执行之后再返回response</li><li>对于要发送的 follower，如果 leader 最新的 log entry 索引 &gt;&#x3D; nextIndex[i]（说明 follower 落后），发送从 nextIndex[i] 索引开始的log entries AppendEntries RPC <ul><li>如果成功发送，更新 nextIndex[i] 和 matchIndex[i]</li><li>如果由于日志不一致导致发送失败，递减 nextIndex 然后重试</li></ul></li><li>如果存在 N，满足 N &gt; commitIndex &amp;&amp; 超过半数的 matchIndex[i] &gt;&#x3D; N &amp;&amp;  log[N].term &#x3D;&#x3D; currentTerm，设置 commitIndex &#x3D; N</li></ol><h2 id="始终成立的属性"><a href="#始终成立的属性" class="headerlink" title="始终成立的属性"></a>始终成立的属性</h2><p>原文 Figure3 的内容：</p><ol><li>Election Safety：在同一 term 最多只有一个 leader 当选</li><li>Leader Append-Only：leader 从来不会覆盖或者删除 log 中已经存在的条目，只会追加新的条目</li><li>Log Matching：如果两个 log 包含了一个相同 term 且相同 index 的条目，那么在该索引之前的所有条目也全部相同</li><li>Leader Completeness：如果在一个给定的 term 内提交了一个 log entry，那么后续其他更高 term 的 leader 的日志中都会包含该 log entry</li><li>State Machine Safety：如果一个服务器执行了一条给定索引处的 log entry，那么其他服务器永远不会在这个索引处执行不同的 log entry</li></ol><h2 id="Raft-basics-5-1"><a href="#Raft-basics-5-1" class="headerlink" title="Raft basics 5.1"></a>Raft basics 5.1</h2><p>一个 raft 集群包含一些服务器，5个是一个经典的数字，可以容忍有两个服务器出错。在任何时间，每个服务器都处于三个状态中的一种：leader，follower，candidate。在正常情况下，有一个服务器是 leader，其他都是 follower。follower 是被动的：他们不会主动发送请求，只会简单的响应从 leader 和 candidate 发送的请求。leader 处理客户端的所有请求（如果一个客户端尝试与 follower 通信，follower 会把请求重定向到 leader）。第三种状态：candidate，用于选举新的 leader。下图展示了状态转换情况：</p><img src="https://files.seeusercontent.com/2026/03/17/b8gQ/pasted-image-1773749462954.webp" alt="pasted-image-1773749462954.webp" style="zoom:67%;" /><blockquote><p>服务器状态图。followers 只会接受其他服务器的请求，如果一个 follower 接受不到任何联系，他会变成 candidate 并发起一次选举。candidate 如果收到了集群中过半服务器的选票就会变成新的 leader。leader 通常会一直运行直到自身故障。</p></blockquote><p>raft 把时间分成特定长度的 terms，如下图所示：</p><img src="https://files.seeusercontent.com/2026/03/17/d0qA/pasted-image-1773749755142.webp" alt="pasted-image-1773749755142.webp" style="zoom:67%;" /><blockquote><p>时间被分成了 terms，每个 term 开始于一次选举。在一轮选举成功之后，单个 leader 会管理整个集群直到这个 term 结束（term 没有固定时长的，如果系统运行正常，那么就会一直处于这个 term）。如果选举失败了，那么该 term 这段时期没有 leader（下个 term 会重新选举）。terms 的转换可能在不同的服务器上于不同的时间被观察到。</p></blockquote><p>Terms 被用连续的整数编号，每个 term 开始于一次选举，在这次选举内，一个或者多个 candidate 尝试竞争成为 leader。如果其中一个 candidate 赢得了选举，他就会成为这个 term 剩余期间的 leader。在某些情况下，一次选举可能会发生分票。此时，这个 term 可能会没有 leader，一个新的 term（便随着一个新的选举）将会很快发生。raft 确保在一个 term 内最多只有一个 leader。</p><p>不同的服务器可能在不同的时间观察到 terms 的转换，在某些情况下一个服务器可能没有观察到选举，甚至没有观察到整个 term。terms 在 raft 中扮演着逻辑时钟的角色，允许服务器检测过时的信息，比如过期的leader。每个服务器存储了 currentTerm 字段（自增字段）。任何情况下，服务器之间通信都需要交换 currentTerm 信息，如果一个服务器的 currentTerm 比其他服务器小，那么它就会更新它的 currentTerm 为更大值。如果一个 candidate 或者 leader 发现它的 currentTerm 过期了，他会立刻降级为 follower。如果一个服务器接受了一个带有过期 term 的请求，他会拒绝这次请求。</p><p>raft 服务器通过 RPCs 相互通信，基础的共识性算法只需要两种 RPCs（RequestVote 和 AppendEntries）。RequestVote RPCs 在选举时被 candidate 执行；AppendEntries RPCs 被 leader 执行，用于复制日志或者提供心跳给其他服务器。高阶段提供了第三种 RPCs 用于在服务器之间做快照传输。如果服务器在规定时间内没有收到响应，它们会重试 RPCs 请求。为了更好的性能，服务器通常并行发送 RPCs 请求。</p><h2 id="Leader-election-5-2"><a href="#Leader-election-5-2" class="headerlink" title="Leader election 5.2"></a>Leader election 5.2</h2><p>Raft 使用心跳机制来触发选举。当服务器启动的时候，它们最初作为 followers，只要服务器能从 leader 或 candidate 收到合法的 RPCs 请求，就会一直保持 follower 状态。leader 定期发送心跳（不带有日志条目的 AppendEntries RPCs）给所有 followers 以维护它们的状态。如果一个 follower 在一定时间内（称为 election timeout）没有收到请求，它就会认为 leader 出故障了，然后发起新的一轮选举来选出一个新的 leader</p><p>开启一次选举时，follower 会自增它的 currentTerm，然后转化为 candidate。他会先给自己投一票，然后通过并发的 RequestVote RPCs 请求，发送请求给集群中的所有其他服务器，candidate 会保持 candidate 状态，直到出现下面三种情况：</p><ol><li>它赢得了选举</li><li>另一个服务器成为了 leader</li><li>一段时间过去了，选举no winner</li></ol><p>以下段落将分别讨论这三种情况：</p><ol><li>candidate 赢得了选举条件是他收到了当前 term 下整个集群中的大多数选票（过半的票）。每个 server 在一个 term 中最多给 candidate 投一票，基于先到先得原则（5.4 添加了投票的额外限制）。过半原则确保了在一个 term 内最多只有一个 candidate 会赢得选举。一旦一个 caniddate 赢得了选举，就会变为 leader。然后这个 leader 对其他的所有服务器发送心跳来建立起他的 leader 身份，并且防止新的选举</li><li>在等待其他服务器投票的过程中，candidate 可能收到来自另外一个server的 AppendEntries RPC，这个 server 宣称自己已经成为了 leader。如果这个 leader 的 term（RPC 的参数）比当前 candidate 的 term 更大或相等，candidate 会认可这个 leader，自己降级为 follower。如果 RPC 参数中的 term 比当前 candidate 的 term 小，candidate 会拒绝这次 RPC 请求然后继续保持 candidate 状态</li><li>如果 candidate 即没有 win 也没有 lose 这次选举：因为很多 followers 在同一时间成为 candidate，出现了分票，导致没有 candidate 能获取过半的票。这种情况下，每个 candidate 将会超时过期，然后开始一轮新的选举（term++），并执行另外一次 RequestVote RPCs。然而，如果不采取额外措施，这种分票现象可能会不断重复出现。</li></ol><p>raft 通过随机的 election timeout 来确保分票现象是低概率的，并且可以被快速的修复。为了避免第一次分票现象，election out 被设置为一个固定区间内的随机值（例如：150 - 300 ms）。这样可以分散服务器，便于在大多数情况下，仅仅只有一个服务器会超时。它赢得了选举然后在其他服务器触发超时之前发送心跳。同样的机制也被用于处理已经发生的分票现象，每个 candidate 在一次选举开始时重置它的随机 election timeout，等待其中一个 candidate 的 election timeout 超时，然后开启下一次选举。</p><h2 id="Log-replication-5-3"><a href="#Log-replication-5-3" class="headerlink" title="Log replication 5.3"></a>Log replication 5.3</h2><p>一旦 leader 被选举产生，它就开始接受客户端的请求。每个客户端请求包含一条将会被复制状态机执行的命令，leader 把命令作为一个新的 log entry 添加到 log 中。然后并发向其他的服务器发送 AppendEntries RPCs 来复制这条 log entry。当这条 entry 被<strong>安全地复制了</strong>（下文有提及），leader 会执行这条 enrty 然后返回执行结果给客户端。如果 followers 崩溃了或者运行缓慢，或者网络包丢失，leader 会无限期地重新尝试发送 AppendEntries RPCs （即便是在 leader 已经响应了客户端之后），直到所有的 follower 最终都储存了所有日志条目。</p><p>logs 用下图所示的方式组织：</p><img src="https://files.seeusercontent.com/2026/03/17/2oaN/pasted-image-1773755884683.webp" alt="pasted-image-1773755884683.webp" style="zoom:67%;" /><blockquote><p>logs 有许多条目（entries）组成，这些条目按次序编号。每个 enrty 包含了他被创建时的 term（图中方框内的数字）和一条指令（用于给状态机执行）。如果一个 entry 可以被状态机安全地执行，这条 entry 被认为是 committed 的。</p></blockquote><p>每个 log entry 存储了状态机指令和 term（当这条 entry 被 leader 接受时的 leader term）。log entry 中的 term 用于检测日志之间的不一致，确保 Figure3 中的某些属性成立。每个 log entry 同样有一个整数索引来明确他在该 log 中的位置。</p><p>leader 决定了何时为在状态机上执行 log entry 的<strong>安全的</strong>时机，此时的 log entry 被视为 committed 的。raft 保证了 committed entries 具有持久性，确保这些 entries 最终会被所有可用的状态机执行。一旦创建这条 log entry 的 leader 把它复制到了过半的服务器上，这条 log entry 就是 committed 的（例如上图中的 entry 7，在 3 个 server 中存储）。这也会提交 leader 的 log 中的所有先前的 entries，包括被先前旧 leader 创建的 entries。这种提交是安全的。leader 会跟踪它所知的被提交过的 log entry 的最高索引，并在未来的 AppendEntries RPCs（包括心跳）中包含该索引，以便其他服务器最终能够获知。一旦 follower 知道一条 log entry is committed，它就会将这条 entry 在本地的状态机上执行（按照 log 的顺序）。</p><p>我们设计的 raft 日志机制旨在在不同的服务器上维持日志的高一致性。这不仅简化了系统的行为，使其更加容易预测，而且这是确保安全性的重要组成部分。raft 维护了以下属性，这些属性构成了 Figure3 种的日志匹配属性：</p><ul><li>如果在不同 log 中的 两个 entries 有相同的索引和 term，则它们存储相同的命令</li><li>如果在不同 log 中的 两个 entries 有相同的索引和 term，则 log 在此之前的所有 entries 都是相同的</li></ul><p>第一条属性来源于以下事实：一个 leader 基于给定的 index 和 term 最多创建一个 entry，并且 entry 在 log 中的 位置永远不会改变。第二条属性来源于：AppendEntries 的简单一致性检查。当发送一个 AppendEntries RPC 时，leader 会在发送信息中包含 prevLogIndex 和 prevLogTerm（prevLogIndex 和 prevLogTerm 是 leader 的 log 中，他接下来要发送的新 entries 的前一条 entry 的索引和 term）。如果 follower 在自己的 log 中没有找到给定 prevLogIndex 和 prevLogTerm 对应的 entry，她就会拒绝这些新的 entries。一致性检查起到归纳步骤的作用：初始 logs 的空状态满足 Log Matching Property，一致性检查在 logs 扩展时维持了 Log Matching Property。结果，只要 AppendEntries 返回 success，leader 就会知道 follower 的 log 和 leader 自身的 log 在新的 entries 之前完全相同。</p><p>通常情况下，leader 和 follower 的日志是保持一致的，因此 AppendEntries 的一致性检查将永远不会失败。然而，leader 故障可能会导致日志的不一致（旧 leader 可能没有来得及复制它 log 中的所有 entries）。这种不一致性可能会在一系列的 leader 和 follower 故障中不断累积。下图展示了 follower 和新的 leader 的 log 之间可能存在的差异。follower 中可能缺失 leader 中有的 entries，follower 中也可能包含 leader 中没有的 entries，甚至这两种情况可以同时发生。这些 log 中缺失或多余的 entries 可能跨越多个 terms。</p><img src="https://files.seeusercontent.com/2026/03/18/D8hc/pasted-image-1773796424493.webp" alt="pasted-image-1773796424493.webp" style="zoom:67%;" /><blockquote><p>当 leader 任职时，下面 a - f 的情况都可能发生在 follower 的 logs 中。每个方框代表一个 log entry，其中的数字代表这条 entry 的 term。一个 follower 可能缺少 entries（如 a - b），可能有多余的未提交的 entries（如 c - d），或者两种情况同时发生（如 e - f）。例如，情况（f）可能由以下产生：f 的服务器从 term2 开始作为 leader，在它自己的 log 中添加了一些 entries，但在提交它们之前崩溃了。它很快又重启了，重新成为了 term3 的 leader，然后又在它自己的 log 中添加了更多的 entries。但 term2 和 term3 中的 entries 都还没被来得及提交，该服务器再次崩溃并在接下来的几个 term 中持续宕机。</p></blockquote><p>在 raft 中，leader 通过强制让 follower 的 log 复制 leader 自己的 log 来解决这种不一致。这意味着 follower logs 中冲突的 entries 将会被 leader 的 log 覆盖。5.4 证明这种行为是安全的（当和另外一个限制条件结合使用）</p><p>为了让 follower 的日志和 leader 自己保持一致，leader 必须找到它与 follower 匹配的最新的 log entry，删除在此之后的 follower 的 log 中所有的 entries，然后把 leader 在此之后的所有 entries 发送给 follower。这些行为发生在响应 AppendEntries 的一致性检查的 response 中。leader 为每个 follower 维护一个 nextIndex[i] 字段，标识了 leader 将要发送给 follower 的下一个 entry 的索引。当 leader 第一次任职时，他会初始化所有 nextIndex 的值为它最新 log entry 索引的下一个索引（以上图为例，nextIndex[i]就是 11）。如果 follower 的 log 和 leader 不一致（一致的判断是通过 AppendEntries 中的 prevLogIndex 和 prevLogTerm 与 follower 中的 log entries 匹配），在下一次 AppendEntries RPC 中 AppendEntries 一致性检查会失败。在拒绝 AppendEntries 之后，leader 会递减 nextIndex[i] 然后重试 AppendEntries RPC。最终 nextIndex[i] 会到达一个索引，使 leader 和 follower 的 logs 相匹配，此时 AppendEntries 会成功，这会移除 follower 的 log 中后面所有冲突的 entries，然后从 leader 的 log 中添加 entries。一旦 AppendEntries 成功，follower 的 log 就和 leader 一致了，并在这个 term 的后续时间保持这种状态。</p><blockquote><p>如果需要，可以优化协议来减少 AppendEntries RPCs 的拒绝次数。例如，当拒绝一个 AppendEntries 请求时，follower 可以在返回值中包含冲突 entry 的 term 和这个 term 任期存储的第一个 entry 的索引。laeder 可以通过这条信息来递减 nextIndex[i] 来跳过该 term 的所有冲突 entries，而不是每个 entry 都要通过 RPC 确认一次。但实际上，这种优化不是必要的，因为故障发生的频率很低，也不会存在大量不一致的条目</p></blockquote><p>通过这种机制，当 leader 任职时，不需要采取特殊的行为来恢复日志的一致性。它只需要正常运行，日志根据给 AppendEntries 一致性检查返回的错误，自动整理汇聚。leadr 永远不会覆盖或者删除它自己已经存在的 log entries</p><p>这种日志复制机制体现了理想的共识机制特性：只要过半的服务器正常运行，raft 就可以接受、复制、执行新的 log entries。通常情况下一条新的 entry 可以通过一轮 RPCs 调用复制到集群中的过半的节点上，某个运行缓慢的 follower 不会影响整个系统的表现。</p><h2 id="Safety-5-4"><a href="#Safety-5-4" class="headerlink" title="Safety 5.4"></a>Safety 5.4</h2><p>先前的章节描述了 raft 的 leader 选举和日志复制机制。然而，这些机制目前不足以确保每个状态机都会精确地按照相同的顺序，执行相同的指令。例如，某个 follower 发生故障变得不可用，同时 leader 提交了一系列的 log entries，后来这个故障的节点恢复并被重新选举为了 leader，它追加了新的 entries。结果，不同的状态机可能会执行不同的命令序列。</p><p>这个章节完善了 raft 算法，添加了一个节点可以被选为 leader 的限制条件。这个限制条件确保了任何 term 下的 leader 拥有在此 term 之前的所有 committed entries。基于该限制条件，我们为提交制定了更精确的规则。最后，我们给出了 Leader Completeness（领导者完备属性）的证明概要，展示了它是如何引导复制状态机做出正确的行为的。</p><h3 id="Election-restriction-5-4-1"><a href="#Election-restriction-5-4-1" class="headerlink" title="Election restriction 5.4.1"></a>Election restriction 5.4.1</h3><p>在任何基于 leader 的共识算法中，leader 必须最终保存所有已经提交的 log entries。在某些共识算法中，例如 Viewstamped Replication，即使 leader 最初并不包含所有已提交的条目，也可以被选出。这些算法包含额外的机制来识别缺失的条目，并在选举过程中或选举后不久将其传输给新的领导者。然而，这会导致大量的额外机制和复杂性。raft 采用了一种更简单的方式，确保在选举之时，任何新的 leader 就都必须具备先前 terms 中所有已经提交的 entries，而不需要发送额外的 entries 给 leader。这意味着 log entries 是单向传输的，只能从 leader 传输到 follower，并且 leader 从来不会覆盖它们的 logs 中已经存在的 entries。</p><p>raft 使用投票过程来确保只有拥有全部 committed entries 的 candidate 才会被选为 leader。一个 candidate 必须与集群中的过半服务器通信才能胜出，意味着每个 committed entry 都必须出现在这些过半服务器中的至少一个。如果 candidate 的 log 至少与这些过半服务器中任何 log 一样<strong>新</strong>（新的定义见下文），那么这个 candidate 就会包含全部的 committed entries。RequestVote RPC 实现了该限制：RPC 中包含了 cadidate 的日志信息，如果 follower 发现自己的 log 比 candidate 的 log 更新，follower 就会拒绝投票。</p><p>raft 判断两个 logs 哪一个更新是通过比较最后一条 log entry 的 index 和 term，如果 last entries 有不同的 term，那么拥有更大 term 的就更新；如果 term 相同，谁的日志更长（index 更大）谁就更新</p><h3 id="Committing-entries-from-previous-terms-5-4-2"><a href="#Committing-entries-from-previous-terms-5-4-2" class="headerlink" title="Committing entries from previous terms 5.4.2"></a>Committing entries from previous terms 5.4.2</h3><p>正如像 5.3 节描述的那样，在当前 term 下，一旦一条 entry 存在于过半的服务器上，leader 就会认为这条 enrty 是 committed 的。如果 leader 在提交这条 entry 之前故障了，未来的 leader 将会尝试完成这条 entry 的复制任务。然而，新的 leader 不能因为这条 entry 在先前任期 term 中存在于过半的服务器上就认为这个 entry 在新的 term 内是 committed 的。下图展示了一种情况：旧的 log entry 存储在了过半的服务器上，但是新的 leader 仍然可能把这些 log entries 覆盖掉。</p><img src="https://files.seeusercontent.com/2026/03/18/p0uW/pasted-image-1773816575311.webp" alt="pasted-image-1773816575311.webp" style="zoom:57%;" /><blockquote><p>一种情况展示了为什么 leader 不能使用来自旧 terms 的 log entries 来确定提交状态。在（a）中，S1 是leader，把 term2 的 log entry 复制到了部分服务器上。在（b）中，S1 崩溃了，S5 被选举为了 term3 新的 leader（收到了来自 S3，S4 的选票），接着 S5 在 index &#x3D; 2 处接受到了一条新的 log entry（不同于之前的 term2，用颜色区别），在（c）中，S5 崩溃了，S1 恢复并成为了新的 leader，继续他的复制任务。在这种情况下，来自 term2 的 log entry 被复制到了过半的服务器上，但是还没有提交。此时在（d）中，S1 再次崩溃了，S5 又被选举为了新的 leader（S5 的 log entry 的 term <strong>更新</strong>），然后他开始复制日志到其他服务器上，用 term3 的日志覆盖掉了其他服务器上的日志。然而，如果 S1 在第二次崩溃之前，把 term4 的 log entry 复制到了过半的服务器上，如（e）中所示，那么这条 entry is committed，在 S1 发生第二次崩溃时，S5就无法赢得选举。这种情况下所有先前的 log entries 才会全部提交。</p></blockquote><p>为了消除图8中的情况，raft 永远不会仅仅通过复制数目提交来自先前 terms 的 log entries。只有当 log entries 来自当前 leader 任期时才会通过判断复制数目过半来提交；一旦一个来自当前 term 的 entry 被以这种方式提交，那么所有先前的的 entries 都也会被间接提交（由于 Log Matching 属性）。有一些这样的情况：leader 可以安全地推断出一条旧的 log entry 是 committed 的（比如所有服务器上都储存了这条 log entry），但是 raft 采用了更加保守的简化方式。</p><h3 id="Safety-argument-5-4-3"><a href="#Safety-argument-5-4-3" class="headerlink" title="Safety argument 5.4.3"></a>Safety argument 5.4.3</h3><p>这部分是关于 leader completeness 的证明，感兴趣可以自己看原文。</p><h2 id="Follower-and-candidate-crashes-5-5"><a href="#Follower-and-candidate-crashes-5-5" class="headerlink" title="Follower and candidate crashes 5.5"></a>Follower and candidate crashes 5.5</h2><p>目前为止我们聚焦于 leader 故障。相对于 leader 故障，follower 和 candidate 的故障更加简单，它们被用同样的方式来处理。如果一个 follower 或者 candidate 发生故障，未来发送到该服务器的 RequestVote 和 AppendEntries RPCs 将会失败。raft 通过无限期不断重试来解决这些故障。如果故障的服务器重启，RPC 就会成功完成。如果一个服务器在处理 RPC 成功，但是在 responde RPC 之前故障，它将会在重启后收到一个相同的 RPC 请求。raft RPCs 是<strong>幂等</strong>的，因此这不会有错。例如，一个 follower 收到了一个 AppendEntries 请求，请求包含了已经存在于该服务器的 log entries，服务器会简单的忽略这些 entries。</p><h2 id="Timing-and-availability-5-6"><a href="#Timing-and-availability-5-6" class="headerlink" title="Timing and availability 5.6"></a>Timing and availability 5.6</h2><p>我们对 raft 的要求之一是：safty 必须不能依赖时间。系统不能因为某些事件发生的比预期时间快或者慢就产生错误的结果。然而，可用性（系统及时响应客户端的能力）不可避免地依赖于时间。例如，如果信息交换花费了太长时间，超过了服务器通常崩溃之间的时间，candidate 将没有足够的时间来赢得一次选举。没有稳定的 leader，系统就会不可用。</p><p>raft 中时间是很关键的，leader 选举只体现了其中一方面。只要系统满足下面的时间要求，raft 将会顺利选举并维护一个 leader：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">broadcastTime &lt;= electionTimeout &lt;= MTBF</span><br></pre></td></tr></table></figure><ul><li>broadcastTime 是一个 server 并行向集群中所有 server 发送 RPCs 并收到回复所需要的平均时间</li><li>electionTimeout 是选举计时器的超时时间</li><li>MTBF 是每两个服务器发生故障的平均时间</li></ul><p>broadcastTime 应该比 electionTimeout 小一个数量级，以便 leader 可以稳定发送心跳来防止 follower 发起新的选举；由于 electionTimeout 采用随机生成的方式，这种不确定性也降低了分票的概率。electionTimeout 应该比 MTBF 小几个数量级以便系统可以稳定运行。当 leader 故障时，系统将会有一段时间变得不可用，大概是 electionTimeout 的时长，我们希望这段时间只占据系统全部运行时间的一小部分。</p><p>broadcastTime 和 MTBF 是系统的潜在属性，而 electionTimeout 是我们可以实际控制的。raft 的 RPCs 通常需要接受者把信息持久化到稳定存储，因此 broadcastTime 可能在 0.5 —— 20 ms 范围内，取决于存储技术。结果，electionTimeout 可能会在 10 —— 500 ms 范围内。通常 MTBFs 长达几个月甚至更长，很容易满足时间规定要求。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;raft共识性算法-Part-1&quot;&gt;&lt;a href=&quot;#raft共识性算法-Part-1&quot; class=&quot;headerlink&quot; title=&quot;raft共识性算法-Part.1&quot;&gt;&lt;/a&gt;raft共识性算法-Part.1&lt;/h1&gt;&lt;p&gt;最近在做 MIT 6.824</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>8.Linux-Part.3</title>
    <link href="https://kiritosuki.top/2026/8.Linux-Part3/"/>
    <id>https://kiritosuki.top/2026/8.Linux-Part3/</id>
    <published>2026-03-17T02:23:00.000Z</published>
    <updated>2026-03-18T12:05:45.057Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Linux-Part-3"><a href="#Linux-Part-3" class="headerlink" title="Linux-Part.3"></a>Linux-Part.3</h1><p>TODO</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Linux-Part-3&quot;&gt;&lt;a href=&quot;#Linux-Part-3&quot; class=&quot;headerlink&quot; title=&quot;Linux-Part.3&quot;&gt;&lt;/a&gt;Linux-Part.3&lt;/h1&gt;&lt;p&gt;TODO&lt;/p&gt;
</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>7.Linux-Part.2</title>
    <link href="https://kiritosuki.top/2026/7.Linux-Part.2/"/>
    <id>https://kiritosuki.top/2026/7.Linux-Part.2/</id>
    <published>2026-02-28T02:23:00.000Z</published>
    <updated>2026-03-03T02:43:14.729Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Linux-Part-2"><a href="#Linux-Part-2" class="headerlink" title="Linux-Part.2"></a>Linux-Part.2</h1><p>Linux 总是无处不在的，或许我对他并不陌生，但从未系统学习过，这篇文章将记录我系统学习 Linux 的过程经历，遇到的问题，总结的笔记，便于我留存记录，也供大家参考学习</p><p>学习路线可参考：<a href="https://labex.io/zh/linuxjourney">https://labex.io/zh/linuxjourney</a></p><h2 id="设备"><a href="#设备" class="headerlink" title="设备"></a>设备</h2><ol><li><p><code>/dev</code> 目录</p><p>在 Linux 中，连接到系统的各种硬件设备（键盘、硬盘等），都由一个特殊文件表示，这些文件被称为设备文件，位于 <code>/dev</code> 目录下，可以使用正常的标准命令来和这些文件进行交互</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls /dev</span><br></pre></td></tr></table></figure><p>这将显示许多条目，每个条目对应一个硬件设备或者虚拟设备，例如我们之前已经与 <code>/dev/null</code> 这个虚拟设备进行过交互</p><p>早期的 <code>/dev</code> 目录是静态的，所有可能的硬件的设备文件都是在安装过程中创建的，导致可能某个设备文件对应于甚至不存在的硬件。此外，在重启系统时根据内核检测设备的顺序，设备名称可能会发生变化，导致设备文件的配置出现混乱</p><p>现代的 <code>/dev</code> 目录使用动态方法，根据硬件的连接状态动态的创建和删除设备文件，确保 <code>/dev</code> 只包含当前正在使用的设备的相应文件，并提供了一个持久的命名方案，使系统更可靠、更易于管理</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls -l /dev</span><br></pre></td></tr></table></figure><p>使用长选项查看 <code>/dev</code> 目录的文件信息，文件类型不仅以 <code>-</code> 表示，设备文件的独特文件类型有助于我们识别：</p><ul><li><code>c</code>：字符设备。这些设备一次传输一个字符的数据，许多伪设备被表示为字符设备，例如 <code>/dev/null</code></li><li><code>b</code>：块设备。这些设备以固定大小的数据块进行传输，例如硬盘等，他们对基于块的数据访问进行了优化</li><li><code>p</code>：管道。FIFO（先进先出），允许进程间通信，作用类似字符设备，但他将输出引导到另一个进程而不是设备</li><li><code>s</code>：套接字（socket）。也促进了进程间通信，更加通用，支持多个进程之间的通信，甚至跨网络通信</li></ul><p>原本的文件大小字段被替换成了两个字段，表示主设备号和次设备号，例如对于 <code>8, 0</code> 的设备，8 通常用于表示 SCSI 磁盘驱动器，而 0 表示这个驱动程序的特定实例，0 通常表示第一个驱动器</p><p>关于常见的设备名称，参考：<a href="https://labex.io/zh/lesson/device-names">https://labex.io/zh/lesson/device-names</a></p></li><li><p><code>/sys</code> 目录</p><p><code>sysfs</code> 是一个虚拟文件系统，通常挂载在 <code>/sys</code>，它将内核对象、硬件设备和驱动程序的信息从内核模型导出到用户空间。<code>/sys</code> 目录的主要作用是提供对系统上所有设备的结构化视图，<code>/sys</code> 能反映出内核和设备的当前状态，当内核与设备状态发生变化时，<code>/sys</code> 能即时反应这种变化</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ ls -l /dev/sda                                                                   </span><br><span class="line">brw-rw---- 1 root disk 8, 0  3月  1 09:12 /dev/sda</span><br><span class="line">  ~/mysrc ❯ ls /sys/block/sda                                                                 </span><br><span class="line">alignment_offset  dev                diskseq       events_poll_msecs  holders    mq        queue      ro     sda16   stat       uevent</span><br><span class="line">bdi               device             events        ext_range          inflight   partscan  range      sda1   size    subsystem</span><br><span class="line">capability        discard_alignment  events_async  hidden             integrity  power     removable  sda15  slaves  trace</span><br></pre></td></tr></table></figure><p>可以看到，<code>/sys</code> 比 <code>/dev</code> 提供了更详细的视图，他们作用不同，<code>/dev</code> 提供设备节点，允许用户通过这些设备文件与设备交互；<code>/sys</code> 用于查看有关设备的详细信息，暴露设备的底层模型</p></li><li><p><code>udev</code></p><p>在旧的 Linux 系统中，我们可以手动创建设备节点：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mknod /dev/sdb1 b 8 3</span><br></pre></td></tr></table></figure><p>如果要删除设备，只需要在 <code>/dev</code> 目录中 <code>rm</code> 该设备文件</p><p>现在的 Linux 版本中，我们不需要这样做，<code>udev</code> 系统会根据设备是否连接来动态地为我们创建和删除设备文件。系统上运行着一个 <code>udev</code> 守护进程，它监听来自内核中关于连接到系统的设备的信息，依据 <code>/etc/udev/rules.d</code> 中的规则自动创建和删除设备文件。我们也可以编写自己的 udev 规则（通常不需要）</p><p><img src="https://files.seeusercontent.com/2026/03/01/oVa0/pasted-image-1772333464956.webp" alt="pasted-image-1772333464956.webp"></p><p>你还可以使用 <code>udevadm info</code> 命令查看设备的详细信息，例如：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">udevadm info --query=all --name=/dev/sda</span><br></pre></td></tr></table></figure></li><li><p>列出硬件设备</p><p>类似 <code>ls</code> 列出文件，第三方工具提供了 ls 的衍生命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">lsusb# sudo apt install usbutils</span><br><span class="line">lspci# sudo apt install pciutils</span><br><span class="line">lsscsi# sudo apt install lsscsi</span><br></pre></td></tr></table></figure><ul><li>USB 设备：通常指一些支持热插拔的外设，例如U盘、鼠标、摄像头等</li><li>PCI 设备：直接连接在主板上的高速总线设备，例如显卡、网络设备器、声卡等</li><li>SCSI 设备：Linux SCSI 子系统管理的存储设备（即使是 SATA、USB 等现代存储设备，Linux 也使用 SCSI 子系统统一管理），大多数存储设备设备都采用 SCSI 协议管理</li></ul></li><li><p><code>dd</code></p><p><code>dd</code> 是一个强大而危险的命令，可以用来复制文件内容（按字节复制）</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dd if=~/mysrc/pic/kirito.jpg of=~/mysrc/pic/kirito-cp.jpg bs=1024</span><br></pre></td></tr></table></figure><p>这个会将 <code>kirito.jpg</code> 的内容复制到 <code>kirito-cp.jpg</code> 中，它每次读取 1024 个字节，循环完成复制整个文件</p><ul><li><code>if</code>：in file，表示输入文件</li><li><code>of</code>：out file，表示输出文件</li><li><code>bs</code>：每次传输的数据大小，默认是 512 字节（b），可以使用后缀表示更大的单位，例如 1k, 1M, 1G</li><li><code>count</code>：如果你不想复制整个文件，可以用 <code>count = 2</code> 来制定复制 2 块</li></ul><p><code>dd</code> 可以很方便的用来备份服务器数据，备份和恢复磁盘数据，但要注意输入命令的正确性，防止数据丢失</p></li></ol><h2 id="文件系统"><a href="#文件系统" class="headerlink" title="文件系统"></a>文件系统</h2><ol><li><p>文件系统层次结构</p><p>大家都知道 Linux 的文件系统是树状的，具体常见目录如下：</p><p><strong>根目录</strong></p><ul><li><code>/</code>：根目录，无须多言</li></ul><p><strong>关键系统目录</strong></p><ul><li><code>bin</code>：包含所有用户可以使用的基本命令程序（二进制文件），如 <code>ls</code> 等</li><li><code>sbin</code>：存放基本的系统二进制文件，这些文件主要由系统管理员使用，通常只能由 root 运行</li><li><code>/etc</code>：核心系统配置文件目录，包含操作系统和已安装应用程序的配置文件，不应该包含任何可执行的二进制文件</li><li><code>/boot</code>：存放系统启动过程中所需要的文件，包括 Linux 内核和引导加载程序文件</li></ul><p><strong>用户和应用程序数据目录</strong></p><ul><li><code>/home</code>：每个用户的个人目录，存放你的文档、应用程序设置和其他个人文件</li><li><code>/root</code> ：root 用户的家目录</li><li><code>/opt</code>：用于存放完整的第三方应用程序软件包（不由发行版“官方仓库策略”管理，自己包含完整目录）</li><li><code>/usr</code>：包含用户安装的各种软件和应用程序。<code>/usr</code> 有它自己的目录结构，例如 <code>/usr/bin</code> 下存放发行版提供的二进制文件，例如使用包管理器安装的 java 二进制文件位于 <code>/usr/bin/java</code>，而 <code>/usr/local</code> 下常存放从外部源下载的软件，例如下载的 <code>golanag.tar.gz</code> 常解压到 <code>/usr/local/go</code>中，以及 <code>/usr/local/bin/kubectl</code>，还有自己手动编译的 <code>/usr/local/bin/hello</code></li></ul><p><strong>动态和临时目录</strong></p><ul><li><code>/var</code>：表示“可变”目录，存储预期会发生大小和内容变化的文件，比如 <code>/var/log/</code> 目录下的日志文件，还有缓存文件，假脱机文件（系统为了“排队等待处理”的任务而创建的临时文件）等。<code>/var/www</code> 目录是 Linux 的 Web 服务默认静态文件的存放位置，Apache 和 Nginx 的默认挂载点（Nginx 也常用 <code>/usr/share</code>）</li><li><code>/tmp</code>：存放临时文件，每个人都可以写入，且不能删除和移动其他人的文件。重启系统后该目录中的文件通常会被删除</li><li><code>/run</code>：包含自上次启动以来，有关正在运行的系统的信息，例如进程 PID 等</li></ul><p><strong>设备和挂载点目录</strong></p><ul><li><code>/dev</code>：设备文件，上有介绍</li><li><code>/media</code>：可移动媒体（如 USB 驱动器、SD 卡等）的标准挂载点</li><li><code>/mnt</code>：临时挂载文件系统的通用挂载点（一般要手动挂载）</li></ul><p><strong>系统信息目录</strong></p><ul><li><code>/proc</code>：虚拟文件系统，提供内核和进程的实时详细信息，part1 有介绍</li><li><code>/srv</code>：用于提供一个目录，存放“对外提供的服务”的数据，一般普通用户不需要在这里存放个人资料</li></ul></li><li><p>文件系统类型</p><p>使用 <code>df -T</code> 命令，<code>df</code> 命令报告文件系统磁盘空间使用情况，<code>-T</code> 标志专门显示文件系统类型</p><p>文件系统是操作系统用来管理存储设备上的数据等一套规则和结构，一些常见的 Linux 文件系统类型：</p><ul><li><code>ext4</code>：Linux 扩展文件系统（extended file system）的最新版本，是许多 Linux 发行版的默认设置。它向后兼容前身版本（ext3 ext2），支持非常大的磁盘卷</li><li><code>Btrfs</code>：B 树文件系统（B-tree file system），一种现代文件系统，具有内置快照、备份、改善性能等高级功能，作为某些发行版的默认文件系统，仍在积极开发中</li><li><code>XFS</code>：一种高性能的日志文件系统，在处理大文件和并行 I &#x2F; O 操作方面表现出色。它是管理大量数据的系统的绝佳选择（如媒体服务器）</li><li><code>NTFS 和 FAT</code>：标准的 windows 文件系统类型，Linux 对他们的读写完全支持，对双系统启动十分有用</li><li><code>HFS+</code>：macOS 的主要文件系统类型，Linux 默认对其只提供只读支持</li></ul><p>有着许多不同的文件系统实现，Linux 采用**虚拟文件系统（VFS）**来与他们进行统一交互，VFS 是 Linux 内核中的一个抽象层，位于应用程序和各种文件系统之间。它提供了一个单一的、统一的接口，确保应用程序可以无缝工作，而无需关心底层的文件系统类型</p><p>大多数现代文件系统默认包含日志记录功能，要理解它的重要性，想象一下在您电脑突然断电时复制一个大文件。在非日志文件系统中，这种中断可能导致文件损坏和文件系统状态不一致。重新启动后，您的系统需要执行完整的磁盘检查 (fsck)，这对于大容量磁盘来说可能非常耗时。日志文件系统可以防止这个问题。在执行写入操作之前，它首先将预期的更改记录在一个特殊的日志文件中，一旦操作成功完成，日志就会被更新以标记任务已完成。如果发生崩溃，系统可以在重新启动时简单地读取日志，查看哪些操作正在进行中，并快速将文件系统恢复到一致状态</p></li><li><p>TODO…</p></li></ol><h2 id="启动系统"><a href="#启动系统" class="headerlink" title="启动系统"></a>启动系统</h2><ol><li><p>启动系统概述</p><p>Linux 系统的启动过程可以分为四个阶段：</p><ul><li><p>Part1. BIOS</p><p>BIOS（基本输入 &#x2F; 输出系统）或 UEFI（代替传统 BIOS）是打开计算机电源时运行的第一个软件。它执行开机自检（POST）来初始化和验证CPU、内存、磁盘等系统硬件。硬件检查通过后，BIOS 的主要任务是从存储设备定位并加载<strong>引导加载程序</strong></p></li><li><p>Part2. 引导加载程序</p><p>引导加载程序从 BIOS 接管控制权。它的主要作用是将 Linux 内核加载到内存中，Linux 系统常见的引导加载程序是 GRUB，通常 GRUB 会显示一个菜单，允许你选择要启动的操作系统或内核版本。在用户选择后或超时后，它会将选定的内核和初始 RAM 加载到内存中，然后将控制权传递给<strong>内核</strong></p></li><li><p>Part3. 内核</p><p>内核一旦加载到内存中，就接管了系统的控制权。它先解压缩自身并初始化核心硬件和内存管理，然后挂载根文件系统，启动第一个进程：<code>init</code> 进程</p></li><li><p>Part4. Init</p><p><code>init</code> 进程是内核启动的第一个进程，也是系统上其他所有进程的祖先，它的工作主要是根据配置启动必要的服务和后台进程（守护进程）。<code>init</code> 有几种实现，例如传统的 <code>System V</code>、<code>Upstart</code> 和现代的 <code>systemd</code></p></li></ul></li><li><p>TODO…</p></li></ol><h2 id="内核"><a href="#内核" class="headerlink" title="内核"></a>内核</h2><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p><code>init</code> 系统的主要作用是启动和停止必要的进程。Linux 经历了三种 init 实现：<code>System V</code>、<code>Upstart</code>、&#96;&#96;</p><h2 id="进程利用率"><a href="#进程利用率" class="headerlink" title="进程利用率"></a>进程利用率</h2><h2 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h2>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Linux-Part-2&quot;&gt;&lt;a href=&quot;#Linux-Part-2&quot; class=&quot;headerlink&quot; title=&quot;Linux-Part.2&quot;&gt;&lt;/a&gt;Linux-Part.2&lt;/h1&gt;&lt;p&gt;Linux 总是无处不在的，或许我对他并不陌生，但从未系统学</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>6.Linux-Part.1</title>
    <link href="https://kiritosuki.top/2026/6.Linux-Part.1/"/>
    <id>https://kiritosuki.top/2026/6.Linux-Part.1/</id>
    <published>2026-02-23T04:17:00.000Z</published>
    <updated>2026-02-28T08:18:11.663Z</updated>
    
    <content type="html"><![CDATA[<h1 id="Linux-Part-1"><a href="#Linux-Part-1" class="headerlink" title="Linux-Part.1"></a>Linux-Part.1</h1><p>Linux 总是无处不在的，或许我对他并不陌生，但从未系统学习过，这篇文章将记录我系统学习 Linux 的过程经历，遇到的问题，总结的笔记，便于我留存记录，也供大家参考学习</p><p>学习路线可参考：<a href="https://labex.io/zh/linuxjourney">https://labex.io/zh/linuxjourney</a></p><h2 id="什么是-Linux-？"><a href="#什么是-Linux-？" class="headerlink" title="什么是 Linux ？"></a>什么是 Linux ？</h2><p>回溯到 1969 年，当时贝尔实验室的 Ken Thompson 和 Dennis Ritchie 开发了 UNIX 操作系统，十多年后，Richard Stallman 发起了 GNU 项目，目标是创建一个完全自由和开源的类 UNIX 操作系统，但统一的内核始终未能完成</p><p>内核是操作系统的核心，它充当桥梁，使硬件能够与软件通信。内核管理系统资源，例如 CPU、内存和外围设备</p><p>到 1991 年，Linus 发明了 Linux 内核，填补了 GNU 操作系统缺失的那一块拼图</p><p>尽管我们通常说 Linux 操作系统，但从技术层面来讲，Linux 指的是 Linux 内核，而使用 Linux 作为内核的操作系统，确切来说成为 Linux 的发行版</p><p>比较流行的发行版有很多，本篇文章笔者采用的是 Ubuntu 24.04.3 LTS</p><p>关于如何在 MacOS 安装 Ubuntu 虚拟机，可以参考我之前的博客：<a href="https://kirito.cloud/2025/2.%E5%88%9D%E5%A7%8B%E5%8C%96multipass%E8%99%9A%E6%8B%9F%E6%9C%BA/">https://kirito.cloud/2025/2.%E5%88%9D%E5%A7%8B%E5%8C%96multipass%E8%99%9A%E6%8B%9F%E6%9C%BA/</a></p><h2 id="命令行操作"><a href="#命令行操作" class="headerlink" title="命令行操作"></a>命令行操作</h2><h3 id="什么是-Shell-？"><a href="#什么是-Shell-？" class="headerlink" title="什么是 Shell ？"></a>什么是 Shell ？</h3><p>Shell 是一个强大的应用程序，它可以接受你输入的命令，传递给操作系统执行</p><p>或许你也听过“终端”这样的词汇，终端只是一个普通的应用程序，他的作用是为你打开一个 Shell 会话</p><p>Shell 有很多，大部分发行版的默认 Shell 是 bash ，其他常见的 Shell 还有 zsh，fish 等</p><h3 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h3><ol><li><p><code>echo</code> 输出命令行参数</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo Hello World</span><br></pre></td></tr></table></figure></li><li><p><code>pwd</code> 打印当前工作目录的绝对路径</p><p>Linux 中，整个文件目录以根目录（用<code>/</code>表示）作为顶级目录，每个子目录分支成多个子目录，形成目录树</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pwd</span><br></pre></td></tr></table></figure></li><li><p><code>cd</code> 切换工作目录</p><p>支持绝对路径和相对路径</p><ul><li><code>.</code>：当前目录</li><li><code>..</code>：当前目录的上一级目录</li><li><code>~</code>：用户目录，通常是<code>/home/xxx</code></li><li><code>-</code>：上一次你所在的目录</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd ~</span><br></pre></td></tr></table></figure></li><li><p><code>ls</code> 列出当前工作目录的目录和文件</p><p>Linux 命令都可以在后面加上选项参数，<code>--</code>后跟选项全名，<code>-</code>后跟选项简写</p><p>例如<code>ls --all</code>和<code>ls -a</code>效果是一样的</p><ul><li><p><code>ls -a</code>：列出所有目录和文件（包括隐藏文件，隐藏文件以<code>.</code>开头）</p></li><li><p><code>ls -l</code>：以长格式显示</p><p>权限 | 链接数 | 所有者名称 | 所有者组 | 文件大小 | 修改时间 | 名称</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">  ~ ❯ ls -l                                                                                </span><br><span class="line">总计 95840</span><br><span class="line">drwxrwxr-x  3 ubuntu ubuntu     4096 12月  2 12:18 go</span><br><span class="line">-rw-rw-r--  1 ubuntu ubuntu 58130616  9月 25 11:11 kubectl</span><br><span class="line">-rw-rw-r--  1 ubuntu ubuntu       64  9月 25 11:11 kubectl.sha256</span><br><span class="line">-rw-rw-r--  1 ubuntu ubuntu 39991324  9月 24 20:25 minikube_latest_arm64.deb</span><br><span class="line">drwxrwxr-x 12 ubuntu ubuntu     4096 12月  4 10:26 mysrc</span><br></pre></td></tr></table></figure></li></ul><p>不同选项也可以组合搭配使用 例如<code>ls -al</code></p><ul><li><code>ls</code> 后面跟的参数可以是目录，这样会列出该目录下的内容，也可以是文件，这样会列出这个文件本身，因此常用<code>ls -l myfile.txt</code>来查看文件的相关信息</li><li><code>ls -d</code>：把目录当成普通文件列出，而不是列出目录内容，常用<code>ls -ld mydir</code>查看目录信息</li></ul></li><li><p><code>touch</code> 更改时间戳 &#x2F; 创建新文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">touch newFile</span><br></pre></td></tr></table></figure><p>在当前目录下创建名为<code>newFile</code>的空文件，如果<code>newFile</code>已经存在，则会更新文件的时间戳</p><ul><li><code>touch -r file1 file2</code>：把<code>file2</code>的时间戳更新为<code>file1</code>的时间戳</li><li><code>touch -d &quot;2024-01-01 12:12&quot; file</code>：把<code>file</code>的时间戳更新为指定值（日期字符串格式可以是任意格式）</li></ul></li><li><p><code>file</code> 查看文件类型</p><p>与 Windows 等其他操作系统不同，Linux 并不认识文件的后缀名，文件的类型不由后缀名决定，而由文件的内容本身决定，例如：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ touch file1.png                                                                    </span><br><span class="line">  ~/mysrc ❯ echo &quot;hello&quot; &gt; file1.png                                                         </span><br><span class="line">  ~/mysrc ❯ file file1.png                                                                   </span><br><span class="line">file1.png: ASCII text</span><br></pre></td></tr></table></figure><p>这里<code>file1.png</code>并不是图片文件，而是普通的文本文件</p><p>但通常，为文件加上约定俗成的后缀名，可以便于编辑器和其他操作系统识别文件类型</p></li><li><p><code>cat</code> 查看文件内容</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat myfile</span><br></pre></td></tr></table></figure><p>会将<code>myfile</code>的内容输出到屏幕上</p><p>也可以跟多个参数</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat file1 file2</span><br></pre></td></tr></table></figure></li><li><p><code>less</code> 文件查看器</p><p>当文件内容过多，使用<code>cat</code>查看文件内容不太便利，<code>less</code>允许你分页查看文件的内容</p><p>键入<code>less</code>后，你会进入文件导航模式</p><ul><li>使用上下箭头逐行翻页，使用<code>page up/down</code>整页翻页</li><li>使用<code>g</code>和<code>G</code>跳转到文件开头 &#x2F; 结尾</li><li>使用<code>q</code>来退出</li><li>输入<code>/Linux</code>，则会高亮文件中所有的<code>Linux</code>关键字，用<code>n</code>跳转到下一次出现，<code>N</code>跳转到上一次出现</li></ul></li><li><p><code>history</code> 输出使用过的历史命令列表</p><ul><li>使用方向键 向上箭头 来切换你的历史命令记录，使用<code>!!</code>来切换到上次使用的命令，再次按下<code>enter</code>即可执行</li><li><code>ctrl + r</code> 是最强大的历史命令查询器，按下<code>ctrl + r</code>会出现搜索框，在框内输入”Linux”则会显示最近一次使用过的带有“Linux”关键字的命令，此时不断按下<code>ctrl + r</code>则可以切换到更早的匹配项</li><li><code>history -c</code>：清空所有历史命令记录</li><li><code>history -d 101</code>：删除第101条历史记录</li></ul></li><li><p><code>clear</code> 清空屏幕</p></li><li><p><code>cp</code> 复制文件到指定目录下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp myfile /home/ubuntu/mysrc</span><br></pre></td></tr></table></figure><p>也可以在复制的时候指定复制后的文件名</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp myfile /home/ubuntu/mysrc/file2</span><br></pre></td></tr></table></figure><p>这会创建<code>file2</code>，并且复制<code>myfile</code>的内容</p><ul><li><code>cp -r</code>：递归复制，通常用于复制目录</li><li><code>*</code>：通配符，例如<code>cp -r ~/mysrc/* ~/target/</code>会把<code>/mysrc</code>下的所有内容复制到<code>/target</code>下，注意，如果是&#96;&#96;cp -r ~&#x2F;mysrc ~&#x2F;target&#x2F;<code>，则会复制整个</code>mysrc<code>目录，包括</code>mysrc&#96;这一层级</li><li><code>cp -f</code>：强制覆盖同名文件</li><li><code>cp -i</code>：交互式，会在覆盖同名文件前向你确认</li><li><code>cp -p</code>：保留元数据，通常复制时会出现文件时间戳和文件所有权变化的情况，<code>-p</code>可以确保这些元数据不发生变化</li></ul></li><li><p><code>mv</code> 移动文件和目录 &#x2F; 重命名文件和目录</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mv file1 file2 ~/mysrc/target</span><br><span class="line">mv file1 file2</span><br><span class="line">mv dir1 dir2</span><br></pre></td></tr></table></figure><ul><li><code>mv -i</code>：交互式，会在覆盖同名文件前向你确认</li><li><code>mv -b</code>：覆盖同名文件的同时，会自动创建被覆盖旧文件的备份，并命名为<code>原文件名 + ~</code></li><li><code>mv -v</code>：会输出<code>mv</code>命令正在执行的操作，显示正在移动或者重命名的文件</li></ul></li><li><p><code>mkdir</code> 创建目录</p><ul><li><code>mkdir</code>：创建多级目录</li></ul></li><li><p><code>rm</code> 删除文件</p><ul><li><code>rm -f</code>：强制删除</li><li><code>rm -r</code>：递归删除，常用于删除目录及其所有内容，<code>rm</code>无法直接删除目录</li><li><code>rm -i</code>：交互式删除，每个文件删除前都会提醒你确认</li><li><code>rmdir</code>：删除空目录</li></ul></li><li><p><code>find</code> 查找文件 &#x2F; 目录</p><ul><li><p><code>find -name</code>：根据名称查找文件 &#x2F; 目录</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find -name file1</span><br></pre></td></tr></table></figure></li><li><p><code>find -type</code>：限制查找类型</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">find -type d -name mysrc</span><br><span class="line">find -type f -name file1.txt</span><br></pre></td></tr></table></figure></li><li><p>与<code>find</code>不同，<code>grep</code>也用于查找，但常用于在文本中查找匹配内容</p></li></ul><p><code>find</code>默认会递归查找给出目录及其所有的子目录</p></li><li><p><code>man</code> 查看命令手册</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">man ls</span><br></pre></td></tr></table></figure><p>大部分命令带有选项<code>--help</code>，也可查看使用说明</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ls --help</span><br><span class="line">ls -h</span><br></pre></td></tr></table></figure><p>这里推荐一个浏览器端的速查链接：<a href="https://tldr.sh/%EF%BC%8C%E6%96%B9%E4%BE%BF%E4%BD%BF%E7%94%A8%E7%BF%BB%E8%AF%91%E6%8F%92%E4%BB%B6%EF%BC%8C%E8%8B%B1%E8%AF%ADweaker%E6%9C%89%E6%95%91%E4%BA%86%EF%BC%88bushi%EF%BC%89">https://tldr.sh/，方便使用翻译插件，英语weaker有救了（bushi）</a></p><p>除此之外，<code>whatis</code>可以快速返回一个简短的命令描述</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ whatis man                                                                       </span><br><span class="line">man (1)              - 系统参考手册的接口</span><br></pre></td></tr></table></figure></li><li><p><code>alias</code> 创建别名</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">alias back=&#x27;cd ..&#x27;</span><br><span class="line">unalias back# 删除别名</span><br></pre></td></tr></table></figure><p>键入<code>back</code>即可返回上级目录，这样的别名只在当前会话生效，退出终端便会清除，<code>unalias</code>也是如此</p><p>想要使别名永久生效，需要写入配置文件<code>~/.bashrc</code>（根据不同 shell 决定）</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">写入...</span></span><br><span class="line">source ~/.bashrc # 使配置生效 也可以关闭会话重新登录</span><br></pre></td></tr></table></figure></li><li><p><code>exit</code> 退出会话</p></li></ol><h2 id="文本操作"><a href="#文本操作" class="headerlink" title="文本操作"></a>文本操作</h2><ol><li><p>标准输出 &#x2F; 输入 <code>stdout/stdin</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;hello world&quot; &gt; file1</span><br></pre></td></tr></table></figure><p>这会在当前目录下创建一个新文件<code>file1</code>，内容是<code>hello world</code></p><p>默认情况下，<code>echo</code>会把命令行参数发送到<code>stdout</code>，在你的屏幕上显示，看起来像回显一样</p><p><code>&gt;</code>是一个重定向符号，默认发送到<code>stdout</code>的数据，可以通过<code>&gt;</code>来拦截，发送到一个新的目的地</p><p>然而，<code>&gt;</code>重定向的内容会覆盖文件中原有的所有内容，如果你想要追加而不是覆盖，可以使用<code>&gt;&gt;</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;hello linux&quot; &gt;&gt; file1 # 会自动在file1的内容结尾加上换行符</span><br></pre></td></tr></table></figure><p>既然标准输出会显示在你的屏幕，那么标准输入就是你键入的内容</p><p>当我们使用<code>cat</code>命令，不加任何参数时，会打开一个交互式的场景，此时你输入<code>hello</code>，屏幕会再次回显一个<code>hello</code>，这里你输入的<code>hello</code>就时标准输入，回显的<code>hello</code>就是标准输出</p><p>这里<code>cat</code>的作用就是接受标准输入，传递给标准输出</p><p>配合重定向符号，我们可以实现：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">把 file1 的内容添加到 file2 的结尾</span></span><br><span class="line">cat &lt; file1 &gt;&gt; file2</span><br></pre></td></tr></table></figure><p><code>cat</code>也可以直接读取文件内容，而不是从标准输入读取，所以下面这样也有同样的效果</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat file1 &gt;&gt; file2</span><br></pre></td></tr></table></figure></li><li><p>标准错误 <code>stderr</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ ls bbb &gt; file1                                                                    </span><br><span class="line">ls: 无法访问 &#x27;bbb&#x27;: 没有那个文件或目录</span><br></pre></td></tr></table></figure><p>这里的报错信息，如果是标准输出，按理来说会重定向到<code>file1</code>中，然而却打印到了屏幕上，这是由于另一个 I &#x2F; O 流在起作用，标准错误<code>stderr</code></p><p>默认情况下，<code>stderr</code>和<code>stdout</code>都会输出到屏幕上，这里的<code>&gt;</code>只重定向了标准输出，而没有重定向标准错误，因此错误信息仍然会打印到屏幕上，要控制标准错误，先要简单理解一下文件描述符<code>fd</code></p><p>文件描述符是一个非负整数，内核通过它来识别打开的文件或流，默认的文件描述符是：</p><ul><li><code>0</code>：标准输入</li><li><code>1</code>：标准输出</li><li><code>2</code>：标准错误</li></ul><p>而重定向符号<code>&gt;</code>实际上是<code>1&gt;</code>的缩写，<code>&lt;</code>实际上是<code>&lt;0</code>的缩写</p><p>如果想要重定向标准错误，应该这样：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ls bbb 2&gt; file1</span><br><span class="line">ls bbb 2&gt;&gt; file1</span><br></pre></td></tr></table></figure><p>如果我希望标准输出和标准错误都能定向到file1，可以将两者合并</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ls bbb &gt; file1 2&gt;&amp;1</span><br><span class="line">ls bbb &gt;&gt; file1 2&gt;&amp;1</span><br></pre></td></tr></table></figure><p>意思是先重定向标准输出，然后在结尾再把标准错误重定向到标准输出所在的位置，即<code>2&gt;&amp;1</code></p><p>Shell 还为我们提供了一种更简单的缩写方法</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ls bbb &amp;&gt; file1</span><br><span class="line">ls bbb &amp;&gt;&gt; file1</span><br></pre></td></tr></table></figure><p>如果我们希望把错误信息完全丢弃，既不输出到屏幕上，也不保存到文件中，这时有一个特殊的文件<code>/dev/null</code>，有些人称之为“黑洞”，因为任何输出到该文件的数据都会被丢弃</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls bbb &gt;&gt; file1 2&gt;/dev/null</span><br></pre></td></tr></table></figure></li><li><p>管道和<code>tee</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls -a | less</span><br></pre></td></tr></table></figure><p><code>|</code>是管道，它可以获取左侧命令的标准输出，放入管道内，将其作为右侧命令的标准输入</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls -a | tee file.txt</span><br></pre></td></tr></table></figure><p><code>tee</code>可以将输出流分为两个方向输出，此时<code>ls</code>的内容会同时输出到标准输出和<code>file.txt</code>中</p><p>使用管道和<code>tee</code>过滤的数据流可以继续进入下一级管道，例如我要实现功能：</p><p>列出当前目录的所有内容，保存到<code>file1</code>中，同时在屏幕上打印名字中带有<code>file</code>的文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls -a | tee file1 | grep &quot;file&quot;</span><br></pre></td></tr></table></figure><p><code>tee</code>和<code>|</code>的一个常见组合用法是提升权限，例如：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo echo &quot;hello&quot; &gt; /root/demofile</span><br></pre></td></tr></table></figure><p>这里的<code>sudo</code>只对<code>echo</code>生效，而重定向符号没办法使用<code>sudo</code>来提升权限，因此需要：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;hello&quot; | sudo tee /root/demofile</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">这里的 <span class="built_in">echo</span> 不需要<span class="built_in">sudo</span></span></span><br></pre></td></tr></table></figure></li><li><p><code>env</code> 环境变量</p><p>使用<code>env</code>命令可以查看当前会话的所有环境变量，环境变量和其他编程语言中的变量一样，有变量名和值，可以使用<code>$</code>查看变量的值</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo $HOME</span><br></pre></td></tr></table></figure><p>普通的变量与环境变量的区别在于，是否被其他进程可见，Linux 的进程管理中，子进程只会继承父进程的环境变量，而不会继承父进程的普通变量，我们可以通过<code>export</code>来声明环境变量</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">TEST=1# 子进程不可见</span><br><span class="line">export TEST=2# 子进程可见</span><br></pre></td></tr></table></figure><p>这样声明的环境变量只在当前 Shell 会话中生效，一旦关闭终端，变量将不再存在</p><p>如果你需要让环境变量一直生效，只需要将其添加到 Shell 的启动文件中即可，例如<code>~/.bashrc</code>（或是<code>.zprofile .zshrc .bash_profile等 由 Shell 决定</code>），这样每次打开终端，Linux 会自动加载这些文件中的环境变量</p><p>在这些环境变量中，有一个特殊的环境变量<code>PATH</code></p><p><code>echo &amp;PATH</code>会返回一个以<code>:</code>冒号作为分隔符的列表</p><p>当你输入一个命令时，系统会先从内置库寻找命令，如果没有找到，就会从<code>PATH</code>变量中开始找</p><p>例如直接键入<code>java --version</code>，可能会出现<code>command not found</code>的错误提示，当你把 java 添加到<code>PATH</code>环境变量中，就可以执行<code>java</code>的相关命令了</p></li><li><p><code>cut</code> 输出切取的字符</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;Hello! Linux is so interestring.&quot; &gt; greeting.txt</span><br></pre></td></tr></table></figure><ul><li><p><code>cut -c 8 greeting.txt</code>：输出第8个字符<code>L</code>（空格也算一个字符）</p></li><li><p><code>cut -f 1 -d &#39;!&#39;</code>：按<code>!</code>进行分割，输出第一部分（若不加<code>-d</code>，默认以<code>tab</code>作为分隔符）</p><p>值得注意的是，<code>!</code>在 Linux 中有特殊含义，用于引用历史记录，这种引用在双引号内仍然会生效，如果想要输出字符<code>!</code>，需要在感叹号前添加转义或者使用单引号，<code>&quot;\!&quot;或&#39;!&#39;</code></p></li></ul></li><li><p><code>paste</code> 输出合并的字符</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">用 vim 编辑 greeting.txt 为以下内容</span></span><br><span class="line">Linux</span><br><span class="line">is</span><br><span class="line">so</span><br><span class="line">interesting</span><br></pre></td></tr></table></figure><ul><li><code>paste -s greeting.txt</code>：输出<code>Linux    is    so    interesting</code>，默认以<code>tab</code>作为分隔符</li><li><code>paste -s -d &#39; &#39; greeting.txt</code>：<code>-d</code>指定分隔符</li></ul></li><li><p><code>head</code> 查看文件开头</p><ul><li><code>head /var/log/syslog</code>：默认会输出文件的前10行</li><li><code>head -n 15 /var/log/syslog</code>：使用<code>-n</code>指定行数</li></ul></li><li><p><code>tail</code> 查看文件末尾</p><ul><li><code>tail /var/log/syslog</code>：默认输出末尾10行</li><li><code>tail -n 15 /var/log/syslog</code>：使用<code>-n</code>指定行数</li><li><code>tail -f /var/log/syslog</code>：使用<code>-f</code>实时监控文件</li></ul></li><li><p><code>expand</code> 与 <code>unexpand</code></p><ul><li><code>expand hello.txt</code>：把文本中的<code>tab</code>转化为空格输出</li><li><code>unexpand -a hello.txt</code>：把文本中的空格转化为<code>tab</code>输出（默认情况下，<code>unexpand</code> 只转换每行开头的空格。<code>-a</code> 选项告诉 <code>unexpand command</code> 将所有 8 个空格的实例转换为制表符）</li></ul><p>如果要将转化后到结果保存到文件，需要<code>expand hello.txt &gt; result.txt</code></p></li><li><p><code>sort</code> 对行排序</p></li></ol><p>   以行为分隔符，对元素进行排序</p><ul><li><code>sort hello.txt</code>：默认是字典排序</li><li><code>sort -r hello.txt</code>：反向排序</li><li><code>sort -n hello.txt</code>：数值排序，先比较行首的数字大小（非数字算作0），数字后的部分按字典排序</li></ul><ol start="11"><li><p><code>tr</code> 转换文本</p><ul><li><p><code>echo &quot;hello linux&quot; | tr a-z A-Z</code>：输出<code>HELLO LINUX</code></p></li><li><p><code>echo &quot;hello 123 linux&quot; | tr -d 0-9</code>：<code>-d</code>删除内容，输出<code>hello linux</code></p></li><li><p><code>echo &quot;hhhelo    linuxxx&quot; | tr -s &#39;h&#39;</code>：把多个连续的<code>h</code>压缩成一个<code>h</code></p><p>如果有两个参数，会把前者压缩为后者，例如<code>echo &quot;hhhelo    linuxxx&quot; | tr -s &#39;h&#39; &#39;H&#39;</code></p><p>如果要输出<code>hello linux</code>，如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;hhhello    linuxxx&quot; | tr -s &#x27;h&#x27; \</span><br><span class="line">| tr -s &#x27; &#x27; \</span><br><span class="line">| tr -s &#x27;x&#x27;</span><br></pre></td></tr></table></figure><p>这里的<code>\</code>是 Linux 中的换行符，表示命令还没输完，输入<code>\</code>按下回车后不会执行命令，而是会继续输入未完成的命令</p><p>实际上，Linux 中的<code>|</code>管道符号同时兼备换行的作用，因此上面的命令也可以写成这样：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;hhhello    linuxxx&quot; |</span><br><span class="line">tr -s &#x27;h&#x27; |</span><br><span class="line">tr -s &#x27; &#x27; |</span><br><span class="line">tr -s &#x27;x&#x27;</span><br></pre></td></tr></table></figure></li></ul></li><li><p><code>uniq</code> 重复行处理</p><p>新建一个<code>reading.txt</code>，内容如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">book</span><br><span class="line">book</span><br><span class="line">paper</span><br><span class="line">paper</span><br><span class="line">article</span><br><span class="line">article</span><br><span class="line">magazine</span><br></pre></td></tr></table></figure><ul><li><p><code>uniq reading.txt</code>：输出<strong>删除相邻的重复行</strong>后的结果（处理后的结果不会修改原文件）</p><p>只会删除相邻的重复行，如果要删除所有的重复行，需要<code>sort reading.txt | uniq</code></p></li><li><p><code>uniq -c reading.txt</code>：统计相邻行重复出现的次数</p></li><li><p><code>uniq -u reading.txt</code>：仅输出重复的行</p></li><li><p><code>uniq -u reading.txt</code>：仅输出不重复的行</p></li></ul></li><li><p><code>wc</code> 数据统计</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ wc reading.txt                                                                   </span><br><span class="line"> 9  8 53 reading.txt</span><br><span class="line"><span class="meta prompt_"> # </span><span class="language-bash">行数 单词数 字节数</span></span><br></pre></td></tr></table></figure><ul><li><code>wc -l</code>：仅显示行数</li><li><code>wc -w</code>：仅显示单词数</li><li><code>wc -c</code>：仅显示字节数</li></ul></li><li><p><code>nl</code> 对行编号</p><p>输出编号后的结果</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nl reading.txt</span><br></pre></td></tr></table></figure></li><li><p><code>grep</code> 内容搜索</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">grep book reading.txt</span><br></pre></td></tr></table></figure><p>搜索<code>reading.txt</code>中含有book的内容</p><p>如果我们要搜索<code>reading.txt</code>中含有关键字<code>-v</code>的文本，<code>grep &quot;-v&quot; reading.txt</code>是无效的，此时的<code>-v</code>会被识别成<code>grep</code>的选项，即便你加了双引号，<code>grep</code>提供了<code>-e</code>选项，明确表示后面是你要匹配的内容，而不是选项</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">grep -e &quot;-v&quot; reading.txt</span><br></pre></td></tr></table></figure><p>除此之外还有一种更通用的方法<code>--</code>，Linux 中的<code>--</code>是一个万能分界符，它明确表示，<code>--</code>之后的内容都将作为参数，不会被命令误当成选项解析</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">grep -- -v reading.txt</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除一个名为 -f 的文件</span></span><br><span class="line">rm -- -f</span><br></pre></td></tr></table></figure><ul><li><p><code>grep -i</code>：搜索时不区分大小写</p></li><li><p><code>grep -o</code>：仅提取匹配项（默认会输出含有匹配项的整行内容），常与正则表达式结合用于提取符合格式的内容</p></li><li><p><code>grep -f pattern.txt log.txt</code>： 当你有多个模版时，把他们放在<code>pattern.txt</code>，搜索<code>log.txt</code>中符合模版的内容</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">pattern.txt</span></span><br><span class="line">info</span><br><span class="line">warn</span><br><span class="line">error</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">log.txt</span></span><br><span class="line">[info]message...</span><br><span class="line">[warn]message...</span><br><span class="line">[error]message...</span><br><span class="line">[info]message...</span><br></pre></td></tr></table></figure></li></ul></li><li><p>正则表达式</p><p>参考<a href="https://labex.io/zh/lesson/regular-expressions-regex">https://labex.io/zh/lesson/regular-expressions-regex</a></p><p>（我一般都让 AI 写正则？）</p></li></ol><h2 id="文本编辑器"><a href="#文本编辑器" class="headerlink" title="文本编辑器"></a>文本编辑器</h2><p><code>Vim</code> 应该是命令行环境中最常用的文本编辑器，这里只简单介绍 <code>Vim</code> 的使用方法</p><p>有关 vim 的配置信息，通常在<code>~/.vimrc</code>文件中，这里留存一份我个人使用的配置文件：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">&quot; 启用语法高亮</span><br><span class="line">syntax on</span><br><span class="line"></span><br><span class="line">&quot; 支持 256 色</span><br><span class="line">set t_Co=256</span><br><span class="line"></span><br><span class="line">&quot; 自动缩进</span><br><span class="line">set autoindent</span><br><span class="line">set smartindent</span><br><span class="line"></span><br><span class="line">&quot; 显示匹配括号</span><br><span class="line">set showmatch</span><br><span class="line"></span><br><span class="line">&quot; 一些常用增强</span><br><span class="line">set number</span><br><span class="line">set tabstop=4</span><br><span class="line">set shiftwidth=4</span><br><span class="line">set expandtab</span><br><span class="line"></span><br><span class="line">&quot; 搜索高亮</span><br><span class="line">set hlsearch</span><br><span class="line">set incsearch</span><br><span class="line"></span><br><span class="line">&quot; 允许鼠标</span><br><span class="line">set mouse=a</span><br></pre></td></tr></table></figure><ol><li><p><code>vim hello.txt</code> 使用 vim 编辑器打开 &#x2F; 新建 <code>hello.txt</code> 文件</p></li><li><p><code>vim</code> 有四种模式，普通模式，尾行模式，编辑模式，视图模式</p><p>在普通模式下，使用<code>i</code>切换到编辑模式，<code>:</code>切换到尾行模式，<code>v</code>切换到视图模式，<code>esc</code>切换回普通模式</p><p>由于我在配置文件中加了<code>set mouse=a</code>，当你用鼠标选中一段文本时，此时 vim 会自动为你切换到视图模式</p><p>使用小技巧：</p><ul><li><p><code>gg</code>跳转到文件开头，<code>G</code>跳转到文件结尾</p></li><li><p><code>0</code>跳转到当前所在行行首，<code>$</code>跳转到当前所在行行尾</p></li><li><p>键入<code>/</code>来进入搜索模式，输入搜索内容后回车，vim 会高亮出所有匹配项，使用<code>n</code>和<code>N</code>跳转到下一个 &#x2F; 上一个匹配项（还有一个<code>?</code>也用于搜索，区别是<code>/</code>用于从当前光标开始向后搜索，<code>?</code>是向前搜索，并且<code>n / N</code>的方向也会根据<code>/</code>和<code>?</code>来发生改变）</p></li><li><p><code>:1,20d</code>后回车，可以删除[1, 20]行的内容</p></li><li><p>鼠标模式很好用，用鼠标选中文本后，按下<code>d</code>删除，按下<code>y</code>复制，按下<code>p</code>粘贴（注意 vim 的剪切板与你的系统剪切板不同步）</p></li></ul></li><li><p>保存与退出：</p><ul><li><code>:w</code>：保存更改的内容</li><li><code>:q</code>：退出，如果你有未保存的改动时，使用<code>:q</code>会退出失败，如果你想放弃更改并退出，使用<code>:q!</code></li><li><code>:wq</code>：保存并退出，你也可以在普通模式下按下<code>ZZ</code> ，快速执行保存并退出的操作</li></ul></li><li><p>撤销更改</p><p>类似 windows 里的 <code>ctrl + z</code>，vim 使用 <code>u</code> 来撤销更改，类似<code>ctrl + shift + z</code>，vim 使用 <code>ctrl + r</code> 来撤销撤销的更改</p></li></ol><h2 id="用户管理"><a href="#用户管理" class="headerlink" title="用户管理"></a>用户管理</h2><p>Linux 中的进程会以启动该进程的用户的身份运行，每个用户都有自己的个人主目录<code>/home/xxx</code>，在这些普通用户之外，<code>root</code>是 Linux 中的超级用户，拥有最高的权限。当出现<code>Permission denied</code>时，普通用户可以通过<code>sudo</code>来以 root 权限执行命令</p><ol><li><p><code>su</code>命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">su &lt;username&gt;</span><br></pre></td></tr></table></figure><p>这会为你打开一个新的 shell 会话，只要你知道目标用户的密码，可以使用<code>su</code>来切换到 Linux 上的任意用户</p><p>如果只输入<code>su</code>，默认会尝试登录 root 用户</p><p>不建议长期在 root shell 下执行命令，因为你在此 shell 中的操作不会记录到你个人用户的日志中，尽量使用<code>sudo</code></p></li><li><p><code>sudo</code></p><p>都有哪些用户可以使用<code>sudo</code>呢？实际上，允许使用<code>sudo</code>的用户由配置文件<code>/etc/sudoers</code>决定，编辑此配置文件，Linux 提供了一个专门的命令 <code>visudo</code></p><p>你应该总是通过<code>visudo</code>而不是<code>sudo vim /etc/sudoers</code>来编辑这个文件，前者会进行语法检查确保安全性，以免你把自己锁在<code>sudo</code>权限之外，导致不能再编辑此文件</p></li><li><p><code>/etc/passwd</code></p><p>Linux 识别用户和组，是通过<code>UID</code>和<code>GID</code>，每个用户和组都有独特的<code>UID</code>和<code>GID</code>，用户的相关信息存储在<code>/etc/passwd</code>这个文件中，当使用<code>cat</code>查看该文件时，可能会出现很多条目，其中有很多是系统为了隔离权限而创建的用户，例如<code>mysql</code>，并不能直接登录，目前我们暂时只关注人类用户，也就是可以登录的用户</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root:x:0:0:root:/root:/bin/bash</span><br><span class="line">ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/usr/bin/zsh</span><br></pre></td></tr></table></figure><p>每个条目被用<code>:</code>分隔成了七个字段，分别代表：</p><ul><li><p>用户名</p></li><li><p>密码：但为了安全考虑，用<code>x</code>来占位，真实的密码会被加密存储在<code>/etc/shadow</code>中，每个条目仍然以<code>:</code>分隔，第一个字段代表用户名，第二个字段代表加密密码，后面是密码更改日期…有效期…之类的，可以不用在意</p></li><li><p>用户id（<code>UID</code>）：用户的唯一标识符，root 用户的 UID 永远是 0</p></li><li><p>组id（<code>GID</code>）：主组id，每个用户至少需要在一个主组内</p><p>实际上，每个用户在创建之初，系统会自动创建一个组作为该用户的主组，并且组名&#x3D;用户名，<code>GID=UID</code></p><p>用户可以退出默认的主组，加入其他主组，除了一个必须的主组外，用户可以自由的加入任意多个附属组</p></li><li><p>GECOS 字段，用于添加备注信息，可以不太关注</p></li><li><p>用户目录的绝对路径</p></li><li><p>用户的默认 shell</p></li></ul><p>不建议手动编辑<code>/etc/passwd</code>，Linux 通常会提供其他专用命令来修改用户信息</p></li><li><p><code>/etc/group</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root:x:0:</span><br><span class="line">docker:x:988:ubuntu</span><br></pre></td></tr></table></figure><p>与<code>/etc/passwd</code>类似，<code>/etc/group</code>的每一行代表一个组，字段用<code>:</code>分隔，分别代表：</p><ul><li>组名</li><li>组密码：这是一个历史遗留项，现在的 Linux 版本已经很少使用组密码了，常见<code>x</code>占位符</li><li>组id（<code>GID</code>）</li><li>附加成员：显示这个组的附加成员有哪些，主成员不会显示在这里</li></ul></li><li><p>用户管理命令</p><ul><li><p>创建用户和组：核心命令是<code>useradd</code>，下面以创建用户<code>sakura</code>为例说明整个过程：</p><ol start="0"><li><p>先确保系统中没有名为<code>sakura</code>的用户，可以使用<code>id sakura</code>查看</p></li><li><p>先创建组，名为 sakura（默认主组名和用户名相同）</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo groupadd sakura</span><br></pre></td></tr></table></figure><p>此时，系统会自动为组 sakura 分配 GID，并写入<code>/etc/group</code>，可以看到，这里我的 GID 是 1001</p><img src="https://files.seeusercontent.com/2026/02/24/Kcc4/pasted-image-1771939841294.webp" alt="pasted-image-1771939841294.webp" style="zoom:80%;" /></li><li><p>创建用户 sakura</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sudo useradd \</span><br><span class="line">-m \</span><br><span class="line">-g sakura \</span><br><span class="line">-s /bin/shell \</span><br><span class="line">sakura</span><br></pre></td></tr></table></figure><ul><li><p><code>-m </code>表示自动创建用户家目录，相当于：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mkdir /home/sakura</span><br><span class="line">cp -a /etc/skel/. /home/sakura/</span><br><span class="line">chown -R sakura:sakura /home/sakura</span><br></pre></td></tr></table></figure><p>值得注意的是，<code>/etc/skel/</code>目录下的内容是用户的家目录模版，因此每个用户在创建家目录时都会拷贝其中的内容，里面通常包含<code>.profile .bashrc</code>等文件，这意味着我们可以编辑<code>/etc/skel/</code>中的内容来使新建的用户自动继承某些配置，这是一种很常见的做法</p></li><li><p><code>-g</code> 指定主组，系统会确保 UID &#x3D; GID</p></li><li><p><code>-s</code> 指定默认 shell，通常为 bash</p></li><li><p><code>sakura</code> 用户名</p></li></ul><p>此时，系统会自动把 sakura 用户的信息写入<code>/etc/passwd</code></p><img src="https://files.seeusercontent.com/2026/02/24/j9dK/pasted-image-1771940618520.webp" alt="pasted-image-1771940618520.webp" style="zoom:80%;" /></li><li><p>为 sakura 设置密码</p><p>此时<code>/etc/shadow</code>中显示 sakura 用户的密码栏目为<code>!</code>，表示我们还没有给用户设置密码，该用户暂时无法用于登录</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo passwd sakura # 给 sakura 用户设置密码</span><br></pre></td></tr></table></figure><p>设置之后，再次查看<code>/etc/shadow</code>，sakura 加密后的密码已经正常显示</p><p><img src="https://files.seeusercontent.com/2026/02/24/eB9d/pasted-image-1771941179177.webp" alt="pasted-image-1771941179177.webp"></p></li><li><p>至此，sakura 用户已经创建成功，其实在 Debian 系等发行版，内置了<code>adduser</code>命令，看起来和<code>useradd</code>很像，但它涵盖了上述 1 ～ 3 的完整步骤，可以方便地直接使用<code>sudo adduser sakura</code>来快速创建用户。然而，在 Arch 系等其他发行版则不提供该内置命令，因此在写脚本的时候需要注意跨平台的问题，<code>useradd</code>仍是更普遍更底层的方式。</p></li></ol></li><li><p>修改用户 &#x2F; 组信息</p><ul><li><code>sudo passwd sakura</code>：修改 sakura 的密码</li><li><code>sudo usermod -g 新组 sakura</code>：修改 sakura 的主组为 新组</li><li><code>sudo usermod -aG 附加组 sakura</code>：添加附加组，可以把附加组设置为 sudo，赋予 sakura <code>sudo</code>的权限</li><li><code>sudo gpasswd -d sakura 组名</code>：把 sakura 从附加组移除</li></ul></li><li><p>删除用户 &#x2F; 组</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo userdel sakura</span><br></pre></td></tr></table></figure><p>这样仅能删除 sakura 用户本身，如果还要删除家目录，加上<code>-r</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo userdel -r sakura</span><br></pre></td></tr></table></figure><p>在某些 Linux 的发行版中，如 Debian 系，若用户的主组内只有这一个用户，当用户被删除时，组也会被自动清理。如果你想手动删除一个组，可以用以下命令：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo groupdel sakura</span><br></pre></td></tr></table></figure></li></ul></li></ol><h2 id="权限"><a href="#权限" class="headerlink" title="权限"></a>权限</h2><ol><li><p>文件权限</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ ll                                                                              </span><br><span class="line">总计 55M</span><br><span class="line">drwxrwxr-x 3 ubuntu ubuntu 4.0K 10月 19 23:28 boss</span><br><span class="line">drwxrwxr-x 7 ubuntu ubuntu 4.0K  1月  5 18:03 doki</span><br><span class="line">-rw-r--r-- 1 ubuntu ubuntu  55M 12月  2 12:10 go1.25.1.linux-arm64.tar.gz</span><br><span class="line">-rw-rw-r-- 1 ubuntu ubuntu  21K  2月 24 15:23 hello2.txt</span><br><span class="line">-rw-rw-r-- 1 ubuntu ubuntu  20K  2月 24 15:20 hello.txt</span><br><span class="line">drwxrwxr-x 8 ubuntu ubuntu 4.0K 11月 14 16:00 jenkins-demo</span><br><span class="line">drwxrwxr-x 2 ubuntu ubuntu 4.0K 10月 20 12:22 k8s-wordpress</span><br><span class="line">drwxrwxr-x 3 ubuntu ubuntu 4.0K 12月  4 10:26 learn-go</span><br><span class="line">drwxrwxr-x 2 ubuntu ubuntu 4.0K 10月 10 20:45 myk8s</span><br><span class="line">drwxrwxr-x 2 ubuntu ubuntu 4.0K 10月 20 01:17 mynginx</span><br><span class="line">drwxrwxr-x 2 ubuntu ubuntu 4.0K 10月 10 15:43 nu</span><br><span class="line">drwxrwxr-x 3 ubuntu ubuntu 4.0K 12月  4 10:25 project</span><br><span class="line">drwxrwxr-x 4 ubuntu ubuntu 4.0K 10月 13 16:46 tvsos</span><br></pre></td></tr></table></figure><p>第一列代表文件类型和权限</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">d | rwx | rwx | r-x</span><br></pre></td></tr></table></figure><p><code>d</code>：目录，<code>-</code>：文件，<code>l</code>：符号链接</p><p><code>r</code>：读取权限，<code>w</code>：写入权限，<code>x</code>：执行权限，<code>-</code>：无权限</p><p>三组 <code>rwx</code> 分别代表不同级别的访问权限：</p><ul><li><p>所有者，默认情况下是创建这个文件 &#x2F; 目录的用户</p></li><li><p>所有组，默认情况下是创建这个文件 &#x2F; 目录的用户所在的主组</p></li><li><p>其他人，指既不是所有者也不在所有组内的用户</p></li></ul></li><li><p>更改权限</p><p>最常用的命令是<code>chmod</code></p><ul><li><p>符号模式</p><p><code>u</code>：所有者，<code>g</code>：所有组，<code>o</code>：其他，<code>a</code>：全部</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">为用户添加执行权限</span></span><br><span class="line">chmod u+x myfile</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">同时为组和其他人添加执行和写入权限</span></span><br><span class="line">chmod go+rx myfile</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">这样也是合法的</span></span><br><span class="line">chmod g-x,o-xw myfile # 注意 , 后面不要多加空格</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">不指定 <span class="built_in">who</span>，会给 ugo 同时加上权限</span></span><br><span class="line">chmod +x myfile</span><br></pre></td></tr></table></figure></li><li><p>八进制模式</p><p><code>4 - r</code>，<code>2 - w</code>，<code>1 - x</code>，这样任意几个数字的和都是唯一的</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">chmod 664 myfile</span><br><span class="line">chmod 775 mydir</span><br></pre></td></tr></table></figure><p>新建的文件的默认权限是<code>664</code>，由于目录需要<code>x</code>权限才能进入，所以一般新建目录的默认权限是<code>775</code></p></li></ul></li><li><p>更改所有权</p><ul><li><p>更改用户所有权</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">把 myfile 的所有权更改为 sakura</span></span><br><span class="line">sudo chown sakura myfile</span><br></pre></td></tr></table></figure></li><li><p>更改所有组</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">把 myfile 的所有组改为 staff</span></span><br><span class="line">sudo chgrp staff myfile</span><br></pre></td></tr></table></figure></li><li><p>同时更改所有用户和组</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo chown sakura:staff myfile</span><br></pre></td></tr></table></figure></li></ul></li><li><p><code>umask</code></p><p>前面说过，Linux 中默认创建的文件权限是<code>664</code>，目录权限是<code>775</code>，实际上，造成这样的原因是<code>umask</code>的存在，Linux 系统默认把文件的最大权限值当作<code>666</code>（系统默认文件是不可执行的），把目录的最大权限值当作<code>777</code>，当新建文件 &#x2F; 目录时，权限值会被设定为 <strong>最大权限值 - <code>umask</code></strong></p><p>在终端输入<code>umask</code>，可以看到，大部分系统的默认<code>umask</code>是<code>002</code>，因此</p><p>新建文件：<code>666 - 002 = 664</code></p><p>新建目录：<code>777 - 002 = 775</code></p><p>换句话说，<code>umask</code>的作用是移除某些权限，你可以在终端中输入<code>umask 021</code>来设置<code>umask</code>的值为 021，但这种更改不是永久的，如果想要永久更改 umask，需要修改<code>.profile 或 .bashrc 等</code>文件，加入以下内容</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">umask 021 # 修改 umask 为 021</span><br></pre></td></tr></table></figure><img src="https://files.seeusercontent.com/2026/02/25/q1zP/pasted-image-1772000790303.webp" alt="pasted-image-1772000790303.webp" style="zoom:80%;" /><p>一般来说使用默认的 002 就好</p></li><li><p><code>SUID</code>与<code>SGID</code></p><p>之前我们说过，可以用<code>passwd</code>更改用户密码，此时系统会更改<code>/etc/shadow</code>中的内容</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ ls -l /etc/shadow                                                                  </span><br><span class="line">-rw-r----- 1 root shadow 1010  2月 24 22:20 /etc/shadow</span><br></pre></td></tr></table></figure><p>然而，查看这个文件，发现他的所有者是 root，且只有 root 才有写入权限，为什么我们能通过<code>passwd</code>来更改这个文件的内容呢？</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ ls -l /bin/passwd                                                                  </span><br><span class="line">-rwsr-xr-x 1 root root 72056  5月 30  2024 /bin/passwd</span><br></pre></td></tr></table></figure><p>这里发现了一个新的权限位<code>s</code>，这个权限位称为<code>SUID</code>（Set User ID），当其他用户执行这个文件时，<code>SUID</code>将赋予此用户该文件所有者的权限，在这里也就是 root，所以当用户允许<code>passwd</code>时，是以 root 的身份执行的</p><p>你也可以用<code>chmod</code>来给自己的文件设置<code>SUID</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo chmod u+s myfile</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">等价写法</span></span><br><span class="line">sudo chmod 4755 myfile</span><br></pre></td></tr></table></figure><p><code>SUID</code>可以用<code>4</code>表示，加在权限集的前面，你可能会见到<code>SUID</code>出现大写<code>S</code>的情况，这是无效的写法，表示虽然加了<code>SUID</code>，但是没有给予文件执行权限</p><p>类似<code>SUID</code>，<code>SGID</code>（Set Group ID）允许其他用户执行文件时，赋予此用户该文件所有组的成员权限</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo chmod g+s myfile</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">等价写法</span></span><br><span class="line">sudo chmod 2755 myfile</span><br></pre></td></tr></table></figure><p><code>SGID</code>的数字表示是<code>2</code></p></li><li><p>进程权限</p><p>可以读一下这个大致了解 <a href="https://labex.io/zh/lesson/process-permissions">https://labex.io/zh/lesson/process-permissions</a></p></li><li><p>粘滞位</p><p>在 Linux 中，有这样一个目录<code>/tmp</code>，任何用户都可以访问该目录，并且在该目录下存放 &#x2F; 修改自己的临时文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ ls -ld /tmp                                                                       </span><br><span class="line">drwxrwxrwt 17 root root 4096  2月 25 15:25 /tmp</span><br></pre></td></tr></table></figure><p>注意到这里其他用户有一个新的权限位<code>t</code>，表示设置了粘滞位。它的作用是即使所有用户都可以在这个目录自由的读写，但是他们无法对其他用户的文件进行<code>rm</code>操作，这可以防止一个用户干扰另一个用户的工作</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">设置粘滞位（只能对目录设置）</span></span><br><span class="line">sudo chmod +t mydir # 不能写成 o+s</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">等价写法</span></span><br><span class="line">sudo chmod 1755 mydir</span><br></pre></td></tr></table></figure><p>粘滞位的数字表示是<code>1</code></p></li></ol><h2 id="进程"><a href="#进程" class="headerlink" title="进程"></a>进程</h2><p>进程是指在系统上正在运行的程序，由 Linux 内核管理，每个进程会被分配一个唯一的 id，即<code>PID</code>，通常在创建新进程时 PID 会按顺序分配。</p><ol><li><p><code>ps</code> 输出与当前终端会话相关的进程列表快照</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ ps                                                                               </span><br><span class="line">    PID TTY          TIME CMD</span><br><span class="line">  68227 pts/0    00:00:03 zsh</span><br><span class="line">  68231 pts/0    00:00:00 zsh</span><br><span class="line">  68260 pts/0    00:00:00 zsh</span><br><span class="line">  68261 pts/0    00:00:00 zsh</span><br><span class="line">  68263 pts/0    00:00:00 gitstatusd-linu</span><br><span class="line">  69183 pts/0    00:00:00 ps</span><br></pre></td></tr></table></figure><ul><li><code>PID</code>：进程唯一 id</li><li><code>TTY</code>：控制该进程的终端</li><li><code>TIME</code>：进程从启动以来，占用 CPU 的总时间，不能代表进程的运行时间，因为大部分时间进程都在睡眠 &#x2F; 挂起，只有被调度时才会占用 CPU</li><li><code>CMD</code>：启动该进程的命令</li></ul><p><code>ps</code> 的选项有两种不同的风格，常用的组合分别是<code>ps aux</code>和<code>ps -ef</code>，前者作为日常使用，后者则更常见于脚本中</p></li><li><p><code>top</code> 进程实时监控</p><p><code>ps</code>是针对某一时刻的进程快照，而<code>top</code>可以实时监控进程资源，每隔几秒便会刷新一次，常用于排查进程占用资源异常</p></li><li><p><code>tty</code></p><p>在进程列表中，有一个字段<code>TTY</code>，称为控制终端，在早期的计算机中，人们通过 <code>Teletype</code>（电传打字机）来输入命令和计算机进行交互，tty 得名于此。在现代的 Linux 系统中，tty 指为进程提供标准输入和输出的终端。</p><p>终端有两种类型：终端设备和伪终端</p><ul><li><p>终端设备</p><p>在 Linux 中，真正的终端设备通常指 <strong>虚拟控制台（Virtual Console）</strong>。在物理机上直接安装 Linux 后，可以通过 <code>Ctrl + Alt + F1 ~ F6</code> 切换到这些控制台，它们对应 <code>/dev/tty1</code>、<code>/dev/tty2</code> 等设备。此时你会看到只带有命令提示符的黑框框，这些终端由内核直接管理，不依赖图形界面，即使图形系统崩溃，也可以正常登录和操作。</p></li><li><p>伪终端</p><p>伪终端是我们日常最常用的，也称为<code>pty</code>，当我们在图形化的操作系统下打开终端应用程序时，此时出现的黑框框实际上是<code>pty</code>，输入命令<code>ps</code>检查<code>TTY</code>字段，会看到输出为<code>pts/0</code>，<code>pts/1</code>等</p></li></ul><p>这里 pty 和 pts 不是打错了，详细概念可以看：<a href="https://dev.to/napicella/linux-terminals-tty-pty-and-shell-192e">https://dev.to/napicella/linux-terminals-tty-pty-and-shell-192e</a></p><p>这里我开启两个终端会话，查看进程：</p><img src="https://files.seeusercontent.com/2026/02/25/K7dx/pasted-image-1772012266675.webp" alt="pasted-image-1772012266675.webp" style="zoom:80%;" /><img src="https://files.seeusercontent.com/2026/02/25/I8em/pasted-image-1772012279928.webp" alt="pasted-image-1772012279928.webp" style="zoom:80%;" /><p>可以看到 TTY 的编号分别是 <code>pts/0</code>，<code>pts/1</code></p><p>大多数进程都需要绑定一个控制终端，进程的生命周期与启动该进程的控制终端相关联，终端窗口被关闭时，与之相关联的进程也会被杀死</p><p>有一类进程称为守护进程（daemon），他们被设计为在后台运行，管理着某些系统服务，通常只在机器 &#x2F; 系统关闭时停止，不与控制终端关联，如果你在<code>ps aux</code>中看到部分进程的 TTY 显示一个问号<code>?</code>，这表示该进程没有关联任何控制终端，独立于用户会话运行</p></li><li><p>有关进程的简单介绍：<a href="https://labex.io/zh/lesson/process-details">https://labex.io/zh/lesson/process-details</a></p></li><li><p>进程的创建</p><p>Linux 中创建进程的现有机制是 <code>fork - exec</code> 模型。首先，现有进程使用 <code>fork</code> 来克隆自身，创建一个和自己几乎一样的子进程，这个子进程会获得自己的唯一 PID ，原始进程则为该进程的父进程，由 PPID 标识</p><p>之后，子进程通过 <code>execve</code> 系统来调用或加载一个新程序，有效地用新程序的内存空间替换了继承的内存空间，从而执行新的任务</p><p>在<code>ps -ef</code> 或 <code>ps l</code>命令中，PPID 的列代表父进程的 PID</p><p><img src="https://files.seeusercontent.com/2026/02/27/fj6Q/pasted-image-1772161312779.webp" alt="pasted-image-1772161312779.webp"></p><p>这里可以看到，<code>ps l</code>的 PPID 就是当前 shell 进程的 PID（<code>68227</code>）</p><p>如果每个进程都是另一个进程的子进程，那么必然存在一个最初的祖先。这就是 <code>init</code> 进程。系统启动时，内核会创建 <code>init</code> 作为第一个用户空间进程，并为其分配 PID 1。<code>init</code> 进程是所有其他进程的最终父进程，并以 root 权限运行以管理系统。在系统关闭之前，它不能被终止，并且负责启动许多维持系统运行的服务</p></li><li><p>进程的终止</p><p>进程通常通过调用 <code>_exit</code> 系统调用来终止。此操作向内核发出信号，表明进程已完成，其资源（如内存和文件描述符）可以被回收。退出时，进程会向内核提供一个终止状态，这是一个整数值。按照惯例，状态 0 表示成功执行，而非零值表示发生错误</p><p>然而，调用 <code>_exit</code> 并不会立即清除进程。父进程必须使用 <code>wait</code> 系统调用来确认其子进程的终止。此调用允许父进程检查子进程的终止状态。这种两步机制对于正确的进程清理至关重要</p><p>另外一种终止进程的方式是信号，后续介绍</p><ul><li><p>孤儿进程</p><p>如果父进程在子进程终止之前就终止了，那子进程就变成了<strong>孤儿进程</strong>。由于其原始父进程没办法调用 <code>wait</code>，内核会介入，孤儿进程会立即被一个特殊的系统进程（通常是 <code>init</code>，PID 为 1）收养，该进程被认为是所有进程的祖先。然后 <code>init</code> 进程承担起父进程的角色，定期调用 <code>wait</code> 来收集其所有被收养子进程的终止状态，使它们能够干净地终止</p></li><li><p>僵尸进程</p><p>当子进程终止，但父进程还尚未调用 <code>wait</code> 时，子进程就变成了<strong>僵尸进程</strong>。内核会释放僵尸进程的大部分资源，但他会在进程表中保留一个条目，包含进程 PID 和终止状态，等待父进程来收集。由于僵尸进程已经死亡，它不会占用 CPU 的资源，你<strong>无法使用信号来终止僵尸进程</strong>，因为他们根本没在运行。父进程调用 <code>wait</code> 来清理僵尸进程的过程叫做<strong>回收</strong>，如果父进程不调用<code>wait</code>来回收僵尸进程，虽然不消耗 CPU 资源，但大量僵尸进程会填满进程表，阻止新进程的创建。在父进程也终止的情况下，<code>init</code> 会收养并回收僵尸进程。</p></li><li><p>孤儿进程 vs 僵尸进程</p><ul><li><strong>孤儿进程</strong>是活动的，正在运行的进程，其父进程已经死亡，他被<code>init</code>收养并继续运行，直到任务完成</li><li><strong>僵尸进程</strong>是死亡的，它已经完成了要执行的任务，但在进程表中仍然存在一个条目，等待父进程读取回收</li></ul><p>总之，孤儿进程是活着但没有父进程，僵尸进程是死了但没有被父进程回收</p></li></ul></li><li><p>信号</p><p>在 Linux 中，信号是发送给进程的软件中断，用于通知它发生了重要事件，是进程间通信（IPC）的主要方式之一</p><ul><li>用户可以在终端输入特殊字符（<code>ctrl + c</code> , <code>ctrl + z</code>）来向前台进程发送停止 &#x2F; 中断信号</li><li>内核也可以向进程发送信号，通知发生了软件 &#x2F; 硬件问题</li><li>系统管理员和其他进程也可以使用信号管理进程的生命周期</li></ul><p>信号生成后，首先传递给目标进程，此时信号处于“待处理”状态，直到内核运行该进程，当进程被调度时，信号被传递。然而，进程具有信号掩码，可以配置掩码来阻止特定信号的传递</p><p>当信号被传递时，进程可以采取以下操作：</p><ul><li>忽略信号：简单地丢弃信号，继续执行任务</li><li>捕获信号：执行信号处理程序（自定义函数）来响应该事件</li><li>执行默认操作：当信号没有被忽略或捕获时，则执行默认操作，对于许多信号来说，这意味着终止进程</li><li>阻塞信号：如果信号在进程的信号掩码中，则信号保持“待处理”状态，直到解除阻塞</li></ul><p>常见的信号如下：（都以 <code>SIG</code> 开头）</p><ul><li><code>SIGHUP (1)</code>：挂断。通常用于告诉守护进程重新加载配置</li><li><code>SIGINT (2)</code>：中断。<code>ctrl + c</code>，它是终止进程的请求</li><li><code>SIGKILL (9)</code>：杀死。这是一种立即、强制的终止，该信号无法被忽略、捕获、阻塞</li><li><code>SIGSEGV (11)</code>：段错误。通知进程进行了无效的内存引用</li><li><code>SIGTERM (15)</code>：终止。这是一种标准且礼貌的请求进程终止的方式，<code>kill</code>命令默认发送此信号，进程可以捕获此信号，在退出前执行清理工作</li><li><code>SIGSTOP (19)</code>：停止。立即停止进程，该信号无法被忽略、捕获、阻塞</li><li><code>SIGTSTP (20)</code>：暂停。礼貌地暂停进程，该信号可以被忽略、捕获、阻塞，暂停后的进程可以恢复</li></ul></li><li><p><code>kill</code></p><p><code>kill</code>命令尽管名称如此，但他不仅仅能向进程发送终止信号，还能发送许多各种信号</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">kill 12345</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">等价写法</span></span><br><span class="line">kill -15 12345</span><br></pre></td></tr></table></figure><p>这将会终止 PID 为 12345 的进程，他会发送 <code>SIGTERM</code> 信号，这是标准、优雅地终止进程的方式，用于请求进程干净地关闭，给了进程清理和释放资源的机会</p><p>有时进程出现无响应，不会对 <code>SIGTERM</code> 信号做出反应，在这种情况下可以使用 <code>SIGKILL</code> 信号强制杀死</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kill -9 12345</span><br></pre></td></tr></table></figure><p>这会立即杀死进程，不给它清理的机会</p><p><code>kill</code>命令后加上数字选项，可以发出各种类型的信号，比如<code>kill -1</code>通知守护进程重新加载配置文件等，你也可以使用信号名称代替数字编号，例如<code>kill -SIGHUP</code>，效果是一样的</p><p>一个特殊的用法是<code>kill -0</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kill -0 12345</span><br></pre></td></tr></table></figure><p>这个命令实际上没有发送任何信号，但是可以用来检查 PID 为 12345 的进程是否存在，以及你是否有权限对该进程发送信号</p></li><li><p>进程优先级</p><p>多个进程同时运行时，实际上，CPU 会在不同的进程之间快速切换，为每段进程分配一小段处理时间，称为“时间片”，时间片结束后，进程会被暂停，CPU 会转而执行下一个进程（以单核 CPU 为例）</p><p>内核的调度决策可以由进程的 <code>niceness</code> 决定，<code>niceness</code> 决定了一个进程的“友好性”，它用一个数字表示，范围是<code>-20 ~ 19</code></p><ul><li>一个进程的 <code>nicenee</code> 比较高，表示该进程十分“友好”，他会先把 CPU 时间让给其他进程，因此该进程的优先级就比较低。换句话说，<code>niceness</code> 与进程的优先级是负相关的，<code>niceness</code> 较小的进程拥有较高的优先级</li></ul><p>使用 <code>top</code> 命令实时查看进程状况，其中 <code>NI</code> 列就代表进程的 <code>niceness</code>。大部分进程在创建时，<code>niceness</code> 的值默认为0，我们也可以手动更改和设定进程的 <code>niceness</code> 值</p><ul><li><p>以 <code>niceness</code> 为 5 启动 <code>apt update</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nice -n 5 apt update</span><br></pre></td></tr></table></figure></li><li><p>把 PID 为 12345 的进程的 <code>niceness</code> 改为 10</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">renice 10 -p 12345</span><br></pre></td></tr></table></figure></li></ul></li><li><p>进程状态</p></li></ol><p>   使用 <code>ps aux</code> 查看进程快照，你会看到 <code>STAT</code> 列，表示当前的进程状态</p><ul><li><code>R</code>：运行中或可运行。要么正在 CPU 核心上运行，要么在运行队列中，一旦有可用 CPU 核心立刻运行</li><li><code>S</code>：可中断睡眠。最常见的进程状态之一，进程正在等待某个事件的完成，可以被信号唤醒</li><li><code>D</code>：不可中断睡眠。无法被信号唤醒，通常用于 I &#x2F; O 操作期间，此时中断进程可能导致状态损坏。如果某个进程长期处于该状态，可能表示硬件和驱动存在问题</li><li><code>Z</code>：僵尸进程。</li><li><code>T</code>：停止。如按下<code>ctrl + z</code>挂起的进程或者调试器跟踪的进程，可以用 <code>SIGCONT</code> 信号恢复它</li></ul><ol start="11"><li><p><code>/proc</code> 文件系统</p><p>Linux 系统中有一个独特的目录 <code>/proc</code>，这个目录不是硬盘上的真实文件系统，它由内核在<strong>内存</strong>中创建</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls /proc</span><br></pre></td></tr></table></figure><p>可以看到，<code>/proc</code> 中有很多带有编号的目录，这些编号代表进程的 PID。还有一些文件例如 <code>cpuinfo</code> 和 <code>meminfo</code>，他们提供系统的硬件信息</p><p>如果你知道某个进程的 PID</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /proc/12345/status</span><br></pre></td></tr></table></figure><p>此命令将显示有关该进程的详细信息，提供了比其他工具多得多的数据。实际上，像 <code>top</code>、<code>ps</code>等这样的应用程序都会从 <code>/proc</code> 读取数据，并以用户友好的形式展现出来，因此 <code>/proc</code> 目录可能会显示这些工具没有的大量额外细节</p><p>你也可以通过读取 <code>/proc</code> 目录来构建你的自定义脚本或监控面板</p></li><li><p>作业控制</p><p>在 Linux 中，你可能会遇到需要等待很长时间才能完成的命令，你不必傻傻等待，让命令占用终端前台而无法使用</p><p>要在后台启动一个进程，只需要在命令后面加上一个 <code>&amp;</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sleep 1000 &amp;</span><br><span class="line">sleep 1001 &amp;</span><br><span class="line">sleep 1002 &amp;</span><br></pre></td></tr></table></figure><p>你在后台启动了三个进程，可以使用 <code>jobs</code> 查看后台正在运行的作业</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">  ~/m/p/linux-lab/lab-01   main +3 !3 ?2 ❯ jobs    </span><br><span class="line"></span><br><span class="line">[1]    running    sleep 1000</span><br><span class="line">[2]  - running    sleep 1001</span><br><span class="line">[3]  + running    sleep 1002</span><br></pre></td></tr></table></figure><p>第一列显示了后台作业的 ID，<code>+</code> 表示最后一个启动的作业，<code>-</code> 表示倒数第二个启动的作业</p><p>后台作业，一般只用于跑一个较为耗时的脚本 &#x2F; 命令时使用，为了不影响前台正常输入命令而用的，通常不适合用于跑长期服务</p><p>如果你将一个命令跑在了前台，运行一半的过程中，你想把他转移到后台作业，但又不想重新启动该命令，可以搭配使用 <code>SIGTSTP (ctrl + z)</code> 和 <code>bg</code> 命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">前台启动进程</span></span><br><span class="line">sleep 1003</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">暂停进程</span></span><br><span class="line">ctrl + z</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">前台启动进程</span></span><br><span class="line">sleep 1004</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">暂停进程</span></span><br><span class="line">ctrl + z</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">ctrl + z 在暂停进程的同时，还会把这些进程登记到后台作业，使用 <span class="built_in">jobs</span> 查看</span></span><br><span class="line">jobs</span><br><span class="line"></span><br><span class="line">[1]  - suspended  sleep 1003</span><br><span class="line">[2]  + suspended  sleep 1004</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">使用 <span class="built_in">bg</span> 来启动后台作业</span></span><br><span class="line">bg %1 # 启动作业 id 为 1 的后台作业</span><br><span class="line">bg # 不指定则会启动最后一个后台作业</span><br></pre></td></tr></table></figure><p>如果你想要把后台作业调回前台，使用 <code>fg</code> 命令，用法与 <code>bg</code> 相同</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fg %2</span><br></pre></td></tr></table></figure><p>后台作业和前台进程一样，都可以用 <code>kill</code> 来传递信号，如果要终止一个后台作业进程</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">kill %2</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">其他各种信号也都可以传递</span></span><br><span class="line">kill -9 %2</span><br><span class="line">kill -20 %2</span><br></pre></td></tr></table></figure></li></ol><h2 id="软件包"><a href="#软件包" class="headerlink" title="软件包"></a>软件包</h2><ol><li><p>软件发行</p><p>你可能知道像 <code>chrome</code> 和 <code>firefox</code> 这样的软件名称，在技术层面上，他们实际上是软件包。软件包是一个文件存档，包含应用程序的可执行文件、配置文件、文档等。软件包的供应链中存在两个角色：</p><ul><li>上游提供者：软件的开发者，他们编译源代码，创建安装说明和发布更新版本</li><li>软件包维护者：新版本准备好之后，上游提供者会将他们发送给软件包维护者。软件包维护者会审查之后将软件以特定 Linux 发行版的软件包形式分发给用户</li></ul><p>虽然你可以直接通过源码编译安装软件，但使用软件包更为高效，如 <code>.deb</code> 、 <code>.rpm</code> 等是 常见的软件包形式</p></li><li><p>软件包存储库</p><p>我们可以在官方网站获取软件包，但更为高效的解决方案是使用<strong>包管理器</strong>，从软件包存储库下载软件包</p><p>每个 Linux 发行版有个字不同的包管理器，他们通常会预先配置一组默认的软件包存储库，用于下载系统上的所有基础软件包。</p><p>以 Ubuntu 系统为例，旧版本的 Ubuntu 会把软件包的源配置在文件 <code>/etc/apt/sources.list</code> 中，当你使用 <code>apt install</code> 下载软件包时，会读取该文件配置的软件源。但通常会出现 Linux 发行版提供的默认软件包源的软件版本落后的情况，例如安装 <code>docker</code> 时，官方文档建议你手动配置软件源，手动配置的软件源位于 <code>/etc/apt/sources.list.d</code> 目录中。在新版本的 Ubuntu 中甚至默认使用 <code>/etc/apt/sources.list.d</code> 目录作为所有软件源的配置目录，默认的软件源的配置信息被迁移到了 <code>/etc/apt/sources.list.d/ubuntu.source</code> 中</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc ❯ ls /etc/apt/sources.list.d                                                       </span><br><span class="line"></span><br><span class="line">docker.list  jenkins.list  ubuntu.sources</span><br></pre></td></tr></table></figure><p>使用 <code>apt</code> 安装软件包时，会先读取 <code>*.list</code> 的软件源，如果没有，再从默认源下载</p><p>在 Linux 中，软件包很少是独立存在的，他们通常必须依赖其他组件才能正常运行，也就是<strong>依赖项</strong>。这些依赖项可以是其他包，或者更常见的是共享库，共享库是多个程序可以同时使用的预编译代码集合，可以节省开发人员为每个新应用程序写通用函数。如果软件在安装过程中缺乏所需的依赖项，这些软件包就是损坏的，Linux 包管理器旨在自动处理 Linux 包依赖项，获取并安装他们，防止损坏包的出现</p></li><li><p>压缩与归档</p><p>在 Linux 中，<strong>归档</strong>与<strong>压缩</strong>是两步操作</p><ul><li>归档：把多个文件 &#x2F; 目录 打包成一个文件</li><li>压缩：减小一个文件的大小以节省磁盘空间和加快传输过程</li></ul><p>由此可见，归档可以对多个文件 &#x2F; 目录使用，而压缩是针对一个文件使用的</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc/demodir ❯ tree                                                                                        ubuntu@vmubuntu  09:08:20 上午</span><br><span class="line">.</span><br><span class="line">├── content# 目录</span><br><span class="line">│   └── file3</span><br><span class="line">├── file1</span><br><span class="line">├── file2</span><br><span class="line">└── target# 目录</span><br><span class="line"></span><br><span class="line">3 directories, 3 files</span><br></pre></td></tr></table></figure><ul><li><p>压缩</p><p><code>gzip</code> 用于压缩文件，生成一个 <code>.gz</code> 结尾的压缩文件代替原文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc/demodir ❯ ls -a                                                                  </span><br><span class="line">.  ..  content  file1  file2  target</span><br><span class="line">  ~/mysrc/demodir ❯ gzip file1                                                             </span><br><span class="line">  ~/mysrc/demodir ❯ ls -a                                                                 </span><br><span class="line">.  ..  content  file1.gz  file2  target</span><br></pre></td></tr></table></figure><p>如果要解压，使用 <code>gunzip</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc/demodir ❯ gunzip file1.gz                                                       </span><br><span class="line">  ~/mysrc/demodir ❯ ls -a                                                                 </span><br><span class="line">.  ..  content  file1  file2  target</span><br></pre></td></tr></table></figure></li><li><p>归档</p><p><code>tar</code> 用于将多个文件 &#x2F; 目录打包成一个归档文件，通常以 <code>.tar</code> 结尾</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc/demodir ❯ ls -a                                                                 </span><br><span class="line">.  ..  content  file1  file2  target</span><br><span class="line">  ~/mysrc/demodir ❯ tar -cvf myarchieve.tar file1 file2 content                           </span><br><span class="line">file1</span><br><span class="line">file2</span><br><span class="line">content/</span><br><span class="line">content/file3</span><br><span class="line">  ~/mysrc/demodir ❯ ls -a                                                                 </span><br><span class="line">.  ..  content  file1  file2  myarchieve.tar  target</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>选项解释：</p><ul><li><code>-c</code> 表示创建归档</li><li><code>-v</code> 表示归档过程中输出详细信息，打印被归档的目录和文件</li><li><code>-f</code> 表示指定目标归档文件（这里的 <code>myarchieve.tar</code>）</li></ul><p>如果要提取归档文件，使用 <code>-x</code> 代替 <code>-c</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc/demodir ❯ tar -xvf myarchieve.tar -C target                                     </span><br><span class="line">file1</span><br><span class="line">file2</span><br><span class="line">content/</span><br><span class="line">content/file3</span><br><span class="line">  ~/mysrc/demodir ❯ ls target                                                              </span><br><span class="line">content  file1  file2</span><br></pre></td></tr></table></figure><p>提取归档文件通常配合 <code>-C</code> 选项使用，用于指定提取到目标位置</p></li><li><p>归档与压缩结合使用</p><p>最常见的用法是归档与压缩一起使用，先归档再压缩，创建 <code>.tar.gz</code> 文件，Linux 也为我们提供了快捷的命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc/demodir ❯ ls -a                                                                 </span><br><span class="line">.  ..  content  file1  file2  target</span><br><span class="line">  ~/mysrc/demodir ❯ tar -czvf myfile.tar.gz file1 file2 ./content                         </span><br><span class="line">file1</span><br><span class="line">file2</span><br><span class="line">./content/</span><br><span class="line">./content/file3</span><br><span class="line">  ~/mysrc/demodir ❯ ls -a                                                                 </span><br><span class="line">.  ..  content  file1  file2  myfile.tar.gz  target</span><br></pre></td></tr></table></figure><p>这里的 <code>-z</code> 选项表示在创建归档之后使用 <code>gzip</code> 压缩</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">  ~/mysrc/demodir ❯ tar -xzvf myfile.tar.gz -C target                                     </span><br><span class="line">file1</span><br><span class="line">file2</span><br><span class="line">./content/</span><br><span class="line">./content/file3</span><br><span class="line">  ~/mysrc/demodir ❯ ls target                                                             </span><br><span class="line">content  file1  file2</span><br></pre></td></tr></table></figure><p>这里的 <code>-x</code> 选项表示提取归档之前使用 <code>gzip</code> 解压，同样这里可以用 <code>-C</code> 指定解压到目标路径</p></li></ul></li><li><p><code>dpkg</code> 与 <code>rpm</code></p><p>如果要直接安装软件包，而不是通过包管理器安装，你可以在软件官网上找到 <code>.deb</code> 或 <code>.rpm</code> 文件，下载后通过以下命令安装：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">Debian 系统</span></span><br><span class="line">dpkg -i nginx.deb</span><br></pre></td></tr></table></figure><p><code>-i</code> 选项表示下载，你也可以用长选项 <code>--install</code> 代替</p><p>如果要移除下载的软件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除软件</span></span><br><span class="line">dpkg -r nginx</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除软件及其相关的配置文件（更彻底）</span></span><br><span class="line">dpkg -P nginx</span><br></pre></td></tr></table></figure><p>实际上，不管是通过包管理器还是手动从 <code>.deb</code> 文件下载的软件，都可由包管理器进行管理，使用 <code>dpkg -l</code> 列出所有已安装的包，他们都可以通过包管理器删除，更为干净彻底，自动处理依赖</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除软件</span></span><br><span class="line">sudo apt remove nginx</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">彻底删除</span></span><br><span class="line">sudo apt purge nginx</span><br></pre></td></tr></table></figure></li><li><p>包管理器</p><p>以 Debian 系操作系统为例，使用 <code>apt</code> 包管理器</p><p>安装与删除</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install nginx</span><br><span class="line">sudo apt remove nginx</span><br></pre></td></tr></table></figure><p>值得注意的是，使用 <code>apt</code> 包管理器下载 &#x2F; 更新软件之前更新软件源索引是个好习惯</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">更新软件源索引</span></span><br><span class="line">sudo apt update</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">更新软件</span></span><br><span class="line">sudo apt upgrade</span><br></pre></td></tr></table></figure><p><code>update</code> 只是更新了仓库源，真正更新软件的是 <code>upgrade</code>，<code>upgrade</code> 是保守更新，在不影响正常服务 &#x2F; 不改变依赖的情况下对软件升级，不会出现跨大版本更新导致服务 &#x2F; 配置失效的情况，只会更新向后兼容的小版本（尽管如此在开发中尽量不要全局使用 <code>apt upgrade</code>）</p></li><li><p>从源码编译下载</p><p>某些软件可能只提供从源码编译的下载方式，掌握如何编译源码来下载软件是必要的，这里以下载 <code>GNU Hello</code> 为例演示从源码编译的步骤。<code>GNU Hello</code> 是一个非常轻量的软件，它的作用仅仅是输出 <code>Hello World</code>，为教学编译使用，大部分 Linux 的发行软件的编译过程都与此类似（不过查看 README 总是好的）</p><ul><li><p>下载工具 <code>build-essential</code>（如果你没有的话）</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line">sudo apt install build-essential</span><br></pre></td></tr></table></figure><p>这个软件包括了一套软件开发工具，有 <code>gcc</code> 编译器 <code>make</code> 应用程序等，对编译代码至关重要</p></li><li><p>下载源码并解压</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wget https://ftp.gnu.org/gnu/hello/hello-2.12.tar.gz</span><br><span class="line">tar -xzvf hello-2.12.tar.gz</span><br><span class="line">cd ./hello-2.12 # 后续操作在软件项目根目录下执行</span><br></pre></td></tr></table></figure></li><li><p>执行 <code>./configure</code> 脚本</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./configure</span><br></pre></td></tr></table></figure><p>开发者会提供 <code>./configure</code> 脚本，他会运行一小段简单的程序，来检查你的机器上是否有该软件运行所必须的依赖，如果缺少依赖项，你需要在继续安装之前补全他们（不用看 yes &#x2F; no，如果缺失依赖会报错 error 的），然后生成 <code>Makefile</code>，其中包含了关于如何将源代码编译成可执行程序的规则</p></li><li><p>执行 <code>make</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make</span><br></pre></td></tr></table></figure><p>读取 <code>Makefile</code>，确认编译内容和顺序，然后执行编译，生成各种可执行文件和目标程序</p></li><li><p>安装软件</p><p>软件编译成功后，还需要安装到系统上，有两种安装方式，传统的安装方式是继续输入 <code>sudo make install</code>，系统会把编译后的文件复制到合适的目录下，但他有一个明显的缺点：这样安装的软件不会被注册到包管理器中，难以管理。因此，更好的方法是使用 <code>checkinstall</code></p><p><code>checkinstall</code>是一个第三方工具，你得先在你的系统上安装它</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install checkinstall</span><br></pre></td></tr></table></figure><p>执行安装</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo checkinstall</span><br></pre></td></tr></table></figure><p>它的原理是利用编译后到文件创建一个 <code>.deb</code> 包，然后安装这个应用</p><p>执行命令后，会输出一系列类似安装引导的文字，交互后即可成功安装</p></li><li><p>检查是否安装成功</p><p><img src="https://files.seeusercontent.com/2026/02/28/fp5Y/pasted-image-1772266649011.webp" alt="pasted-image-1772266649011.webp"></p></li></ul></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;Linux-Part-1&quot;&gt;&lt;a href=&quot;#Linux-Part-1&quot; class=&quot;headerlink&quot; title=&quot;Linux-Part.1&quot;&gt;&lt;/a&gt;Linux-Part.1&lt;/h1&gt;&lt;p&gt;Linux 总是无处不在的，或许我对他并不陌生，但从未系统学</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>5.给服务器配置代理</title>
    <link href="https://kiritosuki.top/2025/5.%E7%BB%99%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%90%86/"/>
    <id>https://kiritosuki.top/2025/5.%E7%BB%99%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%90%86/</id>
    <published>2025-11-14T03:47:00.000Z</published>
    <updated>2025-11-14T04:23:40.119Z</updated>
    
    <content type="html"><![CDATA[<h1 id="给服务器配置代理"><a href="#给服务器配置代理" class="headerlink" title="给服务器配置代理"></a>给服务器配置代理</h1><p>今天在服务器上部署项目时依旧遇到了国内服务器无法访问境外镜像资源的情况，由于已经遇到很多次了，所以本篇文章就来详细记录一下如何给服务器配置代理，避免大家踩坑。当然我的方案只是一种折中的方案，后续有精力再去折腾其他方案。</p><h2 id="基础环境"><a href="#基础环境" class="headerlink" title="基础环境"></a>基础环境</h2><ul><li>一台PC设备，操作系统任意，需要在本地PC上运行clash代理（我的端口是7897，根据个人情况会有所不同，请自己调整端口）。</li><li>一台服务器，有公网ip。</li><li>配置了服务器与本地PC的ssh连接与免密登录（最好是在<code>~/.ssh/config</code>文件里写个<code>Name</code>，方便使用，后续就不用写<code>username@&lt;ip&gt;</code>的形式了，也无需输入密码）。</li></ul><h2 id="原理说明"><a href="#原理说明" class="headerlink" title="原理说明"></a>原理说明</h2><ol><li>在服务器上配置<code>http/https</code>代理，将流量转发至服务器的7897端口。</li><li>通过ssh隧道连接，建立与本地PC的端口映射，将服务器7897的流量转发至本地PC的7897端口（由于本地PC无公网ip，服务器想要与其通信这里采用ssh隧道的方式）。</li></ol><h2 id="服务器操作"><a href="#服务器操作" class="headerlink" title="服务器操作"></a>服务器操作</h2><p>配置环境变量（注意端口换成你自己的）。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># 配置http与https流量 走clash系统代理 后面端口为clash运行端口</span><br><span class="line">export http_proxy=http://127.0.0.1:7897</span><br><span class="line">export https_proxy=http://127.0.0.1:7897</span><br><span class="line">export no_proxy=127.0.0.1,localhost</span><br></pre></td></tr></table></figure><p>直接在终端输入，退出终端作用域就无了，想要长期保存可以写在<code>.zprofile/.bash_profile/.zshrc/.bashrc</code>中，根据自己的shell选择（其实profile和rc文件写哪个都可以，之前看到过一篇帖子详细讲二者区别的，这里不细说了，平时习惯用哪个就写哪个吧，想要分清楚的可以去google一下）。</p><h2 id="本地PC操作"><a href="#本地PC操作" class="headerlink" title="本地PC操作"></a>本地PC操作</h2><ol><li>运行clash（最好把局域网连接也打开）。</li><li>建立ssh隧道（注意是-R不是-L，不要转发反了）。</li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh -R 127.0.0.1:7897:127.0.0.1:7897 &lt;servername&gt; -N</span><br></pre></td></tr></table></figure><p>注意端口和<code>servername</code>换成你自己的。</p><ol start="3"><li>这里ssh隧道是挂在终端前台运行的，这里给出一个bash脚本，大家可以复制下来保存到<code>serverClash.sh</code>文件中，方便后台运行，以及启动&#x2F;关闭ssh隧道（注意端口和<code>servername</code>换成你自己的）。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/bash</span><br><span class="line"></span><br><span class="line">TUNNEL=&quot;ssh -R 127.0.0.1:7897:127.0.0.1:7897 &lt;servername&gt; -N&quot;</span><br><span class="line">PID_FILE=&quot;$HOME/.ssh_tunnel.pid&quot;</span><br><span class="line"></span><br><span class="line">start() &#123;</span><br><span class="line">    if [ -f &quot;$PID_FILE&quot; ] &amp;&amp; kill -0 $(cat &quot;$PID_FILE&quot;) 2&gt;/dev/null; then</span><br><span class="line">        echo &quot;⚠️ 隧道已在运行中，PID=$(cat &quot;$PID_FILE&quot;)&quot;</span><br><span class="line">        exit 0</span><br><span class="line">    fi</span><br><span class="line">    $TUNNEL &amp;</span><br><span class="line">    echo $! &gt; &quot;$PID_FILE&quot;</span><br><span class="line">    echo &quot;✅ 隧道已启动，PID=$(cat &quot;$PID_FILE&quot;)&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">stop() &#123;</span><br><span class="line">    if [ -f &quot;$PID_FILE&quot; ]; then</span><br><span class="line">        kill $(cat &quot;$PID_FILE&quot;) &amp;&amp; rm &quot;$PID_FILE&quot;</span><br><span class="line">        echo &quot;🛑 隧道已停止&quot;</span><br><span class="line">    else</span><br><span class="line">        echo &quot;⚠️ 隧道未运行&quot;</span><br><span class="line">    fi</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">status() &#123;</span><br><span class="line">    if [ -f &quot;$PID_FILE&quot; ] &amp;&amp; kill -0 $(cat &quot;$PID_FILE&quot;) 2&gt;/dev/null; then</span><br><span class="line">        echo &quot;✅ 隧道正在运行，PID=$(cat &quot;$PID_FILE&quot;)&quot;</span><br><span class="line">    else</span><br><span class="line">        echo &quot;⚠️ 隧道未运行&quot;</span><br><span class="line">    fi</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">help() &#123;</span><br><span class="line">    echo &quot;================= SSH 隧道管理脚本 =================&quot;</span><br><span class="line">    echo &quot;用法: $0 &#123;start|stop|status|restart|help&#125;&quot;</span><br><span class="line">    echo &quot;&quot;</span><br><span class="line">    echo &quot;start    - 启动 SSH 反向隧道&quot;</span><br><span class="line">    echo &quot;stop     - 停止 SSH 反向隧道&quot;</span><br><span class="line">    echo &quot;status   - 查看隧道当前状态&quot;</span><br><span class="line">    echo &quot;restart  - 重启 SSH 反向隧道&quot;</span><br><span class="line">    echo &quot;help     - 显示此使用说明&quot;</span><br><span class="line">    echo &quot;====================================================&quot;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">case &quot;$1&quot; in</span><br><span class="line">    start) start ;;</span><br><span class="line">    stop) stop ;;</span><br><span class="line">    status) status ;;</span><br><span class="line">    restart) stop; start ;;</span><br><span class="line">    help) help ;;</span><br><span class="line">    *) echo &quot;⚠️ 参数错误或未提供参数&quot;; help ;;</span><br><span class="line">esac</span><br><span class="line"></span><br></pre></td></tr></table></figure><ol start="4"><li><p>给脚本添加执行权限</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">chmod +x serverClash.sh</span><br><span class="line"># 可以使用 ./serverClash.sh help 查看使用方法，很easy</span><br></pre></td></tr></table></figure></li></ol><h2 id="验证代理"><a href="#验证代理" class="headerlink" title="验证代理"></a>验证代理</h2><p>在服务器上<code>curl</code>一下google</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -I https://www.google.com</span><br></pre></td></tr></table></figure><p>（ping不通是正常的，ping的ICMP是网络层协议，clash代理是工作在传输层TCP &#x2F; UDP以上的，所以ping根本不会走代理端口，是直接由内核转发出去的）</p><h2 id="给docker配代理"><a href="#给docker配代理" class="headerlink" title="给docker配代理"></a>给docker配代理</h2><p>docker拉取镜像默认是不会走http &#x2F; https代理的，docker命令输⼊到shell后，是由docker客户端将请求转发给docker服务器 docker daemon 去执⾏，它并不会读取环境变量中http_proxy的配置。所以我们要给docker daemon也配个代理。</p><ol><li>Docker daemon 的代理配置文件放在：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/etc/systemd/system/docker.service.d/http-proxy.conf</span><br></pre></td></tr></table></figure><p>   如果目录不存在，先创建：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> <span class="built_in">mkdir</span> -p /etc/systemd/system/docker.service.d</span><br></pre></td></tr></table></figure><p>   写入配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf &gt; /dev/null &lt;&lt; EOF</span><br><span class="line">[Service]</span><br><span class="line">Environment=&quot;HTTP_PROXY=http://127.0.0.1:7897&quot;</span><br><span class="line">Environment=&quot;HTTPS_PROXY=http://127.0.0.1:7897&quot;</span><br><span class="line">Environment=&quot;NO_PROXY=localhost,127.0.0.1&quot;</span><br><span class="line">EOF</span><br></pre></td></tr></table></figure><p>   注意换成你自己的端口</p><ol start="2"><li>让 Docker 重新加载配置</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl daemon-reload</span><br><span class="line">sudo systemctl restart docker</span><br></pre></td></tr></table></figure><ol start="3"><li><p>测试是否成功走代理</p><p>运行：</p></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull hello-world</span><br></pre></td></tr></table></figure><p>   如果能拉下来，说明 docker daemon 已经成功走代理。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;给服务器配置代理&quot;&gt;&lt;a href=&quot;#给服务器配置代理&quot; class=&quot;headerlink&quot; title=&quot;给服务器配置代理&quot;&gt;&lt;/a&gt;给服务器配置代理&lt;/h1&gt;&lt;p&gt;今天在服务器上部署项目时依旧遇到了国内服务器无法访问境外镜像资源的情况，由于已经遇到很多次了</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>4.计网-交换机VLAN工作原理</title>
    <link href="https://kiritosuki.top/2025/4.%E8%AE%A1%E7%BD%91-%E4%BA%A4%E6%8D%A2%E6%9C%BAVLAN%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/"/>
    <id>https://kiritosuki.top/2025/4.%E8%AE%A1%E7%BD%91-%E4%BA%A4%E6%8D%A2%E6%9C%BAVLAN%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/</id>
    <published>2025-10-07T08:12:00.000Z</published>
    <updated>2025-10-09T02:20:28.701Z</updated>
    
    <content type="html"><![CDATA[<h1 id="交换机的VLAN是如何工作的？"><a href="#交换机的VLAN是如何工作的？" class="headerlink" title="交换机的VLAN是如何工作的？"></a>交换机的VLAN是如何工作的？</h1><p>一篇文章，用最通俗的人话，带你了解交换机VLAN的工作原理TvT！</p><h2 id="什么是交换机？"><a href="#什么是交换机？" class="headerlink" title="什么是交换机？"></a>什么是交换机？</h2><p>交换机，工作在五层参考模型中的<strong>数据链路层</strong>，用于局域网内的设备通信，负责转发数据帧的网络设备。</p><p>交换机是一种即插即用的网络设备，无需管理员手动分配，它会自动维护一张MAC地址表，关联连接设备的MAC地址与自己的端口。交换机从端口接收数据帧，查看数据帧的目标MAC地址，根据MAC地址表，选择合适的端口发送数据帧。</p><p>如果MAC地址表中不存在目标设备的MAC地址，交换机则会通过广播的形式发送数据帧到所有连接设备，交换机是通过观察流量的源MAC地址来学习的，因此如果目标设备收到了目的地是自己MAC地址的数据帧，会返回一个响应帧，交换机根据这次发送的源MAC地址来学习，把目标MAC地址和端口号加入到MAC地址表中，与此同时记录的还有更新时间，如果长时间无通信，该条数据将会由超时机制被从MAC地址表中移除。</p><h2 id="什么是VLAN？"><a href="#什么是VLAN？" class="headerlink" title="什么是VLAN？"></a>什么是VLAN？</h2><p><strong>Virtual Local Area Network（VLAN）</strong>，即<strong>虚拟局域网</strong>。这里从VLAN的用途，逐步详细介绍VLAN的工作原理，最后会涉及一个小练习，如果你能成功完成它，代表你已经成为VLAN高手了！</p><h3 id="VLAN——将一个物理交换机分割成多个虚拟交换机"><a href="#VLAN——将一个物理交换机分割成多个虚拟交换机" class="headerlink" title="VLAN——将一个物理交换机分割成多个虚拟交换机"></a>VLAN——将一个物理交换机分割成多个虚拟交换机</h3><p>我们知道，交换机用于同一个局域网内的设备间的通信；类似路由器用于局域网网络之间的通信一样。</p><img src="https://s2.loli.net/2025/10/07/o8EALeluFW3GtDU.png" alt="image.png" style="zoom:50%;" /><p>这里有三个局域网，通过两台路由器连接，每个局域网中的设备通过交换机连接，如果每个交换机上有24个端口，但只有两个端口被使用了，剩下的22个端口不就被浪费了吗？</p><p>明明一个交换机的端口数量，已经绰绰有余了，但这里我却用了三个交换机。如果我想搭建这样一个网络模型，能不能只使用一个交换机呢？</p><p>当然是可以的，这就需要用到<strong>VLAN</strong>技术了，它允许你把一个物理交换机划分为多个虚拟交换机。</p><img src="https://s2.loli.net/2025/10/07/3RiVUGHcakhP5et.png" alt="image.png" style="zoom:50%;" /><p>这个网络结构与之前的完全相同，路由器的配置和运行方式也一模一样。</p><p>每个虚拟局域网，即VLAN，仅仅是一个给端口号数字标记，例如：红色虚拟交换机上的两个端口被标记为<code>VLAN #10</code>，黄色虚拟交换机上的两个端口被标记为<code>VLAN #20</code>，蓝色虚拟交换机上的两个端口被标记为<code>VLAN #30</code>。</p><p>那么其他没有被特别标记的端口，都属于默认VLAN，一般是<code>VLAN #1</code>。</p><p>每个VLAN是一个虚拟局域网，如果流量没有经过路由器的转发，即使是在同一个交换机上的端口，也无法跨局域网通信。例如，A发送的数据包，在未经路由器转发时，只能由<code>VLAN #10</code>这个局域网内的设备收到，不可能神奇的出现在<code>VLAN #20</code>，<code>VLAN #30</code>其他局域网中。</p><p>那么，问题来了，交换机是如何知道来自一个设备的流量，都需要转发到哪些端口呢？</p><p>为了实现这一目标，每个交换机都维护一个MAC地址表，该表是连接到每个交换端口的MAC地址的映射。支持VLAN的交换机还会在每个 MAC 地址表的条目中包含 <code>VLAN #</code>。</p><p>一个支持 VLAN 的交换机的 MAC 地址表中的单个条目的简单表示为： <code>VLAN# | MAC Address | Port</code> 。</p><p>如果主机A发送一个目标MAC地址为主机 B 的数据帧，那么这个数据帧仍然只会被发送到 <code>VLAN #10</code> 的交换机端口上。即使MAC地址表中存在与 <code>VLAN #30</code> 相关的主机 B 条目，但不在一个局域网内，就无法直接发送。</p><h3 id="VLAN——将虚拟交换机扩展到多个物理交换机上"><a href="#VLAN——将虚拟交换机扩展到多个物理交换机上" class="headerlink" title="VLAN——将虚拟交换机扩展到多个物理交换机上"></a>VLAN——将虚拟交换机扩展到多个物理交换机上</h3><p>VLAN的强大功能不止上面如此，此外，他可以将虚拟交换机扩展到多个物理交换机上。</p><img src="https://s2.loli.net/2025/10/07/rygZoHvNcLa6Rdj.png" alt="image.png" style="zoom:50%;" /><p>这里将两个交换机连接起来，例如设备A和C，虽然他们连接到了两台不同的交换机，但他们都在<code>VLAN #10</code>中，因此，主机A发送的流量，无需经过路由器，就可以被主机C接收。</p><p>这意味着，单个VLAN可以跨越物理地域限制，例如：我在7号楼，你在8号楼，我们的设备连接的是不同的交换机，但我们仍然可以在同一个局域网<code>VLAN #10</code>中，进行局域网内的通信。单个VLAN可以跨越多个房间、楼层或办公楼。</p><h3 id="Access-VS-Trunk"><a href="#Access-VS-Trunk" class="headerlink" title="Access VS Trunk"></a>Access VS Trunk</h3><p>上面我们介绍的情况，交换机的端口都属于<code>acceess</code>端口，<code>access</code>端口是只属于一个 VLAN 的交换机端口。</p><p>当将端口配置为access端口时，管理员也会指定该端口所属的VLAN编号。每当交换机在access端口上收到任何流量时，它就会将该流量转发到配置的VLAN中。</p><p>上面的例子中，两台交换机连接时，我们注意到<code>VLAN #10</code>和<code>VLAN #30</code>分别用网线进行了一次连接。</p><p>想象一下，如果我们的拓扑使用了十个VLAN，在一个有24个端口的交换机上，几乎有一半的端口会被用于交换机之间的链路。</p><p>这也太浪费了吧，能不能让两个交换机的所有VLAN流量，都通过一对端口来进行转发呢？</p><p>实际上，这种端口是存在的，称为<code>trunk</code>端口，它允许单个交换机端口承载来自多个VLAN的流量。</p><img src="https://s2.loli.net/2025/10/07/YpaDWSt7j1lTFo8.png" alt="image.png" style="zoom:50%;" /><p>采用trunk端口，我们的拓扑模型可以简化成这样。大大减少了端口的浪费，在实际应用中，通常，连接到终端设备（如工作站、打印机、服务器）的交换机端口配置为access端口。相反，连接到其他网络设备的交换机端口配置为trunk端口（如其他交换机、路由器）。</p><p>交换机上的一个trunk端口可以接收多个VLAN的流量。例如，在上面的图中，两个交换机之间的链路正在传输 VLAN 10 和 VLAN 30 的流量。</p><p>但在两种情况下，流量都是以一系列帧的形式离开一个交换机，并以一系列帧的形式到达另一个交换机。这就引出了一个问题：接收交换机将如何确定哪些帧属于 <code>VLAN #10</code> ，哪些帧属于 <code>VLAN #30</code> ？</p><p>这就引出了trunk端口的核心作用，“打标签”。</p><p>为了解决这个问题，每当交换机通过trunk端口发送帧时，它会在每个帧中添加一个标签，以告知另一端该帧属于哪个 VLAN。这使得接收交换机能够读取 VLAN 标签，从而确定传入流量应该关联到哪个 VLAN。</p><p>相比之下，access端口只能承载或接收单个 VLAN 的流量。因此，离开access端口的流量无需添加 VLAN 标签。换句话说，access端口根本不认识VLAN标签，由access端口发送的流量都是不带有VLAN标签的，看起来就好像是它做了“标签清零”的功效一样。</p><p>之前我们提到access端口通常用于终端主机设备，如工作站、打印机或服务器，这使得主机可以无需了解其连接的 VLAN 信息即可运行。从某种意义上说，主机有意地对 VLAN 的存在或使用完全不知情。主机无需关心交换机VLAN的使用情况，主机只是在网络上发送数据，而无需了解 VLAN 或它们可能连接的交换机。</p><p>因此，按通常的惯例来讲，到达主机的流量应该不能带有VLAN标签，有些主机能够接收带有 VLAN 标签的帧，而有些主机则不能——这取决于主机是否理解 802.1q VLAN 标签。（大部分主机都是不能的，部分服务器，虚拟机，专用网卡可以）（VLAN 标签的确切格式由 802.1Q 标准规定。这是一个开放式的 IEEE 标准，是当今普遍使用的 VLAN 标签方法。你可能也会见到 ISL 等，这属于较旧的 VLAN 标记方法，现在几乎已经不再使用了。）</p><p>比较特别的一个例子是，如果单个物理主机托管多个虚拟机（VMs）。某些情况下，这些 VM 中的每一个都需要存在于不同的 VLAN 中。因此，物理主机必须连接到一个 trunk 端口，并且必须发送和接收 VLAN 标签，以便将虚拟机流量限制在特定的 VLAN。</p><h3 id="Native-VLAN"><a href="#Native-VLAN" class="headerlink" title="Native VLAN"></a>Native VLAN</h3><p>这是一个很容易混淆的概念，前面我们说过，trunk端口在发送帧的时候， 会向帧中添加一个标签，但这里有个例外，<strong>当trunk端口收到了来自Native VLAN的数据帧的时候，不会为这个数据帧打标签</strong>，我们结合这个例子来看：</p><img src="https://s2.loli.net/2025/10/07/phYSZ6GLq19uWNb.png" alt="image.png" style="zoom:50%;" /><p>每个trunk都会有一个<code>Native VLAN</code>，这样做的原因是如果trunk端口接收到了来自另外一个trunk端口的数据帧，但该数据帧又不带有VLAN标签，trunk端口就会难以判断这个数据帧要发送到哪个局域网内，此时，这个trunk端口将不会再为该数据帧打上标签，而是默认这个数据帧属于<code>Native VLAN</code>，直接把它转发到<code>Native VLAN</code>局域网中。</p><p>因此，确保中继端口的两端配置相同原生 VLAN 至关重要。如上图：</p><p>若A向C发送了一个数据帧，由于主机与交换机之间的端口是access，该数据帧不带有VLAN标签，当数据帧到达x交换机的trunk端口后，发现是从<code>VLAN 22</code>传来的数据帧，该数据帧恰好是trunk端口的Native VLAN，于是trunk端口没有为这个数据帧打VLAN标签，就直接转发给了交换机y，交换机y收到了该数据帧，发现没有VLAN标签，前面说过，trunk接收到了来自trunk且没有VLAN标签的数据帧，则接受trunk认为这个数据帧是来自自己的Native VLAN的，即<code>VLAN 33</code>，于是它只会在<code>VLAN 33</code>内转发，此时，设备C就无法按照预期收到数据帧，甚至更为严重的是，设备D可能会意外的收到这份数据帧！</p><p>所以简单记忆：</p><ul><li>access去除标签</li><li>trunk添加标签</li><li>例外：trunk收到来自Native VLAN的数据帧时不添加标签，trunk收到来自trunk且无标签的数据帧时默认它属于Native VLAN，在Native VLAN内转发</li></ul><h2 id="小练习"><a href="#小练习" class="headerlink" title="小练习"></a>小练习</h2><img src="https://s2.loli.net/2025/10/07/xN23O4oAIacE1Xj.png" alt="image.png" style="zoom:50%;" /><p>问题：如果A对所有设备发送广播，有哪些设备能收到？</p><p>答案：C，F，H，I，B</p><p>主机 J 和主机 K 也会收到广播，但当它们接收到帧时，该帧将包含 VLAN 标签。有些主机能够接收带有 VLAN 标签的帧，而有些主机则不能——这取决于主机是否理解 802.1q VLAN 标签。</p><p>解析：设备A发送的数据帧从A3发送，不带有标签，在swT内会被转发到A3和trunk端口，即A3，T7。因此设备C可以接收到，T7发送该数据帧到swV时，会为它带上<code>VLAN 3</code>的标签，T5收到该数据帧，在swV内转发给A3端口和trunk端口，即A3。数据帧的标签在经过access时会被去掉，因此设备F可以正常接收到数据帧。同时，数据帧被swV从A3端口发送给swX，swX的A4端口接收到该数据帧，只能把它发送给A4和trunk端口，即A4，T4。此时设备H能正常接收到这个不带有标签的数据帧，数据帧被swX通过T4转发到swY，T4为数据帧添加了<code>VLAN 4</code>标签，但到达swY时，该标签又被A6去除，A6把数据帧转发给A6和trunk，即T4，T6。T4会为该数据帧加上<code>VLAN 6</code>标签，前面说过，绝大多数设备无法接收带有标签的数据帧，因此J无法正常接收。但T6的Native VLAN恰好是<code>VLAN 6</code>，因此T6不会再为数据帧加上<code>VLAN 6</code>标签，到达设备I的是不带有标签的数据帧，因此设备I可以正常接收。swY把数据帧由T6转发给swZ，swZ经A9接收后数据帧不带有标签，转发给A9和trunk，即A9，T8，由于T8会为数据帧添加上<code>VLAN 9</code>的标签，设备K无法正常接收，而设备B连接的端口是access端口，到达设备B的数据帧不带有标签。综上，能正常收到数据的设备有C，F，H，I，B。</p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><p><a href="https://www.practicalnetworking.net/stand-alone/vlans/?utm_source=chatgpt.com"><strong>Virtual Local Area Networks (VLANs)</strong></a></p><p>感兴趣的友友可以去阅读一下原文，写的更详细。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;交换机的VLAN是如何工作的？&quot;&gt;&lt;a href=&quot;#交换机的VLAN是如何工作的？&quot; class=&quot;headerlink&quot; title=&quot;交换机的VLAN是如何工作的？&quot;&gt;&lt;/a&gt;交换机的VLAN是如何工作的？&lt;/h1&gt;&lt;p&gt;一篇文章，用最通俗的人话，带你了解交</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>3.搭建个人VPN</title>
    <link href="https://kiritosuki.top/2025/3.%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BAVPN/"/>
    <id>https://kiritosuki.top/2025/3.%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BAVPN/</id>
    <published>2025-09-28T07:12:00.000Z</published>
    <updated>2025-10-09T02:20:45.796Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何搭建一个个人VPN呢？"><a href="#如何搭建一个个人VPN呢？" class="headerlink" title="如何搭建一个个人VPN呢？"></a>如何搭建一个个人VPN呢？</h1><p>想要科学（？）的使用网络，往往有两种方式，代理（proxy）or 私有局域网（VPN）</p><p>什么？你的梯子又双挂了，为什么不搭建一个自己的VPN呢？</p><blockquote><p>本篇文章仅供计网知识学习交流使用</p></blockquote><h2 id="租借一个海外的服务器"><a href="#租借一个海外的服务器" class="headerlink" title="租借一个海外的服务器"></a>租借一个海外的服务器</h2><p>首先，你需要有一台海外&#x2F;香港等地区的服务器，确保服务器可以正常访问海外网站即可。</p><p>云服务厂商推荐：</p><ul><li>国内：阿里云&#x2F;腾讯云（推荐腾讯云锐驰型，公网流量不会额外收费）</li><li>国外：vultr</li></ul><p>购买国外服务器需要国际支付方式，推荐：海外银行卡&#x2F;PayPal。</p><p>（国内银行卡好像也可以试试，但我试的不太行，换了两张卡都被海外厂商封掉了跨境交易…）</p><p>服务器配置买最低的即可，服务器只用来做流量转发，配置太高只会浪费。</p><h2 id="搭建VPN"><a href="#搭建VPN" class="headerlink" title="搭建VPN"></a>搭建VPN</h2><p>本教程采用WireGuard工具来搭建，大家也可以选择其他的。</p><p>服务器操作系统为ubuntu，客户端操作系统为macOS。（windows可以使用wireguard官方的图形化工具）</p><h3 id="安装WireGuard"><a href="#安装WireGuard" class="headerlink" title="安装WireGuard"></a>安装WireGuard</h3><p>登录到服务器，安装<code>wireguard</code>。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install -y wireguard</span><br></pre></td></tr></table></figure><h3 id="生成服务器密钥对"><a href="#生成服务器密钥对" class="headerlink" title="生成服务器密钥对"></a>生成服务器密钥对</h3><ol><li>更改文件权限，确保新建的文件只有自己的用户可以访问。</li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">umask</span> 077</span><br></pre></td></tr></table></figure><ol start="2"><li>创建目录<code>wireguardkey</code>。</li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> wireguardkey</span><br><span class="line"><span class="built_in">cd</span> ~/wireguardkey</span><br></pre></td></tr></table></figure><ol start="3"><li>生成服务端密钥对，将公钥和私钥保存到这两个文件中，这两个key文件会生成于你所在的目录下。</li></ol><ul><li><code>server_private.key</code>：私钥，写入配置文件。</li><li><code>server_public.key</code>：公钥，给客户端用。</li></ul><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wg genkey | <span class="built_in">tee</span> server_private.key | wg pubkey &gt; server_public.key</span><br></pre></td></tr></table></figure><ol start="4"><li>开启内核转发。</li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vim /etc/sysctl.conf</span><br></pre></td></tr></table></figure><p>输入<code>/</code>进行查找 or 添加以下内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">net.ipv4.ip_forward=1</span><br><span class="line">net.ipv6.conf.all.forwarding=1</span><br></pre></td></tr></table></figure><p>之后保存退出，用以下命令使修改生效：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> sysctl -p</span><br></pre></td></tr></table></figure><h3 id="生成客户端密钥对"><a href="#生成客户端密钥对" class="headerlink" title="生成客户端密钥对"></a>生成客户端密钥对</h3><ol><li>创建目录<code>wireguardkey</code>，切换到该目录下。</li><li>安装<code>WireGuard</code>命令行工具。</li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install wireguard-tools</span><br></pre></td></tr></table></figure><ol start="3"><li>和服务端类似，生成客户端密钥对。</li></ol><ul><li><p><code>client_private.key</code>：私钥，写入客户端配置。</p></li><li><p><code>client_public.key</code>：公钥，要写到服务器配置里。</p></li></ul><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">umask</span> 077</span><br><span class="line">wg genkey | <span class="built_in">tee</span> client_private.key | wg pubkey &gt; client_public.key</span><br></pre></td></tr></table></figure><h3 id="将密钥写入配置文件"><a href="#将密钥写入配置文件" class="headerlink" title="将密钥写入配置文件"></a>将密钥写入配置文件</h3><ol><li>编辑服务器配置文件：<code>/etc/wireguard/wg0.conf</code></li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">[Interface]</span><br><span class="line">Address = 10.0.0.1/24, fd86:ea04:1115::1/64</span><br><span class="line">ListenPort = 51820</span><br><span class="line">PrivateKey = &lt;ServerPrivateKey&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># IPv4 NAT</span></span><br><span class="line">PostUp = iptables -A FORWARD -i wg0 -j ACCEPT</span><br><span class="line">PostUp = iptables -A FORWARD -o wg0 -j ACCEPT</span><br><span class="line">PostUp = iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE</span><br><span class="line"></span><br><span class="line"><span class="comment"># IPv6 转发</span></span><br><span class="line">PostUp = ip6tables -A FORWARD -i wg0 -j ACCEPT</span><br><span class="line">PostUp = ip6tables -A FORWARD -o wg0 -j ACCEPT</span><br><span class="line">PostUp = ip6tables -t nat -A POSTROUTING -s fd86:ea04:1115::/64 -o eth0 -j MASQUERADE</span><br><span class="line"></span><br><span class="line"><span class="comment"># 下线清理</span></span><br><span class="line">PostDown = iptables -D FORWARD -i wg0 -j ACCEPT</span><br><span class="line">PostDown = iptables -D FORWARD -o wg0 -j ACCEPT</span><br><span class="line">PostDown = iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE</span><br><span class="line"></span><br><span class="line">PostDown = ip6tables -D FORWARD -i wg0 -j ACCEPT</span><br><span class="line">PostDown = ip6tables -D FORWARD -o wg0 -j ACCEPT</span><br><span class="line">PostDown = ip6tables -t nat -D POSTROUTING -s fd86:ea04:1115::/64 -o eth0 -j MASQUERADE</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">[Peer]</span><br><span class="line"><span class="comment"># 这是客户端的信息</span></span><br><span class="line">PublicKey = &lt;ClientPublicKey&gt;</span><br><span class="line">AllowedIPs = 10.0.0.2/32, fd86:ea04:1115::2/128</span><br></pre></td></tr></table></figure><ul><li>把<code>&lt;ServerPrivateKey&gt;</code>换成你的<code>server_private.key</code>内容</li><li>把<code>&lt;ClientPublicKey&gt;</code>换成你的<code>client_public.key</code>内容</li><li>把<code>eth0</code>换成你的服务器外网网卡名（可用<code>ip route</code>查看，显示<code>dev eth0</code>就表示<code>eth0</code>是外网网卡）</li></ul><ol start="2"><li>编辑客户端配置文件：<code>/opt/homebrew/etc/wireguard/wg0.conf</code></li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">[Interface]</span><br><span class="line">Address = 10.0.0.2/24, fd86:ea04:1115::2/64</span><br><span class="line">PrivateKey = &lt;ClientPrivateKey&gt;</span><br><span class="line">DNS = 8.8.8.8, 2001:4860:4860::8888</span><br><span class="line"></span><br><span class="line">[Peer]</span><br><span class="line">PublicKey = &lt;ServerPublicKey&gt;</span><br><span class="line">Endpoint = &lt;ServerIP&gt;:51820</span><br><span class="line">AllowedIPs = 0.0.0.0/0, ::/0</span><br><span class="line">PersistentKeepalive = 25</span><br></pre></td></tr></table></figure><ul><li><p>把<code>&lt;ClientPrivateKey&gt;</code>换成<code>client_private.key</code> 内容</p></li><li><p><code>&lt;ServerPublicKey&gt;</code>换成服务器生成的 <code>server_public.key</code> 内容</p></li><li><p><code>&lt;ServerIP&gt;</code>换成服务器的公网ip</p></li><li><p>⚠️注意：如果你的服务器不支持ipv6，需要把<code>[Peer]</code>中的<code>AllowedIPs = 0.0.0.0/0, ::/0</code>更改为<code>AllowedIPs = 0.0.0.0/0</code></p></li></ul><h3 id="启动服务"><a href="#启动服务" class="headerlink" title="启动服务"></a>启动服务</h3><blockquote><p>在启动服务前，请检查你的服务器防火墙或者是云服务商的安全组，确保<code>51820</code>端口的<code>UDP</code>流量是开放的。</p></blockquote><ol><li>在服务器执行：</li></ol><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl start wg-quick@wg0</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> wg-quick@wg0</span><br></pre></td></tr></table></figure><p>启动服务，指定wg0配置文件，并设置为开机自启，可用<code>sudo systemctl status wg-quick@wg0</code>查看服务状态。</p><ol start="2"><li>在客户端执行：</li></ol><ul><li>开启VPN服务：</li></ul><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> wg-quick up wg0</span><br></pre></td></tr></table></figure><ul><li>关闭VPN服务：</li></ul><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> wg-quick down wg0</span><br></pre></td></tr></table></figure><h2 id="验证服务"><a href="#验证服务" class="headerlink" title="验证服务"></a>验证服务</h2><p>按照上面的流程，你的VPN服务应该已经可以正常运行了。</p><p>可以使用以下方法验证一下：</p><ul><li><p><code>ping</code>一下google</p></li><li><p>检查自己的ip地址是不是服务器的ip：<code>curl ifconfig.me</code></p></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;如何搭建一个个人VPN呢？&quot;&gt;&lt;a href=&quot;#如何搭建一个个人VPN呢？&quot; class=&quot;headerlink&quot; title=&quot;如何搭建一个个人VPN呢？&quot;&gt;&lt;/a&gt;如何搭建一个个人VPN呢？&lt;/h1&gt;&lt;p&gt;想要科学（？）的使用网络，往往有两种方式，代理（pr</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>2.初始化multipass虚拟机</title>
    <link href="https://kiritosuki.top/2025/2.%E5%88%9D%E5%A7%8B%E5%8C%96multipass%E8%99%9A%E6%8B%9F%E6%9C%BA/"/>
    <id>https://kiritosuki.top/2025/2.%E5%88%9D%E5%A7%8B%E5%8C%96multipass%E8%99%9A%E6%8B%9F%E6%9C%BA/</id>
    <published>2025-09-24T07:07:00.000Z</published>
    <updated>2025-09-29T06:31:32.983Z</updated>
    
    <content type="html"><![CDATA[<h1 id="如何初始化一个multipass虚拟机？"><a href="#如何初始化一个multipass虚拟机？" class="headerlink" title="如何初始化一个multipass虚拟机？"></a>如何初始化一个multipass虚拟机？</h1><p>包含zsh主题配置，ssh连接，root登录，基本配置等。</p><p>以macOS为例，windows的操作也类似，部分目录结构有差别。</p><hr><h2 id="新建一个multipass虚拟机"><a href="#新建一个multipass虚拟机" class="headerlink" title="新建一个multipass虚拟机"></a>新建一个multipass虚拟机</h2><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">multipass launch --name vmubuntu --cpus 2 --memory 4G --disk 40G</span><br></pre></td></tr></table></figure><p>其中：</p><ul><li>name：你的虚拟机名称</li><li>cpus：分配的cpu个数</li><li>memory：内存</li><li>disk：磁盘大小</li></ul><p>创建成功后，可以使用<code>multipass ls</code>来查看所有的虚拟机实例，镜像，和运行状态。</p><p>如果想要查看某个虚拟机的具体配置，请使用<code>multipass info &lt;name&gt;</code>。</p><p>虚拟机创建后默认是开机自启的，可以使用<code>multipass start &lt;name&gt;</code>和<code>multipass stop &lt;name&gt;</code>来控制虚拟机的启动与关闭。</p><p>使用<code>multipass shell &lt;name&gt;</code>来进入到你的虚拟机实例。默认用户为<code>ubuntu</code>。</p><hr><h2 id="配置ssh连接"><a href="#配置ssh连接" class="headerlink" title="配置ssh连接"></a>配置ssh连接</h2><h3 id="安装openSSH"><a href="#安装openSSH" class="headerlink" title="安装openSSH"></a>安装openSSH</h3><p>multipass默认是最新版本的ubuntu镜像，它也仅支持这一种操作系统，因此非常轻量级。</p><p>首先，你需要先进入虚拟机实例，安装openSSH服务。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install openssh-server -y</span><br></pre></td></tr></table></figure><p>启动ssh服务并设置为开机自启。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl start ssh</span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> ssh</span><br></pre></td></tr></table></figure><p>可以使用下列命令查看启动状态。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl status ssh</span><br></pre></td></tr></table></figure><p>结果显示<code>active(running)</code>表示服务正在运行，<code>enable</code>表示开机自启配置成功。</p><h3 id="配置虚拟机服务端ssh连接"><a href="#配置虚拟机服务端ssh连接" class="headerlink" title="配置虚拟机服务端ssh连接"></a>配置虚拟机服务端ssh连接</h3><p>multipass默认会禁用作为服务端的ssh连接，我们需要修改一些配置。</p><p>当你想要通过宿主机使用ssh连接到虚拟机时，此时虚拟机作为服务端，你的物理机作为客户端。</p><p>配置虚拟机作为服务端ssh连接，需要编辑以下文件。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vim /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><p>vim编辑器中，切换到命令模式，使用<code>/</code>来进行搜索，键入搜索内容，用<code>N,n</code>来切换匹配项，我们需要改的有四个：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">PermitRootLogin <span class="built_in">yes</span>              <span class="comment"># 允许 root 登录</span></span><br><span class="line">PasswordAuthentication <span class="built_in">yes</span>       <span class="comment"># 允许密码登录</span></span><br><span class="line">PubkeyAuthentication <span class="built_in">yes</span>         <span class="comment"># 允许密钥登录</span></span><br><span class="line">KbdInteractiveAuthentication <span class="built_in">yes</span> <span class="comment"># 允许交互认证</span></span><br></pre></td></tr></table></figure><p>之后保存，退出，重启ssh服务来使配置生效。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> systemctl restart ssh</span><br></pre></td></tr></table></figure><p>我们还需要用以下命令来为用户设置密码。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> passwd root</span><br><span class="line"><span class="built_in">sudo</span> passwd ubuntu</span><br></pre></td></tr></table></figure><p>之后使用<code>exit</code>退出虚拟机，尝试ssh连接。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ssh root@&lt;ip&gt;</span><br><span class="line">ssh ubuntu@&lt;ip&gt;</span><br></pre></td></tr></table></figure><p>现在可以登录成功了，但每次需要输入密码，还是有点麻烦，接下来来配置一下ssh密钥，实现免密登录。</p><h3 id="ssh免密登录"><a href="#ssh免密登录" class="headerlink" title="ssh免密登录"></a>ssh免密登录</h3><p>切换到你的宿主机终端，执行以下命令生成ssh密钥对，<code>-f</code>后面跟的是你要保存的密钥文件位置。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -b 4096 -C <span class="string">&quot;your_email@example.com&quot;</span> -f ~/.ssh/id_vmubuntu </span><br></pre></td></tr></table></figure><p>之后一路回车即可，会在<code>~/.ssh</code>目录下生成密钥对。</p><p>接下来编辑宿主机<code>~/.ssh</code>目录下的<code>config</code>文件，加入以下配置。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Host vmubuntu</span><br><span class="line">    HostName &lt;ip&gt;</span><br><span class="line">    User &lt;name&gt;</span><br><span class="line">    IdentityFile ~/.ssh/id_vmubuntu</span><br></pre></td></tr></table></figure><p>如果你是macOS，请确保你在该文件中已经添加了下列全局配置。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Host *</span><br><span class="line">    UseKeyChain <span class="built_in">yes</span></span><br><span class="line">    AddKeysToAgent <span class="built_in">yes</span></span><br></pre></td></tr></table></figure><p>之后进入虚拟机，编辑以下文件：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vim ~/.ssh/authorized_keys</span><br></pre></td></tr></table></figure><p>这里是你放公钥的地方，但你可能会发现里面已经有一个ssh公钥了，原因是实际上当你使用<code>multipass shell &lt;name&gt;</code>连接到虚拟机时，实际上他的底层是ssh连接，当你创建一个虚拟机时，会自动配置这对ssh密钥，你可以选择保留它（建议保留），在后面添加你自己的ssh密钥即可。</p><p>之后使用<code>ssh &lt;name&gt;</code>就可以直接连接了。</p><hr><h2 id="配置zsh"><a href="#配置zsh" class="headerlink" title="配置zsh"></a>配置zsh</h2><p>multipass的默认终端是bash，接下来我们把它修改为zsh，并配置好看的主题和代码补全插件。</p><h3 id="下载字体"><a href="#下载字体" class="headerlink" title="下载字体"></a>下载字体</h3><p>当你连接到虚拟机时，此时你的终端并没有改变，因此默认还会使用你终端的字体，你可以使用系统自带的终端或者是vscode的终端等等，强烈建议下载nerd字体，便于p10k主题图标和字体的正确渲染。</p><ul><li><p>如果你是windows用户或者ubuntu桌面版用户，建议去官网下载，解压到你的字体目录中。</p></li><li><p>如果你是macOS用户，通过brew下载或者在官网下载都是可以的，默认会放进你的字体目录中。</p></li></ul><p>官网：<a href="https://www.nerdfonts.com/">nerd fonts</a></p><p>之后在终端设置中选择字体<code>MesloLGS Nerd Font Mono</code>即可。</p><h3 id="安装zsh"><a href="#安装zsh" class="headerlink" title="安装zsh"></a>安装zsh</h3><p>首先使用以下命令查看自己的可用shell中是否有zsh，有的话就可以跳过这一步了。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> /etc/shells</span><br></pre></td></tr></table></figure><p>没有的话需要用以下命令安装：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install zsh -y</span><br></pre></td></tr></table></figure><p>安装之后用以下命令把zsh设置为默认终端：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">chsh -s $(<span class="built_in">which</span> zsh)</span><br></pre></td></tr></table></figure><p>退出你的虚拟机，重新登录后即可生效，你会进入zsh的欢迎界面，这里会让你选择<code>.zshrc</code>的配置方式，先选择0即可，后续配置主题时还会修改。</p><p>也可以使用以下命令检查：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="variable">$SHELL</span>   <span class="comment"># 输出默认shell</span></span><br><span class="line">ehco <span class="variable">$0</span>       <span class="comment"># 输出当前所在shell</span></span><br></pre></td></tr></table></figure><h3 id="安装on-my-zsh"><a href="#安装on-my-zsh" class="headerlink" title="安装on-my-zsh"></a>安装on-my-zsh</h3><p>on-my-zsh是一个强大的终端工具，可以配置各种插件和主题，下面我们来安装p10k主题，代码补全和高亮提示。</p><p>在这之前，你需要先配置代理，否则可能会下载的很慢（你也可以去网上自己找镜像源）。</p><p>编辑<code>~/.profile</code>文件，添加以下内容，clash需要打开局域网模式。</p><p>顺便把语言中文也配置一下。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install language-pack-zh-hans -y</span><br><span class="line"><span class="built_in">sudo</span> locale-gen zh_CN.UTF-8</span><br><span class="line"><span class="built_in">sudo</span> update-locale LANG=zh_CN.UTF-8 LANGUAGE=zh_CN:zh LC_ALL=zh_CN.UTF-8</span><br></pre></td></tr></table></figure><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 配置代理</span></span><br><span class="line"><span class="built_in">export</span> http_proxy=http://&lt;宿主机局域网ip&gt;:&lt;clash运行端口&gt;</span><br><span class="line"><span class="built_in">export</span> https_proxy=http://&lt;宿主机局域网ip&gt;:&lt;clash运行端口&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改系统语言为中文</span></span><br><span class="line"><span class="built_in">export</span> LANG=zh_CN.UTF-8</span><br><span class="line"><span class="built_in">export</span> LANGUAGE=zh_CN:zh</span><br><span class="line"><span class="built_in">export</span> LC_ALL=zh_CN.UTF-8</span><br></pre></td></tr></table></figure><p>使用<code>source .profile</code>使修改内容生效。</p><p>使用下述命令下载on-my-zsh：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh -c <span class="string">&quot;<span class="subst">$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)</span>&quot;</span></span><br></pre></td></tr></table></figure><p>接下来下载主题和插件：</p><p>在这之前先更新一下你的git版本</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt install git -y</span><br><span class="line"><span class="built_in">sudo</span> apt upgrade git -y</span><br></pre></td></tr></table></figure><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># powerlevel10k主题</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/romkatv/powerlevel10k.git <span class="variable">$ZSH_CUSTOM</span>/themes/powerlevel10k</span><br><span class="line"><span class="comment"># zsh-autosuggestions自动提示插件</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/zsh-users/zsh-autosuggestions <span class="variable">$&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;</span>/plugins/zsh-autosuggestions</span><br><span class="line"><span class="comment"># zsh-syntax-highlighting语法高亮插件</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/zsh-users/zsh-syntax-highlighting.git <span class="variable">$&#123;ZSH_CUSTOM:-~/.oh-my-zsh/custom&#125;</span>/plugins/zsh-syntax-highlighting</span><br></pre></td></tr></table></figure><p>编辑<code>~/.zshrc</code>文件，启用你的插件和主题，修改一下内容：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 修改主题</span></span><br><span class="line">ZSH_THEME=<span class="string">&quot;powerlevel10k/powerlevel10k&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 启用插件</span></span><br><span class="line">plugins=(</span><br><span class="line">  git</span><br><span class="line">  zsh-autosuggestions</span><br><span class="line">  zsh-syntax-highlighting</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>使用<code>source ~/.zshrc</code>使修改生效。</p><p>接下来你会进入p10k主题的引导设置界面，按照自己喜欢的设置即可，如果没有进入，可以通过以下命令进入：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">p10k configure</span><br></pre></td></tr></table></figure><p>于是，你就得到一个好看的shell了！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;如何初始化一个multipass虚拟机？&quot;&gt;&lt;a href=&quot;#如何初始化一个multipass虚拟机？&quot; class=&quot;headerlink&quot; title=&quot;如何初始化一个multipass虚拟机？&quot;&gt;&lt;/a&gt;如何初始化一个multipass虚拟机？&lt;/h1&gt;&lt;</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>1.网站介绍</title>
    <link href="https://kiritosuki.top/2025/1.%E7%BD%91%E7%AB%99%E4%BB%8B%E7%BB%8D/"/>
    <id>https://kiritosuki.top/2025/1.%E7%BD%91%E7%AB%99%E4%BB%8B%E7%BB%8D/</id>
    <published>2025-08-25T02:17:00.000Z</published>
    <updated>2025-08-25T02:56:26.186Z</updated>
    
    <content type="html"><![CDATA[<h1 id="welcome-to-kirito-の-site！"><a href="#welcome-to-kirito-の-site！" class="headerlink" title="welcome to kirito の site！"></a>welcome to kirito の site！</h1><p>你好！👋</p><p>我非常高兴欢迎你来到我的个人空间，在这里我将分享我整理的笔记、经验和见解。无论你是同样领域的爱好者、寻求新想法的人，还是仅仅是来探索的朋友，这个博客将为你提供有价值的资源和思考。</p><hr><h2 id="为什么要写这个博客？"><a href="#为什么要写这个博客？" class="headerlink" title="为什么要写这个博客？"></a>为什么要写这个博客？</h2><p>在这个信息爆炸的时代，信息过载已经成为一个现实的挑战。我的目标很简单：创建一个空间，将复杂的概念拆解成易懂的知识，分享个人经验，释放知识的流动。</p><p>在这里，你将找到涵盖多种主题的文章：</p><p><strong>技术笔记</strong> 🖥️<br> 从开发技巧、教程，到软件推荐。</p><p><strong>个人心得</strong> 💭<br> 关于生产力、创意和开发小窍门的感悟。</p><p><strong>学习与挑战</strong> 📚<br> 从失败和成功中总结的经验教训。</p><hr><h2 id="你能得到什么？"><a href="#你能得到什么？" class="headerlink" title="你能得到什么？"></a>你能得到什么？</h2><p>我将主要写下我所学到的内容——工具、方法、策略和经验，目的是帮助他人提高、学习，或者激发灵感。这不仅是我整理个人思考的方式，也是我希望能帮助到其他人的一种分享方式。</p><hr><h2 id="关于我"><a href="#关于我" class="headerlink" title="关于我"></a>关于我</h2><p>我是热心网友宁桑&#x2F;kirito，一个充满热情的geek。我花了无数小时学习技术、阅读、实验，有时也会失败。这个博客是我记录这个过程，并希望将其分享给你的方式。</p><hr><h2 id="敬请期待"><a href="#敬请期待" class="headerlink" title="敬请期待"></a>敬请期待</h2><p>我会定期发布文章，欢迎你关注、分享你的想法，并希望我们能一起学习和成长。</p><p>感谢你的来访！🙏</p><hr><h2 id="联系方式与社交"><a href="#联系方式与社交" class="headerlink" title="联系方式与社交"></a>联系方式与社交</h2><p>如果你想联系我，合作，或只是打个招呼，欢迎你在下方评论留言，或者访问“关于”界面以获取我的联系方式</p><hr><h2 id="让我们开始吧！🚀"><a href="#让我们开始吧！🚀" class="headerlink" title="让我们开始吧！🚀"></a>让我们开始吧！🚀</h2><p>在接下来的文章中，我会分享一些实用的技巧，特别是面向初学者的内容，欢迎和我一起探索开发的乐趣，望这个网站能成为你学习和成长的一部分，期待与你一起交流和进步！</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;welcome-to-kirito-の-site！&quot;&gt;&lt;a href=&quot;#welcome-to-kirito-の-site！&quot; class=&quot;headerlink&quot; title=&quot;welcome to kirito の site！&quot;&gt;&lt;/a&gt;welcome to </summary>
      
    
    
    
    
  </entry>
  
</feed>
