क्या इरानो धागा सुरक्षित है?


175

इसमें errno.h, इस चर को घोषित किया गया extern int errno;है, इसलिए मेरा प्रश्न है, क्या errnoबहु-थ्रेड कोड में कुछ कॉल या पेरर () का उपयोग करने के बाद मूल्य की जांच करना सुरक्षित है । क्या यह एक धागा सुरक्षित चर है? यदि नहीं, तो विकल्प क्या है?

मैं x86 आर्किटेक्चर पर gcc के साथ linux का उपयोग कर रहा हूँ।


5
2 सटीक विपरीत उत्तर, दिलचस्प !! ;)
विनीत धात्रक

हे, मेरे जवाब को फिर से पढ़ो। मैंने एक प्रदर्शन जोड़ा है जो शायद आश्वस्त करेगा। :-)
DigitalRoss

जवाबों:


176

हाँ, यह धागा सुरक्षित है। लिनक्स पर, वैश्विक त्रुटिपूर्ण चर थ्रेड-विशिष्ट है। POSIX के लिए आवश्यक है कि इरान थ्रेडसेफ़ हो।

Http://www.unix.org/whitepapers/reentrant.html देखें

POSIX.1 में, अशुद्ध को बाहरी वैश्विक चर के रूप में परिभाषित किया गया है। लेकिन यह परिभाषा एक बहुआयामी वातावरण में अस्वीकार्य है, क्योंकि इसके उपयोग के परिणामस्वरूप nondeterministic परिणाम हो सकते हैं। समस्या यह है कि दो या अधिक धागे त्रुटियों का सामना कर सकते हैं, जिससे सभी समान त्रुटिपूर्ण सेट हो सकते हैं। इन परिस्थितियों में, एक थ्रेड गलत हो सकता है जाँच करने के बाद यह पहले से ही एक और थ्रेड द्वारा अद्यतन किया गया है।

परिणामी नंदत्ववाद को दरकिनार करने के लिए, POSIX.1c एक ऐसी सेवा के रूप में त्रुटिपूर्ण को फिर से परिभाषित करता है जो प्रति-थ्रेड त्रुटि संख्या को निम्नानुसार एक्सेस कर सकती है (ISO / IEC 9945: 1-1996, §2.4):

कुछ कार्य प्रतीक ग़लती के माध्यम से पहुँचा चर में त्रुटि संख्या प्रदान कर सकते हैं। प्रतीक इरिनो को हेडर सहित परिभाषित किया गया है, जैसा कि सी स्टैंडर्ड द्वारा निर्दिष्ट किया गया है ... प्रक्रिया के प्रत्येक थ्रेड के लिए, अर्नो का मान फ़ंक्शन या असाइनमेंट को अन्य थ्रेड्स द्वारा गलत करने से प्रभावित नहीं होगा।

इसके अलावा http://linux.die.net/man/3/errno देखें

इरनो थ्रेड-लोकल है; इसे एक धागे में सेट करने से किसी अन्य धागे में इसका मूल्य प्रभावित नहीं होता है।


9
वास्तव में? उन्होंने ऐसा कब किया? जब मैं सी प्रोग्रामिंग कर रहा था, तो इरनो पर भरोसा करना एक बड़ी समस्या थी।
पॉल टॉम्बलिन

7
यार, यह मेरे दिन में बहुत सारे झंझट से बचा सकता है।
पॉल टॉम्बलिन

4
@vinit: इरनो वास्तव में बिट्स / इरनो.ह में परिभाषित किया गया है। शामिल फ़ाइल में टिप्पणी पढ़ें। यह कहता है: "ग़लती से 'चर को घोषित करें, जब तक कि इसे बिट्स / इरनो द्वारा मैक्रो के रूप में परिभाषित नहीं किया जाता है। जीएनयू में यह मामला है, जहां यह एक प्रति-सूत्र चर है। मैक्रो का उपयोग करके यह पुनर्वितरण अभी भी काम करता है, लेकिन यह एक प्रोटोटाइप के बिना एक फ़ंक्शन घोषणा होगी और ट्रिगर हो सकता है -स्ट्रिक्ट-प्रोटोटाइप चेतावनी। "
चार्ल्स साल्विया

2
यदि आप लिनक्स 2.6 का उपयोग कर रहे हैं, तो आपको कुछ भी करने की आवश्यकता नहीं है। बस प्रोग्रामिंग शुरू करें। :-)
चार्ल्स सल्विया

3
@vinit dhatrak # if !defined _LIBC || defined _LIBC_REENTRANTसामान्य कार्यक्रमों को संकलित करते समय _LIBC को परिभाषित नहीं किया जाना चाहिए । वैसे भी, प्रतिध्वनि चलाएं #include <errno.h>' | gcc -E -dM -xc - और बिना -थ्रेड के अंतर को देखें। ग़लती #define errno (*__errno_location ())दोनों मामलों में है।
nos

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किसी ऑब्जेक्ट का पहचानकर्ता नहीं होना चाहिए। यह एक फ़ंक्शन कॉल (उदाहरण के लिए *errno()) से उत्पन्न एक परिवर्तनीय अंतराल तक फैल सकता है ।

आम तौर पर, errnoएक मैक्रो होता है जो एक फ़ंक्शन को वर्तमान थ्रेड के लिए त्रुटि संख्या के पते को वापस करता है, फिर इसे संदर्भित करता है।

यहाँ मेरे पास लिनक्स पर, /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

कई यूनिक्स प्रणालियों पर, -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। कृपया उसी प्रश्न के अन्य उत्तर पर चर्चा का संदर्भ लें।
विनीत धात्रक '

3
@ विनिट: यह आपके प्लेटफ़ॉर्म पर निर्भर करता है - लिनक्स पर, आप सही हो सकते हैं; सोलारिस पर, आप केवल तभी सही होंगे जब आपने _POSIX_C_SOURCE को 199506 या बाद के संस्करण में सेट किया है - संभवतः उपयोग करके -D_XOPEN_SOURCE=500या -D_XOPEN_SOURCE=600। हर कोई यह सुनिश्चित करने के लिए परेशान नहीं करता है कि POSIX वातावरण निर्दिष्ट है - और फिर -D_REENTRANTअपने बेकन को बचा सकता है। लेकिन आपको अभी भी सावधान रहना होगा - प्रत्येक मंच पर - यह सुनिश्चित करने के लिए कि आपको वांछित व्यवहार प्राप्त हो।
जोनाथन लेफ़लर

क्या प्रलेखन का एक टुकड़ा है जो इंगित करता है कि क्या मानक (यानी: C99, ANSI, आदि) या कम से कम कौन से संकलक (यानी: जीसीसी संस्करण और आगे) जो इस सुविधा का समर्थन करते हैं, और क्या यह डिफ़ॉल्ट है या नहीं? धन्यवाद।
क्लाउड

आप C11 मानक या POSIX 2008 (2013) में इरनो के लिए देख सकते हैं । C11 मानक कहता है: ... और errnoजो एक परिवर्तनीय अंतराल (201) में फैलता है, जिसमें intस्थानीय भंडारण अवधि टाइप और थ्रेड होती है, जिसका मान कई लाइब्रेरी फ़ंक्शंस द्वारा सकारात्मक त्रुटि संख्या पर सेट किया जाता है। यदि किसी वास्तविक वस्तु तक पहुंचने के लिए एक स्थूल परिभाषा को दबा दिया जाता है, या कोई प्रोग्राम नाम के साथ एक पहचानकर्ता को परिभाषित करता है errno, तो व्यवहार अपरिभाषित है। [... जारी रखा ...]
जोनाथन लेफ्लर

[... निरंतरता ...] फुटनोट 201 कहता है: स्थूल errnoको किसी वस्तु की पहचानकर्ता होने की आवश्यकता नहीं है। यह एक फ़ंक्शन कॉल (उदाहरण के लिए *errno()) से उत्पन्न एक परिवर्तनीय अंतराल तक फैल सकता है । मुख्य पाठ जारी है: प्रोग्राम स्टार्टअप पर प्रारंभिक थ्रेड में इरनो का मूल्य शून्य है (अन्य थ्रेड में इरनो का प्रारंभिक मूल्य एक अनिश्चित मूल्य है), लेकिन किसी भी लाइब्रेरी फ़ंक्शन द्वारा कभी भी शून्य पर सेट नहीं किया जाता है। POSIX C99 मानक का उपयोग करता है जो थ्रेड्स को नहीं पहचानता है। [... भी जारी रखा ...]
जोनाथन लेफ़लर

10

यह <sys/errno.h>मेरे मैक पर से है :

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

तो errnoअब एक समारोह है __error()। फ़ंक्शन को थ्रेड-सुरक्षित होने के लिए कार्यान्वित किया जाता है।


9

हाँ , जैसा कि इसे गलत आदमी पृष्ठ और अन्य उत्तरों में समझाया गया है , ग़लती एक धागा स्थानीय चर है।

हालाँकि , एक मूर्खतापूर्ण विवरण है जिसे आसानी से भुलाया जा सकता है। सिस्टम कॉल को निष्पादित करने वाले किसी भी सिग्नल हैंडलर पर इरनो को सहेजना और पुनर्स्थापित करना चाहिए। ऐसा इसलिए है क्योंकि सिग्नल को प्रक्रिया के थ्रेड्स में से एक द्वारा नियंत्रित किया जाएगा जो इसके मूल्य को अधिलेखित कर सकता है।

इसलिए, सिग्नल हैंडलर्स को इरनो को सहेजना और पुनर्स्थापित करना चाहिए। कुछ इस तरह:

void sig_alarm(int signo)
{
 int errno_save;

 errno_save = errno;

 //whatever with a system call

 errno = errno_save;
}

निषिद्ध → भूल गया मैं अनुमान। क्या आप इस सिस्टम कॉल सेव / रिस्टोर डिटेल के लिए एक संदर्भ प्रदान कर सकते हैं?
क्रेग मैकक्वीन

हाय क्रेग, टाइपो के बारे में जानकारी के लिए धन्यवाद, अब सही किया गया है। दूसरे मुद्दे के बारे में, मुझे यकीन नहीं है कि अगर मैं सही ढंग से समझ गया कि आप क्या पूछ रहे हैं। जो भी कॉल सिग्नल हैंडलर में इरनो को संशोधित करता है वह उसी थ्रेड द्वारा उपयोग किए जा रहे इरनो के साथ हस्तक्षेप कर सकता था जो बाधित था (उदाहरण के लिए sig_alarm के अंदर स्ट्रैटोल का उपयोग करके)। सही?
marcmagransdeabril

6

मुझे लगता है कि उत्तर "यह निर्भर करता है" है। यदि आप सही झंडे के साथ थ्रेडेड कोड का निर्माण कर रहे हैं तो थ्रेड-सेफ सी रनटाइम लाइब्रेरी आमतौर पर फंक्शन कॉल के रूप में इम्प्लानो (फंक्शन का विस्तार करने वाले मैक्रो) के रूप में कार्यान्वित करते हैं।


@ टिमो, हाँ, ठीक है, कृपया अन्य उत्तर पर चर्चा का संदर्भ लें और यह बताएं कि क्या कुछ गायब है।
विनीत धात्रक

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

इस प्रोग्राम को चलाना और आप प्रत्येक थ्रेड में ग़लती के लिए अलग-अलग पते देख सकते हैं। मेरी मशीन पर एक रन का आउटपुट इस तरह दिखता है: -

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 

ध्यान दें कि सभी थ्रेड्स के लिए पता अलग है।


यद्यपि इसे किसी पुरुष पृष्ठ (या SO पर) में देखना अधिक तेज़ है, मुझे पसंद है कि आपने इसे सत्यापित करने के लिए समय दिया है। +1।
बेउ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.