errno线程安全吗?

errno.h中,这个变量被声明为extern int errno;,所以我的问题是,在一些调用后检查errno值或在多线程代码中使用perror()是否安全。这是线程安全的变量吗?如果不是,那么有什么替代方案?

我在x86架构上使用gcc linux。

53642 次浏览

是的,它是线程安全的。在Linux上,全局errno变量是特定于线程的。POSIX要求errno是线程安全的。

看到http://www.unix.org/whitepapers/reentrant.html

在POSIX.1中,errno被定义为 外部全局变量。但这 定义是不可接受的 多线程环境,因为它 使用可能导致不确定性 结果。问题是两个或 更多的线程都可能遇到错误 导致设置相同的errno。 在这种情况下,一个线程 可能会在它之后检查errno 已经被其他人更新了吗 线程。< / p >

规避结果 不确定性,POSIX.1c重新定义 Errno作为可以访问 每个线程错误数如下所示 (ISO/IEC 9945:1-1996,§2.4):

某些函数可以在被访问的变量中提供错误号 通过符号errno。符号 Errno的定义包括 头中指定的 C标准…对于a的每一个线程 进程中,errno的值不得 受函数调用或影响

也可参见http://linux.die.net/man/3/errno

Errno是线程本地的;在一个线程中设置它不会影响它在其他线程中的值。

是的


Errno不再是一个简单的变量,它是一个复杂的幕后变量,特别是为了它是线程安全的。

看到$ man 3 errno:

ERRNO(3)                   Linux Programmer’s Manual                  ERRNO(3)


NAME
errno - number of last error


SYNOPSIS
#include <errno.h>


DESCRIPTION


...
errno is defined by the ISO C standard to be  a  modifiable  lvalue  of
type  int,  and  must not be explicitly declared; errno may be a macro.
errno is thread-local; setting it in one thread  does  not  affect  its
value in any other thread.

我们可以反复检查:

$ cat > test.c
#include <errno.h>
f() { g(errno); }
$ cc -E test.c | grep ^f
f() { g((*__errno_location ())); }
$

我认为答案是“视情况而定”。线程安全的C运行时库通常将errno实现为函数调用(宏扩展为函数),如果您正在使用正确的标志构建线程代码。

在许多Unix系统上,使用-D_REENTRANT编译可以确保errno是线程安全的。

例如:

#if defined(_REENTRANT) || _POSIX_C_SOURCE - 0 >= 199506L
extern int *___errno();
#define errno (*(___errno()))
#else
extern int errno;
/* ANSI C++ requires that errno be a macro */
#if __cplusplus >= 199711L
#define errno errno
#endif
#endif  /* defined(_REENTRANT) */

这是来自我Mac上的<sys/errno.h>:

#include <sys/cdefs.h>
__BEGIN_DECLS
extern int * __error(void);
#define errno (*__error())
__END_DECLS

所以errno现在是一个函数__error()。函数的实现是线程安全的。

在errno.h中,这个变量被声明为extern int errno;

C标准是这么说的:

errno不一定是对象的标识符。它可以扩展为由函数调用产生的可修改的左值(例如,*errno())。

通常,errno是一个宏,它调用一个函数,返回当前线程的错误编号的地址,然后解引用它。

这是我在Linux上的/usr/include/bits/errno.h:

/* 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

最后,它生成这样的代码:

> cat essai.c
#include <errno.h>


int
main(void)
{
errno = 0;


return 0;
}
> gcc -c -Wall -Wextra -pedantic essai.c
> objdump -d -M intel essai.o


essai.o:     file format elf32-i386




Disassembly of section .text:


00000000 <main>:
0: 55                    push   ebp
1: 89 e5                 mov    ebp,esp
3: 83 e4 f0              and    esp,0xfffffff0
6: e8 fc ff ff ff        call   7 <main+0x7>  ; get address of errno in EAX
b: c7 00 00 00 00 00     mov    DWORD PTR [eax],0x0  ; store 0 in errno
11: b8 00 00 00 00        mov    eax,0x0
16: 89 ec                 mov    esp,ebp
18: 5d                    pop    ebp
19: c3                    ret

是的,正如在Errno手册页和其他应答中解释的那样,errno是一个线程局部变量。

然而,有一个很容易被忘记的愚蠢的细节。程序应该在执行系统调用的任何信号处理程序上保存和恢复errno。这是因为信号将由一个进程线程处理,该进程线程可以覆盖其值。

因此,信号处理程序应该保存并恢复errno。喜欢的东西:

void sig_alarm(int signo)
{
int errno_save;


errno_save = errno;


//whatever with a system call


errno = errno_save;
}

我们可以在机器上运行一个简单的程序来检查。

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#define NTHREADS 5
void *thread_function(void *);


int
main()
{
pthread_t thread_id[NTHREADS];
int i, j;


for(i=0; i < NTHREADS; i++)
{
pthread_create( &thread_id[i], NULL, thread_function, NULL );
}


for(j=0; j < NTHREADS; j++)
{
pthread_join( thread_id[j], NULL);
}
return 0;
}


void *thread_function(void *dummyPtr)
{
printf("Thread number %ld addr(errno):%p\n", pthread_self(), &errno);
}

运行这个程序,您可以在每个线程中看到errno的不同地址。在我的机器上运行的输出看起来像:-

Thread number 140672336922368 addr(errno):0x7ff0d4ac0698
Thread number 140672345315072 addr(errno):0x7ff0d52c1698
Thread number 140672328529664 addr(errno):0x7ff0d42bf698
Thread number 140672320136960 addr(errno):0x7ff0d3abe698
Thread number 140672311744256 addr(errno):0x7ff0d32bd698

注意,所有线程的地址都是不同的。