<errno.h>
定义了整数变量extern int errno
。errno
是一个由POSIX
和ISO C
标准定义的符号,由系统调用和某些库函数根据事件来设置它,用以表明哪里有问题。这个值只有当调用的返回表明错误的时候有用(比如,对于大多数的系统调用是-1,对于大多数的库函数来说是-1或NULL),正确的函数也可以修改errno
。
Linux中系统调用的错误都存储于 errno
中,errno
由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误。
- 在程序启动时,
errno
设置为零,系统调用或 C 标准库中的特定函数修改它的值为一些非零值以表示某些类型的错误。有效的错误number都是非零的,系统调用和库函数不会把errno
设为0。 对于某些系统调用和库函数(比如,getpriority(2)
),没有错误的时候也会返回-1。在这种情况下,可以在调用之前先将errno
设为0,当不确定有没有错误的时候,可以通过查看errno
是不是一个非零值来确定是否发生错误。 errno
的值不会被任何程序清除,因此在使用errno
的值之前,先要通过函数(系统调用/库函数)的返回值来确定有错误发生。- ISO C标准将
errno
定义为一个可以修改的int
型左值,并且不允许准确声明,errno
可能是一个宏,也可能被定义成一个变量,这个具体要看编译器自己的实现。 - 系统调用或库函数正确执行,并不保证
errno
的值不会被改变。
✏ 1、线程安全性
早些时候,POSIX.1
曾把errno
定义成extern int errno
这种形式,但这种形式实现的errno
在多线程环境下是不安全的,errno
变量是被多个线程共享的,这样可能线程A发生某些错误改变了errno
的值,线程B虽然没有发生任何错误,但是当它检测errno
的值的时候,线程B会以为自己发生了错误。
在GCC中,errno
是thread-local
,在一个线程中设置它的值不会影响它在另一个thread中的值。
其定义在bits/errno.h
中:
# ifndef __ASSEMBLER__
/* Function to get address of global `errno' variable. */
extern int *__errno_location (void) __THROW __attribute__ ((__const__));
# if !defined _LIBC || defined _LIBC_REENTRANT
/* When using threads, errno is a per-thread value. */
# define errno (*__errno_location ())
# endif
# endif /* !__ASSEMBLER__ */
#endif /* _ERRNO_H */
在error.h
中:
/* Declare the `errno' variable, unless it's defined as a macro by
bits/errno.h. This is the case in GNU, where it is a per-thread
variable. This redeclaration using the macro still works, but it
will be a function declaration without a prototype and may trigger
a -Wstrict-prototypes warning. */
#ifndef errno
extern int errno;
#endif
可以清晰的看到,bits/errno.h
对errno
进行了重定义。从 __attribute__ ((__const__))
推测出__errno_location ()
会返回与参数无关的与线程绑定的一个特定地址,应用层直接从该地址取出errno
的。(关于__attribute__
用法可以参考Using GNU C attribute)。但是上面使用了条件编译,也就是有两种方法可以使得gcc
重定义errno
:
- 不定义宏
_LIBC
- 定义宏
_LIBC_REENTRANT
有意思的是,我们在编译时,压根不能设置_LIBC
, 在gnu/stubs-64.h
中会检测,如果有_LIBC
宏定义,直接报错终止预编译:
#ifdef _LIBC
#error Applications may not define the macro _LIBC
#endif
因此在正常情况下,我们使用gcc
编译的程序,全局变量errno
一定是线程安全的。
有一个问题,__errno_location
是怎么实现的? 在Linux 源代码中查找 __errno_location()
函数,在 errno-loc.c
中有解释:
#include <errno.h>
#include <hurd/threadvar.h>
int * __errno_location (void)
{
return (int *) __hurd_threadvar_location (_HURD_THREADVAR_ERRNO);
}
strong_alias (__errno_location, __hurd_errno_location)
libc_hidden_def (__errno_location)
显然,__errno_location()
函数的返回值就是 ****__hurd_threadvar_location(_HURD_THREADVAR_ERRNO)
函数的返回值被强转成 int *
了,那 __hurd_threadvar_location(_HURD_THREADVAR_ERRNO)
又是什么函数呢?
在头文件 <hurd/threadvar.h>
中有:
#include <machine-sp.h> /* Define __thread_stack_pointer. */
/* Return the location of the current thread's value for the
per-thread variable with index INDEX. */
extern unsigned long int *
__hurd_threadvar_location (enum __hurd_threadvar_index __index) __THROW
/* This declaration tells the compiler that the value is constant
given the same argument. We assume this won't be called twice from
the same stack frame by different threads. */
__attribute__ ((__const__));
_HURD_THREADVAR_H_EXTERN_INLINE unsigned long int *
__hurd_threadvar_location (enum __hurd_threadvar_index __index)
{
return __hurd_threadvar_location_from_sp (__index,
__thread_stack_pointer ());
}
注:在前面有定义#define _HURD_THREADVAR_H_EXTERN_INLINE extern __inline
了解到其实就是 extern __inline
,其中 __inline
表示函数是内联函数。
这是先定义了函数同时在下面直接就给出了函数代码,这个函数的又是调用了__hurd_threadvar_location_from_sp(__index, __thread_stack_pointer ())
这个函数, 同样在<hurd/threadvar.h>
中往前看,有这样的代码:
extern unsigned long int *__hurd_threadvar_location_from_sp
(enum __hurd_threadvar_index __index, void *__sp);
_HURD_THREADVAR_H_EXTERN_INLINE unsigned long int *
__hurd_threadvar_location_from_sp (