errno는 안전합니까?


175

에서 errno.h,이 변수가 선언 된 extern int errno;내 질문은 그래서, 안전하게 확인하는 것입니다 errno일부 호출 또는 다중 스레드 코드에서 사용 perror는 () 후 값입니다. 스레드 안전 변수입니까? 그렇지 않다면 대안은 무엇입니까?

x86 아키텍처에서 gcc와 함께 Linux를 사용하고 있습니다.


5
흥미로운 정답 2 개 !! ;)
vinit dhatrak 19

Heh, 내 대답을 다시 확인하십시오. 아마도 설득력있는 데모를 추가했습니다. :-)
DigitalRoss

답변:


176

예, 스레드 안전합니다. Linux에서 전역 errno 변수는 스레드마다 다릅니다. POSIX에서는 errno가 스레드 안전 상태 여야합니다.

http://www.unix.org/whitepapers/reentrant.html을 참조 하십시오

POSIX.1에서 errno는 외부 전역 변수로 정의됩니다. 그러나 다중 스레드 환경에서는이 정의를 사용할 수 없으므로 결정적이지 않은 결과가 발생할 수 있습니다. 문제는 둘 이상의 스레드에 오류가 발생하여 모두 동일한 errno가 설정된다는 것입니다. 이러한 상황에서 스레드는 다른 스레드에 의해 이미 업데이트 된 후에 errno를 검사하게됩니다.

결과 비결 정성을 피하기 위해 POSIX.1c는 다음과 같이 스레드 당 오류 번호에 액세스 할 수있는 서비스로 errno를 재정의합니다 (ISO / IEC 9945 : 1-1996, §2.4).

일부 함수는 errno 기호를 통해 액세스 한 변수에 오류 번호를 제공 할 수 있습니다. errno 기호는 C 표준에서 지정한대로 헤더를 포함하여 정의됩니다. 프로세스의 각 스레드에 대해 errno 값은 함수 호출이나 다른 스레드에 의한 errno 지정에 영향을받지 않습니다.

또한 참조 http://linux.die.net/man/3/errno

errno는 스레드 로컬입니다. 한 스레드에서 설정해도 다른 스레드의 값에는 영향을 미치지 않습니다.


9
정말? 그들은 언제 했습니까? C 프로그래밍을 할 때 errno를 신뢰하는 것이 큰 문제였습니다.
Paul Tomblin

7
그게 내 하루에 많은 번거 로움을 덜었을 것입니다.
Paul Tomblin

4
@vinit : errno는 실제로 bits / errno.h로 정의됩니다. 포함 파일에서 주석을 읽으십시오. 비트 /errno.h에 의해 매크로로 정의되지 않는 한`errno '변수를 선언하십시오. 이것은 GNU에서 스레드 당 변수 인 경우에 해당합니다. 매크로를 사용한 재 선언은 여전히 ​​작동하지만 프로토 타입이없는 함수 선언이며 -Wstrict-prototypes 경고를 트리거 할 수 있습니다.
찰스

2
Linux 2.6을 사용하는 경우 별도의 작업을 수행 할 필요가 없습니다. 프로그래밍을 시작하십시오. :-)
Charles Salvia

3
@vinit dhatrak # if !defined _LIBC || defined _LIBC_REENTRANT일반 프로그램을 컴파일 할 때 _LIBC가 정의되어 있지 않아야합니다. 어쨌든 echo를 실행 #include <errno.h>' | gcc -E -dM -xc - 하고 -pthread의 유무에 따른 차이점을 살펴보십시오. errno는 #define errno (*__errno_location ())두 경우 모두입니다.
nos

58


Errno는 더 이상 단순한 변수가 아니며, 특히 스레드 안전을 위해 뒤에서 복잡한 것입니다.

참조 $ man 3 errno:

ERRNO(3)                   Linux Programmers 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 ())); }
$ 

12

errno.h에서이 변수는 extern int errno로 선언됩니다.

C 표준의 내용은 다음과 같습니다.

매크로 errno는 객체의 식별자 일 필요는 없습니다. 함수 호출로 인해 수정 가능한 lvalue로 확장 될 수 있습니다 (예 :) *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

10

많은 유닉스 시스템에서 컴파일 -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) */

1
나는 코드를 명시 적으로 컴파일 할 필요가 없다고 생각한다 -D_REENTRANT. 같은 질문에 대한 다른 답변에 대한 토론을 참조하십시오.
vinit dhatrak

3
@Vinit : 플랫폼에 따라 다릅니다. Linux에서는 정확할 수 있습니다. 아마 사용하여 - Solaris에서, 당신은 당신이 199506 또는 이후 버전에 _POSIX_C_SOURCE을 설정 한 경우에만 올바른 것 -D_XOPEN_SOURCE=500-D_XOPEN_SOURCE=600. 모든 사람이 POSIX 환경이 지정되었는지 확인하고 -D_REENTRANT베이컨을 절약 할 수있는 것은 아닙니다 . 그러나 원하는 동작을 수행하려면 각 플랫폼에서 여전히주의해야합니다.
Jonathan Leffler

이 기능을 지원하는 표준 (예 : C99, ANSI 등) 또는 최소한의 컴파일러 (예 : GCC 버전 이상) 및 기본값인지 여부를 나타내는 문서가 있습니까? 감사합니다.
Cloud

errno 에 대해서는 C11 표준 또는 POSIX 2008 (2013)을 참조하십시오 . C11 표준은 다음 errnointerrno 같이 말합니다. ... 유형 및 스레드 로컬 스토리지 기간 을 갖는 수정 가능한 lvalue (201)로 확장되며 그 값은 여러 라이브러리 함수에 의해 양수 오류 번호로 설정됩니다. 실제 객체에 액세스하기 위해 매크로 정의가 억제되거나 프로그램이 name으로 식별자를 정의 하면 동작이 정의되지 않습니다. [... 계속 ...]
Jonathan Leffler

[... 계속 ...] 각주 201의 말 : 매크로 errno는 객체의 식별자 일 필요는 없습니다. 함수 호출로 인해 수정 가능한 lvalue로 확장 될 수 있습니다 (예 :) *errno(). 기본 텍스트는 다음과 같습니다 . 초기 스레드의 errno 값은 프로그램 시작시 0이지만 (다른 스레드의 errno의 초기 값은 불확실한 값입니다) 라이브러리 함수에 의해 절대 0으로 설정되지 않습니다. POSIX는 스레드를 인식하지 못하는 C99 표준을 사용합니다. [... 또한 계속 ...]
Jonathan Leffler

10

이것은 <sys/errno.h>내 Mac 에서 온 것입니다.

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

그래서 errno지금하는 기능입니다 __error(). 이 기능은 스레드로부터 안전하도록 구현되었습니다.


9

yes , errno 매뉴얼 페이지 와 다른 응답 에서 설명했듯이 errno는 스레드 로컬 변수입니다.

그러나 쉽게 잊을 수있는 어리석은 세부 사항이 있습니다. 프로그램은 시스템 호출을 실행하는 모든 신호 처리기에서 errno를 저장하고 복원해야합니다. 신호를 값을 덮어 쓸 수있는 프로세스 스레드 중 하나에서 처리하기 때문입니다.

따라서 신호 핸들러는 errno를 저장하고 복원해야합니다. 다음과 같은 것 :

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}

금지됨 → 잊어 버렸습니다. 이 시스템 호출 저장 / 복원 세부 사항에 대한 참조를 제공 할 수 있습니까?
Craig McQueen

안녕 크레이그, 오타에 대한 정보를 주셔서 감사합니다, 이제 수정되었습니다. 다른 문제와 관련하여 요구 사항을 올바르게 이해하고 있는지 잘 모르겠습니다. 신호 처리기에서 errno를 수정하는 호출은 중단 된 동일한 스레드에서 사용되는 errno를 방해 할 수 있습니다 (예 : sig_alarm 내의 strtol 사용). 권리?
marcmagransdeabril

6

대답은 "의존"이라고 생각합니다. 스레드 안전 C 런타임 라이브러리는 올바른 플래그로 스레드 코드를 작성하는 경우 일반적으로 errno를 함수 호출 (매크로 함수로 확장)로 구현합니다.


@ 티모, 네, 그렇습니다. 다른 답변에 대한 토론을 참조하고 빠진 것이 있으면 알려주십시오.
vinit dhatrak 20시 02 분

3

기계에서 간단한 프로그램을 실행하여 확인할 수 있습니다.

#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 

주소는 모든 스레드마다 다릅니다.


매뉴얼 페이지 (또는 SO)에서 조회하는 것이 더 빠르지 만이를 확인하기 위해 시간을내어 주셔서 감사합니다. +1.
Bayou
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.