-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.xml
More file actions
444 lines (439 loc) · 443 KB
/
Copy pathindex.xml
File metadata and controls
444 lines (439 loc) · 443 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>chengzhycn's blog</title>
<link>https://blog.jinzhi.site/</link>
<description>Recent content on chengzhycn's blog</description>
<generator>Hugo</generator>
<language>en-us</language>
<lastBuildDate>Fri, 10 Jul 2026 08:23:23 +0800</lastBuildDate>
<atom:link href="https://blog.jinzhi.site/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>opencode memory 实现与最佳实践</title>
<link>https://blog.jinzhi.site/posts/2026-07/opencode-memory-%E5%AE%9E%E7%8E%B0%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</link>
<pubDate>Fri, 10 Jul 2026 08:23:23 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-07/opencode-memory-%E5%AE%9E%E7%8E%B0%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</guid>
<description><blockquote>
<p>注:文章由 codex 整理。</p>
</blockquote>
<h2 id="背景">背景</h2>
<p>这里的 <code>memory</code> 不是操作系统内存,也不是模型推理时的 KV cache,而是 coding agent 在多轮、多 session、多项目之间保存和复用上下文的能力。</p>
<p>截至这次分析的源码快照,<code>opencode</code> 并没有内置一个类似 <code>mem0</code>、<code>mnemopi</code> 或向量数据库的长期记忆系统。它的 memory 更接近三层组合:</p>
<ol>
<li><strong>规则型长期记忆</strong>:<code>AGENTS.md</code>、全局 <code>AGENTS.md</code>、<code>opencode.json</code> 里的 <code>instructions</code>。这些内容每轮都会进入 system prompt,是最稳定、最可审计的记忆。</li>
<li><strong>会话内压缩记忆</strong>:<code>compaction</code> agent 把长会话压缩成摘要,并保留最近若干 turn,让当前 session 可以继续推进。</li>
<li><strong>上下文型临时记忆</strong>:用户用 <code>@file</code>、MCP resource、skill、subagent 等方式临时补充上下文,主要服务当前任务。</li>
</ol>
<p>所以分析 <code>opencode memory</code> 时,关键不是找一个 <code>MemoryBackend</code>,而是看它如何把规则文件、配置、压缩摘要和临时上下文组装进模型请求。</p>
<p>本文基于:</p>
<ul>
<li>官方文档:<a href="https://opencode.ai/docs/rules/">Rules</a>、<a href="https://opencode.ai/docs/config/">Config</a>、<a href="https://opencode.ai/docs/agents/">Agents</a>、<a href="https://opencode.ai/docs/skills/">Agent Skills</a></li>
<li>源码快照:<a href="https://github.com/anomalyco/opencode/commit/251177d56cf2a8821f7e1464dd7c164e8f56036d">anomalyco/opencode@251177d56cf2a8821f7e1464dd7c164e8f56036d</a></li>
<li>重点源码文件:
<ul>
<li><code>packages/opencode/src/session/instruction.ts</code></li>
<li><code>packages/opencode/src/session/prompt.ts</code></li>
<li><code>packages/opencode/src/session/compaction.ts</code></li>
<li><code>packages/opencode/src/session/system.ts</code></li>
<li><code>packages/opencode/src/config/config.ts</code></li>
<li><code>packages/core/src/v1/config/config.ts</code></li>
</ul>
</li>
</ul>
<h2 id="核心结论">核心结论</h2>
<p><code>opencode</code> 的 memory 设计偏“显式上下文工程(context engineering)”,而不是“自动记住一切”。</p>
<p>这带来一个很重要的实践判断:<strong>应该把长期稳定的项目知识写进规则文件,把当前任务过程交给 session history 和 compaction,把低置信度、临时性、可能过期的信息留在对话里,而不是沉淀到 AGENTS.md。</strong></p>
<p>可以这样理解:</p>
<table>
	<thead>
			<tr>
					<th>层次</th>
					<th>opencode 机制</th>
					<th>适合保存什么</th>
					<th>不适合保存什么</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>长期项目记忆</td>
					<td>项目 <code>AGENTS.md</code></td>
					<td>架构边界、测试命令、代码风格、危险操作约束</td>
					<td>临时任务进度、一次性 debug 现象</td>
			</tr>
			<tr>
					<td>长期个人记忆</td>
					<td><code>~/.config/opencode/AGENTS.md</code></td>
					<td>个人偏好、默认工作方式、输出习惯</td>
					<td>某个项目的私有约定</td>
			</tr>
			<tr>
					<td>可复用规则片段</td>
					<td><code>opencode.json</code> 的 <code>instructions</code></td>
					<td>多文件规范、共享团队标准、monorepo 分包规则</td>
					<td>需要模型自己递归解析的隐式引用</td>
			</tr>
			<tr>
					<td>会话内记忆</td>
					<td><code>compaction</code></td>
					<td>当前 session 的目标、决策、已完成动作、未解决问题</td>
					<td>跨 session 的长期知识</td>
			</tr>
			<tr>
					<td>临时上下文</td>
					<td><code>@file</code>、MCP、skills、subagents</td>
					<td>本轮要读的文件、外部资料、专项流程</td>
					<td>需要每次都遵守的硬规则</td>
			</tr>
	</tbody>
</table>
<h2 id="实现总览">实现总览</h2>
<p>一轮模型调用前,<code>SessionPrompt</code> 会把几类 system prompt 片段拼起来:</p></description>
</item>
<item>
<title>Loop Engineering 最佳实践</title>
<link>https://blog.jinzhi.site/posts/2026-07/loop-engineering-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</link>
<pubDate>Sat, 04 Jul 2026 21:27:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-07/loop-engineering-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/</guid>
<description><h2 id="背景">背景</h2>
<blockquote>
<p>注:文章由 codex 整理。</p>
</blockquote>
<p><code>Loop engineering</code> 讨论的不是普通代码里的 <code>for/while</code>,也不是 agent harness 内部已经存在的 <code>observe -&gt; act -&gt; observe</code> 工具调用循环,而是<strong>人类交给 agent harness 的外部循环规格(loop specification)</strong>。</p>
<p>arXiv 论文 <a href="https://arxiv.org/abs/2607.00038">Stop Hand-Holding Your Coding Agent: Engineering the Loops that Replace Step-by-Step Prompting</a> 给出的核心定义是:<code>loop specification</code> 是一个有边界、可复用的 artifact,包含 <code>trigger</code>、<code>goal</code>、<code>verification</code>、<code>stopping rule</code> 和 <code>memory</code>,由人类交给 Claude Code、Codex 等 agent harness,让 agent 在无需人类逐步提示的情况下追踪目标、执行、检查并停止。</p>
<p>这篇论文的价值在于把社区里比较口号化的说法收束成一个工程问题:</p>
<ul>
<li><code>prompt engineering</code> 问的是:这一轮怎么问?</li>
<li><code>context engineering</code> 问的是:agent 应该知道什么?</li>
<li><code>harness engineering</code> 问的是:agent 能在什么环境里行动?</li>
<li><code>loop engineering</code> 问的是:如何设计一个系统,让 agent 能发现工作、执行工作、验证结果、记录状态,并知道什么时候停?</li>
</ul>
<p>所以它不是“prompt engineering 已死”,而是 prompt 之外多了一层控制系统。一个 loop 的底层仍然会使用 prompt,但关键杠杆从“写一句更聪明的话”变成“设计一个带反馈、验证和刹车的闭环”。</p></description>
</item>
<item>
<title>oh-my-pi memory 系统实现分析</title>
<link>https://blog.jinzhi.site/posts/2026-07/oh-my-pi-memory-%E7%B3%BB%E7%BB%9F%E5%AE%9E%E7%8E%B0%E5%88%86%E6%9E%90/</link>
<pubDate>Sat, 04 Jul 2026 11:39:09 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-07/oh-my-pi-memory-%E7%B3%BB%E7%BB%9F%E5%AE%9E%E7%8E%B0%E5%88%86%E6%9E%90/</guid>
<description><blockquote>
<p>注:文章由 codex 整理。</p>
</blockquote>
<h2 id="背景">背景</h2>
<p><code>oh-my-pi</code> 的 memory 系统不是一个单一模块,而是一层 <code>MemoryBackend</code> 抽象下面挂了几种不同的实现。它要解决的问题是:Agent 的上下文窗口是短期的,但用户偏好、项目约定、设计决策、踩坑经验应该跨 session 保留下来,并能在未来对话中重新进入模型上下文。</p>
<p>从实现上看,<code>oh-my-pi</code> 把 memory 拆成三个问题:</p>
<ol>
<li><strong>记忆写到哪里</strong>:本地 Markdown 摘要、本地 SQLite、还是远端 Hindsight 服务。</li>
<li><strong>什么时候写入</strong>:工具显式 <code>retain</code>,还是每 N 轮自动保留 transcript。</li>
<li><strong>什么时候读出</strong>:启动时注入摘要,首轮 prompt 前自动 recall,或者由模型主动调用 <code>recall</code> / <code>reflect</code>。</li>
</ol>
<h2 id="总体架构">总体架构</h2>
<p>memory backend 的公共接口定义在:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">packages/coding-agent/src/memory-backend/types.ts
</span></span><span class="line"><span class="cl">packages/coding-agent/src/memory-backend/resolve.ts
</span></span></code></pre></div><p><code>resolveMemoryBackend(settings)</code> 根据 <code>memory.backend</code> 选择具体实现:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">memory.backend = off -&gt; offBackend
</span></span><span class="line"><span class="cl">memory.backend = local -&gt; localBackend
</span></span><span class="line"><span class="cl">memory.backend = hindsight -&gt; hindsightBackend
</span></span><span class="line"><span class="cl">memory.backend = mnemopi -&gt; mnemopiBackend
</span></span></code></pre></div><p><code>MemoryBackend</code> 的关键 hook 是:</p>
<ul>
<li><code>start()</code>:session 启动时建立运行时状态、订阅 session event。</li>
<li><code>buildDeveloperInstructions()</code>:构造要注入系统提示词的 memory 文本。</li>
<li><code>beforeAgentStartPrompt()</code>:在当前 turn 生成前追加一次性 recall 结果,保证首轮就能吃到记忆。</li>
<li><code>enqueue()</code>:强制触发 retain / consolidation。</li>
<li><code>clear()</code>:清理当前 backend 的持久化状态。</li>
<li><code>status()</code> / <code>search()</code> / <code>save()</code> / <code>stats()</code> / <code>diagnose()</code>:给 UI、slash command、extension 使用。</li>
</ul>
<p>三种真实 backend 的定位不同:</p></description>
</item>
<item>
<title>Claude Code auto-mode 实现分析</title>
<link>https://blog.jinzhi.site/posts/2026-07/claude-code-auto-mode-%E5%AE%9E%E7%8E%B0%E5%88%86%E6%9E%90/</link>
<pubDate>Fri, 03 Jul 2026 14:47:05 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-07/claude-code-auto-mode-%E5%AE%9E%E7%8E%B0%E5%88%86%E6%9E%90/</guid>
<description><blockquote>
<p>注:文章由 codex 整理。</p>
</blockquote>
<h2 id="背景">背景</h2>
<p>本文分析的是 <code>chengzhycn/claude-code</code> 在提交 <code>4b9d30f7953273e567a18eb819f4eddd45fcc877</code> 上的源码实现。</p>
<p>源码入口:</p>
<ul>
<li>仓库:https://github.com/chengzhycn/claude-code/tree/4b9d30f7953273e567a18eb819f4eddd45fcc877</li>
<li>权限模式定义:<code>src/utils/permissions/PermissionMode.ts</code></li>
<li>auto-mode 状态切换:<code>src/utils/permissions/permissionSetup.ts</code></li>
<li>工具权限主流程:<code>src/utils/permissions/permissions.ts</code></li>
<li>auto-mode classifier:<code>src/utils/permissions/yoloClassifier.ts</code></li>
<li>Bash 权限判定:<code>src/tools/BashTool/bashPermissions.ts</code></li>
<li>Bash 只读命令判定:<code>src/tools/BashTool/readOnlyValidation.ts</code></li>
<li>Bash 注入/语法安全检查:<code>src/tools/BashTool/bashSecurity.ts</code></li>
</ul>
<p>核心问题是:Claude Code 怎么在“不每一步都问用户”和“不直接 bypass permissions”之间做折中。源码里的 auto-mode 不是一个简单的全局 allow,而是一套组合机制:</p>
<ol>
<li>进入 auto-mode 时先检查 feature gate 和 opt-in。</li>
<li>临时剥离会绕过 classifier 的危险 allow rule。</li>
<li>工具调用先走原有本地权限系统。</li>
<li>对本地系统仍然需要 <code>ask</code> 的动作,再交给 auto-mode classifier 判断 allow/block。</li>
<li>classifier 不可用、输出不可解析或 transcript 太长时,按场景 fail closed 或回退到人工 permission prompt。</li>
</ol>
<h2 id="模型">模型</h2>
<p>auto-mode 可以理解成一个“permission prompt 的自动代理”,而不是新的沙箱。它的安全性来自三层:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">工具自己的权限检查
</span></span><span class="line"><span class="cl"> -&gt; 本地静态安全规则:路径、只读命令、deny/ask/allow rule、shell 注入模式
</span></span><span class="line"><span class="cl"> -&gt; auto-mode 快速路径:acceptEdits 可允许的操作、安全工具 allowlist
</span></span><span class="line"><span class="cl"> -&gt; auto-mode classifier:读取 transcript + 当前 action,输出 allow/block
</span></span></code></pre></div><p>这意味着:</p></description>
</item>
<item>
<title>HTTP2 流量控制</title>
<link>https://blog.jinzhi.site/posts/2026-06/http2-%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6/</link>
<pubDate>Sun, 14 Jun 2026 21:32:31 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-06/http2-%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6/</guid>
<description><h2 id="核心问题">核心问题</h2>
<blockquote>
<p>注:文章由 codex 整理。</p>
</blockquote>
<p>HTTP/2 的一个核心收益是 multiplexing:多个 request/response 可以复用同一条 TCP connection,并且不同 stream 的 frame 可以交错发送。这个能力解决了 HTTP/1.x 依赖多连接并发的问题,但也引入了一个新问题:<strong>如果某个接收方、某个 stream 或某个应用层消费者处理不过来,发送方应该怎样被限制,而不至于把整个连接的内存打爆?</strong></p>
<p>HTTP/2 flow control 解决的就是这个问题。它不是拥塞控制(congestion control),也不是优先级调度(prioritization),而是一个接收方驱动的背压机制:</p>
<ul>
<li>TCP 拥塞控制关心网络路径能承受多少数据。</li>
<li>HTTP/2 flow control 关心接收端应用和协议栈愿意接收多少 <code>DATA</code> payload。</li>
<li>HTTP/2 priority 关心多个 stream 竞争发送机会时先发谁。</li>
</ul>
<p>可以把它理解为:TCP 管路决定“路上能跑多少车”,HTTP/2 flow control 决定“每个收货口和总仓库还能接多少货”。</p>
<h2 id="基本模型">基本模型</h2>
<p>HTTP/2 的流量控制基于 credit/window:</p>
<ol>
<li>接收方声明自己还能接收多少字节。</li>
<li>发送方发送 <code>DATA</code> frame 时消耗窗口。</li>
<li>接收方消费掉数据、释放缓冲后,通过 <code>WINDOW_UPDATE</code> 把窗口加回来。</li>
<li>如果窗口耗尽,发送方必须暂停发送受流控约束的数据。</li>
</ol>
<p>这里有两个层级的窗口:</p>
<table>
	<thead>
			<tr>
					<th>窗口</th>
					<th>作用范围</th>
					<th>用来限制什么</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>stream flow-control window</td>
					<td>单个 stream</td>
					<td>某个 request/response body 的发送量</td>
			</tr>
			<tr>
					<td>connection flow-control window</td>
					<td>整条 HTTP/2 connection</td>
					<td>所有 stream 的 <code>DATA</code> frame 总发送量</td>
			</tr>
	</tbody>
</table>
<p>发送一个 <code>DATA</code> frame 之前,发送方必须同时检查两个窗口:<strong>frame payload 长度不能超过 stream window,也不能超过 connection window</strong>。发送后,这两个窗口都会按 payload 长度扣减。注意,HTTP/2 frame header 的 9 个字节不计入流控窗口。</p></description>
</item>
<item>
<title>算力×电力:算电协同的商业模式与投资逻辑</title>
<link>https://blog.jinzhi.site/posts/2026-06/%E7%AE%97%E5%8A%9B%E7%94%B5%E5%8A%9B%E7%AE%97%E7%94%B5%E5%8D%8F%E5%90%8C%E7%9A%84%E5%95%86%E4%B8%9A%E6%A8%A1%E5%BC%8F%E4%B8%8E%E6%8A%95%E8%B5%84%E9%80%BB%E8%BE%91/</link>
<pubDate>Mon, 08 Jun 2026 12:32:50 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-06/%E7%AE%97%E5%8A%9B%E7%94%B5%E5%8A%9B%E7%AE%97%E7%94%B5%E5%8D%8F%E5%90%8C%E7%9A%84%E5%95%86%E4%B8%9A%E6%A8%A1%E5%BC%8F%E4%B8%8E%E6%8A%95%E8%B5%84%E9%80%BB%E8%BE%91/</guid>
<description><blockquote>
<p>注:文章由 codex 整理。</p>
</blockquote>
<h2 id="节目--嘉宾--来源">节目 / 嘉宾 / 来源</h2>
<ul>
<li>节目:大方谈钱</li>
<li>单集:算力×电力:算电协同的商业模式、机会与投资逻辑</li>
<li>主持:梁狗蛋</li>
<li>嘉宾:张浩,华源证券研究所所长助理、电力公用首席</li>
<li>来源:https://www.xiaoyuzhoufm.com/episode/69fddfe3e1eb34a939fe8bf0</li>
<li>时长:52:02</li>
<li>发布时间:2026-06-08</li>
</ul>
<p>这一期讨论的是 <code>算电协同</code>:当 AI 数据中心(AIDC)的主要运营变量变成电力成本、稳定供电和新能源消纳之后,电力就不再只是算力产业的后台基础设施,而会进入算力商业模式、区域资源禀赋和投资定价逻辑里。</p>
<p>这期的价值,不在于提供几个“算力 + 电力”的概念标的,而在于把这个题材拆回一个可以检查的商业问题:</p>
<ul>
<li>谁能拿到低成本、可持续、可调度的电?</li>
<li>谁能把风、光、储、负荷组织成一个能稳定运行的小系统?</li>
<li>谁能绑定真实算力客户,让 IDC 上架率和利用小时兑现?</li>
<li>这几个条件合在一起,能不能转化成真实利润,而不是只停留在题材重估?</li>
</ul>
<h2 id="核心启发">核心启发</h2>
<p>算电协同的核心,不是“AI 用电多,所以电力公司受益”这么简单。</p>
<p>更准确地说,它是一种把低成本电力资源重新打包成算力现金流的方式。电本身很难跨区域、跨国定价,电力公司也常被视为低增长、低估值、防守型资产;但如果电被转化成 AI 训练、推理、算力租赁或 <code>token</code> 服务,它就可能进入另一套定价体系。</p>
<p>这期最重要的启发是:**算电协同不是一个产业链无限延伸的故事,而是一个资源整合故事。**真正值钱的环节,不一定是风光储设备,也不一定是调度软件,而是能同时拿到三类资源的主体:</p>
<ul>
<li>优质电源资源,尤其是高利用小时的风电资源;</li>
<li>真实算力客户,最好能和阿里、字节等需求方形成长期绑定;</li>
<li>项目组织能力,能把电源、储能、负荷和调度系统做成有经济性的整体。</li>
</ul>
<p>所以,看这个方向时,不能只问“是不是算力概念”,而要问“它到底赚谁的钱、靠什么资源赚、这个利润能不能长期留在公司里”。</p>
<h2 id="认知校正">认知校正</h2>
<h3 id="1-算电协同不是新发明而是火铝协同的绿色版本">1. 算电协同不是新发明,而是“火铝协同”的绿色版本</h3>
<p>嘉宾用山东魏桥的电解铝业务做类比。电解铝是典型高耗能产业,魏桥过去的核心竞争力之一,是自建燃煤电厂,通过专线给电解铝电解槽供电,降低过网费、系统备用费和部分附加成本,从而获得更低用电成本。</p>
<p>算电协同的底层模式类似,只是协同对象从“火电 + 电解铝”变成“绿电 + 算力”。如果给这种模式一个更朴素的定义,它就是:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">高利用小时电源 + 高负荷率用户 + 局部调度系统 = 更低的单位用电成本
</span></span></code></pre></div><p>这里的“绿电”可以是风电、光伏、垃圾焚烧,远期也可能扩展到核电。这里的“算力”主要是 IDC 或 AIDC。真正的变化不是技术名词,而是电力体制和项目组织方式允许这两端更紧地连接。</p>
<h3 id="2-idc-是电力系统里的优质负荷">2. IDC 是电力系统里的优质负荷</h3>
<p>电力系统喜欢稳定用户。</p>
<p>线路、变压器、发电机组都要按照最大需求配置。如果一个用户一年只有几天达到高峰,其他时间需求很低,供电侧仍然要按高峰准备容量,但大部分时间资产闲置,投资回收就很差。</p>
<p>IDC 和电解铝都是相对优质的负荷。全年 8760 小时里,它们可能运行 6000-7000 小时以上,用电曲线更平稳,电费在运营成本中占比也高。对电力系统来说,这类用户有几个好处:</p></description>
</item>
<item>
<title>普通人如何读财报</title>
<link>https://blog.jinzhi.site/posts/2026-06/%E6%99%AE%E9%80%9A%E4%BA%BA%E5%A6%82%E4%BD%95%E8%AF%BB%E8%B4%A2%E6%8A%A5/</link>
<pubDate>Sun, 07 Jun 2026 18:19:23 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-06/%E6%99%AE%E9%80%9A%E4%BA%BA%E5%A6%82%E4%BD%95%E8%AF%BB%E8%B4%A2%E6%8A%A5/</guid>
<description><blockquote>
<p>注:文章由 codex 整理。</p>
</blockquote>
<h2 id="节目--嘉宾--来源">节目 / 嘉宾 / 来源</h2>
<ul>
<li>节目:一劳永逸</li>
<li>单集:EP86 面子、底子、日子:财报只讲这三件事</li>
<li>主播:麦迪森、麦杰克</li>
<li>来源:https://www.xiaoyuzhoufm.com/episode/69d440fdb977fb2c473f6d08</li>
</ul>
<p>这一期讨论的是普通人如何读上市公司财报。它没有把财报讲成会计考试,而是把三张核心报表压缩成一个很容易记住的框架:利润表是企业的“面子”,资产负债表是企业的“底子”,现金流量表是企业的“日子”。</p>
<p>这套说法的价值在于,它把财报从“很多数字”还原成三个连续问题:</p>
<ul>
<li>这家公司看起来赚不赚钱?</li>
<li>它靠什么资产、负债和商业模式在赚钱?</li>
<li>它有没有真金白银支撑自己继续活下去?</li>
</ul>
<h2 id="核心启发">核心启发</h2>
<p>财报不是用来证明一家企业多好,而是用来检验企业讲的故事哪里可能站不住。</p>
<p>很多投资者看财报,第一反应是找“超预期”“净利润增长”“营收创新高”这类显眼数字。但单个数字很容易误导人。营收可以靠赊销冲出来,利润可以被会计处理美化,存货可以暂时躺在账上不暴露亏损,甚至审计意见也可能因为信息不充分而滞后。</p>
<p>更稳妥的读法是把财报当成排雷工具,而不是选股神器。它未必能直接告诉你哪家公司会成为十倍股,但能帮助你避开一些明显结构不对、现金流吃紧、资产质量可疑、审计风险上升的公司。</p>
<p>这期最值得保留下来的判断是:<strong>普通人不需要把自己训练成注册会计师,但需要知道哪些问题值得问,哪些信号不该忽略。</strong></p>
<h2 id="三张表面子底子和日子">三张表:面子、底子和日子</h2>
<h3 id="利润表面子是赚没赚钱">利润表:面子是赚没赚钱</h3>
<p>利润表回答的是一家公司在一段时间里“收入多少、成本多少、最后赚了多少”。用一家奶茶店类比,营业收入是卖奶茶收到的钱,扣掉茶、奶、杯子等材料成本后得到毛利,再扣掉房租、员工、水电、广告等运营费用,最后交完税,才是净利润。</p>
<p>所以看利润表不能只看收入,还要看毛利率和净利率:</p>
<ul>
<li>收入高但毛利率低,说明公司可能在做苦活、累活,规模很大但赚钱效率一般。</li>
<li>毛利率高,往往说明公司有定价权、品牌、技术壁垒、渠道优势,或处在供不应求的位置。</li>
<li>净利润率低,可能是运营费用、研发费用、销售费用、利息费用或税费吞掉了毛利。</li>
</ul>
<p>利润表是“面子”,因为它最容易被市场拿来讨论,也最容易被截图传播。但面子光鲜不代表企业一定健康。</p>
<h3 id="资产负债表底子是家底和杠杆">资产负债表:底子是家底和杠杆</h3>
<p>资产负债表的核心公式是:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">资产 = 负债 + 所有者权益
</span></span></code></pre></div><p>它回答的不是“这一年赚了多少”,而是“这家公司现在有什么、欠了什么、真正属于股东的还有多少”。</p>
<p>节目里用买房做类比:一套房子价值 100 万,其中首付 30 万、银行贷款 70 万。总资产看起来是 100 万,但真正属于自己的净资产只有 30 万。如果一个企业看起来资产很大,但大部分靠负债撑起来,它的抗风险能力就和“总资产很大”不是一回事。</p>
<p>资产负债表适合用来判断:</p>
<ul>
<li>公司是轻资产还是重资产;</li>
<li>固定资产、在建工程、存货、应收账款占比是否异常;</li>
<li>负债结构是否过重,是否存在短债长投;</li>
<li>净资产是否足以承受周期波动。</li>
</ul>
<h3 id="现金流量表日子是真钱有没有进来">现金流量表:日子是真钱有没有进来</h3>
<p>现金流量表回答的是“钱有没有真的进出口袋”。这是很多人读财报时最容易忽略,却最能暴露企业真实状态的一张表。</p>
<p>利润表遵循权责发生制:只要业务完成,即使钱还没收到,也可以确认收入。现金流量表更接近收付实现制:只有真金白银流入公司账户,才算现金流入。</p>
<p>因此,“赚钱”和“有钱”是两回事。</p>
<p>一家广告公司可以在利润表上确认 1000 万收入和 700 万毛利,但如果客户迟迟不付款,公司账上没有现金,员工工资、供应商款项、贷款利息照样会压过来。此时利润表好看,现金流量表难看,说明企业过的是紧巴巴的日子。</p>
<p>这也是为什么看公司时要特别关注:</p>
<ul>
<li>经营活动现金流是否长期为正;</li>
<li>现金流是否跟得上利润;</li>
<li>自由现金流(Free Cash Flow)是否足够支持再投资、分红、回购或偿债;</li>
<li>现金流改善是否来自真实经营,而不是拖欠供应商、融资输血或出售资产。</li>
</ul>
<h2 id="英伟达和中芯国际同一行业不同财报命运">英伟达和中芯国际:同一行业,不同财报命运</h2>
<p>这一期用英伟达和中芯国际做对比,最有价值的地方不是判断哪家公司“好”或“不好”,而是说明:<strong>财报必须放回商业模式里读。</strong></p></description>
</item>
<item>
<title>复盘 2026 年 6 月纳指大跌</title>
<link>https://blog.jinzhi.site/posts/2026-06/%E5%A4%8D%E7%9B%98-2026-%E5%B9%B4-6-%E6%9C%88%E7%BA%B3%E6%8C%87%E5%A4%A7%E8%B7%8C/</link>
<pubDate>Sun, 07 Jun 2026 12:51:19 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-06/%E5%A4%8D%E7%9B%98-2026-%E5%B9%B4-6-%E6%9C%88%E7%BA%B3%E6%8C%87%E5%A4%A7%E8%B7%8C/</guid>
<description><h1 id="复盘-2026-年-6-月纳指大跌">复盘 2026 年 6 月纳指大跌</h1>
<blockquote>
<p>注:文章由 codex 整理。</p>
</blockquote>
<blockquote>
<p>信息截至 2026-06-07(北京时间)。这篇笔记只做市场复盘和观察框架,不构成投资建议。</p>
</blockquote>
<h2 id="核心问题">核心问题</h2>
<p>2026-06-01 到 2026-06-05 这一周,纳斯达克综合指数(Nasdaq Composite)结束此前由 AI、半导体和大型科技股推动的强势行情,周五单日下跌约 4.18%,全周下跌约 4.7%。这次下跌看起来不像单一公司利空,而更像几个压力点在同一天共振:</p>
<ol>
<li>前期 AI / 半导体交易过于拥挤,获利盘集中撤退。</li>
<li>5 月非农就业数据强于预期,推高了市场对美联储(Federal Reserve)更鹰派的担忧。</li>
<li>利率上行会压低高久期成长股的估值,尤其是对未来现金流定价很高的 AI 相关股票。</li>
<li>中东局势和油价扰动让通胀路径更不稳定,进一步削弱“很快降息”的叙事。</li>
</ol>
<p>当前最重要的问题不是“纳指是不是见顶”,而是:<strong>这次下跌只是拥挤交易的快速去杠杆,还是利率、盈利和 AI 资本开支预期同时重新定价的开始。</strong></p>
<h2 id="已确认的事实">已确认的事实</h2>
<h3 id="1-跌幅与市场结构">1. 跌幅与市场结构</h3>
<p>Reuters 在 2026-06-05 的市场报道中记录:</p>
<ul>
<li>Nasdaq Composite 当日下跌 <code>4.18%</code>,Nasdaq 100 下跌 <code>4.77%</code>,S&amp;P 500 下跌 <code>2.64%</code>。</li>
<li>这是纳指自 2025 年 4 月关税冲击以来最大单日百分比跌幅。</li>
<li>下跌集中在芯片股和此前涨幅较大的科技股,Philadelphia Semiconductor Index 出现自 2020 年 3 月以来最大单日跌幅,并抹去超过 1 万亿美元市值。</li>
<li>S&amp;P 500 结束连续 9 周周线收涨,这是此前市场强势的重要背景。</li>
</ul>
<p><img src="https://blog.jinzhi.site/images/notes/%E5%A4%8D%E7%9B%98-2026-%E5%B9%B4-6-%E6%9C%88%E7%BA%B3%E6%8C%87%E5%A4%A7%E8%B7%8C/nasdaq-2026-selloff.svg" alt="nasdaq-2026-selloff"></p></description>
</item>
<item>
<title>FOMO:当我们谈论踏空时,我们在谈论什么</title>
<link>https://blog.jinzhi.site/posts/2026-06/fomo%E5%BD%93%E6%88%91%E4%BB%AC%E8%B0%88%E8%AE%BA%E8%B8%8F%E7%A9%BA%E6%97%B6%E6%88%91%E4%BB%AC%E5%9C%A8%E8%B0%88%E8%AE%BA%E4%BB%80%E4%B9%88/</link>
<pubDate>Sun, 07 Jun 2026 12:16:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-06/fomo%E5%BD%93%E6%88%91%E4%BB%AC%E8%B0%88%E8%AE%BA%E8%B8%8F%E7%A9%BA%E6%97%B6%E6%88%91%E4%BB%AC%E5%9C%A8%E8%B0%88%E8%AE%BA%E4%BB%80%E4%B9%88/</guid>
<description><blockquote>
<p>注:文章由 codex 整理。</p>
</blockquote>
<h2 id="节目--嘉宾--来源">节目 / 嘉宾 / 来源</h2>
<ul>
<li>节目:大方谈钱</li>
<li>单集:FOMO: 当我们在谈论踏空时,我们在谈论什么</li>
<li>嘉宾:杨天南</li>
<li>来源:https://www.xiaoyuzhoufm.com/episode/6a09c334e1eb34a93991c25e</li>
<li>时长:53:55</li>
</ul>
<p>这期讨论的是投资中的 <code>FOMO</code>(Fear of Missing Out,错失恐惧):当市场出现局部暴涨、社交媒体开始晒收益、身边人好像都抓住了机会时,为什么“没赚到”会比“亏钱”更折磨人,以及普通投资者如何避免因为踏空焦虑而追高、加杠杆或买入结构上不利的产品。</p>
<h2 id="核心启发">核心启发</h2>
<p>这期的价值,不在于复盘“这轮行情谁涨了、谁踏空了”,而在于把踏空焦虑拆成一套可以自检的投资决策系统。</p>
<p>看到踏空时,人很容易把问题理解成“为什么没买”。更准确的问题应该是:</p>
<ul>
<li>后悔的是一个真实可执行的决策,还是一个事后构造出来的完美剧本?</li>
<li>缺的是发现机会的能力,还是仓位、持有、退出这些执行能力?</li>
<li>当前资产结构是否天然会让投资者在每次热点行情里情绪失衡?</li>
<li>是否把社交媒体上的极端样本,当成了自己的收益基准?</li>
</ul>
<p>这期的核心启发是:**FOMO 不只是情绪问题,也是一种资产结构、决策流程和信息环境共同作用的结果。**这样处理之后,解决方案就不是“忍住别焦虑”,而是重新设计配置方式、下注方式和复盘方式。</p>
<h2 id="认知校正">认知校正</h2>
<h3 id="1-踏空不是损失而是想象中的最优路径">1. 踏空不是损失,而是想象中的最优路径</h3>
<p>踏空之所以比亏损更折磨人,是因为它没有明确边界。</p>
<p>亏损至少是账户里的现实数字,亏了多少就是多少。踏空则很容易变成一个理想化的平行世界:最低点买入,仓位足够重,中途没有卖飞,最后又在高点附近退出。</p>
<p>这个平行世界最大的问题,是它跳过了真实执行链里的难点:</p>
<ul>
<li>当时是否真的敢买?</li>
<li>买了之后仓位是否足够?</li>
<li>涨 20%、50%、100% 时是否能继续拿住?</li>
<li>中途回撤时是否会被洗出去?</li>
<li>什么时候卖出,卖出后是否还会后悔?</li>
</ul>
<p>所以复盘踏空时,不能只问“为什么没买”,还要问:“如果当时真的买了,大概率会在哪一步掉队?”</p>
<h3 id="2-看见趋势不等于拥有收益">2. 看见趋势不等于拥有收益</h3>
<p>节目里很有用的一个区分是:看到、买到、买够、拿住、卖出,是五种不同能力。</p>
<p>很多大机会不是完全看不见。AI、机器人、光模块、房地产、新能源,这类趋势出现时,往往有很强的公共信号。真正拉开差距的不是“有没有听说过”,而是后面有没有持续跟踪、修正判断、建立信念、调整仓位。</p>
<p>这可以理解为:</p>
<ul>
<li><code>prior</code>(先验):判断某个方向可能重要。</li>
<li><code>Bayesian update</code>(贝叶斯更新):持续观察证据,判断这个方向是否在兑现。</li>
<li><code>position sizing</code>(仓位管理):把判断转化成合适的风险暴露。</li>
<li><code>holding discipline</code>(持有纪律):知道什么变化会增强或削弱原判断。</li>
</ul>
<p>这里的提醒是:不要把“也知道这个方向”误认为“本来应该赚到这笔钱”。知道只是入口,不是收益权。</p>
<h3 id="3-社交媒体不是收益分布而是极端样本展览">3. 社交媒体不是收益分布,而是极端样本展览</h3>
<p>社交媒体会严重扭曲投资者对市场收益的体感。普通收益没有传播性,只有极端赚钱、极端亏钱、极端戏剧化的内容容易被推到眼前。</p>
<p>这意味着屏幕上看到的不是市场真实分布,而是一个经过传播机制筛选后的极端样本集合。它会带来两种误判:</p>
<ul>
<li>误以为“人人都赚到了”,从而低估市场里大多数人的普通结果。</li>
<li>把别人的最大收益当作自己的最低合格线,从而高估自己应该承担的风险。</li>
</ul>
<p>看到收益截图时,应该先把它当成噪音样本,而不是当成配置参考。</p>
<h3 id="4-投资结果短期内不按认知严格排序">4. 投资结果短期内不按认知严格排序</h3>
<p>节目里提到一种很真实的痛苦:看到一个“好像没那么懂”的人赚得更多。</p>
<p>这背后是对投资结果的误解。短期收益不是对认知水平的精确打分,它还受到运气、仓位、周期位置、交易纪律、风险偏好和退出时点影响。认知更深的人,可能因为更保守而仓位不足;认知较浅的人,可能因为更早行动、更敢下注而阶段性收益更高。</p>
<p>更稳妥的做法,是不把短期收益当作对个人能力的证明或否定,而是复盘自己的投资系统是否可持续。</p>
<h2 id="可迁移框架">可迁移框架</h2>
<h3 id="框架一把后悔拆成执行链">框架一:把后悔拆成“执行链”</h3>
<p>当再次出现“错过了某某机会”的想法,可以按下面这条链条拆:</p></description>
</item>
<item>
<title>股票常见指标与公司财务分析</title>
<link>https://blog.jinzhi.site/posts/2026-05/%E8%82%A1%E7%A5%A8%E5%B8%B8%E8%A7%81%E6%8C%87%E6%A0%87%E4%B8%8E%E5%85%AC%E5%8F%B8%E8%B4%A2%E5%8A%A1%E5%88%86%E6%9E%90/</link>
<pubDate>Fri, 29 May 2026 07:48:49 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-05/%E8%82%A1%E7%A5%A8%E5%B8%B8%E8%A7%81%E6%8C%87%E6%A0%87%E4%B8%8E%E5%85%AC%E5%8F%B8%E8%B4%A2%E5%8A%A1%E5%88%86%E6%9E%90/</guid>
<description><h1 id="股票常见指标与公司财务分析">股票常见指标与公司财务分析</h1>
<blockquote>
<p>这篇笔记用于理解股票软件里常见指标的含义,并把它们组织成一个分析公司财务数据的框架。指标只能帮助形成判断,不构成投资建议。</p>
</blockquote>
<h2 id="1-行情与交易指标">1. 行情与交易指标</h2>
<table>
	<thead>
			<tr>
					<th>指标</th>
					<th>含义</th>
					<th>分析时怎么看</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>现价</td>
					<td>当前或最近成交价格</td>
					<td>只反映市场当下报价,不能单独说明公司贵不贵。</td>
			</tr>
			<tr>
					<td>今开</td>
					<td>当日开盘价</td>
					<td>和昨收、现价一起看,判断当天情绪强弱。</td>
			</tr>
			<tr>
					<td>昨收</td>
					<td>上一个交易日收盘价</td>
					<td>是计算当日涨跌幅的基准。</td>
			</tr>
			<tr>
					<td>最高 / 最低</td>
					<td>当日最高价、最低价</td>
					<td>用来观察盘中波动区间。</td>
			</tr>
			<tr>
					<td>涨跌额 / 涨跌幅</td>
					<td>相对昨收上涨或下跌的金额、比例</td>
					<td>涨跌幅体现短期价格变化,不能直接等同于基本面变化。</td>
			</tr>
			<tr>
					<td>成交量</td>
					<td>成交的股票数量</td>
					<td>放量上涨可能表示买盘增强;放量下跌可能表示抛压加大。需结合位置和消息看。</td>
			</tr>
			<tr>
					<td>成交额</td>
					<td>成交金额</td>
					<td>比成交量更适合比较不同股价股票的交易活跃度。</td>
			</tr>
			<tr>
					<td>换手率</td>
					<td>成交量 / 流通股本</td>
					<td>反映流通筹码被交易的频繁程度。高换手通常代表分歧大或关注度高。</td>
			</tr>
			<tr>
					<td>振幅</td>
					<td>当日最高价与最低价相对昨收的波动幅度</td>
					<td>振幅越大,短期不确定性越高。</td>
			</tr>
			<tr>
					<td>量比</td>
					<td>当前成交速度与过去平均成交速度的比值</td>
					<td>量比大于 1 表示成交比平时活跃;极高时要警惕情绪交易。</td>
			</tr>
			<tr>
					<td>52 周最高 / 最低</td>
					<td>最近一年股价高点和低点</td>
					<td>用于观察价格所处历史区间,但不代表估值合理。</td>
			</tr>
	</tbody>
</table>
<h2 id="2-估值指标">2. 估值指标</h2>
<table>
	<thead>
			<tr>
					<th>指标</th>
					<th>含义</th>
					<th>公式或口径</th>
					<th>分析时怎么看</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>总市值</td>
					<td>公司所有股份的市场价值</td>
					<td>股价 × 总股本</td>
					<td>衡量市场给公司的整体定价。</td>
			</tr>
			<tr>
					<td>流通市值</td>
					<td>可交易股份对应的市场价值</td>
					<td>股价 × 流通股本</td>
					<td>更贴近二级市场实际可交易规模。</td>
			</tr>
			<tr>
					<td>市盈率 TTM</td>
					<td>当前市值 / 最近 12 个月净利润</td>
					<td>TTM = trailing twelve months</td>
					<td>适合盈利稳定公司;亏损公司市盈率通常无意义。</td>
			</tr>
			<tr>
					<td>市盈率 LYR</td>
					<td>当前市值 / 上一财年净利润</td>
					<td>LYR = last year report</td>
					<td>受上一财年一次性因素影响较大。</td>
			</tr>
			<tr>
					<td>预测市盈率</td>
					<td>当前市值 / 预测未来净利润</td>
					<td>通常基于分析师一致预期</td>
					<td>适合成长股,但依赖预测质量。</td>
			</tr>
			<tr>
					<td>市销率 TTM</td>
					<td>当前市值 / 最近 12 个月收入</td>
					<td>Price to Sales</td>
					<td>常用于尚未稳定盈利的成长公司。越高表示市场对未来增长预期越强。</td>
			</tr>
			<tr>
					<td>市净率</td>
					<td>当前市值 / 净资产</td>
					<td>Price to Book</td>
					<td>常用于银行、保险、周期制造等重资产行业;轻资产软件公司参考价值较弱。</td>
			</tr>
	</tbody>
</table>
<h3 id="估值指标的核心问题">估值指标的核心问题</h3>
<p>估值指标本质上是在问:现在的价格,买的是多少利润、多少收入、多少资产,以及这些利润和收入未来能不能继续增长。</p></description>
</item>
<item>
<title>Envoy Go HTTP Filter</title>
<link>https://blog.jinzhi.site/posts/2026-04/envoy-go-http-filter/</link>
<pubDate>Tue, 28 Apr 2026 15:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-04/envoy-go-http-filter/</guid>
<description><p>Go HTTP 过滤器 (<code>envoy.filters.http.golang</code>) 允许使用 Go 编写 Envoy HTTP 过滤器。Go 代码被编译为共享库 (<code>.so</code>),在运行时通过 <code>dlopen</code> 加载,并借由 CGo bridge 集成到 Envoy 的 C++ 过滤器链中。</p>
<h2 id="架构">架构</h2>
<p>Go HTTP Filter 主要分为两端:</p>
<ul>
<li><strong>C++ 端</strong>:负责 DSO 加载、过滤器链集成以及线程安全。</li>
<li><strong>Go 端</strong>:通过 Go SDK 实现过滤器逻辑,通过 CGo 与 C++ 通信。</li>
</ul>
<p>ABI 边界由 <code>contrib/golang/common/go/api/api.h</code> 中的共享 C 结构体定义。</p>
<p><img src="https://blog.jinzhi.site/images/notes/envoy-go-http-filter/1d4a997271052a5a6344e5235da18b13_MD5.jpeg" alt="1d4a997271052a5a6344e5235da18b13_MD5"></p>
<h2 id="dso-动态共享对象-加载机制">DSO (动态共享对象) 加载机制</h2>
<p>Go HTTP 过滤器通过 <code>contrib/golang/common/dso/</code> 中定义的专用 DSO 机制加载 Go 编译的共享库。这与 Envoy 核心扩展中的 <code>dynamic_modules</code> 是两套独立的实现。</p>
<h3 id="核心组件">核心组件</h3>
<h4 id="1-dso-管理器-dsomanager">1. DSO 管理器 (DsoManager)</h4>
<p><code>DsoManager</code> 类 (<code>contrib/golang/common/dso/dso.h</code>) 负责管理共享库的生命周期:</p></description>
</item>
<item>
<title>nano-vllm 代码流程</title>
<link>https://blog.jinzhi.site/posts/2026-01/nano-vllm-%E4%BB%A3%E7%A0%81%E6%B5%81%E7%A8%8B/</link>
<pubDate>Tue, 27 Jan 2026 09:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-01/nano-vllm-%E4%BB%A3%E7%A0%81%E6%B5%81%E7%A8%8B/</guid>
<description><h2 id="整体架构图">整体架构图</h2>
<pre tabindex="0"><code>┌─────────────────────────────────────────────────────────────────┐
│ 用户入口层 │
│ example.py → LLM.generate() → add_request() + step() 循环 │
└─────────────────────────────────────────────────────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ 引擎层 (Engine) │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ Scheduler │ → │ ModelRunner │ → │ BlockManager │ │
│ │ (调度器) │ │ (模型执行器) │ │ (KV Cache管理) │ │
│ └─────────────┘ └─────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ 模型层 (Models) │
│ Qwen3ForCausalLM → Qwen3Model → [Qwen3DecoderLayer x N] │
└─────────────────────────────────────────────────────────────────┘
 │
 ▼
┌─────────────────────────────────────────────────────────────────┐
│ 算子层 (Layers) │
│ Attention │ Linear │ LayerNorm │ RoPE │ Activation │ Sampler │
└─────────────────────────────────────────────────────────────────┘
</code></pre><h2 id="推理服务流程详解">推理服务流程详解</h2>
<h3 id="阶段-1-初始化阶段">阶段 1: 初始化阶段</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># example.py</span>
</span></span><span class="line"><span class="cl"><span class="n">llm</span> <span class="o">=</span> <span class="n">LLM</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">enforce_eager</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">tensor_parallel_size</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span></code></pre></div><p><strong>调用链:</strong></p></description>
</item>
<item>
<title>时延的建模与测量</title>
<link>https://blog.jinzhi.site/posts/2026-01/%E6%97%B6%E5%BB%B6%E7%9A%84%E5%BB%BA%E6%A8%A1%E4%B8%8E%E6%B5%8B%E9%87%8F/</link>
<pubDate>Fri, 09 Jan 2026 23:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2026-01/%E6%97%B6%E5%BB%B6%E7%9A%84%E5%BB%BA%E6%A8%A1%E4%B8%8E%E6%B5%8B%E9%87%8F/</guid>
<description><p>本文是 2025年10月出版的《Latency: Reduce delay in software systems》第二章的读书笔记</p>
<h2 id="时延的定律">时延的定律</h2>
<h3 id="littles-law">Little&rsquo;s Law</h3>
<p>利特尔法则(Little&rsquo;s Law)是排队论和运筹学中最经典、最直观,但也最具威力的定律之一。</p>
<p>Little&rsquo;s Law 的数学表达式如下所示:
$$L = \lambda \times W $$</p>
<ul>
<li>$L$ (Inventory / Queue Length):系统中平均拥有的“东西”数量(比如排队的人数、仓库的库存、处理的任务)</li>
<li>$\lambda$ (Throughput / Arrival Rate):单位时间内进入或离开系统的平均数量(吞吐率/到达率)</li>
<li>$W$ (Wait Time / Cycle Time):一个“东西”在系统里停留的平均时间(前置时间/等待时间)</li>
</ul>
<p>需要注意的是,<strong>Little&rsquo;s Law 描述的是一个稳态系统:在一个稳定的系统中,存货数量 = 到达速率 x 停留时间</strong>。</p>
<p>举个例子,一个咖啡馆内,平均每分钟有 2 个客人进店($\lambda$),每个客人在店里从进门到拿咖啡走人平均停留 10 分钟($W$)。那么,任一时刻店中的停留人数 $L = \lambda \times W$ 为 20 人,即在这 10 分钟内店中累积的人数,第 11 分钟到达速率等于离开速率,系统达到平衡。</p>
<p>在并发系统中,$\lambda$ (吞吐量) 可以理解为每秒处理多少个请求(QPS/TPS),$W$ (响应时间) 可以理解为每个请求平均花多少秒(Latency),$L$ (并发数) 可以理解为在任一给定时刻,已经进入系统但尚未处理完成的请求总数。</p>
<p>在计算机系统中,每一个 $L$ 都不是免费的,它必须占用某种物理资源:</p></description>
</item>
<item>
<title>KubeCon North America 2025 Review</title>
<link>https://blog.jinzhi.site/posts/2025-11/kubecon-north-america-2025-review/</link>
<pubDate>Sun, 23 Nov 2025 15:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-11/kubecon-north-america-2025-review/</guid>
<description><p>KubeCon North America 2025 13号结束了,官网上也有了些会议资料。挑了几个感兴趣的话题总结下。</p>
<h2 id="dynamic-routing-with-multi-cluster-inference-gateway">Dynamic Routing with Multi-Cluster Inference Gateway</h2>
<p><a href="https://kccncna2025.sched.com/event/27FeP/ai-inference-without-boundaries-dynamic-routing-with-multi-cluster-inference-gateway-rob-scott-google-daneyon-hansen-soloio?iframe=no&amp;w=100%25&amp;sidebar=yes&amp;bg=no">https://kccncna2025.sched.com/event/27FeP/ai-inference-without-boundaries-dynamic-routing-with-multi-cluster-inference-gateway-rob-scott-google-daneyon-hansen-soloio?iframe=no&w=100%&sidebar=yes&bg=no</a></p>
<p>这段时间正好在做 AI 网关,这个话题可以说是“瞌睡了送枕头”。</p>
<p>推理服务和传统 API 流量相比,在 payload,响应时间,后端资源开销上都有着很大的差异(见下图)。
<img src="https://blog.jinzhi.site/images/notes/kubecon-north-america-2025-review/2781cd595fe30c8da311d124a8e4ee35_MD5.jpeg" alt="2781cd595fe30c8da311d124a8e4ee35_MD5"></p>
<p>因此,推理网关需要做到后端负载感知的调度(传统 API 网关也有类似的方案,尤其是在后端机型不一样,普通的 rr 无法均匀负载时,做后端负载感知动态调权)。在 Gateway 和推理实例间引入了一个 EPP(Endpoint Picker)组件(注:EPP 现在也是 Kubernetes 做推理服务的一个通用组件),采集推理实例的指标来动态选择推理后端。
<img src="https://blog.jinzhi.site/images/notes/kubecon-north-america-2025-review/3462773fb295a9dabdabc886b1de43ca_MD5.jpeg" alt="3462773fb295a9dabdabc886b1de43ca_MD5"></p>
<p>benchmark 数据显示,使用推理网关相比传统负载均衡,推理实例间的负载更加均衡,请求排队更少,从而降低了响应时间。</p>
<p>在多集群场景下,这套方案需要解决 3 个问题:</p>
<ol>
<li>服务发现:Cluster Inference Services 如何暴露给 Gateway?</li>
<li>后端选择:Gateway 如何在多集群间分配流量?</li>
<li>路由模式:流量如何从 Gateway 转发到集群?</li>
</ol>
<p>第一个问题作者提了 3 个解决方法:
<img src="https://blog.jinzhi.site/images/notes/kubecon-north-america-2025-review/fd7a6ffacbfd58fcd6482dd40b861461_MD5.jpeg" alt="fd7a6ffacbfd58fcd6482dd40b861461_MD5"></p>
<p>不是关注重点,略过。</p>
<p>第二个问题,简单的 RR 和 Active-Passive 肯定就失去了推理网关负载感知的优势。所以,在 EPP 感知负载之外,Gateway 也得做负载感知。作者也提了两个方法:
<img src="https://blog.jinzhi.site/images/notes/kubecon-north-america-2025-review/1d3df4bac299f39b0c917b886bab2a27_MD5.jpeg" alt="1d3df4bac299f39b0c917b886bab2a27_MD5">
<img src="https://blog.jinzhi.site/images/notes/kubecon-north-america-2025-review/f4bd518bc6dce2999d5805d5b2d46dac_MD5.jpeg" alt="f4bd518bc6dce2999d5805d5b2d46dac_MD5"></p>
<p>从层级上来说,EPP Aggregate Metrics 方案更加简洁,毕竟在 EPP 上还得做二次调度。</p>
<p>最后一个问题,如果 EPP 能跨集群直接访问,direct routing 是最合适的方式,不行的话再加一层网关,使用 Cluster-Local Gateway 做暴露也能访问。</p></description>
</item>
<item>
<title>网关的限流方案</title>
<link>https://blog.jinzhi.site/posts/2025-11/%E7%BD%91%E5%85%B3%E7%9A%84%E9%99%90%E6%B5%81%E6%96%B9%E6%A1%88/</link>
<pubDate>Sun, 16 Nov 2025 15:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-11/%E7%BD%91%E5%85%B3%E7%9A%84%E9%99%90%E6%B5%81%E6%96%B9%E6%A1%88/</guid>
<description><p><strong>Note:本文由 Google Gemini DeepResearch 生成。</strong></p>
<h2 id="1网关限流服务的战略必要性与流量控制架构">1.网关限流服务的战略必要性与流量控制架构</h2>
<h3 id="11-网关在流量管理中的控制点角色与核心必要性">1.1. 网关在流量管理中的控制点角色与核心必要性</h3>
<p>API 网关在现代微服务架构中扮演着至关重要的流量控制点角色,负责管理北向(Ingress)和南向(Egress)的流量。对流量实施限制(即限流)是保障上游服务稳定性、维持系统可用性和实现资源公平分配的核心防御机制。</p>
<p>限流服务的战略必要性主要体现在以下几个方面:</p>
<p>首先,限流是防止系统过载和雪崩效应的关键。当面临突发流量高峰时,如不加限制,后台服务器可能因资源耗尽而被压垮。通过限制客户端在指定时间窗内的请求次数,网关可以有效地保护应用编程接口(API)及其背后的微服务。其次,限流是抵御恶意攻击的有效手段,尤其是分布式拒绝服务攻击(DDoS)或防止未受限制的网络爬虫过度消耗计算资源。如果任由客户端无限制地访问上游服务,系统的可用性将受到负面影响。</p>
<p>从架构设计的角度看,网关作为请求进入系统的唯一入口,是实施全局限流策略的最佳位置,可以根据 IP、API 密钥、特定消费者(Consumer)或其他定制标准来应用限流规则。</p>
<h3 id="12-限流策略的粒度层次结构与定制化响应">1.2. 限流策略的粒度、层次结构与定制化响应</h3>
<p>在实际应用中,灵活的限流服务需要支持精细的粒度和层次化配置模型,以满足不同业务和资源保护的需求。这种配置的优先级机制体现了一种 <strong>“微观精确控制”优于“宏观默认保护”</strong> 的设计哲学。</p>
<p>网关通常支持多层次的限流配置,包括:</p>
<ol>
<li><strong>特定 API 或消费者限流:</strong> 这是优先级最高的限流配置。例如,用户可以对单个关键 API 进行限流设置,或者对特定的消费者群体施加限制。这种配置会覆盖所有默认设置。这意味着在限流值冲突时,系统优先执行最明确定义或最严格的限制,确保关键资源的防护级别不受宏观策略的稀释。</li>
<li><strong>API 默认限流:</strong> 作用于应用下大部分 API 的通用配置。当没有针对单个 API 配置限流值时,将采用此默认值。</li>
<li><strong>应用总限流 (App Total Limit):</strong> 这是对当前应用下所有 API 请求的总和设置的上限。应用总限流是防止资源被“虹吸”的最后防线。即使单个 API 的限流都没有达到,一旦应用的总请求量超出限流总和值,所有请求都将被限制。这种设计解决了在微服务架构中,攻击者通过对大量低 QPS API 进行频繁调用,最终耗尽整个应用共享资源预算的“长尾攻击”问题。</li>
</ol>
<p>网关在触发限流时,必须向客户端返回明确的、用户友好的响应。例如,默认的限流响应可能包含 <code>{&quot;resultStatus&quot;:1002, &quot;tips&quot;:&quot;顾客太多,客官请稍候&quot;}</code> 。为了实现友好的降级体验,网关需要支持定制化的响应格式,允许用户配置 JSON 格式的返回内容,如通过 <code>resultStatus</code> 字段(例如,1002 表示限流)和 <code>tips</code> 字段向用户提供定制化的限流提示。</p>
<h2 id="2-常见限流算法的原理适用性与分布式性能剖析">2. 常见限流算法的原理、适用性与分布式性能剖析</h2>
<p>选择合适的限流算法是构建高性能网关服务的基石。不同的算法在精确度、突发处理能力和分布式系统中的扩展性方面存在显著差异。</p>
<h3 id="21-固定窗口计数器-fixed-window-counter-fwc">2.1. 固定窗口计数器 (Fixed Window Counter, FWC)</h3>
<p>固定窗口计数器是最简单、资源消耗最低的限流算法。它在一个固定的时间周期内(例如每 60 秒)累积请求计数,当周期结束时,计数器重置。</p>
<p><strong>优势与局限性:</strong> FWC 的实现非常简单,查询和递增操作计算复杂度为 $O(1)$,因此延迟极低。然而,其主要局限在于<strong>边界突发问题</strong>。如果客户端在窗口结束前的一刻发出大量请求,并在下一个窗口开始后的第一时间再次发出同等量的请求,系统在短时间内实际放行的请求量可能高达阈值的两倍。这使得 FWC 难以应对流量的瞬间爆发,可能导致上游服务过载。</p></description>
</item>
<item>
<title>常见的大模型 OpenAPI 规范</title>
<link>https://blog.jinzhi.site/posts/2025-11/%E5%B8%B8%E8%A7%81%E7%9A%84%E5%A4%A7%E6%A8%A1%E5%9E%8B-openapi-%E8%A7%84%E8%8C%83/</link>
<pubDate>Wed, 05 Nov 2025 21:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-11/%E5%B8%B8%E8%A7%81%E7%9A%84%E5%A4%A7%E6%A8%A1%E5%9E%8B-openapi-%E8%A7%84%E8%8C%83/</guid>
<description><p>AI Gateway 的对接开发中,一个重要的内容就是对接不同厂商推理服务的接口协议。目前,推理服务的接口协议主要分为以下几种类型:</p>
<ul>
<li>文本对话接口,如 OpenAI 的 chat completions 和 response API 等</li>
<li>向量接口,向量接口用于将输入的文本或者图片、视频(多模态)等转换成向量表示,适用于搜索(文搜图、图搜图、图文混合搜索)、聚类、推荐等场景。</li>
</ul>
<h2 id="文本对话">文本对话</h2>
<p>文本对话 API 需要提供如下能力支持:</p>
<ol>
<li>模型选择</li>
<li>用户、系统、模型输入内容角色区分</li>
<li>模型参数调整</li>
<li>工具调用</li>
<li>MCP 支持</li>
<li>用量统计</li>
</ol>
<p>目前使用最广泛的文本对话接口自然是 OpenAI 的 chat completions API。几乎所有的 LLM 服务提供商都支持 chat completions compatible 调用。</p>
<p><a href="https://platform.openai.com/docs/api-reference/introduction">https://platform.openai.com/docs/api-reference/introduction</a></p>
<h3 id="chat-completions">chat completions</h3>
<p>基本调用:</p>
<pre tabindex="0"><code>curl https://api.openai.com/v1/chat/completions \
 -H &#34;Content-Type: application/json&#34; \
 -H &#34;Authorization: Bearer $OPENAI_API_KEY&#34; \
 -d &#39;{
 &#34;model&#34;: &#34;gpt-5&#34;,
 &#34;messages&#34;: [
 {
 &#34;role&#34;: &#34;developer&#34;,
 &#34;content&#34;: &#34;You are a helpful assistant.&#34;
 },
 {
 &#34;role&#34;: &#34;user&#34;,
 &#34;content&#34;: &#34;Hello!&#34;
 }
 ]
 }&#39;
</code></pre><ul>
<li>model: 请求调用的模型</li>
<li>messages:构成对话的消息体。根据消息的来源角色,message 可以分为 developer/system(开发者,系统提供的 prompt),user(用户自身的输入)和 assistant(模型的响应,用于多轮对话时模型的上下文传递)。
<ul>
<li>content 分为两种类型,纯文本即为 string,非纯文本 content 为列表类型,内容根据 type(text,image_url,input_audio 等)有不同的字段</li>
</ul>
</li>
<li>stream:采用正常 HTTP 响应还是 sse 响应</li>
<li>stream_options: 在 sse 响应时,有些模型默认不会输出 usage 信息,需要显式将 stream_options.include_usage 设置成 true</li>
<li>temperature:模型温度,取值范围 0 - 2,值越高,输出的 tokens 随机性越大</li>
<li>top_p:和 tempreature 一样对模型输出进行调整的参数,模型会考虑概率质量最高的top_p个tokens的结果。所以0.1意味着只考虑概率质量最高的10%的tokens。</li>
<li>reasoning_effort:模型的推理深度,比如对于 OpenAI 模型来说有 minimal,low,medium,high 等多种选择。</li>
<li>max_completion_tokens:最大输出 tokens 数,包括 output tokens 和 reasoning tokens。替代原来的 max_tokens 字段。</li>
<li>tools:告知大模型本地可调用的工具列表。工具里面定义了工具的名称、描述和 json schema 表示的参数描述。替代原来的 functions 字段。</li>
<li>tool_choice:告知模型对于工具的调用选择。none 表示不要调用任何工具直接生成 messages,auto 表示由模型自己决定是否调用 allowed_tools。required 表示模型必须调用至少一个工具。</li>
</ul>
<p>除此之外,各家可以在 OpenAI 标准的 API 上扩展自己的字段,比如 cherry studio 会使用 thinking 字段来开启/关闭模型思考:</p></description>
</item>
<item>
<title>APISIX 插件体系</title>
<link>https://blog.jinzhi.site/posts/2025-10/apisix-%E6%8F%92%E4%BB%B6%E4%BD%93%E7%B3%BB/</link>
<pubDate>Thu, 30 Oct 2025 12:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-10/apisix-%E6%8F%92%E4%BB%B6%E4%BD%93%E7%B3%BB/</guid>
<description><h2 id="背景知识">背景知识</h2>
<p>APISIX 基于 Openresty 和 Nginx 开发,了解 APISIX 的插件体系也有必要先了解下 Nginx 和 Openresty 的基础知识。</p>
<h3 id="nginx-请求处理阶段">Nginx 请求处理阶段</h3>
<p>Nginx 将一个 HTTP 请求的处理过程精心划分为一系列有序的阶段(Phases),就像一个工厂的流水线。每个阶段都有特定的任务,不同的模块可以将自己的处理程序(handler)注册到感兴趣的阶段。这种设计使得 Nginx 的功能高度模块化、可扩展,并且处理流程非常清晰高效。</p>
<p>一个请求在 Nginx 中主要会经过以下 <strong>11 个处理阶段</strong>:</p>
<h4 id="1ngx_http_post_read_phase请求读取后阶段">1. <code>NGX_HTTP_POST_READ_PHASE</code> (请求读取后阶段)</h4>
<ul>
<li><strong>任务</strong>: 接收到完整的请求头(Request Header)之后,第一个被执行的阶段。</li>
<li><strong>常用模块</strong>: <code>ngx_http_realip_module</code>。</li>
<li><strong>作用举例</strong>: 在这个阶段,<code>realip</code> 模块会根据 <code>X-Forwarded-For</code> 或 <code>X-Real-IP</code> 等请求头,将客户端的真实 IP 地址替换掉代理服务器的 IP 地址。这对后续的访问控制和日志记录至关重要。</li>
</ul>
<h4 id="2ngx_http_server_rewrite_phaseserver-级别地址重写阶段">2. <code>NGX_HTTP_SERVER_REWRITE_PHASE</code> (Server 级别地址重写阶段)</h4>
<ul>
<li><strong>任务</strong>: 在 <code>server</code> 配置块中执行 URL 重写。</li>
<li><strong>常用模块/指令</strong>: <code>rewrite</code> 指令(当它定义在 <code>server</code> 块中时)。</li>
<li><strong>作用举例</strong>: 在请求进入具体的 <code>location</code> 匹配之前,对 URL 进行全局的、初步的改写。例如,将所有 <code>http</code> 请求强制重定向到 <code>https</code>。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="kn">server_name</span> <span class="s">example.com</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="c1"># 这个 rewrite 就工作在 SERVER_REWRITE 阶段
</span></span></span><span class="line"><span class="cl"> <span class="kn">rewrite</span> <span class="s">^/(.*)</span>$ <span class="s">https://example.com/</span><span class="nv">$1</span> <span class="s">permanent</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h4 id="3ngx_http_find_config_phase配置查找阶段">3. <code>NGX_HTTP_FIND_CONFIG_PHASE</code> (配置查找阶段)</h4>
<ul>
<li><strong>任务</strong>: 根据上个阶段处理完的 URI,查找并匹配对应的 <code>location</code> 配置块。</li>
<li><strong>核心功能</strong>: 这是 Nginx 路由的核心。Nginx 会用请求的 URI 与 <code>location</code> 指令定义的规则进行匹配,找到最合适的 <code>location</code>。这个阶段没有模块可以注册 handler,是 Nginx 核心自己完成的。</li>
</ul>
<h4 id="4ngx_http_rewrite_phaselocation-级别地址重写阶段">4. <code>NGX_HTTP_REWRITE_PHASE</code> (Location 级别地址重写阶段)</h4>
<ul>
<li><strong>任务</strong>: 在上一步匹配到的 <code>location</code> 块内部,执行 URL 重写。</li>
<li><strong>常用模块/指令</strong>: <code>rewrite</code> 指令(当它定义在 <code>location</code> 块中时)、<code>set</code> 指令。</li>
<li><strong>作用举例</strong>: 对特定 <code>location</code> 的请求进行更精细的 URL 改写。这个阶段的 <code>rewrite</code> 可能会导致 Nginx 重新回到 <code>FIND_CONFIG</code> 阶段去匹配新的 <code>location</code>,可能会有循环,最多执行 10 次以防止死循环。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/app/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="c1"># 这个 rewrite 工作在 REWRITE 阶段
</span></span></span><span class="line"><span class="cl"> <span class="kn">rewrite</span> <span class="s">^/app/(.*)</span>$ <span class="s">/v2/app/</span><span class="nv">$1</span> <span class="s">break</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h4 id="5ngx_http_post_rewrite_phase地址重写后阶段">5. <code>NGX_HTTP_POST_REWRITE_PHASE</code> (地址重写后阶段)</h4>
<ul>
<li><strong>任务</strong>: 防止 <code>rewrite</code> 阶段的重写指令导致死循环。如果 URI 在上一个阶段被重写,这个阶段会把重写后的 URI 交给 Nginx,让其重新开始 <code>FIND_CONFIG</code> 阶段,查找新的 <code>location</code>。这是一个内部阶段,用户通常不直接感知。</li>
</ul>
<h4 id="6ngx_http_preaccess_phase访问权限控制前置阶段">6. <code>NGX_HTTP_PREACCESS_PHASE</code> (访问权限控制前置阶段)</h4>
<ul>
<li><strong>任务</strong>: 在正式的访问权限检查之前,做一些准备工作。</li>
<li><strong>常用模块</strong>: <code>ngx_http_limit_conn_module</code> (连接数限制), <code>ngx_http_limit_req_module</code> (请求速率限制)。</li>
<li><strong>作用举例</strong>: 在检查用户名密码之前,先检查客户端的请求速率是否过快,如果过快,直接拒绝,不必再执行后面的阶段。</li>
</ul>
<h4 id="7ngx_http_access_phase访问权限控制阶段">7. <code>NGX_HTTP_ACCESS_PHASE</code> (访问权限控制阶段)</h4>
<ul>
<li><strong>任务</strong>: 对请求进行权限验证。</li>
<li><strong>常用模块/指令</strong>: <code>ngx_http_access_module</code> (<code>allow</code>, <code>deny</code>), <code>ngx_http_auth_basic_module</code> (HTTP 基本认证)。</li>
<li><strong>作用举例</strong>: 检查客户端 IP 是否在允许列表中,或者验证用户提供的用户名和密码是否正确。如果这个阶段有任何模块拒绝了请求,处理就会立即中断并返回错误(如 403 Forbidden)。</li>
</ul>
<h4 id="8ngx_http_post_access_phase访问权限控制后置阶段">8. <code>NGX_HTTP_POST_ACCESS_PHASE</code> (访问权限控制后置阶段)</h4>
<ul>
<li><strong>任务</strong>: 主要用于配合 <code>ACCESS</code> 阶段的 <code>satisfy</code> 指令。</li>
<li><strong>作用举例</strong>: 当 <code>satisfy any;</code> 被使用时,如果 <code>ACCESS</code> 阶段有模块允许了访问(例如 IP 匹配成功),<code>POST_ACCESS</code> 阶段的处理就可以被跳过。如果 <code>ACCESS</code> 阶段没有明确允许,这个阶段的处理(例如 HTTP 基本认证)就会被执行。</li>
</ul>
<h4 id="9ngx_http_try_files_phasetry_files-阶段">9. <code>NGX_HTTP_TRY_FILES_PHASE</code> (Try_files 阶段)</h4>
<ul>
<li><strong>任务</strong>: <code>try_files</code> 指令专属的特殊阶段。</li>
<li><strong>常用模块/指令</strong>: <code>try_files</code>。</li>
<li><strong>作用举例</strong>: 按顺序检查文件或目录是否存在,如果找到,则内部重定向到该文件,如果都找不到,则执行最后一个参数(通常是内部重定向到一个 <code>location</code> 或返回一个状态码)。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="c1"># 这个指令工作在 TRY_FILES 阶段
</span></span></span><span class="line"><span class="cl"> <span class="kn">try_files</span> <span class="nv">$uri</span> <span class="nv">$uri/</span> <span class="s">/index.html</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h4 id="10ngx_http_content_phase内容生成阶段">10. <code>NGX_HTTP_CONTENT_PHASE</code> (内容生成阶段)</h4>
<ul>
<li><strong>任务</strong>: <strong>核心阶段</strong>,负责生成最终的响应内容并发送给客户端。</li>
<li><strong>重要特性</strong>: 每个 <code>location</code> 只有一个模块能成为“内容处理模块”(Content Handler)。</li>
<li><strong>常用模块</strong>:
<ul>
<li><code>ngx_http_static_module</code>: 处理静态文件。</li>
<li><code>ngx_http_proxy_module</code>: 将请求反向代理到后端服务器 (<code>proxy_pass</code>)。</li>
<li><code>ngx_http_fastcgi_module</code>: 将请求转发给 FastCGI 应用(如 PHP-FPM)。</li>
<li><code>ngx_http_index_module</code>: 处理目录索引文件。</li>
<li><code>return</code>: 直接返回指定的状态码或内容。</li>
</ul>
</li>
<li><strong>执行逻辑</strong>: Nginx 会调用在 <code>location</code> 中找到的内容处理模块。例如,如果配置了 <code>proxy_pass</code>,那么 <code>proxy_module</code> 就会接管请求。如果没有特定的内容处理模块,默认会由 <code>static_module</code> 尝试提供静态文件服务。</li>
</ul>
<h4 id="11ngx_http_log_phase日志记录阶段">11. <code>NGX_HTTP_LOG_PHASE</code> (日志记录阶段)</h4>
<ul>
<li><strong>任务</strong>: 请求处理完成后,记录访问日志。</li>
<li><strong>常用模块/指令</strong>: <code>ngx_http_log_module</code> (<code>access_log</code>)。</li>
<li><strong>作用举-例</strong>: 无论请求成功还是失败,这个阶段都会被执行(除非有严重错误),用于将请求的相关信息(如客户端IP、请求时间、状态码等)写入到日志文件中。</li>
</ul>
<h4 id="过滤器模块-filter-modules">过滤器模块 (Filter Modules)</h4>
<p>当 <code>CONTENT</code> 阶段生成了响应内容后,在发送给客户端之前,响应数据还会经过一系列的 <strong>过滤器(Filter)</strong> 链处理。过滤器负责对响应头和响应体进行修改或加工。</p></description>
</item>
<item>
<title>WebSocket Origin Header 校验失败</title>
<link>https://blog.jinzhi.site/posts/2025-10/websocket-origin-header-%E6%A0%A1%E9%AA%8C%E5%A4%B1%E8%B4%A5/</link>
<pubDate>Tue, 21 Oct 2025 15:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-10/websocket-origin-header-%E6%A0%A1%E9%AA%8C%E5%A4%B1%E8%B4%A5/</guid>
<description><p>最近做 APISIX 线上服务时遇到一个场景:业务使用 websocket 转发时,在浏览器会出现 WebSocket close with status code 1006 的错误。打开调试工具查看发现在 websocket 握手时服务端返回了 403。</p>
<p><img src="https://blog.jinzhi.site/images/notes/websocket-origin-header-%E6%A0%A1%E9%AA%8C%E5%A4%B1%E8%B4%A5/76f75e967736245ec923202987a22482_MD5.jpeg" alt="76f75e967736245ec923202987a22482_MD5"></p>
<p><img src="https://blog.jinzhi.site/images/notes/websocket-origin-header-%E6%A0%A1%E9%AA%8C%E5%A4%B1%E8%B4%A5/4efbf4b7a2093ed4f1d6e522139dc92e_MD5.jpeg" alt="4efbf4b7a2093ed4f1d6e522139dc92e_MD5"></p>
<p>非常奇怪的是,如果业务不经过 APISIX 直接访问后端 code-server 是没有问题的(中间也得经过一层 Ingress 转发)。</p>
<p>简单的流量模型如下:</p>
<pre tabindex="0"><code> 
 
 +--------------+ +--------------+ +-------------+
 https://domain.com:9443/xxx | | http://domain.com:23480/xxx | | | |
-------------------------------- APISIX -------------------------------+ Ingress -----------+ code-server |
 | | | | | |
 +--------------+ +--------------+ +-------------+
</code></pre><p>经过对比发现,两者的请求头里面 Host 和 Origin 是存在差异的。尝试在 APISIX 中强制修改 Host 头部,问题没有解决。然后利用 proxy-rewrite 强制修改 Origin 头部,请求恢复正常。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"> <span class="s2">&#34;plugins&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;proxy-rewrite&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;uri&#34;</span><span class="p">:</span> <span class="s2">&#34;/anything&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;headers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;set&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	 <span class="nt">&#34;Origin&#34;</span><span class="p">:</span> <span class="s2">&#34;http://domain.com&#34;</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span><span class="err">,</span>
</span></span></code></pre></div><p>那么问题来了,为什么改完 Origin Header 就行了?code-server 是如何处理 Origin Header 的?为什么 Ingress 可以,APISIX 不行?</p></description>
</item>
<item>
<title>Introducing Kubernetes Gateway API</title>
<link>https://blog.jinzhi.site/posts/2025-10/introducing-kubernetes-gateway-api/</link>
<pubDate>Thu, 16 Oct 2025 16:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-10/introducing-kubernetes-gateway-api/</guid>
<description><p>Kubernetes Gateway API 是一个由 SIG-Network 孵化和维护的 <strong>开放、标准、可扩展的 API</strong>,旨在为 Kubernetes 集群内部和外部的统一流量管理提供更强大、更灵活、更具表现力的方式。</p>
<p>它被视为 <strong>Ingress API 的继任者和演进</strong>,解决了 Ingress 在灵活性、可扩展性、角色分离和 L4-L7 支持方面的诸多局限性。Gateway API 不仅仅关注 HTTP 流量,还支持 TCP、UDP 以及 TLS 路由,覆盖了更广泛的网络场景。</p>
<h2 id="为什么需要-gateway-api">为什么需要 Gateway API</h2>
<p>在 Gateway API 出现之前,Kubernetes 主要使用 <code>Ingress</code> 来管理集群的 L7 HTTP/HTTPS 流量。然而,Ingress 存在一些固有的局限性,促使了 Gateway API 的诞生:</p>
<ol>
<li><strong>功能有限:</strong> Ingress 本身功能非常基础,只支持主机和路径路由。任何高级功能(如流量拆分、重写、限速、认证等)都必须通过特定于 Ingress 控制器(如 NGINX Ingress, Traefik, AWS ALB Ingress 等)的 <strong>注解(Annotations)</strong> 来实现。</li>
<li><strong>可移植性差:</strong> 注解是特定于实现的,这意味着用 NGINX Ingress 注解编写的规则无法直接移植到 Traefik 或其他 Ingress 控制器上,造成供应商锁定。</li>
<li><strong>缺乏角色分离:</strong> Ingress 资源将网络基础设施的配置(例如暴露的端口、TLS 证书)与应用程序的路由规则混在一起,这使得集群管理员和应用开发者之间的职责难以清晰划分。</li>
<li><strong>L4 流量支持不足:</strong> Ingress 仅专注于 HTTP/HTTPS (L7) 流量。对于 TCP、UDP 或 TLS 直通(Passthrough)等 L4 流量,通常需要使用 <code>LoadBalancer</code> 类型的 Service 或其他的解决方案。</li>
<li><strong>表达能力有限:</strong> 难以表达复杂的路由策略,如基于请求头、查询参数的匹配,细粒度的流量权重分配等。</li>
</ol>
<p>Gateway API 旨在解决这些问题,提供一个更加健壮和通用的流量管理框架。</p></description>
</item>
<item>
<title>APISIX balancer 支持 domain</title>
<link>https://blog.jinzhi.site/posts/2025-10/apisix-balancer-%E6%94%AF%E6%8C%81-domain/</link>
<pubDate>Sun, 12 Oct 2025 15:18:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-10/apisix-balancer-%E6%94%AF%E6%8C%81-domain/</guid>
<description><p>最近在做网关开发时遇到了一个场景:<strong>业务在做多实例部署时需要通过 Ingress 将服务注册给 APISIX,由于每个实例属于一个单独的 namespace,导致在 APISIX 和 Ingress 上出现流量覆盖的问题:</strong></p>
<ol>
<li>Ingress 通过 Host 和 URI 匹配入向流量,同一个服务的 Host 和 URI 是完全一致的(Host 用 ip:port 表示),因此注册多个 Ingress 流量只会引流到一个实例上。</li>
<li>APISIX 侧在 balance 阶段对于相同 ip:port 的 upstream node 会做去重,导致跨集群多实例时流量分配并不符合预期。</li>
</ol>
<p>举个例子,有 A,B 两个集群,A 集群部署了 2 个实例,B 集群部署了一个实例。预期这 3 个实例应该平均分配流量(假设采用 rr),A 集群和 B 集群流量比例应该是 2:1,但实际上 A 集群和 B 集群在 APISIX 分配时流量是 1:1,而在 A 集群内一个实例占据了全部流量。</p>
<p>从业务上来说这个可以通过重新规划 namespace 和实例的关系来解决。同一个集群同一个服务的所有实例分布在同一个 namespace 中。这样注册的 Ingress 和 APISIX upstream node 就只会有一个。</p>
<p>抛开这个点,我们来看看从网关的角度如何解决这两个问题。</p>
<p>第一个问题比较好解决,每个注册的实例采用独立的 Domain,Ingress 通过 Host Header 来区分流量。在 APISIX 采用域名注册,域名解析到 Ingress 地址。如果域名没法解析(我们遇到的场景),也可以将 Domain 信息放到 node 的 metadata 内,写一个插件将其注入到 node.domain 内来完成。</p></description>
</item>
<item>
<title>Go 汇编分析</title>
<link>https://blog.jinzhi.site/posts/2025-09/go-%E6%B1%87%E7%BC%96%E5%88%86%E6%9E%90/</link>
<pubDate>Wed, 17 Sep 2025 21:45:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-09/go-%E6%B1%87%E7%BC%96%E5%88%86%E6%9E%90/</guid>
<description><p>Go的汇编不是像 C/C++ 一样,对机器码的直接描述,而是兼容跨平台需求实现的半抽象化的指令集。</p>
<p><a href="https://go.dev/doc/asm">https://go.dev/doc/asm</a></p>
<h2 id="汇编分析go-117">汇编分析(Go 1.17)</h2>
<p>我们用一个简单的例子来开始汇编分析:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nf">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">add</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nx">j</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>汇编结果,删去了一些无关的输出:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># go tool compile -S -l -N main.go</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;</span>.main STEXT <span class="nv">size</span><span class="o">=</span><span class="m">54</span> <span class="nv">args</span><span class="o">=</span>0x0 <span class="nv">locals</span><span class="o">=</span>0x18 <span class="nv">funcid</span><span class="o">=</span>0x0
</span></span><span class="line"><span class="cl"> 0x0000 <span class="m">00000</span> <span class="o">(</span>main.go:3<span class="o">)</span> TEXT <span class="s2">&#34;&#34;</span>.main<span class="o">(</span>SB<span class="o">)</span>, ABIInternal, <span class="nv">$24</span>-0
</span></span><span class="line"><span class="cl"> 0x0000 <span class="m">00000</span> <span class="o">(</span>main.go:3<span class="o">)</span> CMPQ SP, 16<span class="o">(</span>R14<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x0004 <span class="m">00004</span> <span class="o">(</span>main.go:3<span class="o">)</span> PCDATA <span class="nv">$0</span>, <span class="nv">$-</span><span class="m">2</span>
</span></span><span class="line"><span class="cl"> 0x0004 <span class="m">00004</span> <span class="o">(</span>main.go:3<span class="o">)</span> JLS <span class="m">47</span>
</span></span><span class="line"><span class="cl"> 0x0006 <span class="m">00006</span> <span class="o">(</span>main.go:3<span class="o">)</span> PCDATA <span class="nv">$0</span>, <span class="nv">$-</span><span class="m">1</span>
</span></span><span class="line"><span class="cl"> 0x0006 <span class="m">00006</span> <span class="o">(</span>main.go:3<span class="o">)</span> SUBQ <span class="nv">$24</span>, SP
</span></span><span class="line"><span class="cl"> 0x000a <span class="m">00010</span> <span class="o">(</span>main.go:3<span class="o">)</span> MOVQ BP, 16<span class="o">(</span>SP<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x000f <span class="m">00015</span> <span class="o">(</span>main.go:3<span class="o">)</span> LEAQ 16<span class="o">(</span>SP<span class="o">)</span>, BP
</span></span><span class="line"><span class="cl"> 0x0014 <span class="m">00020</span> <span class="o">(</span>main.go:3<span class="o">)</span> FUNCDATA <span class="nv">$0</span>, gclocals·33cdeccccebe80329f1fdbee7f5874cb<span class="o">(</span>SB<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x0014 <span class="m">00020</span> <span class="o">(</span>main.go:3<span class="o">)</span> FUNCDATA <span class="nv">$1</span>, gclocals·33cdeccccebe80329f1fdbee7f5874cb<span class="o">(</span>SB<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x0014 <span class="m">00020</span> <span class="o">(</span>main.go:4<span class="o">)</span> MOVL <span class="nv">$1</span>, AX
</span></span><span class="line"><span class="cl"> 0x0019 <span class="m">00025</span> <span class="o">(</span>main.go:4<span class="o">)</span> MOVL <span class="nv">$3</span>, BX
</span></span><span class="line"><span class="cl"> 0x001e <span class="m">00030</span> <span class="o">(</span>main.go:4<span class="o">)</span> PCDATA <span class="nv">$1</span>, <span class="nv">$0</span>
</span></span><span class="line"><span class="cl"> 0x001e <span class="m">00030</span> <span class="o">(</span>main.go:4<span class="o">)</span> NOP
</span></span><span class="line"><span class="cl"> 0x0020 <span class="m">00032</span> <span class="o">(</span>main.go:4<span class="o">)</span> CALL <span class="s2">&#34;&#34;</span>.add<span class="o">(</span>SB<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x0025 <span class="m">00037</span> <span class="o">(</span>main.go:5<span class="o">)</span> MOVQ 16<span class="o">(</span>SP<span class="o">)</span>, BP
</span></span><span class="line"><span class="cl"> 0x002a <span class="m">00042</span> <span class="o">(</span>main.go:5<span class="o">)</span> ADDQ <span class="nv">$24</span>, SP
</span></span><span class="line"><span class="cl"> 0x002e <span class="m">00046</span> <span class="o">(</span>main.go:5<span class="o">)</span> RET
</span></span><span class="line"><span class="cl"> 0x002f <span class="m">00047</span> <span class="o">(</span>main.go:5<span class="o">)</span> NOP
</span></span><span class="line"><span class="cl"> 0x002f <span class="m">00047</span> <span class="o">(</span>main.go:3<span class="o">)</span> PCDATA <span class="nv">$1</span>, <span class="nv">$-</span><span class="m">1</span>
</span></span><span class="line"><span class="cl"> 0x002f <span class="m">00047</span> <span class="o">(</span>main.go:3<span class="o">)</span> PCDATA <span class="nv">$0</span>, <span class="nv">$-</span><span class="m">2</span>
</span></span><span class="line"><span class="cl"> 0x002f <span class="m">00047</span> <span class="o">(</span>main.go:3<span class="o">)</span> CALL runtime.morestack_noctxt<span class="o">(</span>SB<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x0034 <span class="m">00052</span> <span class="o">(</span>main.go:3<span class="o">)</span> PCDATA <span class="nv">$0</span>, <span class="nv">$-</span><span class="m">1</span>
</span></span><span class="line"><span class="cl"> 0x0034 <span class="m">00052</span> <span class="o">(</span>main.go:3<span class="o">)</span> JMP <span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;</span>.add STEXT nosplit <span class="nv">size</span><span class="o">=</span><span class="m">56</span> <span class="nv">args</span><span class="o">=</span>0x10 <span class="nv">locals</span><span class="o">=</span>0x10 <span class="nv">funcid</span><span class="o">=</span>0x0
</span></span><span class="line"><span class="cl"> 0x0000 <span class="m">00000</span> <span class="o">(</span>main.go:7<span class="o">)</span> TEXT <span class="s2">&#34;&#34;</span>.add<span class="o">(</span>SB<span class="o">)</span>, NOSPLIT<span class="p">|</span>ABIInternal, <span class="nv">$16</span>-16
</span></span><span class="line"><span class="cl"> 0x0000 <span class="m">00000</span> <span class="o">(</span>main.go:7<span class="o">)</span> SUBQ <span class="nv">$16</span>, SP
</span></span><span class="line"><span class="cl"> 0x0004 <span class="m">00004</span> <span class="o">(</span>main.go:7<span class="o">)</span> MOVQ BP, 8<span class="o">(</span>SP<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x0009 <span class="m">00009</span> <span class="o">(</span>main.go:7<span class="o">)</span> LEAQ 8<span class="o">(</span>SP<span class="o">)</span>, BP
</span></span><span class="line"><span class="cl"> 0x000e <span class="m">00014</span> <span class="o">(</span>main.go:7<span class="o">)</span> FUNCDATA <span class="nv">$0</span>, gclocals·33cdeccccebe80329f1fdbee7f5874cb<span class="o">(</span>SB<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x000e <span class="m">00014</span> <span class="o">(</span>main.go:7<span class="o">)</span> FUNCDATA <span class="nv">$1</span>, gclocals·33cdeccccebe80329f1fdbee7f5874cb<span class="o">(</span>SB<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x000e <span class="m">00014</span> <span class="o">(</span>main.go:7<span class="o">)</span> FUNCDATA <span class="nv">$5</span>, <span class="s2">&#34;&#34;</span>.add.arginfo1<span class="o">(</span>SB<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x000e <span class="m">00014</span> <span class="o">(</span>main.go:7<span class="o">)</span> MOVQ AX, <span class="s2">&#34;&#34;</span>.i+24<span class="o">(</span>SP<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x0013 <span class="m">00019</span> <span class="o">(</span>main.go:7<span class="o">)</span> MOVQ BX, <span class="s2">&#34;&#34;</span>.j+32<span class="o">(</span>SP<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x0018 <span class="m">00024</span> <span class="o">(</span>main.go:7<span class="o">)</span> MOVQ <span class="nv">$0</span>, <span class="s2">&#34;&#34;</span>.~r2<span class="o">(</span>SP<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x0020 <span class="m">00032</span> <span class="o">(</span>main.go:8<span class="o">)</span> MOVQ <span class="s2">&#34;&#34;</span>.i+24<span class="o">(</span>SP<span class="o">)</span>, AX
</span></span><span class="line"><span class="cl"> 0x0025 <span class="m">00037</span> <span class="o">(</span>main.go:8<span class="o">)</span> ADDQ <span class="s2">&#34;&#34;</span>.j+32<span class="o">(</span>SP<span class="o">)</span>, AX
</span></span><span class="line"><span class="cl"> 0x002a <span class="m">00042</span> <span class="o">(</span>main.go:8<span class="o">)</span> MOVQ AX, <span class="s2">&#34;&#34;</span>.~r2<span class="o">(</span>SP<span class="o">)</span>
</span></span><span class="line"><span class="cl"> 0x002e <span class="m">00046</span> <span class="o">(</span>main.go:8<span class="o">)</span> MOVQ 8<span class="o">(</span>SP<span class="o">)</span>, BP
</span></span><span class="line"><span class="cl"> 0x0033 <span class="m">00051</span> <span class="o">(</span>main.go:8<span class="o">)</span> ADDQ <span class="nv">$16</span>, SP
</span></span><span class="line"><span class="cl"> 0x0037 <span class="m">00055</span> <span class="o">(</span>main.go:8<span class="o">)</span> RET
</span></span></code></pre></div><p><code>FUNCDATA</code>和<code>PCDATA</code>是由编译器引入的,主要包含垃圾回收时使用的信息,这里略过。</p></description>
</item>
<item>
<title>Lua:Table 浅析</title>
<link>https://blog.jinzhi.site/posts/2025-09/luatable-%E6%B5%85%E6%9E%90/</link>
<pubDate>Sun, 14 Sep 2025 23:45:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-09/luatable-%E6%B5%85%E6%9E%90/</guid>
<description><p>本文的分析基于 OpenResty 的 Lua 分支(https://github.com/openresty/luajit2)。</p>
<h2 id="核心-api">核心 API</h2>
<p>table 的 API 定义在 src/lib_table.c 中,API 分为三个部分:</p>
<p>标准库函数:</p>
<ul>
<li>table.insert() - 向 table 插入元素</li>
<li>table.remove() - 移除 table 元素</li>
<li>table.concat() - 连接 table 元素为字符串</li>
<li>table.sort() - 对 table 进行排序</li>
<li>table.maxn() - 找到 table 中最大数字键</li>
<li>table.move() - 移动 table 元素</li>
</ul>
<p>LuaJIT 扩展函数:</p>
<ul>
<li>table.new() - 预分配指定大小的 table</li>
</ul>
<p>OpenResty 扩展函数:</p>
<ul>
<li>table.clear() - 清空 table 内容</li>
<li>table.clone() - 克隆 table</li>
<li>table.nkeys() - 获取 table 键的数量</li>
<li>table.isarray() - 检查是否为数组</li>
<li>table.isempty() - 检查 table 是否为空</li>
</ul>
<h2 id="数据结构">数据结构</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">Node</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">TValue</span> <span class="n">val</span><span class="p">;</span> <span class="c1">// 值对象,必须是第一个字段
</span></span></span><span class="line"><span class="cl"> <span class="n">TValue</span> <span class="n">key</span><span class="p">;</span> <span class="c1">// 键对象
</span></span></span><span class="line"><span class="cl"> <span class="n">MRef</span> <span class="n">next</span><span class="p">;</span> <span class="c1">// 哈希链指针
</span></span></span><span class="line"><span class="cl"><span class="cp">#if !LJ_GC64
</span></span></span><span class="line"><span class="cl"> <span class="n">MRef</span> <span class="n">freetop</span><span class="p">;</span> <span class="c1">// 32位架构下的空闲节点顶部指针(存储在node[0])
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">Node</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">GCtab</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">GCHeader</span><span class="p">;</span> <span class="c1">// GC 通用头部:nextgc, marked, gct
</span></span></span><span class="line"><span class="cl"> <span class="kt">uint8_t</span> <span class="n">nomm</span><span class="p">;</span> <span class="c1">// 元方法负缓存掩码
</span></span></span><span class="line"><span class="cl"> <span class="kt">int8_t</span> <span class="n">colo</span><span class="p">;</span> <span class="c1">// 数组共址标记 (-1表示已分离, &gt;0表示共址大小)
</span></span></span><span class="line"><span class="cl"> <span class="n">MRef</span> <span class="n">array</span><span class="p">;</span> <span class="c1">// 数组部分指针
</span></span></span><span class="line"><span class="cl"> <span class="n">GCRef</span> <span class="n">gclist</span><span class="p">;</span> <span class="c1">// GC 链表指针
</span></span></span><span class="line"><span class="cl"> <span class="n">GCRef</span> <span class="n">metatable</span><span class="p">;</span> <span class="c1">// 元表引用
</span></span></span><span class="line"><span class="cl"> <span class="n">MRef</span> <span class="n">node</span><span class="p">;</span> <span class="c1">// 哈希部分指针
</span></span></span><span class="line"><span class="cl"> <span class="kt">uint32_t</span> <span class="n">asize</span><span class="p">;</span> <span class="c1">// 数组部分大小 [0, asize-1]
</span></span></span><span class="line"><span class="cl"> <span class="kt">uint32_t</span> <span class="n">hmask</span><span class="p">;</span> <span class="c1">// 哈希掩码 (哈希部分大小-1)
</span></span></span><span class="line"><span class="cl"><span class="cp">#if LJ_GC64
</span></span></span><span class="line"><span class="cl"> <span class="n">MRef</span> <span class="n">freetop</span><span class="p">;</span> <span class="c1">// 64位架构下的空闲节点顶部指针
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">GCtab</span><span class="p">;</span>
</span></span></code></pre></div><p>可以看到,GCtab 中同时定义了数组部分 <code>array</code> 和哈希部分 <code>node</code>。</p></description>
</item>
<item>
<title>Lua:Concurrency</title>
<link>https://blog.jinzhi.site/posts/2025-09/luaconcurrency/</link>
<pubDate>Mon, 01 Sep 2025 00:27:09 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-09/luaconcurrency/</guid>
<description><p>Lua 的并发(Concurrency)设计核心在于其轻量级、嵌入式的哲学,以及对协作式多任务的首选。它通过强大的协程机制实现并发,但本身不提供多线程/多进程的并行能力。</p>
<hr>
<h2 id="多线程多进程">多线程/多进程</h2>
<ul>
<li><strong>核心语言无内置支持:</strong> Lua 语言本身的核心 VM 被设计为<strong>单线程</strong>执行。它不提供内置的语法或标准库来直接创建和管理线程(<code>std::thread</code>)或进程(<code>fork</code>)。</li>
<li><strong>独立的 Lua State:</strong> 一个 Lua VM 实例被称为一个“Lua State”。每个 Lua State 是完全独立的运行时环境,拥有自己的全局变量、栈、打开的文件、垃圾回收器等。它们之间默认不共享任何数据。</li>
<li><strong>宿主语言的责任:</strong> 如果需要在 Lua 中实现真正的并行(多核利用),必须依赖于<strong>宿主语言(如 C/C++)的多线程/多进程机制</strong>。
<ul>
<li><strong>实现方式:</strong> 在宿主语言的每个线程或进程中,创建并运行一个<strong>独立的 Lua State</strong>。</li>
<li><strong>数据交换:</strong> 这些独立的 Lua State 之间无法直接共享内存。数据交换必须通过宿主语言提供的进程间通信 (IPC) 或线程间通信 (ITC) 机制(如消息队列、共享内存、管道、套接字等)来完成。</li>
<li><strong>优点:</strong> 简单安全,因为 Lua State 之间是隔离的,避免了复杂的并发同步问题。</li>
<li><strong>缺点:</strong> 额外的通信开销和复杂性,且无法在单个 Lua State 内部实现并行。</li>
</ul>
</li>
<li><strong>第三方库(封装):</strong> 存在一些第三方库(如 LuaLanes)试图提供在 Lua 中模拟多线程/多进程的 API。这些库通常是在底层创建独立的 Lua State,并封装了 IPC 机制,方便 Lua 开发者使用,但其本质仍然是基于宿主语言的底层能力和独立的 Lua State。</li>
</ul>
<h2 id="协程">协程</h2>
<h3 id="协程的设计与实现">协程的设计与实现</h3>
<ul>
<li><strong>设计理念:</strong> Lua 协程是为了提供<strong>协作式多任务 (Cooperative Multitasking)</strong> 而设计。它们允许在单个线程中实现任务的暂停和恢复,以模拟并发,而无需复杂的锁机制。</li>
<li><strong>“有栈协程” (Stackful Coroutines):</strong> Lua 协程是<strong>有栈的</strong>。这里的“栈”指的不是操作系统的 C 语言栈,而是 Lua 虚拟机内部维护的<strong>Lua VM 栈</strong>。
<ul>
<li><strong>Lua VM 栈:</strong> 每个协程在创建时都会分配一个独立的 Lua VM 栈(或在需要时动态扩展)。这个栈存储着协程的局部变量、函数参数、中间表达式结果和函数调用上下文。</li>
</ul>
</li>
<li><strong>实现机制:</strong>
<ul>
<li><strong><code>coroutine.create(function)</code>:</strong> 创建一个新的协程(一个<code>thread</code>类型的值),但并不立即执行。它会分配并初始化一个新的 Lua VM 栈。</li>
<li><strong><code>coroutine.yield(...)</code> (保存栈):</strong> 当一个协程调用 <code>yield</code> 时,Lua VM 会:
<ol>
<li><strong>保存当前 Lua VM 栈的完整状态</strong>(包括所有活跃的栈帧、局部变量值、程序计数器等)。这些信息会被存储在协程对象本身(在堆上分配)中。</li>
<li><strong>暂停当前协程的执行。</strong></li>
<li><strong>将控制权返回给调用 <code>coroutine.resume</code> 的那个协程或主线程。</strong> C 语言栈会正常展开,<code>yield</code> 作为一个 C 函数正常返回。</li>
</ol>
</li>
<li><strong><code>coroutine.resume(co, ...)</code> (恢复栈):</strong> 当一个协程被 <code>resume</code> 时,Lua VM 会:
<ol>
<li><strong>从协程对象中加载并恢复其之前保存的 Lua VM 栈状态。</strong> 这包括设置栈顶指针、恢复所有栈帧和程序计数器,使得协程能够从上次 <code>yield</code> 的点继续执行。</li>
<li><strong>将控制权转移给被恢复的协程。</strong> C 语言栈上会为 <code>resume</code> 函数创建一个新的栈帧,并在其中运行被恢复的 Lua 协程。</li>
</ol>
</li>
</ul>
</li>
<li><strong>优点:</strong> 简单、高效、避免了与 OS 栈相关的复杂性,并且由于是协作式的,没有竞态条件和锁的开销。</li>
<li><strong>缺点:</strong> 无法利用多核 CPU。</li>
</ul>
<p>协程示例:</p></description>
</item>
<item>
<title>Lua - An Overview</title>
<link>https://blog.jinzhi.site/posts/2025-08/lua-an-overview/</link>
<pubDate>Sun, 31 Aug 2025 13:29:48 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-08/lua-an-overview/</guid>
<description><h2 id="lua-和-luajit">Lua 和 LuaJIT</h2>
<h3 id="lua">Lua</h3>
<p>Lua 是一个开源项目,由巴西里约热内卢天主教大学的 Roberto Ierusalimschy、Luiz Henrique de Figueiredo 和 Waldemar Celes 创建。它的版本控制相对简单明了。</p>
<h4 id="lua-的版本体系">Lua 的版本体系</h4>
<p>Lua 的版本号通常是 <code>X.Y</code> 的形式,例如 <code>5.1</code>, <code>5.2</code>, <code>5.3</code>, <code>5.4</code>。</p>
<ul>
<li><strong><code>X</code> (主版本号/Major Version):</strong> 表示一个<strong>重大更新</strong>,通常会引入不兼容的更改(breaking changes),新的核心特性,或者对虚拟机架构的显著改进。从 <code>5.x</code> 到 <code>5.y</code>,<code>y</code> 增加通常意味着语法、API 或语义的更改,可能需要修改现有代码。
<ul>
<li><strong>例子:</strong>
<ul>
<li><strong>Lua 5.0:</strong> 引入了协程 (coroutines)。</li>
<li><strong>Lua 5.1:</strong> 引入了模块系统 (module system)、<code>vararg</code> 参数的改进。</li>
<li><strong>Lua 5.2:</strong> 引入了 <code>goto</code> 语句、环境 (environments) 的重新设计、新的 <code>_ENV</code> 上值。</li>
<li><strong>Lua 5.3:</strong> 引入了整数类型、位操作 (bitwise operations)、UTF-8 支持。</li>
<li><strong>Lua 5.4:</strong> 引入了新的垃圾回收器、弱表 (weak tables) 的改进。</li>
</ul>
</li>
</ul>
</li>
<li><strong><code>Y</code> (次版本号/Minor Version) 或补丁版本 (Patch Version):</strong> 在主版本中,通常用于表示错误修复、性能优化或次要的功能增强。这些更改通常是<strong>向后兼容</strong>的(backward compatible),不会破坏现有代码运行。有时候,一个版本号会是 <code>X.Y.Z</code> 的形式,<code>Z</code> 就代表补丁版本。
<ul>
<li><strong>例子:</strong> <code>Lua 5.3.1</code>, <code>Lua 5.3.2</code>, <code>Lua 5.3.3</code> 等等。这些通常是修复 bug。</li>
</ul>
</li>
</ul>
<hr>
<h3 id="luajit">LuaJIT</h3>
<p><a href="https://github.com/LuaJIT/LuaJIT">https://github.com/LuaJIT/LuaJIT</a></p></description>
</item>
<item>
<title>KAIST-CS431: Safe Memory Reclamation</title>
<link>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-safe-memory-reclamation/</link>
<pubDate>Thu, 28 Aug 2025 13:09:43 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-safe-memory-reclamation/</guid>
<description><p>无锁数据结构和传统的数据结构在内存管理有一点区别是:当数据不用了,需要释放内存时,传统数据结构直接调用<code>free()</code>就可以了,但是无锁数据结构不行。举个例子,如下图所示,T1正在读取b1的时候,T2释放了b1,T1中的指针就变成了野指针,造成不安全的内存访问。</p>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-safe-memory-reclamation/image-20220410114205442.png" alt="image-20220410114205442"></p>
<p>因此,在无锁数据结构中,内存的释放操作一般要延后执行,即保证没有线程能够访问到该片内存后,再执行内存回收。这种方式被称作Safe Memory Reclamation(SMR)。</p>
<p>从上述例子中,我们可以从两个方面去理解一个SMR算法:</p>
<ol>
<li><strong>如何保护正在使用的数据不被释放?</strong></li>
<li><strong>什么时候能安全地释放已经被标记为需要释放的内存?</strong></li>
</ol>
<p>下面介绍两种常用的SMR算法:</p>
<ol>
<li>Hazard Pointers(Protected Pointers)</li>
<li>Epoch-Based Reclamation(基于代际的内存释放)</li>
</ol>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-safe-memory-reclamation/image-20220411074041712.png" alt="image-20220411074041712"></p>
<h2 id="hazard-pointers">Hazard Pointers</h2>
<p>Hazard Pointers的基本思想是<strong>线程访问数据块时会将指针记录下来,其它线程尝试释放内存时会感知到数据块引用的存在,从而延迟释放内存,直到所有线程对该数据块的访问结束。</strong></p>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-safe-memory-reclamation/image-20220411083606925.png" alt="image-20220411083606925"></p>
<p>如上图所示,使用hazard pointers有如下几个步骤:</p>
<ol>
<li>在线程访问共享数据时,先调用<code>reserve()</code>方法,将指针记录在一个list内;</li>
<li>T2线程尝试释放释放b1时,调用<code>retire()</code>,因为检测到还有线程访问b1,延迟释放内存;</li>
<li>因为内存没有真正释放,T1能在b1 retire后继续访问b1。访问结束后,T1调用<code>unreserve()</code>将指针从list中移除;</li>
<li>T3调用<code>gc()</code>(可以周期性运行,也可以事件触发),遍历所有retired的数据块,如果list内没有指向数据块的指针,即可以真正回收内存。</li>
</ol>
<p>Hazard Pointers有些致命的缺点:</p>
<ol>
<li>不够快。因为<code>reserve()</code>保护的指针需要立即被<code>gc()</code>感知到,所以,两个函数都需要引入开销非常大的store-load fence。在遍历整个数据结构时,每个数据块都会调用<code>reserve()</code>,导致遍历效率降低;</li>
<li>并不是所有的数据结构都适用Hazard Pointers。在前文的Lock-free Linked List中,我们也提过Harris-Michael‘s traversing是为了专门支持Hazard Pointers的。Harris&rsquo;s traversing会retire一个连续的数据块,这样,即便T1线程保护了其中一个数据块,<code>gc()</code>仍然可以回收其它的数据块内存,T1线程访问next指针时,仍然会出现问题。</li>
</ol>
<h2 id="epoch-based-reclamation">Epoch-Based Reclamation</h2>
<p>Hazard Pointers只保护无锁数据结构中真正被访问的那部分数据,这也导致了上文中Hazard Pointers的两个缺陷。与Hazard Pointers不同的是,<strong>EBR保护的是数据结构中所有可能潜在的访问</strong>。这样也就不需要频繁地在线程间同步。</p>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-safe-memory-reclamation/image-20220413090336739.png" alt="image-20220413090336739"></p>
<p>每个线程在访问数据结构前都会开启一个代际(epoch)。T1在代际e中retire了b1和b2。那么什么时候能安全地reclaim b1和b2的内存呢?</p>
<p>如上图,我们在这个EBR算法中规定了一个<strong>代际一致性规则:并发的代际之间最多只能相差1。<strong>也就是说,只要T1还没有调用<code>set_quiescent()</code>结束掉e代,那么其它线程通过<code>set_active()</code>开启的代际号只能是e+1。而所有的e+1代的线程是有可能引用到b1和b2的。当e代结束,T2开启e+2代时,因为e+2代和e代不存在</strong>同时发生</strong>的可能性(通过代际一致性规则保证),所以在e+2代是不可能访问到e代retire的b1和b2的。到了e+3代,表示所有可能访问到b1和b2的e+1代都已经结束,此时即可安全地回收b1和b2内存。</p>
<p>不同的代际一致性规则,可以安全回收的代际是不一样的。</p></description>
</item>
<item>
<title>KAIST-CS431: Concurrent Data Structure</title>
<link>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-concurrent-data-structure/</link>
<pubDate>Thu, 28 Aug 2025 13:09:25 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-concurrent-data-structure/</guid>
<description><h2 id="并发数据结构的关键点">并发数据结构的关键点</h2>
<ul>
<li><strong>安全性:</strong> 安全是并发程序对CDS的最基本的要求
<ul>
<li>Sequential specification:多线程对数据结构的操作要能像一个队列一样</li>
<li>Synchronization:如在栈操作中,不同的线程进行pushing和popping操作时是要同步的,不能这边线程执行完了,另外一边无法感知到这边的操作。</li>
<li><strong>通过锁或者更加底层的同步原语来保护并发数据结构</strong></li>
</ul>
</li>
<li><strong>可扩展性:</strong> 随着CPU核数/并发度的增长,有着良好的性能增长
<ul>
<li>理想情况下,性能增长应该是线性的,但现实情况往往因为各种限制因素达不到线性增长</li>
<li><strong>通过减少锁保护范围(更细粒度的锁)来减少竞争</strong>
<ul>
<li>hand-over-hand locking, lock coupling, read-write locking</li>
</ul>
</li>
<li><strong>通过避免写操作降低缓存失效</strong></li>
</ul>
</li>
<li><strong>Progress:</strong> guaranteeing the completion (or progress) of operations
<ul>
<li>Lock freedom: progress of at least one</li>
<li>Wait freedom: progress of everyone</li>
</ul>
</li>
</ul>
<h2 id="无锁策略">无锁策略</h2>
<p>Lock-Free 和 Wait-Free 都旨在解决传统锁(如互斥锁 mutex)带来的性能和活性问题,但采用了不同的策略和提供了不同的保证。</p>
<h3 id="lock-free">Lock-Free</h3>
<p>Lock-Free 是一个相对较弱的保证,但仍然非常强大和有用。它的核心思想是:<strong>总会有一个线程能够前进,即使其他线程被任意延迟或阻塞。</strong></p>
<p><strong>核心特点:</strong></p>
<ul>
<li><strong>没有死锁 (Deadlock-Free):</strong> 由于没有线程需要等待其他线程释放锁,所以不会发生死锁。</li>
<li><strong>没有活锁 (Livelock-Free):</strong> 虽然有可能发生活锁(即线程反复尝试但总是失败),但通常通过回退策略(如指数退避)或设计良好的原子操作序列可以避免。然而,严格意义上的 Lock-Free 并不直接保证 Livelock-Free。</li>
<li><strong>进度保证 (Progress Guarantee):</strong> 只要系统不是完全停滞,总有一个或多个线程可以完成操作。这意味着整个系统的吞吐量不会因为某个线程的无限期暂停而归零。</li>
<li><strong>饥饿可能 (Starvation Possible):</strong> 某个特定的线程可能会无限期地重试它的操作,而从未成功(即所谓的“饥饿”)。这是 Lock-Free 与 Wait-Free 的一个主要区别。</li>
<li><strong>实现方式:</strong> 主要依赖于原子操作,如<code>CAS (Compare-And-Swap)</code>,<code>FAA (Fetch-And-Add)</code>,<code>LL/SC (Load-Link/Store-Conditional)</code>等。这些操作通常由硬件提供,能够以原子方式读取、修改和写入内存位置,而无需使用操作系统级别的锁。</li>
<li><strong>常见数据结构:</strong> 无锁队列 (Lock-Free Queue),无锁栈 (Lock-Free Stack),无锁哈希表 (Lock-Free Hash Table) 等。</li>
</ul>
<p><strong>工作原理示例(CAS):</strong></p></description>
</item>
<item>
<title>KAIST-CS431: Lock Implementations</title>
<link>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-lock-implementations/</link>
<pubDate>Thu, 28 Aug 2025 12:52:58 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-lock-implementations/</guid>
<description><h2 id="naive-spin-lock">Naive Spin Lock</h2>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-lock-implementations/image-20220324081153922.png" alt="image-20220324081153922"></p>
<h3 id="naive-spin-lock的缺陷">Naive Spin Lock的缺陷</h3>
<ul>
<li>无法保证公平性,可能有的倒霉蛋空转了一辈子也无法cas成功,无法做到按竞争线程先来后到的次序占有锁。</li>
<li>扩展性差,大量对同一内存区域的自旋引发性能问题。</li>
</ul>
<h2 id="对naive-spin-lock的优化">对Naive Spin Lock的优化</h2>
<p>关键思路:</p>
<ul>
<li>通过release/acquire同步机制保证互斥量的排他性
<ul>
<li>从一个临界区的结束(release)到另一个临界区的开始(acquire)</li>
<li>在ticket lock里面是<code>curr</code>成员,CLH/MCS lock是每个waiter的一个新内存区域</li>
</ul>
</li>
<li>通过排队和等待不同的区域释放保证公平性
<ul>
<li>通过公平的指令来排队(如swap, fetch-and-add)</li>
<li>Ticket lock:通过<code>next</code>来排队,<code>curr</code>来等待锁</li>
<li>CLH/MCS lock:通过<code>tail</code>来排队,等待每个锁调用者申请的不同区域中值的变化来获取锁</li>
</ul>
</li>
</ul>
<h3 id="ticket-lock">Ticket Lock</h3>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-lock-implementations/image-20220323075751550.png" alt="image-20220323075751550"></p>
<p>ticket lock在锁的结构体里面新增了一个原子变量<code>next</code>,每次需要竞争锁时,需要先从<code>next</code>中拿到一个ticket,然后再去监听<code>curr</code>,只有<code>curr</code>被更新成当前的ticket值后,才能去占领锁。</p>
<h4 id="优点">优点</h4>
<ul>
<li>利用公平指令排队解决了公平性问题</li>
</ul>
<h4 id="缺点">缺点</h4>
<ul>
<li>API相对较复杂(调用者感知ticket)(为什么不直接用curr+1?)</li>
<li>没有解决spinlock中所有线程监听同一个原子变量的问题</li>
</ul>
<h3 id="clh-lock">CLH Lock</h3>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-lock-implementations/image-20220323082114800.png" alt="image-20220323082114800"></p>
<p>为了减少缓存一致性带来的开销,CLH lock被发明了。CLH是三个人首字母的缩写:Craig, Landin, and Hagersten。</p>
<p>CLH lock给所有等待线程分配了一个Node,每个Node初始为true,在锁内维护一个链表,所有竞争锁的线程都会获取到前一个线程的Node,并将<code>tail</code>指针指向自己的Node。</p>
<p>不同于spin lock和Ticket lock,CLH lock的临界区是前一个Node中的原子变量。在锁释放时,当前线程会将自己的Node值置为false,而排队的下一个线程监听到这个变化,则可以安全地占有锁了。</p>
<h4 id="优点-1">优点</h4>
<ul>
<li>线程监听的不同临界区,减少缓存一致性的开销</li>
<li>链表排队,也能保证公平性</li>
</ul>
<h4 id="缺点-1">缺点</h4>
<ul>
<li>O(n)的空间复杂度开销,n是临界区数目</li>
<li>每个线程都是在前驱节点的Node上自旋,如果跨NUMA会有性能问题</li>
</ul>
<h3 id="mcs-lock">MCS Lock</h3>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-lock-implementations/image-20220324080334262.png" alt="image-20220324080334262"></p>
<p>MCS lock也是以三个人名命名的:John M. Mellor-Crummey and Michael L. Scott。MCS lock和LCH lock最大的不同是:CLH lock是在前驱节点上自旋,MCS则是在自己节点上自旋。</p>
<p>在CLH的Node结构里面,MCS又添加了一个<code>next</code>字段,新的线程竞争锁时,会将自己的Node添加到前驱节点的<code>next</code>字段中。</p></description>
</item>
<item>
<title>KAIST-CS431: Promising Semantics</title>
<link>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-promising-semantics/</link>
<pubDate>Wed, 27 Aug 2025 13:40:27 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-promising-semantics/</guid>
<description><p>本章是基于作者的研究“Promising semantics”: <a href="https://sf.snu.ac.kr/promise-concurrency/">https://sf.snu.ac.kr/promise-concurrency/</a> ,提出的一种对宽松内存(relaxed-memory)并发的建模方法。</p>
<p>主要观点有4个:</p>
<ul>
<li>modeling load hoisting w/ <strong>multi-valued memory</strong>
<ul>
<li>允许一个线程从某个位置读取到一个旧值</li>
</ul>
</li>
<li>modeling read-modify-write w/ <strong>message adjacency</strong>
<ul>
<li>禁止对单个值同时进行多个read-modify-write操作</li>
</ul>
</li>
<li>modeling coherence &amp; ordering w/ <strong>views</strong>
<ul>
<li>限制线程的行为</li>
</ul>
</li>
<li>modeling store hoisting w/ <strong>promises</strong>
<ul>
<li>Allowing a thread to speculatively write a value</li>
</ul>
</li>
</ul>
<p>个人理解,**即便是编译器/硬件的指令重排,也是需要遵循一定的规则的,不能随意乱排。**作者从值读取、存储、read-modify-write多种角度对线程的行为进行了建模,是为了解释哪些情况下出现多线程执行出现哪些结果是可能的,哪些是不被允许的。</p>
<p>hoisting load/store在网上没有搜到解释,但是有个gcc的优化提到了这个概念。大概意思时将存值/取值操作从原先的指令顺序中调整位置,优化执行效率。</p>
<p><a href="https://gcc.gnu.org/news/hoist.html">Load/Store Hoisting - GNU Project</a></p>
<h2 id="multi-value-memory">multi-value memory</h2>
<p>内存是一系列消息(message)所在的位置,而消息可以看作是<strong>值和时间戳</strong>的组合。线程很有可能在读取时从内存中读到一个旧值。(effectively hoisting loads)</p>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-promising-semantics/image-20220318080820894.png" alt="image-20220318080820894"></p>
<p>在作者举的例子中,r1=r2=0是被允许的,因为r1 r2都有可能从Y X中读到一个旧值。从后文的view角度理解,因为在独立的线程中,X和Y的赋值并没有改变当前线程中相应Y和X的view,所以,r1 r2的读取操作是可以读到旧值的。</p>
<h2 id="message-adjacency">message adjacency</h2>
<p>上面说了,消息是值和时间戳范围的组合。read-modify-write操作修改了消息的值,在时间轴上应该和前值紧邻在一起(no gap)。</p>
<p><img src="https://blog.jinzhi.site/images/notes/kaist-cs431-promising-semantics/image-20220318081606125.png" alt="image-20220318081606125"></p>
<p>可以看到,两次fetch_add操作后,从X的视角上看,0、1、2是紧邻的。第二次fetch_add操作,只能紧贴着1操作,而不能插到0和1之间。</p>
<h2 id="views">views</h2>
<p>这个semantics对我是启发性最强的一章。</p>
<p>multi-valued memory允许了太多不在预期中的行为,因此我们需要做些限制,保证一致性和同步。</p>
<p>View分为三种,分别是:</p>
<ul>
<li>Per-thread view:一致性</li>
<li>Per-message view:release/acquire同步</li>
<li>Global view:SC同步</li>
</ul>
<h3 id="per-thread-view">Per-thread view</h3>
<p>Per-thread view表示线程对消息的确认。</p></description>
</item>
<item>
<title>KAIST-CS431: Nondeterminisms of Shared-memory Concurrency</title>
<link>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-nondeterminisms-of-shared-memory-concurrency/</link>
<pubDate>Wed, 27 Aug 2025 13:31:59 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-nondeterminisms-of-shared-memory-concurrency/</guid>
<description><h2 id="nondeterminism">Nondeterminism</h2>
<h3 id="thread-interleaving">thread interleaving</h3>
<p><strong>interleaving semantics:</strong> 将多线程的指令交替组合成好像是单线程执行一样。</p>
<p>不同线程间的Load/store指令是穿插执行的,导致最终的行为有多种多样的可能。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">static</span><span class="w"> </span><span class="no">COUNTER</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">AtomicUsize</span>::<span class="n">new</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// thread A &amp; B
</span></span></span><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">COUNTER</span><span class="p">.</span><span class="n">load</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="no">COUNTER</span><span class="p">.</span><span class="n">store</span><span class="p">(</span><span class="n">c</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>如上示例,两个线程A和B同时对COUNTER进行+1操作,预期结果当然是2。但是<strong>由于线程调度的不确定性</strong>可能出现如下的执行顺序:</p>
<pre tabindex="0"><code>[COUNTER=0] A load, B load, A store, B store [COUNTER=1]
</code></pre><p>导致结果不符合预期。</p>
<h4 id="解决方案">解决方案</h4>
<p>使用原子的reading &amp; writing,禁止掉这种不符预期的执行顺序。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="c1">// thread A &amp; B
</span></span></span><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="no">COUNTER</span><span class="p">.</span><span class="n">fetch_and_add</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><strong>“Read-modify-write”</strong>, e.g. swap, compare-and-swap, fetch-and-add</li>
</ul>
<h3 id="reordering">reordering</h3>
<p><strong>同一个线程中的指令会因为硬件和编译器的优化发生指令重排。</strong></p>
<pre tabindex="0"><code>DATA = 42; || if FLAG.load() {
FLAG.store(1); || assert(DATA == 42);
 || }
</code></pre><p>如上图示例,预期是当<code>FLAG.load()</code>为1时,<code>DATA == 42</code>。但是因为指令重排,左边线程中,<code>FLAG.store(1)</code>可能发生在赋值语句前面;右边线程中assert语句也可能发生在if语句前面。</p></description>
</item>
<item>
<title>KAIST-CS431: Lock Based API</title>
<link>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-lock-based-api/</link>
<pubDate>Wed, 27 Aug 2025 12:58:54 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-08/kaist-cs431-lock-based-api/</guid>
<description><h2 id="标准库中的并发api">标准库中的并发API</h2>
<p>Rust 标准库中基于锁的 API 主要围绕几个核心原语构建,这些原语提供了不同级别的并发控制和用途。它们都包含在 <code>std::sync</code> 模块中。以下是一些最常用的基于锁的 API:</p>
<ol>
<li><strong><code>std::sync::Mutex&lt;T&gt;</code> (互斥锁)</strong>
<ul>
<li><strong>用途:</strong> 最常见的互斥锁,用于保护共享数据,确保一次只有一个线程可以访问该数据。</li>
<li><strong>特点:</strong>
<ul>
<li>当线程尝试获取已被锁定的 Mutex 时,它会阻塞直到锁被释放。</li>
<li>提供内部可变性(<code>&amp;T</code> -&gt; <code>&amp;mut T</code>)通过 RAII (Resource Acquisition Is Initialization) 机制,即 <code>MutexGuard</code>。当 <code>MutexGuard</code> 离开作用域时,锁会自动释放。</li>
<li>是“poisoning”感知的:如果持有锁的线程在锁被释放前发生 panic,<code>Mutex</code> 会被标记为 poisoned。后续尝试获取锁的线程会得到一个 <code>PoisonError</code>,其中包含原始的 <code>MutexGuard</code>,允许它们决定如何处理被中断的数据。</li>
</ul>
</li>
<li><strong>何时使用:</strong> 当你需要独占访问某个共享资源时,例如全局计数器、数据结构或配置设置。</li>
</ul>
</li>
<li><strong><code>std::sync::RwLock&lt;T&gt;</code> (读写锁)</strong>
<ul>
<li><strong>用途:</strong> 允许多个读取者同时访问共享数据,但只允许一个写入者独占访问数据。</li>
<li><strong>特点:</strong>
<ul>
<li><strong>读取者(<code>read()</code>):</strong> 允许多个线程并行获取读锁。只要没有写入者持有锁,所有读锁请求都会成功。</li>
<li><strong>写入者(<code>write()</code>):</strong> 只允许一个线程获取写锁。当有写入者持有锁时,所有读锁和写锁请求都会阻塞。</li>
<li>也提供 RAII 机制,通过 <code>RwLockReadGuard</code> 和 <code>RwLockWriteGuard</code>。</li>
<li>同样是“poisoning”感知的。</li>
</ul>
</li>
<li><strong>何时使用:</strong> 当你的数据被频繁读取但很少写入时,<code>RwLock</code> 可以提供比 <code>Mutex</code> 更好的并发性能。例如,一个缓存系统或一个配置对象。</li>
</ul>
</li>
<li><strong><code>std::sync::Once</code> (只运行一次)</strong>
<ul>
<li><strong>用途:</strong> 确保某个代码块(一个初始化函数)在程序生命周期中只被执行一次,即使有多个线程同时尝试触发它。</li>
<li><strong>特点:</strong>
<ul>
<li><code>call_once()</code> 方法会执行一个闭包。第一次调用会实际执行闭包,后续的调用会等待第一次调用完成,但不会再次执行闭包。</li>
<li>通常用于惰性初始化全局数据或单例模式。</li>
</ul>
</li>
<li><strong>何时使用:</strong> 初始化全局静态变量(例如日志系统、配置加载器)或实现单例模式。通常与 <code>lazy_static</code> crate (在稳定版 Rust 中) 或 <code>std::sync::OnceLock</code> (在 1.70+ 版本中,见下文) 结合使用。</li>
</ul>
</li>
<li><strong><code>std::sync::Barrier</code> (屏障)</strong>
<ul>
<li><strong>用途:</strong> 用于同步一组线程,确保所有线程都到达某个预定义点后才能继续执行。</li>
<li><strong>特点:</strong>
<ul>
<li>通过 <code>wait()</code> 方法实现等待。当调用 <code>wait()</code> 的线程数量达到预设值时,所有等待的线程都会同时被释放。</li>
<li><code>wait()</code> 返回一个 <code>BarrierWaitResult</code>,指示当前线程是否是最后一个到达屏障的。</li>
</ul>
</li>
<li><strong>何时使用:</strong> 需要协调多个并行任务的执行,例如在某个阶段结束后开始下一阶段,或者在所有子任务完成后进行汇总。</li>
</ul>
</li>
</ol>
<hr>
<p><strong>Rust 1.70+ 中引入的更现代的基于锁的 API:</strong></p></description>
</item>
<item>
<title>Wasm Internals - Overview</title>
<link>https://blog.jinzhi.site/posts/2025-08/wasm-internals-overview/</link>
<pubDate>Tue, 26 Aug 2025 12:29:03 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-08/wasm-internals-overview/</guid>
<description><ul>
<li><a href="https://rsms.me/wasm-intro">https://rsms.me/wasm-intro</a></li>
<li><a href="https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/">https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/</a></li>
</ul>
<h2 id="wasm-的历史发展">Wasm 的历史发展</h2>
<h3 id="早期wasm-mvp---2017">早期(Wasm MVP - 2017)</h3>
<ul>
<li><strong>诞生背景:</strong> Wasm 的设计目标是为了替代 asm.js,提供更小、更快、更安全的 Web 二进制格式。</li>
<li><strong>核心模块的初步定义:</strong> MVP(Minimum Viable Product)阶段定义了 Wasm Core Module 的基本结构:函数、内存、表、导入、导出、全局变量等。</li>
<li><strong>主要用例:</strong> 游戏引擎、音视频编解码、计算密集型任务等。</li>
<li><strong>限制:</strong>
<ul>
<li><strong>没有模块化系统:</strong> 模块之间没有标准的链接机制,只能通过宿主环境(如 JavaScript)进行协调。</li>
<li><strong>缺乏垃圾回收(GC):</strong> 需要手动内存管理或使用语言自带的 GC 机制(如 Emscripten 的 mimalloc)。</li>
<li><strong>没有线程:</strong> 无法直接利用多核 CPU。</li>
<li><strong>没有宿主 API 标准化:</strong> 模块与宿主环境的交互方式高度依赖宿主(如浏览器),没有统一的接口定义。</li>
<li><strong>没有组件模型:</strong> 模块重用和组合非常困难。</li>
</ul>
</li>
</ul>
<h3 id="中期mvp-之后---持续演进">中期(MVP 之后 - 持续演进)</h3>
<p>Wasm 社区和工作组认识到 MVP 的局限性,并开始着手扩展 Wasm 的能力,这直接影响了 Core Module 的能力:</p>
<ul>
<li><strong>多值(Multiple Returns &amp; Parameters):</strong> 允许函数返回多个值,接收多个参数,提高表达能力。</li>
<li><strong>引用类型(Reference Types):</strong> 引入了 <code>externref</code> 和 <code>funcref</code>,允许 Wasm 直接引用宿主对象和函数,而无需通过数字 ID 传递,为未来的 GC 和组件模型打下基础。</li>
<li><strong>固定大小的 SIMD(Fixed-width SIMD):</strong> 引入了新的指令集,允许在 Wasm 中进行向量化操作,进一步提升某些计算密集型任务的性能。</li>
<li><strong>线程(Threads):</strong> 引入了共享内存和原子操作,允许 Wasm 模块在多线程环境下运行,极大地提升了并行计算能力。</li>
<li><strong>内存增长和限制(Memory Growth and Limits):</strong> 提供了更灵活的内存管理机制。</li>
<li><strong>Tail Calls(尾调用):</strong> 优化了函数调用的性能。</li>
</ul>
<h3 id="近期和未来wasm-component-model">近期和未来(Wasm Component Model)</h3>
<p>这是 Wasm 发展中最重要的方向之一,旨在解决 Core Module 在模块化和互操作性方面的根本性问题:</p></description>
</item>
<item>
<title>About</title>
<link>https://blog.jinzhi.site/about/</link>
<pubDate>Fri, 22 Aug 2025 13:30:45 +0800</pubDate>
<guid>https://blog.jinzhi.site/about/</guid>
<description></description>
</item>
<item>
<title>Wasm Internals: Stack Machine</title>
<link>https://blog.jinzhi.site/posts/2025-08/wasm-internals-stack-machine/</link>
<pubDate>Wed, 20 Aug 2025 01:04:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-08/wasm-internals-stack-machine/</guid>
<description><p>Wasm 中的“栈机”(Stack Machine),这正是其核心执行模型之一。Wasm 是一种基于栈的虚拟机,这意味着它的所有操作都通过从一个操作数栈中弹出值、执行操作并将结果压回栈中来完成。它没有传统的“寄存器”概念。</p>
<h2 id="什么是栈机">什么是栈机?</h2>
<p>在计算机科学中,栈机是一种计算模型,其中指令操作数被隐含地从一个被称为“操作数栈”的内存区域中获取,并且结果被隐含地压回这个栈。这种模型与基于寄存器或基于累加器的模型形成对比。</p>
<h2 id="wasm-栈机的工作原理">Wasm 栈机的工作原理</h2>
<p>Wasm 模块中的函数是由一系列指令组成的。这些指令会操作一个中央的操作数栈。</p>
<ol>
<li>
<p><strong>操作数栈(Operand Stack)</strong>:</p>
<ul>
<li>这是 Wasm 执行函数时最核心的数据结构。</li>
<li>所有的操作数(如整数、浮点数)和操作结果都临时存储在这个栈上。</li>
<li>指令不会像在注册机中那样直接指定操作数的位置(如“将 R1 的值加到 R2”)。相反,它们会假定操作数已经在栈的顶部。</li>
</ul>
</li>
<li>
<p><strong>局部变量(Local Variables)</strong>:</p>
<ul>
<li>除了操作数栈,每个函数调用还有一个独立的“局部变量”区域。</li>
<li>局部变量是命名的存储位置,可以在函数的整个执行过程中被访问和修改。</li>
<li>虽然局部变量不是栈的一部分,但有很多指令允许你将局部变量的值压入栈中,或者将栈顶部的值存储到局部变量中。</li>
</ul>
</li>
<li>
<p><strong>参数(Parameters)</strong>:</p>
<ul>
<li>函数的参数在函数被调用时,会被初始化为局部变量的一部分(通常是前几个局部变量)。</li>
<li>它们也可以被认为是函数执行上下文的一部分。</li>
</ul>
</li>
<li>
<p><strong>指令的操作</strong>:</p>
<ul>
<li><strong>压栈(Push)</strong>:很多指令会将值压入栈中。例如:
<ul>
<li><code>i32.const 42</code>:将整数常量 <code>42</code> 压入栈。</li>
<li><code>local.get &lt;idx&gt;</code>:获取索引为 <code>&lt;idx&gt;</code> 的局部变量的值并压入栈。</li>
</ul>
</li>
<li><strong>弹栈(Pop)</strong>:大多数操作指令会从栈顶弹出所需数量的操作数。例如:
<ul>
<li><code>i32.add</code>:弹出栈顶的两个 <code>i32</code> 整数,将它们相加,然后将结果压回栈。</li>
<li><code>if</code>/<code>else</code>/<code>loop</code> 等控制流指令的条件值也会从栈中弹出。</li>
</ul>
</li>
<li><strong>复合操作</strong>:一些指令可能弹出一个值,执行一些副作用(如内存写入),而不压入任何新值。例如:
<ul>
<li><code>i32.store</code>:弹出内存地址和要存储的值,将值写入内存。</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="栈机模型的优势与特点">栈机模型的优势与特点</h2>
<ol>
<li>
<p><strong>紧凑性(Compactness)</strong>:</p>
<ul>
<li>指令更短,因为它们不需要编码操作数的位置。例如,一个加法操作,在寄存器机中可能需要指定两个源寄存器和一个目标寄存器;在栈机中,它只是一个简单的 <code>add</code> 指令。</li>
<li>这有助于生成更小的二进制代码,对于 Web 环境中的快速下载和解析非常有利。</li>
</ul>
</li>
<li>
<p><strong>简化编译器后端(Simplified Compiler Backends)</strong>:</p>
<ul>
<li>IR(中间表示)到指令的映射通常更直接。许多高级语言的语义本身就可以很容易地映射到栈操作。</li>
<li>这使得将 C/C++/Rust 等语言编译到 Wasm 变得相对容易。</li>
</ul>
</li>
<li>
<p><strong>易于验证(Easy to Validate)</strong>:</p>
<ul>
<li>Wasm 在加载时会进行严格的类型检查和结构验证。栈机模型使得验证其类型安全变得相对容易。例如,当检查 <code>i32.add</code> 指令时,验证器只需确保栈顶有两个 <code>i32</code> 类型的值。</li>
<li>这对于沙盒环境中的安全性至关重要。</li>
</ul>
</li>
<li>
<p><strong>独立于目标架构(Architecture-Independent)</strong>:</p></description>
</item>
<item>
<title>Kubernetes CSI 简介</title>
<link>https://blog.jinzhi.site/posts/2025-06/kubernetes-csi-%E7%AE%80%E4%BB%8B/</link>
<pubDate>Tue, 24 Jun 2025 09:57:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-06/kubernetes-csi-%E7%AE%80%E4%BB%8B/</guid>
<description><p>K8s CSI 是 Kubernetes 中非常重要的一个组件,它解决了存储与计算分离的复杂性,并为容器化应用提供了持久化存储的能力。</p>
<h2 id="1-基本概念">1. 基本概念</h2>
<p><strong>CSI (Container Storage Interface)</strong> 译为 <strong>容器存储接口</strong>。它是由 Kubernetes 社区与存储厂商共同制定的一套标准接口规范。</p>
<p>在 CSI 出现之前,Kubernetes 存储插件的开发和管理存在以下痛点:</p>
<ul>
<li><strong>紧耦合问题:</strong> Kubernetes 内部集成了大量的存储驱动(In-tree 存储插件),例如 AWS EBS、GCE PD、Azure Disk、Ceph RBD 等。这意味着每当存储厂商需要支持 Kubernetes 时,他们都必须将其存储驱动代码提交到 Kubernetes 的核心代码库中。这种方式导致了:
<ul>
<li><strong>Kubernetes 代码库臃肿:</strong> 集成了大量存储逻辑,增加了核心代码的复杂性和维护难度。</li>
<li><strong>发布周期长:</strong> 存储驱动的更新需要跟随 Kubernetes 的发布周期,新功能和 bug 修复不能及时推送到用户。</li>
<li><strong>存储厂商开发受限:</strong> 每次更新都需要与 Kubernetes 社区协调,开发和测试流程繁琐。</li>
</ul>
</li>
<li><strong>兼容性问题:</strong> 不同存储厂商的存储系统差异巨大,缺乏统一的接口规范,导致存储系统与 Kubernetes 之间的集成困难。</li>
</ul>
<p><strong>CSI 的目标就是解决这些问题,实现存储系统与 Kubernetes 的解耦。</strong> 它定义了一套通用的接口,允许任何存储厂商开发自己的 CSI 驱动,然后通过这些驱动来与 Kubernetes 进行交互,从而为容器提供存储服务。</p>
<h3 id="11-资源定义">1.1. 资源定义</h3>
<h3 id="111-volumes">1.1.1. Volumes</h3>
<p>Volumes 是 Kubernetes 中 Pod 通过文件系统访问和共享数据的抽象。它主要提供了如下功能:</p>
<ul>
<li>通过 ConfigMap 或者 Secret 共享配置;</li>
<li>跨容器、跨 Pod 甚至跨 Node 共享数据;</li>
<li>数据持久化。在 Pod 销毁之后仍能继续访问数据。
对于 Pod 来说,Volumes 通过 <code>.spec.volumes</code> 提供给 Pod,容器通过 <code>.spec.containers[*].volumeMounts</code> 来将指定 Volumes 挂载到指定目录。</li>
</ul>
<h3 id="112-persistent-volumes-和-persistent-volumes-claim">1.1.2. Persistent Volumes 和 Persistent Volumes Claim</h3>
<p>PV 和 PVC 提供了两套 API 将存储的提供和消费分离。</p></description>
</item>
<item>
<title>Spurious Retransmit 导致 NAT 设备响应 RST</title>
<link>https://blog.jinzhi.site/posts/2025-06/spurious-retransmit-%E5%AF%BC%E8%87%B4-nat-%E8%AE%BE%E5%A4%87%E5%93%8D%E5%BA%94-rst/</link>
<pubDate>Fri, 13 Jun 2025 09:57:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-06/spurious-retransmit-%E5%AF%BC%E8%87%B4-nat-%E8%AE%BE%E5%A4%87%E5%93%8D%E5%BA%94-rst/</guid>
<description><p>在一个线上问题排查过程中,出现了一个新的问题:wget 下载文件在连接建立很短的时间内(1 - 2s)就被 RESET 了。
首先先简要说明一下链路:</p>
<p><img src="https://blog.jinzhi.site/images/notes/spurious-retransmit-%E5%AF%BC%E8%87%B4-nat-%E8%AE%BE%E5%A4%87%E5%93%8D%E5%BA%94-rst/5035a324e7b30c51292018e81c2e41b6_MD5.jpeg" alt=""></p>
<p>客户端经过一个SNAT设备出公网,在服务器上下载一个大文件。端到端的 RTT 大约 90 ms。由于最开始并没有怀疑到 SNAT 设备,并且 server 端无法抓包,我们分别在客户端 和 SNAT 设备到 server 间一台网元设备上抓包。
<img src="https://blog.jinzhi.site/images/notes/spurious-retransmit-%E5%AF%BC%E8%87%B4-nat-%E8%AE%BE%E5%A4%87%E5%93%8D%E5%BA%94-rst/adc7d13285d32bfc4c287a8d5afd1da1_MD5.jpeg" alt="中间网元设备"></p>
<p><img src="https://blog.jinzhi.site/images/notes/spurious-retransmit-%E5%AF%BC%E8%87%B4-nat-%E8%AE%BE%E5%A4%87%E5%93%8D%E5%BA%94-rst/a9850b914275bd0d8c5c53f1dfd3b2d4_MD5.jpeg" alt="SNAT 设备"></p>
<p>如上,中间网元设备显示客户端(端口 34567)先发送了 RST 报文,随后服务器(端口 443)响应 RST 报文。但是客户端抓包显示它根本没有发送过 RST 报文。</p>
<p>此时,我们再从 SNAT 设备上抓包,发现就是 SNAT 设备首先给服务器发送了 RST 报文。</p>
<p>这里的 SNAT 设备实际上是一台基于 IPtables 的 Linux 设备。</p>
<p><img src="https://blog.jinzhi.site/images/notes/spurious-retransmit-%E5%AF%BC%E8%87%B4-nat-%E8%AE%BE%E5%A4%87%E5%93%8D%E5%BA%94-rst/bc892bf5d9ba1a52cb9c0ea3baf99b64_MD5.jpg" alt="">
抓包可以看到,RST 是对一个 TCP Spurious Retransmission 的响应。为什么会发出这个 RST 呢?正好网上有一个相似的场景:<a href="https://github.com/moby/libnetwork/issues/1090">Add workaround for spurious retransmits leading to connection resets</a></p>
<p><img src="https://blog.jinzhi.site/images/notes/spurious-retransmit-%E5%AF%BC%E8%87%B4-nat-%E8%AE%BE%E5%A4%87%E5%93%8D%E5%BA%94-rst/1f45fd40111f9e29fb5a47fd8e1baad0_MD5.jpeg" alt=""></p>
<p>总的来说,就是 spurious retransmits 报文在序列号超出 TCP 窗口时,会被 conntrack 认为是 invalid 包,从而不再经过反向 SNAT 规则的处理。由于目的地址没有被转换,报文会按照原来的目的地址送往 INPUT,而本地又没有这个 socket,则响应一个 RST 报文。</p></description>
</item>
<item>
<title>VRF: An Overview</title>
<link>https://blog.jinzhi.site/posts/2025-04/vrf-an-overview/</link>
<pubDate>Fri, 25 Apr 2025 20:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-04/vrf-an-overview/</guid>
<description><h2 id="什么是-vrfvirtual-routing-forwarding">什么是 VRF(virtual routing forwarding)</h2>
<p>VRF是一种实现三层网络隔离的关键技术。<strong>它通过创建多个路由表,为不同的网络流量提供独立的转发路径。</strong> 这意味着,任何三层网络结构,如接口的IP地址、静态路由的配置,甚至BGP(边界网关协议)会话,都可以被映射到特定的VRF中。这种映射机制就像是为每个VRF构建了一个独立的网络空间,彼此之间相互隔离,极大地增强了网络的安全性和管理的便利性。在MPLS VPN(多协议标签交换虚拟专用网络)等应用场景中,VRF为实现大规模的网络隔离和灵活的路由策略提供了基础框架。就像 VLAN 隔离了二层网络一样,VRF 隔离了三层网络。</p>
<p><img src="https://blog.jinzhi.site/images/notes/vrf-an-overview/5eb20c3e919fe3724b92c2ae7a66a7da_MD5.png" alt="5eb20c3e919fe3724b92c2ae7a66a7da_MD5"></p>
<h2 id="为什么需要-vrf">为什么需要 VRF</h2>
<p>在 VRF 出现之前,Linux 用户主要采用两种方式来尝试实现类似的功能:策略路由(policy routing)和网络命名空间(net namespace)。然而,这两种方法都存在明显的局限性。</p>
<p>策略路由虽然能够通过多个路由表和策略规则来模拟 VRF 的部分功能(事实上,在 Linux 中,也是基于策略路由来去对 VRF 做的实现),但它的缺点十分突出。这种方式在配置和管理上非常复杂,难以确保网络隔离的有效性,在面对严格的网络审计时,往往无法通过。其复杂性不仅增加了运维的难度,还可能导致网络故障的风险上升,因此不被推荐使用。</p>
<p>网络命名空间在容器技术兴起后得到了广泛应用,它能够为容器提供全面的网络隔离。但在模拟 VRF 功能时,却显得有些“大材小用”。网络命名空间会对所有网络相关的资源进行完全隔离,包括设备、接口、ARP 表和路由表等。这意味着,即使是一些不需要隔离的服务,也会被隔离在不同的命名空间中。以 LLDP(链路层发现协议)为例,在使用网络命名空间的情况下,若要在不同的网络隔离环境中使用 LLDP,就需要在每个命名空间中单独运行实例,并且由于默认套接字相同,还需要为每个实例创建独特的套接字。这不仅增加了系统的开销,还使得管理变得更加复杂。相比之下,VRF 在隔离三层网络结构的同时,允许全局配置的共享和非三层感知服务的统一运行,大大提高了资源的利用效率。</p>
<table>
	<thead>
			<tr>
					<th>Policy Routing</th>
					<th>VRF</th>
					<th>Net Namespace</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>隔离路由表</td>
					<td>隔离三层网络</td>
					<td>整个协议栈从二层到 socket 隔离</td>
			</tr>
	</tbody>
</table>
<p><img src="https://blog.jinzhi.site/images/notes/vrf-an-overview/a9f7c55f8f39572d339b138fb1e12429_MD5.png" alt="a9f7c55f8f39572d339b138fb1e12429_MD5"><img src="https://blog.jinzhi.site/images/notes/vrf-an-overview/c9b6614c864c7d35a8ef0a4f12ecdbfa_MD5.png" alt="c9b6614c864c7d35a8ef0a4f12ecdbfa_MD5"><img src="https://blog.jinzhi.site/images/notes/vrf-an-overview/9edc8b051f504bf72140d1238513d687_MD5.png" alt="9edc8b051f504bf72140d1238513d687_MD5"></p>
<h2 id="vrf-配置">VRF 配置</h2>
<p>在 Linux 系统中配置 VRF,主要借助<code>iproute2</code>包来完成一系列操作。</p>
<ul>
<li>创建 VRF,并关联到 table 1</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">test1@test1:~$ ip link add vrf-1 <span class="nb">type</span> vrf table <span class="m">1</span>
</span></span><span class="line"><span class="cl">test1@test1:~$ ip link <span class="nb">set</span> vrf-1 up
</span></span></code></pre></div><ul>
<li>添加接口到 VRF,可以看到 wg0 的 master 是 vrf-1,所有 wg0 的流量会使用关联的 vrf-1 路由表进行路由</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">test1@test1:~$ ip link <span class="nb">set</span> wg0 master vrf-1
</span></span><span class="line"><span class="cl">test1@test1:~$ ip -d link show wg0 
</span></span><span class="line"><span class="cl">9: wg0: &lt;POINTOPOINT,NOARP,UP,LOWER_UP&gt; mtu <span class="m">1400</span> qdisc noqueue master vrf-1 state UNKNOWN mode DEFAULT group default qlen <span class="m">1000</span>
</span></span><span class="line"><span class="cl"> link/none promiscuity <span class="m">0</span> minmtu <span class="m">0</span> maxmtu <span class="m">2147483552</span> 
</span></span><span class="line"><span class="cl"> wireguard 
</span></span><span class="line"><span class="cl"> vrf_slave table <span class="m">1</span> addrgenmode none numtxqueues <span class="m">1</span> numrxqueues <span class="m">1</span> gso_max_size <span class="m">65536</span> gso_max_segs <span class="m">65535</span>
</span></span></code></pre></div><ul>
<li>添加和查看 VRF 静态路由</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">root@test1:~# ip route add default via 10.1.0.10 vrf vrf-1
</span></span><span class="line"><span class="cl">root@test1:~# ip route show table <span class="m">1</span> 
</span></span><span class="line"><span class="cl">default via 10.1.0.10 dev wg0 
</span></span><span class="line"><span class="cl"><span class="nb">local</span> 10.1.0.10 dev wg0 proto kernel scope host src 10.1.0.10 
</span></span><span class="line"><span class="cl">root@test1:~# ip route show vrf vrf-1 
</span></span><span class="line"><span class="cl">default via 10.1.0.10 dev wg0 
</span></span></code></pre></div><h3 id="vrf-之间路由">VRF 之间路由</h3>
<p>有两种方法可以执行跨 VRF 路由。第一种方法涉及一个 VRF 的表中配置的路由,指向绑定到不同 VRF 的设备。</p></description>
</item>
<item>
<title>C³: Common Coding Conventions</title>
<link>https://blog.jinzhi.site/posts/2025-03/c-common-coding-conventions/</link>
<pubDate>Sat, 29 Mar 2025 13:52:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2025-03/c-common-coding-conventions/</guid>
<description><h1 id="c-common-coding-conventions">C³: Common Coding Conventions</h1>
<p><a href="https://google.github.io/eng-practices/">https://google.github.io/eng-practices/</a></p>
<p><a href="https://github.com/tum-esi/common-coding-conventions">https://github.com/tum-esi/common-coding-conventions</a></p>
<p>The goal of these conventions is to be concise, universal, and remarkable. It targets emerging code enthusiasts under time pressure and covers 7 topics:</p>
<ol>
<li><a href="https://blog.jinzhi.site/posts/2025-03/c-common-coding-conventions/#user-content-general-clarifications">General Clarifications</a>,</li>
<li><a href="https://blog.jinzhi.site/posts/2025-03/c-common-coding-conventions/#user-content-architecture">Architecture</a>,</li>
<li><a href="https://blog.jinzhi.site/posts/2025-03/c-common-coding-conventions/#user-content-implementation">Implementation</a>,</li>
<li><a href="https://blog.jinzhi.site/posts/2025-03/c-common-coding-conventions/#user-content-naming">Naming</a>,</li>
<li><a href="https://blog.jinzhi.site/posts/2025-03/c-common-coding-conventions/#user-content-code-layout">Code Layout</a>,</li>
<li><a href="https://blog.jinzhi.site/posts/2025-03/c-common-coding-conventions/#user-content-documentation">Documentation</a>, and</li>
<li><a href="https://blog.jinzhi.site/posts/2025-03/c-common-coding-conventions/#user-content-languages">Languages</a>.</li>
</ol>
<p>To follow this guide, you should already have heard about <a href="https://www.educative.io/blog/object-oriented-programming">Object Oriented Programming</a> and know basic programming rules, such as writing loops and meaningful functions instead of copy pasting
instructions. In this Readme, we will shortly summarize the most important rules for every topic.</p></description>
</item>
<item>
<title>eBPF 指令集规范</title>
<link>https://blog.jinzhi.site/posts/2024-01/ebpf-%E6%8C%87%E4%BB%A4%E9%9B%86%E8%A7%84%E8%8C%83/</link>
<pubDate>Fri, 05 Jan 2024 18:06:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2024-01/ebpf-%E6%8C%87%E4%BB%A4%E9%9B%86%E8%A7%84%E8%8C%83/</guid>
<description><p><a href="https://docs.kernel.org/bpf/standardization/instruction-set.html">1 BPF Instruction Set Specification, v1.0 — The Linux Kernel documentation</a></p>
<h2 id="寄存器和调用约定">寄存器和调用约定</h2>
<p>eBPF有10个通用寄存器和一个只读的帧指针寄存器,所有的寄存器都是64bits位长。</p>
<p>eBPF的调用约定如下:</p>
<ul>
<li><strong>R0:</strong> 函数调用的返回值和eBPF程序的退出值</li>
<li><strong>R1 - R5:</strong> 函数调用的参数</li>
<li><strong>R6 - R9:</strong> callee saved registers,由函数调用负责保存到栈上</li>
<li><strong>R10:</strong> 只读的帧指针,用于访问栈
R0 - R5是临时寄存器,eBPF程序在函数调用时如需使用需要将它们保存/填充。</li>
</ul>
<h2 id="指令编码">指令编码</h2>
<p>eBPF使用64bit指令,编码如下:</p>
<table>
	<thead>
			<tr>
					<th><strong>32位(最高有效位)</strong></th>
					<th><strong>16位</strong></th>
					<th><strong>4位</strong></th>
					<th><strong>4位</strong></th>
					<th><strong>8位(最低有效位)</strong></th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>immediate</td>
					<td>offset</td>
					<td>source register</td>
					<td>dest register</td>
					<td>opcode</td>
			</tr>
	</tbody>
</table>
<ul>
<li>imm: 有符号整型立即数</li>
<li>offset: 有符号整型数,用于指针算术</li>
<li>src_reg: 除开特殊指令的编码外(如64位立即数指令)指的是源寄存器(R0 - R10)</li>
<li>dst_reg: 目标寄存器(R0 - R10)</li>
<li>opcode: 操作码</li>
</ul>
<p>绝大多数指令不会使用所有的字段。没有使用的字段应当被置0。多字节的字段(如imm和offset)在大端BPF中以大端字节序存储,小端BPF中以小端字节序存储。</p>
<pre tabindex="0"><code>opcode offset imm assembly
 src_reg dst_reg
07 0 1 00 00 44 33 22 11 r1 += 0x11223344 // little
 dst_reg src_reg
07 1 0 00 00 11 22 33 44 r1 += 0x11223344 // big
</code></pre><p>除了这些基本指令编码外,eBPF还有一种宽指令编码,在基本指令之后使用第2个64位立即数做扩展。第2个立即数包括一个伪指令,和基本指令的格式一样,不过所有的字段(除了imm)都置为0。它的imm作为整个宽指令中的imm64值的高32位:</p></description>
</item>
<item>
<title>eBPF 全局变量的实现</title>
<link>https://blog.jinzhi.site/posts/2024-01/ebpf-%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F%E7%9A%84%E5%AE%9E%E7%8E%B0/</link>
<pubDate>Tue, 02 Jan 2024 19:46:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2024-01/ebpf-%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F%E7%9A%84%E5%AE%9E%E7%8E%B0/</guid>
<description><p>Linux内核在v5.2支持了eBPF全局变量(见<a href="https://www.yuque.com/chengzhycn/hx665b/cmbgkdheaf0wyqht#vVcxE">v5.2</a>)。</p>
<p>全局变量会被Clang编译器放入<code>.bss</code>, <code>.data</code>, <code>.rodata</code> sections。那么支持全局变量即如何把这些sections中的数据加载进内核,并且在relocation时,给访问这些变量的指令正确的地址。</p>
<h2 id="早期-workaround">早期 workaround</h2>
<p>关于 eBPF 静态变量支持的讨论在<a href="https://lpc.events/event/2/contributions/115/">Linux Plumbers Conference 2018</a> 由Cilium提出。早期的 workaround 如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;linux/bpf.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">__u32</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="kt">long</span> <span class="kt">long</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">__64</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#ifndef __section
</span></span></span><span class="line"><span class="cl"><span class="cp"># define __section(NAME)				\
</span></span></span><span class="line"><span class="cl"><span class="cp">	__attribute__((section(NAME), used))
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl"><span class="cp">#ifndef __fetch
</span></span></span><span class="line"><span class="cl"><span class="cp"># define __fetch(x) (__u32)(__u64)(&amp;(x))
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">__u32</span> <span class="n">foo</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span> 			<span class="c1">// .data section
</span></span></span><span class="line"><span class="cl"><span class="c1">// __u32 foo; 			// .bss section
</span></span></span><span class="line"><span class="cl"><span class="c1">// const __u32 foo = 42; 	// .rodata section
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">__main</span><span class="p">(</span><span class="k">struct</span> <span class="n">__sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">skb</span><span class="o">-&gt;</span><span class="n">mark</span> <span class="o">=</span> <span class="nf">__fetch</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">char</span> <span class="n">__license</span><span class="p">[]</span> <span class="nf">__section</span><span class="p">(</span><span class="s">&#34;license&#34;</span><span class="p">)</span> <span class="o">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>编译后,foo变量存储在<code>.data</code> section内。</p>
<pre tabindex="0"><code># llvm-readelf -S test.o
There are 8 section headers, starting at offset 0x148:

Section Headers:
 [Nr] Name Type Address Off Size ES Flg Lk Inf Al
 [ 0] NULL 0000000000000000 000000 000000 00 0 0 0
 [ 1] .strtab STRTAB 0000000000000000 0000fa 00004b 00 0 0 1
 [ 2] .text PROGBITS 0000000000000000 000040 000028 00 AX 0 0 8
 [ 3] .rel.text REL 0000000000000000 0000e8 000010 10 7 2 8
 [ 4] .data PROGBITS 0000000000000000 000068 000004 00 WA 0 0 4
 [ 5] license PROGBITS 0000000000000000 00006c 000001 00 WA 0 0 1
 [ 6] .llvm_addrsig LLVM_ADDRSIG 0000000000000000 0000f8 000002 00 E 7 0 1
 [ 7] .symtab SYMTAB 0000000000000000 000070 000078 18 1 2 8
Key to Flags:
 W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
 L (link order), O (extra OS processing required), G (group), T (TLS),
 C (compressed), x (unknown), o (OS specific), E (exclude),
 p (processor specific)
</code></pre><p><code>.rel.text</code> section中也存在foo变量的relocation信息:</p></description>
</item>
<item>
<title>Linux 收包和发包流程</title>
<link>https://blog.jinzhi.site/posts/2022-10/linux-%E6%94%B6%E5%8C%85%E5%92%8C%E5%8F%91%E5%8C%85%E6%B5%81%E7%A8%8B/</link>
<pubDate>Thu, 13 Oct 2022 14:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-10/linux-%E6%94%B6%E5%8C%85%E5%92%8C%E5%8F%91%E5%8C%85%E6%B5%81%E7%A8%8B/</guid>
<description><h2 id="流程图">流程图</h2>
<p>From 《Understanding Linux Network Internals》</p>
<p><img src="https://blog.jinzhi.site/images/notes/linux-%E6%94%B6%E5%8C%85%E5%92%8C%E5%8F%91%E5%8C%85%E6%B5%81%E7%A8%8B/image-20220128172744059.png" alt="image-20220128172744059"></p>
<p><img src="https://blog.jinzhi.site/images/notes/linux-%E6%94%B6%E5%8C%85%E5%92%8C%E5%8F%91%E5%8C%85%E6%B5%81%E7%A8%8B/image-20220129171450456.png" alt="image-20220129171450456"></p>
<h2 id="收包流程">收包流程</h2>
<p>TL; DR</p>
<p><img src="https://blog.jinzhi.site/images/notes/linux-%E6%94%B6%E5%8C%85%E5%92%8C%E5%8F%91%E5%8C%85%E6%B5%81%E7%A8%8B/Linux_Packet_Receive_Flow.png" alt="image-20220129171450456"></p>
<ul>
<li><strong>收包<code>NET_RX_SOFTRQ</code>的软中断处理函数是<code>net_rx_action</code></strong></li>
<li><strong><code>net_rx_action</code>中会调用网卡驱动注册的poll回调函数处理</strong></li>
<li><strong>poll回调函数将数据帧从网卡ring buffer中取出,构造skb:</strong>
<ul>
<li><strong>运行xdpdrv上的bpf program,得到action result</strong></li>
<li><strong>如果是XDP_PASS,构造skb,并初始化skb中一些metadata字段</strong></li>
</ul>
</li>
<li><strong>调用内核的GRO和RPS处理流程</strong></li>
<li><strong>进入<code>__netif_receive_skb_core</code>,处理skb:</strong>
<ul>
<li><strong>运行xdpgeneric上的bpf program,得到action result</strong></li>
<li><strong>如果是XDP_PASS,遍历<code>ptype_all</code>和<code>dev-&gt;ptype_all</code>,进行抓包处理</strong></li>
<li><strong>tc ingress 处理<code>sch_handle_ingress</code></strong></li>
<li><strong>查找<code>ptype_base</code>和<code>dev-&gt;ptype_specific</code>,交由对应的三层协议栈回调函数处理</strong></li>
</ul>
</li>
</ul>
<h3 id="napi">NAPI</h3>
<p>**NAPI的思想是从完全的中断收包模型,改用中断和polling混合。**如果内核在处理旧的数据帧时,收到了新的数据帧,网卡设备没有必要再触发一个中断。内核继续处理设备input queue里的数据(该设备的interrupt禁止了),在队列为空时重新使能中断。</p>
<p>从内核的角度,NAPI有如下的优势:</p>
<ul>
<li>降低CPU的负载(更少的中断)</li>
<li>更多的设备处理公平性</li>
</ul>
<p>以ixgbe网卡为例,描述下NAPI处理流程。</p>
<h4 id="注册">注册</h4>
<p>ixgbe驱动在初始化中断向量时会调用<code>netif_napi_add</code>初始化NAPI,==<strong>将<code>ixgbe_poll</code>函数注册到napi结构体,并将napi加入到设备的napi_list内</strong>==:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * ixgbe_alloc_q_vector - Allocate memory for a single interrupt vector
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @adapter: board private structure to initialize
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @v_count: q_vectors allocated on adapter, used for ring interleaving
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @v_idx: index of vector in adapter struct
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @txr_count: total number of Tx rings to allocate
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @txr_idx: index of first Tx ring to allocate
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @xdp_count: total number of XDP rings to allocate
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @xdp_idx: index of first XDP ring to allocate
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @rxr_count: total number of Rx rings to allocate
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @rxr_idx: index of first Rx ring to allocate
</span></span></span><span class="line"><span class="cl"><span class="cm"> *
</span></span></span><span class="line"><span class="cl"><span class="cm"> * We allocate one q_vector. If allocation fails we return -ENOMEM.
</span></span></span><span class="line"><span class="cl"><span class="cm"> **/</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">ixgbe_alloc_q_vector</span><span class="p">(</span><span class="k">struct</span> <span class="n">ixgbe_adapter</span> <span class="o">*</span><span class="n">adapter</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">v_count</span><span class="p">,</span> <span class="kt">int</span> <span class="n">v_idx</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">txr_count</span><span class="p">,</span> <span class="kt">int</span> <span class="n">txr_idx</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">xdp_count</span><span class="p">,</span> <span class="kt">int</span> <span class="n">xdp_idx</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="kt">int</span> <span class="n">rxr_count</span><span class="p">,</span> <span class="kt">int</span> <span class="n">rxr_idx</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* initialize NAPI */</span>
</span></span><span class="line"><span class="cl"> <span class="nf">netif_napi_add</span><span class="p">(</span><span class="n">adapter</span><span class="o">-&gt;</span><span class="n">netdev</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">q_vector</span><span class="o">-&gt;</span><span class="n">napi</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="n">ixgbe_poll</span><span class="p">,</span> <span class="mi">64</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h4 id="中断处理函数">中断处理函数</h4>
<p>ixgbe驱动收到中断后,会调用<code>ixgbe_msix_clean_rings</code></p></description>
</item>
<item>
<title>make命令和编译</title>
<link>https://blog.jinzhi.site/posts/2022-07/make%E5%91%BD%E4%BB%A4%E5%92%8C%E7%BC%96%E8%AF%91/</link>
<pubDate>Tue, 26 Jul 2022 14:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-07/make%E5%91%BD%E4%BB%A4%E5%92%8C%E7%BC%96%E8%AF%91/</guid>
<description><h2 id="makefile">Makefile</h2>
<h3 id="标准格式">标准格式</h3>
<pre tabindex="0"><code>target:dependencies1 dependencies2 ...
 recipe

.PHONY: clean
clean:
 -rm ...
</code></pre><h4 id="注意事项">注意事项</h4>
<ul>
<li>运行make时如果没有指定target,那么make会构建Makefile中的第一个target</li>
<li>通常情况下,目标名即生成的文件名。如果一条规则的目标文件存在并且该文件比它所有的依赖都要新,那么make会跳过recipe;如果目标文件不存在,那么目标文件的 <strong>timestamp</strong> 为开始的时间;否则timestamp为相应文件的修改时间。</li>
<li>每次运行 <code>make clean</code>,”clean“中的recipe都会被执行,因为clean文件永远都不会被创建。(可以使用 <strong>.PHONY</strong> 创建伪目标使Makefile可读性更高)</li>
<li>recipes必须用tab缩进</li>
<li>可以通过并行的方式运行recipes:<code>make -j 4</code>(指定并行的任务数)</li>
<li>如果没有指定规则,Make会自动化创建规则。例如,本地有一个C文件”program.c“,当运行 <code>make program</code>时,Make会自动编译生成 program</li>
</ul>
<h3 id="变量">变量</h3>
<ul>
<li><code>$@</code> 目标文件</li>
<li><code>$^</code> 所有的依赖文件</li>
<li><code>$&lt;</code> 第一个依赖</li>
</ul>
<h2 id="头文件">头文件</h2>
<h3 id="环境变量">环境变量</h3>
<ul>
<li><code>C_INCLUDE_PATH</code> C语言头文件路径</li>
<li><code>CPLUS_INCLUDE_PATH</code> C++ 头文件路径</li>
</ul>
<h3 id="搜索路径">搜索路径</h3>
<h4 id="include">#include&lt;&gt;</h4>
<ol>
<li>先搜索 <code>-I</code> 指定的目录</li>
<li>然后搜索gcc的环境变量 <code>CPLUS_INCLUDE_PATH</code></li>
<li>最后搜索gcc的内定目录
<ol>
<li><code>/usr/include</code></li>
<li><code>/usr/local/include</code></li>
<li><code>/usr/lib/gcc/x86_64-redhat-linux/4.1.1/include</code></li>
</ol>
</li>
</ol>
<h4 id="include-">#include &quot;&quot;</h4>
<ul>
<li>搜索当前目录,#include&lt;&gt;方式不会搜索当前目录</li>
</ul>
<h2 id="动态库">动态库</h2>
<h3 id="环境变量-1">环境变量</h3>
<ul>
<li><code>LD_LIBRARY_PATH</code> 动态链接库搜索路径</li>
<li><code>PKG_CONFIG_PATH</code> .pc文件(package config)文件搜索路径</li>
</ul>
<h3 id="搜索路径-1">搜索路径</h3>
<ol>
<li>首先在环境变量 <code>LD_LIBRARY_PATH</code> 所记录的路径中查找</li>
<li>在程序链接时指定的 rpath 中查找,可以 <code>readelf binfile | grep RPATH</code></li>
<li>然后从缓存文件/etc/ld.so.cache中查找。这个缓存文件由/sbin/ldconfig命令读取配置文件/etc/ld.so.conf 之后生成(也可以在 ld.so.conf.d 目录下增加 <em>.conf 文件,里面写入库路径,在 ld.so.conf 中 include ld.so.conf.d/</em>.conf )</li>
<li>如果上述步骤都找不到,则到默认的系统路径中查找,先是/usr/lib然后是/lib</li>
</ol>
<ul>
<li><a href="https://www.cnblogs.com/masterpanda/p/5700446.html">https://www.cnblogs.com/masterpanda/p/5700446.html</a></li>
</ul>
<h3 id="编译参数">编译参数</h3>
<h4 id="-shared">-shared</h4>
<p>指定生成动态连接库</p></description>
</item>
<item>
<title>邓小平时代</title>
<link>https://blog.jinzhi.site/posts/2022-06/%E9%82%93%E5%B0%8F%E5%B9%B3%E6%97%B6%E4%BB%A3/</link>
<pubDate>Mon, 27 Jun 2022 09:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-06/%E9%82%93%E5%B0%8F%E5%B9%B3%E6%97%B6%E4%BB%A3/</guid>
<description><h2 id="邓小平时代">《邓小平时代》</h2>
<p>作者 <em>傅高义</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/%E9%82%93%E5%B0%8F%E5%B9%B3%E6%97%B6%E4%BB%A3/t6_674048.jpg" alt=""></p>
<h3 id="导言这个人和他的使命">导言 这个人和他的使命</h3>
<p>毛确实犯了严重的错误,但在邓小平看来,更大的问题是导致这些错误的制度缺陷。政治体系控制到每家每户的做法搞过了头,造成了恐惧和主动精神的丧失;对经济体系的控制也搞过了头,导致的是失去活力的僵化。中国的领导人究竟怎样才能做到既维持国家稳定,又为社会松绑?</p>
<p>作为最高领导人,邓小平并不认为自己的任务是提出新思想,他认为自己的职责是掌控设计和落实新体制的颠覆性的过程。他要承担最后责任,做出正确判断。</p>
<p>吉米·卡特总统曾评论道,邓小平跟苏联领导人不一样,他有一种内在的自信,这使他能直奔实质问题。他从不纠缠于过去的错误或谁要对其负责。他经常打桥牌,就像他打牌时的表现一样,他只想把摸到手的牌打好。他能认识并接受权力现实,在可能的范围内做事。</p>
<p>从那时起,通过和这些中国革命的大战略家交往,邓小平培养起一种看问题的独特眼光,能够从一个“统领全局”的高度思考如何将理论加以落实、如何用理论来影响社会。</p>
<p>邓小平1974年会见一个美国的大学代表团时说:“我没有上过大学,但我一向认为,从我出生那天起,就在上着人生这所大学。它没有毕业的一天,直到去见上帝。”邓小平终其一生都在不断地学习和解决问题。</p>
<h3 id="第1章革命者建设者改革者19041969">第1章 革命者、建设者、改革者,1904—1969</h3>
<p>还在莫斯科时,21岁的邓小平就萌生了一些对于一个年轻人而言非同寻常的想法,而且这些想法终生未曾改变。不妨举个例子,他在1926年8月12日的课堂作业中写道:“集中的权力要自上而下地行使。服从上级命令是绝对必要的。允许多少民主,要视周围的环境变化而定。”</p>
<h3 id="第2章放逐与回归19691974">第2章 放逐与回归,1969—1974</h3>
<p>中国领导人应当颂扬毛泽东,继续尊敬他。但是在解释毛泽东思想时,不应把它当作僵化的意识形态,而应看作对时代环境的成功适应,这样理解毛的思想可以为毛泽东的接班人提供适应新环境的回旋余地。</p>
<p>十大后的21名新政治局委员中有4个激进派——王洪文、张春桥、江青和姚文元;他们虽然不是一个一起工作的小团体,但有着相似的观点,后来变成了臭名昭著的“四人帮”。</p>
<h3 id="第3章整顿19741975">第3章 整顿,1974—1975</h3>
<p>周恩来作报告时,很多人大代表都为他痛苦的表情落下了眼泪;念完报告后,他们全体起立,向他热烈鼓掌达数分钟之久。这种情感上的反应,是他们向这位临终前的领导人表达的敬意,他把自己的一生奉献给了党和国家,工作出类拔萃;他在“文革”中还保护了他们中间很多人。</p>
<p>邓小平看来,从组织的可靠性上说,一个领导班子要优于一名领导人,不管后者多么能干。一名领导人说不定会出事,但如果是一个小班子,一旦出了问题,其他人可以随时接过工作。理想的安排是,领导班子的成员不但能提供必要的全面领导,而且能掌握各自分管领域的专业知识,例如工业、文化和政法等等。大单位的领导班子可以有七八个成员,小单位也许只需要两三个人。对于领导应当如何开展工作,要给予他们足够的活动空间,只要他们能完成上级下达的目标即可。</p>
<p>这充分显示了邓小平的特色:讲清大局,说明为什么需要做某些事,把注意力集中在任务上面,打好思想基础,为撤换无所作为的干部争取公众支持。</p>
<p><em>德不配位</em></p>
<blockquote>
<p>实际上,王洪文曾做过一系列的努力以承担起主持党的日常工作的职责;有些了解他的人觉得,他并没有参与“四人帮”犯下的罪行。但是北京城里有众多有经验的优秀干部,像王洪文这样一个年轻的新贵,突然之间蹿升到更有经验、更能干的干部之上,很难赢得一个高层领导人不可缺少的尊重。</p>
</blockquote>
<h3 id="第4章向前看1975">第4章 向前看,1975</h3>
<p>对周荣鑫的批判甚至比对邓小平的批判还要严厉。他在1975年12月不断挨批,直到病倒被送进医院。尽管如此,他仍被从医院揪出来参加了50多场批斗会。最后,周荣鑫在1976年4月12日上午的批斗会上昏倒并于次日黎明前去世,年仅59岁。中国的教育改革也一时归于沉寂。</p>
<h3 id="第5章靠边站1976">第5章 靠边站,1976</h3>
<p>追悼会过后,按周恩来遗孀邓颖超的请求,由她陪伴周恩来的骨灰前往机场。在那里,工人将骨灰送上一架飞机,从空中撒向他奉献了一生的中国大地。</p>
<h3 id="第7章三个转折点1978">第7章 三个转折点,1978</h3>
<p>他再次建议首先看大局,然后再想局部;先讲大道理,再讲小道理。</p>
<p>如何鼓励新思想,同时尽量减少保守派干部的抵制;如何既尊重毛泽东,又要摆脱他的路线;如何既保持乐观,又要避免以后的失望;如何既维护稳定,又开放经济;如何既给予地方干部灵活空间,又能维护国家的发展重点。</p>
<p>在12月13日下午中央工作会议的闭幕会上,邓小平一开口就直奔主题:“今天我主要讲一个问题,就是解放思想,开动脑筋,实事求是,团结一致向前看。”邓小平称赞这次工作会议是1957年以来党内最好、最开放的一次讨论会。他说,要允许大家说出对真实情况的看法。“必须有充分的民主,才能做到正确的集中。当前这个时期,特别需要强调民主。因为在过去一个相当长的时间内……民主太少。……应当允许群众提一些意见,即使有个别心怀不满的人,想利用民主闹一点事,也没有什么可怕……最可怕的是鸦雀无声。”邓小平无论在当时还是任何时候,都没有提倡过不受限制的言论自由。</p>
<p>中国的两次大灾难——“大跃进”和“文革”,是由于制度造成的,这种制度允许一人统治,容不下不同的声音。因此中国需要建立法制,这样的话一个人不管能力有多大,都不能由他一个人说了算。法制一开始可能不健全、不完善,但可以逐步使其变得公正合理。</p>
<h3 id="第8章为自由设限19781979">第8章 为自由设限,1978—1979</h3>
<p>此外,鼓吹自由民主的人,和他们的批评者一样,对国外的情况缺少体验,知之甚少。他们开始质疑毛泽东思想和马克思主义理论,又看到另一些国家在经济上远比中国发达,于是对西方民主表现出几近天真的信仰。</p>
<p>尽管《人民日报》没有报道西单的事情,但报社里支持民主墙的人在1979年1月3日发表了一篇大胆的社论《发扬民主和实现四化》,其中说,“让人说话,天塌不下来。……真正可怕的倒是听不到不同的声音。……害怕人民讲话,实际是软弱和神经衰弱的表现。……安定团结和发扬民主并不是对立的。”</p>
<p>在这篇重要讲话中,邓小平阐明了不容挑战的四项基本原则,在可接受和不可接受的事情之间划定了界限。写作不能挑战以下四点:(1)社会主义道路;(2)无产阶级专政;(3)共产党的领导;(4)马克思列宁主义和毛泽东思想。</p>
<p>作为一名遵守纪律的党员,胡耀邦于4月3日在务虚会闭幕式的讲话中表示完全拥护邓小平坚持四项基本原则的立场。但是,在务虚会第一阶段听过胡耀邦讲话的人都知道,胡耀邦本人更希望看到一个较为开放的社会,他相信国家不会因为人们更自由地表达不同观点而陷入混乱。</p>
<p>尽管邓小平和胡耀邦都致力于现代化,仍然合作共事,但是在如何划定自由的界限上,他们的分歧却愈演愈烈,最终导致邓小平在1987年决定将胡耀邦撤职。</p>
<h3 id="第9章苏联越南的威胁19781979">第9章 苏联—越南的威胁,1978—1979</h3>
<p>毛泽东会见外国人时散发着帝王般的自信,谈论哲学、历史和文学,纵论天下大势。周恩来在国内外会见外宾时,则表现得博学而儒雅,他态度亲切,体贴入微,对客人照顾得十分周到。他既谈大事,也愿意讨论细节。</p>
<p>邓小平的战略分析起点和毛泽东是一样的:分清主要敌人,广结盟友与之对抗;分化敌人的盟友,使其疏远敌人。</p>
<p>邓小平要应付的一个棘手问题是,与日美恢复关系时,如何尽量减少对中朝关系的伤害。邓小平不想让朝鲜更加靠近苏联。因此他决定,上策是事先向朝鲜人做出充分解释,不使他们事后感到意外。</p>
<p>即使越南攻入柬埔寨,中国也不会像朝鲜战争时帮助朝鲜那样出兵。邓小平担心陷入其中难以自拔。他决定以军事进攻的方式“给越南一个教训”,拿下几个县城,表明中国可以继续深入,然后迅速撤出。这也可以减少苏联派兵增援越南的风险。越南将由此明白,苏联并不总是能靠得住的,因而要收敛在这个地区的野心。通过攻打越南而不是苏联,中国也可以向苏联表明,它在该地区建立武力的任何做法都是代价高昂的。</p>
<p>1963年马来西亚取得独立后,马来人害怕拥有强大政党的华人可能主导他们的政府。为了避免这种情况,人口的75%是华人、当时仍是马来亚一部分的新加坡在1965年遭到遗弃,被迫变成了一个独立国家。</p>
<h3 id="第10章向日本开放1978">第10章 向日本开放,1978</h3>
<p>中国与这个相邻岛国2200年的交往史中,邓小平是第一个踏上日本国土的中国领导人,也是第一个拜会日本天皇的中国领导人。</p>
<h3 id="第11章向美国开放19781979">第11章 向美国开放,1978—1979</h3>
<p>为了实现同美国建交这个目标,邓小平准备在很多问题上采取灵活的立场。然而在一个问题——台湾问题——上,就像毛泽东和周恩来一样,他有着不可动摇的“原则”。除非美国与台湾断交,终止《美台共同防御条约》,撤出全部驻台美军,他不会和美国建交。</p>
<p><em>这也是埋下了一个伏笔</em></p>
<blockquote>
<p>会谈结束时,邓小平提醒说,如果卡特总统公开宣扬对台售武,中方将不得不做出反应,任何公开争论都将有损于中美建交的重大意义。</p>
</blockquote>
<p>就像美国人对邓小平做出了过度反应一样,很多中国人也对邓向美国的开放做出了过度反应。有些中国人想一夜之间就能得到一切,没有意识到在能够享受经济增长的成果之前,中国需要做出多少改变。还有一些人急于引进中国的现实还难以适应的制度和价值观。在中国和西方道路之间找到适当的平衡并不容易,但是对外开放带来了中西的杂交优势和思想的复兴,它们随着时间的推移将重新塑造中国。</p>
<p>他出访是因为他要为自己的国家完成一项任务。他认为自己有责任改善与邻国的关系,向日本和美国进一步敞开国门。这既是为了遏制苏联,也是为中国的现代化争取帮助。现在他已经完成了自己的使命,履行了自己的职责,他可以转向另一些重要任务了。邓小平在当时的13个月里5次出访国外。虽然他又活了18年,但是从此再也没有迈出国门。</p>
<h3 id="第12章重组领导班子19791980">第12章 重组领导班子,1979—1980</h3>
<p>邓小平对他所说的党内民主的含义做了解释:党员有意见可以提出来,这有助于解决问题;党的领导听取各种意见后,一旦做出决定,党员就要执行。</p>
<p>邓小平年轻时极其敬佩毛泽东,几十年忠诚地为他工作,却被他抛弃了两次,受到公开批判。他的长子由于毛泽东的红卫兵而造成下肢终身瘫痪。如果说他对毛无怨言,那不合情理;尽管邓小平性格刚强,但他也有常人的爱憎之情。不过,在对待历史问题上他并不显露任何个人感情。</p>
<p>文件的第一稿于1980年2月完成。据说邓小平对它并不满意,他把胡耀邦、胡乔木和邓力群叫去,建议他们:(1)要对毛主席的历史作用做出积极评价;(2)本着“实事求是”的精神,说明毛在“文革”中的错误;(3)得出一个全面的结论,使人民能够团结一致向前看。在这三条中,第一条“最重要、最根本、最关键”。不管他本人因毛泽东的批判和决定受过多少罪,他对起草人说,要讲清楚党和人民必须继续坚持毛泽东思想。</p>
<h3 id="第13章邓小平的统制术">第13章 邓小平的统制术</h3>
<p>据说北京的三类人有着融洽的关系:(1)“团派”,即过去在共青团工作过的干部;(2)“太子党”,指中共高干子女,其中一些人还有同窗之谊;(3)“秘书帮”,指给高级干部当过秘书的人。但邓小平愿意跟所有这三类人共事,只要他们能干、忠于上级、不搞帮派活动。他鼓励其他人也这样做。</p>
<h3 id="第14章广东和福建的试验19791984">第14章 广东和福建的试验,1979—1984</h3>
<p>蛇口由此成为中国第一个允许境外直接投资的地方,也是中国第一个允许境外人士对位于内地的公司进行决策的地区。</p></description>
</item>
<item>
<title>当呼吸化为空气</title>
<link>https://blog.jinzhi.site/posts/2022-06/%E5%BD%93%E5%91%BC%E5%90%B8%E5%8C%96%E4%B8%BA%E7%A9%BA%E6%B0%94/</link>
<pubDate>Sun, 26 Jun 2022 14:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-06/%E5%BD%93%E5%91%BC%E5%90%B8%E5%8C%96%E4%B8%BA%E7%A9%BA%E6%B0%94/</guid>
<description><h2 id="当呼吸化为空气双语版">《当呼吸化为空气(双语版)》</h2>
<p>作者 <em>保罗·卡拉尼什</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/%E5%BD%93%E5%91%BC%E5%90%B8%E5%8C%96%E4%B8%BA%E7%A9%BA%E6%B0%94/t6_36214115.jpg" alt=""></p>
<h3 id="推荐语">推荐语</h3>
<p>你在死亡中探究生命的意义,
你见证生前的呼吸化作死后的空气。
新人尚不可知,故旧早已逝去:
躯体有尽时,灵魂无绝期。
读者啊,趁生之欢愉,快与时间同行,
共赴永恒生命!
——福尔克·格莱维尔(布鲁克伯爵)《卡伊利卡》</p>
<h3 id="第一部-当我身强体健时">第一部 当我身强体健时</h3>
<p>我学习的最大动力,不是成就感,而是一种求知欲,我非常认真地想要探究,是什么让人类的生命充满意义?我仍然认为,文学是精神生活的最高境界,而神经系统科学则探索大脑最为优雅的规律。</p>
<p>我和不同的人与事物建立联系,赋予自己生命丰富的意义。</p>
<p>不过,没有改变的,是在鲜血和沮丧之间极富英雄主义精神的责任感。在我看来,这才是一个医生真正的形象。</p>
<p>那一刻我满脑子都是塞缪尔·贝克特(19)的隐喻,正与这对走到生命尽头的双胞胎相合:“有一天我们诞生,有一天我们死去,同样的一天,同样的一秒钟……他们让新的生命诞生在坟墓上,光明只闪现了一刹那,跟着又是黑夜。”(20)</p>
<p>在那儿的我突然意识到,那些集合了生命、死亡与意义的问题,那些所有人在某个时候都必须要面对的问题,通常都发生在医院里。当一个人真正遇到这些问题,这就变成了实践,有着哲学和生物学上的双重意义。人类是生命体,遵循自然法则,很遗憾的是,这些法则就包括一条:熵总是在增大的,生命是无常的。疾病,就是分子的顺序打乱了;生命的基本要求是新陈代谢,而死亡,就是新陈代谢的终止。</p>
<p>开诚布公地与别人联结,并不意味着要一下子打开天窗把亮话全说了,而是要注意病人的接受程度,站在他们的立场,尽量引导他们走得远一些。</p>
<p>脑中有些区域被看作是几乎不可侵犯的,比如大脑皮质运动中枢,如果遭到破坏,就会引起身体某些部位瘫痪。但最最神圣而不可触碰的皮质,是控制语言的。一般来说都在左脑,被称为韦尼克区和布罗卡氏区,一个理解语言,一个产生语言。布罗卡氏区的损伤会导致写和说的能力缺失,尽管病人对语言的理解能力依旧正常。韦尼克区的损伤会让人失去对语言的理解能力,说起话来语无伦次,句不成句,毫无意义。如果两个区都遭到损伤,病人就变成了一座孤岛,人性最核心的部分永远消失。</p>
<p>在要求很高的领域工作的医生们,见到病人的时候,都是他们最艰难的时候,也是最真实的时候,因为他们的生命与个性受到威胁。医生们的职责,包括去了解病人的生命因为什么而宝贵,而值得一活,并好好计划,可能的话,要尽可能保留这些东西——如果不行的话,就让病人去得安详体面。</p>
<p>我们背负着无形的枷锁,肩负着生死攸关的责任。也许病人鲜活的生命就握在我们手中,但死神总是最后的胜者。就算你是完美的,这个世界却不是。秘诀在于,支撑我们继续下去的秘诀在于,明白打从发牌的那一刻起,你已必输无疑,你会手滑,你会判断失误,但即便如此也要拼尽全力为病人奋战到底。你永远无法到达完美的境地,但通过不懈的努力奋斗和追求,你能看见那无限接近完美的渐进曲线。</p>
<h3 id="第二部-至死方休">第二部 至死方休</h3>
<p>人一旦遭遇顽疾,最需要小心的,是价值观的不断变化。你努力思考自己到底看重些什么,答案也会接踵而至。</p>
<p>自己已经经历了悲痛的五个阶段,就是老生常谈的“否认→愤怒→讨价还价→消沉→接受”。</p>
<p>我独有的顿悟:医者的职责,不是延缓死亡或让病人重回过去的生活,而是在病人和家属的生活分崩离析时,给他们庇护与看顾,直到他们可以重新站起来,面对挑战,并想清楚今后何去何从。</p>
<p>科学的方法论是人们的双手创造出来的,因此无法涉及一些永恒的真理。我们建立科学理论,是为了组织和掌控这个世界,将各种现象局限在可控的范围内。科学的基础,就是各种现象的重复出现以及人为制造的客观性。这是很强的后盾,让科学有能力去建立并阐释各种关于物质和能量的主张,但也让科学知识无法解释人类生命中存在主义的本能特性。</p>
<p>人类生命本身就是独特的、主观的、无法预测的。也许在组织和研究重复出现的经验主义数据时,科学提供了最有用的方法,但另一方面,科学却无法用来解释人类生命中最为核心的方面:希望、恐惧、爱、恨、美、妒忌、荣誉、软弱、奋斗、痛苦和美德。</p>
<p>毫无疑问,我们每个人最终能看到的,都只不过是生命的局部。医生看到一个方面,病人看到另一个方面,工程师、经济学家、潜水采集珍珠的人、酗酒的人、有线电视修理工、牧羊人、印度乞丐、牧师……看到的都不尽相同。没有什么人能完全包揽人类所有的认知。认知产生于我们所创造的,我们彼此之间的关系,以及我们与世界的关系之中,永远不可能完整全面。而终极真理凌驾于一切之上,在其存在之处,播种者与收割者可以一同欣喜狂欢,正如礼拜天的《圣经》布道的最后。因为,终极真理之中,有句话说得清楚明白:“那人撒种,这人收割。”(16)我派你去收获你并未付出努力的东西;辛苦劳作都是别人的,你分享了他们的劳动果实。</p>
<p>在往后的生命中,你会有很多时刻,要去回顾自己的过去,罗列出你去过的地方,做过的事,对这个世界的意义。我衷心希冀,遇到这样的时刻,你一定不要忘了,你曾经让一个将死之人的余生充满了欢乐。在你到来之前的岁月,我对这种欢乐一无所知。我不奢求这样的欢乐永无止境,只觉得平和喜乐,心满意足。此时此刻的当下,这是我生命中最重大的事。</p>
<hr>
<p>使用 <a href="http://www.chengxiangqian.com/xiaoyueji">小悦记</a> 导出 | 2022年6月26日</p></description>
</item>
<item>
<title>算法的力量:人类如何共同生存?</title>
<link>https://blog.jinzhi.site/posts/2022-06/%E7%AE%97%E6%B3%95%E7%9A%84%E5%8A%9B%E9%87%8F%E4%BA%BA%E7%B1%BB%E5%A6%82%E4%BD%95%E5%85%B1%E5%90%8C%E7%94%9F%E5%AD%98/</link>
<pubDate>Sun, 26 Jun 2022 14:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-06/%E7%AE%97%E6%B3%95%E7%9A%84%E5%8A%9B%E9%87%8F%E4%BA%BA%E7%B1%BB%E5%A6%82%E4%BD%95%E5%85%B1%E5%90%8C%E7%94%9F%E5%AD%98/</guid>
<description><h2 id="算法的力量人类如何共同生存">《算法的力量:人类如何共同生存?》</h2>
<p>作者 <em>杰米·萨斯坎德</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/%E7%AE%97%E6%B3%95%E7%9A%84%E5%8A%9B%E9%87%8F%E4%BA%BA%E7%B1%BB%E5%A6%82%E4%BD%95%E5%85%B1%E5%90%8C%E7%94%9F%E5%AD%98/t6_3300013547.jpg" alt=""></p>
<h3 id="导论">导论</h3>
<p>我们都需要搭建一种智识上的框架,来帮助我们清晰且批判性地思考数字创新的政治后果。这本书希望使用政治学理论的思想和方法,来促成此种框架的形成。</p>
<p>有些事情看上去是无法言传的,或者干脆是无法想象的,那是因为常用概念库还没有发展出对它们的阐释。正如维特根斯坦(Ludwig Wittgenstein)所说:“我语言的有限性就是我世界的有限性。”</p>
<p>在工业革命期间,动力织布机、长筒袜和精纺机的发明就曾威胁要取代熟练的纺织工人的工作。一些被称为卢德派(Luddites)的工人发动了遍及英国乡村的猛烈攻击,他们在离开时摧毁了新机器。我们仍然使用“卢德派”一词来形容那些抵制具有破坏性的新技术到来的人。</p>
<p>卢德派的例子中可以看出,创新的经济后果常常要求政治给予回应,而新技术也会引发道德上的挑战。</p>
<p>所有政治秩序都建立在协调、合作和控制的基础之上。缺乏三者中的任何一个,组织集体生活都是不可能的。</p>
<p>确切地说,统计学的功能性定义,即“让先验的独立的事物结合在一起,从而将现实性和一致性赋予更大、更复杂的事物”,与政治的目的基本一致,一个是数值的抽象,一个则基于人类的现实。</p>
<p>退后一步,现在提出一个为阅读整本书提供指导的暂定假设已成为可能:我们如何收集、存储、分析并交流信息,本质上就是如何组织它们——这与我们组织政治的方式密切相关。因此,当一个社会开发出新奇的信息技术和通信技术时,我们便可以预见,政治上的变革也将到来。</p>
<h3 id="第一章-日益强大的系统">第一章 日益强大的系统</h3>
<p>下个世纪,政治将被三大发展所转变:日益强大的系统、日益综合的技术和日益量化的社会。</p>
<p>机器学习,借用多明戈斯的话来说,就是自动化本身的“自动化”。</p>
<p>机器学习算法有三种“学习”方法。在监督学习(supervised learning)中,人类程序员设置了一系列确定的结果并为机器提供反馈,告诉它们其判断是否与确定的结果相合。相形之下,在无监督学习(unsupervised learning)中,机器将获得数据,然后自行寻找其范式。因此,一个无监督学习的机器可以用来“发现知识”,即建立人类程序员完全没有意识到的连接。在强化学习(reinforcement learning)中,机器被给予“奖励”和“惩罚”,从而告诉它做得是否正确。机器实现了自我完善。</p>
<p>然而,我们在本书的不同地方都能见到,对有缺陷的现实世界数据的依赖也能对机器学习系统造成破坏。微软于2016年3月23日在Twitter上推出了其人工智能聊天机器人Tay。Tay可以模拟一个19岁的女孩说话,并能从与其他Twitter用户的交流中学习。在它面世16小时后,Tay被禁止继续运行,因其发布了一系列含有种族主义和色情内容的煽动性推文,其中还有一张希特勒的照片,照片上打着“前方高能”的说明,还有一条推文说:“去他妈的机器人爸爸!我就是这样一个淘气的机器人!”Tay从Twitter上其他用户的这种交流方式中“学习”了此类说法。这个例子表明,人类在社交媒体上如何表达,机器学习也能有样学样。</p>
<h3 id="第二章-日益综合的技术">第二章 日益综合的技术</h3>
<p>数字技术的发展还有五种潜在趋势:更无处不在,连接性更强,更敏感,更具构成性,更具沉浸性。</p>
<p>区块链的社会意义在于它可以在陌生人之间实现安全交易,而无须可信的第三方中介,如银行、信用卡公司或国家。它意图解决计算机科学(和政治学)中长期存在的问题:在没有通常所说的“人际关系”的人与人之间,如何建立起“信任”之类的东西。</p>
<p>现代机器人科学仍然是一个充满挑战的领域,部分原因在于“莫拉韦克悖论”(Moravec&rsquo;s paradox),即(也许与预期相反)高层次推理只需要少量的计算,但低层次的感觉运动技巧则需要大量的计算资源。</p>
<h3 id="第三章-日益量化的社会">第三章 日益量化的社会</h3>
<p>数据“是对某种事物的描述,它使对这些事物的记录、分析和重组成为可能”。将现象转化为数据的过程被称为“数据化”。</p>
<p>四个因素促成了这个过程。第一,越来越多的社会活动通过数字系统或数字平台展开,可供收集的数据就更多了。第二,在过去五十年中,存储数据的成本每两年左右就减少一半,而其密度却增加了5000万倍。第三,算力的爆炸式增长使我们有能力处理存储的内容。第四,数字信息的复制几乎没有边际成本,它们可以相当廉价地复制数百万次。这些因素共同解释了为什么从基于印刷的信息系统过渡到数字化系统的过程中就产生了如此大规模的数据爆炸。</p>
<p>数据科学家一直在努力地通过清洗、处理和组织等方式,将原始数据转化为信息,然后对它们进行分析和解读,再将其转化为知识。</p>
<p>Facebook的商业价值主要在于它从用户处收集的数据,这些数据用途十分广泛,从定向广告到搭建面部识别人工智能系统。</p>
<p>日益量化的社会是一个更容易通过机器和控制机器的人来检查和分析的社会。</p>
<h3 id="第四章-像理论家一样思考">第四章 像理论家一样思考</h3>
<p>我们正试图使用一种为昨天的世界创造的语言去掌握今天的世界。而且,过去的生活似乎更加反映了人类的天性,原因很简单,过去的生活是人类语言的更好反映。
——安托万·德·圣艾克絮佩里,《风沙星辰》(1939)</p>
<p>过去,科学技术的进步帮助人类扫清了一些世界上的未解之谜。20世纪初,马克斯·韦伯的写作将现代性的中心化特征定义为“除魅”(Entzauberung),译为“启蒙”(de-magification)或“祛魅”(disenchantment)。这个过程就是理性观察取代魔法和迷信,成为解释生活中神秘事件的方法之过程。我们这代人也许是体验了相反效果的第一代——世界的“再神秘化”。随着时间的流逝,我们会发现自己被具有超凡力量的、微妙而复杂的技术越发紧密地包围起来;其中的大多数我们都几乎无法理解,更不用说控制了。阿瑟·克拉克(Arthur C. Clarke)说:“任何足够先进的技术都无法与魔法区分开来。”诚如斯言,若数字生活世界要为我们准备一场魔术表演,它必将与我们所见过的一切截然不同。</p>
<p>对于某些人来说,政治等同于政府:立法者决定社会集体目标并制定能实现这些目标的法律和政策的过程。根据这种观点,政治是在政客和公务员的控制下,在议会、政府大楼和市政厅里发生的事情。普通公民也能通过投票或行动主义或多或少地参与政治(可以这么理解),这取决于国家性质和公民意愿。</p>
<p>一种观点认为,政治无处不在,不仅存在于公共领域,还存在于私人领域:朋友和同事之间、家庭内部;俱乐部、团队和宗教机构;在政府中,也在艺术、建筑、科学、文学中,并嵌入语言本身。只要是存在合作、冲突或控制的地方,政治就存在;或是在所有某些特定的社会关系可能被另作安排的领域,从职场政治到性政治,政治也存在。这种观点认为,政治不是一种可以避免或忽略的东西。</p>
<p>阻止某事发生或被改变的最可靠方法,就是干脆不要把它提上日程。</p>
<p>我对政治的定义如下:
政治指的是人类的集体生活,包括我们为什么共同生活,如何安排和约束我们的集体生活,以及我们可以或应该以何种方式来有区别地安排和约束这种集体生活。
该定义不以任何特定形式的政治制度为前提。实际上,它所假设的只是(a)人类将继续集体生活,(b)人类的集体生活能够以不止一种方式组织起来,(c)约束集体生活的方式不止一种。这些都是最微小的假设,能让我们以一种开放的心态继续前行。</p>
<p>语言共同体在概念使用上的差别是至关重要的。当一个英国人听到“自由”一词时,他可能会想到弱化政府对经济和公民社会的干预的支持者。而如果是一个美国人听到“自由”一词,结果可能恰恰相反,他更可能想到主张扩大福利国家的社会主义者。</p>
<p>概念分析就是试图去了解当人们在谈论政治时,他们在谈论什么的过程。这就包括使政治话语经得起细致的调查和质疑,弄清定义和区别,从而使其变得清晰、一致、简单。</p>
<p>政治言论的目的经常就在于欺骗、叫停争论、混淆视听和迷惑别人;用奥威尔的话来说:“撒谎听起来很真实,让谋杀受人尊敬,为空中的风赋予固态的外观。”</p>
<p>概念分析还涉及试图理解概念的道德内涵及其意义。</p>
<p>“规范分析”即试图判断政治领域中是非善恶的方法,与道德哲学和伦理学紧密相关。我们对彼此负有何种正义的责任?我们是否有义务参与公共生活?我们享有做何种事情的自由,什么事情是应被禁止的?违反法律在任何情况下都是不可接受的吗?以上这些就是“规范性问题”。规范分析使我们能够通过反思和争论来确定原则,从而指导我们如何共同生活。</p>
<p>规范性理论用于阐明特定制度、政策或原则的道德正误。</p>
<p>语境分析是用来找出概念从何而来、为何能出现、在它们的构思过程中被赋予了何种含义,以及其含义是如何随着时间改变的。</p>
<h3 id="第五章-代码就是力量">第五章 代码就是力量</h3>
<p>强者如何统治弱者?答案是施加权力,它是政治秩序的基石和政治概念的教父。</p>
<p>我认为,未来的权力将采用三种形式。第一种是武力(第六章),第二种是审查(第七章),第三种是感知控制(第八章)。数字技术将逐渐成为这三种形式的主要来源。</p>
<p>我们不妨从“对某人有控制权”(having power over someone)和“有权力做某事”(having power to do something)的简单区分入手开始讨论。控制权(power over)是这样一种权力:如老板告诉他的下属去完成一项任务,或是学校老师要求其学生安静地坐着。这是关于管理者要求被管理者遵守规则的过程。相反,行动权(power to)并不意味着“管理者和被管理者”的体系,它指的是一种能力、才能,或是某人得以做某事的能力,例如,沿着街道行走或是举起沉重的杠铃。这是一个广义的定义。有了行动权的某人或某个团体在自身强大的同时并不需要剥夺相应对象的权力。</p>
<p>若A对B有控制权,即A有能力迫使B做某些B原本不会去做的事情。</p></description>
</item>
<item>
<title>运气的诱饵</title>
<link>https://blog.jinzhi.site/posts/2022-06/%E8%BF%90%E6%B0%94%E7%9A%84%E8%AF%B1%E9%A5%B5/</link>
<pubDate>Sun, 05 Jun 2022 14:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-06/%E8%BF%90%E6%B0%94%E7%9A%84%E8%AF%B1%E9%A5%B5/</guid>
<description><h2 id="运气的诱饵">《运气的诱饵》</h2>
<p>作者 <em>娜塔莎·道·舒尔</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/%E8%BF%90%E6%B0%94%E7%9A%84%E8%AF%B1%E9%A5%B5/t6_3300011889.jpg" alt=""></p>
<h3 id="引言-画出机器迷境的地图">引言 画出机器迷境的地图</h3>
<p>很多人认为来自赌博的收入是一种“智商税”(tax on stupidity),也有人认为这是一种“累退税”(regressive tax),因为它是从弱势群体吸取收入,放入公共收入池,也就是对财富做了向上再分配(可参见Volberg and Wray 2007)。且不论哪种观点正确,各州政府一直都在试图借赌场税收来弥补预算赤字,这一状况推动了赌博业过去30年来在美国的扩张。就在不算久远的1976年,内华达州以外的美国还没有赌场,连允许发行彩票的州也只有13个;而到今天,除了在夏威夷和犹他两个州之外,在任何地方你都有办法赌上两把,连印第安部落的赌博业也从1988年诞生以来成长为一个近270亿美元的产业,今天印第安人在28个州运营着442家赌场(North American Gaming Almanac 2010)。当时各州受经济衰退影响,获得的联邦经费支持也被里根——布什政府削减,于是想要在不增税负的情况下找到新的增收途径。赌注较低的老虎机正好满足了赌博业代言人及一些政府官员想把赌博洗白成“游戏”的需求,他们希望民意认可这项活动,把玩老虎机看作一种主流的消费娱乐,而不是一种道德滑坡或榨取民众钱财的陷阱。</p>
<p>凯卢瓦提出,要对文化进行诊断,可以从其游戏中以下四个元素的组合情况入手:agon,即竞争;alea,几率;mimesis,模拟;以及ilinx,眩晕(vertigo)。他声称,现代文化的突出特征是,其中的游戏,agon和alea之间的张力特别突出。前者主张坚定个人意志,后者则要求向几率屈服。</p>
<p>一场比赛的结果越难预测,比赛的参与者在金钱和感情上的投入度就越高,他们游戏的程度就越“深”,从某种意义上来说,比赛的意义远远超越了物质上的输赢。</p>
<p>我们正经历着一个历史性的时刻,此时,人与机器的互动“越发亲密,规模越来越大”[社会学家布鲁诺·拉图尔(Bruno Latour)这样写道],电脑、电子游戏、手机、iPod等科技产品成了每个人管理自己情绪状态的工具,并且为我们在自己和世界的不确定性与忧虑之间制造了一个缓冲区。虽然我们通常认为交互型消费电子设备可以为我们带来更多选择,彼此连接,创造了自我表达的新形式,但它们同样可以帮我们减少选择,断开连接,疏离自我。探讨赌瘾者与老虎机之间的深切纠葛,不仅仅是对这种特定成瘾现象的个案研究,它还提供了另一些线索,有助于理解我们在更广阔的生活“迷境”里面临的困境、趋势和挑战。</p>
<p>赌博成瘾领域一位著名的研究者霍华德·谢弗(Howard Shaffer)指出:“只要人与某一特定对象或一系列对象(如毒品、赌博、电脑)重复性地交互,且此类交互可以稳定地使人获得自己想要的主观体验变化,成瘾的倾向就会出现。”</p>
<p>有些人天生比别人更容易成瘾,同理,有些客体(因其药理或结构方面的特性)也天然地更容易催生成瘾或让它愈演愈烈。这些客体的独特效力,源自它们令主观体验产生强烈改变的能力,这些主观体验的强烈改变使一些人欲罢不能。</p>
<p>正如前文中莫莉和莎伦所说,让她们成瘾的并不是赢钱的机会,而是坐在机器前赌博时进入的那种远离世间纷扰、悬置主体性的平静状态。</p>
<p>行动不是居于主体或客体之内的某种先定本质,而是二者的“共同创造”(coproduce)。</p>
<p>任何物件都并非“不过是无生命的”:“你拿着枪时,你就变了;枪被你拿在手里时,枪也变了。因为拿着枪,你变成了另一个主体;因为与你发生了某种关系,枪也变成了另一种客体。”换句话说,杀人的既不是枪,也不是人;只有枪和人在一起、互为条件时,才能共同产生杀人这一行为。</p>
<p>我们可以说赌博者只影响了自己,他们通过玩赌博机,以期调控自己的情感状态;但赌博机的设计师、营销人员和赌场经理们则不同,他们的位置允许他们远在千里之外就能通过技术手段在他人身上催生和维持特定的行为。</p>
<p>设计是一种“铭刻”(inscription)过程,设计师会把某些特定的使用模式铭刻于产品之中;而在产品与消费者的交互过程中,前者带有的“刻印”会抑制、妨碍某些行为,同时促成、逼出另一些行为。“通过为用户的行为设定参数”,任何产品,自不必提其背后的设计团队,都在扮演着引导用户行为的角色。</p>
<h3 id="第一章-俘获内心的室内设计-建筑氛围和情感的设计">第一章 俘获内心的室内设计 建筑、氛围和情感的设计</h3>
<p>现代主义建筑为提升平等交融性(communitas),纷纷加高天花板,引入开敞的空间,大量增加窗户和采光,拥抱利落的极简主义美学,而这时,赌场却反其道而行。它们低矮的沉浸式室内设计风格、模糊的空间边界及错综复杂的迷宫之感,为的都是容纳“与他人无明显连接的大群匿名个体”。</p>
<p>赌场空间及子空间的设计结合了黑暗性和封闭性,从而带来私密感、安全感、专注力和控制感。低矮的天花板下这些迷宫般的走道,隔绝了外面的阳光和空间,让置身其中的玩家失去对时间和空间的感知。一旦进入其中,人就会不知身处何处,今夕何夕。</p>
<p>通过分析自身作为机器赌博成瘾者的切身体验,弗里德曼进一步指出,赌博者追求的赌博,是“向内关注自己的私人领域从而忘却周围的一切”。</p>
<p>空间消除法则指出,设计师必须“约束”空间,为玩家创造出庇护之感。(弗里德曼认为赌博本身是“开放的”“无差别的”“无边界的”“广阔的”“无穷无尽的”,而这些特征正是赌博的环境中要消除的。)一种方法是把大厅“分割”成各个紧凑区域,每个区域都与其他地方隔离,互相也看不到。</p>
<p>子空间的设计带来私密感、安全感、专注力和控制感”。</p>
<p>但这个方法有一个十分微妙的平衡,空间限制也可能过犹不及,弗里德曼也承认:“当所有多余的空间都从建筑设计和室内布局中消除之后,客人们可能很难知道自己在哪儿,自己想去哪儿。”如果出现了这种迷失状态,他们会“漫无目的地乱走,眼神空洞地张望”。要抵消这种迷失效应,“机器的阻塞也必须有其章法”。其中的关键是创造一种“有结构的混沌”,而不要形成“让客人讨厌的骚乱”。弗里德曼许诺:“迷宫就是一切问题的答案。”</p>
<p>迷宫的布局方式,可以让眼前的机器牢牢抓住客人的注意力。在短小、狭窄的过道尽头,机器扑面而来。盘绕的死胡同式路径在让人下意识避免撞上机器的同时,也强迫人注意到这些机器。只要客人有一点赌博的可能性,迷宫就会把它激发出来。</p>
<p>但实际上弗里德曼的迷宫通过将空间收缩和结构化,可以引导顾客沿特定的路线前进,牢牢抓住他们的注意力,把他们导向精心布置的路标,引导他们向着最终的目的地前进。而这个目的地能反映——从而也能激发——他们内心的赌博倾向,让他们停下、坐下、玩起来。</p>
<p>弗里德曼建议设计师们可以暗示前面有什么,但提醒他们,决不能提示过于清晰,或让客人看到赌场的深处,因为这样可能会让客人丧失进一步深入的兴趣。</p>
<p><em>用户的行为是被深刻塑造的,你以为你独立自主作出的抉择,恰恰是别人精心设计好的陷阱。汉弗莱爵士曾说过,想要预期结果的民意调查,只要在调查单上排列不同的问题就行了……</em></p>
<blockquote>
<p>弗里德曼曾经给一处赌场入口走廊的直角加上一点曲线,而后,他“被行人行为的改变程度惊呆了”:走进赌场的客人数量从1/3上升到将近2/3。</p>
</blockquote>
<p>赌场布局设计的目标,是将客人保持在一种易受暗示、易被渗透的状态,从而更易受赌场环境提供的刺激物的影响。</p>
<p>音乐要想达到调节消费者行为的最佳效果,也不能太过突出。曲调熟悉、速度舒缓、音量和节奏上变化不大的音乐,可以很好地“编制”客人的行为,而且不会被客人意识到</p>
<p>赌客们对建筑空间和环境氛围的察觉越少,就越容易沉浸在赌博之中。同样,随着游戏一把接一把,赌客的投入程度加深,他们对于不平衡的、有其他干扰因素的环境氛围也会越来越不敏感。社会学家格尔达·里斯(Gerda Reith)写道,赌博可以“抹掉周围的环境”,让空间“收缩到一个单点,失去延展性”。</p>
<p><em>短视频从双列改为单列其实也是顺应了这种思想,让用户沉浸式去观看,而不是让用户做选择。用户每一次下滑都会伴随着对下一条未知视频的预期,希望从中感受到多巴胺的“奖励”。另外,短视频的时长控制也尽可能避免用户回归自己的“理性”,通过连续不断地几十秒的刺激,时间仿佛不存在一样迅速流逝。</em></p>
<blockquote>
<p>虽然赌场的结构、装潢和环境氛围的设计确实想要影响客人的行为,但它的作用方式是诱惑而非限制,是奖励而非惩罚,是引导而非改变。比尔·弗里德曼及其他关注体验的赌场设计师们主张,不要让客人适应建筑和环境,而是让“建筑不断适应人”,按一些人的说法,这样可以产出一种“人体工程学迷宫”,从而引发“快乐的囚禁”。</p>
</blockquote>
<p>虽然赌场的结构、装潢和环境氛围的设计确实想要影响客人的行为,但它的作用方式是诱惑而非限制,是奖励而非惩罚,是引导而非改变。比尔·弗里德曼及其他关注体验的赌场设计师们主张,不要让客人适应建筑和环境,而是让“建筑不断适应人”,按一些人的说法,这样可以产出一种“人体工程学迷宫”,从而引发“快乐的囚禁”。</p>
<p>正如我们所见,赌场设计遵循的原则是一位行业领军人物所说的“沉浸范式”,它让赌客进入一种去主体的、不受打断的行为状态,进而对前面引用的那些专家顾问说的“情感体验”进行激励、导引,最终从中获利。哲学家和人类学家的说法是,当代资本主义的特征在于策略性地调动消费者的情感能力并从中获利,而商业赌场设计就是一个很好的例证。</p>
<h3 id="第二章-打造体验-玩家中心设计的生产力经济">第二章 打造体验 “玩家中心设计”的生产力经济</h3>
<p>以玩家为中心的赌博机设计其实是在遵循用户中心主义的大趋势。所谓用户中心主义,是一种希望通过提升用户体验来产生价值的产品设计思路,或者用社会学家奈杰尔·思里夫特(Nigel Thrift)的话说,是“挖掘新的现象学基底”。“经济的整体趋势是从创造商品和提供服务,变为创造体验。”</p>
<p>币仓可以把最多200个硬币自动吐到托盘里,于是提升了“这些赢来的钱又被赌回机器里的概率”,同时还保证了赌客的势头不被打乱,而这对赌博体验的顺畅性至关重要。</p>
<p>钞口的引入进一步提升了赌博机的游戏速度,因为玩家可以把大面额的钞票投进去,并看着数字显示器中提示的点数下注,而不用停下来把币一个一个投进去(见图2.1)。把金钱去金钱化,变成一种随时可用的点数,这不但让人忽略点数的真实币值,变得更易下注,同时由于玩家无须再把一堆硬币从换币处搬过来,游戏速度也就不再受玩家运动协调能力的限制,这也进一步提升了赌场的收入。</p>
<p>机器的最终目标(即动态游戏速率专利书中所写的“提升机器的采购者通过机器获利的速率”)没有变化,但后者让机器的调整从工厂转移到了赌场。而在赌场的语境下,机器操作者的主观状态获得了强烈的关注:他的欲望、挫败、愉悦和交互感。</p>
<p>十年之间,音响工程师从每局游戏使用15种声音进化到每局使用平均400个独特的“声音事件”,每个声音事件都经过精心校准,好推进游戏的进行又不打扰玩家的注意力。赌博行业的顾问戴维·克兰斯(David Kranes)写道,设置合理的情况下,声音“确实可以让玩家充满能量,让他玩得更久”。一位长期机器赌博者也总结说:“如果声音对路,我会玩得更久。上周末,我玩的机器被关了声音,虽然它玩着还不错,但我还是换了机器,因为我受不了没声音的机器,找不到该有的顺畅感。”</p>
<p>当代声音技术与过去的区别不仅仅在于数字化,更在于声音的功能。因为当代声音技术不仅仅在于使声音扩大或“丰满”,还在于对声音进行控制,从而对赌博者产生一系列起引导作用的不间断线索流。</p>
<p>本章要讨论的最后一类辅助技术——现金处理及存取系统,则是通过使客人的金钱存取变得更为流畅,来提升“生产力”。更确切地说,玩家继续赌博的冲动与这一冲动的实现之间存在时间间隙,而他们把这一间隙尽量缩短,不给玩家时间思考,不让玩家在暂停的间隙自行产生停手的念头。</p>
<p>出现于1999年的“票进票出”(Ticket-in/ticket-out,TITO)技术,现在已经为大多数赌场采用。有了这项技术,客人即使没有办理会员卡,也完全用不着硬币。他们赢钱时,赌博机会直接打印带条码的纸条,这些纸条可以在自助服务柜台兑换为现金,也可以立即在其他机器上使用。通过减少机器的“空闲时间”(即浪费在换硬币、操作硬币及等待赢钱支付的时间),TITO很快证明了自己的赚钱能力,使赌博的整体速度和幅度提升了惊人的20%。</p>
<p>一位名叫卡特里娜的赌博者曾写信给我,信中她写了自己的体会:“与这些机器互动时,你会跟现实世界脱节,真金白银悄悄地就变成了游戏币。我发现TITO这类新系统更是进一步助长了这种倾向,因为它们让与现实脱节的过程更为平滑了。”</p>
<p>马克思主义有一个观点,认为工业生产中的工厂工人们,在操作机器的过程中与自身发生了异化,而福柯则认为,人与这种规训性机器操作之间的关系,与其说是异化疏离,不如说是连接,在这一过程中“与生产工具的强迫性连接”使人体与其操作的客体对象结合在了一起。</p>
<p>这种从强迫到合谋的转变,印证了德勒兹的理论。他把现代社会刻画为一种“资本主义变异”,原有的规训和限制的逻辑让位给了另一种控制逻辑,这种新逻辑着眼于一步步控制身体、情感和资本的连续流动。</p>
<p>迪贝尔在评论他所谓的骰子资本主义现象时说:“教科书式的资本主义剥削,与被剥削者的游戏欲望,两者以一种具有生产力的方式和平共处。”</p>
<p>赌场的设计想要实现持续的生产力,而赌博者想要的是持续地隔绝于迷境之中,双方一拍即合。在赌博业的利润优化过程中,赌博者本身成了合作方。</p>
<p>研究所谓“体验经济”的学者已经感受到这种“合作”的趋势,在这种合作中,企业的关注点“与消费者的关注点空前一致”,而产品的生产也变成了一种动态的“共同创作”过程。</p></description>
</item>
<item>
<title>ELF 文件格式浅析</title>
<link>https://blog.jinzhi.site/posts/2022-05/elf-%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E6%B5%85%E6%9E%90/</link>
<pubDate>Fri, 27 May 2022 09:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-05/elf-%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E6%B5%85%E6%9E%90/</guid>
<description><p>本文是作为 Rust 的练手项目 rself(<a href="https://github.com/chengzhycn/rself">https://github.com/chengzhycn/rself</a>) 的背景知识记录。rself 使用 Rust 重新造了一个 readelf 的轮子。</p>
<p>ELF全称为<strong>Executable and Linkable Format</strong>,是Linux上可执行文件的通用文件格式。主要有三种类型的ELF文件:</p>
<ul>
<li><strong>Relocatable file</strong>: 存储用于和其它对象文件链接的代码和数据,创建一个可执行文件或者共享对象文件</li>
<li><strong>Executable file</strong>: 存储的是用于执行的程序。它指定了exec如何创建一个程序的进程镜像</li>
<li><strong>Shared object file</strong>: 存储了在两种语义下用于链接的代码和数据。第一种是让链接器将共享对象文件和其它的可重定位文件/共享对象文件一起创建一个新的对象文件;第二种是动态链接器将它和可执行文件以及其它的共享对象一起创建一个进程镜像。</li>
</ul>
<h2 id="elf构成">ELF构成</h2>
<p>ELF主要由如下几个部分组成:</p>
<ul>
<li><strong>ELF Header</strong>: 位于文件的起始,描述了整个文件的组织结构</li>
<li><strong>Sections/Segments</strong>: 存储了指令、数据、符号表、可重定位信息等。sections 是这段数据在链接时的表现;segments 是数据在执行时的表现。</li>
<li><strong>Program Header Table</strong>: 用于告诉系统如何创建一个进程镜像。对于可执行程序,Program Header Table是必须的,但是对于可重定位文件就不需要了。</li>
<li><strong>Section Header Table</strong>: 存储描述文件中sections的信息。每个section都在这个表中有一个条目,存储了如section name, section size等信息。用于链接的文件必须得有Section Header Table,其它的对象文件可有可无。</li>
</ul>
<p><img src="https://blog.jinzhi.site/images/notes/elf-%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E6%B5%85%E6%9E%90/d02c13c1fa4bd228f698cf355ec47377_MD5.png" alt="d02c13c1fa4bd228f698cf355ec47377_MD5"></p>
<p>上面的图中虽然描述文件各个部分的位置,但实际上只有ELF Header的位置是固定的,其它的部分在文件中的位置都在ELF Header中指定。</p>
<h2 id="elf-header">ELF Header</h2>
<pre tabindex="0"><code>#define EI_NIDENT 16

typedef struct {
 unsigned char e_ident[EI_NIDENT];
 Elf32_Half e_type;
 Elf32_Half e_machine;
 Elf32_Word e_version;
 Elf32_Addr e_entry;
 Elf32_Off e_phoff;
 Elf32_Off e_shoff;
 Elf32_Word e_flags;
 Elf32_Half e_ehsize;
 Elf32_Half e_phentsize;
 Elf32_Half e_phnum;
 Elf32_Half e_shentsize;
 Elf32_Half e_shnum;
 Elf32_Half e_shstrndx;
} Elf32_Ehdr;

typedef struct {
 unsigned char e_ident[EI_NIDENT];
 Elf64_Half e_type;
 Elf64_Half e_machine;
 Elf64_Word e_version;
 Elf64_Addr e_entry;
 Elf64_Off e_phoff;
 Elf64_Off e_shoff;
 Elf64_Word e_flags;
 Elf64_Half e_ehsize;
 Elf64_Half e_phentsize;
 Elf64_Half e_phnum;
 Elf64_Half e_shentsize;
 Elf64_Half e_shnum;
 Elf64_Half e_shstrndx;
} Elf64_Ehdr;
</code></pre><p>ELF header通常由一个Elfxx_Ehdr结构体表示。它存储了当前二进制文件的一些元数据:</p></description>
</item>
<item>
<title>复杂生命的起源</title>
<link>https://blog.jinzhi.site/posts/2022-05/%E5%A4%8D%E6%9D%82%E7%94%9F%E5%91%BD%E7%9A%84%E8%B5%B7%E6%BA%90/</link>
<pubDate>Mon, 09 May 2022 09:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-05/%E5%A4%8D%E6%9D%82%E7%94%9F%E5%91%BD%E7%9A%84%E8%B5%B7%E6%BA%90/</guid>
<description><h2 id="复杂生命的起源">《复杂生命的起源》</h2>
<p>作者 <em>尼克·莱恩</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/%E5%A4%8D%E6%9D%82%E7%94%9F%E5%91%BD%E7%9A%84%E8%B5%B7%E6%BA%90/t6_34336247.jpg" alt=""></p>
<h3 id="绪论-为什么生命会是这样">绪论 为什么生命会是这样?</h3>
<p>林恩·马古利斯(Lynn Margulis)挑起。马古利斯提出,复杂细胞不是经由“标准”的自然选择演化而来,而是通过一场合作共生的盛宴;细胞之间进行非常密切的合作,直到永久入住彼此体内。共生是指两个或更多物种之间一种长期的互动关系,通常伴随着物质和服务的交换。对微生物来说,物质交换涉及的是新陈代谢所需的、构成细胞生命的基础物质。马古利斯使用的术语是内共生(endosymbiosis):同样是交换物质,但这种合作方式极为亲密,合作一方干脆住到另一方的身体里面去了。</p>
<p>这种新的生物类群和细菌一样,没有真核生物的复杂形态,但它们的基因和蛋白质与细菌截然不同。这类生物被命名为古菌(archaea)</p>
<p>在乌斯著名的“三域”生命树上,古菌域和真核生物域是“姊妹分支”,有较为接近的共同祖先。</p>
<p>那个宿主细胞是一个古菌,属于古菌域。所有的古菌都是原核生物,顾名思义,它们没有细胞核,没有性别,没有包括吞噬作用在内的一切复杂生命特征。这个宿主细胞在形态方面几乎没有任何复杂度可言。然而,不知如何,它捕获了后来变成线粒体的细菌。自那之后,它才演化出了所有的复杂特征。如果事实的确如此,那么复杂生命的单一起源很可能有赖于对线粒体的获取,是线粒体触发了复杂生命的狂飙演化。</p>
<p>他认为,宿主细胞是古菌,不具有真核细胞的那些复杂特征;他还认为,从来就没有所谓的“中间型”,即不存在尚未获得线粒体的简单真核细胞;线粒体的获得和复杂生命的起源,本来就是同一事件。他还认为,真核细胞那些精巧繁复的特征,诸如细胞核、性和吞噬作用,全都发生在获取线粒体之后,是在这种独特的内共生状态下演化而来的。</p>
<p>本质上,所有的活细胞都通过质子(带正电荷的氢原子)回流来为自身提供能量,就像是某种电流——只是用质子代替了电子。我们通过呼吸作用氧化食物而获得的能量,被用来把质子泵过一层膜,在膜的另一边形成质子蓄积。从这个“水库”回流的质子可以为细胞工作供能,如同水电站的涡轮电机。</p>
<p>彼得·米切尔(Peter Mitchell)是20世纪最具独创性的科学家之一,他于1961年首先提出了这一理论,并在此后的30年间逐渐将其完善。米切尔的学说被认为是自达尔文以来最“反直觉”的生物学理论,可与爱因斯坦、海森堡和薛定谔的物理学思想相媲美。</p>
<p>在我看来,这就是位于当代生物学核心的两大未知问题:为什么生命以如此令人困惑的路径演化?为什么细胞的供能方式如此古怪?</p>
<p>要坚持科学性,一个假说就必须做出可验证的科学预测。科学辩论中可能受到的最大侮辱,莫过于一个观点被评价为“连错误都算不上”——因为它无法证伪。</p>
<p>整体论(holism)是一种哲学观念(属于认识论和方法论范畴),认为整体的意义优先于部分,或者只有整体才有意义。整体论最重要的应用是在科学哲学方面。与之相对,强调结构精简、局部细节和层层细化方法的科学哲学观念为还原论(reductionism)。</p>
<h3 id="第一部-问-题">第一部 问 题</h3>
<p>上古的神契不复存在;人类终于认识到,他在无知无觉的浩瀚宇宙中孤立无助,他的出现只是偶然的产物。何为他的天命,何为他的职责,更是无从索解。仰视天国的信念,还是沉入现实的混沌?他只能独自抉择。</p>
<p>本书的中心论点是,生物的演化过程中,确实存在着强有力的、能量方面的约束条件,让我们有可能从基本规律出发,推演出一些最重要又最基础的生命特征。</p>
<p>为什么演化生物学没有预见性?为什么能量方面的约束条件一直遭到忽视?为什么科学界此前几乎没有认识到这方面存在严重的空白?</p>
<p>薛定谔在书中提出了两个核心论点:首先,生命以某种方式抵抗着宇宙万物趋于崩坏的趋势,在局部抑制熵增(混乱),以此对抗热力学第二定律;其次,生命能在局部逃避熵增的诀窍隐藏在基因中。他猜测基因物质是某种“非周期性”的晶体,其晶格结构不做精确重复,因此可以作为“代码脚本”——这是该术语首次应用在生物学语境中。</p>
<p>人类基因组中只有极少一部分代码(不到2%)为蛋白质编码,更大一部分是基因调控区域。</p>
<p>自然界的碳元素以两种稳定的同位素存在,原子质量有细微的差别。②酶(活细胞中可以催化反应的蛋白质)稍稍偏好较轻的碳原子(碳-12),因此有机物中碳-12的浓度略高于自然状态。你可以把碳原子想象成弹跳的微小乒乓球,较轻的球弹得略快一些,更容易碰到酶,所以更容易转化为有机碳。相反,碳-13是较重的同位素,在自然界的全部碳元素中只占1.1%,更容易留在海洋中。当海水中的碳酸盐沉淀形成沉积岩(如石灰岩),碳-13就更容易在其中积聚。这种差异虽然细微,但具有非常稳定的一致性,通常被视为标识着生命存在的地质特征。</p>
<p>叠层石是一种由细菌构成的大型拱状结构,其中单细胞生物层层覆盖生长,掩埋在底部的层级逐渐被矿物质置换,继而变成化石,最终形成奇特的高达一米的叠层石质结构。</p>
<p>32亿年前,生命活动从水以外的各种物质中提取电子。正如生物化学家艾伯特·圣哲尔吉(Albert Szent-Györgyi)的诠释:生命不过是一个电子寻找归宿的过程。光合作用的最终形态(从水中提取电子)是何时产生的,学界还存在很大的争议。</p>
<p>首先,生命在地球极早期就已出现,起码是在40亿~35亿年前,而当时的世界是一个类似于当代地球的水世界。其次,35亿~32亿年前,细菌已经发展出几乎所有的新陈代谢形式,包括各种呼吸作用和光合作用。在大约10亿年间,地球都是一口细菌繁盛的大锅④,而细菌在生物化学上的创造力令人叹为观止。同位素分馏证据表明,所有主要的营养循环,包括碳循环、氮循环、硫循环、铁循环等,在25亿年前就已经存在。然而直到24亿年前,随着大气氧含量的上升,生命才开始彻底改变我们这颗行星的景观。一度只活跃着细菌的世界,这时才有可能从外太空观察到生命的存在。直到这时,大气才开始积累化学性质活泼的气体,如氧气和甲烷,都由生命细胞不断补充。自此之后,生物才开始显露出行星规模的伟力。</p>
<p>过去研究人员认为,对生命而言,氧气是关键的环境决定因素:氧气并不限定地球上可以演化出哪种生命,而是如同松开了闸门,容许演化产生更大的复杂度。例如,动物的生活方式是运动,它们追逐猎物或者被追逐,这显然需要很多能量。而有氧呼吸提供的能量,比其他形式的呼吸作用高出几乎一个数量级,⑤因而不难设想,如果没有氧气,动物就不可能存在。</p>
<p>我们通常认为,氧气是有益的,有利于生命。然而实际上,在太初世界的生物化学机制中,氧气所处的地位恰好相反:它具有强氧化性,是生命的毒药。</p>
<p>大气氧含量明显上升了两次,分别是在24亿年前的大氧化事件时期,以及6亿年前漫长的前寒武纪最后阶段。氧气含量的每一次上升都解放了新的细胞结构和功能。大氧化事件同时带来了新的威胁和机遇,细胞通过一系列内共生,彼此交换、整合,逐渐积累起真核细胞的复杂度。而氧含量在寒武纪大爆发之前的第二次上升,彻底打破了细胞此前受到的物理束缚,就像魔术师的斗篷挥过,刹那间动物生命的无限可能喷涌而出。</p>
<p><em>环境决定论的推演的结果是生物的演化过程是多系辐射演化的,因为不同的环境必然产生不同的进化优势,从而演化出不同的优势种群。这也是生物多样性的体现。但真核生物的细胞不同,目前已知的所有真核生物的细胞几乎有着相同的结构。如果真核生物的起源是因为环境突变引发的进化,那么在细胞结构上也应该有着和宏观生物一样的多样性。而这是与我们目前的观测结果不符的。</em></p>
<blockquote>
<p>这种演化史观基于辩证唯物主义,代表了20世纪中前期形成的新达尔文主义演化生物学观点。这一派认为,演化生物学中主要的对立互动因素是基因和环境,也可称为先天和后天。生物完全由基因决定,而它们的行为又完全取决于环境。除此之外,还能有什么呢?然而,真实世界的生物学并不只有基因和环境,还有细胞,以及约束它们物理结构的条件,而这些因素与基因和环境并没有直接关系。</p>
</blockquote>
<p>这种演化史观基于辩证唯物主义,代表了20世纪中前期形成的新达尔文主义演化生物学观点。这一派认为,演化生物学中主要的对立互动因素是基因和环境,也可称为先天和后天。生物完全由基因决定,而它们的行为又完全取决于环境。除此之外,还能有什么呢?然而,真实世界的生物学并不只有基因和环境,还有细胞,以及约束它们物理结构的条件,而这些因素与基因和环境并没有直接关系。</p>
<p>古利斯自20世纪60年代后期开始发表自己的理论,她认为以上说法是错误的:真核细胞的演化不是通过标准的自然选择,而是通过系列内共生实现的。某些细胞迫于环境压力紧密地合作共生,最终,其中一些进入另一些体内生活。</p>
<p>真核细胞至少有两个部件来源于细菌内共生:线粒体(复杂细胞中的能量转换器)起源于α-变形菌,叶绿体(植物细胞的光合作用机器)起源于蓝细菌。</p>
<p>卡瓦利耶-史密斯的原始噬菌细胞假说与此同理,除了线粒体,其他所有部件都已就位,线粒体只是为细胞提供了更多能量。截至目前,这是关于真核细胞起源最正统的理论。</p>
<p>源真核生物,理论上还没有获取线粒体的原始真核生命,例如梨形鞭毛虫(小图B)。我们现在已经明确,所谓的源真核生物根本不是原始的真核生命,而是源于拥有线粒体的更加复杂的祖先。它们正确的分支点应该在真核生物分支的内部。</p>
<p>所有的植物、动物、藻类、真菌和原生生物都有共同的祖先——真核生物是单源性的!也就是说,植物、动物和真菌并非分别从不同的细菌演化而来,恰好相反:一个形态复杂的真核细胞种群在某个特定历史场合出现,而所有的植物、动物、藻类和真菌都演化自这个始祖种群。共同祖先,概念上就必须是单一的实体——不是指一个单独的细胞,而是一个单独的种群,其中所有的细胞本质上完全相同。</p>
<p>所有真核生物的共同祖先很快繁衍出了五个细胞形态各异的生物“超类群”</p>
<p>第一,每个超类群内部的基因多态性,远远超出各个超类群祖先之间的基因差异(图5)。这标志着早期的爆发性辐射演化,准确地说是单源辐射演化,意味着生命体从某种结构限制中解脱出来。第二,真核生物的共同祖先已经是非常复杂的细胞。通过比较各个超类群之间的特征,我们能归纳出共同祖先可能具有的特征。每个超类群中每个物种的共同特征,有理由认为继承自共同祖先;只有一两个超类群中才有的特征,则可以认为是后续演化出来的。</p>
<p>我们已知共同祖先有细胞核,其中贮藏着DNA。细胞核的结构非常复杂,所有真核生物都保留了下来。细胞核被双层膜包裹,或者说是很多看起来像双层膜的扁囊状膜结构,并续接至细胞质中的内质网膜。核膜上镶嵌着精细的蛋白质核孔,具有弹性内衬支撑结构。细胞核内部的其他结构,比如核仁,同样在所有真核生物中保留。值得强调的是,这些复杂结构中有十多种发挥核心功能的蛋白质,都存在于所有的超类群中;包裹着DNA的各种组蛋白也是如此。所有真核生物的DNA都是线性染色体,两头有端粒,其功能类似于鞋带上的顶盖,可以防止染色体两端“磨损”。真核细胞的基因是所谓的“碎片基因”:编码蛋白质的基因被分成很多个小片段(外显子),散布在长长的非编码区域(内含子)之间。在基因转译成蛋白质之前,内含子会被剪切出去,而这种剪切的生化机制在所有真核生物中都一样。内含子所处的位置,很多时候也都保留下来:在各种真核生物的同一个基因上,同一个序列位置会插入相似的内含子。</p>
<p>所有的真核生物都有基本相同的细胞结构:全都有复杂的内膜结构,例如内质网和高尔基体,后者的功能是包装和向外运输蛋白质;全都有动态的细胞骨架,能够进行各种变形,满足各类功能需求;全都有马达蛋白,作用是沿着细胞骨架的微管或微丝来回运送物质;全都有线粒体、溶酶体、过氧化物酶体、胞内和跨膜运输机制,以及相同的生物化学信号系统。真核生物的其他共同特征不胜枚举:所有真核细胞都进行有丝分裂,同源染色体在一个由微管构成的纺锤体上分离,由同一组酶控制。所有的真核生物都进行有性生殖,生命周期中都经历减数分裂,以形成配子(例如精子和卵子);配子再结合成合子,开始下一周期。极少数失去了有性生殖特征的真核生物,通常很快就会灭绝(在演化生物学的时间尺度下,“很快”一般指几百万年)。</p>
<p>尽管这两类原核生物都有出色的基因多样性和生物化学手段,但都没有演化出复杂的形态。对这些事实最合理的解释是,某种内在的物理限制条件让原核生物无法演化成复杂生命,而这个限制在真核生物演化之初,不知如何被解除了。</p>
<p>先考虑这里的基本假设:一定存在某种结构限制,同时作用于原核生物的两大类(即细菌和古菌),迫使它们在漫长的40亿年间保持简单的形态。只有真核生物迈入了复杂生命领域,而它们是通过爆发性的单源辐射演化达成这一壮举的。这意味着,无论具体是怎样的结构限制,一定是在真核生物演化之初得以解除。这似乎只发生过一次,所有的真核生物都有亲缘关系,都有同一个祖先。</p>
<h3 id="2-什么是活着">2 什么是活着?</h3>
<p>生命的实质在于结构(部分由基因和演化决定),但是生存(生长与繁殖)的实质在于环境,在于结构和环境如何相互关联。</p>
<p>能量以热量的形式释放,而热的本质是分子运动、分子互相推挤冲撞、分子的无序——也就是熵。</p>
<p>总的来说,对于任何自发的反应,自由能变化ΔG必须是负值。构成生命的所有反应,其总体效果也必须如此。也就是说,只有当ΔG为负值时,反应才能自发进行。要达到这个效果,要么系统的熵必须增加(系统变得更加混乱),要么系统必须以放热的形式失去能量,或者二者同时发生。这意味着当系统释放很多热量到环境中时,ΔH是很大的负值,这时局部的熵可以增加,系统可以变得更加有序。</p>
<p>所以追根溯源,生长的动力其实来自环境的反应性,这股力量源源不绝地流过活细胞;对我们来说是以食物和氧气的形式,对植物来说是以光子的形式。活细胞利用这股持续的能量流生长,克服自身趋于分解的倾向。</p>
<p>生命的能量问题有两个出人意料之处。首先,所有细胞的能量都来自一种特别的化学反应:氧化还原反应。</p>
<p>呼吸作用就是这样,从食物分子中夺取的电子并不直接传递给氧气(否则能量一下子就被全部释放了),而是传给一个“踏脚石”分子——通常是一种名为“铁硫簇”(iron-sulphur cluster)的无机晶体,其中含有带正电荷的铁离子(Fe3+);好几个铁硫簇会嵌在一个呼吸蛋白质中(图8)。从第一个开始,电子会跳到下一个类似的铁硫簇,不过这个比上一个更“渴望”电子。当电子从一个铁硫簇跳到下一个时,每一个都先被还原(接受电子,Fe3+ 变成Fe2+),再被氧化(失去电子,Fe2+变回Fe3+)。电子最终经过至少15次这样的转移,才到达氧气分子。</p>
<p>第二个出人意料之处是能量在ATP化学键中保存的具体机制。生命合成ATP,不是通过直截了当的化学反应,而是在一张薄膜的两侧制造质子梯度作为中间步骤。</p>
<p>这个巨大的电位势,或称为质子动力,驱动着最令人叹为观止的蛋白质纳米机器:ATP合酶(ATP synthase,图10)。“动力”意味着运动,而ATP合酶确实是一台旋转马达。在ATP合酶中,质子流推动曲轴,曲轴转动具有催化能力的旋转头——正是这些机械力驱动着ATP的合成。</p>
<p>呼吸作用根本不是化学反应;大家都在寻找的活性中间体根本不存在;电子流动和ATP合成的偶联机制其实是质子梯度,位于一层不可渗透的薄膜两边,叫作质子动力。难怪他那么招人讨厌!</p>
<p>为什么所有的活细胞都使用氧化还原反应作为自由能的来源?为什么所有细胞都使用跨膜的质子梯度保存这些自由能?我们还可以问得更本质一点:为什么用电子?为什么用质子?</p>
<p>碳到底有什么特别之处?说实话,很多。每个碳原子可以形成四个强力的化学键,比它在周期表上的同族邻居硅元素形成的键强得多。这些键让碳原子可以连接成变化极为丰富的长链分子,特别是蛋白质、脂质、糖和DNA。硅元素根本无法支持这样的化学多样性。而且,常温下没有类似二氧化碳的气态硅氧化物。</p>
<p>生命之所以存在,确实是因为活化能障壁存在——生命擅长的就是推倒这些障壁。强烈的反应性被压制在活化能障壁的背后:这就是化学为生命留下的一条“生路”。如果化学原理上不存在这么一个空子可钻,生命也许根本不会存在。</p>
<p>呼吸作用同时也是光合作用的基础。前面提过,光合作用有好几种不同的形式。无论哪种,太阳能都以光子的形式被一种色素分子(通常是叶绿素)吸收,激发这个分子的一个电子,电子通过一连串氧化还原中心到达电子受体(在光合作用中受体就是二氧化碳)。失去一个电子的叶绿素分子,会从最近的电子供体那里夺取一个补足,供体可以是水、硫化氢或者亚铁离子。与呼吸作用一样,供体是什么物质,原则上并不重要;不产氧光合作用把硫化氢或者铁作为电子供体,留下硫黄或者三价铁为废物。⑬产氧光合作用使用的供体要难对付得多:水;而它排放的废物是氧气。这里的关键在于,所有这些类型的光合作用都明显是从呼吸作用演变而来。它们使用同样的呼吸蛋白,同样的氧化还原中心,同样的跨膜质子梯度,同样的ATP合酶,整套工具全都一样。⑭唯一真正的不同,是光合作用发明了一个新的色素分子:叶绿素。不过叶绿素也不是凭空出现,而是与血红素(haem)有很近的亲缘关系。后者也是色素分子,存在于很多古老的呼吸蛋白之中。光合作用直接利用太阳能,深刻改变了整个世界,但从分子角度来看,它不过是让电子更快地流过呼吸链而已。</p>
<p>所以,呼吸作用最大的优势在于它的灵活多变。基本上任何一种氧化还原对,都可以被用在呼吸链上产生电子流。</p>
<p>呼吸蛋白可以通过水平基因转移扩散,还可以在任何环境中混搭工作,很大程度上是因为它们都基于共同的操作系统:化学渗透偶联。</p>
<p>细菌与古菌DNA复制的机制细节完全不同,包括几乎所有参与的酶。还有,细菌和古菌细胞壁(保护脆弱细胞的坚硬外壳)的化学成分也完全不同。细菌和古菌的发酵反应生化路径也有很大差异。甚至二者细胞膜的生物化学成分都不一样。</p>
<p>为什么生命出现得如此之早?为什么它们的形态复杂度停滞了几十亿年?为什么在漫长的40亿年间,复杂的真核细胞只演化出一次?为什么所有的真核生物都有一些令人费解的特征,而这些特征,包括有性生殖、两种性别和衰老,从来没有出现在细菌或古菌身上?这里我要再加上两个同样令人困惑的问题:为什么所有的生命都使用跨膜质子梯度来储存能量?这种怪异而又基础的机制是怎样(以及何时)演化出来的?</p></description>
</item>
<item>
<title>生命的起源</title>
<link>https://blog.jinzhi.site/posts/2022-05/%E7%94%9F%E5%91%BD%E7%9A%84%E8%B5%B7%E6%BA%90/</link>
<pubDate>Mon, 09 May 2022 09:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-05/%E7%94%9F%E5%91%BD%E7%9A%84%E8%B5%B7%E6%BA%90/</guid>
<description><h2 id="生命的起源">《生命的起源》</h2>
<p>作者 <em>刘大可</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/%E7%94%9F%E5%91%BD%E7%9A%84%E8%B5%B7%E6%BA%90/t6_41176020.jpg" alt=""></p>
<h3 id="序幕-钟表匠与石头">序幕 钟表匠与石头</h3>
<p>“类比只有偶然性,没有必然性”,两个无关的东西在某个方面像,仅此而已,不能证明别的方面也像——乌鸦是黑漆漆的,隐约泛着金属般的光泽,煤炭也是黑漆漆的,隐约泛着金属般的光泽,那又怎样呢,难道看见乌鸦会飞会下蛋,就说煤炭同理,也会飞会下蛋吗?</p>
<p>生物的代谢活动往往会制造一些有颜色的物质,这些物质可以被统称为色素。而色素之所以有颜色,就是因为它们会吸收某种频率的光,将其短暂地转变为化学能,生物如果能够及时地捕捉到这些化学能,也就感知到了光。</p>
<p>眼睛的进化历史要远远长于人类这个物种的进化历史,甚至长于所有脊椎动物的进化历史——它至少要追溯到所有两侧对称动物的共同祖先身上。这个共同祖先突变出了一个控制眼睛发育的关键基因PAX6,数亿年来,这个基因都没有明显的变化,把哺乳动物的PAX6 基因切下来放在昆虫身上,昆虫照样长出正常的眼睛。</p>
<p>复眼对运动极其敏感,昆虫可以分辨每秒240帧的动画,达到人眼的10倍,这同样带来了显著的生存优势——你想徒手抓苍蝇,那可难极了。</p>
<h3 id="第一章-原始浓汤">第一章 原始浓汤?</h3>
<p>40多亿年前的原始大气并不像“原始有机汤”假设的那样,是还原性的氢、氨、甲烷等物质,而是以二氧化碳和氮气为主的中性大气,这让米勒-尤里实验从根源上受到了质疑。</p>
<p>原始有机汤假说还有一个重大的理论破绽:要让小分子有机物形成大分子有机物,再形成生命,必须先让这些有机物达到相当高的浓度,并且维持这种浓度,否则那些诱人的化学反应在统计上根本就不会发生</p>
<p>从小分子有机物到大分子有机物,再到类似细胞的有机物复合体,最后到真正的细胞,需要持续数百万年的稳定环境,闪电当然达不到这样的条件,陨石坑也同样不具有这样的稳定性。而且,一切生化反应都非常脆弱敏感,所以孕育生命的环境不仅要稳定,还要足够温和。而闪电、火山活动、强紫外线都太过暴戾,它们虽然能够促使无机物转化成小分子有机物,但仅此而已,那些小分子胆敢长大一点,立刻就会被这些能量打碎。</p>
<h3 id="第二章-黑烟囱">第二章 黑烟囱?</h3>
<p>万物生长靠太阳,实际上是生物利用光合作用将太阳能固定下来,为生物的各种活动供能。如果有其他的能量获取来源,是不是太阳也就不是必需的了万物生长靠太</p>
<p>黑烟囱的热液中富含甲烷、硫离子、亚铁离子、氨还有二氧化碳,而生活在烟囱附近的微生物,就会利用这些物质,构造各种各样千奇百怪的氧化还原反应,获取能量,制造有机物</p>
<p>它源源不断喷发出来的矿物质具有丰富的催化活性——铁、锰、锌、铜,直到今天都是所有细胞里最重要的微量元素,与多种新陈代谢所需的酶有关</p>
<p>卡尔·波普尔,也就是那个提出“可证伪性是判断一个理论是不是科学的标准”的科学哲学家,也对这个与生物化学结合得更加紧密的生命起源理论青睐有加。</p>
<h3 id="第三章-还是白烟囱">第三章 还是白烟囱?</h3>
<p>亚铁+2价,二硫-2价二硫化亚铁(FeS2)</p>
<p>科学就是科学:无论综合的道路还是分析的道路,都立足于可观察、可检验的客观事实,白烟囱假说中的任何一个细节,也都会坦诚地接受包括实验在内的一切事实的检验,不断地修正进步——科学从来不幻想掌握任何真理,我们只是不断地逼近事实,逼近那40亿年前的事实。</p>
<h3 id="第四章-生命是什么">第四章 生命是什么?</h3>
<p>生命是维持在非平衡态上的物理系统,这通过从环境中汲取“负熵”实现。</p>
<p>前半句中所谓“平衡态”,是指一种“泯灭了一切差异,而变得处处均匀”的状态——绝大多数物理系统,如果没有得到专门的维护,没有从外界获得物质和能量的支持,那么它的任何运动都会使自己更加接近这种平衡态。</p>
<p>生命活动非但不会泯灭系统内部的差异,不会把自己变成一团均匀的物质,而且会维持各种尺度上的差异,形成复杂而有序的结构。</p>
<p>汽车的机械活动是汽车报废的原因,而生命活动不是衰老和死亡的原因。恰恰相反——这恐怕会颠覆绝大多数读者的认知——生命活动的结果是永恒。</p>
<p>熵是系统混乱程度的度量,物理学上常用S表示,而当一个系统的熵达到了最大,就会泯灭掉一切差异,变得处处均匀,也就是薛定谔的前一句话讲述的那种“平衡态”。</p>
<p>系统如果是孤立的,或者所处环境的熵已经达到了最大,那么这个系统的任何运动都不会减少自己的熵”——是的,这就是热力学第二定律的具体表现,有时也叫“熵增原理”</p>
<p>生命为什么总能维持很低的熵?
拿这句话和热力学第二定律对照一下,答案已经没有备选项:因为生命不是孤立的,而且它所处的环境的熵也没有达到最大。</p>
<p><em>吸收低熵原料,排出高熵产物,使整个生命系统维持低熵状态</em></p>
<blockquote>
<p>“新”带有较低的熵,“陈”带有较高的熵。</p>
</blockquote>
<p>新陈代谢的真谛,既不在于物质,也不在于能量,而在于熵增的转移,生命摄入了较低熵的物质,又排放了较高熵的物质,把熵增的趋势转嫁给了外部环境,自己就可以在热力学第二定律的眼皮子底下维持很低的熵,但如果将人和环境视为一个整体,我们又会发现,蕴含了生命的孤立系统,熵不会减少,完全符合热力学第二定律。</p>
<p>既然“熵”度量了系统的混乱,那就用“负熵”度量系统的秩序好了——事儿就这样成了,“生命是什么”的大问题有了“负熵”这个深入人心的回答。</p>
<p>耗散结构是一类不断与外界交换物质和能量,而维持在非平衡态上的物理系统。</p>
<p>凡是盯着流水发过呆的人,都会注意到绕过障碍物之后水流会出现漩涡。实际上,如果障碍物的形状比较规整,我们还常会看到两个漩涡一摇一摆周期交替地伸展下去,形成一种独特的有序图案,它被称为“卡门涡街”</p>
<p><em>那离开漩涡后的水流重归平稳,这又是什么过程呢?按照这里的逻辑,混乱的水流是高熵的,平稳的水流是低熵的,对水流来说这是一个熵减的的过程?如果在一个包含了这条水流的孤立系统里,是谁获得了这些熵呢?</em></p>
<blockquote>
<p>进入漩涡的是低熵的水流,离开漩涡的是高熵的水流,漩涡获得的一定是负熵。所以这些漩涡同样是典型的耗散结构。</p>
</blockquote>
<p>生命是一个控制系统,它的控制对象是它的自身,它的控制效果是令自身持存。</p>
<p>我们就不得不承认生命才是已知世界里最了不得的控制系统。生命体内充满了各种各样的调节机制和反馈机制,它们让构成自身的各种物质全都遵照一套严密的规则,在恰当的位置上发生恰当的反应,生命正是因此得以持存</p>
<p>一个控制系统一定是低熵的,而精确的控制行为一定会增加外界环境的熵,所以如果一个控制系统的功能就是维持自身,就必须一边摄入低熵的物质,一边释放高熵的物质,结果就是“汲取负熵”。</p>
<p>追问生命的起源是什么,我们先要回答耗散结构是怎么来的,再来回答生命作为一个控制系统是怎么来的。</p>
<p>热力学第二定律之后,非平衡热力学的研究还揭示了一个更加普遍的“熵增最大化定律”II:排除外界影响,一个变化的热力学系统不但会向着熵最大的状态发展,还会向着熵增最快的状态发展。</p>
<p>因此,一个热力学系统如果具有显著的熵增潜力,却因为某种“障碍”不能顺利实现熵增,就可能突变出某种耗散结构,加快熵增的速度。</p>
<p>所谓“燃点”,就是随机运动已经足够剧烈,淀粉分子普遍失去稳定性,很快产生了大量的自由基。自由基与氧此起彼伏的反应产生的能量来不及消散,于是累积下来,打碎了更多淀粉分子,制造了更多的自由基,终于形成一种滚雪球般的链式反应,释放了可观的能量,发出了光和热。</p>
<p>生命作为一种耗散结构,最初的起源也势必符合这个具有普遍意义的回答:原始地球上的某个环境中存在一些关键的物质,蕴含着显著的熵增潜力,但这些潜力却因为某种障碍无法充分释放。于是,那里的物质就在随机的化学反应中形成了某种耗散结构,奠定了生命的雏形。</p>
<p>那么,生命最基本的控制功能是什么呢?</p>
<p>第一项是“物质代谢”和“能量代谢”。它们控制了许多关键的生化反应的方向,借此,来自外界的物质被组织成了生命自身的结构,其他形式的能量转换成了生命可以利用的形式。</p>
<p>第二项是分子生物学的“中心法则”。</p>
<p>它规定了核酸要如何储存遗传信息,遗传信息又该如何表达为具体的蛋白质。</p>
<p>第三项是“边界控制”,这在前文刚有所涉及,但在生命起源问题中,它特指细胞膜的边界控制,或者说,是在追问最初的细胞膜是如何产生的,又是如何发展出选择透过性的。</p>
<p>自从进化生物学正式建立,“人类知道的一切生命形式,任何一个物种,都在进化上源自同一个祖先”已经成为生物学所有领域的普遍共识,其中很少再有什么可争议的事情。</p>
<p>为了之后的整本书讲述方便,我们从此就把“已知生命的最后一个共同祖先”简称作“末祖”,在其他中文科学读物里,这个概念大多会被叫作“露卡”,音译自“Last Universal Common Ancestor”的首字母缩写“LUCA”。</p>
<p>自由能就是系统当中可以用来对外界做功的能量——实际上,“熵”更直接的定义,正是“系统当中不能用来对外界做功的能量的总量”:</p>
<h3 id="第五章-难平的怪账">第五章 难平的怪账</h3>
<p>丙酮酸解体的过程就发生在线粒体的基质内。在那里,它首先会被切掉羧基。所谓“羧基”,就是——COOH,在原子比例上可以看作1个二氧化碳加上1个氢原子。实际上,那个被切掉的羧基也会立刻转化成二氧化碳,剩余一个氢原子就与氧分子结合成水。
丙酮酸被切掉羧基之后,剩下的部分就是乙酰基,乙酰基会继续发生一系列非常复杂的化学反应,彻底转化2分子二氧化碳,由此产生的氢原子,都会被拿去与氧气化合成水。</p>
<p>这一系列非常复杂的化学反应,被总称为“三羧酸循环”。从20世纪的30年代到50年代,共有3名生物学家因为揭示了它的机制而荣获诺贝尔奖。之后的研究还进一步确认了三羧酸循环就是细胞内一切物质能量代谢的枢纽,整套生物化学的中心。</p>
<p>整个三羧酸循环仍然不需要氧分子,没能释放出期望中的那些能量也就毫不奇怪了。是的,三羧酸循环产生的水和二氧化碳全都是有机物被催化分解的产物,而不是有机物被氧分子氧化的产物。</p>
<p><em>那这些电子的能量去哪了呢?</em></p></description>
</item>
<item>
<title>贪婪的多巴胺</title>
<link>https://blog.jinzhi.site/posts/2022-05/%E8%B4%AA%E5%A9%AA%E7%9A%84%E5%A4%9A%E5%B7%B4%E8%83%BA/</link>
<pubDate>Mon, 09 May 2022 09:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-05/%E8%B4%AA%E5%A9%AA%E7%9A%84%E5%A4%9A%E5%B7%B4%E8%83%BA/</guid>
<description><h2 id="贪婪的多巴胺">《贪婪的多巴胺》</h2>
<p>作者 <em>丹尼尔·利伯曼 迈克尔·E.朗</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/%E8%B4%AA%E5%A9%AA%E7%9A%84%E5%A4%9A%E5%B7%B4%E8%83%BA/t6_41626009.jpg" alt=""></p>
<h3 id="第1章-爱情">第1章 爱情</h3>
<p>由此人们提出了一个新的假说:多巴胺不是快乐的制造者,而是对意外的反应,即对可能性和预期的反应。</p>
<p>为什么爱情会消逝?我们的大脑生来渴求意外之喜,也因此期盼未来,每个激动人心的梦想都在那里萌生。但当任何事情,包括爱情变得习以为常时,那种兴奋感就悄然溜走,而我们的注意力又被其他新奇的事物吸引了。</p>
<p>研究这个现象的科学家把这种从新奇事物中得到的快感命名为“奖赏预测误差”。</p>
<p><em>这个理论感觉好玄学,不知道有没有科学依据</em></p>
<blockquote>
<p>佩蒂格鲁发现,大脑将外部世界分为两个独立的区域来管理,即“近体的”和“远体的”——简单来说就是远近两个区域。近体空间包括手臂可及之处,在此范围内的事物你可以马上用手控制,这是一个真实的世界。远体空间是指你的手臂无法触及的地方,不管是3英尺(约1米)远还是300万英里(约500万千米)外,这个领域代表着可能性。</p>
</blockquote>
<p><em>不识庐山真面目的魅力</em></p>
<blockquote>
<p>不管是天上的飞机、好莱坞的电影明星,还是遥远的山峰,只有不可及、虚幻的事物才富有魅力。魅力就是一个谎言。</p>
</blockquote>
<p><em>著名的斯金纳箱,盲盒和gotcha系统为什么能让人兴奋,原因正在于此</em></p>
<blockquote>
<p>在一项实验中,斯金纳把一只鸽子放到盒子里。他安装了一套装置,鸽子每啄一下杠杆就可以获得一颗食物丸。有些实验设定为需要啄一下,还有一些设置为10下,但在某次特定实验中条件都保持不变。得出的结果并不是特别有趣:不管需要啄多少次才能得到食物丸,每只鸽子都像官僚主义者在堆成山的文件上盖章一样啄着杠杆。
然后,斯金纳尝试了另一种方法。他设计了一个实验,释放一个食物丸所需的按压次数是随机变化的。现在,鸽子不知道什么时候会有食物,对它们而言每一次奖励都是出乎意料的。鸽子们变得兴奋起来,它们啄得更快了。有某种因素促使它们做出了更大的努力。多巴胺这个惊喜分子已被驯服,老虎机的科学基础也随之诞生。</p>
</blockquote>
<p>“为什么你就不能享受你已经拥有的一切呢?”</p>
<p>从多巴胺的角度来说,拥有是无趣的,只有获得才有趣。</p>
<p>为了享受我们拥有的东西,而不是仅仅可能得到的东西,我们的大脑必须从面向未来的多巴胺过渡到面向现在的某种化学物质,这是一系列神经递质,我们称之为“当下分子”。大多数人都对它们的名字略有耳闻,包括血清素、催产素、内啡肽(相当于大脑自产的吗啡)和内源性大麻素(相当于大脑自产的大麻)。</p>
<p>尽管多巴胺和当下分子的回路能一起工作,但在大多数情况下它们是相互对抗的。在当下分子回路被激活时,我们更喜欢体验周围的真实世界,多巴胺就会被抑制;而当多巴胺回路被激活时,我们则进入一个充满可能性的未来,当下分子会被抑制。</p>
<p>多巴胺代表着痴迷与渴望,而与长期关系最相关的化学物质则是催产素和血管升压素。催产素在女性中更活跃,而血管升压素在男性中更活跃。</p>
<p>当下神经递质让我们去体验现实(性生活的现实是强烈的),而多巴胺则超越现实,它总是使人“脑补”更好的事物。为了增加它的诱惑力,它让我们去控制另一个现实。这些想象中的世界或许永远都不可能变为现实,但这并不重要。多巴胺总是让我们追逐幻影。</p>
<p>等待延迟了爱情中最令人兴奋的阶段。由距离和节制产生的苦乐参半的感觉,正是这个化学反应的结果。</p>
<p>多巴胺的反应不是针对奖赏,而是针对奖赏预测误差,即实际奖赏减去预期奖赏。</p>
<p>激情之爱也可能转化为更持久的东西。它可以成为陪伴之爱,这可能不会像多巴胺那样让人兴奋,但基于当下神经递质,如催产素、血管升压素和内啡肽的作用,它会为你提供长期的幸福感。</p>
<p>爱情可以始于多巴胺,但不能终于多巴胺。多巴胺永不知足,它只会说“我想要更多”。</p>
<h3 id="第2章-毒品">第2章 毒品</h3>
<p>满怀希望的旅途要比到达目的地更快乐,这是多巴胺爱好者的座右铭。</p>
<p>在期望购买一件自己想要的东西时,瞄向未来的多巴胺系统被激活并创造出兴奋感。而一旦占有,所期望的对象就从向上看的远体空间转移到向下看的近体空间,从未来遥远的多巴胺领域转移到自我满足的当下分子领域。买家之所以会懊悔,就是因为当下体验无法弥补多巴胺能激励的损失。如果我们的购买行为是明智的,强烈的当下满足感可能会弥补多巴胺兴奋的损失。</p>
<p>有三种方法可以用来弥补买家懊悔:其一,通过购买更多的东西来追求更高的多巴胺;其二,通过少买一些东西来预防多巴胺的剧减;其三,增强从多巴胺欲望向当下喜爱转变的能力。</p>
<p>想要和喜欢是由大脑中的两个不同的系统产生的,所以我们想要的东西往往是我们不喜欢的。</p>
<p>“喜欢”涉及的大脑回路与欲望回路不同,并使用当下化学物质来发送信息,而不是多巴胺。特别是,“喜欢”依赖的化学物质与促进陪伴式爱情的长期满足相同,它们是内啡肽和内源性大麻素。海洛因和奥施康定等阿片类药物是目前最容易上瘾的药物之一,正是因为它们同时扰乱了欲望回路和喜欢回路(多巴胺和内啡肽分别在其中起作用)。大麻也同样如此,它与两个回路相互作用,刺激多巴胺和内源性大麻素系统。这种双重效应导致了不寻常的结果。</p>
<p>尼古丁不会像大麻一样让你兴奋,不会像酒精一样让你陶醉,也不会像飙车一样给你刺激。有些人说抽烟能让他们更放松,也有人说抽烟让他们更警觉,但实际上,它的主要作用是减少渴望。这是一个完美的闭环。吸烟的唯一目的是上瘾,这样就可以减轻渴望带来的不适感,由此让人体验到快乐,就像一个整天抱着石头的人,终于放下石头时会感觉一身轻松。</p>
<p>上瘾不是性格软弱或意志力不足的表现,它只是欲望回路因过度刺激而进入病理状态时会发生的情况。</p>
<p>多巴胺回路的增强促进了冲动行为,而不是满足感——它促进了“想要”,而不是“喜欢”。</p>
<p>一个东西是否容易获取,是它是否容易成瘾的关键因素。</p>
<p>事实上,为了减少这些物质引起的问题,最有效的方法就是使获得它们变得更困难。</p>
<p><em>贤者模式</em></p>
<blockquote>
<p>虽然性瘾者在观看色情作品时表现出更高的欲望水平,但他们给色情视频的评分并不一定高。</p>
</blockquote>
<p>青少年大脑与成年人大脑最大的差异在额叶,额叶直到20岁出头才发育完全。这会造成问题,因为正是额叶赋予了成年人良好的判断力。它们就像刹车闸,在我们要做一些不太好的事情时警告我们收手。在额叶功能不全的情况下,青少年容易冲动行事,即使他们知道怎么做更好,也容易做出不明智的决定。</p>
<p>电子游戏都是关于想象的。它们让我们沉浸在一个美梦成真的世界里,这样逃避现实的多巴胺就可以享受无尽的可能性。我们可以探索不断变化的环境,确保惊喜一直都在。我们可能从沙漠出发,走过雨林,然后走在逼真的城镇地狱里一条黑暗的小巷中,接着突然坐上火箭,飞向一个外星世界。</p>
<p>值得一提的是,在电子游戏中,当下的愉悦也有助于提升其吸引力。很多游戏可以让你和朋友一起玩。当我们为了娱乐而与同伴一起社交时,我们所获得的享受就是一种当下的体验。而且,聚在一起完成一个共同目标,这种行为也是由多巴胺驱动的,因为我们正朝着一个更好的未来而努力(即使只是占领敌方基地)。电子游戏能满足这两种类型的社交乐趣。</p>
<p>把想要和喜欢混为一谈是很自然的。我们会喜欢自己想要的东西,这似乎显而易见。如果我们是理性的生物,这句话确实应该成立。虽然我们认为我们是理性的生物,但一切证据都表明,我们不是。我们经常想要自己不喜欢的东西,欲望也可能会引导我们追求那些可能破坏我们生活的事物,如毒品、赌博和其他失控的行为。</p>
<h3 id="第3章-掌控的力量">第3章 掌控的力量</h3>
<p>经过中脑边缘回路的多巴胺会产生冲动,我们称该回路为多巴胺欲望回路。计算和规划(控制各种情况的手段)来自中脑皮层回路,我们称之为多巴胺控制回路</p>
<p>对于欲望多巴胺来说,这些幻影是我们希望拥有但目前还没有的,即我们想要在将来拥有的。对于控制多巴胺来说,幻影是想象力和创造性思维的基石,包括思想、计划、理论、抽象概念(如数学和美),以及尚未形成的世界。</p>
<p>多巴胺驱动了努力。这种努力的特征可能受到其他很多因素的影响,但如果没有多巴胺,努力从一开始就根本不会存在。</p>
<p><em>持续正反馈的重要性</em></p>
<blockquote>
<p>要刺激老鼠,可能一顿培根味的美餐就够了,但人类更复杂。我们要成功,首先必须相信我们能成功。而这会影响韧性。如果早早地取得一些成效,我们就更容易坚持下去。</p>
</blockquote>
<p>在正常情况下,强健的自我效能是一项宝贵的财富。有时它能成为一个自我实现的预言:充满信心地期待成功会让你眼前的障碍消失。</p>
<p>斯坦福大学商学院的研究人员研究了微妙的非言语行为如何影响人们对彼此的感知。他们指出,当人们伸展自己的肢体,占据大量空间时,他们就被认为占据了主导地位,或者说具有控制力。相反,当他们收紧肢体,占用尽可能少的空间时,他们就被认为是顺从的。</p>
<p>我们无意识地知道,当别人对成功抱有很高的期望时,我们就会给他们让路。我们会服从他们的意志——在控制多巴胺的驱动下,他们的自我效能得到了完全的表达。</p>
<p>为了实现一个目标而形成的关系被称为代理关系,它由多巴胺来控制。在这种关系中,其他人是你的延伸,是帮助你实现目标的代理人。</p>
<p>我们在社交活动中建立的关系主要是代理关系,这类关系通常会给双方都带来利益。为了享受社会交往本身而形成的关系则叫亲和关系。与另一个人在此时此地经历的简单的快乐,和当下神经递质(如催产素、血管升压素、内啡肽和内源性大麻素)有关。</p>
<p>代理关系的建立是为了控制环境,以尽可能多地从中获取可利用的资源,这是控制多巴胺掌管的领域。尽管我们认为控制是一种积极的,甚至是有些侵入性的活动,但也不一定。多巴胺不关心如何获得某种东西,它只是想得到它想要的。</p>
<p>欲望多巴胺会促进毒瘾,让人追求快感,但收获的多巴胺刺激越来越少,同样,有些人的控制多巴胺如此之多,这使他们对成就上瘾,因而无法体验当下的满足。</p>
<p>这些人身上表现出一种失衡效应,这是聚焦未来的多巴胺和聚焦现在的当下神经递质之间的失衡。他们想要逃避当下的情感和感官体验。对他们来说,生活关乎未来,关乎进步,关乎创新。尽管他们的努力带来了金钱甚至名声,但他们总是不开心。不管做了多少,他们都觉得不够。</p>
<p>ADHD最常见于儿童,这是有原因的。额叶负责控制多巴胺的活动,它是发育得最晚的脑区,直到一个人结束青春期进入成年期之后,它才与大脑的其他部分完成连接。控制回路的工作之一是限制欲望回路,因此冲动控制与ADHD相关。当控制多巴胺很弱时,人们会去追求他们想要的东西而不考虑长期的后果。</p>
<p>对ADHD最常见的治疗药物是哌甲酯和苯丙胺,这两种兴奋剂可以促进大脑中多巴胺的分泌。</p>
<p>用科学术语来说就是“相关性”并不意味着“因果关系”——仅仅因为两件事被一起发现并不一定说明其中一件事导致了另一件事。</p>
<p>对多巴胺系统控制能力极强或者极弱的人可以做出改变。患有ADHD的人通过药物治疗和心理治疗,有时只需等待几年时间就能有显著改善。</p>
<p>多巴胺的生成不受良心的约束。相反,在欲望的滋养下,它是狡猾的源泉。它被激发时,会抑制内疚感这种当下的情绪。它能够激励人们做出不懈的努力,但在追求的过程中不能避免使用欺骗甚至暴力手段。</p>
<p>多巴胺激增的感觉不错,但它不同于当下愉悦感的激增,后者是一种满足的激增。这一区别是关键:胜利引发的多巴胺激增会使得我们想要更多。</p>
<p>没有人喜欢输掉的滋味,而在你赢过之后,失利的滋味更是要苦涩十倍。</p>
<p>胜利者作弊的原因与吸毒者吸毒的原因相同。兴奋的感觉很好,而戒毒的感觉很糟糕。他们都知道自己的行为有可能毁掉自己的一生,但欲望回路并不关心这个问题。它只想要更多——更多的毒品,更多的成功。但真正的成功不可能来自欺骗。如果你犯了一个错误,人们会原谅你,但是如果你不诚实的话,这个污点会一直伴随着你。这就是为什么控制回路如此重要。它是理性的,能够做出冷静、合理的决定,这些决定能使你的利益最大化,不仅是今天的利益,也包括将来的利益。然而,对许多人来说,在追求胜利时,欺诈是一种强大的诱惑,有时会压倒一切。至少在短期内,欺诈是有效的。</p>
<p>暴力有时是功能障碍或病理状态的结果。但大多数情况下,暴力是一种选择——一种经过深思熟虑而采取的,以胁迫手段获得你想要的东西的方式。</p>
<p>暴力可以给我们统治之力,但要获得成功,它必须来自控制多巴胺的冷回路。</p></description>
</item>
<item>
<title>A Philosophy of Software Design</title>
<link>https://blog.jinzhi.site/posts/2022-03/a-philosophy-of-software-design/</link>
<pubDate>Fri, 25 Mar 2022 08:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-03/a-philosophy-of-software-design/</guid>
<description><h2 id="a-philosophy-of-software-design">《A Philosophy of Software Design》</h2>
<p>作者 <em>John Ousterhout</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/a-philosophy-of-software-design/CB_8wl4ol4qr1v36Rp6QB_parsecover.jpg" alt=""></p>
<h2 id="为什么要关注软件的复杂度">为什么要关注软件的复杂度</h2>
<p>从开发的视角来看,软件设计遇到的最基础的问题就是<strong>解构功能</strong>:如何将一个复杂的问题分解成能够被独立解决的小问题。这是一个熵减的过程。而从软件整体的视角来看,恰恰相反,随着加入的功能越来越多,维护的人员规模越来越庞大,是一个复杂性逐渐升高的过程。从另外一个角度说,<strong>软件开发时最大的一个限制就是如何去理解我们已经创造的这个系统。</strong></p>
<p>代码写出来是给人读的。机器执行只认机器码,代码写的再好也没用。但是软件系统除了运行以外,还有一个作用是能让除了写代码的你以外的人看懂。代码复杂度越低,从另外一个角度说,系统的鲁棒性也越强。因为有越多的人能读懂你的代码,review你的代码,代码中潜在的bug也越容易被发现。所以,作为开发者,我们的工作不仅是编写我们自己维护更轻松的代码,更要是编写所有人维护更轻松的代码。</p>
<p>如何降低软件整体的复杂度,作者在文中提到了两种方法:</p>
<ul>
<li><strong>让代码更加简洁明了</strong>。这个也有一个著名的KISS原则:<strong>Keep It Simple and Stupid</strong>。比如,忽略一些特殊的cases。</li>
<li><strong>将复杂性封装在黑盒,通过接口提供易用的功能</strong>。这种方式也被称作<strong>模块化设计</strong>。在这种情况下,局部系统的复杂性并不会显著增加整体系统的复杂性。</li>
</ul>
<h2 id="复杂度的表现">复杂度的表现</h2>
<p>复杂度可以有三种表现形式:</p>
<ul>
<li><strong>Change amplification</strong>:如果一个简单的变更需要我们在多个不同的地方修改相同的代码时,这时复杂度的第一个表现;</li>
<li><strong>Cognitive load</strong>:第二个复杂度的表现是开发者要完成一项任务,他需要了解多少前置知识。这个需要花的时间越多,潜藏忽略的信息就越多,完成的任务里存在bug的可能性越大;有时候,某种方法可能需要更多简单的代码实现,但它可能是更好的方法,因为它能降低其他开发者的心智负担。</li>
<li><strong>Unknown unknowns</strong>:第三个表现就是完成功能需要修改某处代码,但是这部分代码十分隐蔽,看起来与要完成的功能都毫无关联。这种情况是最糟的。因为前两种情况都可以通过花费更多的精力来解决,但是Unknown unknowns在于你根本没有意识到这一点。可能直到bug出现了才发现这个问题。</li>
</ul>
<p>一个好的系统设计是明了清晰。两种情况会显著增加系统的复杂性:</p>
<ul>
<li><strong>依赖过多</strong>,表现在代码不能被独立地理解和修改,必须先要搞明白或者修改另外一块代码;</li>
<li><strong>代码晦涩难懂</strong>,出现这种情况绝大部分是因为文档的不完善。不过,优秀的设计需要的文档会更少,如果代码需要大量的文档,通常也说明设计存在一定的问题。</li>
</ul>
<h2 id="如何避免复杂度增高">如何避免复杂度增高</h2>
<p>在开发过程中,要始终秉承一个思路:<strong>==我们的首要目标不仅仅是开发迭代一个功能,而是产出一个优秀的设计,这个设计正要能满足我们的功能需求==</strong>。这是一种更高层次地,从战略地眼光去看待软件开发。如果我们仅仅关注眼下的功能,一味地追求效率,留给后来者的很可能是一个烂摊子,俗称“屎山”。这种方式是非常短视的。</p>
<p>要做到这一点,就需要我们在开发中不断迭代系统的设计,使系统更符合当下的需求。作者建议花费总开发时间的10%-20%去改善系统设计,一方面不会过多地影响整体进度,另一方面也对后续的开发有着非常积极的意义。</p>
<p>当然,对于雇主来说,最好的降低开发成本的方式是雇佣优秀的工程师,相比平庸的工程师,他们的价钱更高点,但是能极大地提高生产效率。</p>
<h3 id="设计深模块">设计深模块</h3>
<p>所谓的深模块指的是<strong>模块里面尽量包括更多的复杂性,然后通过简单的接口暴露给用户。</strong> 这样做的目的是减少模块之间的依赖。</p>
<p><img src="https://blog.jinzhi.site/images/notes/a-philosophy-of-software-design/image-20220319190303080.png" alt="image-20220319190303080"></p>
<p>模块由两部分组成:接口和实现。接口包括了开发者想要使用这个模块所需知道的所有信息,即模块能干什么。接口不仅仅是传参,返回值,还包括了接口的注释,错误信息等等。</p>
<p>实现包括了模块为了实现这个承诺所有的代码,即怎么做的。</p>
<p>接口尽量简单,这样能减少模块给系统其他部分带来的复杂度。实现需要包括尽可能多的功能,可能有些人的想法是将每个功能都独立成一个模块,这样的缺点是整个系统的模块数增多,导致模块间的依赖也变多了,反而增加了复杂度。合理的方式应该是把相近的功能尽可能的合并到一个模块中。</p>
<p>如果考虑到有些用户对模块有定制化的需求,最好的办法也是通过默认值等方式,让最通用的case尽量简单,同时提供一些自定义修改的参数。</p>
<h3 id="减少errors的定义">减少Errors的定义</h3>
<p>在模块中,过多地返回定义地errors其实是一种懒惰地行为。因为很多时候,模块里不知道怎么处理错误,调用者也不会知道怎么处理错误。反而让调用者需要考虑到各种异常case,增大了代码的复杂度。如果模块有太多的异常需要处理,会增加接口的复杂性,也就是上面说的浅模块。</p>
<p>减少Errors的几种方法:</p>
<ol>
<li>最好的方式是定义一个没有errors需要处理的API,这是最能简化软件的方式;</li>
<li>第二种方法是能够让errors在模块内部消化,不对外暴露;</li>
<li>第三种方法是聚合多个异常,抛出一个统一的异常,这样也能减少调用者的代码量;</li>
<li>第四种方法就是打印错误信息,直接crash程序。这些异常通常不值得处理,并且也很难处理,发生的概率也很低。</li>
</ol>
<h3 id="设计第二种方案">设计第二种方案</h3>
<p>设计第二种方案是从设计的角度降低复杂度的方式。好的设计能从根本上降低软件的复杂度。软件设计很难,所以大多数时候我们的第一个想法并不一定是最好的设计。考虑多个可能性,对比他们之间的优劣,选择最好的一个,或者将他们取长补短组合起来,形成新的方案。</p>
<h3 id="注释">注释</h3>
<p>写注释的过程,也是一个改善系统设计的过程。好的注释能够给软件质量带来很大的提升。</p>
<p>要不要写注释主要取决于两点:</p>
<ol>
<li>函数是否足够简单,用户能一眼看出实现的功能;</li>
<li>函数名称和函数声明是否能够表达出函数的功能。</li>
</ol>
<p>如果不能达到这两点之一,那么注释就是必要的。不能因为不想写注释而把函数拆成多个简单的接口,增加系统的复杂度。</p>
<p>注释能够携带一些代码内看不到的信息。这些信息可能是设计者写代码的想法,有什么考虑因素。这些是在代码中无法体现的。注释本身也是抽象的一部分,对于复杂函数的注释,也更容易让用户了解接口的功能,以及检查接口的实现是否和设计者所期望的一致。</p>
<p>写注释最好的时间是在写代码之前。注释是对代码的总结,而不是简单的重复。</p>
<h3 id="summary-of-design-principles">Summary of Design Principles</h3>
<p>Here are the most important software design principles discussed in this book:</p></description>
</item>
<item>
<title>置身事内</title>
<link>https://blog.jinzhi.site/posts/2022-03/%E7%BD%AE%E8%BA%AB%E4%BA%8B%E5%86%85/</link>
<pubDate>Wed, 09 Mar 2022 14:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-03/%E7%BD%AE%E8%BA%AB%E4%BA%8B%E5%86%85/</guid>
<description><h2 id="置身事内中国政府与经济发展">《置身事内:中国政府与经济发展》</h2>
<p>作者 <em>兰小欢</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/%E7%BD%AE%E8%BA%AB%E4%BA%8B%E5%86%85/t6_40055543.jpg" alt=""></p>
<h3 id="前言-从了解现状开始">前言 从了解现状开始</h3>
<p>所以对于常见数据,如直接来自《中国统计年鉴》或万得数据库中的数据</p>
<h3 id="第一章-地方政府的权力与事务">第一章 地方政府的权力与事务</h3>
<p>要理解政府治理和运作的模式,首先要了解权力和资源在政府体系中的分布规则,既包括上下级政府间的纵向分布,也包括同级政府间的横向分布。</p>
<h3 id="第一节-政府治理的特点">第一节 政府治理的特点</h3>
<p>图1-1描绘了中国的五级政府管理体系:中央—省—市—县区—乡镇。</p>
<p>在乡以下的村落,则实行村民自治,因为行政能力毕竟有限,若村落也建制,那财政供养人口又要暴涨一个数量级。</p>
<p>一方面,维持大一统的国家必然要求维护中央权威和统一领导;另一方面,中国之大又决定了政治体系的日常运作要以地方政府为主。</p>
<p>中国共产党对政府的绝对领导是政治生活的主题。简单说来,党负责重大决策和人事任免,政府负责执行,但二者在组织上紧密交织、人员上高度重叠,很难严格区分。</p>
<p>大多数地方部门都要同时接受“条条”和“块块”的双重领导。</p>
<p>制度设计的一大任务就是要避免把过多决策推给上级,减轻上级负担,提高决策效率,所以体制内简化决策流程的原则之一,就是尽量在能达成共识的最低层级上解决问题。</p>
<p>若是部门事务,本部门领导就可以决定;若是经常性的跨部门事务,则设置上级“分管领导”甚至“领导小组”来协调推进。</p>
<p>在任何体制下,权力运作都受到两种约束:做事的能力及做事的意愿。前者取决于掌握的资源,后者取决于各方的积极性和主动性。</p>
<h3 id="第二节-外部性与规模经济">第二节 外部性与规模经济</h3>
<p>影响行政区划的首要因素是“外部性”,这是个重要的经济学概念,简单来说就是人的行为影响到了别人。在公共场合抽烟,让别人吸二手烟,是负外部性;打流感疫苗,不仅自己受益,也降低了他人的感染风险,是正外部性。</p>
<p>一件事情该不该由地方自主决定,可以从外部性的角度来考虑。若此事只影响本地,没有外部性,就该由本地全权处理;若还影响其他地方,那上级就该出面协调。</p>
<p>按照经典经济学的看法,政府的核心职能是提供公共物品和公共服务,比如国防和公园。这类物品一旦生产出来,大家都能用,用的人越多就越划算——因为建造和维护成本也分摊得越薄,这就是“规模经济”。但绝大部分公共物品只能服务有限人群。</p>
<p>但教材内容却不受物理条件限制,而且外部性极强。</p>
<p>第一个重要因素是人口密度。</p>
<p>第二个重要因素是地理条件。</p>
<p>第三个重要因素是语言文化差异。</p>
<p>理解了方言和文化的多样性,也就理解了推广普通话和共同的文化历史教育对维护国家统一的重要性。</p>
<p>另一个曾长期困扰边界公共治理的问题是环境污染,尤其是跨省的大江、大河、大湖,比如淮河、黄河、太湖等流域的污染。这是典型的跨区域外部性问题</p>
<p>下级之间一旦出现了互相影响、难以单独决断的事务,就要诉诸上级决策。反过来看,各级政府的权力都是由上级赋予的,而下放哪些权力也和外部性有关。在外部性较小的事务上,下级一般会有更大决策权。虽然从原则上说,上级可以干预下级的所有事务,但在现实工作中,干预与否、干预到什么程度、能否达到干预效果,都受制于公共事务的外部性大小、规模经济、跨地区协调的难度等。</p>
<p>撤县设区扩张了城市面积,整合了本地人口,将县城很多农民转化为了市民,有利于充分利用已有的公共服务,发挥规模收益。很多撤县设区的城市还吸引了更多外来人口。(21)这些新增人口扩大了市场规模,刺激了经济发展。</p>
<h3 id="第三节-复杂信息">第三节 复杂信息</h3>
<p>所以有信息优势的一方,或者说能以更低代价获取信息的一方,自然就有决策优势。</p>
<p>所以上级虽然名义上有最终决定权,拥有“形式权威”,但由于信息复杂、不易处理,下级实际上自主性很大,拥有“实际权威”。维护两类权威的平衡是政府有效运作的关键。若下级有明显信息优势,且承担主要后果,那就该自主决策。若下级虽有信息优势,但决策后果对上级很重要,上级就可能多干预。但上级干预可能会降低下级的工作积极性,结果不一定对上级更有利。</p>
<p>下级通常有信息优势,所以如果下级想办某件事,只要上级不明确反对,一般都能办,即使上级反对也可以变通着干,所谓“县官不如现管”;如果下级不想办某事,就可以拖一拖,或者干脆把皮球踢给上级,频繁请示,让没有信息优势的上级来面对决策的困难和风险,最终很可能就不了了之。即使是上级明确交代的事情,如果下级不想办,那办事的效果也会有很大的弹性,所谓“上有政策,下有对策”。</p>
<p>信息优势始终是权力运作的关键要素。</p>
<p><em>汉弗莱爵士YYDS</em></p>
<blockquote>
<p>衙门就像车,来办事就像坐车,当官的是骡子,我们才是车把式,决定车的方向。</p>
</blockquote>
<p>获取和传递信息需要花费大量时间精力,上级要不断向下传达,下级要不断向上汇报,平级要不断沟通,所以体制内工作的一大特点就是“文山会海”。作为信息载体的文件和会议也成了权力的载体之一,而一套复杂的文件和会议制度就成了权力运作不可或缺的部分。</p>
<p><em>私企从理论上讲信息传递效率要比体制内的工作要高,但现在很多大公司部门间各种扯皮,反而信息传递效率低下</em></p>
<blockquote>
<p>获取和传递信息需要花费大量时间精力,上级要不断向下传达,下级要不断向上汇报,平级要不断沟通,所以体制内工作的一大特点就是“文山会海”。作为信息载体的文件和会议也成了权力的载体之一,而一套复杂的文件和会议制度就成了权力运作不可或缺的部分。</p>
</blockquote>
<p>所谓权力,实质就是在说不清楚的情况下由谁来拍板决策的问题。</p>
<h3 id="第四节-激励相容">第四节 激励相容</h3>
<p>如果一方想做的事,另一方既有意愿也有能力做好,就叫激励相容。</p>
<p>所有面临双重领导的部门,都有一个根本的激励机制设计问题:到底谁是主要领导?工作应该向谁负责?</p>
<p>对于更宏观的工作,比如发展经济,涉及方方面面,需要地方调动各种资源。激励相容原则要求给地方放权:不仅要让地方负责,也要与地方分享发展成果;不仅要能激励地方努力做好,还要能约束地方不要搞砸,也不要努力过头。做任何事都有代价,最优的结果是让效果和代价匹配,而不是不计代价地达成目标。</p>
<p>激励相容原则首先要求明确地方的权利和责任。</p>
<p>其次是权力和资源的配置要制度化,不能朝令夕改。无论对上级还是对下级,制度都要可信,才能形成明确的预期。</p>
<h3 id="第五节-招商引资">第五节 招商引资</h3>
<p>这种“混合经济”体系,不是主流经济学教科书中所说的政府和市场的简单分工模式,即政府负责提供公共物品、市场主导其他资源配置;也不是简单的“政府搭台企业唱戏”模式。而是政府及其各类附属机构(国企、事业单位、大银行等)深度参与大多数生产和分配环节的模式。在我国,想脱离政府来了解经济,是不可能的。</p>
<h3 id="第一节-分税制改革">第一节 分税制改革</h3>
<p>公众所接触的信息和看到的现象,大都已经是博弈后的结果,而缺少社会阅历的学生容易把博弈结果错当成博弈过程。</p>
<p>成功的政策背后是成功的协商和妥协,而不是机械的命令与执行,所以理解利益冲突,理解协调和解决机制,是理解政策的基础。</p>
<p>分税制是20世纪90年代推行的根本性改革之一,也是最为成功的改革之一。改革扭转了“两个比重”不断下滑的趋势(图2-2):中央占全国预算收入的比重从改革前的22%一跃变成55%,并长期稳定在这一水平;国家预算收入占GDP的比重也从改革前的11%逐渐增加到了20%以上。改革大大增强了中央政府的宏观调控能力,为之后应付一系列重大冲击(1997年亚洲金融危机、2008年全球金融危机和汶川地震等)奠定了基础,也保障了一系列重大改革(如国企改革和国防现代化建设)和国家重点建设项目的顺利实施。分税制也从根本上改变了地方政府发展经济的模式。</p>
<h3 id="第二节-土地财政">第二节 土地财政</h3>
<p>这部分税是谁交呢?土地使用者?意思是房地产开发商在缴纳了土地使用权转让费用后,还需要承担其他的税务费用么?一类是直接和土地相关的税收,主要是土地增值税、城镇土地使用税、耕地占用税和契税,其收入百分之百归属地方政府。</p>
<p>土地转让虽然能带来收入,但地方政府也要负担相关支出,包括征地拆迁补偿和“七通一平”等基础性土地开发支出。</p>
<p>地方政府本来也不是靠卖地赚钱,它真正要的是土地开发之后吸引来的工商业经济活动。</p>
<p>2001年所得税改革后,中央财政进一步集权,拿走了企业所得税的六成。从那以后,地方政府发展经济的方式就从之前的“工业化”变成了“工业化与城市化”两手抓:一方面继续低价供应大量工业用地,招商引资;另一方面限制商住用地供给,从不断攀升的地价中赚取土地垄断收益。这些年出让的城市土地中,工业用地面积约占一半,但出让价格极低:2000年每平方米是444元,2018年是820元,只涨了85%。而商业用地价格增长了4.6倍,住宅用地价格更是猛增了7.4倍</p>
<p><em>这就是内卷</em></p>
<blockquote>
<p>如果竞争不能让资源转移到效率更高的地方,那这种竞争就和市场竞争不同,无法长久地提高整体效率。</p>
</blockquote>
<h3 id="第三章-政府投融资与债务">第三章 政府投融资与债务</h3>
<p>土地资本化的魔力,在于可以挣脱物理属性,在抽象的意义上交易承诺和希望,将过去的储蓄、现在的收入、未来的前途,统统汇聚和封存在一小片土地上,使其价值暴增。</p></description>
</item>
<item>
<title>KAIST-CS431: Lock</title>
<link>https://blog.jinzhi.site/posts/2022-03/kaist-cs431-lock/</link>
<pubDate>Tue, 08 Mar 2022 00:00:00 +0000</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-03/kaist-cs431-lock/</guid>
<description><ul>
<li><strong>Pros &amp; cons:</strong> simple &amp; possibly inefficient</li>
</ul>
<h2 id="low-level-lock-api">Low-Level Lock API</h2>
<p>常用的low-level锁的API有:</p>
<ul>
<li><strong>Lock.acquire()</strong>: 阻塞,直到获取到锁</li>
<li><strong>Lock.try_acquire():</strong> 返回锁是否已经被占用了,如果是,返回false,否,占用锁并返回true。不阻塞</li>
<li><strong>Lock.release()</strong>: 释放锁</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">L</span><span class="p">.</span><span class="n">acquire</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">r1</span> <span class="o">=</span> <span class="n">X</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">X</span> <span class="o">=</span> <span class="n">r1</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">L</span><span class="p">.</span><span class="n">release</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">L</span><span class="p">.</span><span class="n">acquire</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">r2</span> <span class="o">=</span> <span class="n">X</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">X</span> <span class="o">=</span> <span class="n">r2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">L</span><span class="p">.</span><span class="n">release</span><span class="p">();</span>
</span></span></code></pre></div><p>但是,这些API给用户在使用时造成了很多挑战(心智负担):</p>
<ul>
<li><strong>Relating lock and resource</strong>: 用户只有在拿到锁时才能访问被保护的变量;</li>
<li><strong>Matching acquire/release</strong>: 用户只能释放已经拿到的锁。</li>
</ul>
<p>如果锁没有得到正确的处理,会造成很多潜在的问题,并且,这些问题很难发现。因此,在并发编程时,low-level的锁API存在如下问题:</p>
<ul>
<li><strong>High cost</strong>:程序员需要始终关注API的使用;</li>
<li><strong>Potential bugs</strong>:不正确的使用容易造成很多潜在的bugs。</li>
</ul>
<h2 id="high-level-lock-api">High-Level Lock API</h2>
<ul>
<li>想要一个易用地,始终能保证安全地high-level API。
<ul>
<li>Acquire/release自动匹配;</li>
<li>Lock和resource显式关联。</li>
</ul>
</li>
</ul>
<p>C++中,这种API被称作<strong>RAII</strong>:<strong>Resource Acquisition Is Initialization</strong>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;string&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;mutex&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;iostream&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;fstream&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdexcept&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">write_to_file</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="o">&amp;</span> <span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="c1">// 创建关于文件的互斥锁
</span></span></span><span class="line"><span class="cl"> <span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">mutex</span> <span class="n">mutex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// 在访问文件前进行加锁
</span></span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">lock</span><span class="p">(</span><span class="n">mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// 尝试打开文件
</span></span></span><span class="line"><span class="cl"> <span class="n">std</span><span class="o">::</span><span class="n">ofstream</span> <span class="n">file</span><span class="p">(</span><span class="s">&#34;example.txt&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">file</span><span class="p">.</span><span class="n">is_open</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"> <span class="k">throw</span> <span class="n">std</span><span class="o">::</span><span class="n">runtime_error</span><span class="p">(</span><span class="s">&#34;unable to open file&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// 输出文件内容
</span></span></span><span class="line"><span class="cl"> <span class="n">file</span> <span class="o">&lt;&lt;</span> <span class="n">message</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// 当离开作用域时,文件句柄会被首先析构 (不管是否抛出了异常)
</span></span></span><span class="line"><span class="cl"> <span class="c1">// 互斥锁也会被析构 (同样地,不管是否抛出了异常)
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>RAII要求,资源的有效期与持有资源的<a href="https://zh.wikipedia.org/w/index.php?title=%e5%af%b9%e8%b1%a1%e7%9a%84%e7%94%9f%e5%91%bd%e6%9c%9f&amp;action=edit&amp;redlink=1">对象的生命期</a>严格绑定,即由对象的<a href="https://zh.wikipedia.org/wiki/%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">构造函数</a>完成<a href="https://zh.wikipedia.org/w/index.php?title=%e8%b5%84%e6%ba%90%e7%9a%84%e5%88%86%e9%85%8d&amp;action=edit&amp;redlink=1">资源的分配</a>(获取),同时由<a href="https://zh.wikipedia.org/wiki/%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0">析构函数</a>完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现<a href="https://zh.wikipedia.org/w/index.php?title=%e8%b5%84%e6%ba%90%e6%b3%84%e9%9c%b2&amp;action=edit&amp;redlink=1">资源泄露</a>问题。</p></description>
</item>
<item>
<title>枪炮、病菌与钢铁</title>
<link>https://blog.jinzhi.site/posts/2022-03/%E6%9E%AA%E7%82%AE%E7%97%85%E8%8F%8C%E4%B8%8E%E9%92%A2%E9%93%81/</link>
<pubDate>Mon, 07 Mar 2022 14:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-03/%E6%9E%AA%E7%82%AE%E7%97%85%E8%8F%8C%E4%B8%8E%E9%92%A2%E9%93%81/</guid>
<description><h2 id="枪炮病菌与钢铁">《枪炮、病菌与钢铁》</h2>
<p>作者 <em>贾雷德·戴蒙德</em></p>
<p><img src="https://blog.jinzhi.site/images/notes/%E6%9E%AA%E7%82%AE%E7%97%85%E8%8F%8C%E4%B8%8E%E9%92%A2%E9%93%81/t6_42766048.jpg" alt=""></p>
<h3 id="致我的中国读者">致我的中国读者</h3>
<p>物理学家、化学家和分子生物学家告诉我们,唯一严谨的科学研究方法是进行可操纵的实验室实验</p>
<h3 id="开场白-亚力的问题">开场白 亚力的问题</h3>
<p>几乎所有的儿童发展都强调:童年的刺激和活动有助于心智发展,不可逆的心智障碍与童年时的刺激不足有关。</p>
<p>各族群的历史循着不同的轨迹开展,那是环境而非生物差异造成的。</p>
<p>世界技术发展的共通模式反倒更容易理解。食物生产手段让农民生产出食物盈余,因此农业社会可以供养全职的技术专家,他们不用亲自耕作,只要专注于发展技术。</p>
<h3 id="第一部分-从伊甸园到卡哈马卡">第一部分 从伊甸园到卡哈马卡</h3>
<p>第一批走出非洲的人类祖先是直立人,其化石证据是在东南亚的爪哇岛发现的“爪哇人”(Java man)。</p>
<p>今天西伯利亚与阿拉斯加之间浅浅的白令海峡在冰期由于海面的升降,有时是海峡,有时则是宽广的洲际陆桥。</p>
<h3 id="第2章-历史的自然实验">第2章 历史的自然实验</h3>
<p>一般而言,政治单元越大,人口密度越高,技术和组织就越繁复</p>
<p>人口密度与政治单元大小的差异对波利尼西亚各地的社会有何影响?经济生活仍非常简单的岛屿要么人口密度低(如查塔姆群岛上的狩猎—采集社群),要么人口数量少(小环礁上的社群),要么二者皆是。在这种社会,家家户户自给自足,经济上几乎不需要专业分工。经济专业化是在面积较大、人口密度较高的岛屿发展出来的</p>
<h3 id="第3章-卡哈马卡的冲突">第3章 卡哈马卡的冲突</h3>
<p>阿塔瓦尔帕的被俘是现代历史上最大冲突中的决定性时刻,因此令人玩味不已。但这一事件之所以能引起更广泛的关注,是因为使得皮萨罗虏获阿塔瓦尔帕的种种因素,也在现代世界中许多殖民者和土著的冲突中起了作用。可以说,阿塔瓦尔帕被俘为我们了解世界史打开了一扇窗。</p>
<p>皮萨罗的武力优势在于西班牙人的枪炮、刀剑和马匹。而阿塔瓦尔帕的军队作战时没有骑乘任何动物,武器也只有石头、铜器、木棒、狼牙棒、斧头,加上弹弓和其他拼凑起来的武器。这种悬殊决定了美洲土著等族群与欧洲人交锋时的命运。</p>
<p>阿塔瓦尔帕之后成为印加皇帝的曼科(Manco),其麾下最骁勇善战的是尤潘基(Quizo Yupanqui)。1536年,尤潘基率军在利马发动突袭攻打西班牙人,然而对方只派出两支西班牙骑兵就把他们打得落花流水,尤潘基和手下的将领都被杀,全军覆没。另一支26名骑兵组成的队伍则在库斯科击败了曼科亲率的精锐。</p>
<p>较有免疫力的入侵族群把传染病带给其他没有免疫力的族群。天花、麻疹、流感、斑疹伤寒、腺鼠疫等已在欧洲蔓延的传染病,反倒成了欧洲人征服世界各地族群的助力。</p>
<p>皮萨罗成功的直接原因包括基于枪炮、钢铁武器和马匹的军事技术,来自欧亚大陆的传染病,欧洲的海事技术,中央集权的政治组织,以及文字。</p>
<h3 id="第二部分-食物生产的兴起与扩散">第二部分 食物生产的兴起与扩散</h3>
<p>食物生产对枪炮、病菌和钢铁的发展而言,是间接的前提条件。因此,从各大洲族群从事农牧的地理条件可看出日后的命运。</p>
<p>通过驯化动植物比狩猎—采集方式产出更多食物,从而增加人口密度,这是作物和家畜对人口直接的影响。间接一些的影响,是此类食物生产方法需要人们定居下来,这种生活方式促成了人口密度的增加。</p>
<p>作物和牲畜的有无,从根本上解释了为何帝国、文字、钢铁武器最早在欧亚大陆出现,而在其他地方较晚甚至没有出现。</p>
<h3 id="第5章-历史上的有与无">第5章 历史上的有与无</h3>
<p>所有生物体内的碳元素中,都含有固定比例的放射性碳14原子。生物死亡后,就不再从外界吸收碳元素,体内已有的放射性碳14原子仍继续衰变,半衰期约5 700年,生物死后大约4万年,其体内碳14的量就低到难以测量,或者难以和晚近时期混入的少量含碳14的遗存分辨了。</p>
<p>利用碳14测年法的第二个问题,就是大气中碳14与碳12的比例实际上并不恒定,而是在不同的时间段内有波动,因此基于比例恒定假设的计算结果必然存在系统性的小误差,必须加以校正。</p>
<p>在有些地区,生产食物的手段完全是独立发展出来的。这些地区在外来作物与动物输入之前,自行驯化了很多本土的作物与动物。这种地区目前只有5个我们有翔实的证据:西南亚(或称近东或肥沃新月地带)、中国、中美洲(墨西哥中部、南部以及邻近的中美洲地区)、南美洲的安第斯山脉(或许还包括邻近的亚马孙盆地)和美国东部</p>
<p>食物生产方面取得先机的族群,在迈向枪炮、病菌和钢铁的路途上,领先群雄。其结果就是历史上一连串“有”与“无”的冲突。</p>
<h3 id="第6章-下田好还是打猎好">第6章 下田好,还是打猎好?</h3>
<p>人类最近1万年的历史彰彰在目的事实,就是人类生计的变迁,主流是从狩猎—采集转变成食物生产。因此我们必须问的是:哪些因素让食物生产显得有利,使其他的生计类型都失色了?</p>
<p>第一个因素是可获得的野生食物越来越少。</p>
<p>第二个因素是,随着可驯化的野生植物变多,驯化植物的回报越来越多</p>
<p>影响狩猎—采集和农业消长的第三个因素,是生产食物的技术(例如采收、处理和储藏)不断改进。</p>
<p>第四个因素是人口密度上升与食物生产兴起的双向关联。</p>
<p>接受食物生产是一种自催化的过程,也就是在一个正回馈循环中不断自我催化的过程,这个过程开启后就会加速。</p>
<p>明白了食物生产与人口密度的这种双向关联,就可以解释这样一种矛盾状况:食物生产增加了每英亩土地上可食用卡路里的数量,但食物生产者的营养状况不如被他们取代的狩猎—采集者。之所以有时会出现这种状况,是因为食物增产的速度稍稍落后于人口增加的速度。</p>
<p>食物生产者社群的人口密度很高,凭数量优势就足以驱逐或消灭狩猎—采集者,更别提其他优势了(包括技术、病菌和职业军人)。</p>
<p>只有当地理或生态屏障将食物生产者拦在外面,或使得适用于当地的食物生产技术难以传入的时候,狩猎—采集者才有可能一直到近现代都在适于农牧的土地上保持原有的生活方式。</p>
<h3 id="第7章-杏仁的前世今生">第7章 杏仁的前世今生</h3>
<p>这些作物多半是自花传粉,直接把有利的基因传给下一代,不必和其他较没有价值的植物杂交,坏了自己的种。</p>
<p>谷物和豆类相比,这些作物的缺点是至少得种植3年才可能有收成,盛产期则必须等待10年之久。因此,只有在一地长住的人才有可能种植这些作物。这些最早的果树和坚果树还容易栽种,插枝甚至撒下种子就长出来了,晚期才被驯化的树木可没这么简单。插枝还有个好处,也就是保证后代和亲代一模一样。</p>
<p>谷物的优点有长得快、碳水化合物含量高,每公顷可收获多达1吨的食物。因此,谷物在今天人类摄入的卡路里中占了超过半数。今日世界的12种主要作物中,谷物就有5种:小麦、玉米、稻米、大麦和高粱。很多谷物蛋白质含量低,但这种缺陷可由豆类来补足。豆类的25%是蛋白质,黄豆更高达38%,谷物和豆类是均衡饮食不可或缺的。</p>
<h3 id="第8章-是苹果的问题还是印第安人的问题">第8章 是苹果的问题,还是印第安人的问题?</h3>
<p>现代世界作物年产量的80%是由十几种植物贡献的。这十几种“重量级”的作物如下:谷物有小麦、玉米、稻米、大麦和高粱,豆类有黄豆,块根或块茎类为马铃薯、木薯、甘薯,糖分的来源则是甘蔗、甜菜,水果如香蕉。</p>
<p>小麦、大麦的天生优势和墨西哥类蜀黍的明显劣势,大概就是欧亚社会和新大陆社会发展差异的要因。</p>
<p>肥沃新月地带的第三大优势,就是雌雄同株自花传粉的植物比例很高,这些植物偶尔也行异花传粉。</p>
<p>大多数的野生植物是雌雄同株异花传粉,或是雌雄异株——这种生殖生物学的现象给早期农民添了很多麻烦,因为他们选择一种突变的植物栽种后,其子代往往因和其他植株杂交而失去原来的特色。因此,大部分的作物来自野生植物中小部分行雌雄同株自花传粉者,或经无性生殖产生者(如用根来种植以复制亲代基因)。</p>
<p>野生单粒小麦、二粒小麦和大麦这三种的蛋白质含量很高,为8%~14%,相形之下,东亚的首要作物稻米和新大陆的玉米则蛋白质含量少,营养问题比较严重。</p>
<p>农业最早在肥沃新月地带发端,主要是“八大始祖作物”之功,它们是属于谷物的二粒小麦、野生单粒小麦、大麦,属于豆类的兵豆、豌豆、鹰嘴豆、苦野豌豆,属于纤维作物的亚麻。在这八种作物之中,只有亚麻和大麦的野生种分布超出了肥沃新月地带和安纳托利亚地区;有两种始祖作物的分布范围很窄,鹰嘴豆只在土耳其东南部,二粒小麦的分布范围则限于肥沃新月地带。因此,肥沃新月地带驯化当地现成的野生植物就足以发展农业,无须仰赖外地引入的作物包。有两种始祖作物无法在肥沃新月地带以外的地方被驯化,主要原因是它们只在肥沃新月地带有。</p>
<p>在食物生产兴起前,人类大抵以野生物种为食,因此掌握的关于野生动植物的知识特别丰富。最早的农民承袭了这样的知识——几千年来和大自然亲密生活、观察而累积下来的经验。因此,有价值的物种似乎不大可能逃过早期农民的眼睛。</p>
<p><em>其他领域也是一样的道理</em></p>
<blockquote>
<p>能优先获得引进的新作物和家畜(或者在文化上乐意接受新作物和家畜)的族群就可扩张版图,没有门路或意愿接受的族群自然遭到淘汰。</p>
</blockquote>
<p>肥沃新月地带登峰造极,新几内亚和美国东部则乏善可陈。肥沃新月地带野生动植物的驯养可谓轻而易举,不但驯化了许多物种,其中品种多产而优良者比比皆是,而且种类繁多。结果得以发展出精密的粮食生产业,促使人口更加稠密,进而迈入有先进技术和复杂政治组织的现代世界,同时携带可以消灭其他族群的传染病。</p>
<p>事实上,这个地表的各大洲上有成百上千互相竞争的社会,有些比较开放,很能接受新事物,有些则趋向保守。接受新的作物、家畜和技术的,就能日益精进,领袖群伦,人口数目远远超过那些不愿接受新事物的族群,接着便向后者大举入侵,甚至将之连根拔起。</p>
<p>北美土著不能驯化苹果,问题在于北美整个野生动植物的组合。这个组合的发展潜力有限,也是北美食物生产起步晚的主因。</p></description>
</item>
<item>
<title>Linux 进程调度</title>
<link>https://blog.jinzhi.site/posts/2022-01/linux-%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6/</link>
<pubDate>Tue, 11 Jan 2022 14:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2022-01/linux-%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6/</guid>
<description><p>总体来说,调度主要解决两类问题:</p>
<ul>
<li>什么时候调度</li>
<li>调度的目标是谁
而一个优秀的调度系统,可以从如下几个指标判断:</li>
<li>fast process response time</li>
<li>good throughput for background jobs</li>
<li>avoidance of process starvation</li>
<li>reconciliation of the needs of low- and high- priority processes</li>
</ul>
<p>Linux调度基于分时(Time-Sharing):多个进程共享同一个CPU,CPU运行时切分成多个时间片。分时技术依赖于timer interrupt,对进程来说是透明的。</p>
<p>Linux进程是<strong>可抢占</strong>的。当进程切换到<code>TASK_RUNNING</code>状态,内核检查其动态优先级是否比当前运行的进程的优先级高。如果更高,当前执行的进程中断掉,调度器切入,选择另外一个进程运行(通常是刚刚切换成runnable的进程)。当进程的时间片消耗完毕时,进程也是可被抢占的。当前进程<code>struct thread_info</code>中的<code>TIF_NEED_RESCHED</code>被设置,timer interrupt handler终止后,scheduler切入。</p>
<p>一个被抢占的进程并不是被暂停,它还维持在<code>TASK_RUNNING</code>状态,只不过不再使用CPU。</p>
<h2 id="调度算法">调度算法</h2>
<p>Linux 2.6版本之前的调度算法很简单,进程切换时,内核扫描runnable进程列表,计算它们的优先级,选择“最优”的进程运行。这种算法的问题是切换时的时间开销会随着进程数目的增多呈线性增长。</p>
<p>调度策略:</p>
<ul>
<li><code>SCHED_FIFO</code></li>
<li><code>SCHED_RR</code></li>
<li><code>SCHED_NORMAL</code></li>
</ul>
<h3 id="o1-scheduler">O(1) Scheduler</h3>
<p>基于Linux-2.6.12版本。</p>
<h4 id="优先级">优先级</h4>
<p>Linux 2.6.12 内核里,优先级表示分为用户态和内核态。用户态优先级就是我们常用的nice值。取值范围[-20, 19]。内核态优先级被称作静态优先级(static priority),取值范围是[0, 139]。其中,[0, 99]是Real-Time Process,[100, 139]对应用户态nice值。</p>
<p>在静态优先级外,真正发挥作用的是<strong>动态优先级(dynamic priority)</strong>。动态优先级实际上是静态优先级加上了一个运行时补偿(bonus):
$$
dynamic_priority = max(100, min((static_priority - 5 + bonus), 139)), bonus \in [0, 10]
$$</p></description>
</item>
<item>
<title>Linux 进程</title>
<link>https://blog.jinzhi.site/posts/2021-11/linux-%E8%BF%9B%E7%A8%8B/</link>
<pubDate>Tue, 30 Nov 2021 08:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2021-11/linux-%E8%BF%9B%E7%A8%8B/</guid>
<description><h2 id="进程和轻量级进程">进程和轻量级进程</h2>
<p><img src="https://blog.jinzhi.site/images/notes/linux-%E8%BF%9B%E7%A8%8B/image-20211112165309944.png" alt="image-20211112165309944"></p>
<p>在Linux内核中,进程/线程对应的数据结构是<code>task_struct</code>,定义在<code>include/linux/sched.h</code>中。</p>
<p>线程在Linux中的实现是 <strong><a href="https://en.wikipedia.org/wiki/Native_POSIX_Thread_Library">Naive POSIX Thread Library</a></strong> 。在内核眼中,Linux的线程实际上也是一个进程(<code>task_struct</code>),区别是线程的“进程”共享了地址空间、文件描述符等,称作==<strong>轻量级进程</strong>==。因此,Linux的线程也是独立的调度单元,是可以分别在不同的CPU上同时运行的。原生的Linux 线程(2.6版本内核之前)因为只实现在了用户态,所以,即使是一个多线程程序,对于内核来说只能看到一个进程,这些线程就只能在一个CPU上运行,对于多核多线程来说是很致命的。</p>
<p>从实现的角度,Linux的线程(LWP)是通过pthread库创建/使用的。而进程和线程的创建都调用了<code>clone()</code>系统调用(<code>kernel/fork.c</code> )。区别是两者使用了不同的flags。</p>
<h2 id="进程管理">进程管理</h2>
<h3 id="进程状态">进程状态</h3>
<ul>
<li><strong>TASK_RUNNING</strong>: The process is either executing on a CPU or waiting to be executed</li>
<li><strong>TASK_INTERRUPTIBLE</strong>: The process is suspended (sleeping) until some condition becomes true.</li>
<li><strong>TASK_UNINTERRUPTIBLE</strong>: Like TASK_INTERRUPTIBLE, except that delivering a signal to the sleeping process leaves its state unchanged.</li>
</ul>
<h3 id="pid">PID</h3>
<p>PID用来区分不同的进程结构体。Linux中最大PID数目可以在<code>/proc/sys/kernel/pid_max</code>中查看。</p>
<p>每个进程/轻量级进程都分配有一个唯一的PID。但是对于同一进程中的线程来说,我们拿到的是进程ID确是相同的,这是怎么实现的呢?</p>
<p>Linux为了兼容POSIX标准,利用了线程组(thread group)这一概念。所有的线程都会把线程组里面第一个线程的PID存在tgid字段内。==getpid()系统调用返回的实际上是tgid的值。==</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * sys_getpid - return the thread group id of the current process
</span></span></span><span class="line"><span class="cl"><span class="cm"> *
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Note, despite the name, this returns the tgid not the pid. The tgid and
</span></span></span><span class="line"><span class="cl"><span class="cm"> * the pid are identical unless CLONE_THREAD was specified on clone() in
</span></span></span><span class="line"><span class="cl"><span class="cm"> * which case the tgid is the same in all threads of the same group.
</span></span></span><span class="line"><span class="cl"><span class="cm"> *
</span></span></span><span class="line"><span class="cl"><span class="cm"> * This is SMP safe as current-&gt;tgid does not change.
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="nf">SYSCALL_DEFINE0</span><span class="p">(</span><span class="n">getpid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="nf">task_tgid_vnr</span><span class="p">(</span><span class="n">current</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h4 id="bitmap管理pid">bitmap管理PID</h4>
<h4 id="idr管理pid">IDR管理PID</h4>
<ul>
<li>PID: replace pid bitmap implementation with IDR API <a href="https://git.kernel.org/linus/95846ecf9dac5089aed4b144d912225f8ef86ae4">commit</a>, <a href="https://git.kernel.org/linus/e8cfbc245e24887e3c30235f71e9e9405e0cfc39">commit</a></li>
</ul>
<h2 id="进程切换">进程切换</h2>
<p>==<strong>TLDR</strong>:进程在调用<code>schedule()</code>方法时,将当前进程运行的寄存器信息保存在<code>task_struct-&gt;thread_info</code>内,同时从进程B中的<code>task_struct-&gt;thread_info</code>中加载B运行时的寄存器信息。==</p></description>
</item>
<item>
<title>正念冥想</title>
<link>https://blog.jinzhi.site/posts/2021-11/%E6%AD%A3%E5%BF%B5%E5%86%A5%E6%83%B3/</link>
<pubDate>Sat, 27 Nov 2021 08:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2021-11/%E6%AD%A3%E5%BF%B5%E5%86%A5%E6%83%B3/</guid>
<description><h2 id="正念是什么">正念是什么</h2>
<blockquote>
<p>与事物的现实性达成一致,我们可以通过拥抱它们来接受它们,在一刻一刻的觉知里。</p>
</blockquote>
<p>正念不是一个概念,而是一种与生命保持<strong>明智关系</strong>的方式,在生命唯一的当下展开。</p>
<h2 id="正念的态度因素">正念的态度因素</h2>
<p>如果想要长期地实践正念,并将正念融入到生活当中,那么我们需要关注一些正念的态度因素。对于这些正念的态度因素,最主要的是让这些概念融入到脑内,心里,而并非是强制遵守它。记住即可。</p>
<h3 id="不做评判">不做评判</h3>
<p>不做评判并不意味着我们不去对事件品头论足,而是说==<strong>意识到在做评判,并且有目的地不陷入到评判中,这本身就是一种美丽而又温柔的修习。</strong>==从某种意义上来说,这种修习的目的是让我们==足够关心自己以及我们所处的环境,从而不会有那种瞬间崩溃,也不会拘泥于喜欢或者不喜欢,想要或者不想要。==归根结底,当我们做出一个评判时,实际上我们就成为了它的囚徒。因为当事情发生时,评判降低了我们应对挑战的可能性。我们会困在一个观点中,而这个观点可能并不是一个完整地故事,完整地视角(盲人摸象)。</p>
<p>==<strong>关键点:不被自己地喜欢和厌恶所禁锢。把自己从下意识去评判“好”与“恶”这个习惯中解救出来,从自己的欲望和恐惧中解救出来。</strong>==</p>
<h3 id="耐心">耐心</h3>
<p>当我们失去耐心时,受害者往往是与我们生活在一起的人,我们最爱的人,还有和我们一起工作的人。</p>
<p>我们都希望朝着一个真正变化的、有益的方向前进。但如禅语所说,==<strong>&ldquo;you can&rsquo;t push the river&rdquo;</strong>==,不能逆势而为。为了到达某个地方而逼自己加快脚步是不明智的,因为这样往往会让自己忘记了真正的目的。</p>
<p>耐心是我们可以培养的一种非常可贵的品质。特别是在我们当下这个时代,它显得愈发重要。在这个电子信息时代,我们被电子产品吸引,甚至对其上瘾。我们的社会变成了一个点击型社会,所有在手机或者电脑上呈现的东西,逐步变得愈来愈“快餐”和“标题党”。它们通过社交媒体分发给我们,给我们想要的东西。这些“快餐”或者朋友圈的“点赞”实际上触发了我们大脑中神经元的连接,刺激多巴胺的释放,在这一瞬间会给人你一种非常非常满足的感觉。我们会因为朋友圈的一个“点赞”获得极大的满足感,但是这只持续了不到一秒,然后呢?我们会想要更多。我们开始观察社交媒体的生态,然后变得愈发不耐烦,这就是上瘾的一种表现,而我们甚至对此都毫无觉察。</p>
<p>所以耐心实际上培养了对这种事情的免疫力。因为当我们意识到有那种想要得到什么的冲动时,我们可以让自己慢下来(延迟满足)。==<strong>所以在一整天里,不断地觉察到自己有多么不耐烦,这便能帮助我们培养耐心。</strong>==</p>
<p>我们不必强迫自己变得耐心,而是去关注那些真的陷进去的时刻,强烈地想要去得到某个结果,然后引发出一系列蝴蝶效应般的结果,最后我们再也无法被满足(成瘾)。</p>
<h3 id="初学者心态">初学者心态</h3>
<blockquote>
<p>In the beginner&rsquo;s mind there are many possibilities, in the expert&rsquo;s mind, there are few.</p>
<p>—— Suzuki Roshi(铃木俊隆)</p>
</blockquote>
<p>==<strong>在某种意义上,我们所知道地一切都会妨碍我们看到那里的一些东西。因为我们是在透过我们的有色眼镜看面前的东西。</strong>==原有的知识结构,可能会演变为严重的问题。因为它会限定你,让你在已知的领域中对那些新兴未知的苗头视而不见,而那才是你想要发现的,全新的,前所未见的东西。</p>
<h3 id="信任">信任</h3>
<p>将信任带入修习实际上是在提醒自己有很多我们可以信任的东西,就比如我们可以相信我们的呼吸正在i体内穿梭,我们的脚将带着我们前行,我们的心脏仍会在今晚继续跳动。这些都是在提醒我们<strong>在当下这个时刻发生的事情中,有多少是可信的,是真正值得信任的。</strong> 最重要的是,我们的心是否值得信赖。==<strong>唯一能让自己的新值得信赖的方法就是相信自己,相信自己值得信赖,即便是我们把注意力放在了可能辜负自己或者他人信任的事情上</strong>==。</p>
<p>信任是深刻的,象征洞察力的源泉。它包括如何将内在与外在的经历发散开,使其保持良好的关联。</p>
<h3 id="不争">不争</h3>
<p>不争不是反对积极。**不争可能会要你去做更多的事情——不依附于结果,不去拼命让结果显得更好,而是确保当下正在做的每一件都具有完整性,然后水到渠成。**现实中我们会有DDL,会有一些杂七杂八的事情,看起来和不争的概念相悖。但是当把不争带到我们的意识前,让我们专注于当下,实际上可以帮助我们更快地完成工作。</p>
<p>当你的目标非常明确的时候,不争是很有效的。当我们太执着于胜利的结果时,可能会得不到很好的结果,因为心浮气躁。不争是将我们对胜利的渴望剥离出来,这并不意味着我们会成为一个消极的人,总是一事无成。</p>
<h3 id="接纳">接纳</h3>
<p><strong>接纳意味着看到事物的现实,承认并接受它们就是这样的。</strong> 接纳并不意味着我们不能努力去改变它们,接纳意味着看清现实,意味着你有时会意识到一些不可接受的事情。然后的问题是,我们该如何与这些不可容忍的事情,需要改变的事情保持明智的关系?这和消极的顺从一点关系都没有。</p>
<p>==<strong>我们需要看到真实的现实,正在发生的一切,看到它的复杂性以及简洁性。看得清楚,然后开始行动。接纳意味着对事物的现状有深刻洞察的认识,如果你是唯一一个看清这件事情走向的人,相信自己。</strong>==</p></description>
</item>
<item>
<title>Go channel 实现</title>
<link>https://blog.jinzhi.site/posts/2021-09/go-channel-%E5%AE%9E%E7%8E%B0/</link>
<pubDate>Sun, 12 Sep 2021 09:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2021-09/go-channel-%E5%AE%9E%E7%8E%B0/</guid>
<description><p>Go的Channel在runtime里面是一个hchan的结构体,每次我们make一个新的channel时,runtime从heap内分配一个hchan结构体管理channel。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">hchan</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">qcount</span><span class="w"> </span><span class="kt">uint</span><span class="w"> </span><span class="c1">// total data in the queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">dataqsiz</span><span class="w"> </span><span class="kt">uint</span><span class="w"> </span><span class="c1">// size of the circular queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">buf</span><span class="w"> </span><span class="nx">unsafe</span><span class="p">.</span><span class="nx">Pointer</span><span class="w"> </span><span class="c1">// points to an array of dataqsiz elements</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">elemsize</span><span class="w"> </span><span class="kt">uint16</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">closed</span><span class="w"> </span><span class="kt">uint32</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">elemtype</span><span class="w"> </span><span class="o">*</span><span class="nx">_type</span><span class="w"> </span><span class="c1">// element type</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">sendx</span><span class="w"> </span><span class="kt">uint</span><span class="w"> </span><span class="c1">// send index</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">recvx</span><span class="w"> </span><span class="kt">uint</span><span class="w"> </span><span class="c1">// receive index</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">recvq</span><span class="w"> </span><span class="nx">waitq</span><span class="w"> </span><span class="c1">// list of recv waiters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">sendq</span><span class="w"> </span><span class="nx">waitq</span><span class="w"> </span><span class="c1">// list of send waiters</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// lock protects all fields in hchan, as well as several</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// fields in sudogs blocked on this channel.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">//</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// Do not change another G&#39;s status while holding this lock</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// (in particular, do not ready a G), as this can deadlock</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// with stack shrinking.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nx">lock</span><span class="w"> </span><span class="nx">mutex</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><ul>
<li>qcount:当前channel queue内的element数目,当qcount等于dataqsiz时,表示channel buffer已经满了,send channel会被阻塞;</li>
<li>dataqsiz:channel的buffer大小,也就是我们在make时设定的值。dataqsiz在channel创建后不会再变动,因此channel的buffer是不会动态扩容的;</li>
<li>buf:buffered channel缓存elements的内存地址。是一个数组实现的环形队列;</li>
<li>elemsize:element类型大小;</li>
<li>closed:channel是否已经被关闭,防止关闭已经关闭的channel;</li>
<li>elemtype:channel element 类型;</li>
<li>sendx:buffer中下一个生产的element的index;</li>
<li>recvx:buffer中下一个消费的element的index;</li>
<li>recvq:阻塞的接收channel,是一个sudog的链表</li>
<li>sendq:阻塞的发送channel,是一个sudog的链表</li>
</ul>
<p>当buffer没有满时,send channel发送elements时直接把elements放在buf内,增加sendx和qcount;当buffer满时,send channel被阻塞,并加入到sendq链表内,等待被唤醒;</p></description>
</item>
<item>
<title>Rust: Concurrency</title>
<link>https://blog.jinzhi.site/posts/2021-06/rust-concurrency/</link>
<pubDate>Sun, 27 Jun 2021 09:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2021-06/rust-concurrency/</guid>
<description><h2 id="为什么rust不做green-threading">为什么Rust不做green threading</h2>
<ul>
<li>
<p><a href="https://github.com/rust-lang/rfcs/blob/master/text/0230-remove-runtime.md">remove-runtime rfcs</a></p>
</li>
<li>
<p><a href="https://github.com/rust-lang/rfcs/pull/230">remove-runtime pull request</a></p>
</li>
</ul>
<h2 id="send-和-sync">Send 和 Sync</h2>
<p>Rust中几乎所有的并发特性都是标准库或者第三方库提供的,真正由Rust语言本身提供的很少。而<code>std::marker</code>中的traits <code>Send</code>和<code>Sync</code>算是其中一个。</p>
<p>**实现<code>Send</code>的类型值的所有权可以在线程间传递。**Rust中绝大多数类型都实现了<code>Send</code>,但也有些例外,如<code>Rc&lt;T&gt;</code>:因为如果将<code>Rc&lt;T&gt;</code>的拷贝值的所有权在多个线程中传递,Rust无法保证<code>Rc&lt;T&gt;</code>引用值的正确性。</p>
<p><strong>实现<code>Sync</code>的类型表示该类型值可以在多个线程中被引用。也就是说,如果<code>&amp;T</code>实现了<code>Send</code>,那么类型<code>T</code>就是<code>Sync</code>。</strong></p>
<p><code>Send</code>和<code>Sync</code> markers其实就是将其他语言中的一些潜规则显式地标明出来,让编译器提前检查出代码中的隐患。</p>
<h2 id="线程原语">线程原语</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">thread</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">v</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">handle</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">thread</span>::<span class="n">spawn</span><span class="p">(</span><span class="k">move</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;Here&#39;s a vector: </span><span class="si">{:?}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">v</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">handle</span><span class="p">.</span><span class="n">join</span><span class="p">().</span><span class="n">unwrap</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>thread::spawn</code>新建一个线程,执行传递的闭包函数,返回一个<code>JoinHandler</code>,可以在主线程中调用<code>join</code>等待子线程结束。<code>move</code>用于强制闭包获取它使用的变量的所有权。</p>
<p>我们可以看看<code>thread::spawn</code>的实现:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="cp">#[stable(feature = </span><span class="s">&#34;rust1&#34;</span><span class="cp">, since = </span><span class="s">&#34;1.0.0&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">spawn</span><span class="o">&lt;</span><span class="n">F</span><span class="p">,</span><span class="w"> </span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">f</span>: <span class="nc">F</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">JoinHandle</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">where</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">F</span>: <span class="nb">FnOnce</span><span class="p">()</span><span class="w"> </span>-&gt; <span class="nc">T</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">F</span>: <span class="nb">Send</span> <span class="o">+</span><span class="w"> </span><span class="nb">&#39;static</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">T</span>: <span class="nb">Send</span> <span class="o">+</span><span class="w"> </span><span class="nb">&#39;static</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Builder</span>::<span class="n">new</span><span class="p">().</span><span class="n">spawn</span><span class="p">(</span><span class="n">f</span><span class="p">).</span><span class="n">expect</span><span class="p">(</span><span class="s">&#34;failed to spawn thread&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p><code>spawn</code>的入参和返回值都实现<code>Send</code>,同时其生命周期为<code>'static</code>。这是因为在多线程中,每个线程的执行周期不是同步的。父线程或者子线程的结束都会导致入参或者返回值的生命周期不满足Rust的约束条件。</p></description>
</item>
<item>
<title>Rust:Generic and Traits</title>
<link>https://blog.jinzhi.site/posts/2021-06/rustgeneric-and-traits/</link>
<pubDate>Fri, 25 Jun 2021 14:30:28 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2021-06/rustgeneric-and-traits/</guid>
<description><h2 id="泛型">泛型</h2>
<p>Rust 泛型会在编译时根据参数将泛型单态化(<em>Monomorphization</em> ),因此,Rust 泛型在运行时是没有任何损耗的。</p>
<h3 id="泛型在函数定义">泛型在函数定义</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">largest</span><span class="o">&lt;</span><span class="n">T</span>: <span class="nc">std</span>::<span class="n">cmp</span>::<span class="nb">PartialOrd</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nb">Copy</span><span class="o">&gt;</span><span class="p">(</span><span class="n">list</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="n">T</span><span class="p">])</span><span class="w"> </span>-&gt; <span class="nc">T</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">largest</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">list</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="o">&amp;</span><span class="n">item</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">list</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">item</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">largest</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">largest</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">item</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">largest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">number_list</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="mi">34</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">,</span><span class="w"> </span><span class="mi">43</span><span class="p">,</span><span class="w"> </span><span class="mi">15</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;the largest number is </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">largest</span><span class="p">(</span><span class="o">&amp;</span><span class="n">number_list</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">char_list</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="sc">&#39;y&#39;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;m&#39;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;c&#39;</span><span class="p">,</span><span class="w"> </span><span class="sc">&#39;d&#39;</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;the largest char is </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">largest</span><span class="p">(</span><span class="o">&amp;</span><span class="n">char_list</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><h3 id="泛型在结构体定义">泛型在结构体定义</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">Point</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">x</span>: <span class="nc">T</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">y</span>: <span class="nc">T</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Point</span><span class="p">{</span><span class="n">x</span>: <span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="n">y</span>: <span class="mi">3</span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;x is </span><span class="si">{}</span><span class="s">, y is </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Point</span><span class="p">{</span><span class="n">x</span>: <span class="mf">5.0</span><span class="p">,</span><span class="w"> </span><span class="n">y</span>: <span class="mf">3.0</span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;x is </span><span class="si">{}</span><span class="s">, y is </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>需要注意,这里表示结构体内x和y是同一种类型,如果需要x和y类型不同,需要定义两个泛型参数。</p></description>
</item>
<item>
<title>Rust:Ownership</title>
<link>https://blog.jinzhi.site/posts/2021-06/rustownership/</link>
<pubDate>Wed, 23 Jun 2021 22:24:24 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2021-06/rustownership/</guid>
<description><p>所有权(Ownership)是Rust中最独特的特性之一。正是因为所有权,Rust才能够在不需要GC的情况下保证内存安全。</p>
<p>我们常用的语言中,内存管理一般分为两种:</p>
<ul>
<li>自管理,谁分配的谁负责回收,如果没有回收,就会导致内存泄漏。如C、C++。</li>
<li>由统一的垃圾回收器管理,GC负责追踪和管理已分配内存,如果内存不再被使用,则由GC进行回收。</li>
</ul>
<p>Rust选择了第三种,内存会在拥有它的变量离开作用域时回收。因此,理解Rust的内存管理,就需要理解Rust中的所有权、借用规则、生命周期等概念。Rust会在编译过程中校验变量是否符合借用规则,从而保证运行时的内存安全,而且没有GC的额外开销。</p>
<p>在C++中,这种模式也被称作<strong>Resource Acquisition Is Initialization (RAII)</strong>。</p>
<h3 id="所有权基本规则">所有权基本规则</h3>
<p>所有权有三条基本规则:</p>
<ul>
<li>Rust中每个值都有一个对应的变量称之为所有者(owner);</li>
<li>同一时刻,一个值只能存在一个所有者;</li>
<li>当所有者离开作用域时,其拥有的值会被丢弃。</li>
</ul>
<h3 id="copy和move">copy和move</h3>
<p>因为同一时刻,一个值只能存在一个所有者,对于所有权的转让,Rust根据对象类型分为两种。</p>
<p>一种是Copy。即将值做拷贝,拷贝后,原变量拥有原值的所有权,新变量拥有新值的所有权,没有所有权的转移,两者可以同时使用。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">x</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;x is </span><span class="si">{}</span><span class="s">, y is </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">);</span><span class="w"> </span><span class="c1">// valid
</span></span></span></code></pre></div><p>支持Copy操作常见的类型通常是只存储在栈上的数据类型,如:</p>
<ul>
<li>所有的integer类型,如u32;</li>
<li>bool值;</li>
<li>char值;</li>
<li>所有的float类型,如f64;</li>
<li>元组,如果其包含的类型也都是支持Copy的。</li>
</ul>
<p>这些类型都实现了Copy特型。如果一种类型实现了Copy特型,在分配新变量后,原变量仍是可用的。</p>
<p>另外一种是Move。对于类似String这种在栈上存储了元数据,实际数据存储在堆上的数据类型来说,将原变量赋给新变量实际上是将堆上数据的所有权转移给新变量,原变量在赋值后不再使用。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">s1</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">String</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;hello!!!&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">let</span><span class="w"> </span><span class="n">s2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="fm">println!</span><span class="p">(</span><span class="s">&#34;s2 is </span><span class="si">{}</span><span class="s">, s1 is </span><span class="si">{}</span><span class="s">&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">s2</span><span class="p">,</span><span class="w"> </span><span class="n">s1</span><span class="p">);</span><span class="w"> </span><span class="c1">// invalid, s1 is no longer valid any more.
</span></span></span></code></pre></div><p><img src="https://blog.jinzhi.site/images/notes/rustownership/image-20210623093004432.png" alt="image-20210623093004432"></p></description>
</item>
<item>
<title>Dns Zone Files</title>
<link>https://blog.jinzhi.site/posts/2021-01/dns-zone-files/</link>
<pubDate>Sun, 03 Jan 2021 10:18:55 +0800</pubDate>
<guid>https://blog.jinzhi.site/posts/2021-01/dns-zone-files/</guid>
<description><h2 id="cname-记录">CNAME 记录</h2>
<h3 id="记录格式">记录格式</h3>
<pre tabindex="0"><code>bar.example.com.	CNAME	foo.example.com.
foo.example.com.	A		192.168.0.1
</code></pre><h3 id="cname-记录的限制">CNAME 记录的限制</h3>
<ol>
<li>如果一个 domain name 有了一个 CNAME 记录,那么它不能再拥有其它类型的记录</li>
<li>CNAME 记录必须指向另外一个 domain name,而不是一个 IP</li>
<li>CNAME 记录最好不要指向另外一个 CNAME 记录</li>
<li>MX 和 NS 记录不能指向一个 CNAME 记录</li>
</ol>
<h2 id="srv-记录">SRV 记录</h2>
<p>type code:33</p>
<h3 id="记录格式-1">记录格式</h3>
<pre tabindex="0"><code>_service._protocol.name. TTL class SRV priority weight port target.

_sip._tcp.example.com. 86400 IN SRV 0 5 5060 sipserver.example.com.
</code></pre><ul>
<li>_service: 服务名称 symbolic name</li>
<li>protocol: 协议</li>
<li>name: domain name,以 . 结束</li>
<li>priority: 条目优先级, 值越低优先级越高</li>
<li>weight: 同样优先级内不同条目的权重</li>
<li>target: canonical hostname,以 . 结束
如果是一个 MX 记录,那么 target 需要是一个 IP 地址(A 或者 AAAA)而不是 CNAME</li>
</ul>
<h2 id="soa-记录">SOA 记录</h2>
<p>start of authority, 一个权威的起始</p></description>
</item>
</channel>
</rss>