“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
GNU Emacs 架构深度解析:解锁核心设计
GNU Emacs 的架构核心是基于C语言编写的底层核心与Emacs Lisp解释器,以及由Lisp实现的上层扩展系统,这种双层设计使其成为高度可定制的文本编辑器与计算环境。
引言
GNU Emacs 是一个拥有超过四十年历史的文本编辑器,但其架构设计至今仍极具启发意义。本文基于一篇学术论文《The GNU Emacs Architecture: Unlocking the Core》,深入剖析其内部结构,从启动流程、内存管理、缓冲区机制到事件循环,揭示 Emacs 如何实现“可扩展的自文档化编辑器”这一设计哲学。
1. 整体架构:C 核心与 Lisp 外套
Emacs 的架构分为两层:
- 底层(C 层):提供最基础的功能,如文本渲染、文件 I/O、进程管理、垃圾回收器、以及一个高效的 Emacs Lisp(Elisp)解释器。这部分代码量不大,但执行效率高。
- 上层(Elisp 层):几乎所有的编辑功能、用户界面、模式(major/minor mode)、插件系统都是用 Emacs Lisp 编写的。用户和开发者主要通过 Elisp 与 Emacs 交互。
这种分层设计的核心优势在于:
- 稳定与灵活分离:C 层负责底层稳定性和性能,Elisp 层允许用户在不修改核心代码的情况下深度定制。
- 自文档化:Elisp 代码中的文档字符串(docstring)可以直接被用户查询,形成“代码即文档”的体验。
- 热加载:大部分 Elisp 代码可以在运行时重新加载,无需重启编辑器。
2. 启动流程:从 exec 到 init
Emacs 的启动过程分为三个阶段:
阶段一:C 初始化
main()函数(在emacs.c中)进行早期设置,包括信号处理、环境变量、终端类型检测。- 初始化内存分配器(
alloc.c),建立垃圾回收器的基础结构。 - 初始化符号表(
lread.c),这是 Emacs Lisp 对象系统的根基。
阶段二:加载预编译的 Lisp 库
- 调用
Fload()加载lisp/loadup.el,该文件指定了启动时必需的 Lisp 库,例如simple.el、files.el、window.el等。 - 这些库被编译成
.elc字节码文件,由 C 层的字节码解释器执行。 - 加载完成后,Emacs 进入“裸”编辑状态,此时没有用户自定义配置。
阶段三:用户初始化
- 执行用户的初始化文件(通常是
~/.emacs或~/.emacs.d/init.el)。 - 用户配置会覆盖或扩展默认行为,例如安装包、设置键绑定、加载主题。
- 完成后进入主事件循环。
值得注意的是,Emacs 的启动速度受限于 Elisp 的加载和字节码编译质量。近年来社区通过 native-comp(将 Elisp 编译为原生机器码)大幅提升了启动和运行性能。
3. 内存管理与对象系统
3.1 Lisp 对象表示
Emacs Lisp 使用一种称为“Lisp_Misc”的联合体(union)来表示不同类型的对象。每个对象都有一个类型标签(type tag),存储在对象的第一个字段中。常见的类型包括:
Lisp_Symbol:符号,是变量和函数的名字。Lisp_Cons:cons cell,用于构建链表。Lisp_String:字符串,包含长度、字符数据和哈希值缓存。Lisp_Vectorlike:包括向量、记录(record)、哈希表等。
3.2 内存分配与垃圾回收
Emacs 使用**标记-清除(mark-and-sweep)**垃圾回收器,由 C 层实现。关键数据结构:
struct gcpro:用于保护栈上的 Lisp 对象不被回收。struct gc_root:全局根对象,如Vobarray(符号表)、Vbuffer_alist(缓冲区列表)。
垃圾回收触发条件:
- 当
cons或string的分配计数达到阈值(gc-cons-threshold,默认 800KB)。 - 手动调用
garbage-collect。
回收过程:
- 从所有根对象出发,遍历所有可达对象,并标记它们。
- 遍历所有已分配对象,清除未标记的对象,并回收内存。
- 更新阈值(使用
gc-cons-percentage动态调整)。
性能考量:由于 Elisp 是函数式风格,频繁创建临时对象会导致 GC 压力。用户可以通过 benchmark 包或 profiler 分析 GC 开销。
4. 缓冲区(Buffer)与文本表示
缓冲区是 Emacs 的核心数据结构,它存储了文件内容、编辑历史、属性等。每个缓冲区由 C 层的 struct buffer 表示,包含:
own_text:指向struct buffer_text,其中存储了实际文本和间隙(gap)。undo_list:撤销历史链表。local_var_alist:缓冲区局部变量。overlays_before/after:覆盖层(overlay)链表,用于高亮等。
4.1 间隙缓冲区(Gap Buffer)
Emacs 使用间隙缓冲区作为文本存储模型。原理:
- 文本被分为两部分,中间有一个“间隙”(gap)。
- 光标位置就是间隙的位置。
- 插入操作:在间隙处写入新字符,间隙缩小。
- 删除操作:将字符移入间隙,间隙扩大。
- 当间隙耗尽时,将间隙移动到新的光标位置(通过
make_gap和move_gap)。
优势:插入和删除操作在光标附近是 O(1) 的,比链表或数组更高效。
劣势:移动光标(间隙移动)需要 O(n) 的复制,但 Emacs 通过“间隙窗口”优化了常见场景。
4.2 文本属性与覆盖层
文本属性(text properties)是附着在字符上的属性列表,例如字体、颜色、帮助文本。它们存储在缓冲区中,与文本内容分开管理。
覆盖层(overlays)则是独立于文本的区间标记,常用于高亮、折叠等,具有优先级和属性继承特性。
5. 事件循环与窗口系统
5.1 主事件循环
Emacs 的主循环位于 keyboard.c 中的 command_loop() 函数。流程:
- 调用
read_key_sequence()读取用户输入序列。 - 通过
lookup_key()在键盘映射表中查找绑定命令。 - 调用
call_interactively()执行命令,该函数会解析交互式参数。 - 执行后,调用
redisplay()更新屏幕。
5.2 显示引擎
Emacs 的显示引擎(dispnew.c 和 xdisp.c)负责将缓冲区内容渲染到窗口。关键概念:
- 窗口(window):显示缓冲区的视口。每个窗口有自己的光标、模式和滚动位置。
- 框架(frame):对应操作系统的一个窗口,可以包含多个窗口。
- 字形(glyph):显示的最小单元,每个字形对应一个字符或图像。
- 行(row):字形矩阵的一行,包含字符和属性。
显示过程:
- 从缓冲区中提取当前窗口可见的行。
- 计算每个字符的显示属性(字体、颜色)。
- 构建字形矩阵(
struct glyph_matrix)。 - 将字形矩阵发送到终端或 GUI 后端。
5.3 事件处理与钩子
Emacs 使用钩子(hooks)机制实现事件驱动的扩展。例如:
after-change-functions:在缓冲区内容变化后执行。window-scroll-functions:窗口滚动时触发。pre-command-hook/post-command-hook:每个命令前后执行。
这些钩子允许插件在几乎任意时刻插入自定义逻辑,但过多钩子会影响性能。
6. 进程与子进程管理
Emacs 支持异步进程(process.c),用于与外部程序交互。每个进程由一个 Lisp_Process 对象表示,包含:
- 输入/输出缓冲区(
process->raw_name、process->filter)。 - 状态(运行、停止、退出)。
- 过滤器函数(
process->filter):当进程有输出时自动调用。 - 发送函数(
process->sentinel):进程状态变化时调用。
Emacs 使用 select() 系统调用监控多个文件描述符,实现非阻塞 I/O。这使得 Emacs 可以同时管理多个子进程(如编译器、LSP 服务器、Shell)。
7. 字节码与原生编译
7.1 字节码解释器
Elisp 代码首先被编译为字节码(.elc 文件),由 C 层的 bytecode.c 解释执行。字节码指令集包括:
Bvarref:变量引用。Bcar/Bcdr:列表操作。Bcall/BcallN:函数调用。Bgoto/Bif:控制流。
字节码解释器使用栈式虚拟机,性能大约是原生 C 代码的 1/10 到 1/5。
7.2 原生编译(native-comp)
Emacs 28 引入的 native-comp 子系统(基于 GCC 的 libgccjit)将 Elisp 函数编译为原生机器码。原理:
- 将 Elisp 字节码转换为中间表示(SSA 形式)。
- 通过 libgccjit 生成机器码。
- 缓存编译结果(
.eln文件)。
原生编译可以将性能提升 2-5 倍,尤其对计算密集型任务(如正则表达式、语法高亮)效果显著。
8. 扩展性与限制
8.1 线程模型
Emacs 传统上是单线程的。虽然 Emacs 26 引入了“线程”(threads.el),但它们本质上是在同一个操作系统线程内协作式调度的“Lisp 线程”,无法利用多核 CPU 进行并行计算。真正的并行执行(如并发 GC)仍在开发中。
8.2 模块系统
Emacs 支持动态模块(emacs-module.h),允许用 C/C++ 编写扩展。模块可以访问 Emacs 的 Lisp 对象,但需要遵循严格的 API 限制(例如不能直接调用 Lisp 解释器)。
8.3 可移植性
Emacs 的 C 核心高度可移植,支持 POSIX、Windows、macOS,以及多种终端协议(xterm、VT100 等)。GUI 后端包括 X11、Cocoa(macOS)、Windows GDI 和 Wayland。
9. 总结与启示
GNU Emacs 的架构设计体现了“小而美”的 Unix 哲学:一个稳定的 C 核心,加上一个功能强大的 Lisp 解释器,使得用户能够以极低的成本扩展编辑器功能。这种双层架构在四十年前是超前的,至今仍是许多现代应用(如 VS Code 的 Electron + TypeScript 架构)的灵感来源。
对于开发者而言,理解 Emacs 架构有助于:
- 编写更高效的 Elisp 代码(避免 GC 陷阱)。
- 深入调试编辑器行为(如使用
M-x toggle-debug-on-error)。 - 设计类似的可扩展系统(如文本编辑器、IDE、甚至游戏引擎)。
Emacs 或许不是最快的编辑器,但它的架构赋予了它无与伦比的灵活性和生命力。正如论文标题所言,“解锁核心”意味着理解其设计背后的权衡与智慧。
原文链接:https://www.diva-portal.org/smash/get/diva2:2052282/FULLTEXT01.pdf