errno宏定义为一个int类型的左值, 用于传达标准C函数的错误码,在多线程的情况下当前线程调用标准C函数到访问errno之间可能会切换到其它线程执行标准C函数,导致当前线程访问errno不可信。这就需要不同的线程有自己的errno。
Zephyr的errno
Zephyr的标准C库可以使用自己的minimal libc,也可以配置为使用newlib-c,二者使用的方式大致一样, 最后都是访问的z_errno()。
minimal libc
在zephyr\lib\libc\minimal\include\errno.h中定义了errno宏1
#define errno (*z_errno())
在Zephyr的minimal/socket/fs中的标准C函数内,将直接访问写errno
newlib-c
在zephyr\lib\libc\newlib\libc-hooks.c中定义了errno函数
weak int *__errno(void)
{
return z_errno();
}
在newlib-c代码中libc/include/sys/errno.h中定义了errno宏,newlib-c中将直接访问写errno1
#define errno (*__errno())
z_errno()
zephyr\include\sys\errno_private.h的代码片段可以看到CONFIG_ERRNO_IN_TLS=y
的情况下将使用__thread声明z_errno_var,z_errno返回的是该变量的地址,这是TLS变量每个线程一份。更多TLS详情可参考Zephyr TLS线程本地存储的实现一文。1
2
3
4
5
6
7
8
9
10
11
12#ifdef CONFIG_ERRNO_IN_TLS
extern __thread int z_errno_var;
static inline int *z_errno(void)
{
return &z_errno_var;
}
#else
__syscall int *z_errno(void);
#endif /* CONFIG_ERRNO_IN_TLS */
zephyr\kernel\errno.c中定义了z_impl_z_errno函数,非TLS的情况下将使用struct k_thread
中的errno_var
成员存放errno1
2
3
4
5
6
7
8
9
10
11
12
13
14#ifdef CONFIG_ERRNO
#ifdef CONFIG_ERRNO_IN_TLS
__thread int z_errno_var;
#else
int *z_impl_z_errno(void)
{
return &_current->errno_var;
}
#endif /* CONFIG_ERRNO_IN_TLS */
#endif /* CONFIG_ERRNO */
编译后脚本会产生系统调用关系z_errno会调用到z_impl_z_errno。
我们再观察一下thread.h中关于errno_var1
2
3#if defined(CONFIG_ERRNO) && !defined(CONFIG_ERRNO_IN_TLS)
int errno_var;
#endif
可以看到要支持多线程errno,需要配置CONFIG_ERRNO=y
,是否使用GCC支持的TLS由CONFIG_ERRNO_IN_TLS
决定
总结一下:
- 使用多线程errno, 配置
CONFIG_ERRNO=y
- TLS errno: 配置
CONFIG_ERRNO_IN_TLS=y
,z_errno()返回z_errno_var的地址 - 非TLS errno:
CONFIG_ERRNO_IN_TLS=n
, z_errno()返回struct k_thread
中的errno_var
的地址