errno是线程安全的吗? 假设有A, B两个线程都执行系统调用, 其中A返回EIO, B返回EAGAIN, 在判断返回值时是否会引起混淆?
简单的通过man errno就可以获取答案: errno is thread-local; setting it in one thread does not affect its value in any other thread.
但是errno究竟是如何实现的? 为什么errno还可能是一个宏? 带着疑问我们来研究下glibc. 官网下载到最新的是2.25版本的源码, 我们就以glibc-2.25为例一探究竟.

先看对外暴露的stdlib/errno.h:

1 #include <bits/errno.h>
2 #undef __need_Emath
3 #ifndef errno
4 extern int errno;
5 #endif 

这里的注释指明两点:
1. bits/errno.h是系统相关头文件, 在该文件中会测试__need_Emath与_ERRNO_H宏.
2. 如果bits/errno.h未定义errno为宏则声明外部变量errno.

sysdeps/unix/sysv/linux/bits/errno.h中将其定义为函数:

1 #ifdef _ERRNO_H
2 # ifndef __ASSEMBLER__
3 extern int *__errno_location (void) __THROW __attribute__ ((__const__));
4 #  if !defined _LIBC || defined _LIBC_REENTRANT
5 #  define errno (*__errno_location ())
6 #  endif
7 # endif
8 #endif 

搞清楚errno的定义后再来看看errno的修改. 由于不同架构系统调用部分相同部分不同, glibc使用脚本来动态生成系统调用函数的封装, sysdeps/unix/make-syscalls.sh即生成函数封装的脚本. 它会先去读取syscalls.list保存在calls变量中, 通过sed将注释行与空行删除, 将得到的文件按行输入(读入的前三个参数分别为file caller rest)并判断对应架构目录下是否存在$file.c $file.S $caller.c $caller.S(如果$caller不为-)文件中一个, 如果有则记录在calls变量中. 接下来根据系统调用类型及参数配置不同参数, 最后将其输出, 注意line 256开始的宏定义与包含的文件. 此处有点不明白, 输出的文件是怎么确定的?
make-syscalls.sh脚本输出的信息有何作用? 上文中line 256可以解答这个问题. 先来看下系统调用的模板(defined in sysdeps/unix/syscall-template.S):

1 #define T_PSEUDO(SYMBOL, NAME, N) PSEUDO (SYMBOL, NAME, N)
2 #define T_PSEUDO_END(SYMBOL) PSEUDO_END (SYMBOL)
3 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
4     ret
5 T_PSEUDO_END (SYSCALL_SYMBOL) 

syscall-template.S定义了一组宏用于定义系统调用的接口, 这里仅分析最常见的情况.
看下以PSEUDO开头命名的宏(defined in sysdeps/unix/sysv/linux/arm/sysdep.h):

 1 #undef PSEUDO
 2 #define PSEUDO(name, syscall_name, args)    \
 3     .text;                                  \
 4 ENTRY (name);                               \
 5     DO_CALL (syscall_name, args);           \
 6     cmn r0, $4096;
 7 #undef PSEUDO_END
 8 #define PSEUDO_END(name)                    \
 9     SYSCALL_ERROR_HANDLER;                  \
10 END (name) 

因以PSEUDO开头命名的宏较多, 此处仅分析下PSEUDO与PSEUDO_END, 可见两个宏需成对使用, 分别用于系统调用与错误返回, 继续分析DO_CALL(defined in sysdeps/unix/sysv/linux/arm/sysdep.h):

1 #undef DO_CALL
2 #define DO_CALL(syscall_name, args)         \
3     DOARGS_##args;                          \
4     ldr r7, =SYS_ify (syscall_name);        \
5     swi 0x0;                                \
6     UNDOARGS_##args 

DO_CALL宏有三条注释, 分别说明:
1. ARM EABI用户接口将系统调用号放在R7中, 而非swi中传递. 这种方式更加高效, 因为内核无需从内存中获取调用号, 这对于指令cache与数据cache分开的架构比较麻烦. 因此swi中必须传递0.
2. 内核通过R0-R6共传递7个参数, 而编译器通常只使用4个参数寄存器其余以入栈方式传参(见AAPCS), 此处需要做转换防止栈帧毁坏并保证内核正确获取参数.
3. 由于缓存系统调用号在发生系统调用时必须保存并恢复R7.
根据注释理解代码就方便多了, 先保存R7并将参数传递给对应寄存器, 将系统调用号传递给R7并调用swi 0x0, 最后恢复寄存器. DOARGS_#args根据传入args值不同展开为不同的宏(都定义在同一文件下), 此处仅分析DOARGS_7情况(UNDOARGS_#args类似, 不展开分析):

 1 #undef  DOARGS_7
 2 #define DOARGS_7                            \
 3     .fnstart;                               \
 4     mov ip, sp;                             \
 5     push {r4, r5, r6, r7};                  \
 6     cfi_adjust_cfa_offset (16);             \
 7     cfi_rel_offset (r4, 0);                 \
 8     cfi_rel_offset (r5, 4);                 \
 9     cfi_rel_offset (r6, 8);                 \
10     cfi_rel_offset (r7, 12);                \
11     .save { r4, r5, r6, r7 };               \
12     ldmia ip, {r4, r5, r6} 

先将当前栈指针保存在IP中, 将R4-R7依次入栈, 最后通过IP将已经入栈的参数传递给R4-R7. 中间以cfi开头的宏都是伪指令(defined in sysdeps/generic/sysdep.h), 用于debugger分析程序调用间寄存器状态, 不详细分析了, 具体可参见(http://dwarfstd.org/doc/DWARF5.pdf).
SYS_ify宏(defined in sysdeps/unix/sysv/linux/arm/sysdep.h)用于拼接字符串生成对应的调用号(生成的即是内核定义的系统调用号的宏):

#define SYS_ify(syscall_name) (__NR_##syscall_name)

再回头看PSEUDO_END, 其展开即调用SYSCALL_ERROR_HANDLER(sysdeps/unix/sysv/linux/arm/sysdep.h)然后声明函数结束. SYSCALL_ERROR_HANDLER根据不同预处理宏有不同定义, 此处仅分析使用libc的errno且架构不支持THUMB_INTERWORK情况:

 1 #define SYSCALL_ERROR_HANDLER               \
 2 __local_syscall_error:                      \
 3     push { lr };                            \
 4     cfi_adjust_cfa_offset (4);              \
 5     cfi_rel_offset (lr, 0);                 \
 6     push { r0 };                            \
 7     cfi_adjust_cfa_offset (4);              \
 8     bl PLTJMP(C_SYMBOL_NAME(__errno_location)); \
 9     pop { r1 };                             \
10     cfi_adjust_cfa_offset (-4);             \
11     rsb r1, r1, #0;                         \
12     str r1, [r0];                           \
13     mvn r0, #0;                             \
14     POP_PC; 

代码还是比较简单的, 首先将LR压栈, 再将R0压栈(注意此时R0为系统调用返回值). 然后获取errno的地址, PLTJMP宏表明__errno_location符号是由程序链接表指定而非静态生成的. 由于函数返回值保存在R0, 出栈时使用R1保存系统调用返回值, 又系统调用返回值为复数, 此处再做一次减法取正, 再将其保存在R0给定的地址上(errno). 最后将R0设置为-1, 将LR出栈并跳转.

待续......

转载于:https://www.cnblogs.com/Five100Miles/p/8459193.html

errno是否是thread safe的相关推荐

  1. mysql thread safe_Windows环境下完全手工配置Apache、MySQL和PHP(Thread Safe)

    happydagui:现在LAMP(Linux.Apache.MySQL.PHP/Perl/Python的简称)已经很流行了.在Windows下也有类似的,比如 WAMP(Apache, MySQL, ...

  2. PHP5 VC9、VC6、Thread Safe、Non Thread Safe各个版本区别

    2019独角兽企业重金招聘Python工程师标准>>> 一.如何选择PHP5.3的VC9版本和VC6版本 网站推广 VC6版本是使用Visual Studio 6编译器编译的,如果你 ...

  3. PHP版本VC6与VC9/VC11/VC14、Thread Safe与None-Thread Safe等的区别

    原文:PHP版本VC6与VC9/VC11/VC14.Thread Safe与None-Thread Safe等的区别 最近正好在弄一个PHP的程序,在这之前一直没有怎么以接触,发现对PHP版本知识了解 ...

  4. php5.6non thread safe 区别,PHP版本Non Thread Safe和Thread Safe如何选择?区别是什么?

    PHP版本分为Non Thread Safe和Thread Safe,Non Thread Safe是指非线程安全,Thread Safe是指线程安全,区别是什么?如何选择? Non Thread S ...

  5. PHP关于VC11,VC9,VC6以及Thread Safe和Non Thread Safe版本选择

    2019独角兽企业重金招聘Python工程师标准>>> 这里是我在搭建php环境时收集的资料供大家参考: 现在PHP官网上下载PHP安装包都有VC11或VC9的字样,这是什么含义,我 ...

  6. PHP 5.3 下载时 VC9、VC6、Thread Safe、Non Thread Safe 是什么意思?

    我最近在 PHP 官网上看到又有新版的 PHP 下载了,于是上去找找 For Windows 的版本,可是一看确傻眼了,一共给了四个版本,VC9 x86 Non Thread Safe.VC9 x86 ...

  7. PHP版本选择讲解:VC6与VC9,Thread Safe与None-Thread Safe等的选择

    October 28, 2010 | 作者:白菜 最近发现很多PHP程序员对PHP版本知识了解不是很清楚,自己也看了不少类似的文章,还是感觉不够明确和全面,网上的结论又都是模棱两可,在此,给出最完整甚 ...

  8. non thread safe php vc11,PHP 中什么线程安全(TS)和非线程安全(NTS)

    显示行号 | 选择喜欢的代码风格 默认 GitHub Dune LakeSide Plateau Vibrant Blue Eighties Tranquil Windows 版的 PHP 从版本 P ...

  9. 关于VC9和VC6以及Thread Safe和Non Thread Safe版本选择的问题

    一.如何选择PHP5.3的VC9版本和VC6版本 VC6版本是使用Visual Studio 6编译器编译的,如果你的PHP是用Apache来架设的,那你就选择VC6版本. VC9版本是使用Visua ...

  10. PHP版本VC6与VC9、Thread Safe与None-Thread Safe等的区别

    转载:http://www.cnblogs.com/whoknows/articles/2425841.html 最近发现很多PHP程序员对PHP版本知识了解不是很清楚,自己也看了不少类似的文章,还是 ...

最新文章

  1. 手机安全卫士——软件管理-用户程序和系统程序
  2. 锁优化:逃逸分析、自旋锁、锁消除、锁粗化、轻量级锁和偏向锁
  3. 阿里云专家手把手教你重塑 IT 架构!
  4. java map存放班级和姓名_Java 创建一个HashMap对象,并在其中添加学生的姓名和成绩,键为学生姓名,值为学生成绩,使用增强for循环遍历该HashMap,并输出学生成绩。...
  5. 工作108:精准的v-for和if
  6. 鸿蒙os来了,华为操作系统“鸿蒙OS”来了!
  7. Python之进程+线程+协程(并发与并行、GIL锁、同步锁、死锁、递归锁)
  8. Linux C编程Makefile编写初步-转
  9. 如何修改apache 2最大连接数
  10. 【电子商务安全与支付实验】数字证书的申请及使用
  11. 软件评测师-13.软件测试技术与应用
  12. 正则表达式提取HTML中IMG标签的SRC地址
  13. HDLBits在线练习题之Exams/ece241 2014 q7b
  14. 在Windows中查看文件的MD5值
  15. 一鸣心所向:想成功?变身蝙蝠侠吧
  16. PageOffice 在线打开 word 文件并添加水印
  17. python批量检索文献pubmed_PubMed快速检索文献,学学这些技巧!
  18. 数据结构算法 - ConcurrentHashMap 源码解析
  19. yii 添加,操作成功,但数据并没有插入到数据库中
  20. HTML学习第十二章------布局和排版

热门文章

  1. skimage读取不到图片会报错 cv2读取不到图片返回None
  2. pandas--groupby相关操作
  3. matlab拟合出余弦曲线,如何用matlab做正弦曲线拟合?
  4. 用Codeblocks 10.05调试程序
  5. mysql contain和like_mysql不带%的like 与等号之间的区别
  6. vs2013 mfc连接MySQL数据库
  7. 非常实用的aix 6.1系统安装的教程
  8. hibernate 入门案例
  9. Swift - iCloud存储介绍
  10. 表达式语言输出map