Ist errno threadsicher?

175

In errno.hwird diese Variable so deklariert, extern int errno;dass meine Frage lautet: errnoIst es sicher, den Wert nach einigen Aufrufen zu überprüfen oder perror () in Multithread-Code zu verwenden? Ist das eine thread-sichere Variable? Wenn nicht, was ist dann die Alternative?

Ich verwende Linux mit gcc auf x86-Architektur.

vinit dhatrak
quelle
5
2 genau entgegengesetzte Antworten, interessant !! ;)
vinit dhatrak
Heh, überprüfe meine Antwort noch einmal. Ich habe eine Demonstration hinzugefügt, die wahrscheinlich überzeugen wird. :-)
DigitalRoss

Antworten:

176

Ja, es ist threadsicher. Unter Linux ist die globale Variable errno threadspezifisch. POSIX erfordert, dass errno threadsicher ist.

Siehe http://www.unix.org/whitepapers/reentrant.html

In POSIX.1 wird errno als externe globale Variable definiert. Diese Definition ist jedoch in einer Multithread-Umgebung nicht akzeptabel, da ihre Verwendung zu nicht deterministischen Ergebnissen führen kann. Das Problem ist, dass bei zwei oder mehr Threads Fehler auftreten können, die alle dazu führen, dass derselbe Fehler gesetzt wird. Unter diesen Umständen überprüft ein Thread möglicherweise errno, nachdem er bereits von einem anderen Thread aktualisiert wurde.

Um den daraus resultierenden Nichtdeterminismus zu umgehen, definiert POSIX.1c errno als einen Dienst neu, der wie folgt auf die Fehlernummer pro Thread zugreifen kann (ISO / IEC 9945: 1-1996, §2.4):

Einige Funktionen können die Fehlernummer in einer Variablen bereitstellen, auf die über das Symbol errno zugegriffen wird. Das Symbol errno wird definiert, indem der Header gemäß C-Standard eingeschlossen wird ... Für jeden Thread eines Prozesses wird der Wert von errno nicht durch Funktionsaufrufe oder Zuweisungen an errno durch andere Threads beeinflusst.

Siehe auch http://linux.die.net/man/3/errno

errno ist threadlokal; Das Festlegen in einem Thread wirkt sich nicht auf den Wert in einem anderen Thread aus.

Charles Salvia
quelle
9
"Ja wirklich?" Wann haben sie das gemacht? Als ich C-Programmierung machte, war es ein großes Problem, Errno zu vertrauen.
Paul Tomblin
7
Mann, das hätte mir damals viel Ärger erspart.
Paul Tomblin
4
@vinit: errno ist tatsächlich in bits / errno.h definiert. Lesen Sie die Kommentare in der Include-Datei. Es heißt: "Deklarieren Sie die Variable" errno ", es sei denn, sie wird durch bits / errno.h als Makro definiert. Dies ist in GNU der Fall, wo es sich um eine Pro-Thread-Variable handelt. Diese Neudeklaration unter Verwendung des Makros funktioniert weiterhin, funktioniert jedoch wird eine Funktionsdeklaration ohne Prototyp sein und kann eine Warnung "Strikte Prototypen" auslösen. "
Charles Salvia
2
Wenn Sie Linux 2.6 verwenden, müssen Sie nichts tun. Starten Sie einfach die Programmierung. :-)
Charles Salvia
3
@vinit dhatrak _LIBC sollte # if !defined _LIBC || defined _LIBC_REENTRANTbeim Kompilieren normaler Programme nicht definiert sein. Wie auch immer, führen Sie echo aus #include <errno.h>' | gcc -E -dM -xc - und sehen Sie sich den Unterschied mit und ohne -pthread an. errno ist #define errno (*__errno_location ())in beiden Fällen.
Nr.
58

Ja


Errno ist keine einfache Variable mehr, es ist etwas Komplexes hinter den Kulissen, speziell damit es threadsicher ist.

Siehe $ 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.

Wir können überprüfen:

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

In errno.h wird diese Variable als extern int errno deklariert.

Folgendes sagt der C-Standard:

Das Makro errnomuss nicht die Kennung eines Objekts sein. Es kann sich zu einem veränderbaren Wert erweitern, der sich aus einem Funktionsaufruf ergibt (z. B. *errno()).

Im Allgemeinen errnohandelt es sich um ein Makro, das eine Funktion aufruft, die die Adresse der Fehlernummer für den aktuellen Thread zurückgibt und diese dann dereferenziert.

Folgendes habe ich unter Linux in /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

Am Ende generiert es diese Art von Code:

> 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
Bastien Léonard
quelle
10

Auf vielen Unix-Systemen stellt das Kompilieren mit -D_REENTRANTsicher, dass errnoes threadsicher ist.

Beispielsweise:

#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) */
Jonathan Leffler
quelle
1
Ich denke, Sie müssen Code nicht explizit mit kompilieren -D_REENTRANT. Bitte beziehen Sie sich auf die Diskussion über andere Antworten für dieselbe Frage.
Vinit Dhatrak
3
@Vinit: Das hängt von Ihrer Plattform ab. Unter Linux sind Sie möglicherweise richtig. Unter Solaris wären Sie nur dann korrekt, wenn Sie _POSIX_C_SOURCE auf 199506 oder eine spätere Version gesetzt haben - wahrscheinlich mit -D_XOPEN_SOURCE=500oder -D_XOPEN_SOURCE=600. Nicht jeder kümmert sich darum, dass die POSIX-Umgebung angegeben wird - und -D_REENTRANTkann dann Ihren Speck retten. Sie müssen jedoch auf jeder Plattform vorsichtig sein, um sicherzustellen, dass Sie das gewünschte Verhalten erhalten.
Jonathan Leffler
Gibt es eine Dokumentation, aus der hervorgeht, welcher Standard (z. B. C99, ANSI usw.) oder zumindest welche Compiler (z. B. GCC-Version und höher) diese Funktion unterstützen und ob es sich um eine Standardeinstellung handelt oder nicht? Danke dir.
Wolke
Sie können sich den C11-Standard oder POSIX 2008 (2013) für errno ansehen . Der C11-Standard lautet: ... und errnoerweitert sich zu einem modifizierbaren Wert (201) mit intlokaler Speicherdauer für Typ und Thread, dessen Wert von mehreren Bibliotheksfunktionen auf eine positive Fehlernummer gesetzt wird. Wenn eine Makrodefinition unterdrückt wird, um auf ein tatsächliches Objekt zuzugreifen, oder ein Programm einen Bezeichner mit dem Namen definiert errno, ist das Verhalten undefiniert. [... Fortsetzung ...]
Jonathan Leffler
[... Fortsetzung ...] In Fußnote 201 heißt es: Das Makro errnomuss nicht die Kennung eines Objekts sein. Es kann sich zu einem veränderbaren Wert erweitern, der sich aus einem Funktionsaufruf ergibt (z. B. *errno()). Der Haupttext wird fortgesetzt: Der Wert von errno im Anfangsthread ist beim Programmstart Null (der Anfangswert von errno in anderen Threads ist ein unbestimmter Wert), wird jedoch von keiner Bibliotheksfunktion auf Null gesetzt. POSIX verwendet den C99-Standard, der keine Threads erkannte. [... auch weiter ...]
Jonathan Leffler
10

Dies ist von <sys/errno.h>auf meinem Mac:

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

So errnoist jetzt eine Funktion __error(). Die Funktion ist threadsicher implementiert.

vy32
quelle
9

Ja , wie in der Manpage errno und den anderen Antworten erläutert, ist errno eine lokale Thread-Variable.

Es gibt jedoch ein dummes Detail, das leicht vergessen werden könnte. Programme sollten die Fehlernummer auf jedem Signalhandler speichern und wiederherstellen, der einen Systemaufruf ausführt. Dies liegt daran, dass das Signal von einem der Prozessthreads verarbeitet wird, der seinen Wert überschreiben könnte.

Daher sollten die Signalhandler errno speichern und wiederherstellen. Etwas wie:

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}
marcmagransdeabril
quelle
verboten → vergessen nehme ich an. Können Sie eine Referenz für diesen Systemaufruf zum Speichern / Wiederherstellen von Details angeben?
Craig McQueen
Hallo Craig, danke für die Infos zum Tippfehler, jetzt ist korrigiert. In Bezug auf das andere Problem bin ich mir nicht sicher, ob ich richtig verstehe, wonach Sie fragen. Jeder Aufruf, der errno im Signalhandler ändert, kann die errno stören, die von demselben Thread verwendet wird, der unterbrochen wurde (z. B. mit strtol in sig_alarm). richtig?
marcmagransdeabril
6

Ich denke die Antwort ist "es kommt darauf an". Thread-sichere C-Laufzeitbibliotheken implementieren normalerweise errno als Funktionsaufruf (Makro wird zu einer Funktion erweitert), wenn Sie Thread-Code mit den richtigen Flags erstellen.

Timo Geusch
quelle
@ Timo, ja du hast Recht, bitte beziehen Sie sich auf die Diskussion über andere Antworten und lassen Sie uns wissen, wenn etwas fehlt.
Vinit Dhatrak
3

Wir können dies überprüfen, indem wir ein einfaches Programm auf einer Maschine ausführen.

#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);                                                                                       
}

Wenn Sie dieses Programm ausführen, werden in jedem Thread unterschiedliche Adressen für errno angezeigt. Die Ausgabe eines Laufs auf meinem Computer sah folgendermaßen aus:

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 

Beachten Sie, dass die Adresse für alle Threads unterschiedlich ist.

Rajat Paliwal
quelle
Obwohl das Nachschlagen in einer Manpage (oder auf SO) schneller ist, haben Sie sich Zeit genommen, um es zu überprüfen. +1.
Bayou