chenpengcong / blog

14 stars 3 forks source link

深入理解errno #5

Open chenpengcong opened 6 years ago

chenpengcong commented 6 years ago

我们知道当执行系统调用或库函数调用出现错误时,errno的值将被根据错误类型被置为相应的值(非0),且errno是线程安全的,现在我们来探究errno的本质。

首先,写个demo

#include "errno.h"
int main()
{
    errno = 1;
    return 0;
}

对文件进行预处理 $ gcc -E test2.c -o test2.i

预编译后的文件内容

# 1 "test2.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test2.c"
# 1 "/usr/include/errno.h" 1 3 4
# 28 "/usr/include/errno.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 367 "/usr/include/features.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4
# 410 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4
# 411 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
# 368 "/usr/include/features.h" 2 3 4
# 391 "/usr/include/features.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 1 3 4
# 10 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/gnu/stubs-64.h" 1 3 4
# 11 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4
# 392 "/usr/include/features.h" 2 3 4
# 29 "/usr/include/errno.h" 2 3 4

# 1 "/usr/include/x86_64-linux-gnu/bits/errno.h" 1 3 4
# 24 "/usr/include/x86_64-linux-gnu/bits/errno.h" 3 4
# 1 "/usr/include/linux/errno.h" 1 3 4
# 1 "/usr/include/x86_64-linux-gnu/asm/errno.h" 1 3 4
# 1 "/usr/include/asm-generic/errno.h" 1 3 4

# 1 "/usr/include/asm-generic/errno-base.h" 1 3 4
# 5 "/usr/include/asm-generic/errno.h" 2 3 4
# 1 "/usr/include/x86_64-linux-gnu/asm/errno.h" 2 3 4
# 1 "/usr/include/linux/errno.h" 2 3 4
# 25 "/usr/include/x86_64-linux-gnu/bits/errno.h" 2 3 4
# 50 "/usr/include/x86_64-linux-gnu/bits/errno.h" 3 4

# 50 "/usr/include/x86_64-linux-gnu/bits/errno.h" 3 4
extern int *__errno_location (void) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__const__));
# 36 "/usr/include/errno.h" 2 3 4
# 58 "/usr/include/errno.h" 3 4

# 2 "test2.c" 2

# 2 "test2.c"
int main()
{

# 4 "test2.c" 3 4
(*__errno_location ()) 
# 4 "test2.c"
      = 1;
return 0;
}

可以看到errno经过预处理变成了*__errno_location(),表示对__errno_location()函数返回值取地址内容。

那么__errno_location()函数内部是怎么实现的呢? 首先我们需要找到__errno_location函数在哪里定义

这里有个小技巧,利用gdb调试查看进入哪个文件

(gdb) s
__GI___errno_location () at errno-loc.c:26
26    errno-loc.c: No such file or directory

可以看到__errno_location定义在errno-loc.c文件,且本机没有该文件,这里我们可以到github上查看glibc源码

可以看到errno-loc.c文件内容如下

#include <errno.h>
#include <tls.h>

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

__errno_location函数返回errno变量的地址,那么此处的errno又是什么,查看该文件包含的头文件(在glibc/include目录下)

可以看到如下一行

extern __thread int errno attribute_tls_model_ie;

定义了一个整型变量errno

所以__errno_location最终就是返回了一个整型变量的地址

值得注意的是该变量使用了__thread关键字

查阅维基百科,看到C已经在与语言层面实现了thread local storage (TLS),其中GNU C使用的就是__thread关键字

Tip:除了语言层面支持,pthreads的API也支持使用线程本地存储,详见维基百科

参考: http://blog.csdn.net/js072110/article/details/44855565 https://en.wikipedia.org/wiki/Thread-local_storage