Errno iş parçacığı için güvenli midir?


175

In errno.hgibi bu değişkeni bildirildi extern int errno;benim sorum bu yüzden, bu güvenli kontrol etmektir errnobazı çağrılar veya çok dişli kodunda kullanım perror () sonra değerini. Bu güvenli bir değişken midir? Değilse, alternatif nedir?

X86 mimarisinde gcc ile linux kullanıyorum.


5
2 tam ters cevap, ilginç !! ;)
vinit dhatrak

Heh, cevabımı tekrar kontrol et. Muhtemelen inandırıcı olacak bir gösteri ekledim. :-)
DigitalRoss

Yanıtlar:


176

Evet, iplik güvenli. Linux'ta genel errno değişkeni iş parçacığına özgüdür. POSIX, errno'nun threadsafe olmasını gerektirir.

Bkz. Http://www.unix.org/whitepapers/reentrant.html

POSIX.1'de, errno harici bir global değişken olarak tanımlanır. Ancak bu tanım çok iş parçacıklı bir ortamda kabul edilemez, çünkü kullanımı belirsiz sonuçlara yol açabilir. Sorun, iki veya daha fazla iş parçacığının hatalarla karşılaşmasıdır ve bunların tümü aynı errno'nun ayarlanmasına neden olur. Bu koşullar altında, bir iş parçacığı zaten başka bir iş parçacığı tarafından güncelleştirildikten sonra errno kontrol edebilirsiniz.

Ortaya çıkan belirsizliği atlatmak için POSIX.1c, errno'yu iş parçacığı başına hata numarasına aşağıdaki gibi erişebilen bir hizmet olarak yeniden tanımlar (ISO / IEC 9945: 1-1996, §2.4):

Bazı fonksiyonlar, errno sembolü ile erişilen bir değişkende hata numarasını sağlayabilir. Errno sembolü, C Standardı tarafından belirtildiği gibi başlık dahil edilerek tanımlanır ... Bir işlemin her iş parçacığı için, errno değeri, diğer çağrıların errno işlev çağrılarından veya atamalarından etkilenmeyecektir.

Ayrıca bkz. Http://linux.die.net/man/3/errno

errno iş parçacığı yerel; bunu bir iş parçacığında ayarlamak başka bir iş parçacığındaki değerini etkilemez.


9
Gerçekten mi? Bunu ne zaman yaptılar? C programlama yaparken errno'ya güvenmek büyük bir sorundu.
Paul Tomblin

7
Dostum, bu benim günümde çok fazla güçlük çekerdi.
Paul Tomblin

4
@vinit: errno aslında bit / errno.h içinde tanımlanır. İçerme dosyasındaki yorumları okuyun. Diyor ki: "bits / errno.h tarafından bir makro olarak tanımlanmadığı sürece" errno 'değişkenini bildirin. GNU'da iş parçacığı değişkeni olduğu durum budur. prototip içermeyen bir işlev bildirimi olacak ve -Wstrict-prototypes uyarısını tetikleyebilir. "
Charles Salvia

2
Linux 2.6 kullanıyorsanız hiçbir şey yapmanıza gerek yoktur. Programlamaya başlayın. :-)
Charles Salvia

3
@vinit dhatrak # if !defined _LIBC || defined _LIBC_REENTRANTNormal programlar derlenirken _LIBC tanımlanmamış olmalıdır . Her neyse, echo #include <errno.h>' | gcc -E -dM -xc - komutunu çalıştırın ve -pthread ile ya da onsuz arasındaki farka bakın. errno #define errno (*__errno_location ())her iki durumda da.
nos

58

Evet


Errno artık basit bir değişken değil, özellikle iş parçacığı açısından güvenli olması için perde arkasında karmaşık bir şey.

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

Tekrar kontrol edebiliriz:

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

12

Errno.h dosyasında bu değişken extern int errno;

C standardının söyledikleri:

Makronun errnobir nesnenin tanımlayıcısı olması gerekmez. Bir işlev çağrısından (örneğin, *errno()) kaynaklanan değiştirilebilir bir değere genişleyebilir .

Genel olarak, errnogeçerli iş parçacığının hata numarasının adresini döndüren bir işlevi çağıran ve ardından başvuruyu geri çeken bir makrodur.

İşte /usr/include/bits/errno.h içinde Linux'ta ne var:

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

Sonunda, bu tür bir kod üretir:

> 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

Birçok Unix sisteminde, derleme iş parçacığı için güvenli -D_REENTRANTolmasını sağlar errno.

Örneğin:

#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
Açıkça kod derlemek zorunda değilsiniz sanırım -D_REENTRANT. Lütfen aynı soru için diğer cevaplarla ilgili tartışmaya başvurun.
vinit dhatrak

3
@Vinit: platformunuza bağlıdır - Linux'ta doğru olabilirsiniz; Muhtemelen kullanarak - Solaris üzerinde, size 199506 veya sonraki sürümü _POSIX_C_SOURCE'a ayarlamış yalnızca doğru olacaktır -D_XOPEN_SOURCE=500ya -D_XOPEN_SOURCE=600. Herkes POSIX ortamının belirtildiğinden emin olmaz - ve sonra -D_REENTRANTpastırmanızı kaydedebilir. Ancak, istenen davranışı elde etmenizi sağlamak için - her platformda - hala dikkatli olmalısınız.
Jonathan Leffler

Hangi standardı (yani: C99, ANSI, vb.) Veya en azından bu özelliği destekleyen derleyicileri (yani: GCC sürümü ve sonrası) ve varsayılan olup olmadığını gösteren bir belge parçası var mı? Teşekkür ederim.
Bulut

C11 standardına veya errno için POSIX 2008'e (2013) bakabilirsiniz . C11 standardı şunları söylüyor: ... ve değeri çeşitli kütüphane işlevleri tarafından pozitif bir hata numarasına ayarlanan errnotip intve iş parçacığı yerel depolama süresine sahip değiştirilebilir bir değere (201) genişliyor . Gerçek bir nesneye erişmek için bir makro tanımı bastırılırsa veya bir program bu adla bir tanımlayıcı tanımlarsa errno, davranış tanımsızdır. [... devamı ...]
Jonathan Leffler

[... devamı ...] Dipnot 201 diyor ki: Makronun errnobir nesnenin tanımlayıcısı olması gerekmez. Bir işlev çağrısından (örneğin, *errno()) kaynaklanan değiştirilebilir bir değere genişleyebilir . Ana metin devam ediyor: İlk iş parçacığında errno değeri program başlangıcında sıfırdır (diğer iş parçacıklarındaki errno'nun başlangıç ​​değeri belirsiz bir değerdir), ancak hiçbir kütüphane işlevi tarafından hiçbir zaman sıfır değerine ayarlanmaz. POSIX, iş parçacıklarını tanımayan C99 standardını kullanır. [... ayrıca devam ediyor ...]
Jonathan Leffler

10

Bu <sys/errno.h>benim Mac bilgisayarımdan:

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

Yani errnoşimdi bir işlevdir __error(). İşlev, iş parçacığı için güvenli olacak şekilde uygulanır.


9

evet , errno man sayfasında ve diğer yanıtlarda açıklandığı gibi, errno bir iş parçacığı yerel değişkenidir.

Ancak , kolayca unutulabilecek aptalca bir detay var. Programlar, sistem çağrısını yürüten herhangi bir sinyal işleyiciye errno'yu kaydetmeli ve geri yüklemelidir. Bunun nedeni, sinyalin, değerinin üzerine yazılabilen işlem dişlerinden biri tarafından işlenmesidir.

Bu nedenle, sinyal işleyicileri errno'yu kaydetmeli ve geri yüklemelidir. Gibi bir şey:

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}

yasak → unuttum sanırım. Bu sistem çağrısı kaydetme / geri yükleme ayrıntısı için bir referans sağlayabilir misiniz?
Craig McQueen

Merhaba Craig, yazım hatasıyla ilgili bilgi için teşekkürler, şimdi düzeltildi. Diğer konuyla ilgili olarak, ne istediğini doğru anladığımdan emin değilim. Sinyal işleyicisindeki errno'yu değiştiren çağrı ne olursa olsun, kesilen aynı iş parçacığı tarafından kullanılan errno'ya müdahale edebilir (örneğin sig_alarm içinde strtol kullanarak). sağ?
marcmagransdeabril

6

Bence cevap "duruma göre değişir". İş parçacığı için güvenli C çalışma zamanı kitaplıkları, doğru bayraklarla iş parçacıklı kod oluşturuyorsanız, genellikle errno'yu bir işlev çağrısı olarak (bir işleve genişleyen makro) uygular.


@Timo, Evet, doğru, lütfen diğer cevap hakkındaki tartışmaya bakın ve eksik bir şey olup olmadığını bildirin.
vinit dhatrak

3

Bir makinede basit bir program çalıştırarak kontrol edebiliriz.

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

Bu programı çalıştırarak her iş parçacığında errno için farklı adresler görebilirsiniz. Makinemdeki bir çalışmanın çıktısı şöyle görünüyordu:

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 

Adresin tüm evreler için farklı olduğuna dikkat edin.


Bir man sayfasına (veya SO'ya) bakmak daha hızlı olmasına rağmen, bunu doğrulamak için zaman ayırdığınızdan hoşlanıyorum. +1.
Bayou
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.