errnoはスレッドセーフですか?


175

ではerrno.h、この変数が宣言されているextern int errno;ので、私の質問は、errnoいくつかの呼び出しの後に値をチェックするか、マルチスレッドコードでperror()を使用しても安全ですか?これはスレッドセーフ変数ですか?そうでない場合、代替案は何ですか?

私はLinuxをx86アーキテクチャのgccで使用しています。


5
2つの正反対の答え、興味深い!! ;)
vinit dhatrak 2009年

ええ、私の答えを再確認してください。おそらく説得力のあるデモを追加しました。:-)
DigitalRoss

回答:


176

はい、スレッドセーフです。Linuxでは、グローバルerrno変数はスレッド固有です。POSIXでは、errnoをスレッドセーフにする必要があります。

http://www.unix.org/whitepapers/reentrant.htmlを参照してください

POSIX.1では、errnoは外部グローバル変数として定義されています。ただし、この定義はマルチスレッド環境では受け入れられません。これを使用すると、非決定的な結果が生じる可能性があるためです。問題は、2つ以上のスレッドでエラーが発生し、すべて同じerrnoが設定されることです。これらの状況では、別のスレッドによってすでに更新された後、スレッドがerrnoをチェックしてしまう可能性があります。

結果として生じる非決定性を回避するために、POSIX.1cはerrnoを次のようにスレッドごとのエラー番号にアクセスできるサービスとして再定義します(ISO / IEC 9945:1-1996、§2.4)。

一部の関数は、シンボルerrnoを介してアクセスされる変数にエラー番号を提供する場合があります。シンボルerrnoは、C標準で指定されているようにheaderを含めることで定義されます。プロセスの各スレッドについて、errnoの値は、関数呼び出しや他のスレッドによるerrnoへの割り当ての影響を受けません。

http://linux.die.net/man/3/errnoも参照してください

errnoはスレッドローカルです。1つのスレッドで設定しても、他のスレッドの値には影響しません。


9
本当に?彼らはいつそれをしましたか?私がCプログラミングをしているとき、errnoを信頼することは大きな問題でした。
ポールトンブリン

7
男、それは私の日に戻って多くの面倒を救ったでしょう。
ポールトンブリン

4
@vinit:errnoは実際にはbits / errno.hで定義されています。インクルードファイルのコメントを読みます。「bits / errno.hによってマクロとして定義されていない限り、「errno」変数を宣言します。これは、スレッドごとの変数であるGNUの場合です。このマクロを使用した再宣言は引き続き機能しますが、プロトタイプなしの関数宣言となり、-Wstrict-prototypes警告をトリガーする可能性があります。」
Charles Salvia、

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を使用した場合と使用しない場合の違いを確認します。#define errno (*__errno_location ())どちらの場合もerrnoです。
番号

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はオブジェクトの識別子である必要はありません。関数呼び出しの結果、変更可能な左辺値に拡張される場合があります(たとえば、*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

多くの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) */

1
で明示的にコードをコンパイルする必要はないと思います-D_REENTRANT。同じ質問については、他の回答に関する議論を参照してください。
vinit dhatrak 2009年

3
@Vinit:それはプラットフォームに依存します-Linuxでは正しいかもしれません。Solarisでは、_POSIX_C_SOURCEを199506以降のバージョンに設定した場合にのみ正しいと思われます。おそらく-D_XOPEN_SOURCE=500またはを使用し-D_XOPEN_SOURCE=600ます。すべての人がPOSIX環境が指定されていることを確認する必要がなく-D_REENTRANT、ベーコンを節約できるわけではありません。ただし、希望する動作が確実に得られるようにするには、プラットフォームごとに注意が必要です。
ジョナサンレフラー

この機能をサポートする標準(C99、ANSIなど)または少なくともコンパイラ(GCCバージョン以降)、およびそれがデフォルトかどうかを示すドキュメントはありますか?ありがとうございました。
クラウド

C11標準、またはPOSIX 2008(2013)でerrnoを確認できます。C11標準は次のように述べています。...そして、errnoタイプintとスレッドのローカルストレージ期間を持つ変更可能な左辺値(201)に展開され、その値はいくつかのライブラリ関数によって正のエラー番号に設定されます。実際のオブジェクトにアクセスするためにマクロ定義が抑制されている場合、またはプログラムがという名前の識別子を定義しているerrno場合、動作は未定義です。[...続き...]
ジョナサンレフラー2014

[...続き...]脚注201には、次errnoように記載されています。マクロはオブジェクトの識別子である必要はありません。関数呼び出しの結果、変更可能な左辺値に拡張される場合があります(たとえば、*errno())。 メインテキストは続きます。初期スレッドのerrnoの値はプログラムの起動時にゼロです(他のスレッドのerrnoの初期値は不定値です)が、ライブラリ関数によってゼロに設定されることはありません。 POSIXはスレッドを認識しなかったC99標準を使用します。[...また継続...]
ジョナサンレフラー2014

10

これは<sys/errno.h>私のMac からです:

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

だからerrno今は関数__error()です。この関数はスレッドセーフになるように実装されています。


9

はい。errnoのマニュアルページとその他の応答で説明されているように、errnoはスレッドローカル変数です。

しかし、簡単に忘れられるかもしれない愚かな細部があります。プログラムは、システムコールを実行するシグナルハンドラのerrnoを保存および復元する必要があります。これは、シグナルがその値を上書きする可能性のあるプロセススレッドの1つによって処理されるためです。

したがって、シグナルハンドラは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 2013

6

答えは「場合によります」と思います。スレッドセーフなCランタイムライブラリは、正しいフラグを使用してスレッド化されたコードを構築している場合、通常、errnoを関数呼び出し(マクロを関数に拡張)として実装します。


@Timo、そうだね。そうだね。他の答えの議論を参照して、何か足りないものがあったら知らせてくれ。
vinit dhatrak 2009年

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 

アドレスはすべてのスレッドで異なることに注意してください。


manページ(またはSOで)で検索する方が高速ですが、確認する時間を作っていただけたと思います。+1。
バイユー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.