第42期 | 内核出错时,为什么只回一个-1?
一、错误过门时,只带一个负数
1979 年,V7 Unix 的 errno.h 里躺着 34 个错误码¹,从 EPERM=1 到 ERANGE=34,一个不多,一个不少。那是整个 Unix 世界全部的"出错方式"——34 种。
协议简单得吝啬:系统调用出错,只返回 -1;错在哪,写进一个叫 errno 的全局变量。1972 年的 C 函数只能返回一个值,-1 加一个全局变量,是当时最便宜的约定。
1991 年的 0.01,把这套约定拆成两截,门里门外各干各的。门里:fs/exec.c 里 `return -ENOENT`、`return -EACCES`,sys_* 直接把负的错误码扔进 eax²;kernel/system_call.s 把 eax 原样送回用户态,不做任何翻译——内核只负责把 -13 递出门²。
门外:include/unistd.h 的 _syscall0 宏接住它——`if (__res >= 0) return __res; errno = -__res; return -1;`²。-13 在门外被翻译成"返回 -1,errno=13"。整个"-1 + errno"协议,被压缩成四行宏,塞进内核自带的头文件里。
两个数字值得记住。0.01 的 errno.h 里有 40 个错误码²——从 1979 到 1991,十二年,只多了 6 种出错方式。errno 还是 `extern int errno`,一个普通的 int²——1991 年没有线程,自然也没有"线程本地"这回事。
今天再看,errno(3) 手册写明:errno is thread-local³。一个 int 从全局变量长成线程本地,背后是整个并发时代。POSIX 还留了个活口:EAGAIN 和 EWOULDBLOCK 允许同值,Linux 上它俩都是 11³——"资源暂时不可用,请再试一次"。这是最早的背压信号,比 backpressure 成为流行词早了几十年。
包豪斯讲形式追随功能,errno 是这句话在系统编程里的样子:一个整数,五十年的错误,刚刚好。这也是发展性研究——没发明新原理,把 1979 年的约定工程化地搬进 386,赢在门里门外分工干净。
二、AI 的门,带回的不该只是一个数字
如果操作系统的第一批用户是智能体,errno 这套协议第一个被嫌弃。
嫌弃的理由很具体:errno 只回答"哪类错",不回答"怎么办"。ENOENT=2 告诉你文件不存在,但不告诉你它可能在哪、要不要新建、重试有没有用。人类程序员靠经验补全,智能体补不全——它会把同一个错撞三遍。
AI 原生的门,带回的该是一份"错误对象":错在哪一步、当时看到了什么证据、建议的修复动作、值不值得重试。错误从"一个整数"变成"一段诊断"。
1991 年的门防的是恶意用户,检查在进门时;智能体的麻烦不是恶意,是幻觉——它真心实意地调错了门。检查的重心要从"进门验身份"移到"出门验效果":调用发出去容易,效果落下来之前,门得回头看一眼。
还有一层:errno 从不说"谁的错"。-13 只说权限不够,不指认是谁的权限。AI 时代的门上,或许该刻"责任"二字:这一次调用的后果,记在谁账上。
三、今晚,亲手看一次 errno 翻身
三条命令,看见 1991 年的协议今天还在跑。
第一,看翻译。手边任何一台 Linux 机器上跑(普通用户身份):
python3 -c "open('/etc/shadow')"
你会看到 PermissionError: [Errno 13]。13 号,就是当年那个 EACCES,四十七年没挪过窝。
第二,看门的断面:
strace -e trace=openat cat /no/such/file 2>&1 | tail -1
输出里有 openat(...)= -1 ENOENT (No such file or directory)。门里递出来的是 -2,门外(libc)翻译成 -1 + errno。你亲眼看见那四行宏在干活。
第三,数错误码:
grep -hoE "#define[[:space:]]+E[A-Z0-9]+" /usr/include/asm-generic/errno*.h | sort -u | wc -l
今天是 133 个。从 34,到 40,到 133——Unix 的"出错方式"五十年只长了四倍。吝啬,是一种美德。
给智能体设计工具接口时,别学 errno 只回一个数字。回结构化错误:code、message、能不能重试、建议怎么修。errno 教我们的恰是反面:一个 int 撑了五十年,是因为当年付不起更丰富的错误;今天我们付得起。
这套"结构化错误对象"的思路,放在今天任何一家企业的私有智能中心里都一样:智能体调工具的那一层,门上记账,错了知道找谁。
觉 然 小 注
「以无我、无人、无众生、无寿者,修一切善法。」⁴
一个 -1,不带情绪,不指认凶手,只把事实递出来。errno 协议里没有"你错了",只有"发生了什么"。无我相,门才立得住。
引 用 与 出 处
- [1] UNIX V7. usr/include/errno.h. Unix Heritage Society (TUHS) 在线存档, 1979. https://www.tuhs.org/cgi-bin/utree.pl?file=V7/usr/include/errno.h
- [2] Linus Torvalds. Linux 0.01 内核源码(fs/exec.c、kernel/system_call.s、include/unistd.h、include/errno.h 为一手核对). kernel.org Historic 存档, 1991. https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/linux-0.01.tar.gz
- [3] Michael Kerrisk. errno(3). Linux man-pages, man7.org 在线版. https://man7.org/linux/man-pages/man3/errno.3.html
- [4] 鸠摩罗什 译. 《金刚般若波罗蜜经》. 后秦弘始年间(约402年)译本. (化用"以无我、无人、无众生、无寿者,修一切善法"一句)
· · ·