Zephyr errno的多线程实现方式

Creative Commons
本作品采用知识共享署名

本文分析说明Zephyr errno的多线程实现方式。

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中将直接访问写errno

1
#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成员存放errno

1
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_var

1
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的地址