Ist errno Thread-sicher?

Lesezeit: 8 Minuten

Benutzeravatar von Vinit Dhatrak
Vinit Dhatrak

Im errno.hwird diese Variable als deklariert extern int errno; Also meine Frage ist, ist es sicher zu überprüfen errno Wert nach einigen Aufrufen oder verwenden Sie perror() in Multithread-Code. Ist das eine threadsichere Variable? Wenn nicht, was ist dann die Alternative?

Ich verwende Linux mit gcc auf x86-Architektur.

  • Mögliches Duplikat von Gibt es eine Möglichkeit, errno sicher in einer Multithread-Anwendung zu verwenden?

    – jww

    16. Juni 2018 um 15:00 Uhr


Benutzeravatar von Charles Salvia
Karl Salvia

Ja, es ist threadsicher. Unter Linux ist die globale errno-Variable Thread-spezifisch. POSIX erfordert, dass errno threadsicher ist.

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

In POSIX.1 ist 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 zwei oder mehr Threads auf Fehler stoßen können, die alle dazu führen, dass dieselbe Fehlernummer gesetzt wird. Unter diesen Umständen kann es vorkommen, dass ein Thread errno überprüft, nachdem er bereits von einem anderen Thread aktualisiert wurde.

Um den daraus resultierenden Nichtdeterminismus zu umgehen, definiert POSIX.1c errno als 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 durch Einschließen des Headers definiert, wie vom C-Standard spezifiziert … Für jeden Thread eines Prozesses darf der Wert von errno nicht durch Funktionsaufrufe oder Zuweisungen an errno durch andere Threads beeinflusst werden.

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

errno ist Thread-lokal; Wenn Sie es in einem Thread festlegen, wirkt sich dies nicht auf seinen Wert in einem anderen Thread aus.

  • Wirklich? Wann haben sie das gemacht? Als ich mit C programmiert habe, war es ein großes Problem, errno zu vertrauen.

    – Paul Tomblin

    7. November 2009 um 19:43 Uhr

  • @vinit: errno ist tatsächlich in bits/errno.h definiert. Lesen Sie die Kommentare in der Include-Datei. Dort heißt es: “Deklarieren Sie die `errno’-Variable, es sei denn, sie ist von bits/errno.h als Makro definiert. Dies ist der Fall in GNU, wo es sich um eine Per-Thread-Variable handelt. Diese Neudeklaration mit dem Makro funktioniert immer noch, aber es wird eine Funktionsdeklaration ohne Prototyp sein und kann eine -Wstrict-prototypes-Warnung auslösen.”

    – Karl Salvia

    7. November 2009 um 19:53 Uhr

  • Wenn Sie Linux 2.6 verwenden, müssen Sie nichts tun. Fangen Sie einfach an zu programmieren. 🙂

    – Karl Salvia

    7. November 2009 um 19:59 Uhr

  • @vinit dhatrak Es sollte sein # if !defined _LIBC || defined _LIBC_REENTRANT , _LIBC ist beim Kompilieren normaler Programme nicht definiert. Wie auch immer, führe 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

    28. November 2011 um 15:47 Uhr


  • Erwägen Sie, einen Auszug aus dem C11-Standard hinzuzufügen (nicht früher), 7.5 Errors <errno.h>: Es ist auch dort garantiert Thread-sicher.

    – Deduplizierer

    23. Juli 2014 um 0:08 Uhr

Benutzeravatar von DigitalRoss
DigitalRoss

Ja


Errno ist keine einfache Variable mehr, sondern etwas Komplexes hinter den Kulissen, speziell um Thread-sicher zu sein.

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

Wir können Folgendes überprüfen:

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

Benutzeravatar von Bastien Léonard
Bastien Leonard

In errno.h ist diese Variable als extern int errno deklariert;

Hier ist, was der C-Standard sagt:

Das Makro errno muss nicht der Bezeichner eines Objekts sein. Es kann zu einem änderbaren Lvalue erweitert werden, der sich aus einem Funktionsaufruf ergibt (z. B. *errno()).

Allgemein, errno ist ein Makro, das eine Funktion aufruft, die die Adresse der Fehlernummer für den aktuellen Thread zurückgibt, und sie 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

Benutzeravatar von marcmagransdeabril
marcmagransdeabril

Jawohlwie es in der erklärt wird errno Manpage und der andere antwortet, errno ist eine lokale Variable des Threads.

Jedoch, gibt es 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;
}

Benutzeravatar von vy32
vy32

Das ist von <sys/errno.h> Auf meinem Mac:

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

So errno ist jetzt eine Funktion __error(). Die Funktion ist Thread-sicher implementiert.

Benutzeravatar von Ky -
Ky –

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, können Sie in jedem Thread unterschiedliche Adressen für errno sehen. Die Ausgabe eines Laufs auf meinem Computer sah so 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.

Benutzeravatar von Jonathan Leffler
Jonathan Leffler

Auf vielen Unix-Systemen kompilieren mit -D_REENTRANT versichert dass errno ist Thread-sicher.

Zum Beispiel:

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

  • Ich denke, Sie müssen Code nicht explizit mit kompilieren -D_REENTRANT. Bitte beziehen Sie sich auf die Diskussion zu anderen Antworten für dieselbe Frage.

    – Vinit Dhatrak

    7. November 2009 um 20:12 Uhr

  • @Vinit: Es hängt von Ihrer Plattform ab – unter Linux haben Sie möglicherweise Recht; Unter Solaris wären Sie nur dann richtig, wenn Sie _POSIX_C_SOURCE auf 199506 oder eine neuere Version gesetzt haben – wahrscheinlich durch Verwendung von -D_XOPEN_SOURCE=500 oder -D_XOPEN_SOURCE=600. Nicht jeder kümmert sich darum, dass die POSIX-Umgebung angegeben wird – und dann -D_REENTRANT kann Ihren Speck retten. Aber Sie müssen immer noch vorsichtig sein – auf jeder Plattform – um sicherzustellen, dass Sie das gewünschte Verhalten erhalten.

    – Jonathan Leffler

    7. November 2009 um 20:44 Uhr

  • Gibt es eine Dokumentation, die angibt, welcher Standard (dh: C99, ANSI usw.) oder zumindest welche Compiler (dh: GCC-Version und höher) diese Funktion unterstützen und ob es sich um eine Standardeinstellung handelt oder nicht? Vielen Dank.

    – Wolke

    25. August 2014 um 15:41 Uhr


  • Sie können sich den C11-Standard oder POSIX 2008 (2013) ansehen Fehlernr. Der C11-Standard sagt: … und errno die zu einem modifizierbaren lvalue (201) mit Typ erweitert wird int und lokale Speicherdauer des Threads, deren Wert von mehreren Bibliotheksfunktionen auf eine positive Fehlerzahl gesetzt wird. Wenn eine Makrodefinition unterdrückt wird, um auf ein tatsächliches Objekt zuzugreifen, oder ein Programm einen Bezeichner mit dem Namen definiert errnoist das Verhalten undefiniert. […continued…]

    – Jonathan Leffler

    25. August 2014 um 16:26 Uhr

  • […continuation…] Fußnote 201 sagt: Das Makro errno muss nicht der Bezeichner eines Objekts sein. Es kann zu einem änderbaren Lvalue erweitert werden, der sich aus einem Funktionsaufruf ergibt (z. B. *errno()). Der Haupttext fährt fort: Der Wert von errno im anfänglichen Thread ist beim Programmstart null (der anfängliche Wert von errno in anderen Threads ist ein unbestimmter Wert), wird jedoch niemals von einer Bibliotheksfunktion auf null gesetzt. POSIX verwendet den C99-Standard, der keine Threads erkennt. […also continued…]

    – Jonathan Leffler

    25. August 2014 um 16:30 Uhr

1425850cookie-checkIst errno Thread-sicher?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy