c - सिग्नल हैंडलर में printf का उपयोग करने से कैसे बचें?




linux signals (4)

सिग्नल हैंडलर में printf का उपयोग करने से कैसे बचें?

  1. हमेशा इसे टालें, कहेंगे: सिग्नल हैंडलर में printf() उपयोग न करें।

  2. कम से कम POSIX अनुरूप प्रणाली पर, आप printf() बजाय write(STDOUT_FILENO, ...) उपयोग कर सकते हैं। स्वरूपण आसान नहीं हो सकता है: लिखने या एसिंक-सुरक्षित कार्यों का उपयोग कर सिग्नल हैंडलर से प्रिंट प्रिंट करें

चूंकि printf पुनर्वित्त नहीं है, इसलिए इसे सिग्नल हैंडलर में उपयोग करने के लिए सुरक्षित नहीं होना चाहिए। लेकिन मैंने बहुत सारे उदाहरण कोड देखे हैं जो इस तरह printf का उपयोग करते हैं।

तो मेरा सवाल है: सिग्नल हैंडलर में printf का उपयोग करने से हमें बचने की ज़रूरत है, और क्या एक अनुशंसित प्रतिस्थापन है?


आप कुछ ध्वज चर का उपयोग कर सकते हैं, सिग्नल हैंडलर के अंदर ध्वज सेट कर सकते हैं, और सामान्य ऑपरेशन के दौरान मुख्य () या प्रोग्राम के अन्य भाग में उस ध्वज कॉल printf() फ़ंक्शन के आधार पर सेट कर सकते हैं।

सिग्नल हैंडलर के भीतर से printf जैसे सभी कार्यों को कॉल करना सुरक्षित नहीं है। एक उपयोगी तकनीक एक flag सेट करने के लिए सिग्नल हैंडलर का उपयोग करना है और उसके बाद उस flag को मुख्य प्रोग्राम से जांचें और यदि आवश्यक हो तो एक संदेश प्रिंट करें।

नीचे दिए गए उदाहरण में नोटिस, सिग्नल हैंडलर डिंग () ने एक ध्वज alarm_fired सेट किया है जिसे सिग्नलआरएम पकड़ा गया है और मुख्य समारोह में alarm_fired वैल्यू को सशर्त रूप से प्रिंटफ को सही तरीके से कॉल करने के लिए जांच की जाती है।

static int alarm_fired = 0;
void ding(int sig) // can be called asynchronously
{
  alarm_fired = 1; // set flag
}
int main()
{
    pid_t pid;
    printf("alarm application starting\n");
    pid = fork();
    switch(pid) {
        case -1:
            /* Failure */
            perror("fork failed");
            exit(1);
        case 0:
            /* child */
            sleep(5);
            kill(getppid(), SIGALRM);
            exit(0);
    }
    /* if we get here we are the parent process */
    printf("waiting for alarm to go off\n");
    (void) signal(SIGALRM, ding);
    pause();
    if (alarm_fired)  // check flag to call printf
      printf("Ding!\n");
    printf("done\n");
    exit(0);
}

संदर्भ: लिनक्स प्रोग्रामिंग, चौथा संस्करण शुरू करना , इस पुस्तक में बिल्कुल आपका कोड समझाया गया है (आप क्या चाहते हैं), अध्याय 11: प्रक्रियाएं और सिग्नल, पृष्ठ 484

इसके अतिरिक्त, आपको हैंडलर फ़ंक्शंस लिखने में विशेष देखभाल करने की आवश्यकता है क्योंकि उन्हें असीमित रूप से कहा जा सकता है। यही है, कार्यक्रम में किसी भी बिंदु पर एक हैंडलर को अप्रत्याशित रूप से बुलाया जा सकता है। यदि दो सिग्नल बहुत ही कम अंतराल के दौरान आते हैं, तो एक हैंडलर दूसरे के भीतर दौड़ सकता है। और volatile sigatomic_t घोषित करने के लिए यह बेहतर अभ्यास माना जाता है, इस प्रकार को हमेशा परमाणु रूप से उपयोग किया जाता है, एक चर के उपयोग में बाधा डालने के बारे में अनिश्चितता से बचें। (पढ़ें: विस्तृत विस्तार के लिए परमाणु डेटा एक्सेस और सिग्नल हैंडलिंग )।

सिग्नल हैंडलर को परिभाषित करना पढ़ें: सिग्नल हैंडलर फ़ंक्शन को लिखना सीखें जिसे signal() या sigaction() फ़ंक्शंस के साथ स्थापित किया जा सकता है।
मैन्युअल पेज में अधिकृत फ़ंक्शंस की सूची, सिग्नल हैंडलर के अंदर इस फ़ंक्शन को कॉल करना सुरक्षित है।


डिबगिंग उद्देश्यों के लिए, मैंने एक उपकरण लिखा है जो सत्यापित करता है कि आप वास्तव में केवल async-signal-safe सूची पर फ़ंक्शंस कॉल कर रहे हैं, और सिग्नल संदर्भ में बुलाए गए प्रत्येक असुरक्षित फ़ंक्शन के लिए एक चेतावनी संदेश प्रिंट करते हैं। हालांकि यह सिग्नल संदर्भ से गैर एसिंक-सुरक्षित कार्यों को कॉल करने की इच्छा को हल नहीं करता है, यह कम से कम उन मामलों को खोजने में आपकी सहायता करता है जहां आपने गलती से ऐसा किया है।

स्रोत कोड गिटहब पर है । यह signal/sigaction ओवरलोडिंग द्वारा काम करता है, फिर अस्थायी रूप से असुरक्षित कार्यों की signal/sigaction प्रविष्टियों को अपहरण कर रहा है; इससे असुरक्षित कार्यों को एक रैपर पर रीडायरेक्ट करने का कारण बनता है।


प्राथमिक समस्या यह है कि यदि सिग्नल malloc() या कुछ समान कार्य को बाधित करता है, तो आंतरिक स्थिति अस्थायी रूप से असंगत हो सकती है जबकि यह मुक्त और उपयोग की गई सूची, या अन्य समान संचालन के बीच स्मृति के ब्लॉक को स्थानांतरित कर रही है। यदि सिग्नल हैंडलर में कोड एक फ़ंक्शन को कॉल करता है जो तब malloc() आमंत्रित करता है, तो यह स्मृति प्रबंधन को पूरी तरह से मिटा सकता है।

सिग्नल हैंडलर में आप क्या कर सकते हैं इसके बारे में सी मानक एक रूढ़िवादी दृष्टिकोण लेता है:

आईएसओ / आईईसी 98 99: 2011 §7.14.1.1 signal फ़ंक्शन

¶5 यदि सिग्नल हैंडल को कॉल abort या फ़ंक्शन को raise के परिणाम के अलावा सिग्नल होता है, तो सिग्नल हैंडलर स्थिर या थ्रेड स्टोरेज अवधि के साथ किसी ऑब्जेक्ट को संदर्भित करता है जो असाइन करने के अलावा लॉक-फ्री परमाणु ऑब्जेक्ट नहीं है volatile sig_atomic_t रूप में घोषित ऑब्जेक्ट का मान, या सिग्नल हैंडलर abort फ़ंक्शन के अलावा मानक लाइब्रेरी में किसी भी फ़ंक्शन को कॉल करता है, _Exit फ़ंक्शन, quick_exit फ़ंक्शन, या signal फ़ंक्शन को संबंधित सिग्नल नंबर के बराबर पहले तर्क के साथ संकेत जो हैंडलर के आविष्कार का कारण बनता है। इसके अलावा, यदि signal फ़ंक्शन के लिए ऐसी कोई कॉल SIG_ERR रिटर्न में होती है, तो SIG_ERR का मान अनिश्चित है। 252)

252) यदि किसी सिग्नल को एसिंक्रोनस सिग्नल हैंडलर द्वारा उत्पन्न किया जाता है, तो व्यवहार अपरिभाषित होता है।

सिग्नल हैंडलर में आप क्या कर सकते हैं इसके बारे में पॉज़िक्स बहुत अधिक उदार है।

POSIX 2008 संस्करण में सिग्नल अवधारणाओं का कहना है:

यदि प्रक्रिया बहु-थ्रेडेड है, या यदि प्रक्रिया एकल-थ्रेडेड है और सिग्नल हैंडलर को इसके परिणामस्वरूप अन्य निष्पादित किया गया है:

  • प्रक्रिया को abort() , raise() , kill() , pthread_kill() , या sigqueue() को सिग्नल उत्पन्न करने के लिए जो अवरुद्ध नहीं है

  • लंबित सिग्नल को अनब्लॉक किया जा रहा है और उस कॉल से पहले डिलीवर किया जा रहा है जो इसे वापस लाता है

यदि सिग्नल हैंडलर volatile sig_atomic_t रूप में घोषित ऑब्जेक्ट को मान निर्दिष्ट करने के अलावा स्थिर स्टोरेज अवधि के साथ errno अलावा किसी अन्य ऑब्जेक्ट को संदर्भित करता है, या सिग्नल हैंडलर इस मानक में परिभाषित किसी भी फ़ंक्शन को किसी एक के अलावा परिभाषित करता है निम्नलिखित तालिका में सूचीबद्ध कार्यों।

निम्न तालिका फ़ंक्शंस के एक सेट को परिभाषित करती है जो एसिंक-सिग्नल-सुरक्षित होगी। इसलिए, अनुप्रयोग सिग्नल-कैचिंग फ़ंक्शंस से प्रतिबंध के बिना उन्हें आवेदक कर सकते हैं:

_Exit()             fexecve()           posix_trace_event() sigprocmask()
_exit()             fork()              pselect()           sigqueue()
…
fcntl()             pipe()              sigpause()          write()
fdatasync()         poll()              sigpending()

उपरोक्त तालिका में नहीं सभी कार्यों को सिग्नल के संबंध में असुरक्षित माना जाता है। सिग्नल की उपस्थिति में, POSIX.1-2008 की इस मात्रा द्वारा परिभाषित सभी फ़ंक्शंस एक सिग्नल-कैचिंग फ़ंक्शन द्वारा सिग्नल-कैचिंग फ़ंक्शन से कॉल या बाधित होने पर परिभाषित किया जाएगा: जब एक सिग्नल एक असुरक्षित फ़ंक्शन और सिग्नल- पकड़ने का कार्य एक असुरक्षित कार्य कहता है, व्यवहार अपरिभाषित है।

ऑपरेशंस जो errno और ऑपरेशंस का मान प्राप्त करते हैं जो errno को मान निर्दिष्ट करते हैं, वे एसिंक-सिग्नल-सुरक्षित होंगे।

जब किसी सिग्नल को थ्रेड पर डिलीवर किया जाता है, तो उस सिग्नल की क्रिया क्रमशः समाप्ति, रोक या जारी रखती है, तो पूरी प्रक्रिया को क्रमशः समाप्त, बंद या जारी रखा जाएगा।

हालांकि, कार्यों की printf() परिवार विशेष रूप से उस सूची से अनुपस्थित है और सिग्नल हैंडलर से सुरक्षित रूप से नहीं कहा जा सकता है।

POSIX 2016 अद्यतन सुरक्षित कार्यों की सूची को विस्तारित करता है, विशेष रूप से, <string.h> से फ़ंक्शंस की एक बड़ी संख्या, जो विशेष रूप से मूल्यवान जोड़ (या विशेष रूप से निराशाजनक निरीक्षण था) शामिल है। सूची अब है:

_Exit()              getppid()            sendmsg()            tcgetpgrp()
_exit()              getsockname()        sendto()             tcsendbreak()
abort()              getsockopt()         setgid()             tcsetattr()
accept()             getuid()             setpgid()            tcsetpgrp()
access()             htonl()              setsid()             time()
aio_error()          htons()              setsockopt()         timer_getoverrun()
aio_return()         kill()               setuid()             timer_gettime()
aio_suspend()        link()               shutdown()           timer_settime()
alarm()              linkat()             sigaction()          times()
bind()               listen()             sigaddset()          umask()
cfgetispeed()        longjmp()            sigdelset()          uname()
cfgetospeed()        lseek()              sigemptyset()        unlink()
cfsetispeed()        lstat()              sigfillset()         unlinkat()
cfsetospeed()        memccpy()            sigismember()        utime()
chdir()              memchr()             siglongjmp()         utimensat()
chmod()              memcmp()             signal()             utimes()
chown()              memcpy()             sigpause()           wait()
clock_gettime()      memmove()            sigpending()         waitpid()
close()              memset()             sigprocmask()        wcpcpy()
connect()            mkdir()              sigqueue()           wcpncpy()
creat()              mkdirat()            sigset()             wcscat()
dup()                mkfifo()             sigsuspend()         wcschr()
dup2()               mkfifoat()           sleep()              wcscmp()
execl()              mknod()              sockatmark()         wcscpy()
execle()             mknodat()            socket()             wcscspn()
execv()              ntohl()              socketpair()         wcslen()
execve()             ntohs()              stat()               wcsncat()
faccessat()          open()               stpcpy()             wcsncmp()
fchdir()             openat()             stpncpy()            wcsncpy()
fchmod()             pause()              strcat()             wcsnlen()
fchmodat()           pipe()               strchr()             wcspbrk()
fchown()             poll()               strcmp()             wcsrchr()
fchownat()           posix_trace_event()  strcpy()             wcsspn()
fcntl()              pselect()            strcspn()            wcsstr()
fdatasync()          pthread_kill()       strlen()             wcstok()
fexecve()            pthread_self()       strncat()            wmemchr()
ffs()                pthread_sigmask()    strncmp()            wmemcmp()
fork()               raise()              strncpy()            wmemcpy()
fstat()              read()               strnlen()            wmemmove()
fstatat()            readlink()           strpbrk()            wmemset()
fsync()              readlinkat()         strrchr()            write()
ftruncate()          recv()               strspn()
futimens()           recvfrom()           strstr()
getegid()            recvmsg()            strtok_r()
geteuid()            rename()             symlink()
getgid()             renameat()           symlinkat()
getgroups()          rmdir()              tcdrain()
getpeername()        select()             tcflow()
getpgrp()            sem_post()           tcflush()
getpid()             send()               tcgetattr()

नतीजतन, आप या तो printf() et al द्वारा प्रदान किए गए स्वरूपण समर्थन के बिना write() का उपयोग कर समाप्त करते हैं, या आप अपने कोड में उपयुक्त स्थानों में परीक्षण (समय-समय पर) ध्वज सेट करते हैं। इस तकनीक का ग्रिशेश चौहान के answer में अत्यधिक प्रदर्शन किया गया है।

मानक सी कार्यों और सिग्नल सुरक्षा

chqrlie एक दिलचस्प सवाल asks , जिसके लिए मेरे पास आंशिक उत्तर से अधिक नहीं है:

<ctype.h> से अधिकांश स्ट्रिंग फ़ंक्शंस या <ctype.h> से वर्ण वर्ग फ़ंक्शंस कैसे आते हैं और कई और सी मानक लाइब्रेरी फ़ंक्शंस उपरोक्त सूची में नहीं हैं? सिग्नल हैंडलर से कॉल करने के लिए असुरक्षित strlen() असुरक्षित बनाने के लिए एक कार्यान्वयन को जानबूझकर बुराई की आवश्यकता होगी।

<string.h> में कई कार्यों के लिए, यह देखना मुश्किल है कि उन्हें एसिंक-सिग्नल सुरक्षित क्यों घोषित नहीं किया गया था, और मैं सहमत हूं कि strlen() एक प्रमुख उदाहरण है, strchr() , strchr() के साथ , आदि। दूसरी तरफ, strtok() , strcoll() और strxfrm() जैसे अन्य फ़ंक्शन अपेक्षाकृत जटिल हैं और strxfrm() -सिग्नल सुरक्षित होने की संभावना नहीं है। चूंकि strtok() कॉल के बीच स्थिति को बरकरार रखता है, और सिग्नल हैंडलर आसानी से यह नहीं बता सकता कि strtok() का उपयोग करने वाले कोड का कुछ हिस्सा गड़बड़ हो जाएगा। strcoll() और strxfrm() फ़ंक्शन लोकेल-संवेदनशील डेटा के साथ काम करते हैं, और लोकेल लोड करने से सभी प्रकार की राज्य सेटिंग शामिल होती है।

<ctype.h> से फ़ंक्शंस (मैक्रोज़) सभी लोकेल-संवेदनशील हैं, और इसलिए strcoll() और strxfrm() के समान मुद्दों में भाग ले सकते हैं।

मुझे यह देखना मुश्किल लगता है कि <math.h> से गणितीय फ़ंक्शन <math.h> -सिग्नल सुरक्षित क्यों नहीं हैं, जब तक ऐसा न हो क्योंकि वे एक SIGFPE (फ़्लोटिंग पॉइंट अपवाद) से प्रभावित हो सकते हैं, हालांकि केवल एक बार मुझे उनमें से एक दिखाई देता है इन दिनों शून्य से पूर्णांक विभाजन के लिए है। इसी तरह की अनिश्चितता <complex.h> , <fenv.h> और <tgmath.h> से उत्पन्न होती है।

उदाहरण के लिए <stdlib.h> में से कुछ कार्यों को छूट दी जा सकती है - abs() । अन्य विशेष रूप से समस्याग्रस्त हैं: malloc() और परिवार प्रमुख उदाहरण हैं।

पॉज़िक्स पर्यावरण में उपयोग किए जाने वाले मानक सी (2011) में अन्य शीर्षकों के लिए एक समान मूल्यांकन किया जा सकता है। (मानक सी इतनी सीमित है कि शुद्ध मानक सी पर्यावरण में उनका विश्लेषण करने में कोई रूचि नहीं है।) 'लोकेल-आश्रित' चिह्नित चिह्नित लोग असुरक्षित हैं क्योंकि स्थानीय लोगों में हेरफेर करने के लिए स्मृति आवंटन आदि की आवश्यकता हो सकती है।

  • <assert.h> - शायद सुरक्षित नहीं है
  • <complex.h> - संभवतः सुरक्षित
  • <ctype.h> - सुरक्षित नहीं है
  • <errno.h> - सुरक्षित
  • <fenv.h> - शायद सुरक्षित नहीं है
  • <float.h> - कोई फ़ंक्शन नहीं
  • <inttypes.h> - लोकेल-संवेदनशील फ़ंक्शन (असुरक्षित)
  • <iso646.h> - कोई फ़ंक्शन नहीं
  • <limits.h> - कोई फ़ंक्शन नहीं
  • <locale.h> - लोकेल-संवेदनशील फ़ंक्शन (असुरक्षित)
  • <math.h> - संभवतः सुरक्षित
  • <setjmp.h> - सुरक्षित नहीं है
  • <signal.h> - अनुमत
  • <stdalign.h> - कोई फ़ंक्शन नहीं
  • <stdarg.h> - कोई फ़ंक्शन नहीं
  • <stdatomic.h> - संभवतः सुरक्षित, शायद सुरक्षित नहीं है
  • <stdbool.h> - कोई फ़ंक्शन नहीं
  • <stddef.h> - कोई फ़ंक्शन नहीं
  • <stdint.h> - कोई फ़ंक्शन नहीं
  • <stdio.h> - सुरक्षित नहीं है
  • <stdlib.h> - सभी सुरक्षित नहीं हैं (कुछ की अनुमति है; अन्य नहीं हैं)
  • <stdnoreturn.h> - कोई फ़ंक्शन नहीं
  • <string.h> - सभी सुरक्षित नहीं
  • <tgmath.h> - संभवतः सुरक्षित
  • <threads.h> - शायद सुरक्षित नहीं है
  • <time.h> - लोकेल-निर्भर (लेकिन time() स्पष्ट रूप से अनुमति है)
  • <uchar.h> - लोकेल-निर्भर
  • <wchar.h> - लोकेल-निर्भर
  • <wctype.h> - लोकेल-निर्भर

पॉज़िक्स हेडर का विश्लेषण करना मुश्किल होगा ... उनमें से बहुत सारे हैं, और कुछ फ़ंक्शन सुरक्षित हो सकते हैं लेकिन कई नहीं होंगे ... लेकिन यह भी आसान है क्योंकि पॉज़िक्स कहता है कि कौन से फ़ंक्शन एसिंक-सिग्नल सुरक्षित हैं (उनमें से कई नहीं)। ध्यान दें कि <pthread.h> जैसे शीर्षलेख में तीन सुरक्षित फ़ंक्शन और कई असुरक्षित फ़ंक्शन हैं।

एनबी: पीओएसईक्स पर्यावरण में सी कार्यों और शीर्षकों के लगभग सभी आकलन अर्ध-शिक्षित अनुमान हैं। यह मानकों के शरीर से एक निश्चित बयान नहीं है।





signals