에서 errno.h
,이 변수가 선언 된 extern int errno;
내 질문은 그래서, 안전하게 확인하는 것입니다 errno
일부 호출 또는 다중 스레드 코드에서 사용 perror는 () 후 값입니다. 스레드 안전 변수입니까? 그렇지 않다면 대안은 무엇입니까?
x86 아키텍처에서 gcc와 함께 Linux를 사용하고 있습니다.
에서 errno.h
,이 변수가 선언 된 extern int errno;
내 질문은 그래서, 안전하게 확인하는 것입니다 errno
일부 호출 또는 다중 스레드 코드에서 사용 perror는 () 후 값입니다. 스레드 안전 변수입니까? 그렇지 않다면 대안은 무엇입니까?
x86 아키텍처에서 gcc와 함께 Linux를 사용하고 있습니다.
답변:
예, 스레드 안전합니다. 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는 스레드 로컬입니다. 한 스레드에서 설정해도 다른 스레드의 값에는 영향을 미치지 않습니다.
# if !defined _LIBC || defined _LIBC_REENTRANT
일반 프로그램을 컴파일 할 때 _LIBC가 정의되어 있지 않아야합니다. 어쨌든 echo를 실행 #include <errno.h>' | gcc -E -dM -xc -
하고 -pthread의 유무에 따른 차이점을 살펴보십시오. errno는 #define errno (*__errno_location ())
두 경우 모두입니다.
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 ())); }
$
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
많은 유닉스 시스템에서 컴파일 -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) */
-D_REENTRANT
. 같은 질문에 대한 다른 답변에 대한 토론을 참조하십시오.
-D_XOPEN_SOURCE=500
나 -D_XOPEN_SOURCE=600
. 모든 사람이 POSIX 환경이 지정되었는지 확인하고 -D_REENTRANT
베이컨을 절약 할 수있는 것은 아닙니다 . 그러나 원하는 동작을 수행하려면 각 플랫폼에서 여전히주의해야합니다.
errno
int
errno
같이 말합니다. ... 유형 및 스레드 로컬 스토리지 기간 을 갖는 수정 가능한 lvalue (201)로 확장되며 그 값은 여러 라이브러리 함수에 의해 양수 오류 번호로 설정됩니다. 실제 객체에 액세스하기 위해 매크로 정의가 억제되거나 프로그램이 name으로 식별자를 정의 하면 동작이 정의되지 않습니다. [... 계속 ...]
errno
는 객체의 식별자 일 필요는 없습니다. 함수 호출로 인해 수정 가능한 lvalue로 확장 될 수 있습니다 (예 :) *errno()
. 기본 텍스트는 다음과 같습니다 . 초기 스레드의 errno 값은 프로그램 시작시 0이지만 (다른 스레드의 errno의 초기 값은 불확실한 값입니다) 라이브러리 함수에 의해 절대 0으로 설정되지 않습니다. POSIX는 스레드를 인식하지 못하는 C99 표준을 사용합니다. [... 또한 계속 ...]
yes , errno 매뉴얼 페이지 와 다른 응답 에서 설명했듯이 errno는 스레드 로컬 변수입니다.
그러나 쉽게 잊을 수있는 어리석은 세부 사항이 있습니다. 프로그램은 시스템 호출을 실행하는 모든 신호 처리기에서 errno를 저장하고 복원해야합니다. 신호를 값을 덮어 쓸 수있는 프로세스 스레드 중 하나에서 처리하기 때문입니다.
따라서 신호 핸들러는 errno를 저장하고 복원해야합니다. 다음과 같은 것 :
void sig_alarm(int signo)
{
int errno_save;
errno_save = errno;
//whatever with a system call
errno = errno_save;
}
대답은 "의존"이라고 생각합니다. 스레드 안전 C 런타임 라이브러리는 올바른 플래그로 스레드 코드를 작성하는 경우 일반적으로 errno를 함수 호출 (매크로 함수로 확장)로 구현합니다.
기계에서 간단한 프로그램을 실행하여 확인할 수 있습니다.
#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
주소는 모든 스레드마다 다릅니다.