写点什么

C++ 学习 ------cerrno 头文件的作用与源码学习

作者:桑榆
  • 2022 年 9 月 03 日
    广东
  • 本文字数:1879 字

    阅读完需:约 6 分钟

引言

cerrno 是 C++对 errno.h 头文件的封装,里面实现了一个 errno 宏,返回上一次的错误码。我们来看看这个宏的具体实现以及其背后的原理。

cerrno 头文件

代码位置: http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/include/errno.h

52  int* __errno(void) __attribute_const__;53  54  /**55   * [errno(3)](http://man7.org/linux/man-pages/man3/errno.3.html) is the last error on the calling56   * thread.57   */58  #define errno (*__errno())
复制代码

可见这个宏的具体实现其实是调用了int* __errno(void)函数,我们继续追踪

//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/__errno.cpp32  #include "pthread_internal.h"33  34  int*  __errno() {35    return &__get_thread()->errno_value;36  }
复制代码

这里调用了__get_thread()函数,获取其对应的errno_value值,然后取地址返回指针,我们看看这个函数的作用:

//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/pthread_internal.h197  // Make __get_thread() inlined for performance reason. See http://b/19825434.198  static inline __always_inline pthread_internal_t* __get_thread() {199    return static_cast<pthread_internal_t*>(__get_tls()[TLS_SLOT_THREAD_ID]);200  }
复制代码

这里实际是通过__get_tls()获取之后,取 index 为 TLS_SLOT_THREAD_ID 的元素,格式转换为 pthread_internal_t*得到的这个信息,我们先来看看 pthread_internal_t 的结构定义:

//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/pthread_internal.h65  class pthread_internal_t {66   public:67    class pthread_internal_t* next;68    class pthread_internal_t* prev;69  70    pid_t tid;...160    bionic_tls* bionic_tls;161  162    int errno_value;//这个就是我们需要的errno163  };
复制代码

对应 TLS_SLOT_THREAD_ID 的定义,是一个与计算机体系结构相关的量:

72  #if defined(__arm__) || defined(__aarch64__)...86  #define TLS_SLOT_THREAD_ID        1...97  #elif defined(__i386__) || defined(__x86_64__)98  99  // x86 uses variant 2 ELF TLS layout, which places the executable's TLS segment100  // immediately before the thread pointer. New slots are allocated at positive101  // offsets from the thread pointer....106  #define TLS_SLOT_THREAD_ID        
复制代码

继续追踪__get_tls()函数

//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/bionic/ndk_cruft.cpp75  void** __get_tls() {76  #include "platform/bionic/tls.h"77    return __get_tls();78  }
//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/platform/bionic/tls.h31  #if defined(__aarch64__)32  # define __get_tls() ({ void** __val; __asm__("mrs %0, tpidr_el0" : "=r"(__val)); __val; })33  #elif defined(__arm__)34  # define __get_tls() ({ void** __val; __asm__("mrc p15, 0, %0, c13, c0, 3" : "=r"(__val)); __val; })35  #elif defined(__i386__)36  # define __get_tls() ({ void** __val; __asm__("movl %%gs:0, %0" : "=r"(__val)); __val; })37  #elif defined(__x86_64__)38  # define __get_tls() ({ void** __val; __asm__("mov %%fs:0, %0" : "=r"(__val)); __val; })39  #else40  #error unsupported architecture41  #endif
复制代码

到这里其实就已经很明显了,对应我们分析__x86_64__架构的逻辑,定义 void**指针__val,通过__asm__汇编语法,__asm__("mov %%fs:0, %0" : "=r"(__val));这里是将__val 作为返回参数,填充到 %0 的位置,即mov %%fs:0, __val,即将 fs 段寄存器偏移为 0 的地址拷贝给到__val,这里保存了 TLS(Thread Local Storage)的信息,当前线程的相关信息都保存在此处,通过后续的转换,即可以得到对应的错误信息。

为什么 fs 段寄存器会保存 TLS 的信息?有哪些段寄存器可以使用呢?------这些都是与对应的操作系统架构相关的,通过上面的宏我们可以看到至少有四种不同的获取方式,针对 aarch64/arm/i386/x86_64 都是使用不同的汇编语句返回其对应的 TLS 信息。x86_64 架构下面还有如下的段寄存器:

  • cs(Code Segment): 代码段

  • ds(Data Segment): 数据段

  • ss(Stack Segment): 栈段

  • es(Extra Segment): 扩展段

  • fs: 数据段,通常保存动态创建的数据结构

  • gs: 数据段,保存另一个程序共享出来的数据

发布于: 刚刚阅读数: 3
用户头像

桑榆

关注

北海虽赊,扶摇可接;东隅已逝,桑榆非晚! 2020.02.29 加入

Android手机厂商-相机软件系统工程师 爬山/徒步/Coding

评论

发布
暂无评论
C++学习------cerrno头文件的作用与源码学习_c++_桑榆_InfoQ写作社区