<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
<channel>
<title>NothAmor - Little Stone</title>
<link>https://nothamor.com/</link>
<description>Just So So ...</description>
<atom:link href="https://nothamor.com/rss.xml" rel="self" type="application/rss+xml" />
<lastBuildDate>Mon, 11 May 2026 11:05:39 +0800</lastBuildDate>
<item>
<title>Moonshot Kimi Coding Plan 额度解析与同类对比</title>
<link>https://nothamor.com/archives/kimiCodingPlan.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/kimiCodingPlan.html</guid>
<pubDate>Wed, 22 Apr 2026 17:43:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[> 在 AI 编程助手几乎成为开发者「标配」的 2026 年，各大厂商的 **Coding Plan**（编程套餐）已成为新的战场。不同于单纯的 API 按量付费，Coding Plan 以固定月费换取高频、低延迟、不限峰值的模型调用权限，正成为日常写代码...]]></description>
<content:encoded><![CDATA[
<blockquote>
<p>在 AI 编程助手几乎成为开发者「标配」的 2026 年，各大厂商的 <strong>Coding Plan</strong>（编程套餐）已成为新的战场。不同于单纯的 API 按量付费，Coding Plan 以固定月费换取高频、低延迟、不限峰值的模型调用权限，正成为日常写代码的主力方案。</p>
<p>本文基于笔者对 <strong>Moonshot Kimi</strong> 的实际使用体验（含真实 Token 消耗数据推算），并结合国内六大主流厂商的最新套餐，做一份全景式对比，供开发者选型参考。</p>
</blockquote>
<hr />
<h2>一、Kimi Coding Plan：真实额度推算</h2>
<h3>1.1 套餐档位</h3>
<p>Moonshot Kimi 目前提供三档 Coding Plan（均以 <strong>7 天为周期</strong>刷新额度，未用完不累积）：</p>
<table>
<thead>
<tr>
<th>档位</th>
<th>月费</th>
<th>核心权益</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Andante</strong></td>
<td>¥49</td>
<td>5 小时 Token 配额（约 300~1,200 次 API 调用），Kimi K2.5 旗舰模型，AI 建站/文档/PPT 权益</td>
</tr>
<tr>
<td><strong>Moderato</strong></td>
<td>¥99</td>
<td>更大 Token 配额，多设备登录共享额度，支持 Kimi CLI / Claude Code / Roo Code</td>
</tr>
<tr>
<td><strong>Allegretto</strong></td>
<td>¥199</td>
<td>Kimi Code <strong>20 倍额度</strong>，4 倍 Agent 额度，4 倍 K2.5 额度，支持 Agent 集群</td>
</tr>
</tbody>
</table>
<p>此外，Kimi 在 2026 年初推出了<strong>限时 3 倍额度扩容</strong>活动，活动期间所有套餐的编程额度自动翻 3 倍，这让中低档位用户的实际可用量大幅提升。</p>
<h3>1.2 从真实账单反推额度</h3>
<p>笔者订阅的是 Kimi Coding Plan，当前周期（7 天滚动）已运行约 <strong>6.3 天</strong>，距离下次重置还剩 <strong>17 小时</strong>。通过解析本地 <code>~/.kimi/sessions/*/wire.jsonl</code> 的 <code>StatusUpdate</code> 记录，得到以下真实消耗：</p>
<table>
<thead>
<tr>
<th>指标</th>
<th>数值</th>
</tr>
</thead>
<tbody>
<tr>
<td>输入 Token (input_other)</td>
<td>7,210,923</td>
</tr>
<tr>
<td>输入 Token (input_cache_read)</td>
<td>155,220,747</td>
</tr>
<tr>
<td><strong>输入 Token 总计</strong></td>
<td><strong>162,431,670</strong></td>
</tr>
<tr>
<td><strong>输出 Token 总计</strong></td>
<td><strong>1,054,264</strong></td>
</tr>
<tr>
<td><strong>当前周期已用总计</strong></td>
<td><strong>163,485,934</strong></td>
</tr>
</tbody>
</table>
<p>后台显示本周已消耗 <strong>62%</strong> 的额度，由此反推：</p>
<pre><code>周期额度 ≈ 1.63 亿 ÷ 0.62 ≈ 2.64 亿 Tokens
剩余额度 ≈ 1.00 亿 Tokens（38%）</code></pre>
<p>也就是说，在<strong>限时 3 倍扩容</strong>的加持下，笔者所在的这档套餐，<strong>单周期（7 天）可用额度约为 2.6 亿 Tokens</strong>。若按自然月折算，月可用 Token 量轻松突破 <strong>10 亿</strong>。</p>
<blockquote>
<p><strong>关键发现</strong>：Kimi 的 Token 消耗中，<strong>input_cache_read</strong> 占了绝对大头（约 95%）。这是 Moonshot 的上下文缓存机制在起作用——重复读取已缓存的上下文会被计入 Token，但实际计费时通常有折扣。因此，2.6 亿的「名义 Token」与实际等效 API Token 并不能 1:1 划等号，实际等效调用量可能更高。</p>
</blockquote>
<hr />
<h2>二、国内主流 Coding Plan 横向对比</h2>
<p>下面将 Kimi 与阿里云百炼、火山方舟（字节）、智谱 GLM、MiniMax、DeepSeek 等国内主流方案进行横向对比。</p>
<h3>2.1 核心参数一览</h3>
<table>
<thead>
<tr>
<th>厂商</th>
<th>入门档月费</th>
<th>计费单位</th>
<th>核心额度（入门档）</th>
<th>周期</th>
<th>支持核心模型</th>
<th>适配工具数</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Kimi</strong></td>
<td>¥49</td>
<td>Token</td>
<td>约 2.6 亿 / 7 天（3 倍扩容后实测）</td>
<td>7 天</td>
<td>Kimi K2.5（1T 参数，256K 上下文）</td>
<td>3~4 款</td>
</tr>
<tr>
<td><strong>阿里云百炼</strong></td>
<td>¥40（首月 ¥7.9）</td>
<td>请求次数</td>
<td>每 5h 1,200 次 / 每周 9,000 次 / 每月 18,000 次</td>
<td>5h/周/月</td>
<td>Qwen3.5-Plus、GLM-4.7、Kimi-K2.5 等</td>
<td>4+</td>
</tr>
<tr>
<td><strong>火山方舟</strong></td>
<td>¥40（首月 ¥8.91）</td>
<td>请求次数</td>
<td>每 5h 1,200 次 / 每周 9,000 次 / 每月 18,000 次</td>
<td>5h/周/月</td>
<td>Doubao-Seed-Code、DeepSeek-V3.2、GLM-4.7、Kimi-K2.5</td>
<td>7+</td>
</tr>
<tr>
<td><strong>智谱 GLM</strong></td>
<td>¥49</td>
<td>Prompt 次数</td>
<td>每 5h 80 次 / 每周 400 次（无月限）</td>
<td>5h/周</td>
<td>GLM-4.7、GLM-5（Max 档）</td>
<td>20+</td>
</tr>
<tr>
<td><strong>MiniMax</strong></td>
<td>¥29（首月 ¥9.9）</td>
<td>Prompt 次数</td>
<td>每 5h 40 次（无周/月限额）</td>
<td>5h</td>
<td>MiniMax M2.5、M2.5-highspeed</td>
<td>2+</td>
</tr>
<tr>
<td><strong>DeepSeek</strong></td>
<td>¥0（纯 API）</td>
<td>Token</td>
<td>新用户 500 万 Token 免费（30 天）</td>
<td>按量</td>
<td>DeepSeek-V3.2、DeepSeek-R1</td>
<td>全兼容</td>
</tr>
</tbody>
</table>
<h3>2.2 进阶套餐对比</h3>
<table>
<thead>
<tr>
<th>厂商</th>
<th>进阶档</th>
<th>月费</th>
<th>核心升级点</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Kimi</strong></td>
<td>Moderato / Allegretto</td>
<td>¥99 / ¥199</td>
<td>更大 Token 配额、多设备共享、Agent 集群</td>
</tr>
<tr>
<td><strong>阿里云百炼</strong></td>
<td>Pro</td>
<td>¥200</td>
<td>每 5h 6,000 次 / 每周 45,000 次 / 每月 90,000 次，支持 qwen3.6-plus、glm-5</td>
</tr>
<tr>
<td><strong>火山方舟</strong></td>
<td>Pro</td>
<td>¥200</td>
<td>额度为 Lite 的 5 倍，每月可达数十亿 Token 等效量，Auto 智能调度模型</td>
</tr>
<tr>
<td><strong>智谱 GLM</strong></td>
<td>Pro / Max</td>
<td>¥149 / ¥469</td>
<td>Max 档解锁 GLM-5（754B 参数），但高峰期按 3 倍抵扣额度</td>
</tr>
</tbody>
</table>
<hr />
<h2>三、深度解析：各家优劣势</h2>
<h3>3.1 Kimi：长上下文与原生多模态的「重炮手」</h3>
<p><strong>优势：</strong></p>
<ul>
<li><strong>模型性能强</strong>：Kimi K2.5 是国内参数规模最大的旗舰模型之一（1T 参数），支持 <strong>256K 长上下文</strong>和原生多模态（截图即可 Vibe Coding）。</li>
<li><strong>Token 额度充裕</strong>：在 3 倍扩容活动下，入门档即可获得单周数亿级的 Token 配额，对于大型代码库分析、长文档处理非常友好。</li>
<li><strong>Agent 能力突出</strong>：Allegretto 档支持 Agent 集群，适合复杂多步骤开发任务。</li>
</ul>
<p><strong>劣势：</strong></p>
<ul>
<li><strong>工具生态封闭</strong>：仅官方支持 Kimi CLI、VSCode 插件、Claude Code、Roo Code 等少数工具，非指定工具使用有封号风险。</li>
<li><strong>仅限个人</strong>：明确禁止企业场景使用。</li>
<li><strong>缓存机制不透明</strong>：大量 Token 消耗在 <code>input_cache_read</code> 上，实际可用次数与名义 Token 数存在认知差，新手容易「看不懂账单」。</li>
</ul>
<h3>3.2 阿里云百炼：「稳」字当头的多模型超市</h3>
<p><strong>优势：</strong></p>
<ul>
<li><strong>模型选择最丰富</strong>：一次订阅即可调用 Qwen、GLM、Kimi、MiniMax 等多家模型，相当于「模型超市」。</li>
<li><strong>阿里云基础设施</strong>：稳定性在国内第一梯队，适合对 SLA 有要求的商业项目。</li>
<li><strong>首月价格极低</strong>：Lite 首月 ¥7.9，几乎等于白送。</li>
</ul>
<p><strong>劣势：</strong></p>
<ul>
<li><strong>Lite 已停售</strong>：2026 年 3 月 20 日起停止新购，4 月 13 日起停止续费，入门门槛被抬高到 Pro（¥200/月）。</li>
<li><strong>不支持子账号</strong>：仅主账号可用，团队协作不便。</li>
</ul>
<h3>3.3 火山方舟（字节）：「多而全」的 Auto 调度派</h3>
<p><strong>优势：</strong></p>
<ul>
<li><strong>模型数量最多</strong>：不仅支持字节自研的豆包 Seed Code，还聚合了 DeepSeek、GLM、Kimi 等第三方模型，支持 <strong>Auto 模式</strong>自动匹配最优模型。</li>
<li><strong>工具适配最广</strong>：支持 Claude Code、Cursor、Cline、ArkClaw 等 7 款以上工具，配置简单。</li>
<li><strong>等效 Token 量大</strong>：Pro 档每月可用额度折算后高达「大几亿至数十亿 Token」。</li>
</ul>
<p><strong>劣势：</strong></p>
<ul>
<li><strong>高峰期超售</strong>：用户反馈高峰期响应缓慢，频繁出现 400/429 错误。</li>
<li><strong>退款条款苛刻</strong>：订阅前需仔细阅读退订政策。</li>
</ul>
<h3>3.4 智谱 GLM：工具链最完善的「极客之选」</h3>
<p><strong>优势：</strong></p>
<ul>
<li><strong>工具适配无敌</strong>：支持 20+ 款编程工具（Claude Code、Cline、CodeGeeX 等），并提供免费 MCP 工具链。</li>
<li><strong>纯自研模型</strong>：GLM-5（744B 参数，MoE 架构）在开源社区口碑极佳，代码能力国内第一梯队。</li>
</ul>
<p><strong>劣势：</strong></p>
<ul>
<li><strong>涨价后性价比下滑</strong>：2026 年 2 月 Lite 从 ¥40 涨至 ¥49，且取消首购优惠，入门档每周仅 400 次 Prompt，重度用户容易触顶。</li>
<li><strong>额度抵扣复杂</strong>：GLM-5 高峰期按 3 倍、非高峰按 2 倍抵扣，计费心智成本高。</li>
</ul>
<h3>3.5 MiniMax：低价高频的「轻量快刀」</h3>
<p><strong>优势：</strong></p>
<ul>
<li><strong>入门价最低</strong>：Starter 仅 ¥29/月，首月 ¥9.9。</li>
<li><strong>无每周限额</strong>：仅有每 5 小时 40 次的短周期限制，适合连续高强度使用（如通宵赶项目）。</li>
<li><strong>响应速度快</strong>：极速版可达 100+ TPS。</li>
</ul>
<p><strong>劣势：</strong></p>
<ul>
<li><strong>模型规模小</strong>：M2.5 仅 10B 参数级别，适合轻量脚本任务，面对大型代码库或复杂推理时能力有限。</li>
</ul>
<h3>3.6 DeepSeek：不按套路出牌的「价格屠夫」</h3>
<p><strong>优势：</strong></p>
<ul>
<li><strong>无订阅费</strong>：纯 API 按量付费，无月费门槛。</li>
<li><strong>价格全球最低</strong>：V3.2 输入仅 $0.27~0.30 / 百万 Token，缓存命中低至 $0.028 / 百万 Token，比 GPT-5 便宜 80% 以上。</li>
<li><strong>新用户免费送 500 万 Token</strong>（30 天内有效）。</li>
</ul>
<p><strong>劣势：</strong></p>
<ul>
<li><strong>无专属 Coding Plan</strong>：没有固定月费的「无限额度」套餐，高频使用者难以预估月度成本。</li>
<li><strong>稳定性波动</strong>：高峰期偶发延迟，且没有订阅用户的优先级保障。</li>
</ul>
<hr />
<h2>四、选型建议：对号入座</h2>
<table>
<thead>
<tr>
<th>人群</th>
<th>首选方案</th>
<th>理由</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>新用户 / 羊毛党</strong></td>
<td>阿里云百炼 Lite（首月 ¥7.9）</td>
<td>首月成本极低，模型丰富，适合试水</td>
</tr>
<tr>
<td><strong>学生党 / 预算敏感</strong></td>
<td>MiniMax Starter（¥29）或 DeepSeek 免费 API</td>
<td>月费低，无周限额，按量付费可控</td>
</tr>
<tr>
<td><strong>长文本 / 多模态需求</strong></td>
<td><strong>Kimi Andante / Moderato</strong></td>
<td>256K 上下文 + 截图编程，单周数亿 Token 额度</td>
</tr>
<tr>
<td><strong>多模型切换 / 工具党</strong></td>
<td>火山方舟 或 阿里云百炼 Pro</td>
<td>模型多、Auto 调度、工具适配广</td>
</tr>
<tr>
<td><strong>重度全栈开发者</strong></td>
<td>阿里云百炼 Pro / 火山方舟 Pro（¥200）</td>
<td>额度充裕，稳定性高，支持复杂项目</td>
</tr>
<tr>
<td><strong>追求极致性价比</strong></td>
<td>DeepSeek API 按量付费</td>
<td>单价最低，无月费，适合能自己控制调用量的开发者</td>
</tr>
<tr>
<td><strong>GLM 生态死忠</strong></td>
<td>智谱 GLM Pro（¥149）</td>
<td>自研模型 + MCP 工具链 + 最广工具适配</td>
</tr>
</tbody>
</table>
<hr />
<h2>五、结论</h2>
<p>从笔者对 <strong>Kimi Coding Plan</strong> 的实际使用数据来看，在 3 倍扩容活动的加持下，Kimi 入门档的单周 Token 额度（约 <strong>2.6 亿 Tokens</strong>）已经处于国内第一梯队水平，与火山方舟 Pro、阿里云百炼 Pro 的「大几亿至数十亿」月额度处于同一量级。考虑到 Kimi K2.5 的模型规模（1T 参数）和长上下文能力（256K），其<strong>「单位 Token 能买到的模型性能」</strong>具有很强竞争力。</p>
<p>然而，Kimi 的短板同样明显：<strong>工具生态封闭、仅限个人使用、缓存机制不透明</strong>。如果你是习惯在多种 IDE 和 Agent 框架间切换的开发者，或者需要团队协作，火山方舟和阿里云百炼可能是更务实的中庸之选；如果你追求极致低价且能自己管理调用节奏，DeepSeek 依然是不可撼动的「价格屠夫」。</p>
<p>在 2026 年的 Coding Plan 战场，已经没有绝对的「最优解」，只有<strong>「最匹配你工作流」</strong>的选择。建议开发者优先利用各家的首月优惠或免费额度（Kimi 3 倍扩容、阿里云百炼 ¥7.9、DeepSeek 500 万免费 Token 等）进行实际体验，再决定长期订阅方案。</p>
<hr />
<p><em>本文数据更新于 2026 年 4 月 22 日。Coding Plan 价格和活动变动频繁，请以各厂商官网最新信息为准。</em></p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://nothamor.com/archives/kimiCodingPlan.html#comments</comments>
<enclosure url="https://cdn.nothamor.com/usr/uploads/2026/04/3914543155.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>UGREEN AX900 (AIC8800) USB WiFi 网卡 Linux 6.x 折腾全记录</title>
<link>https://nothamor.com/archives/AIC8800.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/AIC8800.html</guid>
<pubDate>Sun, 05 Apr 2026 03:27:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<description><![CDATA[> 记录从开箱到开箱即用的完整折腾路径，包括内核兼容性修复和开机自动配置。
目录

背景
问题一：驱动编译失败
问题二：每次开机都要手动加载驱动
问题三：为什么要"拔掉再插上"
完整解决方案
总结


背景
硬件：UGREEN AX900 USB WiFi...]]></description>
<content:encoded><![CDATA[
<blockquote>
<p>记录从开箱到开箱即用的完整折腾路径，包括内核兼容性修复和开机自动配置。</p>
</blockquote>
<h2>目录</h2>
<ul>
<li><a href="#背景">背景</a></li>
<li><a href="#问题一驱动编译失败">问题一：驱动编译失败</a></li>
<li><a href="#问题二每次开机都要手动加载驱动">问题二：每次开机都要手动加载驱动</a></li>
<li><a href="#问题三为什么要拔掉再插上">问题三：为什么要"拔掉再插上"</a></li>
<li><a href="#完整解决方案">完整解决方案</a></li>
<li><a href="#总结">总结</a></li>
</ul>
<hr />
<h2>背景</h2>
<p><strong>硬件</strong>：UGREEN AX900 USB WiFi 6 网卡<br />
<strong>芯片</strong>：AICSemi AIC8800DC<br />
<strong>系统</strong>：Ubuntu 24.04 LTS<br />
<strong>内核</strong>：6.17.0-20-generic  </p>
<p>刚装完系统，插入网卡，<strong>没有任何反应</strong>。</p>
<pre><code class="language-bash">$ lsusb | grep -i aic
# 空，什么都没有

$ ip link
# 只有 lo 和有线网卡，没有无线网卡</code></pre>
<p>AIC8800 官方驱动只支持到内核 3.10+，在 Linux 6.x 上编译直接报错。于是开始了折腾之路。</p>
<hr />
<h2>问题一：驱动编译失败</h2>
<h3>现象</h3>
<p>从绿联官网或 AICSemi 下载的驱动源码，直接 <code>make</code> 报错：</p>
<pre><code class="language-bash">$ make
...
error: implicit declaration of function 'del_timer'
error: too few arguments to function 'cfg80211_rx_spurious_frame'
error: initialization of 'int (*)(struct wiphy *, int, u32)' from incompatible pointer type
...</code></pre>
<h3>原因分析</h3>
<p>Linux 6.x 内核有几处 API 变更：</p>
<table>
<thead>
<tr>
<th>API 变更</th>
<th>旧版本</th>
<th>Linux 6.x</th>
</tr>
</thead>
<tbody>
<tr>
<td>定时器</td>
<td><code>del_timer()</code></td>
<td><code>timer_delete()</code></td>
</tr>
<tr>
<td>cfg80211</td>
<td><code>set_wiphy_params(wiphy, changed)</code></td>
<td>增加 <code>radio_idx</code> 参数</td>
</tr>
<tr>
<td>唤醒源</td>
<td><code>wakeup_source_create()</code> + <code>wakeup_source_add()</code></td>
<td><code>wakeup_source_register()</code></td>
</tr>
</tbody>
</table>
<h3>修复方案</h3>
<h4>1. 修复 <code>del_timer</code> API</h4>
<p>在 <code>rwnx_main.c</code>、<code>rwnx_rx.c</code>、<code>aicwf_tcp_ack.c</code> 中添加：</p>
<pre><code class="language-c">#include &lt;linux/version.h&gt;
#include &lt;linux/timer.h&gt;

#if LINUX_VERSION_CODE &gt;= KERNEL_VERSION(6, 0, 0)
#define del_timer(timer) timer_delete(timer)
#define del_timer_sync(timer) timer_delete_sync(timer)
#endif</code></pre>
<h4>2. 修复 <code>cfg80211</code> API</h4>
<p>在 <code>rwnx_main.c</code> 中修改两个函数签名：</p>
<pre><code class="language-c">// set_wiphy_params 增加 radio_idx 参数
static int rwnx_cfg80211_set_wiphy_params(struct wiphy *wiphy,
#if LINUX_VERSION_CODE &gt;= KERNEL_VERSION(6, 0, 0)
                                          int radio_idx,
#endif
                                          u32 changed)

// set_tx_power 增加 radio_idx 参数
static int rwnx_cfg80211_set_tx_power(struct wiphy *wiphy,
#if LINUX_VERSION_CODE &gt;= KERNEL_VERSION(3, 8, 0)
    struct wireless_dev *wdev,
#endif
#if LINUX_VERSION_CODE &gt;= KERNEL_VERSION(6, 0, 0)
    int radio_idx,
#endif
    enum nl80211_tx_power_setting type, int mbm)</code></pre>
<h4>3. 修复 <code>wakeup_source</code> API</h4>
<p>在 <code>rwnx_wakelock.c</code> 中：</p>
<pre><code class="language-c">struct wakeup_source *rwnx_wakeup_init(const char *name)
{
#if LINUX_VERSION_CODE &gt;= KERNEL_VERSION(6, 0, 0)
    return wakeup_source_register(NULL, name);
#else
    struct wakeup_source *ws;
    ws = wakeup_source_create(name);
    wakeup_source_add(ws);
    return ws;
#endif
}</code></pre>
<h4>4. 修复 <code>sprintf</code> 源目标重叠</h4>
<p>在 <code>aicwf_compat_8800d80.c</code> 等文件中：</p>
<pre><code class="language-c">// 错误的写法（GCC 会警告）
sprintf(aic_fw_path, "%s/%s", aic_fw_path, "aic8800DC");

// 正确的写法
{
    char tmp_path[200];
    strncpy(tmp_path, aic_fw_path, sizeof(tmp_path) - 1);
    tmp_path[sizeof(tmp_path) - 1] = '\0';
    snprintf(aic_fw_path, sizeof(aic_fw_path), "%s/%s", tmp_path, "aic8800DC");
}</code></pre>
<h4>5. 修复 <code>from_timer</code> 宏</h4>
<p>在需要使用的文件中添加：</p>
<pre><code class="language-c">#ifndef from_timer
#define from_timer(var, callback_timer, timer_fieldname) \
    container_of(callback_timer, typeof(*var), timer_fieldname)
#endif</code></pre>
<h3>编译安装</h3>
<pre><code class="language-bash">cd aic8800_linux_driver/drivers/aic8800
make clean
make
sudo make install</code></pre>
<p>成功后会生成两个 <code>.ko</code> 文件：</p>
<ul>
<li><code>aic_load_fw/aic_load_fw.ko</code> - 固件加载器</li>
<li><code>aic8800_fdrv/aic8800_fdrv.ko</code> - WiFi 驱动</li>
</ul>
<hr />
<h2>问题二：每次开机都要手动加载驱动</h2>
<h3>现象</h3>
<p>每次重启后：</p>
<pre><code class="language-bash">$ lsmod | grep aic
# 空的，驱动没加载

$ ip link
# 没有无线网卡</code></pre>
<p>必须手动执行：</p>
<pre><code class="language-bash">sudo modprobe cfg80211
sudo modprobe aic_load_fw
sudo modprobe aic8800_fdrv</code></pre>
<h3>原因</h3>
<p>驱动没有配置为开机自动加载。</p>
<h3>解决方案</h3>
<p><strong>方案 1：配置 modules-load.d（推荐）</strong></p>
<pre><code class="language-bash">echo -e "cfg80211\naic_load_fw\naic8800_fdrv" | sudo tee /etc/modules-load.d/aic8800.conf</code></pre>
<p><strong>方案 2：使用 systemd 服务</strong></p>
<p>创建 <code>/etc/systemd/system/aic8800-autoload.service</code>：</p>
<pre><code class="language-ini">[Unit]
Description=AIC8800 USB WiFi Driver
After=systemd-udev-settle.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/modprobe aic8800_fdrv

[Install]
WantedBy=multi-user.target</code></pre>
<p>启用服务：</p>
<pre><code class="language-bash">sudo systemctl daemon-reload
sudo systemctl enable aic8800-autoload.service</code></pre>
<hr />
<h2>问题三：为什么要"拔掉再插上"</h2>
<h3>现象</h3>
<p>按照网上教程，加载驱动后要：</p>
<ol>
<li><code>lsmod | grep aic</code> 确认驱动加载成功</li>
<li><strong>拔掉</strong>网卡</li>
<li><strong>重新插入</strong>网卡</li>
<li>网卡才能工作</li>
</ol>
<h3>根本原因：USB 网卡的双模式设计</h3>
<p>AIC8800 芯片的 USB WiFi 网卡采用<strong>双模式设计</strong>：</p>
<table>
<thead>
<tr>
<th>模式</th>
<th>VID:PID</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>存储模式</strong></td>
<td><code>a69c:5721</code></td>
<td>插入时默认模式，系统识别为 U 盘（用于 Windows 自动安装驱动）</td>
</tr>
<tr>
<td><strong>WiFi 模式</strong></td>
<td><code>a69c:8800</code></td>
<td>实际工作的无线网卡模式</td>
</tr>
</tbody>
</table>
<h3>工作流程</h3>
<pre><code>插入网卡
    ↓
系统识别为 USB 存储设备 (a69c:5721)
    ↓
执行 eject /dev/aicudisk（弹出存储设备）
    ↓
网卡自动重新枚举为 WiFi 设备 (a69c:8800)
    ↓
驱动识别并初始化设备
    ↓
网卡正常工作</code></pre>
<h3>为什么必须先加载驱动再插网卡？</h3>
<p><strong>驱动加载顺序问题</strong>：</p>
<ul>
<li>
<p><strong>如果先插网卡，后加载驱动</strong>：</p>
<ul>
<li>网卡处于存储模式 (<code>a69c:5721</code>)</li>
<li>驱动加载时寻找 WiFi 设备 (<code>a69c:8800</code>)，找不到</li>
<li>驱动初始化失败或无法绑定设备</li>
</ul>
</li>
<li>
<p><strong>正确的顺序</strong>：</p>
<ol>
<li>先加载驱动（注册 USB 设备监听）</li>
<li>插入网卡 → 触发 udev 规则 → 自动执行 <code>eject</code> 切换模式</li>
<li>网卡切换到 WiFi 模式 → 驱动识别并绑定</li>
</ol>
</li>
</ul>
<h3>手动操作流程</h3>
<pre><code class="language-bash"># 1. 加载驱动（等待设备）
sudo modprobe cfg80211
sudo modprobe aic_load_fw
sudo modprobe aic8800_fdrv

# 2. 确认驱动已加载
lsmod | grep aic
# aic8800_fdrv          696320  0
# aic_load_fw            94208  1 aic8800_fdrv

# 3. 插入网卡（udev 自动处理模式切换）
# 或者手动切换：sudo eject /dev/aicudisk

# 4. 查看网卡
ip link
# wlan0: &lt;BROADCAST,MULTICAST&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000</code></pre>
<hr />
<h2>完整解决方案</h2>
<h3>一键配置开机自动加载</h3>
<p>我已经创建了完整的自动配置脚本：</p>
<pre><code class="language-bash">cd aic8800_linux_driver/tools/autostart
sudo ./setup-autostart.sh</code></pre>
<p>这个脚本会自动完成：</p>
<ol>
<li><strong>安装固件</strong>到 <code>/lib/firmware/aic8800D80/</code></li>
<li><strong>配置 udev 规则</strong>：<ul>
<li>检测到存储模式 (<code>a69c:5721</code>) → 自动 <code>eject</code> 切换模式</li>
<li>检测到 WiFi 模式 (<code>a69c:8800</code>) → 自动加载驱动</li>
</ul>
</li>
<li><strong>配置模块开机加载</strong>：<code>cfg80211</code>、<code>aic_load_fw</code>、<code>aic8800_fdrv</code></li>
<li><strong>创建 systemd 服务</strong>：处理开机时网卡已插入的情况</li>
</ol>
<p>然后<strong>重启电脑</strong>，网卡就能自动识别了。</p>
<h3>卸载自动配置</h3>
<p>如果需要恢复手动操作：</p>
<pre><code class="language-bash">sudo ./remove-autostart.sh</code></pre>
<hr />
<h2>验证</h2>
<h3>检查网卡是否识别</h3>
<pre><code class="language-bash">$ lsusb | grep a69c
Bus 001 Device 007: ID a69c:8800 AICSemi AIC8800DC
# 注意 VID:PID 是 8800（WiFi 模式），不是 5721（存储模式）</code></pre>
<h3>检查驱动是否加载</h3>
<pre><code class="language-bash">$ lsmod | grep aic
aic8800_fdrv          696320  0
aic_load_fw            94208  1 aic8800_fdrv
cfg80211             1462272  3 b43,mac80211,aic8800_fdrv</code></pre>
<h3>检查网络接口</h3>
<pre><code class="language-bash">$ ip link
3: wlan0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc mq state UP mode DORMANT group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff</code></pre>
<h3>连接 WiFi</h3>
<pre><code class="language-bash">nmcli device wifi list
nmcli device wifi connect "SSID" password "password"</code></pre>
<hr />
<h2>总结</h2>
<table>
<thead>
<tr>
<th>问题</th>
<th>原因</th>
<th>解决方案</th>
</tr>
</thead>
<tbody>
<tr>
<td>编译失败</td>
<td>Linux 6.x API 变更</td>
<td>修改 9 个文件，添加兼容性宏</td>
</tr>
<tr>
<td>开机不自动加载</td>
<td>未配置模块自动加载</td>
<td>配置 <code>modules-load.d</code> 和 systemd 服务</td>
</tr>
<tr>
<td>需要拔插</td>
<td>USB 双模式设计，需要切换模式</td>
<td>udev 规则自动处理模式切换</td>
</tr>
</tbody>
</table>
<p><strong>最终效果</strong>：</p>
<ul>
<li>开机自动识别网卡，无需手动操作</li>
<li>热插拔支持，随时插拔都能自动识别</li>
<li>适配 Linux 6.x 内核</li>
</ul>
<hr />
<h2>参考</h2>
<ul>
<li>修复后的驱动源码：<a href="https://github.com/NothAmor/ugreen-ax900-6.x-kernel-fix-ver">ugreen-ax900-6.x-kernel-fix-ver</a></li>
<li>AICSemi 官方驱动（旧版）</li>
<li>Linux 内核 API 变更文档</li>
</ul>
<hr />
<p><em>Ubuntu 24.04 + 内核 6.17.0</em></p>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://nothamor.com/archives/AIC8800.html#comments</comments>
<enclosure url="https://cdn.nothamor.com/usr/uploads/2026/04/3783647151.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>深入浅出 Golang Runtime 源码分析</title>
<link>https://nothamor.com/archives/166.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/166.html</guid>
<pubDate>Mon, 02 Mar 2026 13:10:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[# Golang Runtime 源码深度解析：从调度器到内存管理
引言
Go 语言以其简洁的语法和强大的并发能力赢得了众多开发者的青睐。然而，Go 的魅力远不止于此——其内置的 runtime 系统才是支撑这一切的核心引擎。本文将深入 Go runtim...]]></description>
<content:encoded><![CDATA[
<h1>Golang Runtime 源码深度解析：从调度器到内存管理</h1>
<h2>引言</h2>
<p>Go 语言以其简洁的语法和强大的并发能力赢得了众多开发者的青睐。然而，Go 的魅力远不止于此——其内置的 runtime 系统才是支撑这一切的核心引擎。本文将深入 Go runtime 的源码，带你一探究竟，理解这个精巧系统如何高效地管理 goroutine、内存分配和垃圾回收。</p>
<h2>1. Go Runtime 架构概览</h2>
<p>Go runtime 是 Go 程序运行时的核心组件，它负责：</p>
<ul>
<li>Goroutine 调度</li>
<li>内存分配与垃圾回收</li>
<li>网络轮询</li>
<li>系统调用封装</li>
<li>栈管理</li>
</ul>
<p>Runtime 的源码主要位于 <code>src/runtime</code> 目录下，其中最关键的几个文件包括：</p>
<ul>
<li><code>proc.go</code> - 调度器实现</li>
<li><code>mheap.go</code> - 内存分配器</li>
<li><code>mgc.go</code> - 垃圾回收器</li>
<li><code>netpoll.go</code> - 网络轮询器</li>
</ul>
<h2>2. Goroutine 调度器：GMP 模型</h2>
<h3>2.1 什么是 GMP？</h3>
<p>Go 的调度器采用 GMP 模型：</p>
<ul>
<li><strong>G (Goroutine)</strong>: 用户级轻量级线程</li>
<li><strong>M (Machine)</strong>: 操作系统线程</li>
<li><strong>P (Processor)</strong>: 逻辑处理器，包含 goroutine 队列</li>
</ul>
<p>这种设计巧妙地解决了传统线程模型的性能瓶颈问题。</p>
<h3>2.2 调度器工作原理</h3>
<p>当一个 goroutine 被创建时，它会被放入某个 P 的本地队列中。M 线程会从关联的 P 中获取 goroutine 来执行。如果本地队列为空，M 会尝试从其他 P 的队列中"偷取"工作（work-stealing）。</p>
<p>关键数据结构：</p>
<pre><code class="language-go">// G 结构体（简化版）
type g struct {
    stack       stack          // goroutine 栈
    sched       gobuf          // 调度信息
    goid        int64          // goroutine ID
    status      uint32         // 状态
}

// M 结构体（简化版）
type m struct {
    g0          *g             // 用于调度的特殊 goroutine
    curg        *g             // 当前正在执行的 goroutine
    p           puintptr       // 关联的 P
}

// P 结构体（简化版）
type p struct {
    runq        [256]guintptr  // 本地运行队列
    runnext     guintptr       // 下一个要运行的 goroutine
}</code></pre>
<h3>2.3 调度时机</h3>
<p>Go 调度器在以下时机进行调度：</p>
<ul>
<li><strong>系统调用</strong>：当 goroutine 进行系统调用时，M 可能会被阻塞，此时 runtime 会将 P 交给其他 M</li>
<li><strong>channel 操作</strong>：当 goroutine 在 channel 上阻塞时</li>
<li><strong>网络 I/O</strong>：通过 netpoller 实现非阻塞 I/O</li>
<li><strong>主动让出</strong>：通过 <code>runtime.Gosched()</code> 主动让出 CPU</li>
</ul>
<h2>3. 内存分配器</h2>
<p>Go 的内存分配器借鉴了 TCMalloc 的设计思想，采用多级缓存策略来减少锁竞争。</p>
<h3>3.1 内存分配层级</h3>
<p>Go 的内存分配分为三个层级：</p>
<ol>
<li><strong>mcache</strong>: 每个 P 对应一个 mcache，用于小对象分配，无锁</li>
<li><strong>mcentral</strong>: 全局中心缓存，按大小类组织，有锁但粒度较小</li>
<li><strong>mheap</strong>: 全局堆，管理大块内存和虚拟地址空间</li>
</ol>
<h3>3.2 分配流程</h3>
<p>当需要分配内存时：</p>
<ol>
<li>首先检查 mcache 中对应大小类的 span 是否有空闲对象</li>
<li>如果没有，从 mcentral 获取一个 span</li>
<li>如果 mcentral 也没有，从 mheap 分配新的 span</li>
</ol>
<p>关键函数 <code>mallocgc</code> 的简化逻辑：</p>
<pre><code class="language-go">func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    if size &lt;= maxSmallSize {
        // 小对象分配
        if noscan &amp;&amp; size &lt; maxTinySize {
            // 微小对象分配（&lt;16字节）
            return tinyAlloc(size, needzero)
        }
        // 小对象分配（16字节-32KB）
        return smallAlloc(size, needzero)
    }
    // 大对象分配（&gt;32KB）
    return largeAlloc(size, needzero)
}</code></pre>
<h2>4. 垃圾回收器</h2>
<p>Go 采用三色标记清除算法，实现了低延迟的并发垃圾回收。</p>
<h3>4.1 三色标记法</h3>
<ul>
<li><strong>白色</strong>: 未被访问的对象，垃圾回收后会被回收</li>
<li><strong>灰色</strong>: 已被访问但其引用对象未被扫描的对象</li>
<li><strong>黑色</strong>: 已被访问且其引用对象也已扫描的对象</li>
</ul>
<h3>4.2 GC 流程</h3>
<p>Go 的 GC 分为四个阶段：</p>
<ol>
<li><strong>Sweep Termination</strong>: 清理上一轮 GC 的残留</li>
<li><strong>Mark</strong>: 并发标记阶段，将可达对象标记为黑色</li>
<li><strong>Mark Termination</strong>: 完成标记，重新扫描栈</li>
<li><strong>Sweep</strong>: 并发清理白色对象</li>
</ol>
<h3>4.3 写屏障</h3>
<p>为了保证并发标记的正确性，Go 使用了 Dijkstra 写屏障：</p>
<pre><code class="language-go">// 写屏障伪代码
func writePointer(slot *unsafe.Pointer, ptr unsafe.Pointer) {
    *slot = ptr
    if gcphase == _GCmark || gcphase == _GCmarktermination {
        gcWriteBarrier(ptr)
    }
}</code></pre>
<p>写屏障确保在 GC 过程中，任何新建立的引用都会被正确标记。</p>
<h2>5. 栈管理</h2>
<p>Go 的 goroutine 栈是动态增长的，初始栈大小很小（2KB），根据需要自动扩展。</p>
<h3>5.1 栈扩容</h3>
<p>当函数调用需要更多栈空间时，runtime 会：</p>
<ol>
<li>分配更大的栈</li>
<li>复制原有栈内容到新栈</li>
<li>更新所有指向旧栈的指针</li>
</ol>
<h3>5.2 栈收缩</h3>
<p>在 Go 1.3 之前，栈只会增长不会收缩。从 Go 1.4 开始，引入了栈收缩机制，当栈使用率低于 1/4 时会尝试收缩。</p>
<h2>6. 网络轮询器</h2>
<p>Go 的网络轮询器基于 epoll (Linux)、kqueue (macOS) 或 IOCP (Windows) 实现，将阻塞的网络 I/O 转换为非阻塞操作。</p>
<h3>6.1 netpoller 工作原理</h3>
<ol>
<li>当 goroutine 执行网络读写时，如果数据不可用，会将 goroutine 挂起</li>
<li>netpoller 监控文件描述符的可读/可写状态</li>
<li>当文件描述符就绪时，netpoller 唤醒对应的 goroutine</li>
</ol>
<h3>6.2 关键数据结构</h3>
<pre><code class="language-go">// pollDesc 结构体（简化版）
type pollDesc struct {
    link   *pollDesc     // 链表链接
    fd     uintptr       // 文件描述符
    rg     guintptr      // 读等待者
    wg     guintptr      // 写等待者
}</code></pre>
<h2>7. 性能优化技巧</h2>
<p>基于对 runtime 的理解，我们可以写出更高效的 Go 代码：</p>
<h3>7.1 减少内存分配</h3>
<ul>
<li>使用 sync.Pool 复用对象</li>
<li>预分配切片容量</li>
<li>避免不必要的字符串转换</li>
</ul>
<h3>7.2 优化 goroutine 使用</h3>
<ul>
<li>避免创建过多的 goroutine</li>
<li>使用 worker pool 模式限制并发数</li>
<li>及时关闭不再需要的 goroutine</li>
</ul>
<h3>7.3 GC 调优</h3>
<ul>
<li>通过 <code>GOGC</code> 环境变量调整 GC 频率</li>
<li>使用 <code>debug.SetGCPercent()</code> 动态调整</li>
<li>监控 GC 统计信息：<code>runtime.ReadMemStats()</code></li>
</ul>
<h2>结论</h2>
<p>Go runtime 是一个精巧而复杂的系统，它通过 GMP 调度模型、分层内存分配、并发垃圾回收等机制，为我们提供了高效的并发编程体验。理解 runtime 的工作原理不仅能满足我们的好奇心，更能帮助我们写出更高效、更可靠的 Go 代码。</p>
<p>正如 Rob Pike 所说："Concurrency is not parallelism, it's about composition." Go runtime 正是这种哲学的最佳体现——它让我们能够优雅地组合并发原语，构建复杂的并发系统。</p>
<h2>参考资料</h2>
<ul>
<li>Go 源码: <a href="https://github.com/golang/go/tree/master/src/runtime">https://github.com/golang/go/tree/master/src/runtime</a></li>
<li>"Go 语言学习笔记" - 雨痕</li>
<li>"Go 程序设计语言" - Alan A. A. Donovan &amp; Brian W. Kernighan</li>
<li>Go Blog: <a href="https://blog.golang.org/">https://blog.golang.org/</a></li>
</ul>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://nothamor.com/archives/166.html#comments</comments>
<enclosure url="https://cdn.nothamor.com/usr/uploads/2026/03/3667465910.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>oh-my-zsh默认主题显示全路径</title>
<link>https://nothamor.com/archives/156.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/156.html</guid>
<pubDate>Thu, 29 Aug 2024 17:38:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[网上查的让我改PWD，但是改PWD只能一直显示一个路径，不会随当前的目录改变而改变
搞了一下分享出来
打开终端，输入如下指令
vi ~/.oh-my-zsh/themes/robbyrussell.zsh-theme
进入后按99dd，然后按i键后，粘贴如...]]></description>
<content:encoded><![CDATA[
<p>网上查的让我改PWD，但是改PWD只能一直显示一个路径，不会随当前的目录改变而改变</p>
<p>搞了一下分享出来</p>
<p>打开终端，输入如下指令</p>
<pre><code>vi ~/.oh-my-zsh/themes/robbyrussell.zsh-theme</code></pre>
<p>进入后按99dd，然后按i键后，粘贴如下内容</p>
<pre><code>PROMPT="%(?:%{$fg_bold[green]%}%1{➜%} :%{$fg_bold[red]%}%1{➜%} ) %{$fg[cyan]%}%~%{$reset_color%}"
PROMPT+=' $(git_prompt_info)'

ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[blue]%}git:(%{$fg[red]%}"
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} "
ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg[blue]%}) %{$fg[yellow]%}%1{✗%}"
ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg[blue]%})"</code></pre>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://nothamor.com/archives/156.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/08/29/1724924302.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>Golang时间轮精简实现</title>
<link>https://nothamor.com/archives/golangTimeWheel.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/golangTimeWheel.html</guid>
<pubDate>Sat, 03 Aug 2024 15:23:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[**文章背景**
最近遇到一个业务上的问题，用户进行一个操作，会同时生成两个kafka消息
在一个消费者消费消息的时候依赖另一个消费者生产的数据
被依赖方执行的速度比依赖方的慢，所以希望延迟一点消费这条数据
处理方法
kafka生产消息的时候可以加入一个d...]]></description>
<content:encoded><![CDATA[
<p><strong>文章背景</strong></p>
<p>最近遇到一个业务上的问题，用户进行一个操作，会同时生成两个kafka消息</p>
<p>在一个消费者消费消息的时候依赖另一个消费者生产的数据</p>
<p>被依赖方执行的速度比依赖方的慢，所以希望延迟一点消费这条数据</p>
<p><strong>处理方法</strong></p>
<p>kafka生产消息的时候可以加入一个delay参数，用于控制消息的延迟消费</p>
<p>但是这里的问题是生产者面对非常多的消费者，加入这个参数风险不可控</p>
<p>所以决定在希望延迟消费的消费者这里加入一个时间轮，用于实现延迟消费的功能</p>
<p>所以有了这篇文章</p>
<p><strong>时间轮代码</strong></p>
<pre><code class="language-go">package timewheel

import (
    "container/list"
    "fmt"
    "sync"
    "time"
)

type Timer struct {
    expiration time.Time
    task       func()
}

type TimeWheel struct {
    ticker      *time.Ticker  // 定时器
    slots       []*list.List  // 时间槽
    currentSlot int           // 当前时间槽
    slotCount   int           // 时间槽数量
    duration    time.Duration // 时间槽间隔
    lock        sync.Mutex    // 锁
}

// NewTimeWheel 创建时间轮
func NewTimeWheel(slotCount int, duration time.Duration) *TimeWheel {
    slots := make([]*list.List, slotCount)
    for i := range slots {
        slots[i] = list.New()
    }
    return &amp;TimeWheel{
        ticker:      time.NewTicker(duration),
        slots:       slots,
        slotCount:   slotCount,
        duration:    duration,
        currentSlot: 0,
    }
}

// AddTask 添加一个定时任务到时间轮
func (tw *TimeWheel) AddTask(delay time.Duration, task func()) {
    tw.lock.Lock()
    defer tw.lock.Unlock()

    expiration := time.Now().Add(delay)

    // 计算定时任务在时间轮中的到期时间, 添加到对应的时间槽
    ticks := int(delay / tw.duration)
    slotIndex := (tw.currentSlot + ticks) % tw.slotCount

    timer := &amp;Timer{expiration: expiration, task: task}
    tw.slots[slotIndex].PushBack(timer)
}

// Start 启动时间轮
func (tw *TimeWheel) Start() {
    go func() {
        defer func() {
            if err := recover(); err != nil {
                fmt.Println("timeWheel panic: ", err)
            }
        }()
        for range tw.ticker.C {
            tw.tickHandler()
        }
    }()
}

// Stop 停止时间轮
func (tw *TimeWheel) Stop() {
    tw.ticker.Stop()
}

func (tw *TimeWheel) tickHandler() {
    tw.lock.Lock()
    defer tw.lock.Unlock()

    slot := tw.slots[tw.currentSlot]
    tw.currentSlot = (tw.currentSlot + 1) % tw.slotCount

    for e := slot.Front(); e != nil; {
        next := e.Next()
        timer := e.Value.(*Timer)
        if timer.expiration.Before(time.Now()) || timer.expiration.Equal(time.Now()) {
            go timer.task()
            slot.Remove(e)
        }
        e = next
    }
}


</code></pre>
]]></content:encoded>
<slash:comments>2</slash:comments>
<comments>https://nothamor.com/archives/golangTimeWheel.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/08/03/1722669785.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>2024可用的docker镜像源</title>
<link>https://nothamor.com/archives/docker2024.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/docker2024.html</guid>
<pubDate>Tue, 02 Jul 2024 10:38:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[docker daemon.json配置：
{
    "registry-mirrors": [
        "https://docker.1panel.live",
        "https://hub.rat.dev"
    ]
}...]]></description>
<content:encoded><![CDATA[
<p>docker daemon.json配置：</p>
<pre><code>{
    "registry-mirrors": [
        "https://docker.1panel.live",
        "https://hub.rat.dev"
    ]
}</code></pre>
]]></content:encoded>
<slash:comments>2</slash:comments>
<comments>https://nothamor.com/archives/docker2024.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/07/02/1719887898.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>Golang八股面试题+大厂面经</title>
<link>https://nothamor.com/archives/144.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/144.html</guid>
<pubDate>Thu, 11 Jan 2024 17:13:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[Golang面试题
https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%E9%9D%A2%E8%AF%95%E9%A2%98.pdf
Golang大厂面经
https://nothamor...]]></description>
<content:encoded><![CDATA[
<p>Golang面试题</p>
<p><a href="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%E9%9D%A2%E8%AF%95%E9%A2%98.pdf">https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%E9%9D%A2%E8%AF%95%E9%A2%98.pdf</a></p>
<p>Golang大厂面经</p>
<p><a href="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%20%E5%A4%A7%E5%8E%82%E9%9D%A2%E7%BB%8F.zip">https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%20%E5%A4%A7%E5%8E%82%E9%9D%A2%E7%BB%8F.zip</a></p>
]]></content:encoded>
<slash:comments>3</slash:comments>
<comments>https://nothamor.com/archives/144.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/01/11/1704964373.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>Golang Gin框架中实现Server Sent Events(SSE)</title>
<link>https://nothamor.com/archives/golangGinSSE.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/golangGinSSE.html</guid>
<pubDate>Tue, 02 Jan 2024 10:18:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[被这个功能困扰了好久，一直没实现，因为网上的Gin框架实现的SSE都非常复杂，写成了一整套复杂的中间件，经过研究，写出了一套精简的实现。
SSE接口代码, 发送信息修改sendEvents内代码即可：
func ChatStreamSSE(ctx cont...]]></description>
<content:encoded><![CDATA[
<p>被这个功能困扰了好久，一直没实现，因为网上的Gin框架实现的SSE都非常复杂，写成了一整套复杂的中间件，经过研究，写出了一套精简的实现。</p>
<p>SSE接口代码, 发送信息修改sendEvents内代码即可：</p>
<pre><code>func ChatStreamSSE(ctx context.Context, c *gin.Context, req vo.ChatCompletionRequest) {
event := Event{
    ConversationId: uuid.New().String(),
    IsFinish:       false,
    IsReasoning:    false,
}

stream, err := openai.NewChatCompletionStream(ctx, req)
if err != nil {
    logger.Ex(ctx, tag, "NewChatCompletionStream:%+v", err)
    event.Content = fmt.Sprintf("new chat completion stream failed. err:[%+v]", err)
    event.IsFinish = true
    c.SSEvent("data", event)
    c.Writer.Flush()
    return
}
defer stream.Close()

for {
    streamData, streamErr := stream.Recv()
    if streamErr != nil &amp;&amp; !errors.Is(streamErr, io.EOF) &amp;&amp; streamErr.Error() != "EOF" {
        logger.Ex(ctx, tag, "stream data error: %v", streamErr)
        event.Content = fmt.Sprintf("stream data error: %v", streamErr)
        event.IsFinish = true
        c.SSEvent("data", event)
        c.Writer.Flush()
    }
    c.SSEvent("data", event)
    c.Writer.Flush()
}

return
}</code></pre>
]]></content:encoded>
<slash:comments>2</slash:comments>
<comments>https://nothamor.com/archives/golangGinSSE.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/01/02/1704162847.png" length="0" type="image/jpeg" />
</item>
<item>
<title>驾校一点通 - 理论考试爬虫</title>
<link>https://nothamor.com/archives/DriverLicenseQuestionsSpider.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/DriverLicenseQuestionsSpider.html</guid>
<pubDate>Mon, 12 Jul 2021 19:04:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[[github repo="NothAmor/DriverLicenseQuestionsSpider" /]
因为写过了很多爬虫项目，临近科目四考试，在驾考宝典刷题的时候就在想能不能把这些题目全都爬取下来，百度搜了一下发现了这个驾校一点通，因为有题目接口...]]></description>
<content:encoded><![CDATA[
<p>[github repo="NothAmor/DriverLicenseQuestionsSpider" /]</p>
<p>因为写过了很多爬虫项目，临近科目四考试，在驾考宝典刷题的时候就在想能不能把这些题目全都爬取下来，百度搜了一下发现了这个驾校一点通，因为有题目接口，而且完全没有反爬机制，一晚上就搞定了</p>
<p>直接存储所有题目到mysql数据库里，图片存储到本地文件夹内，如果这个文件夹在网站内，可以直接修改imageurl为网址存储到数据库里</p>
<p>然后写完了之后我也不知道有啥用，水篇博客吧</p>
]]></content:encoded>
<slash:comments>1</slash:comments>
<comments>https://nothamor.com/archives/DriverLicenseQuestionsSpider.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/blog/typecho/E5cm9EPXIAMkyoi.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>不支持M.2接口的旧主板使用M.2 SSD作为开机启动盘的三种方法</title>
<link>https://nothamor.com/archives/old_masterboard_use_nvme.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/old_masterboard_use_nvme.html</guid>
<pubDate>Fri, 18 Jun 2021 19:34:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[## 前言
之所以会有这个文章是因为云计算赛项中获得了国赛二等奖, 发了奖金就给老电脑更新一点配件, 暂时买了一个NVME M.2转换PCI-E的转接卡,  金士顿骇客神条DDR3 8GB 1600 * 2, 希捷2TB 5900转硬盘, 影驰256GB ...]]></description>
<content:encoded><![CDATA[
<h2>前言</h2>
<p>之所以会有这个文章是因为云计算赛项中获得了国赛二等奖, 发了奖金就给老电脑更新一点配件, 暂时买了一个NVME M.2转换PCI-E的转接卡,  金士顿骇客神条DDR3 8GB 1600 * 2, 希捷2TB 5900转硬盘, 影驰256GB NVME M.2固态硬盘</p>
<h2>起因</h2>
<p>最初我以为旧电脑中, 虽然没有NVME M.2的接口, 但是买一个转接卡转给PCI-E作为启动盘, 是没有什么问题的, 但是当我给SSD装好系统, 插上主板之后, 发现BIOS中无法选择PCI-E上的硬盘作为启动设备, 特征为不认盘, 就是在启动菜单中不显示PCI-E槽中的SSD, 经过笔者一整天的思考, 发现了三种方法解决这一个问题, 让没有M.2接口的旧主板作为启动设备</p>
<h2>三种解决方案</h2>
<ol>
<li>使用Clover这一类boot工具, 在进入boot manager后再选择SSD进行启动</li>
<li>手动修改主板BIOS, 导入NVME模块, 使BIOS支持PCI-E硬盘引导</li>
<li>将M.2 SSD转为USB, 开机使用USB作为启动设备</li>
</ol>
<h2>解决方案一: Clover</h2>
<p>这里可以直接参考这个大佬的文章, 写的很详细</p>
<p><a href="https://www.bilibili.com/read/cv5496032/">【教程】使用Clover启动Nvme协议的固态硬盘 - 哔哩哔哩 (bilibili.com)</a></p>
<h2>解决方案二: 更新主板BIOS</h2>
<p><strong>事先提醒, 刷BIOS有风险</strong></p>
<p>参考文章: <a href="https://www.52pojie.cn/thread-1136840-1-1.html">mmtool——BIOS修改工具及添加nvme模块教程 - 『精品软件区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn</a></p>
<p>如果你的主板是B85-PLUS R2.0, 那么可以用我在下面提供的BIOS进行操作, 这个是我亲测没问题的</p>
<p><a href="https://cdn.nothamor.com/B85-PLUS-R2.0-NVME(Z97).zip">https://cdn.nothamor.com/B85-PLUS-R2.0-NVME(Z97).zip</a></p>
<p>这个包的操作方法:</p>
<ol>
<li>
<p>准备一个空U盘, 新建一个BIOS文件夹, 将包中的三个文件放入其中</p>
</li>
<li>
<p>准备一个PE启动U盘/DVD, 进入纯DOS模式(一定要纯DOS模式, 大白菜就有纯DOS)</p>
</li>
<li>
<p>进入纯DOS模式后, 按照以下操作</p>
<p>首先进入C盘, 输入: <code>C:\</code>后回车</p>
<p>输入 <code>dir</code>后回车, 查看C盘下文件, 如果有BIOS文件夹, 则是正确, 如果没有BIOS文件夹, 依次输入 <code>D:\</code>等, 直到你找到BIOS文件夹</p>
<p>输入 <code>cd BIOS</code>回车进入BIOS文件夹下, 再输入 <code>dir</code>查看当前目录下的所有文件, 如果有 <code>AFUDOS.exe, NVME.ROM, START.bat</code>三个文件的话, 则为正确, 直接输入 <code>START.bat</code>回车进行BIOS的刷写, 然后等待之后BIOS就刷写好了</p>
<p>刷写好BIOS后输入 <code>r</code>回车, 进行重新启动, 这时候进入BIOS, 会发现启动设备多了一个Windows Boot Manager, 则为成功</p>
</li>
<li>
<p>这时候工作还没有做完, SSD中的系统必须按照如下要求安装</p>
<p>进入PE系统, 使用Disk Genius分区工具, 将PCI-E槽中的SSD硬盘进行格式化(格式化前备份好资料)</p>
<p>格式化后, 选中SSD硬盘, 选择上方的快速分区, 分区表类型选择 <code>GUID</code></p>
<p>下方的分区数目可以自定义, 想要分几个区, 就选几个区, 一般这个SSD都是用于引导系统, 所以分一个区就够了, 给它全部空间</p>
<p>在下方选择 <code>重建主引导记录(MBR)</code>, 在右侧高级设置中分配分区空间</p>
<p>一定要勾选右下角的 <code>对齐分区到此扇区数的整数倍</code>, 给默认值就好(2048扇区)</p>
<p>然后确定, 这时候就分区好了</p>
<p>只有PCI-E槽上的SSD需要这么分区, 其它硬盘按照正常分区方法进行分区就可以</p>
</li>
<li>
<p>在分区之后, 使用Disk Genius工具清除 <code>除了ESP, MSR分区</code>以外的 <code>所有分区盘符</code>(就是C盘, D盘这些东西, 先给清掉)</p>
</li>
<li>
<p>然后再使用Disk Genius工具, <code>给那些分区重新分配盘符</code>, 但是 <code>一定要给SSD的那个分区C盘</code>, 其它的自己分就好 <code>(ESP, MSR不用管)</code></p>
</li>
<li>
<p>然后使用老毛桃PE中的 <code>WinNTSetUp工具</code>, 进行操作系统的安装, 位置在 <code>开始菜单-&gt;所有程序-&gt;安装维护</code>中, Windows安装源选择官方Windows10镜像, 引导驱动器选择刚才的ESP分区(这个ESP分区一定要在Disk Genius中和文件管理器中确认好), 安装驱动器选择C盘, 然后单击开始安装就好了</p>
</li>
<li>
<p>安装完系统后进行重启, 重启后一定要进入BIOS确认好Windows Boot Manager在第一启动项, 确认好后按F10保存并重启</p>
</li>
<li>
<p>重启后就进行系统的配置了, 这时候就已经结束了所有步骤了, Enjoy it!</p>
</li>
</ol>
<h2>解决方案三: M.2转USB</h2>
<p>事先声明, 这个方案只是笔者想到的, 但是没有实践, 因为我使用解决方案二解决了我的问题, 但是我认为这个解决方案应该是可行的</p>
<p>就是淘宝买一个支持M.2转USB3.0的转接器, 然后插在电脑上, BIOS中选择USB启动, 选择这个USB, 应该没有问题</p>
<p>但是SSD的速度有可能被影响, 这个笔者没有试验过</p>
]]></content:encoded>
<slash:comments>6</slash:comments>
<comments>https://nothamor.com/archives/old_masterboard_use_nvme.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/E3Bs_8WXoAsOwYy.jpg" length="0" type="image/jpeg" />
</item>
</channel>
</rss>
