结论:errno 名义上是全局变量,实际实现是线程局部存储(TLS)——每个线程有独立的一份,互不覆盖。C 标准只要求 errno 是一个"可修改的左值",glibc 中它展开为 *(__errno_location()),返回当前线程的 errno 地址。

展开:为什么必须这么设计:系统调用的错误码若共享一份,线程 A 刚失败、错误码还没读就被线程 B 的调用覆盖,错误处理完全不可靠;TLS 让"调用 → 立即检查 errno"的惯用法在多线程下依然成立。实现层面:glibc 用 __thread 关键字(编译期 TLS),通过线程指针(x86-64 的 fs 段寄存器)相对寻址,访问开销接近普通全局变量。使用规则:1)只在函数返回失败指示后检查 errno,成功时它的值是未定义的旧值;2)调用其他可能设置 errno 的函数(哪怕无关)前先保存;3)信号处理函数中要 save/restore errno。易错点:自己实现库函数时随意设置 errno 污染语义;以及部分老代码 #define errno 的兼容 hack 在 TLS 实现下依然成立是因为宏展开结果仍指向线程局部实例。

if (write(fd, buf, n) < 0) {
    int saved = errno;          // 立即保存
    log_error(saved);
    errno = saved;              // 如需透传
}

追问方向:TLS 的两种模型(global-dynamic vs initial-exec)性能差异、内核如何把错误码从系统调用约定(rax 负值)翻译成 errno(glibc 封装层做的)。