Table of Contents
操作系统:浅谈 errno 的线程安全问题

我始终以为,C库中常用的 errno 仅是一个全局变量,使用了全局变量就无法保证线程安全了,因为全局变量在所有线程中都是共享的。

要实现线程安全的errno 就必须将其设置为线程私有的变量,下面就来看看GCC是如何巧妙的实现的。

正文

现在的errno定义并非一个全局变量, 而是一个宏定义, 以下是在usr/include/errno中的声明:

extern int *__errno_location (void);
# define errno (*__errno_location ())

这种方式下其实现原理大概是: __errno_location 函数返回一个int指针, 而这个函数的实现中, 返回的就恰好是实际的errno 变量(与宏同名)的地址, 所以对其解引用就相当于对其值进行操作. 所以, 这种定义规则下, 左值和右值表达式均成立.

errno = 10;    // *__errno_location () = 10
int x = errno; // x = *__errno_location ();

__errno_location 的实现就至关重要, 因为如果其返回的变量地址不包含任何技巧的话, 就和原先直接定义全局变量的方式没差了, 说到底能否实现线程安全, 还得看实际保存errno的变量是否为线程独有的. 目前还没有发掘到其精髓, 只是套壳而已.

以下给出/csu/errno-loc.c__errno_location 的实现, 与我们预期一致, 返回变量的地址. 而同名变量errno则定义在/csu/errno.c中, 决定了能够实现errno的线程安全.

int *
__errno_location (void)
{
  return &errno;
}
__thread int errno;

__thread” 是GCC提供的扩展前缀, 表示该变量将被库处理为线程私有的, 注意这一步是C库完成的, 对程序员透明. 相关的理论叫 Thread-local Storage, AArch64 架构实现的原理是利用TPIDR_EL0 寄存器, 其他架构可以参考此PDF

❓ 以上源文件中有注释为 non-threaded版本的实现, 是代表什么含义呢?

虽然我暂时没有查阅到errno的其他线程安全的实现原理, 但起码GCC下该方式这是可行的. 依靠的是"__thread“的支持, 与换成宏定义的方式无关, 不排除可能为了考虑兼容其他实现方式的可能性.

参考

c - How is thread-safe errno initialized if #define substitutes errno symbol? - Stack Overflow


创建于: 2022-12-21T19:08:22, Lastmod: 2023-09-24T18:08:59