<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<atom:link href="https://taitres.com//feed" rel="self" type="application/rss+xml"/>
<title>一打木</title>
<link>https://taitres.com/</link>
<description>分支不限界</description>
<language>zh-CN</language>
<copyright>© Dailin Liu </copyright>
<pubDate>Sun, 16 Aug 2026 18:54:35 GMT</pubDate>
<generator>Mix Space CMS (https://github.com/mx-space)</generator>
<docs>https://mx-space.js.org</docs>
<image>
    <url>https://avatars.githubusercontent.com/u/54065110?v=4</url>
    <title>一打木</title>
    <link>https://taitres.com/</link>
</image>
<item>
    <title>wigolo 使用教程：给 AI Agent 一个本地优先的 Web 层</title>
    <link>https://taitres.com/posts/default/wigolo-AI-Agent-Web</link>
    <pubDate>Sat, 15 Aug 2026 09:03:46 GMT</pubDate>
    <description>wigolo 使用教程：给 AI Agent 一个本地优先的 Web 层

wigolo 是一个面向</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/posts/default/wigolo-AI-Agent-Web'>https://taitres.com/posts/default/wigolo-AI-Agent-Web</a></blockquote>
          <h1>wigolo 使用教程：给 AI Agent 一个本地优先的 Web 层</h1>
<p>wigolo 是一个面向 AI Agent 的本地优先 Web 智能层，提供 <strong>搜索、抓取、爬取、提取、缓存、相似查找、研究</strong> 和自主采集循环等能力。它通过 MCP 协议与 Claude Code、Cursor、Codex、Gemini CLI、OpenCode、VS Code、Windsurf、Zed、Antigravity 等编码 Agent 协作，也支持 LangChain、CrewAI、LlamaIndex、Vercel AI SDK、n8n 等框架，以及任何 MCP 客户端和纯 REST 调用。</p>
<p>核心卖点很直接：<strong>不需要 API key，不依赖云服务，没有按量计费</strong>。所有数据默认留在 <code>~/.wigolo/</code> 目录下。</p>
<h2>安装前提</h2>
<ul>
<li><strong>Node.js ≥ 20</strong>（建议使用 LTS 版本 20、22 或 24）</li>
<li><strong>约 1.5 GB 可用磁盘空间</strong>（macOS、Linux、Windows 均可）</li>
<li>网络连接用于首次下载浏览器引擎和本地模型</li>
</ul>
<h2>安装步骤</h2>
<p>安装通过 npx 完成，核心命令是 <code>init</code>：</p>
<pre><code class="language-bash">npx wigolo init                              # 基础安装，任何系统通用
npx wigolo init --agents=claude-code,cursor  # 安装并同时配置日常使用的 Agent</code></pre><p><code>init</code> 会下载浏览器引擎和本地模型，运行健康检查，并逐项报告每个组件的状态。默认是非交互式的，适合在脚本和 CI 中安全执行。</p>
<p><code>--agents</code> 参数支持逗号分隔的多个 Agent 名称：<code>claude-code</code>、<code>cursor</code>、<code>codex</code>、<code>gemini-cli</code>、<code>opencode</code>、<code>vscode</code>、<code>windsurf</code>、<code>zed</code>、<code>antigravity</code>。wigolo 会为这些 Agent 写入 MCP 配置和必要的指令文件。</p>
<p>其他 MCP 客户端或自托管 Agent 需要在自己的 MCP 配置中注册 <code>npx -y wigolo</code>。官方文档提供了每种客户端的精确配置块，以及 Docker、Homebrew 和单文件二进制等安装渠道。</p>
<p>两个有用的安装选项：</p>
<ul>
<li><code>--interactive</code>：纯文本交互式安装流程</li>
<li><code>--wizard</code>：完整的终端 TUI 界面</li>
<li><code>--no-warmup</code>：延迟下载模型和浏览器，等到首次使用时再加载</li>
</ul>
<p>即使某个组件下载失败，<code>init</code> 也会正常完成退出（退出码 0），并在报告中指出哪些组件未就绪及修复方法。核心的搜索、抓取、爬取、提取和缓存功能在无模型、无浏览器的情况下也能工作。</p>
<h2>启动与健康检查</h2>
<p>安装完成后，随时检查系统状态：</p>
<pre><code class="language-bash">npx wigolo doctor</code></pre><p><code>doctor</code> 会指出任何损坏的组件，并给出确切的修复命令或环境变量；<code>wigolo doctor --fix</code> 可以自动修复常见问题；<code>wigolo verify</code> 则对每个组件做健康检查。</p>
<p>彻底卸载：</p>
<pre><code class="language-bash">npx wigolo config --uninstall --yes</code></pre><h2>配置</h2>
<p>默认安装即可使用。三个配置项可以显著提升输出质量：</p>
<p><strong>1. 配置 LLM 提供方（最大杠杆）</strong></p>
<p>搜索、抓取、爬取、提取、缓存和相似查找完全不需要 API key。但 <code>research</code>、<code>agent</code> 和 <code>search format=answer</code> 这三个功能需要 LLM 来生成带引用的综合回答。没有 LLM 时，它们会返回原始简报和证据，由宿主 Agent 自行整理。</p>
<p>推荐使用免费的 Gemini key：</p>
<pre><code class="language-bash">export WIGOLO_LLM_PROVIDER=gemini
export GEMINI_API_KEY=&lt;free-key&gt;      # 在 aistudio.google.com/apikey 获取，免费额度足够</code></pre><p>也支持 <code>anthropic</code>、<code>openai</code>、<code>groq</code>，或者完全本地无 key 的方案：<code>WIGOLO_LLM_PROVIDER=ollama</code>（或任何 OpenAI 兼容的 URL）。</p>
<p><strong>2. 扩大检索漏斗</strong></p>
<pre><code class="language-bash">export WIGOLO_SEARCH=hybrid                         # 核心引擎 + 聚合器回退
export WIGOLO_GITHUB_TOKEN=...                      # GitHub 代码搜索从 10 提升到 30 req/min</code></pre><p><strong>3. 提升抓取成功率</strong></p>
<pre><code class="language-bash">export WIGOLO_TLS_TIER=auto                         # 按域名学习抓取加固策略
export WIGOLO_EAGER_WARMUP=1                        # 提前支付约 1 秒的模型加载时间</code></pre><h2>典型命令示例</h2>
<p>所有工具都可以通过 CLI 直接运行：</p>
<pre><code class="language-bash">wigolo search "local-first software" --json</code></pre><p>启动 REST API 服务：</p>
<pre><code class="language-bash">wigolo serve                          # 默认监听 127.0.0.1:3333</code></pre><p>通过 REST 调用搜索：</p>
<pre><code class="language-bash">curl -sX POST http://127.0.0.1:3333/v1/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"local-first software","max_results":5}'</code></pre><p><code>POST /v1/{tool}</code> 覆盖全部十个工具，<code>GET /openapi.json</code> 提供 OpenAPI 3.1 契约，<code>/mcp</code> 和 <code>/sse</code> 端点支持远程 MCP 客户端。绑定到非 loopback 地址时必须设置 bearer token，服务默认 fail-closed。</p>
<p>交互式 shell 模式（支持 NDJSON 管道）：</p>
<pre><code class="language-bash">wigolo shell</code></pre><h2>十个核心工具</h2>
<table>
<thead>
<tr>
<th>工具</th>
<th>功能</th>
</tr>
</thead>
<tbody><tr>
<td><code>search</code></td>
<td>多引擎搜索（18 个直接适配器），带排名融合、ML 重排序和可解释的逐结果评分。支持查询数组并行扩展、域名/时间范围限定、精确短语匹配、图片结果</td>
</tr>
<tr>
<td><code>fetch</code></td>
<td>加载单个 URL，通过分层路由器自动升级（从普通 HTTP 到无头浏览器引擎），处理反爬挑战和 SPA 外壳。输出干净的 markdown + 元数据 + 链接，支持 PDF、单标题 section、认证会话和页面操作（点击/输入/滚动/截图）</td>
</tr>
<tr>
<td><code>crawl</code></td>
<td>多页面爬取，支持 BFS、DFS、sitemap 或仅地图模式。按域名限速、尊重 robots.txt、去重样板内容</td>
</tr>
<tr>
<td><code>extract</code></td>
<td>从页面提取结构化数据：表格、元数据、JSON-LD、品牌信息、命名 schema（Article / Recipe / Product 等）或自定义 JSON Schema</td>
</tr>
<tr>
<td><code>cache</code></td>
<td>查询所有已见内容，支持关键词或混合语义搜索，还有统计、清除和变更检测</td>
</tr>
<tr>
<td><code>find_similar</code></td>
<td>通过关键词 + 语义 + 实时网页三方融合，查找与某 URL 或概念相似的页面</td>
</tr>
<tr>
<td><code>research</code></td>
<td>分解问题 → 并行子查询 → 抓取来源 → 生成带引用的报告（或结构化简报）</td>
</tr>
<tr>
<td><code>agent</code></td>
<td>自主采集循环：规划 → 搜索 → 抓取 → 提取 → 综合，带步骤日志、时间预算和可选输出 schema</td>
</tr>
<tr>
<td><code>diff</code> + <code>watch</code></td>
<td>查看页面自上次访问以来的变化；按需重新检查并通过 webhook 推送变更</td>
</tr>
</tbody></table>
<h2>搜索结果的结构</h2>
<p>每次搜索返回的结果都带有可操作的证据信息，包括原文摘录（精确到字节偏移）、可引用的 citation ID 和可检查的评分：</p>
<pre><code class="language-jsonc">{
  "results": [{
    "title": "Logical replication - PostgreSQL docs",
    "url": "https://www.postgresql.org/docs/current/logical-replication.html",
    "excerpt": "Logical replication is a method of replicating data objects…",
    "citation_id": "src-1",
    "source_span": { "start": 1042, "end": 1305 },          // 字节级精确来源
    "evidence_score": { "final": 0.86, "semantic": 0.91, "lexical": 0.78, "engine_consensus": 3 }
  }],
  "citations": [{ "id": "src-1", "url": "…" }],
  "freshness_signal": { "published": "2026-05-12", "confidence": "high" }
}</code></pre><p>弱结果会被 wigolo 自己的评分器标记为 junk，失败的引擎和过期的缓存也会在输出中标注。</p>
<h2>SDK 与框架集成</h2>
<p><strong>TypeScript</strong>（零依赖，支持 Node / Bun / Deno / edge）：</p>
<pre><code class="language-bash">npm install wigolo-sdk</code></pre><pre><code class="language-ts">import { createLocalClient } from 'wigolo-sdk/local';

const { client, close } = await createLocalClient();   // 复用运行中的守护进程，或启动一个
const res = await client.search({ query: 'local-first web search', max_results: 5 });
console.log(res.results.map((r) =&gt; r.title));
await close();                                          // 仅当本次调用启动了守护进程时才停止它</code></pre><p><strong>Python</strong>（仅标准库，支持同步和异步）：</p>
<pre><code class="language-bash">pip install wigolo</code></pre><pre><code class="language-python">from wigolo import local_client

with local_client() as client:                          # 复用健康的守护进程，或启动一个
    res = client.search(query="local-first web search", max_results=5)
    for r in res["results"]:
        print(r["title"], r["url"])</code></pre><p><strong>框架集成包</strong>：</p>
<ul>
<li><code>wigolo-langchain</code>：每个工具作为 <code>BaseTool</code>，另有 <code>BaseRetriever</code> 用于 RAG</li>
<li><code>wigolo-crewai</code>：<code>wigolo_tools()</code> 直接交给任意 crew</li>
<li><code>wigolo-llamaindex</code>：<code>BaseReader</code> 加载抓取/爬取/搜索到的页面作为文档</li>
<li><code>wigolo-vercel-ai-sdk</code>：为 <code>generateText</code> / <code>streamText</code> 提供工具工厂</li>
</ul>
<p><strong>Docker</strong>：</p>
<pre><code class="language-bash"># stdio MCP 模式

docker run -i --rm -v wigolo-data:/data ghcr.io/knockoutez/wigolo

# HTTP 服务器模式（远程/多客户端）

docker run -p 3333:3333 -v wigolo-data:/data \
  -e WIGOLO_API_TOKEN=a-long-random-secret \
  ghcr.io/knockoutez/wigolo serve --host 0.0.0.0</code></pre><p>slim 镜像按需加载模型到卷中；<code>:full</code> 标签预装了浏览器引擎。Docker Hub 上也有 <code>towhid69420/wigolo</code>。</p>
<h2>注意事项</h2>
<ol>
<li><p><strong>磁盘占用</strong>：1.5 GB 是本地模型和浏览器引擎的体积，这是本地运行的成本。安装后每次查询都免费使用这些资源。</p>
</li>
<li><p><strong>网络环境</strong>：如果下载慢或失败，重新运行 <code>wigolo warmup --all</code>（或 <code>--browser</code> / <code>--embeddings</code> / <code>--reranker</code>），支持断点续传和重试。</p>
</li>
<li><p><strong>Linux 浏览器启动问题</strong>：<code>wigolo warmup --browser</code> 会自动安装所需的系统库，或打印出确切命令。</p>
</li>
<li><p><strong>代理环境</strong>：设置 <code>USE_PROXY=true</code> 和 <code>PROXY_URL</code>；如果代理做 TLS 检查，还需要 <code>NODE_EXTRA_CA_CERTS</code>。</p>
</li>
<li><p><strong>自托管注意</strong>：部分有反爬保护的网站会评估 IP 信誉，数据中心 IP 可能无法通过家庭网络能通过的验证。wigolo 会标注这类失败，官方自托管指南介绍了可选的代理方案。</p>
</li>
<li><p><strong>许可协议</strong>：wigolo 使用 AGPL-3.0 许可。在公司内部使用没有义务；只有当你<strong>修改了 wigolo 并作为网络服务运行</strong>时，才需要以相同许可发布修改后的源码。</p>
</li>
<li><p><strong>公共搜索引擎的稳定性</strong>：wigolo 通过 18 个引擎的排名融合来降低单一引擎失效的影响，可选的聚合器回退和本地缓存进一步保证了可用性。后端降级会在输出中明确报告。</p>
</li>
<li><p><strong>爬取礼仪</strong>：默认尊重 robots.txt、按域名限速，面向单机单 Agent 的研究级流量，刻意保持在礼貌的一端。</p>
</li>
</ol>
<p>wigolo 目前处于 public beta，官方称有 7,600 项测试保障稳定性。如果你遇到问题，<code>wigolo doctor</code> 是最快的诊断入口。</p>

          <p style='text-align: right'>
          <a href='https://taitres.com/posts/default/wigolo-AI-Agent-Web#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">170459141716844544</guid>
  <category>post</category>
<category>Default</category>
 </item>
  <item>
    <title>Vue入门</title>
    <link>https://taitres.com/posts/vue/vueinit</link>
    <pubDate>Fri, 13 Feb 2026 03:00:00 GMT</pubDate>
    <description>Vue入门

1. Vue 是什么？为什么后端也要学？

Vue 是一个渐进式前端框架，专门用来构建</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/posts/vue/vueinit'>https://taitres.com/posts/vue/vueinit</a></blockquote>
          <h1>Vue入门</h1>
<h3>1. Vue 是什么？为什么后端也要学？</h3>
<ul>
<li>Vue 是一个<strong>渐进式前端框架</strong>，专门用来构建用户界面（View层）。</li>
<li>核心特点：<strong>声明式渲染</strong> + <strong>组件化</strong> + <strong>响应式系统</strong>。</li>
<li>和你熟悉的后端不同，前端关注“数据变化 → 页面自动更新”，Vue 把这部分做得非常简单。</li>
<li>学习Vue后，你能快速做出交互丰富的管理后台、单页应用（SPA）。</li>
</ul>
<p>推荐直接看官方文档（中文极好）：
<a href="https://cn.vuejs.org/">Vue 3 官方文档</a></p>
<h3>2. 快速起步（10分钟搭建第一个项目）</h3>
<p>使用 Vite（官方推荐，最快最现代的构建工具）：</p>
<pre><code class="language-bash"># 1. 创建项目
npm create vite@latest my-vue-app -- --template vue

# 2. 进入目录并安装依赖
cd my-vue-app
npm install

# 3. 启动开发服务器
npm run dev</code></pre><p>打开 <a href="http://localhost:5173%EF%BC%8C%E5%B0%B1%E8%83%BD%E7%9C%8B%E5%88%B0%E2%80%9CHello">http://localhost:5173，就能看到“Hello</a> Vue”页面。</p>
<p>项目结构重点看：</p>
<ul>
<li><code>src/App.vue</code>：根组件（单文件组件，.vue文件集成了HTML、JS、CSS）</li>
<li><code>src/main.js</code>：入口文件，创建Vue应用实例</li>
</ul>
<h3>3. 单文件组件（.vue文件）结构</h3>
<p>每个.vue文件分为三部分：</p>
<pre><code class="language-vue">&lt;template&gt;
  
  <div>
    <h1>{{ title }}</h1>
    &lt;button @click="count++"&gt;计数：{{ count }}&lt;/button&gt;
  </div>
&lt;/template&gt;

&lt;script setup&gt;
// 这里是脚本，使用 Composition API（推荐）
import { ref } from 'vue'

const title = ref('Hello Vue')
const count = ref(0)
&lt;/script&gt;

&lt;style scoped&gt;
/* CSS，scoped表示只作用于当前组件 */
h1 { color: blue; }
&lt;/style&gt;</code></pre><p>这就是Vue最常见的写法，后面所有例子都基于<code>&lt;script setup&gt;</code>。</p>
<h3>4. 核心概念</h3>
<h4>4.1 响应式数据（ref / reactive）</h4>
<p>Vue 最牛的地方：数据变了，页面自动更新。</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { ref, reactive } from 'vue'

// 基本类型用 ref
const count = ref(0)

// 对象/数组用 reactive（更方便）
const state = reactive({
  name: '张三',
  list: [1, 2, 3]
})
&lt;/script&gt;

&lt;template&gt;
  <p>计数：{{ count }}</p>
  <p>姓名：{{ state.name }}</p>
  &lt;button @click="count++"&gt;+1&lt;/button&gt;
  &lt;button @click="state.list.push(4)"&gt;添加&lt;/button&gt;
&lt;/template&gt;</code></pre><h4>4.2 模板语法</h4>
<ul>
<li>插值：<code>{{ expression }}</code></li>
<li>指令（以v-开头）：<ul>
<li><code>v-bind</code>（缩写 <code>:</code>）绑定属性：<code>&lt;img :src=&quot;imgUrl&quot;&gt;</code></li>
<li><code>v-on</code>（缩写 <code>@</code>）绑定事件：<code>&lt;button @click=&quot;handleClick&quot;&gt;</code></li>
<li><code>v-model</code> 双向绑定表单：<code>&lt;input v-model=&quot;searchText&quot;&gt;</code></li>
<li><code>v-if / v-else</code> 条件渲染</li>
<li><code>v-for</code> 列表渲染（必须加 <code>:key</code>）</li>
</ul>
</li>
</ul>
<pre><code class="language-vue">&lt;template&gt;
  <div>欢迎回来！</div>
  <div>请登录</div>

  <ul>
    <li>{{ item.name }}</li>
  </ul>
&lt;/template&gt;</code></pre><h4>4.3 计算属性（computed）</h4>
<p>当你需要基于响应式数据做计算时用，避免在模板里写复杂逻辑。</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { ref, computed } from 'vue'

const list = ref([1, 2, 3, 4])
const evenList = computed(() =&gt; list.value.filter(n =&gt; n % 2 === 0))
&lt;/script&gt;

&lt;template&gt;
  <p>偶数：{{ evenList }}</p>
&lt;/template&gt;</code></pre><h4>4.4 事件处理</h4>
<pre><code class="language-vue">&lt;script setup&gt;
const handleClick = () =&gt; {
  alert('点击了！')
}
&lt;/script&gt;

&lt;template&gt;
  &lt;button @click="handleClick"&gt;点我&lt;/button&gt;
  
  &lt;button @click="add($event, 5)"&gt;加5&lt;/button&gt;
&lt;/template&gt;</code></pre><h4>4.5 生命周期</h4>
<p>组件的生命周期大致流程：</p>
<pre><code class="language-">创建阶段 → 挂载阶段 → 更新阶段 → 销毁阶段</code></pre><ul>
<li><strong>创建阶段</strong>：实例被创建，data/props 初始化（适合初始化数据）</li>
<li><strong>挂载阶段</strong>：模板编译、DOM 插入（适合发请求、操作 DOM）</li>
<li><strong>更新阶段</strong>：数据变化触发（适合处理副作用）</li>
<li><strong>销毁阶段</strong>：组件移除（适合清理定时器、事件监听）</li>
</ul>
<h5>4.5.2. 完整钩子对比表（Vue 3）</h5>
<table>
<thead>
<tr>
<th>阶段</th>
<th>Options API（旧写法）</th>
<th>Composition API（新写法，&lt;script setup&gt;）</th>
<th>常见用途</th>
</tr>
</thead>
<tbody><tr>
<td>创建前</td>
<td>beforeCreate</td>
<td>setup() 开始</td>
<td>几乎不用（实例还没创建）</td>
</tr>
<tr>
<td>创建后</td>
<td>created</td>
<td>setup() 结束</td>
<td>初始化数据、发初始请求（不能操作 DOM）</td>
</tr>
<tr>
<td>挂载前</td>
<td>beforeMount</td>
<td>onBeforeMount</td>
<td>模板已编译，但还没插入 DOM</td>
</tr>
<tr>
<td>挂载后</td>
<td>mounted</td>
<td>onMounted</td>
<td><strong>最常用</strong>：发请求、操作 DOM、启动定时器</td>
</tr>
<tr>
<td>更新前</td>
<td>beforeUpdate</td>
<td>onBeforeUpdate</td>
<td>数据变了，即将重新渲染（可访问旧 DOM）</td>
</tr>
<tr>
<td>更新后</td>
<td>updated</td>
<td>onUpdated</td>
<td>渲染完成（慎用，容易无限循环）</td>
</tr>
<tr>
<td>销毁前</td>
<td>beforeUnmount</td>
<td>onBeforeUnmount</td>
<td>即将移除，清理资源</td>
</tr>
<tr>
<td>销毁后</td>
<td>unmounted</td>
<td>onUnmounted</td>
<td><strong>常用</strong>：清理定时器、事件监听、订阅</td>
</tr>
<tr>
<td>错误捕获</td>
<td>errorCaptured</td>
<td>onErrorCaptured</td>
<td>子组件报错时捕获</td>
</tr>
<tr>
<td>keep-alive 激活</td>
<td>activated</td>
<td>onActivated</td>
<td>缓存组件被激活时</td>
</tr>
<tr>
<td>keep-alive 失活</td>
<td>deactivated</td>
<td>onDeactivated</td>
<td>缓存组件被停用时</td>
</tr>
</tbody></table>
<h5>4.5.3. 代码示例对比</h5>
<p>Options API 写法（传统对象式，很多老教程用这个）</p>
<pre><code class="language-vue">&lt;script&gt;
export default {
  data() {
    return { count: 0 }
  },
  beforeCreate() {
    console.log('beforeCreate')
  },
  created() {
    console.log('created：数据已初始化，可以发请求')
    // 常见：this.axios.get(...)
  },
  beforeMount() {
    console.log('beforeMount')
  },
  mounted() {
    console.log('mounted：DOM 已插入，可以操作 DOM')
    // 常见：获取 DOM 元素、第三方库初始化
  },
  beforeUpdate() {
    console.log('beforeUpdate')
  },
  updated() {
    console.log('updated')
  },
  beforeUnmount() {
    console.log('beforeUnmount')
  },
  unmounted() {
    console.log('unmounted：彻底清理')
    // 常见：clearInterval、removeEventListener
  }
}
&lt;/script&gt;</code></pre><p>Composition API 写法（Vue 3 推荐，&lt;script setup&gt; 最简洁）</p>
<pre><code class="language-vue">&lt;script setup&gt;
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted
} from 'vue'

onBeforeMount(() =&gt; console.log('onBeforeMount'))
onMounted(() =&gt; {
  console.log('onMounted：最常用，发请求、操作 DOM')
  // 这里发 axios 请求
})
onBeforeUpdate(() =&gt; console.log('onBeforeUpdate'))
onUpdated(() =&gt; console.log('onUpdated（慎用）'))
onBeforeUnmount(() =&gt; console.log('onBeforeUnmount'))
onUnmounted(() =&gt; {
  console.log('onUnmounted：清理资源')
  // 清理定时器、订阅等
})
&lt;/script&gt;</code></pre><h3>5. 组件化（Vue的灵魂）</h3>
<ul>
<li>所有页面都是组件嵌套而成。</li>
<li>创建组件：新建一个.vue文件即可。</li>
</ul>
<p><strong>父组件 → 子组件传值（props）</strong></p>
<p>子组件 <code>Child.vue</code>：</p>
<pre><code class="language-vue">&lt;script setup&gt;
defineProps({
  msg: String,
  count: Number
})
&lt;/script&gt;

&lt;template&gt;
  <p>收到：{{ msg }}，数字：{{ count }}</p>
&lt;/template&gt;</code></pre><p>父组件使用：</p>
<pre><code class="language-vue">&lt;template&gt;
  &lt;Child msg="hello" :count="123" /&gt;
&lt;/template&gt;</code></pre><p><strong>子组件 → 父组件通信（emit）</strong></p>
<p>子组件：</p>
<pre><code class="language-vue">&lt;script setup&gt;
const emit = defineEmits(['update'])
const send = () =&gt; emit('update', '新数据')
&lt;/script&gt;

&lt;template&gt;
  &lt;button @click="send"&gt;发送&lt;/button&gt;
&lt;/template&gt;</code></pre><p>父组件：</p>
<pre><code class="language-vue">&lt;Child @update="handleUpdate" /&gt;</code></pre><h3>6. 常用进阶</h3>
<h4>6.1 Vue Router（路由）</h4>
<p>安装：<code>npm install vue-router@4</code></p>
<p>基本配置（src/router/index.js）：</p>
<pre><code class="language-js">import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About }
  ]
})

export default router</code></pre><p>main.js 中使用：</p>
<pre><code class="language-js">import router from './router'
app.use(router)</code></pre><p>模板中使用：</p>
<pre><code class="language-vue">&lt;router-link to="/about"&gt;去关于页&lt;/router-link&gt;
&lt;router-view /&gt;  </code></pre><h4>6.2 Pinia（状态管理，Vue官方推荐，替代Vuex）</h4>
<p>安装：<code>npm install pinia</code></p>
<p>创建 store（src/stores/counter.js）：</p>
<pre><code class="language-js">import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () =&gt; ({ count: 0 }),
  getters: {
    double: state =&gt; state.count * 2
  },
  actions: {
    increment() { this.count++ }
  }
})</code></pre><p>任意组件中使用：</p>
<pre><code class="language-vue">&lt;script setup&gt;
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()

// 直接用
counter.increment()
&lt;/script&gt;

&lt;template&gt;
  <p>计数：{{ counter.count }}，双倍：{{ counter.double }}</p>
&lt;/template&gt;</code></pre><p>推荐UI组件库（省大量CSS时间）：</p>
<ul>
<li><a href="https://element-plus.org/">Element Plus</a>（最流行）</li>
<li><a href="https://www.naiveui.com/">Naive UI</a></li>
</ul>

          <p style='text-align: right'>
          <a href='https://taitres.com/posts/vue/vueinit#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164325116924674077</guid>
  <category>post</category>
<category>Vue</category>
 </item>
  <item>
    <title>一些破解工具</title>
    <link>https://taitres.com/posts/tool/pojie</link>
    <pubDate>Fri, 10 Oct 2025 08:27:39 GMT</pubDate>
    <description>idea

https://blog.idejihuo.com/

navicat 12

http</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/posts/tool/pojie'>https://taitres.com/posts/tool/pojie</a></blockquote>
          <h2>idea</h2>
<p><a href="https://blog.idejihuo.com/">https://blog.idejihuo.com/</a></p>
<h2>navicat 12</h2>
<p><a href="https://www.jb51.net/article/202450.htm">https://www.jb51.net/article/202450.htm</a></p>

          <p style='text-align: right'>
          <a href='https://taitres.com/posts/tool/pojie#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164325116924674076</guid>
  <category>post</category>
<category>工具</category>
 </item>
  <item>
    <title>写在毕业后</title>
    <link>https://taitres.com/notes/4</link>
    <pubDate>Fri, 29 Aug 2025 17:14:11 GMT</pubDate>
    <description>工作已经一个半月了，本来早想写点什么去总结秋招，去缅怀这段记忆，但是不得闲。闲不是像要毕业时有大把时</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/notes/4'>https://taitres.com/notes/4</a></blockquote>
          <p>工作已经一个半月了，本来早想写点什么去总结秋招，去缅怀这段记忆，但是不得闲。闲不是像要毕业时有大把时间挥霍，那会让我觉得玩排满了档期，不能从玩里偷闲，闲就像现在这个时刻，我刚写完一个工作的文档，我突然就感觉闲下来了，没有人玩游戏，没有人发消息，也没人，只有我自己。</p>
<p>秋招是痛苦的，没啥好总结的，拼实力也拼运。结果在现在看来是不那么好的，学了大半年，自认八股娴熟，最后去了一家国企，没问任何的技术知识。我常常想那我之前还学干嘛，我以后是不是会废了，现在说的话对于技术而言，应该是没啥上升空间了，虽然理智告诉我技术不值钱，但作为一个学习了二十多年的学生来说，在学校说知识，在社会说技术的东西感性上是底气和资本，不免有些泄气。</p>
<p>每当发工资的时候都很后悔，我不是一个节约的人，但过了学生这段小资阶段，我会觉得30的外卖有些贵了，聚餐频繁有些奢侈了，有落差有压力，更有点想家。</p>
<p>刚租房的时候，我一个人把屋子收拾好，坐在沙发上，在B站看到GAI哥的故湘·风，我不是湖南的，不知道是不是人寂寞无助的时候就会思乡，唱的每一句都扎在我心里，会想亲人，会想朋友，会想那儿时的味道，会想那山和水，高中上学时的公交，和窗外的城。会想一切北京不会有的，真的忍不住落泪，哭完还得点个乡村基。</p>
<p>工作了，开始干事了就没那么多想法了，刚开始熟悉了会儿系统，改了改代码，我还是很喜欢改代码的，那一周很快乐很规律。上班半个多月，事情确实忙不过来了，我开始出差。我明明还什么都不知道呀，没办法，做中学，社会不会给人时间准备好，干活不是考试不是学了才考，分数没有八九十，只有干完和没干完。</p>
<p>第一次出差很新奇，虽然工作前不知道原来干的是外包的活，本着中国人骨子里的既来之则安之，我很想把事干好。与出差单位的交流是困难的，我不懂很多东西，我得猜，我猜不出来了，我得问，问了没人回，我得等，一边等一边自己琢磨，每个人都有自己的事。不同的城市确实有不一样的风情，但下班后的我没心力去逛太多。</p>
<p>工作中最常见的就是昨天的计划，早上一上班就被打乱了，意想不到的事永远意想不到。下周出差得去四川了，算是一个小的回家，至少能找点小馆子一个人吃饭。</p>
<p>生活往前走，也许我真的做了很错的决定，但就这样吧，我以前最擅长的就是乐观，但离开父母太久，已经快忘了。没有过不去的坎，但有失去的心气。</p>

          <p style='text-align: right'>
          <a href='https://taitres.com/notes/4#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164325116928868353</guid>
  <category>note</category>
false
 </item>
  <item>
    <title>Linux命令</title>
    <link>https://taitres.com/posts/computer/linuxcommand</link>
    <pubDate>Thu, 25 Jul 2024 12:45:45 GMT</pubDate>
    <description>系统命令

Linux怎么看进程号/进程占用的 CPU?

ps 和 top 命令都能查看进程号和进</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/posts/computer/linuxcommand'>https://taitres.com/posts/computer/linuxcommand</a></blockquote>
          <h1>系统命令</h1>
<h2>Linux怎么看进程号/进程占用的 CPU?</h2>
<p>ps 和 top 命令都能查看进程号和进程占用的的 CPU。</p>
<p>TOP命令：进程按照CPU使用率的降序排列，最占用CPU的进程会显示在列表的顶部。</p>
<p></p>
<p></p>
<h2>Linux怎么看进程中的线程？</h2>
<p>先通过 ps 命令找到进程的 pid 号之后，通过 ps -T -p &lt;pid&gt;，就可以显示进程中的线程了。或者 top -H -p &lt;pid&gt; 命令也可以。</p>
<p></p>
<p></p>
<h2>Linux怎么看端口被哪个进程占用了？</h2>
<p>通过 netstat 或者 lsof 命令都可以知道端口被哪个进程占用了。</p>
<p></p>
<h2>怎么查看一个进程占用的端口号？</h2>
<p>通过 netstat 或者 lsof 命令输出的内容之后，通过 grep 指定的进程号，然后就可以过滤出进程占用的端口号。</p>
<p></p>
<h2>Linux怎么看tcp状态？</h2>
<p>netstat 或者 ss 命令可以查看到 tcp 连接的状态</p>
<p></p>
<p></p>
<h2>如何判断远端端口是否开启？</h2>
<p>可以用 nc 或者 telnet 来判断远程端的端口是否开启</p>
<p></p>
<h2>Linux查看TCP连接数</h2>
<pre><code class="language-bash">netstat -nat | grep ESTABLISHED | wc -l  </code></pre><ul>
<li>-nat：显示所有tcp连接状态，并以数字形式显示端口号和IP地址，而不进行主机名解析。 </li>
<li>grep ESTABLISHED：通过 grep 命令筛选出处于 ESTABLISHED 状态的连接。</li>
<li>wc -l：wc 命令用于统计输出的行数，-l 参数表示只统计行数。</li>
</ul>
<p>先通过 netstat -nat 命令显示 tcp 连接信息，然后用 grep 过滤出处于 ESTABLISHED 状态的 tcp 连接，最后用 wc -l 统计连接个数，就可以查看当前系统上处于 ESTABLISHED 状态的连接数了</p>
<p></p>
<h2>Linux top 命令有哪些信息？</h2>
<p>主要有系统的负载均衡情况、 CPU使用情况、内存使用情况、运行进程数量，还有进程列表，进程列表主要会显示当前运行的进程的详细信息，包括进程ID（PID）、CPU使用率、内存使用率、进程优先级等。</p>
<h2>CPU使用率达到100%呢？怎么排查？</h2>
<p>首先先通过 top 命令找到占用CPU最高的进程，然后通过 top -Hp (top -H -p)命令找到进程中占用CPU最高的线程，记录这个线程的 id 号，接着通过 jstack 打印这个线程的堆栈信息，通过这些信息定位到具体的代码位置去排查问题。</p>
<h2>Linux top结果CPU占用会超过100%吗？</h2>
<p>top命令显示的是所有 cpu 占用的总数，如果 cpu 是多核心的，那么是会观察到 cpu 显示超过 100%的，可以通过按键盘数字 1，来显示每个 cpu 的 cpu 占用率。</p>
<h2>Linux如何查看内存使用情况？</h2>
<p>可以用 free 命令来看</p>
<p></p>
<h2>Linux怎么查看磁盘剩余多少</h2>
<p>df -h 就能查看磁盘的空间大小了</p>
<p></p>
<h1>文件相关的命令</h1>
<h2>Linux查看文件的命令有哪些？</h2>
<p>查看文件内容常用的命令是： </p>
<ul>
<li>cat，可以显示文件的所有内容 </li>
<li>head，显示文件的开头部分，默认显示头部 10 行。</li>
<li>tail：显示文件的末尾部分，默认显示末尾 10 行。 </li>
<li>more：逐页显示文件内容，只能向前翻页，无法向后滚动 </li>
<li>less ：与 more 类似，提供更多功能，如向前/向后翻页、搜索等。</li>
</ul>
<h2>Linux查看文件大小命令</h2>
<p>可以用 ls -l 或者 du -h 命令查看文件的大小。</p>
<p>ls -l：显示文件的详细信息，包括文件大小（以字节为单位）。</p>
<p>du -h：显示目录或文件的大小，以人类可读的方式（例如 KB、MB）显示。</p>
<p></p>
<h2>Linux查询当前所在目录的语句</h2>
<p>pwd</p>
<h2>Linux创建文件夹和文件的语句是什么？</h2>
<p>创建文件夹用 mkdir 命令， 创建文件用 touch 命令</p>
<h2>Linux如何删除一个文件？</h2>
<p>可以使用 rm 命令，可以使用以下语句来删除一个文件：rm your_file_name</p>
<p><strong>如何删除一个目录（文件夹）？</strong></p>
<p>可以使用 <code>rm</code> 命令结合 <code>-r</code> 参数来递归地删除目录：rm -r directory_name</p>
<h2>Linux cp 命令怎么复制整个文件夹？</h2>
<p>使用 -r 选项来进行递归复制。</p>
<pre><code class="language-bash">cp -r /path/to/source /path/to/destination</code></pre><h2>Linux如何文件重命名</h2>
<p>可以使用 mv 命令。可以使用以下语句来重命名文件：mv old_file_name new_file_name</p>
<h2>Linux 文件夹中如何查看最近被修改的文件？</h2>
<p>可以用 ls 命令结合 -lt 参数，按照文件修改时间排序显示，这样最近修改的文件将会显示在最上面。</p>
<p>-l 是以列表方式显示文件和目录的详细信息，-t 是按照文件修改时间排序，最近修改的文件将会显示在最上面。</p>
<h2>Linux怎么修改文件的权限？</h2>
<p>chmod命令的基本语法：<code>chmod [选项] 权限模式 文件名</code></p>
<p>其中，选项可以是：</p>
<ul>
<li>-c：显示修改的详细信息。</li>
<li>-R：递归地修改目录及其子目录下的文件权限。</li>
</ul>
<p>权限模式可以使用数字或符号两种方式表示。 </p>
<ul>
<li>数字方式：每个权限用一个数字表示，分别对应读（r）、写（w）和执行（x）权限。数字1表示执行权限，数字2表示写权限，数字4表示读权限。将这三个数字相加，即可得到对应的权限模式。例如，权限模式为rwxr-xr--可以用数字表示为754。</li>
<li>符号方式：使用u（所有者）、g（所属组）和o（其他人）表示权限的对象，加上+、-、=表示添加、删除或设置权限。例如，将文件的所有者权限设置为读写，可以使用命令chmod u+rw 文件名。</li>
</ul>
<p>例：通过 chmod 命令来修改文件权限，如果这个文件需要执行权限，可以通过 chmod u+x 文件名命令来实现。</p>
<h2>Linux中如何查找一个文件</h2>
<p>find命令：<code>find /path/to/search -name &quot;filename&quot;</code></p>
<h2>Linux 怎么查看实时滚动日志？</h2>
<p>可以用 tail 命令，再加一个 -f 的参数，来查看实时滚动日志</p>
<h2>现在有一个txt文件，如何查看后三行</h2>
<p>tail -n 3 your_file.txt</p>
<h2>查找一个字符串是否在文件中</h2>
<p>可以使用grep命令来查找一个字符串是否在文件中出现。grep命令用于在文件中搜索指定的字符串模式，并输出匹配的行。命令的基本语法：<code>grep [选项] 字符串 文件名</code></p>
<p>选项可以是：</p>
<ul>
<li>-i：忽略大小写。</li>
<li>-r：递归地搜索指定目录及其子目录下的文件。 </li>
<li>-n：显示匹配行的行号。 </li>
<li>-l：仅显示包含匹配字符串的文件名。</li>
</ul>
<h3>Linux怎么查找一个文件里的某一个字符串的位置</h3>
<p><code>grep -n &quot;your_string&quot; your_file</code>，这将显示包含指定字符串的行数。</p>
<h3>在一个目录下寻找含有字符串“admin”的文件</h3>
<p><code>grep -r &quot;admin&quot; 目录名</code></p>
<h2>Linux查看文件行数命令</h2>
<p>wc -l your_file</p>
<h2>统计一个文件中某一个字段的次数</h2>
<p>可以通过 grep -o 过滤出字段之后，然后用 wc -l 统计出现的次数。<code>grep -o &#39;字段A&#39; filename | wc -l</code></p>
<p>-o 或 --only-matching : 只显示匹配PATTERN 部分。</p>

          <p style='text-align: right'>
          <a href='https://taitres.com/posts/computer/linuxcommand#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164325116924674073</guid>
  <category>post</category>
<category>计算机基础</category>
 </item>
  <item>
    <title>操作系统（五）网络 I/O</title>
    <link>https://taitres.com/posts/computer/systemio</link>
    <pubDate>Tue, 23 Jul 2024 11:22:07 GMT</pubDate>
    <description>Linux五种IO模型



阻塞I/O： 这是最常见的I/O模型。在此模式中，当应用程序执行I/O</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/posts/computer/systemio'>https://taitres.com/posts/computer/systemio</a></blockquote>
          <h2>Linux五种IO模型</h2>
<p></p>
<ul>
<li><strong>阻塞I/O</strong>： 这是最常见的I/O模型。在此模式中，当应用程序执行I/O操作时，如果数据还没有准备好，应用程序就会被阻塞（挂起），直到数据准备好为止。这期间，应用程序不能做其他事情。 </li>
<li><strong>非阻塞I/O</strong>：在此模式中，如果I/O操作的数据还没有准备好，操作会立即返回一个错误，而不是阻塞应用程序。应用程序可以继续执行其他操作，也可以反复尝试该I/O操作。 </li>
<li><strong>I/O多路复用</strong>：也常称为事件驱动I/O。在此模式中，应用程序可以同时监控多个I/O描述符（比如，socket），当任何一个I/O描述符准备好数据时，应用程序就可以对其进行处理。这可以在一个单独的进程或线程中同时处理多个I/O操作，并且不需要阻塞或轮询。select、poll、epoll都是这种模型的实现。 </li>
<li><strong>信号驱动</strong>：在此模型中，应用程序可以向操作系统注册一个信号处理函数，当数据准备好时，操作系统会发送一个信号，应用程序可以在接收到信号时读取数据。这种模式避免了阻塞和轮询，但是编程复杂性较高。</li>
<li><strong>异步I/O</strong>：在此模型中，应用程序发起I/O操作后，可以立即开始做其他事情，当数据准备好时，操作系统会将数据复制到应用程序的缓冲区，并通知应用程序。这种模型的优点是应用程序不需要等待I/O操作的完成，缺点是编程复杂性较高。</li>
</ul>
<blockquote>
<p>5 种 I/O 模型主要是阻塞 I/O、非阻塞 I/O、信号驱动式 I/O、I/O 多路复用、异步 I/O，其中前四个都属于同步I/O模型。 </p>
<p>阻塞同步I/O模型和非阻塞同步I/O模型的区别在于：进程发起系统调用后，是会被挂起直到收到数据后在返回、还是立即返回成功或错误。</p>
<p>同步I/O 和异步I/O 的区别在于：将数据从内核复制到用户空间时，用户进程是否会阻塞，如果用户进程会阻塞，则是同步I/O，如果不会阻塞就是异步  I/O。</p>
</blockquote>
<h2>阻塞IO和非阻塞IO的应用场景问题，有一个计算密集型的场景，和一个给用户传视频的场景，分别应该用什么io？</h2>
<p>计算密集型的场景，需要消耗的是 CPU 资源，用阻塞 IO 会比较好，如果用非阻塞 IO，在非阻塞IO模型中，用户线程需要不断地询问内核数据是否就绪，也就说非阻塞IO不会交出CPU，而会一直占用CPU。 </p>
<p>用户传视频的场景，瓶颈不是 CPU 资源，用非阻塞 IO 比较好，使用非阻塞IO可以避免阻塞在传输函数上，提高程序的并发性和响应时间。</p>
<h2>谈谈你对 I/O 多路复用的理解</h2>
<p>如果不使用 I/O 多路复用，服务端要并发处理多个客户端的 I/O 事件的话，需要通过创建子进程或者线程的方式来实现，也就是针对每一个连接的  I/O 事件要需要一个子进程或者线程来处理，但是随着客户端越来越多，意味着服务端需要创建更多的子进程或者线程，这样对系统的开销太大了。 </p>
<p>那么有了 I/O 多路复用就可以解决这个问题，I/O 多路复用可以实现是多个I/O复用一个进程，也就是只需要一个进程就能并发处理多个客户端的 I/O 事件， 进程可以通过select、poll、epoll这类 I/O 多路复用系统调用接口从内核中获取有事件发生的 socket 集合，然后应用程序就可以遍历这个集合，对每一个 socket 事件进行处理。 </p>
<p>Redis 单线程也能做到高性能的原因，也跟 I/O 多路复用有关系。</p>
<h2>select、poll、epoll 有什么区别？</h2>
<p>select 和 poll 内部都是使用「线性结构」来存储进程关注的 Socket 集合，在使用的时候，首先需要把关注的 Socket 集合通过 select/poll 系统调用从用户态拷贝到内核态，然后由内核检测事件，当有网络事件产生时，内核需要遍历进程关注 Socket 集合，找到对应的 Socket，并设置其状态为可读/可写，然后把整个 Socket 集合从内核态拷贝到用户态，用户态还要继续遍历整个 Socket 集合找到可读/可写的 Socket，然后对其处理。 很明显发现，select 和 poll 的缺陷在于，当客户端越多，也就是 Socket 集合越大，Socket 集合的遍历和拷贝会带来很大的开销，epoll 通过两个方面解决了 select/poll 的问题。 </p>
<p>epoll 在内核里使用「红黑树」来关注进程所有待检测的 Socket，通过 <code>epoll_ctl()</code> 函数加入内核中的红黑树里，红黑树是个高效的数据结构，增删改一般时间复杂度是 O(logn)，通过对这棵黑红树的管理，不需要像 select/poll 在每次操作时都传入整个 Socket 集合，只需要传入一个待检测的 socket，减少了内核和用户空间大量的数据拷贝和内存分配。</p>
<p>epoll 使用事件驱动的机制，内核里维护了一个链表来记录就绪事件，当某个 socket 有事件发生时，通过回调函数内核会将其加入到这个就绪事件列表中，当用户调用 <code>epoll_wait() </code>函数时，只会返回有事件发生的文件描述符链表表，不需要像 select/poll 那样轮询扫描整个 socket 集合，大大提高了检测的效率。</p>
<h2>select、poll、epoll 适合哪些应用场景？</h2>
<p>在连接数较少并且都十分活跃的情况下，选择 select 或者 poll 会比 epoll 性能好，因为  epoll 中的所有描述符都存储在内核中，必须使用epoll_ctl来添加到内核的红黑树中，这就意味着每一个新的连接都需要两次系统调用（epoll_ctl+epoll_wait），而在select 和poll中只需要一次，频繁系统调用降低效率。</p>
<p> 在连接数较多并且有很多的不活跃连接时，epoll 会比 select 和 poll 性能好，因为 epoll 在内核用红黑树来关注所有待检测的  socket，不需要像 select/poll 在每次操作时都传入整个 Socket 集合，减少了内核和用户空间大量的数据拷贝和内存分配，再加上 epoll 有就绪队列，也不需要像 select/poll 那样轮询扫描整个集合（包含有和无事件的 Socket ），大大提高了检测的效率。</p>
<h2>epoll ET 模式和 LT 模式有什么区别？哪一个更高效？</h2>
<p>ET 模式和 LT 模式 区别如下：</p>
<ul>
<li>边缘触发（ET，Edge Trigger）：当描述符从未就绪变为就绪时，只会通知一次，之后不会再通知，因此我们程序要保证一次性将事件处理完。 </li>
<li>水平触发（LT，Level Trigger）：当文件描述符就绪时，会触发通知，如果用户程序没有一次性把数据读/写完，下次还会发出可读/可写信号进行通知。</li>
</ul>
<p> 边缘触发的效率比水平触发的效率要高，因为边缘触发可以减少 epoll_wait 的系统调用次数。</p>
<h2>零拷贝技术了解过吗？说一下原理</h2>
<p><strong>传统文件传输</strong>：</p>
<ul>
<li>共发生了 <strong>4 次用户态与内核态的上下文切换</strong>，因为发生了两次系统调用，一次是 read() ，一次是 write()，每次系统调用都得先从用户态切换到内核态，等内核完成任务后，再从内核态切换回用户态。 </li>
<li>其次，还发生了 <strong>4 次数据拷贝</strong>，其中两次是 DMA 的拷贝，另外两次则是通过 CPU 拷贝的。</li>
</ul>
<p><strong>sendfile</strong>：</p>
<pre><code class="language-c">#include &lt;sys/socket.h&gt; 
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); </code></pre><p>它的前两个参数分别是目的端和源端的文件描述符，后面两个参数是源端的偏移量和复制数据的长度，返回值是实际复制数据的长度。 </p>
<ul>
<li>首先，它可以替代前面的 read() 和 write() 这两个系统调用，这样就可以减少一次系统调用，也就减少了 2 次上下文切换的开销。 </li>
<li>其次，该系统调用，可以直接把内核缓冲区里的数据拷贝到 socket 缓冲区里，不再拷贝到用户态，这样就只有 <strong>2 次上下文切换</strong>，和 <strong>3 次数据拷贝</strong>。</li>
</ul>
<p><strong>零拷贝</strong>：</p>
<p>对于支持网卡支持 SG-DMA 技术的情况下， <code>sendfile() </code>系统调用的过程发生了点变化，具体过程如下： </p>
<ul>
<li>第一步，通过 DMA 将磁盘上的数据拷贝到内核缓冲区里； </li>
<li>第二步，缓冲区描述符和数据长度传到 socket 缓冲区，这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里，此过程不需要将数据从操作系统内核缓冲区拷贝到 socket 缓冲区中，这样就减少了一次数据拷贝，这个过程之中，只进行了 2 次数据拷贝；</li>
</ul>
<p>这就是所谓的<strong>零拷贝（Zero-copy）技术</strong>，因为我们没有在内存层面去拷贝数据，也就是说全程没有通过 CPU 来搬运数据，所有的数据都是通过 DMA 来进行传输的。</p>
<p>零拷贝技术的文件传输方式相比传统文件传输的方式，减少了 2 次上下文切换和数据拷贝次数，<strong>只需要 2 次上下文切换和数据拷贝次数</strong>，就可以完成文件的传输，而且 2 次的数据拷贝过程，都不需要通过 CPU，2 次都是由 DMA 来搬运。</p>
<p>所以零拷贝技术主要是为了提升文件传输的效率，Kafka 消息队列 I/O 的吞吐量高的原因，也是因为使用了零拷贝技术。</p>
<p></p>
<p></p>
<p></p>
<h2>reactor 模式有哪些方案？</h2>
<p>常见的 Reactor 实现方案有三种。</p>
<p>第一种方案单 Reactor 单进程 / 线程，不用考虑进程间通信以及数据同步的问题，因此实现起来比较简单，这种方案的缺陷在于无法充分利用多核 CPU，而且处理业务逻辑的时间不能太长，否则会延迟响应，所以不适用于计算机密集型的场景，适用于业务处理快速的场景，比如 Redis（6.0之前 ） 采用的是单 Reactor 单进程的方案。</p>
<p>第二种方案单 Reactor 多线程，通过多线程的方式解决了方案一的缺陷，但它离高并发还差一点距离，差在只有一个 Reactor 对象来承担所有事件的监听和响应，而且只在主线程中运行，在面对瞬间高并发的场景时，容易成为性能的瓶颈的地方。</p>
<p>第三种方案多 Reactor 多进程 / 线程，通过多个 Reactor 来解决了方案二的缺陷，主 Reactor 只负责监听事件，响应事件的工作交给了从 Reactor，Netty 和 Memcache 都采用了「多 Reactor 多线程」的方案，Nginx 则采用了类似于 「多 Reactor 多进程」的方案。</p>
<h2>proactor 和 reactor 模式有什么区别？</h2>
<ul>
<li><strong>Reactor 是非阻塞同步网络模式，感知的是就绪可读写事件</strong>。在每次感知到有事件发生（比如可读就绪事件）后，就需要应用进程主动调用 read 方法来完成数据的读取，也就是要应用进程主动将 socket 接收缓存中的数据读到应用进程内存中，这个过程是同步的，读取完数据后应用进程才能处理数据。</li>
<li><strong>Proactor 是异步网络模式， 感知的是已完成的读写事件</strong>。在发起异步读写请求时，需要传入数据缓冲区的地址（用来存放结果数据）等信息，这样系统内核才可以自动帮我们把数据的读写工作完成，这里的读写工作全程由操作系统来做，并不需要像 Reactor 那样还需要应用进程主动发起 read/write 来读写数据，操作系统完成读写工作后，就会通知应用进程直接处理数据。</li>
</ul>
<p>因此，<strong>Reactor 可以理解为「来了事件操作系统通知应用进程，让应用进程来处理」</strong>，而 <strong>Proactor 可以理解为「来了事件操作系统来处理，处理完再通知应用进程」</strong>。</p>
<p>参考：</p>
<p><a href="https://xiaolincoding.com/os/">https://xiaolincoding.com/os/</a></p>

          <p style='text-align: right'>
          <a href='https://taitres.com/posts/computer/systemio#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164325116924674072</guid>
  <category>post</category>
<category>计算机基础</category>
 </item>
  <item>
    <title>操作系统（四）文件系统</title>
    <link>https://taitres.com/posts/computer/systemfile</link>
    <pubDate>Sun, 21 Jul 2024 07:52:29 GMT</pubDate>
    <description>读取一个文件的时候，操作系统会发生什么？



读取磁盘文件的时候，CPU 不会负责从磁盘读取数据的</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/posts/computer/systemfile'>https://taitres.com/posts/computer/systemfile</a></blockquote>
          <h2>读取一个文件的时候，操作系统会发生什么？</h2>
<p>读取磁盘文件的时候，CPU 不会负责从磁盘读取数据的过程，而是将磁盘数据搬运到内存的工作全部交给 DMA （直接内存访问）控制器，CPU 不再参与任何与数据搬运相关的事情，这样 CPU 就可以去处理别的工作，不会阻塞住。 具体的过程是这样的： </p>
<ul>
<li>用户进程调用 read 方法，向操作系统发出 I/O 请求，然后进程进入阻塞状态； </li>
<li>操作系统收到请求后，进一步将 I/O 请求发送 DMA，然后 CPU 会执行其他任务；</li>
<li>DMA 进一步将 I/O 请求发送给磁盘，磁盘收到 DMA 的 I/O 请求，把数据从磁盘读取到磁盘控制器的缓冲区中，当磁盘控制器的缓冲区被读满后，向 DMA 发起中断信号，告知自己缓冲区已满；</li>
<li>DMA 收到磁盘的信号，将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中，数据准备好后，然后就会发送中断信号给 CPU，CPU 收到 DMA 的信号，于是将数据从内核拷贝到用户空间，read 系统调用返回；</li>
</ul>
<h2>操作系统复制一个文件的流程是怎么样的？</h2>
<p>复制文件的流程：</p>
<ul>
<li>打开源文件，调用 read 系统调用读取源文件的数据，这时候会通过 DMA 将磁盘数据拷贝到内核缓冲区，然后 CPU 再将内核缓冲区的数据拷贝到用户缓冲区，然后 read 系统调用返回。</li>
<li>打开目标文件，调用 write 系统调用将从源文件读到的数据，写入到目标文件，这时候 CPU 会将用户缓冲区的数据拷贝到内核缓冲区， 然后write函数返回，后续操作系统会将内核缓冲区的数据刷入磁盘。</li>
</ul>
<h2>软链接和硬链接有什么区别？</h2>
<p>软链接和硬链接都是给文件取一个别名，区别主要有：</p>
<ul>
<li>软链接文件有独立的 inode 节点，文件的内容是目标文件的路径，不存储实际的文件数据，支持跨文件系统建立，而且目标文件删除了，软链接还是存在的，只不过指向的文件找不到了（类似快捷方式）</li>
<li>硬链接文件和目标文件都共用一个 inode 节点，说明他们是用一个文件，不支持跨文件系统建立，删除文件的时候，要除文件的所有硬链接以及源文件时，系统才会彻底删除该文件。</li>
</ul>

          <p style='text-align: right'>
          <a href='https://taitres.com/posts/computer/systemfile#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164325116924674071</guid>
  <category>post</category>
<category>计算机基础</category>
 </item>
  <item>
    <title>操作系统（三）内存管理</title>
    <link>https://taitres.com/posts/computer/systemmemory</link>
    <pubDate>Sat, 20 Jul 2024 07:40:23 GMT</pubDate>
    <description>为什么操作系统会有虚拟内存？

虚拟内存可以使得进程对运行内存超过物理内存大小，因为程序运行符合局部</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/posts/computer/systemmemory'>https://taitres.com/posts/computer/systemmemory</a></blockquote>
          <h2>为什么操作系统会有虚拟内存？</h2>
<ul>
<li>虚拟内存可以使得进程对运行内存超过物理内存大小，因为程序运行符合局部性原理，CPU 访问内存会有很明显的重复访问的倾向性，对于那些没有被经常使用到的内存，我们可以把它换出到物理内存之外，比如硬盘上的 swap 区域。 </li>
<li>由于每个进程都有自己的页表，所以每个进程的虚拟内存空间就是相互独立的。进程也没有办法访问其他进程的页表，所以这些页表是私有的，这就解决了多进程之间地址冲突的问题。</li>
<li>页表里的页表项中除了物理地址之外，还有一些标记属性的比特，比如控制一个页的读写权限，标记该页是否存在等。在内存访问方面，操作系统提供了更好的安全性。（如写时复制的权限）</li>
</ul>
<h2>什么是内存分段？</h2>
<p>分段是为了满足程序员在编写代码的时候的一些逻辑需求(比如数据共享，数据保护，动态链接等)。 </p>
<p>分段内存管理当中，地址是二维的，一维是段号，二维是段内地址；其中每个段的长度是不一样的，而且每个段内部都是从0开始编址的。</p>
<p>由于分段管理中，每个段内部是连续内存分配，但是段和段之间是离散分配的，因此也存在一个逻辑地址到物理地址的映射关系，相应的就是段表机制。</p>
<h2>什么是内存分页？</h2>
<p>把内存空间划分为大小相等且固定的块，作为主存的基本单位。</p>
<p>因为程序数据存储在不同的页面中，而页面又离散的分布在内存中，因此需要一个页表来记录映射关系，以实现从页号到物理块号的映射。</p>
<p>访问分页系统中内存数据需要两次的内存访问 (一次是从内存中访问页表，从中找到指定的物理块号，加上页内偏移得到实际物理地址；第二次就是根据第一次得到的物理地址访问内存取出数据)。</p>
<h2>段式管理和页式管理会出现内存碎片吗？</h2>
<p>段式管理可以做到根据段根据实际需求分配空间，所以有多少需求就分配多大的段，那么在段的内部就不会产生空间浪费，也就是没有碎片，但是由于每个段的长度不是固定的，所以段与段之间会产生碎片，这种称为<strong>外部碎片</strong>。 </p>
<p>而页则是固定大小的，通常是 4k，假设我们要为代码准备空间，即使这一段机器码不足4k，我们也要为它分配 4k，因为一个页的大小就是 4k，我们最少只能分配一个页，所以页式管理，页与页之间紧密排列，但是页内出现内存浪费，也就是内存碎片，这种称为<strong>内部碎片</strong>。</p>
<h2>页面置换算法</h2>
<p><strong>最佳置换算法（OPT）</strong>：置换掉未来永远不会被访问，理论上这种算法效果最好，但是在实际中无法得知未来进程的行为，所以无法实现。</p>
<p><strong>先进先出算法（FIFO）</strong>：置换掉最早进入内存的页面。这个算法简单，易于实现，但可能导致&quot;先进入&quot;的页面是常用页面而被频繁置换，造成性能下降。</p>
<p><strong>最近最久未使用算法（LRU）</strong>：置换掉最近一段时间内最少被访问的页面，算法的实现是给每个页面设置一个时间戳，记录最近一次访问的时间，如果发生缺页错误，则从所有页面中淘汰时间戳最久远的一个。</p>
<p><strong>最少使用算法（LFU）</strong>：置换掉访问频率最少的内存页面，实现方式是，对每个页面设置一个「访问计数器」，每当一个页面被访问时，该页面的访问计数器就累加 1。在发生缺页中断时，淘汰计数器值最小的那个页面。然而，当一个页面在进程的初始阶段大量使用但是随后不再使用时，会出现问题。由于被大量使用，它有一个大的计数，即使不再需要却仍保留在内存中。一种解决方案是，定期地将计数右移 1 位，以形成指数衰减的平均使用计数。</p>
<p><strong>时钟算法（Clock）</strong>：每页设置一个访问位，再将内存中的所有页面都通过链接指针链接成一个循环队列。当某个页面被访问时，其访问位置1。淘汰时，检查其访问位，如果是0，就换出；若为1，则重新将它置0，然后检查下一个页面，如果到队列中的最后一个页面时，若其访问位仍为1，则再返回到队首再去检查第一个页面。</p>
<h2>数组的物理空间连续吗？</h2>
<p>不是的，数组的虚拟地址是连续的，但是虚拟地址映射到物理地址是由操作系统负责的，操作系统并不会保证映射的物理地址也是连续的。</p>
<h2>栈的增长趋势是什么？</h2>
<p>栈的增长趋势是向内存地址减小的方向增长。也就是说，栈的底部在较高的内存地址，而栈的顶部在较低的内存地址。当新的元素被添加到栈中时，栈的顶部会向较低的内存地址方向移动，栈的大小会增加。当元素从栈中被移除时，栈的顶部会向较高的内存地址方向移动，栈的大小会减小。因此，栈的增长趋势是由内存布局决定的，向内存地址减小的方向增长。</p>
<h2>堆区和栈区有什么区别？</h2>
<ul>
<li>栈区的内存用于函数调用栈，主要保存函数的入参、局部变量、返回值等，栈区内存不可被其他线程共享，栈区的内存管理是由编译器自动完成的，编译时就确定了变量的生命周期，不需程序员来分配内存和释放内存，当函数执行结束时，栈区中的数据会自动被释放。</li>
<li>堆区内存需要通过动态内存分配函数来申请，堆区的内存可被其他线程共享，堆区内存的生命周期是由程序员控制的，需要进行显示分配和释放内存，如果反复向操作系统申请堆内存而不释放，会导致内存泄露。在 C / C++ 中，必须由程序员手动释放堆内存。而 Java / Golang 中有垃圾回收器，会定期主动回收内存。但是即使有垃圾回收器，也有内存泄漏的风险，比如长期持有某个大对象的引用。</li>
<li>堆区可分配的大小会比栈大很多，因为栈的内存是限的，通常是 8MB 大小，而堆的空间较大，受限于系统中有效的虚拟内存，比如 32 位系统，虚拟内存最大 4GB，64 位操作系统就 128TB。</li>
<li>栈区的内存分配效率会比堆区分配内存效率高，因为分配栈区内存，只需要移动栈指针即可，而分配堆区内存需要在堆中找到可用的内存块。</li>
</ul>
<h2>在栈上的数据操作比堆上快很多的原因？</h2>
<ul>
<li><p>访问栈上的数据是可以直接访问，因为 CPU 有栈相关的寄存器直接对栈进行访问，而堆上的数据需要通过指针进行访问，是一个间接寻址的过程，访问速度相对较慢。 </p>
</li>
<li><p>还有栈上的数据是连续存在，这样可以更好的利用 CPU 的 CPU Cache，访问栈上的数据缓存命中率比较高，而堆上的数据分布是散乱的，无法充分利用 CPU 缓存，导致缓存命中率较低，访问速度相对较慢。</p>
</li>
</ul>
<h2>32 位操作系统，4G物理内存，程序可以申请 8GB内存吗？</h2>
<p>32 位系统的内核空间占用 1G，位于最高处，剩下的 3G 是用户空间； </p>
<p>64 位系统的内核空间和用户空间都是 128T，分别占据整个内存空间的最高和最低处，剩下的中间部分是未定义的。</p>
<p>程序申请的内存实际上是虚拟内存，32 位操作系统中用户空间的虚拟内存大小是 3 GB，进程最多只能申请 3 GB 大小的虚拟内存空间，所以进程申请 8GB 内存的话，在申请虚拟内存阶段就会失败。</p>
<h2>64 位操作系统，4G物理内存，程序可以申请 8GB内存吗？</h2>
<p>在 64位 位操作系统，因为进程理论上最大能申请 128 TB 大小的虚拟内存，即使物理内存只有 4GB，申请 8G 内存也是没问题，因为申请的内存是虚拟内存。如果这块虚拟内存被访问了，要看系统有没有 Swap 分区： </p>
<ul>
<li>如果没有 Swap 分区，因为物理空间不够，进程会被操作系统杀掉，原因是 OOM（内存溢出）； </li>
<li>如果有 Swap 分区，即使物理内存只有 4GB，程序也能正常使用 8GB 的内存，进程可以正常运行；</li>
</ul>
<h2>fork 的写时复制是如何实现的？</h2>
<p>fork 是创建进程的系统调用， 执行 fork 的时候，子进程会复制父进程的 PCB 复制一份，这里包含页表、文件资源等，并且将页表的权限设置为只读，但是并不会复制物理内存，所以这时候父子进程的页表指向的<strong>物理内存的是同一片区域</strong>，此时父子进程的内存数据都是共享的。 </p>
<p>当父进程或者子进程对这片共享的内存数据进行了写操作，发现页表是只读的属性，这时候就会发生<strong>写保护中断</strong>，内核就会为发生中断的进程<strong>分配新的物理内存</strong>，把老的物理页的数据拷贝进这个新的物理页，然后将最新的数据写入到这个新的物理页，最后把发生中断的虚拟地址映射到新的物理页，同时该进程的页表的权限设置为可读写，这就完成了一次写时复制。</p>
<h2>malloc会陷入内核态吗？malloc的底层原理是什么？</h2>
<p>malloc 不是系统调用函数，不会陷入到内核态，malloc 申请内存的时候，会通过 brk 或者 mmap 这两个系统调用来申请内存。为了避免频繁向操作系统申请内存，malloc 还实现了内存池化技术，先申请一大块内存，然后将内存分成不同大小的内存块，然后用户申请内存时，直接从内存池中选择一块相近的内存块即可，这样就减少系统调用的开销。</p>

          <p style='text-align: right'>
          <a href='https://taitres.com/posts/computer/systemmemory#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164325116924674070</guid>
  <category>post</category>
<category>计算机基础</category>
 </item>
  <item>
    <title>操作系统（二）进程管理</title>
    <link>https://taitres.com/posts/computer/systemprocess</link>
    <pubDate>Wed, 17 Jul 2024 12:16:19 GMT</pubDate>
    <description>进程与线程

为什么创建进程比创建线程慢？

Linux 中创建一个进程自然会创建一个线程，也就是主</description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/posts/computer/systemprocess'>https://taitres.com/posts/computer/systemprocess</a></blockquote>
          <h1>进程与线程</h1>
<h2>为什么创建进程比创建线程慢？</h2>
<p>Linux 中创建一个进程自然会创建一个线程，也就是主线程。创建进程需要为进程划分出一块完整的内存空间，包含代码区、堆区、栈区、数据区等内存资源 </p>
<p>创建线程则简单得多，只需要确定 PC 指针和寄存器的值，并且给线程分配一个栈用于执行程序，同一个进程的多个线程间可以复用进程的虚拟内存空间，减少了创建虚拟内存的开销。</p>
<p>因此，创建进程比创建线程慢，而且进程的内存开销更大。</p>
<h2>为什么进程的切换比线程开销大？</h2>
<p>进程间的切换的时候，除了需要切换 CPU 上下文，还需要切换页表，切换了页表会影响TLB的命中率，那么虚拟地址转换为物理地址就会变慢，表现出来的就是程序运行会变慢，而线程切换只需要切换 CPU 上下文，不会改变虚拟地址空间，不会影响TLB命中率。</p>
<h2>线程的上下文切换过程</h2>
<p>线程发生上下文切换的时候，正在运行的线程会将寄存器的状态保存到内核中的 TCB（线程控制块） 里，然后恢复另一个线程的上下文。和进程的区别是，线程只需要切换CPU的上下文，不会改变地址空间.</p>
<h2>进程有哪些状态？</h2>
<p><em><strong>操作系统理论中的进程状态</strong></em></p>
<p>主要有 5 种，分别是创建状态、就绪状态、运行状态、阻塞状态、结束状态。</p>
<p> 一个进程刚开始创建的时候，是处于创建状态，随后就会进入就绪状态，等待被操作系统调度，当进程被调度器选中后，就会进入运行状态，此时进程就持有 CPU 执行权，当进程发生 I/O 事件的时候，就会进入到阻塞状态，等待 I/O 事件完成，I/O 事件完后进程就会恢复为就绪状态，当进程退出后，就会变为结束状态。</p>
<p><em><strong>Linux系统的进程状态</strong></em></p>
<ul>
<li><p><strong>可运行状态</strong>：该状态表示进程正在运行或者等待被调度（就绪和运行）；</p>
</li>
<li><p><strong>睡眠状态</strong>：在运行中的进程，一旦要进行一些 I/O 操作，需要等待 I/O 完毕，这个时候会释放 CPU，进入睡眠状态；</p>
<ul>
<li><strong>可中断的睡眠状态</strong>：等待 I/O 完毕期间，如果收到了其他信号，还是可以被唤醒；</li>
<li><strong>不可中断的睡眠状态</strong>：只有等待 I/O 完毕才有可能返回运行状态，任何信号都无法打断它。如果这种状态的进程出错，无法杀死，只能重启。</li>
</ul>
</li>
<li><p><strong>暂停状态</strong>：当进程接收到SIGSTOP、SIGTTIN、SIGTSTP 或者 SIGTTOU 信号之后进入该状态；</p>
</li>
<li><p><strong>跟踪状态</strong>：当对进程进行 gdb 调试的时候，进程会进入跟踪状态；</p>
</li>
<li><p><strong>僵尸状态</strong>：当子进程先于父进程退出后，父进程没有执行 wait 或者 waitpid 函数来回收子进程的时候，子进程的状态就会变为僵尸状态，表示已经死亡的进程，但是进程ID还保留着；</p>
</li>
<li><p><strong>结束状态</strong>：当进程正确退出后，就进入结束状态，是进程的最终状态。</p>
</li>
</ul>
<h2>僵尸进程，孤儿进程，守护进程的区别？</h2>
<ul>
<li><p>僵尸进程是指子进程已经终止，但其父进程尚未调用wait()或waitpid()函数来获取子进程的终止状态，导致子进程的进程描述符仍然保留在系统进程表中，成为僵死进程，僵死进程不占用系统资源，但会占用一个进程ID。 </p>
</li>
<li><p>孤儿进程是指父进程先于子进程退出或异常终止，导致子进程成为孤儿进程。孤儿进程会被init进程（进程ID为1）接管，init进程会成为孤儿进程的新的父进程。 </p>
</li>
<li><p>守护进程是在后台运行的一种特殊进程，不与任何终端关联，关闭终端并不会影响守护进程的生命周期，守护进程的生命周期通常是伴随系统的启动和关闭，会一直在后台运行。</p>
</li>
</ul>
<h2>怎么杀死僵尸进程？</h2>
<p>僵尸进程是已经死了的，不能直接使用 kill 命令杀掉僵尸进程，只能通过 ps 命令找到僵尸进程的父进程的 pid 号，然后通过 kill 命令杀掉父进程的方式来达到僵尸进程的效果，因为当父进程被杀掉后，操作系统会将僵尸进程的父进程会变为 1 号 init 进程，接着 init 进程会自动接管僵尸进程的回收工作。</p>
<h2>多进程和多线程的区别？</h2>
<p>多线程由于可以共享进程资源，而多进程不共享地址空间和资源，多进程需要通过进程间通信技术来实现数据传输，开发起来会比较麻烦。 </p>
<p>但多进程安全性较好，在某一个进程出问题时，其他进程一般不受影响；而在多线程的情况下，一个线程执行了非法操作会导致整个进程退出。</p>
<h2>一个进程fork出一个子进程，那么他们占用的内存是之前的2倍吗？</h2>
<p>不是的。</p>
<p> fork 的时候，创建的子进程是复父进程的虚拟内存，并不是物理内存，这时候父子的虚拟内存指向的是同一个物理内存空间，这样能够节约物理内存资源，页表对应的页表项的属性会标记该物理内存的权限为<strong>只读</strong>。</p>
<p>不过，当父进程或者子进程在向这个内存发起写操作时，CPU 就会触发写保护中断，这个写保护中断是由于违反权限导致的，然后操作系统会在「写保护中断处理函数」里进行物理内存的复制，并重新设置其内存映射关系，新页面的访问权限设置为可读写。此时，写入操作的进程将拥有该页面的独立副本，而另一个进程仍然共享原始的只读页面，这个过程被称为「<strong>写时复制</strong>」，发生了这个过程，内存占用才会增多。</p>
<h1>进程间通信</h1>
<h2>进程间有哪些通信方式？</h2>
<p>进程间通信方式主要有管道、消息队列、共享内存、信号、信号量和 socket 通信。</p>
<ul>
<li><p><strong>管道</strong>通信的数据是无格式的字节流，并且通信方向是单向的，只能在一个方向上流动，管道分为匿名管道和有名管道；</p>
<ul>
<li>匿名管道是没有文件实体，只能用于存在父子关系的进程间通信，匿名管道的生命周期随着进程创建而建立，随着进程终止而消失。</li>
<li>有名管道有文件实体，可以用于任何进程间的通信，并且数据可以持久化保存，即使创建它的进程退出，其他进程仍然可以使用该管道。</li>
</ul>
</li>
<li><p><strong>消息队列</strong>在内核中是通过链表来组织消息的，克服了管道通信的数据是无格式的问题；</p>
</li>
<li><p>管道和消息队列在读写数据的时候，都需要经过用户态与内核态之间的拷贝过程，<strong>共享内存</strong>就解决了这个问题，多个进程可以将共享内存映射到各自的虚拟地址空间，实现共享数据的读写，不会涉及用户态和内核态之间的数据拷贝，所以共享内存方式是进程间通信方式里最高效的，不过共享内存带来新的问题，多进程竞争同个共享资源会造成数据的错乱，因为需要同步机制来保证多进程下的读写数据的安全。</p>
</li>
<li><p><strong>信号量</strong>可以实现进程间的同步和互斥访问共享资源，信号量其实是一个计数器，表示的是资源个数，当一个进程想要访问资源时，它会尝试递减信号量（称为P操作）。如果信号量的值大于零，这个操作会成功，否则进程就会阻塞，直到信号量变为正数。当进程完成对资源的访问后，它会递增信号量（称为V操作），允许其他阻塞的进程访问资源。</p>
</li>
<li><p><strong>信号</strong>是一种异步的通知机制，用于通知进程某个事件已经发生。当一个信号发送给一个进程时，操作系统会中断进程的正常流程来处理信号，只适用于简单的通知和事件处理。</p>
</li>
<li><p>前面提到的这些通信方式都只能在本地上进行进程间通信，如果要实现跨主机的进程间通信，就需要通过 <strong>socket 通信</strong>了，可以实现基于 TCP 或者 UDP 协议的通信方式。</p>
</li>
</ul>
<h3>哪个进程间通信效率最高的</h3>
<p>共享内存的通信效率最高，因为共享内存不涉及内核态和用户态之间的数据拷贝。</p>
<p>共享内存的机制，就是拿出一块虚拟地址空间来，映射到相同的物理内存中。这样这个进程写入的东西，另外一个进程马上就能看到了，都不需要拷贝、传递，大大提高了进程间通信的速度。</p>
<h3>信号和信号量的区别</h3>
<p><strong>信号用于异步</strong>处理异常或特殊事件，而<strong>信号量用于同步</strong>进程以安全地访问共享资源。 </p>
<ul>
<li>信号是一种异步的通知机制，用于通知进程某个事件已经发生。当一个信号发送给一个进程时，操作系统会中断进程的正常流程来处理信号。</li>
<li>信号量是一种用于解决多进程同步和互斥问题的工具，主要用于保护共享资源，防止多个进程同时访问。信号量有一个整数值，该值的含义是可以同时访问某个资源的进程数量。当一个进程想要访问资源时，它会尝试递减信号量（称为P操作）。如果信号量的值大于零，这个操作会成功，否则进程就会阻塞，直到信号量变为正数。当进程完成对资源的访问后，它会递增信号量（称为V操作），允许其他阻塞的进程访问资源。</li>
</ul>
<h1>调度</h1>
<h2>进程的调度算法有哪些？</h2>
<ul>
<li><p><strong>先来先服务</strong>：按照进程到达的顺序进行调度，先到达的进程先执行。优势是<strong>简单、公平</strong>，但可能导致<strong>长作业等待时间过长，无法适应实时性要求高</strong>的场景。</p>
</li>
<li><p><strong>最短作业优先</strong>：选择估计运行时间最短的进程优先执行。优势是能够最大程度地<strong>减少平均等待时间</strong>，但需要准确预测进程的运行时间，这个<strong>实现起来会比较困难</strong>。</p>
</li>
<li><p><strong>时间片轮转（RR）</strong>：将CPU时间分成多个时间片，每个进程轮流占用一个时间片，如果一个进程在该时间片结束时还没有完成，则将其移到队列的末尾，等待下一次调度。优势是<strong>公平</strong>，但对于<strong>长作业和实时性要求高的场景可能不够高效</strong>。</p>
</li>
<li><p><strong>优先级调度</strong>：为每个进程分配一个优先级，优先级高的进程先执行。可根据不同的调度策略确定优先级，如静态优先级、动态优先级等。优势是<strong>能够根据进程的重要性和紧急程度进行调度</strong>，但<strong>可能导致优先级低的进程长时间等待</strong>。</p>
</li>
<li><p><strong>多级反馈队列调度（MFQS）</strong>：「多级」表示有多个队列，每个队列优先级从高到低，同时优先级越高时间片越短。「反馈」表示如果有新的进程加入优先级高的队列时，立刻停止当前正在运行的进程，转而去运行优先级高的队列。优点：<strong>动态优先级调整、短作业优先处理、提高系统吞吐量和利用率、支持实时任务</strong>。多级反馈队列调度存在的问题，优先级较低的队列可能会被优先级较高的队列长时间占用，导致优先级较低的进程无法得到执行，从而产生<strong>饥饿</strong>现象。</p>
<ul>
<li><p>设置了多个队列，赋予每个队列不同的优先级，每个队列优先级从高到低，同时优先级越高时间片越短；</p>
</li>
<li><p>新的进程会被放入到第一级队列的末尾，按先来先服务的原则排队等待被调度，如果在第一级队列规定的时间片没运行完成，则将其转入到第二级队列的末尾，以此类推，直至完成；</p>
</li>
<li><p>当较高优先级的队列为空，才调度较低优先级的队列中的进程运行。如果进程运行时，有新进程进入较高优先级的队列，则停止当前运行的进程并将其移入到原队列末尾，接着让较高优先级的进程运行；</p>
</li>
</ul>
</li>
</ul>
<h1>锁</h1>
<h2>线程间同步方式有哪些？</h2>
<p>Linux 系统提供了五种用于线程间同步的方式：互斥量、读写锁、信号量、自旋锁、条件变量。 </p>
<ul>
<li>互斥锁：用于保护共享资源，确保同一时间只有一个线程可以访问该资源。只有获得互斥锁的线程才能进入临界区，其他线程需要等待锁的释放。 </li>
<li>读写锁：也称为共享-独占锁，允许多个线程同时读取共享资源，但在写操作时需要独占访问。读写锁在读多写少的场景中可以提供更好的并发性能。 </li>
<li>信号量：用于控制对一组资源的访问。信号量可以允许多个线程同时访问资源，但是需要在访问前进行P操作（申请资源）和在访问结束后进行V操作（释放资源），以确保资源的正确使用。 </li>
<li>自旋锁：是一种忙等待锁，在获取锁之前，线程会一直尝试获取锁，而不会进入睡眠状态。自旋锁适用于保护临界区较小、锁占用时间短暂的情况。 </li>
<li>条件变量：用于在线程之间进行条件同步。一个线程可以等待某个条件满足，而另一个线程在满足条件时可以通知等待的线程继续执行。（wait-notify）</li>
</ul>
<h3>信号量和互斥锁应用场景有什么区别？</h3>
<ul>
<li>信号量一般以<strong>同步</strong>的方式对共享资源进行控制，而互斥锁通过<strong>互斥</strong>的方式对共享资源对其进行控制；</li>
<li>互斥量的加锁和解锁必须由同一线程分别对应使用，信号量可以由一个线程释放，另一个线程得到。</li>
</ul>
<h3>自旋锁和互斥锁有什么区别？分别适合哪些应用场景？</h3>
<ul>
<li>互斥锁加锁失败的时候，线程会放弃CPU，陷入到内核态，执行线程切换和CPU上下文切换的过程，切换到其他线程；</li>
<li>而自旋锁加锁失败后，线程不会放弃 CPU，而是选择忙等待，直到它拿到锁。</li>
</ul>
<p>自旋锁适用于并发竞争时间短暂的情况，可以减少线程切换和CPU上下文切换的开销。互斥锁适用于并发竞争时间较长或资源争用较激烈的情况，可以避免线程忙等待，但会带来线程切换和上下文切换的开销。</p>
<h2>悲观锁和乐观锁有什么区别？</h2>
<p>悲观锁做事比较悲观，它认为多线程同时修改共享资源的概率比较高，很容易出现冲突，造成数据错乱，所以会每次读写共享资源之前，先要加锁，确保任意时刻只有一个线程才能对数据进行读写操作。 </p>
<p>乐观锁做事比较乐观，它假定冲突的概率很低，先修改完共享资源，再验证这段时间内有没有发生冲突，如果没有其他线程在修改资源，那么操作完成，如果发现有其他线程已经修改过这个资源，就放弃本次操作，并选择报错、重试等策略。 </p>
<p>悲观锁适合<strong>并发写入多和竞争激烈</strong>的场景，这种场景下悲观锁可以避免大量的无用的反复尝试等消耗。乐观锁适合<strong>读多写少和并发不激烈</strong>的场景，在这些场景下乐观锁不加锁的特点能让性能大幅提高。</p>

          <p style='text-align: right'>
          <a href='https://taitres.com/posts/computer/systemprocess#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164325116924674069</guid>
  <category>post</category>
<category>计算机基础</category>
 </item>
  <item>
    <title>操作系统（一）系统结构</title>
    <link>https://taitres.com/posts/computer/systemstructure</link>
    <pubDate>Tue, 16 Jul 2024 13:36:34 GMT</pubDate>
    <description>存储结构 

为什么计算机要给储存结构分级？

不同级别的储存结构具有不同的访问速度，CPU 访问 </description>
    <content:encoded><![CDATA[
      <blockquote>This rendering is produced by marked and may have formatting issues. For the best experience, visit: <a href='https://taitres.com/posts/computer/systemstructure'>https://taitres.com/posts/computer/systemstructure</a></blockquote>
          <h1>存储结构</h1>
<h2>为什么计算机要给储存结构分级？</h2>
<p>不同级别的储存结构具有不同的访问速度，CPU 访问 CPU Cache 的延时只需要几个时钟周期，访问物理内存的延时则是几百个，速度相差 100 倍，访问磁盘延时就更高了，已经到毫秒级别了（百万时钟周期），但是访问速度越快的存储器，造价成本会高很多，容量也比较小。 通过分级储存结构，可以将数据按照不同的访问速度、容量和成本要求进行管理，CPU Cache用于存储近期频繁访问的数据，内存用于存储当前执行的程序和数据，而硬盘则用于存储大量数据和长期存储。（一个时钟周期小于10纳秒）</p>
<h2>CPU cache L1 L2 L3 读取数据的时间量级差距有多大？</h2>
<p>CPU 访问 L1 Cache 只需要 2<del>4 个时钟周期，访问 L2 Cache 大约 10</del>20 个时钟周期，访问 L3 Cache 大约 20<del>60 个时钟周期，而访问内存速度大概在 200</del>300 个 时钟周期之间。</p>
<h2>CPU 缓存对程序性能的影响都体现在哪些方面？</h2>
<p>程序具有<strong>局部性原理</strong>：如果一个数据被访问，那么附近的数据很可能也会被访问到，</p>
<p>CPU Cache就是基于这个局部性原理，在访问数据的时候，如果访问的数据不在 CPU Cache中，不会只从内存读这一个数据，而是会从内存连续加载数据到 CPU Cache，这样相邻的数据都会被缓存在  CPU Cache，那么程序在访问相邻数据的时候，相邻数据就可以直接在缓存中命中，无需访问内存，可以提高访问的效率。</p>
<h1>内核态</h1>
<h2>什么是内核？</h2>
<p>内核是系统中第一个加载的程序，并拥有最高的特权级别，控制整个系统的运行和资源分配。</p>
<p>负责<strong>管理进程</strong>、线程：决定哪个进程、线程使用 CPU，也就是进程调度的能力；</p>
<p><strong>管理内存</strong>：决定内存的分配和回收，也就是内存管理的能力；</p>
<p><strong>管理硬件设备</strong>：为进程与硬件设备之间提供通信能力；</p>
<p><strong>提供系统调用</strong>：它与用户空间程序进行交互，提供系统调用接口，实现了操作系统的基本功能。</p>
<h2>内核态和用户态区别？为什么要区分内核态和用户态？</h2>
<p>CPU 将指令分为了特权指令和非特权指令，然后操作系统根据 CPU 的特权分级机制，把进程的运行空间分为内核空间和用户空间。</p>
<p><strong>内核态</strong>具有最高权限，可以执行<strong>特权指令</strong>，直接访问所有系统和硬件资源，而<strong>用户态</strong>不能执行特权指令，只能执行<strong>非特权指令</strong>，所以只能访问受限的资源，比如不可以直接访问内存、硬盘、网卡等硬件设备，如果要访问这些硬件资源，需要通过系统调用的方式，让进程陷入到内核态才能访问这些特权资源。 </p>
<p>之所以要区分内核态和用户态，目的就是为了保证系统的稳定性和安全性，因为通过限制应用程序对特权指令的访问，可以防止恶意程序直接操作危险的指令导致系统崩溃的问题。</p>
<h2>什么时候会由用户态陷入内核态？</h2>
<p>主要有三种情况，会导致应用程序从用户态陷入到内核态： </p>
<ul>
<li><p>应用程序执行<strong>系统调用</strong>的时候，会触发一个<strong>软中断</strong>，会进入内核态执行相应的内核代码，完成后再返回用户态。 </p>
</li>
<li><p>当应用程序发生<strong>异常</strong>情况，如访问非法内存、除零错误等，CPU会触发一个异常，将控制权转移到内核态的异常处理程序中。 </p>
</li>
<li><p>当系统接收到<strong>外部设备的中断</strong>信号，如硬件设备的输入/输出请求、定时器中断等，处理器会中断当前的执行，进入内核态执行相应的中断处理程序。</p>
</li>
</ul>
<p>第一种属于软件中断，后两种属于硬件中断</p>
<h2>系统调用的过程？</h2>
<p>当应用程序发生系统调用的时候，会发生用户态和内核态的切换，过程是这样的： </p>
<ul>
<li>执行系统调用的时候，先将系统调用名称转换为系统调用号，接着将<strong>系统调用号</strong>和请求的参数放到寄存器中，然后执行软件中断命令，CPU会从<strong>用户态切换到内核态</strong>。 </li>
<li>CPU 跳转到中断处理程序，将当前<strong>用户态的现场信息保存到内核栈中</strong>，接着根据系统调用号从系统调用表中找到对应的系统调用函数，并将寄存器中保存的参数取出来，作为函数参数，然后在<strong>内核中执行系统调用函数</strong>。 </li>
<li>执行完系统调用后，执行<strong>中断返回指令</strong>，<strong>内核栈</strong>会弹出之前保存的用户态的现场信息，将原来用户态保存的<strong>现场恢复</strong>回来，这时候 <strong>CPU 恢复到用户态</strong>，用户态进程恢复执行。</li>
</ul>
<h2>用户态和内核态是如何切换的？</h2>
<p>简单就参考<a href="#%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E7%9A%84%E8%BF%87%E7%A8%8B%EF%BC%9F">系统调用的过程？</a></p>
<h2>为什么用户态和内核态的相互切换过程开销比较大</h2>
<ul>
<li>上下文切换：在用户态和内核态之间切换时，需要保存当前进程或线程的上下文信息，包括程序计数器、寄存器状态、栈指针等。这些上下文信息保存在内存中或者内核数据结构中，在切换完成后需要再次加载回来。这个过程会消耗一定的时间和资源。 </li>
<li>权限切换：用户态和内核态具有不同的权限级别，内核态拥有更高的特权级别，可以执行一些用户态不可访问的指令和操作。因此，从用户态切换到内核态需要进行特权级别的变更，这个过程也会增加开销。</li>
<li>地址空间切换：用户态和内核态通常有不同的地址空间，为了保护操作系统内核不受用户程序的直接影响，当切换到内核态时需要进行地址空间的切换和映射，这也会增加开销。</li>
<li>TLB刷新：TLB是CPU中的一种缓存，用于存储虚拟地址到物理地址的映射关系。当发生用户态和内核态的切换时，可能导致TLB中的缓存无效，需要进行TLB刷新，这会引起一定的性能损失。</li>
</ul>
<h1>中断</h1>
<h2>什么是中断？为什么要有中断？</h2>
<p>中断是系统用来响应硬件设备请求的一种机制，操作系统收到硬件的中断请求，会打断正在执行的进程，然后调用内核中的中断处理程序来响应请求。</p>
<p>中断机制的好处是化主动为被动，避免 CPU 轮询等待某条件成立。如果没有中断机制，那么“某个条件成立”就需要 CPU 轮询判断，这样就会增加系统的开销。而使用中断机制，就可以在条件成立之后，向 CPU 发送中断事件，强制中断 CPU 执行程序，转而去执行中断处理程序。</p>
<p>可以理解为中断是一种异步的事件处理机制，可以提高系统的并发处理能力。</p>
<h2>什么是硬件中断和软件中断？</h2>
<ul>
<li>中断（interrupt）：由<strong>硬件设备</strong>触发，比如点击一下鼠标、敲一下键盘，这时候外部设备会给 CPU 发送一个中断号，属于异步事件； </li>
<li>异常（exception）：<strong>CPU</strong> 在执行指令时检测到的反常条件，比如除法异常、错误指令异常，缺页异常等，然后CPU 自己给自己一个中断号，无需外界给，属于同步事件； </li>
<li>INT 指令：INT 指令后面跟一个数字，就相当于直接用指令的形式，告诉 CPU 一个中断号。比如 INT 0x80，就是告诉 CPU 中断号是 0x80。Linux 内核提供的系统调用，就是用了 INT 0x80 这种指令。</li>
</ul>
<p>硬件中断：由硬件自动触发的中断就属于硬件中断，比如中断和异常都属于硬件中断，中断是由外部设备触发的，异常是由 CPU 触发的。 </p>
<p>软件中断：由软件程序主动触发的中断属于软件中断，比如 INT 指令，Linux 内核提供的系统调用，就是用了 INT 0x80 这种指令。</p>
<h2>什么是硬中断和软中断？</h2>
<p>根据实现的方式的不同，分为了硬中断和软中断： </p>
<ul>
<li>由 CPU <strong>硬件实现</strong>的中断机制，就属于硬中断，比如中断、异常、INT指令都是由 CPU 实现的中断机制。 （ CPU 在每一个指令周期的最后，都会留一个 CPU 周期去查看是否有中断，如果有，就把中断号取出，去「中断向量表」中寻找中断处理程序，然后跳过去执行中断处理程序。）</li>
<li>由<strong>软件实现</strong>的中断机制，就属于软中断。比如 Linux 实现的软中断守护进程。 （一个单独的守护进程，不断轮询一组标志位，如果哪个标志位有值了，那去这个标志位对应的「软中断向量表数组」的相应位置，找到软中断处理函数，然后跳过去执行软中断处理函数。）</li>
</ul>
<p>硬中断和软中断的中断效果是一样的，都是打断当前正在运行的程序，转而去执行中断处理程序，执行完之后再返回原程序。</p>
<h2>为什么要有软中断？</h2>
<p>软中断可以承接原本硬中断处理程序比较复杂且耗时的工作，让硬中断的中断处理函数的逻辑尽可能的简单，从而提高系统的中断响应速度。 </p>
<p>比方说，在网卡收到数据包的时候，会把数据包的事件分为上半部分和下半部分： </p>
<ul>
<li>上部分由硬中断处理程序处理，要做的事情很少，做完后就会发起了一次软中断，然后就结束了。这里发起的软中断，并不是向 CPU 发送中断信号，而是将软中断标记数组中的某一个位置标记一下。 </li>
<li>下半部由软中断处理程序处理，内核守护进程会不断轮询软中断标记数组，看哪个位置被标记为 1 了，接着就去软中断向量表里，寻找这个标志位对应的处理程序，然后执行软中断处理程序处理，其主要是需要从内存中找到网络数据，再按照网络协议栈，对网络数据进行逐层解析和处理，最后把数据送给应用程序。</li>
</ul>

          <p style='text-align: right'>
          <a href='https://taitres.com/posts/computer/systemstructure#comments'>Finished reading? Leave a comment</a>
          </p>
    ]]>
    </content:encoded>
  <guid isPermaLink="false">164325116924674067</guid>
  <category>post</category>
<category>计算机基础</category>
 </item>
  
</channel>
</rss>