从零重建 VisiCalc 电子表格软件一、概述1. 项目背景A. VisiCalc 的历史意义VisiCalc 是世界上第一款电子表格软件，由 Dan Bricklin 和 Bob Frankston 于 1979 年创造。这款软件仅用数千行 6502 汇编代码编写，却能在 16KB 内存的机器上运行。它成为 Apple II 的杀手级应用，销量超过 100 万份，将早期的个人电脑转变为真正的商业工具。B. 电子表格的设计理念电子表格被誉为史上最佳用户体验设计之一。它具有极简的学习曲线，允许用户快速操作数据、描述逻辑、可视化结果，甚至可以用它创作艺术和运行 GameBoy 模拟器。C. 本文目标从零开始构建一个最小化的 VisiCalc 克隆版本，探索电子表格的核心设计原理。最终实现将包含完整的数据模型、公式求值器和简单的终端用户界面。2. 核心问题如何用最少的代码实现一个功能完整的电子表格系统，需要解决以下问题：如何表示和存储单元格数据如何解析和计算数学公式如何处理单元格间的依赖关系如何构建高效的用户界面二、系统设计1. 核心概念电子表格系统由以下几个基本概念组成：单元格（Cell）：表格的基本单位，可存储值、公式或标签网格（Grid）：单元格的二维集合公式（Formula）：引用其他单元格的数学表达式响应式计算：当单元格值变化时自动重新计算相关公式2. 数据模型设计A. 单元格结构每个单元格可以处于四种状态之一：空、数值、标签或公式。#define MAXIN 128 // 单元格输入最大长度 enum { EMPTY, NUM, LABEL, FORMULA }; // 单元格类型 struct cell { int type; // 单元格类型 float val; // 数值 char text[MAXIN]; // 原始用户输入 };B. 网格结构Excel 有 1,048,576 行和 16,384 列的限制，原始 VisiCalc 有 256 行和 64 列。我们的迷你版本可以使用更小的尺寸：#define NCOL 26 // 最大列数 (A..Z) #define NROW 50 // 最大行数 struct grid { struct cell cells[NCOL][NROW]; };3. 系统架构graph TB subgraph 用户界面 UI[TUI 界面] Input[输入处理] end subgraph 核心引擎 Grid[网格数据] Parser[公式解析器] Recalc[重新计算] end subgraph 数据模型 Cell[单元格] Formula[公式] end UI --> Input Input --> Grid Grid --> Parser Parser --> Recalc Recalc --> Grid Grid --> Cell Cell --> Formula三、公式求值器1. 解析器设计使用递归下降解析器（Recursive Descent Parser）来计算公式。解析器需要能够：识别数字和单元格引用处理运算符优先级支持函数调用访问网格中的单元格值A. 解析器结构struct parser { const char* s; // 公式字符串 int pos; // 当前位置 struct grid* g; // 网格引用 };2. 语法解析层次公式解析遵循经典的运算符优先级规则：表达式（Expression）：由加法和减法连接的项项（Term）：由乘法和除法连接的因子因子（Factor）：数字、单元格引用、函数调用或括号表达式graph TD A[表达式 Expression] --> B[项 Term] B --> C[因子 Factor] C --> D[数字 Number] C --> E[单元格引用 CellRef] C --> F[函数调用 Function] C --> G[括号表达式 ParenExpr] B -->|+/-| B A -->|*//| A3. 解析实现A. 因子解析因子是最基本的语法单位，可以是：带符号的一元运算函数调用（以 @ 开头）括号表达式数字字面量单元格引用float primary(struct parser* p) { skipws(p); if (!*p->p) return NAN; // 处理一元加号 if (*p->p == '+') p->p++; // 处理一元减号 if (*p->p == '-') { p->p++; return -primary(p); } // 函数调用 if (*p->p == '@') { p->p++; return func(p); } // 括号表达式 if (*p->p == '(') { p->p++; float v = expr(p); skipws(p); if (*p->p != ')') return NAN; p->p++; return v; } // 数字字面量 if (isdigit(*p->p) || *p->p == '.') return number(p); // 单元格引用 return cellval(p); }B. 项解析处理乘法和除法运算：float term(struct parser* p) { float l = primary(p); for (;;) { skipws(p); char op = *p->p; if (op != '*' && op != '/') break; p->p++; float r = primary(p); if (op == '*') l *= r; else if (r == 0) return NAN; else l /= r; } return l; }C. 表达式解析处理加法和减法运算：float expr(struct parser* p) { float l = term(p); for (;;) { skipws(p); char op = *p->p; if (op != '+' && op != '-') break; p->p++; float r = term(p); l = (op == '+') ? l + r : l - r; } return l; }4. 单元格引用解析将单元格地址（如 A1、AB123）转换为网格坐标：int ref(const char* s, int* col, int* row) { const char* p = s; // 解析列字母 if (!isalpha(*p)) return 0; *col = toupper(*p++) - 'A'; if (isalpha(*p)) *col = *col * 26 + (toupper(*p++) - 'A'); // 解析行数字 char* end; int n = strtol(p, &end, 10); if (n <= 0 || end == p) return 0; *row = n - 1; return (int)(end - s); }5. 函数支持支持基本的内置函数：@SUM：求和函数，支持范围参数（如 @SUM(A1...C3)）@ABS：绝对值函数@INT：取整函数@SQRT：平方根函数四、响应式计算1. 计算依赖问题电子表格的核心特性是响应式计算：当某个单元格的值发生变化时，所有引用该单元格的公式都应该自动重新计算。2. VisiCalc 的解决方案原始 VisiCalc 受限于 16KB 内存，采用了简单而有效的策略：每次单元格更新时重新计算整个表格用户可以选择按行优先或按列优先的计算顺序对于复杂依赖，建议用户多次运行手动重新计算命令3. 自动迭代计算现代实现可以自动化这个过程，运行多次迭代直到没有新的变化：graph TD A[单元格更新] --> B[开始重新计算] B --> C[遍历所有单元格] C --> D{单元格是否为公式?} D -->|是| E[重新计算公式值] D -->|否| C E --> F{值是否改变?} F -->|是| G[标记已变化] F -->|否| C G --> C C --> H{是否遍历完成?} H -->|否| C H -->|是| I{是否有变化?} I -->|是| B I -->|否| J[完成]实现代码void recalc(struct grid* g) { for (int pass = 0; pass < 100; pass++) { int changed = 0; for (int r = 0; r < NROW; r++) for (int c = 0; c < NCOL; c++) { struct cell* cl = &g->cells[c][r]; if (cl->type != FORMULA) continue; struct parser p = {cl->text, cl->text, g}; float v = expr(&p); if (v != cl->val) changed = 1; cl->val = v; } if (!changed) break; } }五、单元格设置1. 输入分类根据用户输入的第一个字符，自动判断单元格类型：以 +、-、( 或 @ 开头：公式以数字或小数点开头：数值其他：标签void setcell(struct grid* g, int c, int r, const char* input) { struct cell* cl = cell(g, c, r); if (!cl) return; // 空输入清空单元格 if (!*input) { *cl = (struct cell){0}; recalc(g); return; } // 保存原始输入 strncpy(cl->text, input, MAXIN - 1); // 分类输入类型 if (input[0] == '+' || input[0] == '-' || input[0] == '(' || input[0] == '@') { cl->type = FORMULA; } else if (isdigit(input[0]) || input[0] == '.') { char* end; double v = strtod(input, &end); cl->type = (*end == '\0') ? NUM : LABEL; if (cl->type == NUM) cl->val = v; } else { cl->type = LABEL; } // 触发重新计算 recalc(g); }2. 测试用例struct grid g = {0}; // 设置 A1=5, A2=7, A3=11, A4=@SUM(A1...A3) setcell(&g, 0, 0, "5"); setcell(&g, 0, 1, "7"); setcell(&g, 0, 2, "11"); setcell(&g, 0, 3, "+@SUM(A1...A3)"); assert(g.cells[0][3].val == 23.0f); // 修改值，总和应重新计算 setcell(&g, 0, 0, "5"); setcell(&g, 0, 1, "+A1+5"); setcell(&g, 0, 2, "+A2+A1"); assert(g.cells[0][3].val == 5.0f + 10.0f + 15.0f); // 修改 A1，所有公式应重新计算 setcell(&g, 0, 0, "7"); assert(g.cells[0][3].val == 7.0f + 12.0f + 19.0f);六、用户界面1. 界面布局VisiCalc 的屏幕分为四个区域，垂直排列：graph TB subgraph VisiCalc 界面 S[状态栏: 单元格地址 + 值/公式 + 模式] E[编辑行: 当前输入或单元格内容] H[列标题: A, B, C, ...] G[网格区域: 单元格内容 + 行号] end2. 视口设计由于网格可能比终端屏幕大，需要实现滚动视口：#define CW 9 // 列显示宽度 #define GW 4 // 行号栏宽度 // 可见的行列数 int vcols(void) { return (COLS - GW) / CW; } int vrows(void) { return LINES - 4; } struct grid { struct cell cells[NCOL][NROW]; int cc, cr; // 光标列、行 int vc, vr; // 视口左上角 };3. 视口滚动逻辑当光标移动到屏幕外时，视口自动跟随：// 水平滚动 if (g.cc < g.vc) g.vc = g.cc; if (g.cc >= g.vc + vcols()) g.vc = g.cc - vcols() + 1; // 垂直滚动 if (g.cr < g.vr) g.vr = g.cr; if (g.cr >= g.vr + vrows()) g.vr = g.cr - vrows() + 1;4. 渲染实现A. 状态栏显示当前单元格地址、值或公式，以及当前模式：// 状态栏：单元格地址 + 值 + 模式指示器 attron(A_BOLD | A_REVERSE); mvprintw(0, 0, " %c%d", 'A' + g->cc, g->cr + 1); if (cur->type == FORMULA) printw(" %s = %.10g", cur->text, cur->val); mvprintw(0, COLS - 6, mode == ENTRY ? "ENTRY" : "READY"); attroff(A_BOLD | A_REVERSE);B. 编辑行显示当前输入或单元格内容：if (mode) mvprintw(1, 0, "> %s_", buf); else if (cur->type != EMPTY) mvprintw(1, 0, " %s", cur->text);C. 列标题和网格绘制列标题（A、B、C...）和网格内容：// 列标题 attron(A_BOLD | A_REVERSE); for (int c = 0; c < vcols(); c++) mvprintw(2, GW + c * CW, "%*c", CW, 'A' + g->vc + c); attroff(A_BOLD | A_REVERSE); // 网格单元格 for (int r = 0; r < vrows() && g->vr + r < NROW; r++) { int row = g->vr + r, y = 3 + r; // 行号栏 attron(A_REVERSE); mvprintw(y, 0, "%*d ", GW - 1, row + 1); attroff(A_REVERSE); // 单元格内容 for (int c = 0; c < vcols() && g->vc + c < NCOL; c++) { int col = g->vc + c; struct cell* cl = cell(g, col, row); // 格式化显示 int is_cur = (col == g->cc && row == g->cr); if (is_cur) attron(A_REVERSE); mvprintw(y, GW + c * CW, "%s", formatted_buf); if (is_cur) attroff(A_REVERSE); } }七、输入处理1. 模态界面设计VisiCalc 采用模态界面设计，用户处于两种模式之一：READY 模式：在网格中导航ENTRY 模式：编辑单元格内容2. 特殊字符处理在 READY 模式下，以下字符有特殊含义：/：进入命令模式（/B 清空、/Q 退出、/F 格式化）：进入跳转模式（输入单元格地址如 B5 并按 Enter）其他可打印字符：进入单元格编辑模式3. 命令模式实现if (ch == '/') { mvprintw(1, 0, "Command: /"); refresh(); ch = getch(); if (toupper(ch) == 'Q') break; // 退出 if (toupper(ch) == 'B') { // 清空单元格 *cell(&g, g.cc, g.cr) = (struct cell){0}; recalc(&g); } if (toupper(ch) == 'F') { // 格式化 // 格式化命令处理 } }4. 确认和导航在 ENTRY 模式下：Enter：确认输入并向下移动Tab：确认输入并向右移动if (ch == 10 && mode == ENTRY) { setcell(&g, g.cc, g.cr, buf); if (g.cr < NROW - 1) g.cr++; mode = READY; }八、实现总结1. 核心功能通过约 500 行 C 代码，我们实现了：完整的单元格数据模型递归下降公式解析器自动迭代重新计算机制模态 TUI 用户界面视口滚动和渲染2. 未实现功能完整版本还可以添加：文件 I/O/R 复制命令（批量复制公式）更多内置函数移动和删除行列更复杂的格式化选项3. 设计启示VisiCalc 的设计历经 47 年仍然有效，证明了其核心设计的优雅和简洁。关键设计原则包括：极简的数据模型响应式计算直观的用户界面高效的实现方式参考资料VisiCalc reconstructed - zserge.comkalk - VisiClone implementation on GitHubWorld smallest office suite
生活在平面国的程序员一、概述1. 文章简介A. 标题The programmers who live in Flatland（生活在平面国的程序员）B. 发布时间2025 年 11 月 24 日C. 来源Red Planet Labs 博客D. 作者Nathan Marz（Clojure 专家、Rama 框架创始人）2. 核心隐喻文章借用 Edwin Abbott 的经典小说《Flatland：A Romance of Many Dimensions》中的概念：二维世界的居民无法理解第三维度的存在。作者将此隐喻延伸到编程领域——许多程序员受限于自己的"维度"，无法理解更强大的编程抽象。3. 文章主旨大多数程序员生活在"编程平面国"中，他们无法理解 Lisp/Clojure 宏（macros）所代表的更高维度的编程能力。这并非因为宏没有价值，而是因为他们缺乏理解这种抽象所需的"表征机制"。二、背景故事：Paul Graham 与 Lisp1. Beating the Averages2001 年，Paul Graham 发表著名文章《Beating the Averages》，核心论点包括：A. Lisp 的根本优势Lisp 比其他语言本质上更强大宏（macros）是 Lisp 的核心区分能力Viaweb 使用 Lisp 构建了竞争对手无法实现的功能B. 作者的亲身体验Nathan Marz 受这篇文章影响，过去 15 年一直将 Clojure 作为主要编程语言。他证实 Paul Graham 关于宏的描述"绝对正确"。2. 现实困境A. 采用率极低Lisp/Clojure 全球使用比例极小（Stack Overflow 调查显示约 2.6%）与其巨大的优势不成正比B. 常见误解"生态系统"障碍：对 Common Lisp 有效，但 Clojure 可与 Java 生态系统互操作"括号很怪"的直观反应括号的感知成本从未与 Clojure 的巨大收益进行权衡C. Blub ParadoxPaul Graham 提出的"Blub 悖论"试图解释这种脱节，本文从另一个角度补充这个解释。三、编程的维度1. 两类抽象A. 自动化抽象大多数程序员使用的抽象属于此类：将一组操作打包在一个名称后面函数是经典例子：接受输入，产生输出只需要了解函数的规格和性能特征B. 维度扩展抽象罕见的抽象类型：扩展编程本身的代数改变基本概念和概念间关系的种类创造新的"维度"2. Lisp/Clojure 宏的维度之力A. 语言的自我组合源于语言的统一性，使语言能够自我组合：编译时逻辑可以像运行时一样执行使用相同的函数和技术语法树可以被操纵和转换B. 语义控制实现对代码语义本身的控制：宏不仅仅是语法糖它们改变了代码的结构和含义可以创造全新的控制结构C. 编译时的轻松操纵effortless compile-time manipulation 是一个新的编程维度：编译时代码生成变得简单元编程成为一等公民运行时和编译时的界限变得模糊3. Flatland 隐喻graph LR subgraph Flatland["2D 平面国"] F1[三角形] F2[正方形] F3[圆形] end subgraph Spaceland["3D 空间国"] S[球体] end S -.-> F1 S -.-> F2 S -.-> F3在 Flatland 中，正方形无法理解第三维度，因为他只能以 2D 思考。同样，你无法理解新的编程维度，因为你不知道如何在该维度中思考。你没有"表征机制"来理解新维度提供的东西。2D 程序员可能会得出 3D 概念客观上错误的结论——这不是因为他们理解了它，而是因为他们试图将其扁平化到自己的坐标系中。四、学习新维度1. 说服的局限性你不能用 3D 论据说服 2D 的人。这就像在 Flatland 中，球体无法让正方形理解"上"和"下"的含义。2. 隐喻的断裂点然而，这正是隐喻失效的地方：你的大脑永远无法理解 4D 空间但你的大脑可以适应新的编程维度3. Lisp 学习曲线采用 Lisp/Clojure 的人通常描述相似的经历：graph LR A[不适感] --> B[一系列清晰时刻] B --> C[无法回头的顿悟] C --> D[新维度的思维方式]A. 第一阶段：不适感语法看起来"奇怪"编程范式不同需要改变思维习惯B. 第二阶段：清晰时刻逐渐理解宏的威力开始看到代码抽象的新可能性意识到之前方法的局限性C. 第三阶段：无法回头新维度的思维成为自然回到旧语言感觉受限理解了什么是真正的编程抽象4. 突破认知偏见需要的只是：好奇心理解最好的编程思想有时无法立即被欣赏识别自己的认知偏见认识到这种偏见足以打破它，这是作为程序员成长的最好方式之一。五、社区讨论与反馈1. Valerie Kim：Laws of Form 的类比A. 稀有抽象的概念文章中关于"稀有抽象"的论述让她想起 George Spencer-Brown 的《Laws of Form》（1969）。B. 形式系统的维度扩展即使是最简单的形式系统（一个符号、两个规则、约一个值），在引入"重新进入"（re-entry）的自指扩展后，也能获得全新的维度：类似于引入虚数 i 作为 x = -1/x 的解宏本质上是自指的它们提供了引入新区分（概念）的强大方式2. ao：实用主义的质疑A. 维护成本论点实际程序在其生命周期中经常被修改修改的成本和速度很重要宏像"魔法"：难以快速理解B. Blub 语言的优势可能更冗长，但显式更容易找到替代开发者更快地进行更改C. 维度的代价第三维度需要很多时间，用户并不总是愿意为此付费。这就像大多数人通勤使用道路车辆而不是飞机。D. Viaweb 的后续据互联网消息，Yahoo 在长期维护中将 Viaweb 从 Lisp 迁移到主流技术栈。3. Nathan Marz 的回应A. "魔法"与"难以理解"的误解这是混淆了糟糕的使用与思想本身：可以用宏写出糟糕的代码，就像任何语言构造一样宏确实能让你写出更糟糕的代码但这不是反对宏的论据，你可以选择不写糟糕的代码B. 宏的使用原则Lisp 中有句俗话："宏的第一条规则是不要使用宏"：绝对正确除非需要，否则不应使用当你使用它们时，你在做任何其他语言构造都无法很好完成的事情C. Specter 项目示例一个项目中的关键宏实现了：极具表现力的抽象难以匹配的性能即使是手写优化代码也难以匹敌D. 知情决策的缺失确实有少数程序员理解 Lisp/Clojure 并做出知情决定不使用它们但大多数程序员没有花时间真正理解它们的益处只有约 2.6% 的开发者使用 Lisp 或 Clojure作者怀疑甚至不到 5% 的程序员真正理解 Lisp/Clojure 足以做出知情决定E. 团队入职经验在 Red Planet Labs 的亲身经验：如果大型 Clojure 代码库编写良好，让开发者入职不是问题即使是新学习 Clojure 的开发者也能适应有调整期，但任何大型代码库都有调整期当学习新的编程范式时，调整期可能更强烈，但这是值得的Clojure 中的开发速度高于其他语言，因为能够构建更好的核心抽象宏是重要因素，Clojure 对不可变性的强调也是六、结论1. 选择权最终，生活在 Flatland 是一个选择。2. 关键时刻选择出现在你注意到自己对不熟悉思想的本能退缩时，当你感觉到"这没有意义"和"也许我还没有概念来理解它"之间的张力时。3. 定义时刻你在那一刻做的事情定义了你是留在 Flatland 还是走出它。4. 更广泛的启示宏不是唯一具有这种维度转移品质的伟大编程思想：函数式编程的概念类型系统的抽象并发模型领域特定语言（DSL）每一个伟大的编程思想都可能遇到同样的"Flatland 效应"。关键在于保持开放的心态，认识到自己的认知局限，并愿意投入时间去理解新的维度。参考资料The programmers who live in Flatland - Red Planet Labs Blog
足够详细的规范就是代码一、新闻概述1. 标题足够详细的规范就是代码2. 发布时间2026 年 3 月 17 日3. 来源Haskell for all 技术博客二、核心内容1. 事件摘要A. 主要内容本文作者以批判的视角分析了"从规范文档生成代码"（agentic coding）的理念，认为这种做法本质上是在重蹈历史覆辙——试图用自然语言替代精确的代码表达。B. 核心亮点指出 agentic coding 宣传的两个常见误解以 OpenAI 的 Symphony 项目为案例，剖析其"规范文档"的本质引用 Dijkstra 的理论，说明精确描述必然演变为代码通过实际测试证明，即使非常详细的规范也无法可靠生成工作代码2. 关键信息A. 涉及项目OpenAI Symphony：一个声称从规范文档生成的 agent 编排器B. 核心论点当规范文档足够精确到能可靠生成实现时，它必然扭曲成代码或类似代码的形式规范文档从来不是省时工具，如果追求交付速度，直接写代码更有效C. 技术术语Agentic coding：基于 AI agent 的代码生成Specification：规范文档Narrow interfaces：狭义接口（即代码）3. 背景介绍A. 相关上下文近年来，业界兴起"从自然语言规范生成代码"的理念，宣传工程师可以转变为编写规范的"管理者"，而由 AI agent 完成编码工作。B. 作者立场作者对此持反对意见，认为这是对软件工程本质的误解。三、详细报道1. 主要内容A. 两个常见误解误解 1：规范文档比对应代码更简单Agentic coding 推广者常将此理念卖给相信者，让他们幻想工程师变成管理者，只需编写规范文档后交给 agent 团队执行。这只有在规范工作比实际编码更便宜时才成立。误解 2：规范工作必须比编码工作更深思熟虑推广者用此观点说服怀疑者，声称通过规范文档过滤工作能提高质量并促进更好的工程实践。B. Symphony 案例分析OpenAI 的 Symphony 项目被宣传为从规范文档生成项目的范例。其所谓的"规范文档"（SPEC.md）实际上更像 Markdown 形式的伪代码，而非传统意义上的规范。文档包含：数据库 schema 的散文描述：列出字段名、类型、约束代码的散文描述：如并发控制、重试逻辑专门为模型代码生成添加的"速查表"部分完整的算法代码块示例：服务启动算法function start_service(): configure_logging() start_observability_outputs() start_workflow_watch(on_change=reload_and_reapply_workflow) state = { poll_interval_ms: get_config_poll_interval_ms(), max_concurrent_agents: get_config_max_concurrent_agents(), running: {}, claimed: set(), retry_attempts: {}, completed: set(), codex_totals: {input_tokens: 0, output_tokens: 0, total_tokens: 0, seconds_running: 0}, codex_rate_limits: null } ...C. Dijkstra 理论支持Edsger W. Dijkstra 在 EWD667 文章中解释了为何精确描述必然演变为代码：数学史表明，希腊数学停留在口头和图形阶段，穆斯林代数在符号化尝试后因回归修辞风格而消亡，现代文明世界只有在西欧摆脱中世纪经院主义后才能兴起。软件工程同理，试图用自然语言追求精确性是徒劳的。2. 技术细节A. 规范与代码的关系graph LR A[自然语言想法] --> B{足够精确?} B -->|否| C[模糊规范] B -->|是| D[结构化形式] D --> E[伪代码] D --> F[形式化规范] E --> G[实际代码] F --> G C --> H[生成失败/不完整]B. 实际测试结果作者尝试使用 Claude Code 按照 Symphony 规范生成 Haskell 实现，结果：存在多个 bug，需要多次提示修复即使"成功"运行，codex agent 也只是静默旋转，无法处理 Linear 工单结果存储在 Gabriella439/symphony-haskell 仓库C. 其他规范示例YAML 规范极其详细、广泛使用、包含一致性测试套件，但大多数 YAML 实现仍不完全符合规范。这印证了精确规范不等于可靠实现。3. 数据与事实A. 规范文档规模Symphony 规范文档长度：已达到所含 Elixir 实现的 1/6若继续扩展，将重演博尔赫斯"论科学的精确性"中的荒谬故事B. 代码质量问题作者指出 Symphony 规范中充斥着"AI 产物"的痕迹：代码片段标注为 text 而非代码块缺乏连贯性、目的感和对全局的理解例如第 10.5 节的"规范形"句子杂乱堆砌四、影响分析1. 行业影响A. 技术趋势Agentic coding 运动试图将工程师转变为规范编写者，但这忽视了软件工程对精确性的内在需求。B. 工程实践过度依赖规范文档可能导致：规范本身质量下降（为求快而牺牲思考）生成代码质量不稳定维护成本增加2. 用户影响A. 工程师被误导以为可以"外包"编码工作实际上需要投入与编码相当的精力编写精确规范B. 团队在追求速度的工程文化中，规范质量无法保证"垃圾输入，垃圾输出"原则依然适用3. 技术趋势A. 根本问题代码就是规范最精确的表达形式。试图绕过这一本质是徒劳的。B. 前景AI 辅助编码有价值，但不应寄望于完全替代精确思维和直接编码。五、各方反应1. 作者立场规范文档从来不是省时设备。如果优化交付时间，直接写代码比通过中间规范文档更有效。2. 业内观点A. 支持 Agentic Coding认为规范能提高质量、减少编码工作、让工程师转向更高层抽象。B. 反对意见强调精确描述必然演变为代码，规范质量决定生成质量。六、相关链接1. 相关项目OpenAI Symphony 项目Symphony Haskell 实现2. 参考资料Dijkstra EWD667YAML 规范参考资料A sufficiently detailed spec is code - Haskell for all
rathole 入门指南一、概述1. 简介A. 是什么rathole 是一个用 Rust 编写的轻量级、高性能反向代理工具，用于 NAT 穿透。它可以将位于 NAT 后面的设备上的服务暴露到互联网上。与 frp 和 ngrok 类似，rathole 通过一台具有公网 IP 的服务器来中转流量，使内网服务可被外网访问。B. 为什么学解决家庭服务器或 NAS 无法直接访问的问题相比 frp 具有更高的性能和更低的资源占用用 Rust 编写，二进制文件可小至 500KiB，适合嵌入式设备C. 学完能做什么在公网服务器上部署 rathole 服务端在内网设备上配置客户端，将本地服务暴露到公网配置加密传输，保护数据安全热重载配置，无需重启服务二、环境准备1. 系统要求服务器：具有公网 IP 的 Linux 服务器客户端：位于 NAT 后面的设备（家用电脑、NAS 等）操作系统：Linux、macOS、Windows、支持 Docker 的平台2. 安装步骤从官方发布页面下载预编译二进制文件：Linux:wget https://github.com/rathole-org/rathole/releases/download/v0.5.0/rathole-x86_64-unknown-linux-musl.tar.gz tar -xzf rathole-x86_64-unknown-linux-musl.tar.gzDocker:docker pull rapiz1/rathole从源码编译：git clone https://github.com/rathole-org/rathole.git cd rathole cargo build --release3. 验证安装./rathole --version三、核心概念1. 基本术语服务端（Server）：运行在具有公网 IP 的服务器上，监听来自客户端的连接和来自外网的访问请求客户端（Client）：运行在 NAT 后面的设备上，主动连接服务端并注册需要转发的服务Token：服务端和客户端之间用于身份验证的密钥服务（Service）：需要转发的具体应用，如 SSH、Web 服务器等2. 工作原理graph LR A[外网用户] -->|访问公网端口| B[服务端] B <-->|加密通道| C[客户端] C -->|转发请求| D[本地服务]rathole 的工作流程如下：客户端主动连接服务端，并使用 Token 进行身份验证服务端记录客户端注册的服务映射关系外网用户访问服务端的公网端口服务端通过加密通道将流量转发给客户端客户端将流量转发到本地服务四、快速上手1. 基本示例：暴露 NAS 的 SSH 服务场景你有一台位于家庭网络的 NAS，希望从互联网上通过 SSH 访问它。服务端配置创建 server.toml 文件：[server] bind_addr = "0.0.0.0:2333" [server.services.my_nas_ssh] token = "use_a_secret_that_only_you_know" bind_addr = "0.0.0.0:5202"启动服务端：./rathole --server server.toml客户端配置创建 client.toml 文件：[client] remote_addr = "myserver.com:2333" [client.services.my_nas_ssh] token = "use_a_secret_that_only_you_know" local_addr = "127.0.0.1:22"启动客户端：./rathole --client client.toml验证连接ssh -p 5202 myserver.com2. 多服务配置可以在一个配置文件中定义多个服务：客户端配置：[client] remote_addr = "myserver.com:2333" [client.services.ssh] token = "ssh_token" local_addr = "127.0.0.1:22" [client.services.web] token = "web_token" local_addr = "127.0.0.1:8080" [client.services.minecraft] token = "mc_token" local_addr = "127.0.0.1:25565" type = "tcp"服务端配置：[server] bind_addr = "0.0.0.0:2333" [server.services.ssh] token = "ssh_token" bind_addr = "0.0.0.0:5202" [server.services.web] token = "web_token" bind_addr = "0.0.0.0:5203" [server.services.minecraft] token = "mc_token" bind_addr = "0.0.0.0:5204" type = "tcp"3. UDP 服务转发rathole 支持 UDP 协议转发：[client.services.quic] type = "udp" token = "quic_token" local_addr = "127.0.0.1:4433"[server.services.quic] type = "udp" token = "quic_token" bind_addr = "0.0.0.0:4433"五、进阶配置1. 传输层加密rathole 支持三种传输模式：TCP、TLS、Noise。TLS 加密服务端配置：[server.transport] type = "tls" [server.transport.tls] pkcs12 = "identity.pfx" pkcs12_password = "your_password"客户端配置：[client.transport] type = "tls" [client.transport.tls] trusted_root = "ca.pem" hostname = "example.com"Noise Protocol 加密Noise Protocol 是现代的加密协议，无需证书：[client.transport] type = "noise" [client.transport.noise] pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s" local_private_key = "your_base64_key" remote_public_key = "server_base64_key"2. 代理连接客户端可以通过代理连接服务端：[client.transport.tcp] proxy = "socks5://user:passwd@127.0.0.1:1080"支持 HTTP 和 SOCKS5 代理。3. 日志控制通过环境变量控制日志级别：RUST_LOG=error ./rathole config.toml可用级别：error、warn、info、debug、trace默认级别为 info。4. 性能调优从 v0.4.7 开始，rathole 默认启用 TCP_NODELAY，这对延迟敏感的应用（如 RDP、Minecraft 服务器）有益，但会略微降低带宽。如果带宽更重要，可以禁用：[client.transport.tcp] nodelay = false [server.transport.tcp] nodelay = false也可以针对单个服务配置：[client.services.my_service] nodelay = false5. 心跳与重连客户端心跳配置：[client] heartbeat_timeout = 40 retry_interval = 1服务端心跳配置：[server] heartbeat_interval = 30heartbeat_interval：应用层心跳发送间隔，设为 0 禁用heartbeat_timeout：心跳超时时间，必须大于 server.heartbeat_intervalretry_interval：重连服务器的间隔六、部署实战1. systemd 服务配置创建 /etc/systemd/system/rathole-server.service：[Unit] Description=Rathole Server After=network.target [Service] Type=simple User=root ExecStart=/path/to/rathole --server /path/to/server.toml Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target创建 /etc/systemd/system/rathole-client.service：[Unit] Description=Rathole Client After=network.target [Service] Type=simple User=root ExecStart=/path/to/rathole --client /path/to/client.toml Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target启用并启动：systemctl daemon-reload systemctl enable rathole-server systemctl start rathole-server2. Docker 部署服务端：docker run -d \ --name rathole-server \ -p 2333:2333 \ -p 5202:5202 \ -v /path/to/server.toml:/app/server.toml \ rapiz1/rathole --server /app/server.toml客户端：docker run -d \ --name rathole-client \ --network host \ -v /path/to/client.toml:/app/client.toml \ rapiz1/rathole --client /app/client.toml七、性能对比1. 与 frp 的对比rathole 在性能方面相比 frp 有显著优势：吞吐量：HTTP 吞吐量更高连接数：能处理更多并发连接内存占用：显著低于 frp延迟：与 frp 相当2. 性能基准根据官方基准测试结果：HTTP 吞吐量：比 frp 提升 2-3 倍（本地回环测试）TCP 比特率：显著优于 frp内存占用：仅为 frp 的 30-50%需要注意的是，这些测试是在本地回环环境下进行的，反映的是 CPU 限制场景下的性能。实际使用中，网络往往是瓶颈，此时性能提升可能不那么明显。但 rathole 的主要优势在于低资源消耗。3. 资源占用优化rathole 的二进制文件可以编译至约 500KiB，非常适合：路由器等嵌入式设备资源受限的 VPS需要同时运行多个服务的环境八、常见问题1. 连接失败问题：客户端无法连接到服务端排查：检查服务端防火墙是否开放监听端口确认 Token 配置是否一致验证 remote_addr 地址和端口是否正确查看服务端日志是否有连接记录2. 热重载不生效问题：修改配置后服务未生效解决：目前需要重启服务应用新配置HTTP API 配置功能正在开发中3. 性能不佳问题：转发服务变慢排查：检查客户端和服务端的网络延迟确认没有启用不必要的代理尝试调整 TCP_NODELAY 设置检查本地服务本身是否有性能问题参考资料rathole GitHub 仓库rathole 官方文档
每一层审查让你慢 10 倍一、新闻概述1. 标题每一层审查让你慢 10 倍2. 发布时间2026 年 3 月 16 日3. 来源apenwarr.ca（Avery Pennarun 的个人博客）二、核心内容1. 事件摘要A. 主要内容Tailscale CEO Avery Pennarun 发表观点文章，指出软件开发中每一层审查都会让流程变慢 10 倍。文章分析了为什么 AI 代码生成无法解决这一瓶颈，并提出了通过减少审查层级和建立信任文化来提高开发效率的方案。B. 核心亮点每一层审查会让流程慢 10 倍（按实际时间计算，而非工作量）AI 的加速作用被后续审查等待时间抵消传统 QA（质量保证）模式反而会降低质量通过模块化和小组信任文化替代多层级审查2. 关键信息A. 版本号/发布内容这是一篇博客观点文章，非产品发布B. 重要数据简单 Bug 修复：30 分钟同事代码审查：300 分钟（5 小时）设计文档批准：50 小时（约 1 周）跨团队协调：500 小时（12 周，一个季度）高管决策：10 个季度（约 2.5 年）C. 涉及产品/技术软件开发流程、代码审查、质量保证、AI 编码助手、模块化架构3. 背景介绍A. 前置版本/历史作者 Avery Pennarun 之前有多篇关于系统设计的文章，本文是其系统设计系列的延续，特别关注 LLM（大型语言模型）时代的软件工程问题。B. 相关上下文随着 Claude Code 等 AI 编码工具的兴起，软件开发速度大幅提升，但传统的审查流程成为新的瓶颈。三、详细报道1. 主要内容A. 审查层级的速度衰减规律作者提出一个经验法则：每一层审查会让流程变慢 10 倍。这里的变慢指的是实际时间，而非工作量。大部分额外时间都花在等待上。具体示例：graph LR A[简单 Bug 修复 30 分钟] --> B[同事代码审查 300 分钟] B --> C[设计文档批准 3000 分钟] C --> D[跨团队协调 30000 分钟] D --> E[高管决策 75000 分钟]B. AI 无法解决等待延迟问题AI 编码工具可以将编码时间从 30 分钟缩短到 3 分钟，但：如果开发者用节省的时间自己审查 AI 生成的代码，仍然是循环等待如果直接提交未验证代码，审查者仍需 5 小时，且会因阅读低质量代码而不满如果用于大型项目，代码太长无法一次审查，需要拆分为多个小块，每个都有 5 小时审查周期C. QA（质量保证）的悖论作者引用 W. E. Deming 的质量管理哲学，指出传统 QA 模式的问题：在工厂环境中，如果每一轮 QA 能捕获 90% 的缺陷，理论上两轮就能减少 100 倍缺陷。但现实中的问题：第一道 QA 知道有第二道 QA，会降低工作标准第二道 QA 不愿过于严格，因为这会影响同事的评价生产人员知道有 QA 检查，会降低生产时的质量控制这种"多层检查"模式实际上激励了质量控制的外部化，而非内部化。D. 日本汽车制造业的启示著名的丰田生产系统完全取消了专门的 QA 阶段，而是给每个工人一个"停止生产线"按钮。当发现缺陷时，任何人都可以停止生产线进行修复。美国汽车制造商试图复制这一系统，安装了同样的按钮，但没有人敢使用，因为害怕被解雇。关键差异在于信任：员工信任上级真心希望了解每个缺陷管理层信任员工会主动发现问题执行层信任系统确实有效E. 代码审查的真正使命代码审查者的工作不是审查代码，而是弄清楚如何让这一类评论在未来不再出现。等到审查发现错误时，错误已经发生了。审查只是症状，而非根本原因。F. 模块化和小组协作的解决方案作者提出的解决方案：组件应该由能全权负责的小团队构建小团队可以建立对质量的共识和信任通过清晰定义的接口，组件可以可靠地交付给其他团队可以让多个小组竞争构建同一组件，通过进化选择质量利用 AI 快速尝试不同的模块边界代码现在很便宜，但好的想法仍然稀缺。AI 编码使我们能比以往更快地实验新想法。2. 技术细节A. AI 开发者的堕落循环作者描述了 AI 辅助开发中常见的恶性循环：graph TD A[1. 原型快速完成] --> B[2. 发现 Bug 让 AI 修复] B --> C[3. 每次修复引入新 Bug] C --> D[4. 用 AI 代理审查代码] D --> E[5. 人工在代理间传递数据] E --> F[6. 需要代理框架] F --> G[7. 让 AI 编写代理框架] G --> A许多开发者陷入这个循环，不断让 AI 生成更复杂的代理框架，而实际代码质量没有提升。B. 单体和微服务的平衡微服务之前名声不好，因为它们"太微"。在原始术语中，一个"微"服务正好适合"两个披萨团队"独立构建和运维。有了 AI，可能只需要一个披萨和一些 token。有趣的是，可以利用更快的编码来实验不同的模块边界。C. 信任的重要性质量建立在信任之上：信任系统确实有效信任员工有能力识别问题信任管理层不会惩罚发现问题的人3. 数据与事实A. 审查层级的量化影响审查层级所需时间相对倍数无审查（直接编码）30 分钟1x同事审查300 分钟10x设计文档批准3,000 分钟100x跨团队协调30,000 分钟1,000x高管决策75,000 分钟2,500xB. QA 效率的数学陷阱如果每轮 QA 捕获 90% 的缺陷：一轮：剩余 10%两轮：剩余 1%三轮：剩余 0.1%这看起来很有效，但忽视了人类激励系统的复杂性。四、影响分析1. 行业影响A. 技术趋势AI 编码工具大幅提升编码速度传统审查流程成为新的瓶颈需要重新思考质量保证机制B. 竞争格局小型创业公司因审查层级少，可能更具优势大公司因固有流程难以改革，面临挑战2. 用户影响A. 现有用户AI 编码工具的使用者发现"第一环节很快，后续环节很慢"一些人盲目相信可以"不审查也能行"，导致质量下降B. 潜在用户需要理解 AI 只是加速器，而非替代品系统设计和组织文化同样重要C. 迁移成本减少审查层级不能单独进行，必须有替代机制需要建立信任文化和质量意识3. 技术趋势A. 技术方向从"多层审查"转向"模块化 + 信任"组件质量由源头保证，而非后续检查利用 AI 快速实验和迭代B. 生态影响代码变得廉价，但好的想法仍然稀缺通过进化选择找到最优架构和边界微服务架构可能重新获得重视五、各方反应1. 官方回应本文为个人观点文章，非官方公告2. 业内评价A. 专家观点作者参考了 Deming 的质量管理哲学，指出传统 QA 模式的根本问题质量应该内置于生产过程，而非事后检查审查应该用于学习，而非惩罚B. 社区反馈许多开发者经历过 AI 编码加速后审查瓶颈的困境对如何平衡速度和质量存在分歧3. 用户反馈A. 正面评价量化分析（10x 规则）直观易懂模块化和信任文化的建议具有建设性B. 关注点完全取消审查可能导致质量灾难需要渐进式改革，而非一刀切C. 中立观察大公司固有流程难以改变实际落地需要结合具体组织环境六、相关链接1. 相关文章Highlights on quality, and Deming's work as it applies to software development（2016）Systems design 3: LLMs and semantic revolution（2025）2. 参考资料丰田生产系统W. E. Deming 质量管理哲学参考资料Every layer of review makes you 10x slower
Folio-OCR 三栏文档 OCR 工作台一、概述1. 简介A. 是什么Folio-OCR 是一款基于 GLM-OCR 和 Ollama 的三栏文档 OCR 工作台，专为书籍和文档的日常批量识别设计。它提供了一个现代化的 Web 界面，支持图片和 PDF 文件的批量光学字符识别，并具备智能版面分析、实时编辑和多种格式导出功能。B. 为什么学解决文档数字化过程中的人工识别效率问题利用本地化 OCR 方案保护数据隐私学习 FastAPI 前后端分离架构和版面分析技术了解如何将大型语言模型集成到实际应用中C. 学完能做什么搭建本地化的 OCR 识别服务批量处理 PDF 和图片文档使用版面分析技术提高识别准确率将识别结果导出为 Markdown、TXT 或 Word 文档2. 前置知识A. 必备技能基础的 Linux 命令行操作了解 Docker 基础概念B. 推荐知识Python 基础编程知识Web 开发基础概念OCR 和版面分析基础二、环境准备1. 系统要求操作系统：Linux、macOS、Windows 10/11Docker：20.10+（推荐使用 Docker 部署）Python：3.10+（本地部署）Ollama：已安装（本地部署）硬件：建议 8GB+ 内存，有 NVIDIA 显卡可获得更好性能2. 安装步骤Docker 部署（推荐）# 1. 克隆项目 git clone https://github.com/vorojar/Folio-OCR.git cd Folio-OCR # 2. 启动服务 docker compose up -d # 3. 下载 OCR 模型（约 2GB，只需执行一次） docker compose exec ollama ollama pull glm-ocr本地部署# 安装依赖 pip install -r requirements.txt # 拉取模型 ollama pull glm-ocr # 启动服务 python server.py # Windows 一键启动 start.bat3. 验证安装打开浏览器访问 http://localhost:3000，若能看到界面则表示安装成功。# 检查 Ollama 状态 curl http://localhost:11434/api/tags # 检查服务状态 curl http://localhost:3000/api/status三、核心概念1. 基本术语GLM-OCR：智谱 AI 开发的 OCR 模型，用于图像文字识别Ollama：本地化大模型运行平台，提供 GLM-OCR 的推理服务FastAPI：Python Web 框架，用于构建后端 APIPP-DocLayoutV3：PaddlePaddle 的文档版面分析模型SQLite：轻量级数据库，用于持久化存储文档和 OCR 结果版面分析：自动检测文档中的标题、正文、表格、图片等区域2. 工作原理graph LR User[用户] --> FE[前端界面] FE --> API[FastAPI 后端] API --> DB[(SQLite 数据库)] API --> Layout[版面分析模型] API --> Ollama[Ollama OCR 服务] Layout --> API Ollama --> API API --> FE API --> Storage[文件存储]系统采用三层架构设计：前端层：提供三栏式用户界面，负责文件上传、结果显示和用户交互后端层：FastAPI 提供 RESTful API，处理版面分析、OCR 调用和数据持久化服务层：Ollama 提供 OCR 推理服务，PP-DocLayoutV3 提供版面分析能力四、快速上手1. 基本使用流程graph TD A[启动服务] --> B[上传文档] B --> C[自动拆页处理] C --> D[版面分析] D --> E[OCR 识别] E --> F[编辑校对] F --> G[导出文档]2. 上传文档支持以下文件格式：图片：PNG、JPG、GIF、BMP文档：PDF支持多文件混合上传，系统会自动将所有文件合并为一个文档，并按页码顺序排列。3. OCR 识别系统提供两种 OCR 模式：单页识别：对选中的页面进行 OCR批量识别：点击 OCR All Pages 一键处理全部页面识别过程中会显示实时进度条和 ETA 时间估算，用户可以随时点击 Stop 按钮中断处理。4. 编辑与导出识别结果支持：Edit 模式：纯文本编辑，便于手动修正Preview 模式：实时渲染 HTML 表格和 Markdown 格式段落重排：自动合并因换行断开的段落导出格式：Markdown（.md）纯文本（.txt）Word 文档（.docx）五、核心功能1. OCR 引擎A. 版面分析使用 PP-DocLayoutV3 模型进行智能版面分析，自动识别以下区域：标题（title）正文（text）表格（table）图片（figure）页眉页脚、脚注、页码（自动跳过）B. 智能合并相邻文本区域会自动合并，减少 OCR 调用次数。例如 11 个文本区域可合并为 3 组，实现约 2.5 倍加速。C. LaTeX 处理自动将模型输出的 LaTeX 符号转换为 Unicode 字符：$\textcircled{1}$ → ①$\frac{1}{2}$ → ½其他常见数学符号D. 清理处理自动清理模型输出的 Markdown 围栏标记。2. 数据持久化使用 SQLite 数据库存储以下信息：文档元数据（ID、文件名、创建时间）页面信息（页码、文件名、OCR 结果、版面区域、处理时间）自动保存编辑内容（800ms 防抖）3. 界面交互A. 三栏布局左栏：页面缩略图，支持快速切换页面中栏：图片预览，显示版面分析区域框右栏：OCR 结果，支持编辑和预览B. 双向高亮点击图片中的区域框会高亮对应的文本块，点击文本块会高亮对应的图片区域。C. 全文搜索支持 Ctrl+F 全文搜索，跨页高亮显示匹配内容并统计命中次数。D. 键盘导航使用 ↑↓ 方向键快速切换页面。4. 网络容错所有网络请求都带有超时保护：状态检查：5 秒OCR 调用：180 秒其他请求：30 秒Ollama 断开后 UI 不会冻结，超时后会自动恢复可操作状态并通过 Toast 弹窗通知用户。六、技术架构1. 后端架构graph TB subgraph 后端服务 API[FastAPI Server] DB[(SQLite)] Layout[PP-DocLayoutV3] Uploads[uploads/ 目录] end subgraph 外部服务 Ollama[Ollama GLM-OCR] end API --> DB API --> Layout API --> Uploads API --> Ollama2. 版面分析流程sequenceDiagram participant U as 用户 participant API as FastAPI participant L as Layout 模型 participant O as Ollama U->>API: 上传图片 API->>L: 版面分析 L-->>API: 返回区域列表 API->>API: 合并相邻区域 loop 对每个合并区域 API->>O: OCR 调用 O-->>API: 返回识别文本 end API->>U: 返回完整结果3. 数据库设计erDiagram documents ||--o{ pages : 包含 documents { string doc_id PK string filename string created_at } pages { string doc_id FK integer num string filename text ocr_text text ocr_regions real ocr_time }4. 主要依赖依赖版本用途fastapi>=0.100.0Web 框架uvicorn>=0.23.0ASGI 服务器httpx>=0.25.0异步 HTTP 客户端pymupdf>=1.23.0PDF 处理Pillow>=10.0.0图像处理transformers>=4.40.0版面分析模型torch>=2.0.0深度学习框架python-docx>=1.1.0Word 文档生成七、部署架构1. Docker Compose 配置graph LR Host[宿主机] --> LB[Nginx/Proxy] LB --> App[App 容器] LB --> Ollama[Ollama 容器] App --> DB[(SQLite 数据卷)] App --> Files[uploads 数据卷]2. GPU 加速若有 NVIDIA 显卡，可编辑 docker-compose.yml 取消 deploy 段落的注释以启用 GPU 加速。services: ollama: deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu]3. 常用 Docker 命令docker compose down # 停止服务 docker compose up -d # 重新启动（数据不会丢失） docker compose logs -f app # 查看应用日志 docker compose logs -f ollama # 查看 Ollama 日志八、性能参考操作时间说明模型冷启动首次请求~50 秒需加载模型到内存后续单页识别~0.5 秒版面分析 + OCRPDF 渲染取决于页数2x 缩放矩阵渲染整体加速比2.5x相比逐区域 OCR九、常见问题1. 安装问题Ollama 未找到错误信息：Ollama not found解决方法：先安装 Ollama 并确保其在 PATH 中# macOS/Linux curl -fsSL https://ollama.com/install.sh | sh # Windows # 从 https://ollama.com/download 下载安装包模型下载失败解决方法：检查网络连接，手动执行模型拉取ollama pull glm-ocr2. 运行问题OCR 超时可能原因：图片过大或网络问题解决方法：系统已设置 180 秒超时，等待即可检查 Ollama 服务是否正常运行尝试重启服务版面分析失败可能原因：模型未正确加载解决方法：确保已安装 torch 和 transformers检查服务器日志查看具体错误3. 使用问题PDF 拆页不清晰说明：系统使用 2x 缩放矩阵渲染 PDF 以保证 OCR 质量解决方法：如需更高分辨率，可修改 server.py 中的 mat 参数导出的 Word 文档格式问题说明：DOCX 导出基于 python-docx，解析 OCR 结果中的表格和标题解决方法：识别时确保版面分析正确检测到表格区域十、项目结构Folio-OCR/ ├── server.py # FastAPI 后端（单文件） ├── index.html # HTML 页面 ├── script.js # 前端逻辑 ├── style.css # 样式 ├── latex_unicode.json # LaTeX → Unicode 映射表 ├── requirements.txt # Python 依赖 ├── Dockerfile # Docker 镜像构建 ├── docker-compose.yml # Docker Compose 编排 ├── start.bat # Windows 启动脚本 ├── folio_ocr.db # SQLite 数据库（运行时生成） └── uploads/ # 上传文件目录（运行时生成）十一、API 端点方法路径说明GET/前端页面GET/api/status服务状态、Ollama 连通性POST/api/load-model启动 Ollama 并预热模型POST/api/upload上传文件，返回 SSE 页面流GET/api/images/{doc_id}/{filename}获取页面图片POST/api/ocr/{doc_id}/{page_num}单页 OCRPOST/api/ocr/{doc_id}/all批量 OCR 全部页面GET/api/documents列出所有文档GET/api/documents/{doc_id}获取文档详情DELETE/api/documents/{doc_id}删除文档PUT/api/pages/{doc_id}/{page_num}/text保存编辑后的文本POST/api/export/{doc_id}导出 DOCX参考资料Folio-OCR GitHub 仓库GLM-OCR 模型Ollama 官方文档FastAPI 官方文档PP-DocLayoutV3 模型
Meta 重启 jemalloc 开发承诺投资基础设施一、新闻概述1. 标题Investing in Infrastructure: Meta's Renewed Commitment to jemalloc2. 发布时间2026 年 3 月 2 日3. 来源Engineering at Meta二、核心内容1. 事件摘要A. 主要内容Meta 宣布重新聚焦 jemalloc，这是一款高性能内存分配器。Meta 将致力于减少维护需求、现代化代码库，同时继续演进分配器以适应最新的硬件和工作负载。B. 核心亮点原始 jemalloc 开源仓库已解档Meta 继续担任项目维护者承诺与开源社区共同开发规划四大重点改进方向2. 关键信息A. 涉及产品/技术jemalloc（Jason Evans malloc）、内存分配器B. 重要数据预计阅读时间：3 分钟作者：Wenlei He, Paul Saab, Stan AngelovC. 发布背景jemalloc 长期以来一直是 Meta 软件栈中高度杠杆化的组件，与 Linux 内核和编译器一起为 Meta 提供可靠和高性能的基础设施。3. 背景介绍A. 技术定位构建软件系统就像建造摩天大楼：人们看到的产品是顶部，但防止其倒塌的是埋在泥土中的基础和隐藏的脚手架。jemalloc 正是这样的一种基础组件。B. 历史演进jemalloc 随着底层硬件和上层软件的变化不断适应，为 Meta 带来了长期收益。三、详细报道1. 主要内容A. 倾听与反思近年来， jemalloc 的开发逐渐偏离了长期指导其发展的核心工程原则。虽然某些决策带来了短期收益，但产生的技术债务最终拖慢了进度。B. 社区合作Meta 认真听取了社区的反馈。本着合作精神，团队深入反思了作为维护者的角色及其对 jemalloc 长期健康的影响。Meta 已与多位社区成员会面，包括项目创始人 Jason Evans，分享了反思内容以及如何改变方法。C. 改变开始团队已开始努力消除技术债务，并为 jemalloc 重建长期路线图。2. 技术细节A. jemalloc 在 Meta 的地位graph TB subgraph Meta 软件栈 A[jemalloc] B[Linux 内核] C[编译器] end A -->|内存分配| D[上层应用] B -->|系统调用| D C -->|代码生成| D A -.持续适应.-> E[硬件变化] A -.持续适应.-> F[软件变化] subgraph 长期收益 G[可靠性] H[高性能] end A --> G A --> HB. 工程原则在实用与原则性工程实践的谱系中， jemalloc 这样的基础软件组件需要最高的严谨性。虽然 jemalloc 提供的高杠杆作用可能带来短期收益的诱惑，但组织需要强大的自律来抵制这种诱惑并坚持核心工程原则。3. 新章节规划Meta 为 jemalloc 制定了四个重点改进领域：A. 技术债务减少专注于清理技术债务、重构和增强 jemalloc，确保其对所有用户保持高效、可靠和易用。B. 大页分配器继续改进 jemalloc 的大页分配器（HPA），以更好地利用透明大页（THP），提高 CPU 效率。C. 内存效率计划改进打包、缓存和清除机制，以优化内存效率。D. AArch64 优化确保 jemalloc 在 AArch64（ARM64）平台上具有良好的开箱即用性能。四、影响分析1. 行业影响A. 技术趋势Meta 作为技术巨头重新重视基础设施组件，表明基础软件的重要性正在被重新认识。B. 开源社区jemalloc 仓库解档并恢复活跃开发，对依赖该项目的广大开源社区是重大利好。2. 用户影响A. 现有用户更稳定的内存分配器更好的性能优化更容易的维护体验B. 潜在用户AArch64 平台用户将获得更好的原生性能对大页支持有需求的用户将受益3. 技术趋势A. 基础设施投资大型科技公司越来越重视基础软件组件的长期健康，而非短期功能迭代。B. 社区协作Meta 强调与开源社区共同开发，体现了开放协作的趋势。五、各方反应1. 官方回应Meta 表示信任是通过行动赢得的，希望通过时间证明其对 jemalloc 的重新承诺。2. 社区合作Meta 邀请社区加入这个新章节，分享反馈并帮助塑造 jemalloc 的未来。团队期待与社区合作推动 jemalloc 向前发展。3. 关键人物Jason Evans：jemalloc 项目创始人，已参与 Meta 的反思和规划讨论Wenlei He、Paul Saab、Stan Angelov：本文作者六、相关链接1. 官方公告Engineering at Meta 原文2. 技术文档jemalloc 官方仓库3. 相关资源Jason Evans 原始 jemalloc 项目参考资料Investing in Infrastructure: Meta's Renewed Commitment to jemalloc
The Linux Programming Interface 作为大学课程教材
The Linux Programming Interface 作为大学课程教材一、概述1. 书籍简介A. 书籍信息The Linux Programming Interface（简称 TLPI）是由 Michael Kerrisk 编写的 Linux/UNIX 系统编程权威指南。这本书深入介绍了 Linux 系统编程的各个方面，包括文件 I/O、进程管理、线程、网络编程等核心技术。B. 目标读者虽然作者在编写本书时并未专门针对大学市场，但如今已有许多大学教师将 TLPI 用作 Linux 或 UNIX 系统编程课程的必读教材或推荐读物。C. 教学价值TLPI 内容全面、讲解深入，适合作为大学计算机相关课程的系统编程教材，帮助学生掌握 Linux 系统级编程的核心知识和实践技能。2. 使用现状A. 大学应用多所高校已将 TLPI 纳入课程体系，用作系统编程、操作系统原理等相关课程的教材。B. 课程级别主要用于高年级本科生（大三、大四）或研究生课程。二、作者反馈征集1. 反馈目的作者 Michael Kerrisk 希望了解更多关于 TLPI 在大学课程中使用情况的细节，以便在未来版本的书籍改进中更好地满足大学教学需求。2. 征集对象正在使用或计划使用 TLPI 作为课程教材的大学教师。3. 调查问题A. 基本信息您所在的机构名称和网站地址是什么？您能否提供或指向使用 TLPI 的课程大纲？该课程属于哪个年级？（如大三、大四等）课程有多少学生？B. 使用情况TLPI 是作为必读教材，还是推荐读物？C. 改进建议在您看来，如何改进 TLPI 以更好地作为大学课程教材使用？作者欢迎详细建议，内容越多越好！三、使用建议1. 适用课程类型A. 系统编程课程涵盖 Linux 系统调用、库函数、进程管理等核心内容。B. 操作系统课程实践性强的操作系统原理课程配套教材。C. 高级编程课程需要深入理解系统级编程概念的高级编程课程。2. 教学优势A. 内容全面覆盖 Linux 系统编程的各个方面，从基础到高级。B. 实践导向包含大量代码示例和练习，便于实践教学。C. 权威性强作者 Michael Kerrisk 是 Linux man-pages 项目的维护者，内容具有高度权威性。四、联系方式如果正在使用 TLPI 作为大学课程教材，欢迎发送邮件联系作者，分享您的使用经验和改进建议。参考资料The Linux Programming Interface as a university course textThe Linux Programming Interface - Official Site
Emacs 与 Vim 在 AI 时代的未来一、新闻概述1. 标题Emacs and Vim in the Age of AI（Emacs 与 Vim 在 AI 时代）2. 发布时间2026 年 3 月 9 日3. 来源(think) 个人博客二、核心内容1. 事件摘要A. 主要内容文章作者 Bozhidar Batsov 作为拥有 20 年以上 Emacs 使用经验的核心贡献者，深入分析了在 AI 工具快速发展的背景下，Emacs 和 Vim 这两款传统编辑器面临的挑战与机遇。B. 核心亮点VS Code 和专用 AI 编辑器（Cursor、Windsurf 等）正在形成强大的吸引引力AI 改变了编程的核心技能：从编辑速度转向意图表达和代码审查AI 可能降低 Emacs Lisp 和 VimScript 的学习门槛终端原生 AI 工具与 Emacs/Vim 的工作流天然契合2. 关键信息A. 作者背景Emacs 狂热使用者超过 20 年维护了多个热门 Emacs 包，包括 copilot.el近期重新学习 Vim 和 NeovimB. 涉及技术Emacs、Vim、NeovimClaude Code、Copilot、Cursor、Windsurf 等 AI 工具gptel、ellama、aider.el、avante.nvim 等集成插件C. 文章定位这是一篇技术观点文章，而非纯粹的新闻报道，基于作者多年开源社区经验对未来趋势的思考。3. 背景介绍A. 前置背景VS Code 已经在编辑器市场占据绝对主导地位，并且正在获得所有主要 AI 工具的一流集成支持。微软有充分的资源和动力让 VS Code 成为 AI 辅助开发的最佳宿主。B. 相关上下文编程领域正在经历根本性转变，AI 越来越多地承担代码生成工作，人类的角色从编写代码转向审查、引导和精炼代码。三、详细报道1. 风险分析A. IDE 的引力井VS Code 的市场份额已经远超其他编辑器，正在获得 Copilot、Codex、Claude、Gemini 等 AI 工具的深度集成。Cursor、Windsurf 等专用 AI 编辑器也吸引了大量投资和人才。这些工具围绕 AI 工作流构建完整的体验，提供集成的上下文管理、内联差异、多文件编辑和代理循环。每个转向这些工具的开发者都是一个不再学习 Emacs 或 Vim 快捷键、不再编写 Elisp、不再为生态系统做出贡献的人。这种引力井效应是真实的。B. "强力工具"的必要性存疑Emacs 和 Vim 一直以来的卖点在于让用户更快地编写和编辑代码。快捷键、宏、可扩展性——所有这些都在于让人类更高效地完成编码的机械动作。但如果 AI 正在编写你的大部分代码，机械编辑速度还重要吗？当你正在审查和引导 AI 生成的差异而不是逐个字符地输入代码时，瓶颈从"我能编辑多快"转移到"我能多清楚地表达意图并评估输出"。这是一种完全不同的技能，而且不清楚 Emacs 或 Vim 在这方面有内在优势。学习曲线的论证也变得更难成立。当使用 Cursor 的初级开发者可以在一个下午搭建整个应用程序时，要求某人花费六个月学习 Emacs 以获得 10 倍速度是一个艰难的推销。C. 企业支持的不对称VS Code 有微软。Cursor 有风险投资。Emacs 有一小群志愿者和自由软件基金会。Vim 有 Bram 和现在的维护者社区。Neovim 有一个小型但专注的核心团队。这种情况一直存在，但 AI 放大了差距。构建深度 AI 集成需要跟上快速发展的 API、模型和范式。资金充足的团队可以专门投入工程师全职从事这项工作。志愿者驱动的项目以人们业余时间和热情的节奏前进。D. 末日场景让我们走向极端：如果我们熟悉的编程在下一个十年内完全自动化会怎样？如果 AI 代理可以接收规范并生成工作、经过测试、已部署的软件而无需人工干预，我们就根本不需要编码编辑器了。没有 Emacs，没有 Vim，没有 VS Code，没有 Cursor。整个类别变得无关紧要。作者认为这在短期内不太可能，但值得承认这是一种可能性。AI 能力的轨迹甚至让乐观主义者感到惊讶。2. 机遇分析A. AI 使配置和扩展变得微不足道几乎没有人谈论这一点：Emacs 和 Vim 一直受困于其扩展语言的晦涩。Emacs Lisp 是大多数程序员从未见过的 20 世纪 80 年代的 Lisp 方言。VimScript 就是 VimScript。即使是 Neovim 专门采用因为它更易接近的 Lua，也足够小众以至于大多数开发者没有写过一行。这几十年来一直是两个生态系统的最大瓶颈。不是编辑器本身——它们极其强大——而是自定义它们需要学习一种不熟悉的语言，而且大多数人从未超越从博客文章和 README 文件复制片段。作者在第一次学习 Emacs 和 Vim 时对 Elisp 和 VimScript 感到不知所措，他认为自己不是唯一一个。只有投入大量时间真正学会 Elisp 后，他才开始在 Emacs 中感到非常富有成效。（从未费心对 VimScript 做同样的事情，而且坦率地说，他也不太渴望掌握 Lua。）AI 一夜之间改变了这一点。你现在可以用简单的英语描述你想要什么，并获得工作的 Elisp、VimScript 或 Lua。"写一个 Emacs 函数，将当前段落重新格式化为 72 列并添加前缀"——完成。"配置 lazy.nvim 使用这些快捷键设置 LSP"——完成。扩展语言障碍，几十年来一直是采用的最大障碍，突然之间变得低得多。B. 插件开发同样受益在 Emacs 社区 20 多年后，作者经常感觉到一个相对较小的群体——也许 50 到 100 人——正在推动最有意义的进展。同样的名字出现在 MELPA、邮件列表和错误报告中。这不是对那些人的批评（他自豪地是其中之一），但这是一个结构性弱点。一个依赖如此少贡献者的社区是脆弱的。不仅仅是 Elisp 和 VimScript。Emacs 和 Vim 的 C 内部（以及 Neovim 的 C 核心）由更小的群体维护。找到既愿意又能够入侵数十年旧的 C 代码库的人确实很难，而且随着越来越少的人学习 C，这变得更难。AI 工具可以在这里以两种方式提供帮助。首先，它们降低了新贡献者的门槛——理解想要构建东西的"概念"的人现在可以获得 AI 对不熟悉语言"实现"的帮助。其次，它们帮助现有维护者更快地移动。作者个人发现 AI 非常擅长生成测试脚手架、编写文档和处理减慢包维护速度的繁琐部分。C. AI 集成已经在发生Emacs 和 Neovim 社区并没有坐以待毙。已经存在令人印象深刻的 AI 集成：Emacs：gptel——多功能 LLM 客户端，支持多个后端（Claude、GPT、Gemini、本地模型）ellama——通过 llama.cpp 和 Ollama 与 LLM 交互的 Emacs 接口aider.el——Aider 的 Emacs 集成，Aider 是流行的 AI 结对编程工具copilot.el——GitHub Copilot 集成（作者恰好是项目的当前维护者）elysium——具有内联差异应用的 AI 驱动编码助手agent-shell——通过代理客户端协议与 LLM 代理（Claude Code、Gemini CLI 等）交互的原生 Emacs 缓冲区Neovim：avante.nvim——Neovim 内部的类似 Cursor 的 AI 编码体验codecompanion.nvim——支持多个 LLM 提供商的 Copilot Chat 替代品copilot.lua——Neovim 的原生 Copilot 集成gp.nvim——具有多个提供商支持的 Neovim 中类似 ChatGPT 的会话这只是样本。构建这些集成并不像看起来那么困难——API 很简单，两个编辑器的可扩展性意味着你可以以感觉原生的方式连接 AI 工具。在 AI 帮助下，创建新的集成变得更加容易。作者不会惊讶插件开发速度显著加速。D. 终端原生 AI 工具是天然契合这里有一个值得更多注意的讽刺：许多最强大的 AI 编码工具是终端原生的。Claude Code、Aider 和各种 Copilot CLI 工具都在终端中运行。而终端里有什么？Emacs 和 Vim。在 Emacs vterm 缓冲区或 Neovim 终端分割中运行 Claude Code 是完全自然的工作流。你在一个窗格中获得 AI 代理，在另一个窗格中获得你的编辑器，所有快捷键和工具都保持完整。没有切换到不同的应用程序——都在同一个环境中。这实际上是优于基于 GUI 的 AI 编辑器的优势，其中 AI 集成与编辑器自己的界面紧密耦合。使用终端原生工具，你可以选择自己的编辑器和自己的 AI 工具，它们自然地组合在一起。E. Emacs 作为 AI 集成平台Emacs 的"编辑器即操作系统"哲学非常适合 AI 集成。它不仅仅是代码编辑器——它是邮件客户端（Gnus、mu4e）、笔记系统（Org mode）、Git 接口（Magit）、终端模拟器、文件管理器、RSS 阅读器等等。AI 可以在每一层集成。想象一个 AI 助手，可以读取你的 org-mode 议程、在 mu4e 中起草邮件回复、在 Magit 中帮助你编写提交消息以及在源缓冲区中重构代码——都在同一个环境中，共享上下文。没有其他编辑器架构使这种深度的跨域集成像 Emacs 一样自然。F. AI 帮助你自助对于 Emacs 和 Vim 用户来说，AI 最被低估的好处之一是平凡的：故障排除。两个编辑器都有臭名昭著的陡峭学习曲线和不透明的错误消息。"Wrong type argument: stringp, nil" 比任何竞争对手都让更多人远离 Emacs。AI 工具在解释神秘错误消息、诊断配置问题和建议修复方面非常出色。它们可以读取你的 init 文件并发现问题。它们可以解释一段 Elisp 做什么。它们可以帮助你理解为什么你的快捷键不起作用。这显着拉平了学习曲线——不是通过使编辑器更简单，而是通过给每个用户提供耐心、知识渊博的指南。作者在 Emacs 设置中确实不需要任何 AI 帮助来排除故障，但在 Neovim 世界中偶尔很方便，相比之下他的知识相对有限。至少有一个记录在案的案例表明，有人离开多年后回到 Emacs，特别是因为 Claude Code 使修复配置问题无痛无苦。他们因为配置负担太烦人而离开 Emacs 去 IntelliJ——一旦 AI 消除那个障碍就回来了。"快乐该死的日子我又回家了"，正如他们所说的那样。如果 AI 能带回离开的 Emacs 用户，在我看来这是一件好事。G. 即使在编程后的世界，Emacs 和 Vim 也能生存让我们重新审视末日场景。假设编程完全自动化，没有人再编写代码。Emacs 会死吗？不一定。Emacs 已经用于远不止编程。人们使用 Org mode 管理他们的整个生活——任务、笔记、日历、日志、时间跟踪，甚至学术论文。Emacs 是散文的强大写作环境，对 LaTeX、Markdown、AsciiDoc 和纯文本有出色的支持。你可以阅读电子邮件、浏览网页、管理文件，是的，玩俄罗斯方块。Vim 同样是一种文本编辑范式，也是一个程序。Vim 快捷键已经殖民了计算世界的每一个文本输入——VS Code、IntelliJ、浏览器、shell，甚至 Emacs（通过 Evil mode）。即使 Vim 程序淡出，Vim 想法是不朽的。谁知道——也许有一天会有手工制作的软件市场。"本地采购、自由放养的代码，由人类在 Emacs 中编写。"作者会买那件 T 恤。而且他相当确定那些工匠程序员不会使用 VS Code。因此即使在最极端的情况下，两个编辑器都有超越代码的生命。一个可能 diminished 的生活，但仍然是生活。3. 技术细节A. 编辑器角色转变几十年来，编辑器是你"编写"代码的地方。越来越地，它正在成为你"审查、引导和精炼"AI 编写的代码的地方。重要的技能从打字速度和编辑体操转移到规范清晰度、代码阅读和架构判断。在这个世界中，获胜的编辑器不是具有最佳代码补全的编辑器——而是给你对工作流最大控制的编辑器。而这一直是 Emacs 和 Vim 的核心价值主张。B. 社区适应的挑战问题是社区能否足够快地适应。工具在那里。架构在那里。哲学是正确的。需要的是人——更多的贡献者、更多的插件作者、更多的文档作者、对话中更多的声音。AI 可以帮助弥合差距，但它不能取代真正的社区参与。C. 伦理关注Emacs 和 Vim 社区中并非每个人都对 AI 热情，反对意见超越了单纯的技术恐惧症。存在将在很长一段时间内辩论的真正伦理关注：能源消耗。训练和运行大型语言模型需要大量的计算能力和电力。对于一直重视效率极简主义的社区——以在 40 年前的编辑器上运行为荣的 Emacs 用户、以亚秒启动时间为豪的 Vim 用户——AI 的环境成本难以忽视。版权和训练数据。LLM 在大量代码和文本语料库上训练，该训练的合法性和伦理仍然有争议。一些开发者对使用可能未经明确同意从受版权保护的代码中学习的工具感到不舒服。这种担忧深深地触动了对许可深切关心的开源社区。工作置换。如果 AI 使开发人员显着更高效，可能需要更少的开发人员。对于任何编程社区来说这是一个不舒服的想法，而对于围绕赋予人类程序员力量的身份而建立的编辑器来说，这尤其尖锐。这些担忧已经产生了具体的行动。Vim 社区最近看到了 EVi 的创建，这是 Vim 的一个分支，其整个存在理由是提供没有 AI 辅助（生成的？）代码贡献的文本编辑器。无论你是否同意前提，人们因此分叉既定编辑器这一事实告诉你一些社区成员感觉有多强烈。四、影响分析1. 行业影响A. 编辑器竞争格局VS Code 和专用 AI 编辑器在资金和市场份额上拥有巨大优势，但 Emacs 和 Vim 的核心价值——工作流控制和可定制性——在新的编程范式中仍然相关。B. 技术趋势编程的角色从"编写"转向"审查和引导"，这有利于那些在上下文管理和工具集成方面提供最大灵活性的编辑器。2. 用户影响A. 现有用户对于现有 Emacs 和 Vim 用户，AI 工具可以降低配置障碍、加速插件开发，并在需要时提供故障排除帮助。B. 新用户学习曲线曾经是 Emacs 和 Vim 的主要障碍。AI 可以显着降低这一障碍，使新用户更容易进入生态系统。C. 离开用户的回归已记录的案例表明，一些用户因配置复杂而离开 Emacs，在 AI 工具让故障排除变得容易后回归。3. 社区影响A. 贡献者门槛降低AI 可以帮助不熟悉 Elisp 或 VimScript 等语言的新贡献者更快地实现他们的想法，扩大贡献者基础。B. 伦理分歧关于 AI 的伦理担忧可能导致社区分裂，例如 EVi 分支展示了部分社区对 AI 代码的强烈反对。五、各方反应1. 作者观点作者承认担忧，但保持谨慎乐观。他一直在听人说 Emacs 即将死亡 20 年了，但它仍然存在。社区很小但充满激情，编辑器比以往任何时候都更有能力，架构确实适合 AI 时代。2. 社区行动A. AI 集成插件蓬勃发展Emacs 和 Neovim 社区已经开发了丰富的 AI 集成插件生态系统，包括 gptel、copilot.el、avante.nvim 等。B. 伦理反对Vim 社区出现了 EVi 分支，明确反对 AI 代码，显示了社区内对 AI 的分歧。3. 作者建议如果你是 Emacs 或 Vim 用户：不要恐慌，但也不要自满。学习新的 AI 工具（如果你不是从根本上反对它们）。美化你的设置并使其令人敬畏。写你的工作流。帮助新来者。确保你的编辑器在 AI 时代生存的最佳方式是让它在其中繁荣。也许未来不是它曾经的样子——但不一定是一件坏事。参考资料Emacs and Vim in the Age of AI
NVMe 驱动自动加载配置指南一、概述1. 简介A. 是什么NVMe（Non-Volatile Memory Express）是一种专为闪存存储设计的通信协议和驱动接口。Linux 内核 6.12.63 已内置完整的 NVMe 驱动模块，但默认情况下未配置为启动时自动加载。B. 为什么需要配置虽然现代 Linux 系统支持 udev 按需自动加载模块，但显式配置自动加载有以下优势：确保 NVMe 设备驱动在设备插入前已就绪，避免竞态条件系统启动时提前加载驱动，减少设备识别延迟明确的配置便于系统维护和故障排查C. 配置后效果系统启动时自动加载 nvme 及其依赖模块插入 NVMe 设备后系统可立即识别可通过 lsmod、lsblk 等命令快速验证模块状态2. 前置知识A. 必备技能基本 Linux 命令行操作了解 systemd 管理机制B. 推荐知识了解 Linux 内核模块管理熟悉 systemd 配置目录结构二、系统环境1. 系统要求Linux 内核版本：6.12.63+（已内置 NVMe 驱动）systemd 管理的系统（大多数现代 Linux 发行版）root 权限或 sudo 访问权限2. 模块依赖关系graph TD A[nvme] -->|依赖| B[nvme_core] B -->|依赖| C[nvme_auth] D[modules-load配置] -->|加载| A三、配置原理1. systemd modules-load 机制systemd-modules-load.service 是 systemd 提供的一个服务，负责在系统启动早期阶段自动加载内核模块。该服务会读取 /etc/modules-load.d/ 目录下的配置文件，并将列出的模块传递给 modprobe 命令进行加载。2. 配置目录结构graph LR A[/etc/modules-load.d] -->|优先级高| B[nvme.conf] C[/usr/lib/modules-load.d] -->|优先级低| D[系统默认配置] E[systemd-modules-load.service] -->|读取| A E -->|读取| C三、配置原理1. systemd modules-load 机制systemd-modules-load.service 是 systemd 提供的一个服务，负责在系统启动早期阶段自动加载内核模块。该服务会读取 /etc/modules-load.d/ 目录下的配置文件，并将列出的模块传递给 modprobe 命令进行加载。2. 配置目录结构/etc/modules-load.d/ 目录中的配置文件优先级高于 /usr/lib/modules-load.d/，系统会自动合并两个目录中的配置，冲突时以 /etc/ 目录为准。四、配置步骤1. 创建模块自动加载配置文件创建 /etc/modules-load.d/nvme.conf 文件，添加 nvme 模块：sudo tee /etc/modules-load.d/nvme.conf > /dev/null << 'EOF' # NVMe 驱动模块自动加载配置 # 添加 nvme 模块以确保 NVMe 设备驱动在启动时加载 # nvme 模块会自动加载依赖的 nvme-core 模块 nvme EOF2. 配置文件说明配置文件内容简洁，仅包含模块名称 nvme。模块加载时会自动处理依赖关系：模块名大小说明nvme约 57KBNVMe 主驱动模块nvme_core约 225KBNVMe 核心功能模块（自动加载）nvme_auth约 24KBNVMe 认证模块（自动加载）3. 可选扩展配置如需支持 NVMe over TCP 或 RDMA 传输，可在配置文件中添加额外模块：# 编辑配置文件添加 sudo nano /etc/modules-load.d/nvme.conf # 添加以下模块（按需选择） nvme-tcp # NVMe over TCP 传输 nvme-rdma # NVMe over RDMA 传输 nvme-fabrics # Fabric 传输核心模块五、验证配置1. 验证配置文件使用 systemd-analyze 命令验证配置是否正确：systemd-analyze cat-config modules-load.d/nvme.conf预期输出显示配置文件路径和内容。2. 重载 systemd 配置sudo systemctl daemon-reload3. 检查服务状态sudo systemctl status systemd-modules-load.service --no-pager服务状态应显示为 active（exited），表示模块加载服务已正常运行。六、测试模块加载1. 手动加载模块验证使用 modprobe 手动加载模块测试：sudo modprobe nvme2. 检查已加载模块lsmod | grep nvme预期输出：nvme 57344 0 nvme_core 225280 1 nvme nvme_auth 24576 1 nvme_core3. 模块加载流程sequenceDiagram participant A as 系统启动 participant B as systemd-modules-load participant C as modprobe participant D as 内核 A->>B: 启动阶段 B->>B: 读取 /etc/modules-load.d/*.conf B->>C: 调用 modprobe nvme C->>D: 加载 nvme 模块 D->>C: 返回依赖 nvme_core C->>D: 加载 nvme_core D->>C: 返回依赖 nvme_auth C->>D: 加载 nvme_auth D-->>B: 模块加载完成 B-->>A: 服务退出七、设备验证1. 检查 NVMe 设备系统重启后，插入 NVMe 设备，使用以下命令验证设备识别：# 查看块设备 lsblk | grep nvme # 或使用 nvme 命令（需安装 nvme-cli） sudo nvme list2. 设备识别成功标志lsblk 输出中显示 nvme 开头的设备（如 nvme0n1）dmesg 日志中显示 NVMe 设备识别信息/dev/nvme* 设备节点存在3. 安装 nvme-cli 工具（可选）如需查看详细的 NVMe 设备信息，可安装 nvme-cli：# Ubuntu/Debian sudo apt install nvme-cli # CentOS/RHEL sudo yum install nvme-cli # Arch Linux sudo pacman -S nvme-cli八、故障排查1. 模块加载失败如果模块加载失败，检查以下事项：问题排查解决方案模块不存在确认内核版本包含 NVMe 驱动配置文件语法错误检查配置文件内容格式权限不足使用 sudo 或 root 权限操作2. 设备未识别如果设备插入后未识别：# 检查内核日志 dmesg | grep -i nvme # 检查模块是否加载 lsmod | grep nvme # 检查硬件信息 sudo lspci | grep -i nvme3. 常见问题A. 重启后模块未加载确认配置文件位置正确，使用 systemd-analyze 验证配置。B. 设备识别慢可能是 udev 按需加载导致延迟，确认配置生效后应解决。C. 多个 NVMe 设备配置无需修改，驱动会自动支持多个 NVMe 设备。九、总结配置 NVMe 驱动自动加载的完整流程如下：flowchart TD A[开始] --> B[创建配置文件] B --> C[验证配置语法] C --> D{验证通过?} D -->|否| E[修正配置] E --> C D -->|是| F[重载 systemd] F --> G[手动测试加载] G --> H{加载成功?} H -->|否| I[排查故障] I --> J[检查内核日志] J --> G H -->|是| K[重启系统] K --> L[验证自动加载] L --> M{设备识别正常?} M -->|是| N[配置完成] M -->|否| O[检查硬件连接] O --> L参考资料systemd-modules-load.service(8) - Linux manual pagemodprobe(8) - Linux manual pageNVMe Technical Specifications